From 406abd7c4df1ace2cd3e4e17159e8941a2e8c0c4 Mon Sep 17 00:00:00 2001 From: Tristan Zur Date: Wed, 10 Jun 2015 20:55:53 +0200 Subject: Initial import --- .gitignore | 1 + .htaccess | 82 + LICENSE | 340 + README | 85 + application/Bootstrap.php | 67 + application/config/config.php | 156 + bin/.htaccess | 8 + bin/README | 5 + index.php | 116 + installer/cli.php | 121 + installer/database_config.php | 46 + installer/index.php | 40 + installer/init_var.php | 12 + installer/install.css | 92 + installer/install.sql | 433 ++ installer/installer.php | 270 + installer/views/already_installed.html.php | 5 + installer/views/db_not_empty.html.php | 8 + installer/views/environment_errors.html.php | 20 + installer/views/get_db_info.html.php | 89 + installer/views/install.html.php | 24 + installer/views/invalid_db_info.html.php | 6 + installer/views/invalid_db_version.html.php | 5 + installer/views/missing_db.html.php | 13 + installer/views/oops.html.php | 10 + installer/views/success.html.php | 23 + installer/web.php | 94 + lib/flowplayer.controls.swf.php | 31 + lib/flowplayer.js | 228 + lib/flowplayer.pseudostreaming-byterange.swf.php | 31 + lib/flowplayer.pseudostreaming.swf.php | 31 + lib/flowplayer.swf.php | 47 + lib/gallery.ajax.js | 15 + lib/gallery.common.js | 253 + lib/gallery.dialog.js | 221 + lib/gallery.in_place_edit.js | 75 + lib/gallery.panel.js | 100 + lib/gallery.show_full_size.js | 58 + lib/images/Galerie-Logo.png | Bin 0 -> 4047 bytes lib/images/apple-touch-icon.png | Bin 0 -> 4441 bytes lib/images/favicon.ico | Bin 0 -> 1150 bytes lib/images/logo.png | Bin 0 -> 10222 bytes lib/jquery-ui.js | 328 + lib/jquery.MultiFile.js | 11 + lib/jquery.autocomplete.css | 49 + lib/jquery.autocomplete.js | 13 + lib/jquery.cookie.js | 96 + lib/jquery.form.js | 645 ++ lib/jquery.jeditable.js | 32 + lib/jquery.js | 19 + lib/jquery.localscroll.js | 9 + lib/jquery.scrollTo.js | 11 + lib/json2-min.js | 39 + lib/superfish/css/superfish.css | 136 + lib/superfish/images/arrows-ffffff-rtl.png | Bin 0 -> 378 bytes lib/superfish/images/arrows-ffffff.png | Bin 0 -> 244 bytes lib/superfish/images/shadow.png | Bin 0 -> 1698 bytes lib/superfish/js/superfish.js | 121 + lib/swfobject.js | 4 + lib/uploadify/cancel.png | Bin 0 -> 603 bytes lib/uploadify/jquery.uploadify.min.js | 37 + lib/uploadify/uploadify.allglyphs.swf.php | 47 + lib/uploadify/uploadify.css | 58 + lib/uploadify/uploadify.fla | Bin 0 -> 125952 bytes lib/uploadify/uploadify.swf.php | 47 + lib/yui/base-min.css | 7 + lib/yui/reset-fonts-grids.css | 7 + modules/akismet/controllers/admin_akismet.php | 67 + modules/akismet/helpers/akismet.php | 193 + modules/akismet/helpers/akismet_event.php | 70 + modules/akismet/helpers/akismet_installer.php | 28 + modules/akismet/module.info | 7 + modules/akismet/views/admin_akismet.html.php | 18 + modules/akismet/views/admin_akismet_stats.html.php | 11 + modules/calendarview/controllers/calendarview.php | 296 + modules/calendarview/css/calendarview_calendar.css | 59 + modules/calendarview/css/calendarview_menu.css | 3 + modules/calendarview/helpers/calendarview.php | 48 + .../calendarview/helpers/calendarview_block.php | 72 + .../calendarview/helpers/calendarview_event.php | 99 + .../helpers/calendarview_installer.php | 40 + .../calendarview/helpers/calendarview_theme.php | 25 + .../calendarview/images/ico-view-calendarview.png | Bin 0 -> 4191 bytes modules/calendarview/libraries/PHPCalendar.php | 87 + modules/calendarview/module.info | 7 + .../views/calendarview_sidebar.html.php | 9 + .../calendarview/views/calendarview_year.html.php | 95 + .../views/user_profile_calendarview.html.php | 86 + modules/comment/controllers/admin_comments.php | 60 + .../comment/controllers/admin_manage_comments.php | 144 + modules/comment/controllers/comments.php | 81 + modules/comment/css/comment.css | 45 + modules/comment/helpers/comment.php | 71 + modules/comment/helpers/comment_block.php | 39 + modules/comment/helpers/comment_event.php | 97 + modules/comment/helpers/comment_installer.php | 118 + modules/comment/helpers/comment_rest.php | 74 + modules/comment/helpers/comment_rss.php | 92 + modules/comment/helpers/comment_theme.php | 46 + modules/comment/helpers/comments_rest.php | 62 + modules/comment/helpers/item_comments_rest.php | 50 + modules/comment/js/comment.js | 45 + modules/comment/models/comment.php | 194 + modules/comment/module.info | 7 + .../views/admin_block_recent_comments.html.php | 23 + modules/comment/views/admin_comments.html.php | 7 + .../comment/views/admin_manage_comments.html.php | 46 + .../views/admin_manage_comments_queue.html.php | 157 + modules/comment/views/comment.html.php | 25 + modules/comment/views/comment.mrss.php | 43 + modules/comment/views/comments.html.php | 56 + .../comment/views/user_profile_comments.html.php | 20 + .../downloadalbum/controllers/downloadalbum.php | 300 + modules/downloadalbum/css/downloadalbum_menu.css | 3 + .../downloadalbum/helpers/downloadalbum_event.php | 40 + .../downloadalbum/helpers/downloadalbum_theme.php | 24 + .../images/ico-view-downloadalbum.png | Bin 0 -> 4125 bytes modules/downloadalbum/module.info | 7 + .../controllers/admin_downloadfullsize.php | 93 + .../controllers/downloadfullsize.php | 37 + .../downloadfullsize/css/downloadfullsize_menu.css | 3 + .../helpers/downloadfullsize_block.php | 51 + .../helpers/downloadfullsize_event.php | 57 + .../helpers/downloadfullsize_theme.php | 26 + .../images/ico-view-downloadfullsize.png | Bin 0 -> 4125 bytes modules/downloadfullsize/module.info | 7 + .../views/admin_downloadfullsize.html.php | 5 + .../views/downloadfullsize_block.html.php | 18 + modules/exif/controllers/exif.php | 33 + modules/exif/helpers/exif.php | 167 + modules/exif/helpers/exif_event.php | 39 + modules/exif/helpers/exif_installer.php | 45 + modules/exif/helpers/exif_task.php | 88 + modules/exif/helpers/exif_theme.php | 38 + modules/exif/lib/exif.php | 1135 ++++ modules/exif/lib/makers/canon.php | 426 ++ modules/exif/lib/makers/fujifilm.php | 247 + modules/exif/lib/makers/gps.php | 218 + modules/exif/lib/makers/nikon.php | 411 ++ modules/exif/lib/makers/olympus.php | 189 + modules/exif/lib/makers/panasonic.php | 292 + modules/exif/lib/makers/sanyo.php | 158 + modules/exif/models/exif_key.php | 21 + modules/exif/models/exif_record.php | 21 + modules/exif/module.info | 7 + modules/exif/views/exif_dialog.html.php | 33 + modules/exif/views/exif_sidebar.html.php | 6 + modules/familygallery/controllers/login.php.backup | 10 + .../familygallery/helpers/familygallery_event.php | 20 + modules/familygallery/module.info | 7 + modules/forge/libraries/Forge.php | 323 + modules/forge/libraries/Form_Checkbox.php | 83 + modules/forge/libraries/Form_Checklist.php | 92 + modules/forge/libraries/Form_Dateselect.php | 138 + modules/forge/libraries/Form_Dropdown.php | 78 + modules/forge/libraries/Form_Group.php | 89 + modules/forge/libraries/Form_Hidden.php | 25 + modules/forge/libraries/Form_Input.php | 555 ++ modules/forge/libraries/Form_Password.php | 23 + modules/forge/libraries/Form_Phonenumber.php | 98 + modules/forge/libraries/Form_Radio.php | 22 + modules/forge/libraries/Form_Submit.php | 30 + modules/forge/libraries/Form_Textarea.php | 31 + modules/forge/libraries/Form_Upload.php | 191 + modules/g2_import/controllers/admin_g2_import.php | 136 + modules/g2_import/controllers/g2.php | 121 + modules/g2_import/data/broken-image.gif | Bin 0 -> 1589 bytes modules/g2_import/helpers/g2_import.php | 1375 ++++ modules/g2_import/helpers/g2_import_event.php | 40 + modules/g2_import/helpers/g2_import_installer.php | 50 + modules/g2_import/helpers/g2_import_task.php | 225 + .../g2_import/libraries/G2_Import_Exception.php | 39 + modules/g2_import/models/g2_map.php | 21 + modules/g2_import/module.info | 7 + modules/g2_import/views/admin_g2_import.html.php | 150 + modules/gallery/config/cache.php | 50 + modules/gallery/config/cookie.php | 48 + modules/gallery/config/database.php | 23 + modules/gallery/config/locale.php | 51 + modules/gallery/config/log_file.php | 29 + modules/gallery/config/routes.php | 32 + modules/gallery/config/session.php | 66 + modules/gallery/config/upload.php | 36 + modules/gallery/config/user_agents.php | 25 + modules/gallery/controllers/admin.php | 94 + .../controllers/admin_advanced_settings.php | 57 + modules/gallery/controllers/admin_dashboard.php | 97 + modules/gallery/controllers/admin_graphics.php | 50 + modules/gallery/controllers/admin_languages.php | 135 + modules/gallery/controllers/admin_maintenance.php | 243 + modules/gallery/controllers/admin_modules.php | 119 + modules/gallery/controllers/admin_movies.php | 72 + modules/gallery/controllers/admin_sidebar.php | 69 + .../gallery/controllers/admin_theme_options.php | 120 + modules/gallery/controllers/admin_themes.php | 80 + .../gallery/controllers/admin_upgrade_checker.php | 57 + modules/gallery/controllers/albums.php | 207 + modules/gallery/controllers/combined.php | 96 + modules/gallery/controllers/file_proxy.php | 174 + modules/gallery/controllers/items.php | 43 + modules/gallery/controllers/l10n_client.php | 179 + modules/gallery/controllers/login.php | 90 + modules/gallery/controllers/logout.php | 29 + modules/gallery/controllers/movies.php | 91 + modules/gallery/controllers/packager.php | 191 + modules/gallery/controllers/permissions.php | 91 + modules/gallery/controllers/photos.php | 91 + modules/gallery/controllers/quick.php | 144 + modules/gallery/controllers/reauthenticate.php | 105 + modules/gallery/controllers/upgrader.php | 118 + modules/gallery/controllers/uploader.php | 133 + modules/gallery/controllers/user_profile.php | 108 + modules/gallery/controllers/welcome_message.php | 30 + modules/gallery/css/debug.css | 28 + modules/gallery/css/gallery.css | 213 + modules/gallery/css/l10n_client.css | 206 + modules/gallery/css/upgrader.css | 183 + modules/gallery/helpers/MY_html.php | 91 + modules/gallery/helpers/MY_num.php | 54 + modules/gallery/helpers/MY_remote.php | 167 + modules/gallery/helpers/MY_url.php | 92 + modules/gallery/helpers/MY_valid.php | 26 + modules/gallery/helpers/access.php | 758 +++ modules/gallery/helpers/ajax.php | 31 + modules/gallery/helpers/album.php | 126 + modules/gallery/helpers/auth.php | 134 + modules/gallery/helpers/batch.php | 40 + modules/gallery/helpers/block_manager.php | 115 + modules/gallery/helpers/data_rest.php | 115 + modules/gallery/helpers/dir.php | 40 + modules/gallery/helpers/encoding.php | 35 + modules/gallery/helpers/gallery.php | 233 + modules/gallery/helpers/gallery_block.php | 145 + modules/gallery/helpers/gallery_error.php | 30 + modules/gallery/helpers/gallery_event.php | 621 ++ modules/gallery/helpers/gallery_graphics.php | 183 + modules/gallery/helpers/gallery_installer.php | 844 +++ modules/gallery/helpers/gallery_rss.php | 76 + modules/gallery/helpers/gallery_task.php | 826 +++ modules/gallery/helpers/gallery_theme.php | 151 + modules/gallery/helpers/graphics.php | 546 ++ modules/gallery/helpers/identity.php | 247 + modules/gallery/helpers/item.php | 433 ++ modules/gallery/helpers/item_rest.php | 210 + modules/gallery/helpers/items_rest.php | 96 + modules/gallery/helpers/json.php | 31 + modules/gallery/helpers/l10n_client.php | 323 + modules/gallery/helpers/l10n_scanner.php | 178 + modules/gallery/helpers/legal_file.php | 310 + modules/gallery/helpers/locales.php | 264 + modules/gallery/helpers/log.php | 108 + modules/gallery/helpers/message.php | 109 + modules/gallery/helpers/model_cache.php | 42 + modules/gallery/helpers/module.php | 594 ++ modules/gallery/helpers/movie.php | 282 + modules/gallery/helpers/photo.php | 145 + modules/gallery/helpers/random.php | 48 + modules/gallery/helpers/site_status.php | 132 + modules/gallery/helpers/system.php | 113 + modules/gallery/helpers/task.php | 113 + modules/gallery/helpers/theme.php | 113 + modules/gallery/helpers/tree_rest.php | 92 + modules/gallery/helpers/upgrade_checker.php | 105 + modules/gallery/helpers/user_profile.php | 55 + modules/gallery/helpers/xml.php | 35 + modules/gallery/hooks/init_gallery.php | 56 + modules/gallery/images/ffmpeg.png | Bin 0 -> 2888 bytes modules/gallery/images/gallery.png | Bin 0 -> 22178 bytes modules/gallery/images/gd.png | Bin 0 -> 5531 bytes modules/gallery/images/graphicsmagick.png | Bin 0 -> 1486 bytes modules/gallery/images/imagemagick.jpg | Bin 0 -> 20337 bytes modules/gallery/images/missing_album_cover.jpg | Bin 0 -> 4453 bytes modules/gallery/images/missing_movie.jpg | Bin 0 -> 3428 bytes modules/gallery/images/missing_photo.jpg | Bin 0 -> 2034 bytes modules/gallery/js/albums_form_add.js | 25 + modules/gallery/js/item_form_delete.js | 5 + modules/gallery/js/l10n_client.js | 315 + modules/gallery/libraries/Admin_View.php | 120 + modules/gallery/libraries/Block.php | 30 + modules/gallery/libraries/Breadcrumb.php | 70 + modules/gallery/libraries/Form_Script.php | 66 + modules/gallery/libraries/Form_Uploadify.php | 72 + .../gallery/libraries/Form_Uploadify_buttons.php | 25 + modules/gallery/libraries/Gallery_I18n.php | 472 ++ modules/gallery/libraries/Gallery_View.php | 243 + modules/gallery/libraries/IdentityProvider.php | 283 + modules/gallery/libraries/InPlaceEdit.php | 91 + modules/gallery/libraries/MY_Database.php | 101 + modules/gallery/libraries/MY_Forge.php | 45 + modules/gallery/libraries/MY_Input.php | 31 + modules/gallery/libraries/MY_Kohana_Exception.php | 101 + modules/gallery/libraries/MY_ORM.php | 52 + modules/gallery/libraries/MY_View.php | 85 + modules/gallery/libraries/Menu.php | 257 + modules/gallery/libraries/ORM_MPTT.php | 341 + modules/gallery/libraries/SafeString.php | 162 + modules/gallery/libraries/Sendmail.php | 98 + modules/gallery/libraries/Task_Definition.php | 50 + modules/gallery/libraries/Theme_View.php | 271 + .../gallery/libraries/drivers/Cache/Database.php | 166 + .../gallery/libraries/drivers/IdentityProvider.php | 134 + modules/gallery/models/access_cache.php | 21 + modules/gallery/models/access_intent.php | 21 + modules/gallery/models/cache.php | 20 + modules/gallery/models/failed_auth.php | 20 + modules/gallery/models/graphics_rule.php | 21 + modules/gallery/models/incoming_translation.php | 21 + modules/gallery/models/item.php | 1191 ++++ modules/gallery/models/log.php | 38 + modules/gallery/models/message.php | 21 + modules/gallery/models/module.php | 21 + modules/gallery/models/outgoing_translation.php | 21 + modules/gallery/models/permission.php | 21 + modules/gallery/models/task.php | 86 + modules/gallery/models/theme.php | 21 + modules/gallery/models/var.php | 21 + modules/gallery/module.info | 7 + modules/gallery/vendor/joomla/crypt.php | 151 + .../gallery/views/admin_advanced_settings.html.php | 57 + .../gallery/views/admin_block_log_entries.html.php | 15 + modules/gallery/views/admin_block_news.html.php | 11 + .../views/admin_block_photo_stream.html.php | 14 + .../gallery/views/admin_block_platform.html.php | 24 + modules/gallery/views/admin_block_stats.html.php | 12 + modules/gallery/views/admin_block_welcome.html.php | 20 + modules/gallery/views/admin_dashboard.html.php | 43 + modules/gallery/views/admin_graphics.html.php | 40 + modules/gallery/views/admin_graphics_gd.html.php | 30 + .../views/admin_graphics_graphicsmagick.html.php | 21 + .../views/admin_graphics_imagemagick.html.php | 21 + modules/gallery/views/admin_graphics_none.html.php | 8 + modules/gallery/views/admin_languages.html.php | 118 + modules/gallery/views/admin_maintenance.html.php | 212 + .../views/admin_maintenance_show_log.html.php | 19 + .../gallery/views/admin_maintenance_task.html.php | 84 + modules/gallery/views/admin_modules.html.php | 129 + .../gallery/views/admin_modules_confirm.html.php | 22 + modules/gallery/views/admin_movies.html.php | 44 + modules/gallery/views/admin_sidebar.html.php | 64 + .../gallery/views/admin_sidebar_blocks.html.php | 5 + modules/gallery/views/admin_theme_options.html.php | 7 + modules/gallery/views/admin_themes.html.php | 98 + .../gallery/views/admin_themes_buttonset.html.php | 48 + .../gallery/views/admin_themes_preview.html.php | 8 + modules/gallery/views/error.html.php | 12 + modules/gallery/views/error_404.html.php | 26 + modules/gallery/views/error_admin.html.php | 307 + modules/gallery/views/error_cli.txt.php | 3 + modules/gallery/views/error_user.html.php | 60 + modules/gallery/views/form.html.php | 77 + modules/gallery/views/form_uploadify.html.php | 167 + .../gallery/views/form_uploadify_buttons.html.php | 11 + modules/gallery/views/in_place_edit.html.php | 21 + modules/gallery/views/kohana/error.php | 46 + modules/gallery/views/kohana_profiler.php | 35 + modules/gallery/views/l10n_client.html.php | 82 + modules/gallery/views/login_ajax.html.php | 52 + modules/gallery/views/login_current_user.html.php | 7 + modules/gallery/views/menu.html.php | 24 + modules/gallery/views/menu_ajax_link.html.php | 10 + modules/gallery/views/menu_dialog.html.php | 9 + modules/gallery/views/menu_link.html.php | 9 + modules/gallery/views/movieplayer.html.php | 50 + modules/gallery/views/permissions_browse.html.php | 62 + modules/gallery/views/permissions_form.html.php | 92 + .../gallery/views/quick_delete_confirm.html.php | 12 + modules/gallery/views/reauthenticate.html.php | 15 + .../gallery/views/upgrade_checker_block.html.php | 55 + modules/gallery/views/upgrader.html.php | 163 + .../gallery/views/user_languages_block.html.php | 19 + modules/gallery/views/user_profile.html.php | 47 + modules/gallery/views/user_profile_info.html.php | 9 + modules/gallery/views/welcome_message.html.php | 36 + .../gallery/views/welcome_message_loader.html.php | 7 + modules/greydragon/changelog.txt | 11 + modules/greydragon/css/gd_common.css | 59 + modules/greydragon/images/blue-grad.png | Bin 0 -> 337 bytes modules/greydragon/module.info | 9 + modules/greydragon/views/gd_admin_include.html.php | 180 + modules/html5_uploader/controllers/uploader.php | 126 + modules/html5_uploader/module.info | 7 + modules/image_block/controllers/image_block.php | 27 + modules/image_block/helpers/image_block_block.php | 56 + .../image_block/helpers/image_block_installer.php | 43 + modules/image_block/module.info | 7 + .../image_block/views/image_block_block.html.php | 8 + modules/info/helpers/info_block.php | 93 + modules/info/helpers/info_installer.php | 45 + modules/info/helpers/info_theme.php | 41 + modules/info/module.info | 7 + modules/info/views/info_block.html.php | 8 + modules/kbd_nav/changelog.txt | 47 + modules/kbd_nav/helpers/kbd_nav_theme.php | 7 + modules/kbd_nav/js/kbd_nav.js | 120 + modules/kbd_nav/module.info | 8 + modules/kohana23_compat/config/pagination.php | 27 + .../libraries/MY_Database_Builder.php | 50 + modules/kohana23_compat/libraries/Pagination.php | 252 + .../localprint/controllers/admin_localprint.php | 26 + modules/localprint/css/localprint_menu.css | 16 + modules/localprint/helpers/localprint_event.php | 39 + .../localprint/helpers/localprint_installer.php | 35 + modules/localprint/helpers/localprint_theme.php | 27 + modules/localprint/images/localprint_logo.png | Bin 0 -> 2449 bytes modules/localprint/js/localprint.js | 8 + modules/localprint/module.info | 3 + modules/localprint/views/admin_localprint.html.php | 4 + modules/localprint/views/localprint_code.html.php | 9 + modules/notification/controllers/notification.php | 36 + modules/notification/helpers/notification.php | 218 + .../notification/helpers/notification_event.php | 149 + .../helpers/notification_installer.php | 54 + .../notification/models/pending_notification.php | 21 + modules/notification/models/subscription.php | 21 + modules/notification/module.info | 7 + .../notification/views/comment_published.html.php | 35 + modules/notification/views/item_added.html.php | 29 + modules/notification/views/item_deleted.html.php | 25 + modules/notification/views/item_updated.html.php | 35 + .../views/user_profile_notification.html.php | 12 + modules/organize/controllers/organize.php | 228 + modules/organize/css/organize_dialog.css | 17 + modules/organize/css/organize_frame.css | 118 + modules/organize/helpers/organize_event.php | 54 + modules/organize/helpers/organize_installer.php | 28 + modules/organize/module.info | 7 + modules/organize/vendor/ext/css/ext-all.css | 6969 ++++++++++++++++++++ modules/organize/vendor/ext/css/ux-all.css | 772 +++ .../vendor/ext/images/default/box/tb-blue.gif | Bin 0 -> 851 bytes .../vendor/ext/images/default/button/btn.gif | Bin 0 -> 4298 bytes .../vendor/ext/images/default/dd/drop-no.gif | Bin 0 -> 949 bytes .../vendor/ext/images/default/dd/drop-yes.gif | Bin 0 -> 1016 bytes .../vendor/ext/images/default/form/text-bg.gif | Bin 0 -> 819 bytes .../vendor/ext/images/default/form/trigger.gif | Bin 0 -> 1816 bytes .../ext/images/default/grid/invalid_line.gif | Bin 0 -> 815 bytes .../vendor/ext/images/default/grid/loading.gif | Bin 0 -> 771 bytes .../ext/images/default/panel/tool-sprites.gif | Bin 0 -> 4392 bytes .../ext/images/default/panel/white-top-bottom.gif | Bin 0 -> 872 bytes .../ext/images/default/progress/progress-bg.gif | Bin 0 -> 834 bytes .../organize/vendor/ext/images/default/qtip/bg.gif | Bin 0 -> 1091 bytes modules/organize/vendor/ext/images/default/s.gif | Bin 0 -> 43 bytes .../vendor/ext/images/default/shadow-c.png | Bin 0 -> 118 bytes .../vendor/ext/images/default/shadow-lr.png | Bin 0 -> 135 bytes .../organize/vendor/ext/images/default/shadow.png | Bin 0 -> 311 bytes .../vendor/ext/images/default/toolbar/bg.gif | Bin 0 -> 904 bytes .../vendor/ext/images/default/tree/arrows.gif | Bin 0 -> 617 bytes .../vendor/ext/images/default/tree/drop-add.gif | Bin 0 -> 1001 bytes .../ext/images/default/tree/drop-between.gif | Bin 0 -> 907 bytes .../vendor/ext/images/default/tree/drop-over.gif | Bin 0 -> 911 bytes .../vendor/ext/images/default/tree/folder-open.gif | Bin 0 -> 956 bytes .../vendor/ext/images/default/tree/folder.gif | Bin 0 -> 952 bytes .../vendor/ext/images/default/tree/loading.gif | Bin 0 -> 771 bytes .../ext/images/default/window/left-corners.png | Bin 0 -> 200 bytes .../ext/images/default/window/left-right.png | Bin 0 -> 152 bytes .../ext/images/default/window/right-corners.png | Bin 0 -> 256 bytes .../ext/images/default/window/top-bottom.png | Bin 0 -> 180 bytes modules/organize/vendor/ext/images/fam/delete.gif | Bin 0 -> 989 bytes .../organize/vendor/ext/js/ext-organize-bundle.js | 2202 +++++++ modules/organize/views/organize_dialog.html.php | 23 + modules/organize/views/organize_frame.html.php | 604 ++ modules/recaptcha/controllers/admin_recaptcha.php | 61 + modules/recaptcha/css/recaptcha.css | 7 + modules/recaptcha/helpers/recaptcha.php | 152 + modules/recaptcha/helpers/recaptcha_event.php | 42 + modules/recaptcha/helpers/recaptcha_installer.php | 28 + modules/recaptcha/helpers/recaptcha_theme.php | 28 + modules/recaptcha/libraries/Form_Recaptcha.php | 67 + modules/recaptcha/module.info | 7 + modules/recaptcha/views/admin_recaptcha.html.php | 35 + modules/recaptcha/views/form_recaptcha.html.php | 18 + modules/rest/controllers/rest.php | 121 + modules/rest/helpers/registry_rest.php | 30 + modules/rest/helpers/rest.php | 191 + modules/rest/helpers/rest_event.php | 102 + modules/rest/helpers/rest_installer.php | 52 + modules/rest/libraries/Rest_Exception.php | 37 + modules/rest/models/user_access_key.php | 21 + modules/rest/module.info | 8 + modules/rest/views/error_rest.json.php | 6 + modules/rest/views/reset_api_key_confirm.html.php | 7 + modules/rest/views/user_profile_rest.html.php | 13 + modules/rss/controllers/rss.php | 66 + modules/rss/helpers/rss.php | 36 + modules/rss/helpers/rss_block.php | 49 + modules/rss/module.info | 7 + modules/rss/views/feed.mrss.php | 79 + modules/rss/views/rss_block.html.php | 13 + modules/search/controllers/search.php | 123 + modules/search/helpers/search.php | 163 + modules/search/helpers/search_event.php | 39 + modules/search/helpers/search_installer.php | 50 + modules/search/helpers/search_task.php | 84 + modules/search/helpers/search_theme.php | 29 + modules/search/models/search_record.php | 24 + modules/search/module.info | 7 + modules/search/views/search.html.php | 65 + modules/search/views/search_link.html.php | 22 + .../server_add/controllers/admin_server_add.php | 97 + modules/server_add/controllers/server_add.php | 315 + modules/server_add/css/server_add.css | 38 + modules/server_add/helpers/server_add.php | 49 + modules/server_add/helpers/server_add_event.php | 42 + .../server_add/helpers/server_add_installer.php | 73 + modules/server_add/helpers/server_add_theme.php | 27 + modules/server_add/js/server_add.js | 125 + modules/server_add/models/server_add_entry.php | 21 + modules/server_add/module.info | 7 + modules/server_add/views/admin_server_add.html.php | 40 + modules/server_add/views/server_add_tree.html.php | 37 + .../views/server_add_tree_dialog.html.php | 52 + modules/slideshow/helpers/slideshow_event.php | 80 + modules/slideshow/helpers/slideshow_installer.php | 43 + modules/slideshow/helpers/slideshow_theme.php | 26 + modules/slideshow/module.info | 7 + modules/tag/controllers/admin_tags.php | 119 + modules/tag/controllers/tag.php | 96 + modules/tag/controllers/tag_name.php | 33 + modules/tag/controllers/tags.php | 65 + modules/tag/css/tag.css | 102 + modules/tag/helpers/item_tags_rest.php | 66 + modules/tag/helpers/tag.php | 181 + modules/tag/helpers/tag_block.php | 45 + modules/tag/helpers/tag_event.php | 165 + modules/tag/helpers/tag_installer.php | 59 + modules/tag/helpers/tag_item_rest.php | 51 + modules/tag/helpers/tag_items_rest.php | 64 + modules/tag/helpers/tag_rest.php | 88 + modules/tag/helpers/tag_rss.php | 48 + modules/tag/helpers/tag_task.php | 97 + modules/tag/helpers/tag_theme.php | 31 + modules/tag/helpers/tags_rest.php | 77 + modules/tag/models/tag.php | 169 + modules/tag/module.info | 7 + modules/tag/views/admin_tags.html.php | 59 + modules/tag/views/tag_block.html.php | 30 + modules/tag/views/tag_cloud.html.php | 9 + modules/thumbnav/changelog.log | 20 + modules/thumbnav/controllers/admin_thumbnav.php | 65 + modules/thumbnav/css/thumbnav.css | 11 + modules/thumbnav/helpers/thumbnav_block.php | 89 + modules/thumbnav/helpers/thumbnav_event.php | 20 + modules/thumbnav/helpers/thumbnav_theme.php | 8 + modules/thumbnav/module.info | 7 + modules/thumbnav/views/admin_include.html.php | 95 + modules/thumbnav/views/admin_thumbnav.html.php | 10 + modules/thumbnav/views/thumbnav_block.html.php | 5 + modules/user/config/identity.php | 37 + modules/user/controllers/admin_users.php | 442 ++ modules/user/controllers/password.php | 141 + modules/user/controllers/users.php | 239 + modules/user/css/user.css | 120 + modules/user/helpers/group.php | 82 + modules/user/helpers/user.php | 176 + modules/user/helpers/user_event.php | 30 + modules/user/helpers/user_installer.php | 142 + modules/user/helpers/user_theme.php | 30 + modules/user/images/progressImg1.png | Bin 0 -> 390 bytes modules/user/js/password_strength.js | 39 + modules/user/lib/PasswordHash.php | 248 + .../libraries/drivers/IdentityProvider/Gallery.php | 164 + modules/user/models/group.php | 89 + modules/user/models/user.php | 185 + modules/user/module.info | 8 + modules/user/views/admin_users.html.php | 142 + .../user/views/admin_users_delete_user.html.php | 7 + modules/user/views/admin_users_group.html.php | 40 + modules/user/views/reset_password.html.php | 18 + modules/watermark/controllers/admin_watermarks.php | 158 + modules/watermark/helpers/watermark.php | 82 + modules/watermark/helpers/watermark_event.php | 29 + modules/watermark/helpers/watermark_installer.php | 42 + modules/watermark/module.info | 7 + modules/watermark/views/admin_watermarks.html.php | 39 + php.ini | 19 + robots.txt | 14 + serveradd/Raphael_2013-10-18_optimiert.mpg | Bin 0 -> 31750148 bytes system/KohanaLicense.html | 30 + system/config/cache.php | 37 + system/config/cookie.php | 55 + system/config/credit_cards.php | 70 + system/config/database.php | 53 + system/config/encryption.php | 39 + system/config/http.php | 30 + system/config/image.php | 22 + system/config/inflector.php | 70 + system/config/locale.php | 26 + system/config/log.php | 35 + system/config/mimes.php | 228 + system/config/profiler.php | 16 + system/config/routes.php | 58 + system/config/session.php | 56 + system/config/sql_types.php | 99 + system/config/upload.php | 24 + system/config/user_agents.php | 120 + system/config/view.php | 25 + system/controllers/template.php | 54 + system/core/Benchmark.php | 126 + system/core/Event.php | 231 + system/core/Kohana.php | 1118 ++++ system/core/Kohana_Config.php | 329 + system/core/Kohana_Exception.php | 622 ++ system/helpers/arr.php | 273 + system/helpers/cookie.php | 149 + system/helpers/date.php | 395 ++ system/helpers/db.php | 47 + system/helpers/download.php | 135 + system/helpers/expires.php | 129 + system/helpers/feed.php | 120 + system/helpers/file.php | 184 + system/helpers/form.php | 466 ++ system/helpers/format.php | 112 + system/helpers/html.php | 364 + system/helpers/inflector.php | 252 + system/helpers/num.php | 24 + system/helpers/remote.php | 64 + system/helpers/request.php | 618 ++ system/helpers/security.php | 35 + system/helpers/text.php | 596 ++ system/helpers/upload.php | 157 + system/helpers/url.php | 264 + system/helpers/utf8.php | 744 +++ system/helpers/valid.php | 453 ++ system/libraries/Cache.php | 248 + system/libraries/Cache_Exception.php | 11 + system/libraries/Controller.php | 44 + system/libraries/Database.php | 648 ++ system/libraries/Database_Builder.php | 1241 ++++ system/libraries/Database_Cache_Result.php | 81 + system/libraries/Database_Exception.php | 15 + system/libraries/Database_Expression.php | 23 + system/libraries/Database_Mysql.php | 226 + system/libraries/Database_Mysql_Result.php | 174 + system/libraries/Database_Mysqli.php | 90 + system/libraries/Database_Mysqli_Result.php | 175 + system/libraries/Database_Query.php | 95 + system/libraries/Database_Result.php | 170 + system/libraries/Encrypt.php | 176 + system/libraries/I18n.php | 100 + system/libraries/Image.php | 501 ++ system/libraries/Input.php | 507 ++ system/libraries/Kohana_404_Exception.php | 56 + system/libraries/Kohana_Log.php | 90 + system/libraries/Kohana_PHP_Exception.php | 99 + system/libraries/Kohana_User_Exception.php | 30 + system/libraries/Model.php | 62 + system/libraries/ORM.php | 1528 +++++ system/libraries/ORM_Iterator.php | 266 + system/libraries/ORM_Validation_Exception.php | 24 + system/libraries/Profiler.php | 306 + system/libraries/Profiler_Table.php | 67 + system/libraries/Router.php | 315 + system/libraries/Session.php | 500 ++ system/libraries/URI.php | 279 + system/libraries/Validation.php | 815 +++ system/libraries/View.php | 329 + system/libraries/drivers/Cache.php | 42 + system/libraries/drivers/Cache/File.php | 255 + system/libraries/drivers/Cache/Memcache.php | 132 + system/libraries/drivers/Cache/Xcache.php | 161 + system/libraries/drivers/Config.php | 257 + system/libraries/drivers/Config/Array.php | 83 + system/libraries/drivers/Image.php | 158 + system/libraries/drivers/Image/GD.php | 440 ++ system/libraries/drivers/Image/GraphicsMagick.php | 225 + system/libraries/drivers/Image/ImageMagick.php | 233 + system/libraries/drivers/Log.php | 22 + system/libraries/drivers/Log/Database.php | 40 + system/libraries/drivers/Log/File.php | 44 + system/libraries/drivers/Log/Syslog.php | 34 + system/libraries/drivers/Session.php | 70 + system/libraries/drivers/Session/Cache.php | 108 + system/libraries/drivers/Session/Cookie.php | 83 + system/libraries/drivers/Session/Database.php | 178 + system/messages/kohana/core.php | 37 + system/messages/validation/default.php | 25 + system/views/kohana/error.php | 252 + system/views/kohana/error_disabled.php | 19 + system/views/kohana/template.php | 36 + system/views/profiler/profiler.php | 37 + system/views/profiler/table.css | 53 + system/views/profiler/table.php | 24 + themes/admin_wind/css/fix-ie.css | 18 + themes/admin_wind/css/screen-rtl.css | 400 ++ themes/admin_wind/css/screen.css | 1058 +++ .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_55_fbec88_40x100.png | Bin 0 -> 182 bytes .../images/ui-bg_glass_75_d0e5f5_1x400.png | Bin 0 -> 124 bytes .../images/ui-bg_glass_85_dfeffc_1x400.png | Bin 0 -> 123 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../images/ui-bg_gloss-wave_55_5c9ccc_500x100.png | Bin 0 -> 4033 bytes .../images/ui-bg_inset-hard_100_f5f8f9_1x100.png | Bin 0 -> 104 bytes .../images/ui-bg_inset-hard_100_fcfdfd_1x100.png | Bin 0 -> 88 bytes .../themeroller/images/ui-icons_217bc0_256x240.png | Bin 0 -> 7638 bytes .../themeroller/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 7626 bytes .../themeroller/images/ui-icons_469bdd_256x240.png | Bin 0 -> 5399 bytes .../themeroller/images/ui-icons_6da8d5_256x240.png | Bin 0 -> 8447 bytes .../themeroller/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4379 bytes .../themeroller/images/ui-icons_d8e7f3_256x240.png | Bin 0 -> 4379 bytes .../themeroller/images/ui-icons_f9bd01_256x240.png | Bin 0 -> 4379 bytes themes/admin_wind/css/themeroller/ui.base.css | 7 + themes/admin_wind/css/themeroller/ui.core.css | 37 + .../admin_wind/css/themeroller/ui.datepicker.css | 62 + themes/admin_wind/css/themeroller/ui.dialog.css | 13 + .../admin_wind/css/themeroller/ui.progressbar.css | 4 + themes/admin_wind/css/themeroller/ui.resizable.css | 13 + themes/admin_wind/css/themeroller/ui.tabs.css | 9 + themes/admin_wind/css/themeroller/ui.theme.css | 243 + themes/admin_wind/images/avatar.jpg | Bin 0 -> 1172 bytes themes/admin_wind/images/ico-denied-inactive.png | Bin 0 -> 604 bytes themes/admin_wind/images/ico-denied-passive.png | Bin 0 -> 916 bytes themes/admin_wind/images/ico-denied.png | Bin 0 -> 715 bytes themes/admin_wind/images/ico-error.png | Bin 0 -> 701 bytes themes/admin_wind/images/ico-info.png | Bin 0 -> 778 bytes themes/admin_wind/images/ico-lock.png | Bin 0 -> 749 bytes themes/admin_wind/images/ico-separator-rtl.gif | Bin 0 -> 106 bytes themes/admin_wind/images/ico-separator.gif | Bin 0 -> 106 bytes themes/admin_wind/images/ico-success-inactive.png | Bin 0 -> 476 bytes themes/admin_wind/images/ico-success-passive.png | Bin 0 -> 617 bytes themes/admin_wind/images/ico-success.png | Bin 0 -> 537 bytes themes/admin_wind/images/ico-warning.png | Bin 0 -> 666 bytes themes/admin_wind/images/loading-large.gif | Bin 0 -> 8238 bytes themes/admin_wind/images/loading-small.gif | Bin 0 -> 673 bytes themes/admin_wind/js/ui.init.js | 62 + themes/admin_wind/theme.info | 10 + themes/admin_wind/thumbnail.png | Bin 0 -> 28330 bytes themes/admin_wind/views/admin.html.php | 104 + themes/admin_wind/views/block.html.php | 18 + themes/admin_wind/views/paginator.html.php | 88 + .../admin/controllers/admin_theme_options.php | 860 +++ .../admin/views/admin_theme_options.html.php | 38 + themes/greydragon/changelog.txt | 641 ++ themes/greydragon/controllers/greydragon.php | 14 + themes/greydragon/css/base.css | 322 + .../css/colorpacks/blackhawk/css/colors.css | 169 + .../colorpacks/blackhawk/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../css/colorpacks/blackhawk/images/colorpack.png | Bin 0 -> 307 bytes .../css/colorpacks/blackhawk/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/blackhawk/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/blackhawk/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/blackhawk/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/blackhawk/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/blackhawk/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/blackhawk/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/blackhawk/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/blackhawk/images/ui-icons.png | Bin 0 -> 13169 bytes .../blackhawk/images/view-calendar-b.png | Bin 0 -> 653 bytes .../colorpacks/blackhawk/images/view-calendar.png | Bin 0 -> 429 bytes .../blackhawk/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/blackhawk/images/view-comments.png | Bin 0 -> 492 bytes .../blackhawk/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../colorpacks/blackhawk/images/view-fullsize.png | Bin 0 -> 407 bytes .../colorpacks/blackhawk/images/view-info-b.png | Bin 0 -> 493 bytes .../colorpacks/blackhawk/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/blackhawk/images/view-info.png | Bin 0 -> 938 bytes .../blackhawk/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/carbon/css/colors.css | 188 + .../css/colorpacks/carbon/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../css/colorpacks/carbon/images/colorpack.png | Bin 0 -> 286 bytes .../css/colorpacks/carbon/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/carbon/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/carbon/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/carbon/images/ico-info.png | Bin 0 -> 778 bytes .../css/colorpacks/carbon/images/ico-warning.png | Bin 0 -> 666 bytes .../css/colorpacks/carbon/images/loading-large.gif | Bin 0 -> 8238 bytes .../css/colorpacks/carbon/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/carbon/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/carbon/images/section.png | Bin 0 -> 192 bytes .../css/colorpacks/carbon/images/ui-icons.png | Bin 0 -> 13162 bytes .../colorpacks/carbon/images/view-calendar-b.png | Bin 0 -> 653 bytes .../css/colorpacks/carbon/images/view-calendar.png | Bin 0 -> 429 bytes .../colorpacks/carbon/images/view-comments-b.png | Bin 0 -> 600 bytes .../css/colorpacks/carbon/images/view-comments.png | Bin 0 -> 492 bytes .../colorpacks/carbon/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../css/colorpacks/carbon/images/view-fullsize.png | Bin 0 -> 407 bytes .../css/colorpacks/carbon/images/view-info-b.png | Bin 0 -> 493 bytes .../css/colorpacks/carbon/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/carbon/images/view-info.png | Bin 0 -> 938 bytes .../colorpacks/carbon/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/greydragon/css/colors.css | 173 + .../colorpacks/greydragon/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../greydragon/images/background-bottom.gif | Bin 0 -> 1040 bytes .../greydragon/images/background-top.gif | Bin 0 -> 862 bytes .../colorpacks/greydragon/images/background.gif | Bin 0 -> 1098 bytes .../css/colorpacks/greydragon/images/colorpack.png | Bin 0 -> 414 bytes .../css/colorpacks/greydragon/images/footer.png | Bin 0 -> 376 bytes .../css/colorpacks/greydragon/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/greydragon/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/greydragon/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/greydragon/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/greydragon/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/greydragon/images/image-thumb.gif | Bin 0 -> 830 bytes .../colorpacks/greydragon/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/greydragon/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/greydragon/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/greydragon/images/section.png | Bin 0 -> 192 bytes .../css/colorpacks/greydragon/images/ui-icons.png | Bin 0 -> 13160 bytes .../greydragon/images/view-calendar-b.png | Bin 0 -> 653 bytes .../colorpacks/greydragon/images/view-calendar.png | Bin 0 -> 429 bytes .../greydragon/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/greydragon/images/view-comments.png | Bin 0 -> 492 bytes .../greydragon/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../colorpacks/greydragon/images/view-fullsize.png | Bin 0 -> 407 bytes .../colorpacks/greydragon/images/view-info-b.png | Bin 0 -> 493 bytes .../colorpacks/greydragon/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/greydragon/images/view-info.png | Bin 0 -> 938 bytes .../greydragon/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/roundrobin/css/colors.css | 192 + .../colorpacks/roundrobin/images/ajax-loading.gif | Bin 0 -> 2545 bytes .../css/colorpacks/roundrobin/images/bg-body.jpg | Bin 0 -> 13916 bytes .../css/colorpacks/roundrobin/images/bg-header.jpg | Bin 0 -> 13609 bytes .../css/colorpacks/roundrobin/images/colorpack.png | Bin 0 -> 475 bytes .../css/colorpacks/roundrobin/images/gallery.png | Bin 0 -> 553 bytes .../css/colorpacks/roundrobin/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/roundrobin/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/roundrobin/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/roundrobin/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/roundrobin/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/roundrobin/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/roundrobin/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/roundrobin/images/search.png | Bin 0 -> 550 bytes .../css/colorpacks/roundrobin/images/ui-icons.png | Bin 0 -> 14168 bytes .../roundrobin/images/view-calendar-b.png | Bin 0 -> 891 bytes .../colorpacks/roundrobin/images/view-calendar.png | Bin 0 -> 260 bytes .../roundrobin/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/roundrobin/images/view-comments.png | Bin 0 -> 298 bytes .../roundrobin/images/view-fullsize-b.png | Bin 0 -> 670 bytes .../colorpacks/roundrobin/images/view-fullsize.png | Bin 0 -> 275 bytes .../colorpacks/roundrobin/images/view-info-b.png | Bin 0 -> 698 bytes .../colorpacks/roundrobin/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/roundrobin/images/view-info.png | Bin 0 -> 286 bytes .../roundrobin/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/slateblue/css/colors.css | 176 + .../colorpacks/slateblue/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../css/colorpacks/slateblue/images/background.jpg | Bin 0 -> 907 bytes .../css/colorpacks/slateblue/images/colorpack.png | Bin 0 -> 414 bytes .../css/colorpacks/slateblue/images/footer.png | Bin 0 -> 376 bytes .../css/colorpacks/slateblue/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/slateblue/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/slateblue/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/slateblue/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/slateblue/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/slateblue/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/slateblue/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/slateblue/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/slateblue/images/section.png | Bin 0 -> 192 bytes .../css/colorpacks/slateblue/images/ui-icons.png | Bin 0 -> 13160 bytes .../slateblue/images/view-calendar-b.png | Bin 0 -> 653 bytes .../colorpacks/slateblue/images/view-calendar.png | Bin 0 -> 429 bytes .../slateblue/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/slateblue/images/view-comments.png | Bin 0 -> 492 bytes .../slateblue/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../colorpacks/slateblue/images/view-fullsize.png | Bin 0 -> 407 bytes .../colorpacks/slateblue/images/view-info-b.png | Bin 0 -> 493 bytes .../colorpacks/slateblue/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/slateblue/images/view-info.png | Bin 0 -> 938 bytes .../slateblue/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/whitehawk/css/colors.css | 172 + .../colorpacks/whitehawk/images/ajax-loading.gif | Bin 0 -> 2545 bytes .../css/colorpacks/whitehawk/images/colorpack.png | Bin 0 -> 417 bytes .../css/colorpacks/whitehawk/images/gallery.png | Bin 0 -> 1383 bytes .../css/colorpacks/whitehawk/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/whitehawk/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/whitehawk/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/whitehawk/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/whitehawk/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/whitehawk/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/whitehawk/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/whitehawk/images/search.png | Bin 0 -> 571 bytes .../css/colorpacks/whitehawk/images/ui-icons.png | Bin 0 -> 9914 bytes .../whitehawk/images/view-calendar-b.png | Bin 0 -> 891 bytes .../colorpacks/whitehawk/images/view-calendar.png | Bin 0 -> 260 bytes .../whitehawk/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/whitehawk/images/view-comments.png | Bin 0 -> 298 bytes .../whitehawk/images/view-fullsize-b.png | Bin 0 -> 670 bytes .../colorpacks/whitehawk/images/view-fullsize.png | Bin 0 -> 275 bytes .../colorpacks/whitehawk/images/view-info-b.png | Bin 0 -> 698 bytes .../colorpacks/whitehawk/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/whitehawk/images/view-info.png | Bin 0 -> 286 bytes .../whitehawk/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../greydragon/css/colorpacks/wind/css/colors.css | 175 + .../css/colorpacks/wind/images/ajax-loading.gif | Bin 0 -> 2545 bytes .../css/colorpacks/wind/images/colorpack.png | Bin 0 -> 397 bytes .../css/colorpacks/wind/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/wind/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/wind/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/wind/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/wind/images/ico-view-fullsize.png | Bin 0 -> 1207 bytes .../wind/images/ico-view-slideshow-rtl.png | Bin 0 -> 1013 bytes .../colorpacks/wind/images/ico-view-slideshow.png | Bin 0 -> 854 bytes .../css/colorpacks/wind/images/ico-warning.png | Bin 0 -> 666 bytes .../css/colorpacks/wind/images/loading-large.gif | Bin 0 -> 8238 bytes .../css/colorpacks/wind/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/wind/images/section.png | Bin 0 -> 314 bytes .../css/colorpacks/wind/images/ui-icons.png | Bin 0 -> 10682 bytes .../css/colorpacks/wind/images/view-calendar.png | Bin 0 -> 637 bytes .../css/colorpacks/wind/images/view-comments-b.png | Bin 0 -> 892 bytes .../css/colorpacks/wind/images/view-comments.png | Bin 0 -> 347 bytes .../css/colorpacks/wind/images/view-fullsize.png | Bin 0 -> 365 bytes .../css/colorpacks/wind/images/view-info-b.png | Bin 0 -> 698 bytes .../css/colorpacks/wind/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/wind/images/view-info.png | Bin 0 -> 383 bytes .../colorpacks/wind/images/view-slideshow-b.png | Bin 0 -> 1179 bytes themes/greydragon/css/custom.css | 61 + themes/greydragon/css/forms.css | 120 + .../css/framepacks/android/css/frame.css | 32 + .../css/framepacks/android/images/thumb-dgt-e.png | Bin 0 -> 1482 bytes .../framepacks/android/images/thumb-dgt-eext.png | Bin 0 -> 1939 bytes .../framepacks/android/images/thumb-dgt-ext.png | Bin 0 -> 1791 bytes .../css/framepacks/android/images/thumb-dgt.png | Bin 0 -> 1447 bytes .../css/framepacks/android/images/thumb-flm-e.png | Bin 0 -> 1443 bytes .../framepacks/android/images/thumb-flm-eext.png | Bin 0 -> 1796 bytes .../framepacks/android/images/thumb-flm-ext.png | Bin 0 -> 1737 bytes .../css/framepacks/android/images/thumb-flm.png | Bin 0 -> 1413 bytes .../css/framepacks/android/images/thumb-sqr-e.png | Bin 0 -> 1579 bytes .../framepacks/android/images/thumb-sqr-eext.png | Bin 0 -> 1916 bytes .../framepacks/android/images/thumb-sqr-ext.png | Bin 0 -> 1900 bytes .../css/framepacks/android/images/thumb-sqr.png | Bin 0 -> 1544 bytes .../css/framepacks/android/images/thumb-wd-e.png | Bin 0 -> 1428 bytes .../framepacks/android/images/thumb-wd-eext.png | Bin 0 -> 1720 bytes .../css/framepacks/android/images/thumb-wd-ext.png | Bin 0 -> 1659 bytes .../css/framepacks/android/images/thumb-wd.png | Bin 0 -> 1379 bytes .../greydragon/css/framepacks/book/css/frame.css | 64 + .../css/framepacks/book/images/a-thumb-dgt-e.png | Bin 0 -> 1130 bytes .../framepacks/book/images/a-thumb-dgt-eext.png | Bin 0 -> 1579 bytes .../css/framepacks/book/images/a-thumb-dgt-ext.png | Bin 0 -> 1513 bytes .../css/framepacks/book/images/a-thumb-dgt.png | Bin 0 -> 1062 bytes .../css/framepacks/book/images/a-thumb-flm-e.png | Bin 0 -> 1105 bytes .../framepacks/book/images/a-thumb-flm-eext.png | Bin 0 -> 1478 bytes .../css/framepacks/book/images/a-thumb-flm-ext.png | Bin 0 -> 1300 bytes .../css/framepacks/book/images/a-thumb-flm.png | Bin 0 -> 1030 bytes .../css/framepacks/book/images/a-thumb-sqr-e.png | Bin 0 -> 1283 bytes .../framepacks/book/images/a-thumb-sqr-eext.png | Bin 0 -> 1863 bytes .../css/framepacks/book/images/a-thumb-sqr-ext.png | Bin 0 -> 1733 bytes .../css/framepacks/book/images/a-thumb-sqr.png | Bin 0 -> 1225 bytes .../css/framepacks/book/images/a-thumb-wd-e.png | Bin 0 -> 1073 bytes .../css/framepacks/book/images/a-thumb-wd-eext.png | Bin 0 -> 1371 bytes .../css/framepacks/book/images/a-thumb-wd-ext.png | Bin 0 -> 1265 bytes .../css/framepacks/book/images/a-thumb-wd.png | Bin 0 -> 991 bytes .../css/framepacks/book/images/thumb-dgt-e.png | Bin 0 -> 619 bytes .../css/framepacks/book/images/thumb-dgt-eext.png | Bin 0 -> 802 bytes .../css/framepacks/book/images/thumb-dgt-ext.png | Bin 0 -> 773 bytes .../css/framepacks/book/images/thumb-dgt.png | Bin 0 -> 575 bytes .../css/framepacks/book/images/thumb-flm-e.png | Bin 0 -> 613 bytes .../css/framepacks/book/images/thumb-flm-eext.png | Bin 0 -> 783 bytes .../css/framepacks/book/images/thumb-flm-ext.png | Bin 0 -> 625 bytes .../css/framepacks/book/images/thumb-flm.png | Bin 0 -> 587 bytes .../css/framepacks/book/images/thumb-sqr-e.png | Bin 0 -> 676 bytes .../css/framepacks/book/images/thumb-sqr-eext.png | Bin 0 -> 902 bytes .../css/framepacks/book/images/thumb-sqr-ext.png | Bin 0 -> 864 bytes .../css/framepacks/book/images/thumb-sqr.png | Bin 0 -> 648 bytes .../css/framepacks/book/images/thumb-wd-e.png | Bin 0 -> 600 bytes .../css/framepacks/book/images/thumb-wd-eext.png | Bin 0 -> 727 bytes .../css/framepacks/book/images/thumb-wd-ext.png | Bin 0 -> 695 bytes .../css/framepacks/book/images/thumb-wd.png | Bin 0 -> 575 bytes .../css/framepacks/darkglass/css/frame.css | 32 + .../framepacks/darkglass/images/thumb-dgt-e.png | Bin 0 -> 1876 bytes .../framepacks/darkglass/images/thumb-dgt-eext.png | Bin 0 -> 2132 bytes .../framepacks/darkglass/images/thumb-dgt-ext.png | Bin 0 -> 2097 bytes .../css/framepacks/darkglass/images/thumb-dgt.png | Bin 0 -> 1873 bytes .../framepacks/darkglass/images/thumb-flm-e.png | Bin 0 -> 1814 bytes .../framepacks/darkglass/images/thumb-flm-eext.png | Bin 0 -> 2092 bytes .../framepacks/darkglass/images/thumb-flm-ext.png | Bin 0 -> 2048 bytes .../css/framepacks/darkglass/images/thumb-flm.png | Bin 0 -> 1811 bytes .../framepacks/darkglass/images/thumb-sqr-e.png | Bin 0 -> 1896 bytes .../framepacks/darkglass/images/thumb-sqr-eext.png | Bin 0 -> 2220 bytes .../framepacks/darkglass/images/thumb-sqr-ext.png | Bin 0 -> 2187 bytes .../css/framepacks/darkglass/images/thumb-sqr.png | Bin 0 -> 3675 bytes .../css/framepacks/darkglass/images/thumb-wd-e.png | Bin 0 -> 1787 bytes .../framepacks/darkglass/images/thumb-wd-eext.png | Bin 0 -> 2051 bytes .../framepacks/darkglass/images/thumb-wd-ext.png | Bin 0 -> 2006 bytes .../css/framepacks/darkglass/images/thumb-wd.png | Bin 0 -> 1767 bytes .../css/framepacks/greydragon/css/frame.css | 43 + .../greydragon/css/framepacks/iphone/css/frame.css | 31 + .../css/framepacks/iphone/images/thumb-dgt-e.png | Bin 0 -> 2923 bytes .../framepacks/iphone/images/thumb-dgt-eext.png | Bin 0 -> 3544 bytes .../css/framepacks/iphone/images/thumb-dgt-ext.png | Bin 0 -> 3449 bytes .../css/framepacks/iphone/images/thumb-dgt.png | Bin 0 -> 2813 bytes .../css/framepacks/iphone/images/thumb-flm-e.png | Bin 0 -> 2734 bytes .../framepacks/iphone/images/thumb-flm-eext.png | Bin 0 -> 3414 bytes .../css/framepacks/iphone/images/thumb-flm-ext.png | Bin 0 -> 3272 bytes .../css/framepacks/iphone/images/thumb-flm.png | Bin 0 -> 2612 bytes .../css/framepacks/iphone/images/thumb-sqr-e.png | Bin 0 -> 2848 bytes .../framepacks/iphone/images/thumb-sqr-eext.png | Bin 0 -> 3930 bytes .../css/framepacks/iphone/images/thumb-sqr-ext.png | Bin 0 -> 3850 bytes .../css/framepacks/iphone/images/thumb-sqr.png | Bin 0 -> 2759 bytes .../css/framepacks/iphone/images/thumb-wd-e.png | Bin 0 -> 2769 bytes .../css/framepacks/iphone/images/thumb-wd-eext.png | Bin 0 -> 3108 bytes .../css/framepacks/iphone/images/thumb-wd-ext.png | Bin 0 -> 3012 bytes .../css/framepacks/iphone/images/thumb-wd.png | Bin 0 -> 2676 bytes .../greydragon/css/framepacks/iphoto/css/frame.css | 31 + .../css/framepacks/iphoto/images/thumb-dgt-e.png | Bin 0 -> 1093 bytes .../framepacks/iphoto/images/thumb-dgt-eext.png | Bin 0 -> 1294 bytes .../css/framepacks/iphoto/images/thumb-dgt-ext.png | Bin 0 -> 1251 bytes .../css/framepacks/iphoto/images/thumb-dgt.png | Bin 0 -> 1065 bytes .../css/framepacks/iphoto/images/thumb-flm-e.png | Bin 0 -> 1071 bytes .../framepacks/iphoto/images/thumb-flm-eext.png | Bin 0 -> 1258 bytes .../css/framepacks/iphoto/images/thumb-flm-ext.png | Bin 0 -> 1218 bytes .../css/framepacks/iphoto/images/thumb-flm.png | Bin 0 -> 1045 bytes .../css/framepacks/iphoto/images/thumb-sqr-e.png | Bin 0 -> 1141 bytes .../framepacks/iphoto/images/thumb-sqr-eext.png | Bin 0 -> 1382 bytes .../css/framepacks/iphoto/images/thumb-sqr-ext.png | Bin 0 -> 1342 bytes .../css/framepacks/iphoto/images/thumb-sqr.png | Bin 0 -> 1110 bytes .../css/framepacks/iphoto/images/thumb-wd-e.png | Bin 0 -> 1056 bytes .../css/framepacks/iphoto/images/thumb-wd-eext.png | Bin 0 -> 1209 bytes .../css/framepacks/iphoto/images/thumb-wd-ext.png | Bin 0 -> 1168 bytes .../css/framepacks/iphoto/images/thumb-wd.png | Bin 0 -> 1015 bytes .../css/framepacks/iphoto/views/frame.html.php | 5 + .../greydragon/css/framepacks/panel/css/frame.css | 37 + .../css/framepacks/roundcorners/css/frame.css | 44 + .../greydragon/css/framepacks/simple/css/frame.css | 39 + .../greydragon/css/framepacks/wall/css/frame.css | 48 + themes/greydragon/css/ipad.css | 20 + themes/greydragon/css/layout.css | 77 + themes/greydragon/css/menus.css | 58 + themes/greydragon/css/modules.css | 165 + themes/greydragon/css/normalize.css | 431 ++ themes/greydragon/css/rtl.css | 47 + themes/greydragon/css/screen.css | 17 + themes/greydragon/helpers/exif_event.php | 45 + themes/greydragon/helpers/greydragon_event.php | 92 + themes/greydragon/helpers/greydragon_installer.php | 30 + themes/greydragon/images/apple-touch-icon.png | Bin 0 -> 9420 bytes themes/greydragon/images/arrows_left.png | Bin 0 -> 1895 bytes themes/greydragon/images/arrows_right.png | Bin 0 -> 1826 bytes themes/greydragon/images/avatar.jpg | Bin 0 -> 1442 bytes themes/greydragon/images/blue-grad.png | Bin 0 -> 337 bytes themes/greydragon/images/button-grad-active-vs.png | Bin 0 -> 152 bytes themes/greydragon/images/button-grad-vs.png | Bin 0 -> 155 bytes themes/greydragon/images/close.png | Bin 0 -> 564 bytes themes/greydragon/images/donate.png | Bin 0 -> 399 bytes themes/greydragon/images/gallery.png | Bin 0 -> 627 bytes themes/greydragon/images/ico-allowed.png | Bin 0 -> 715 bytes themes/greydragon/images/ico-denied-inactive.png | Bin 0 -> 604 bytes themes/greydragon/images/ico-denied-passive.png | Bin 0 -> 916 bytes themes/greydragon/images/ico-denied.png | Bin 0 -> 715 bytes themes/greydragon/images/ico-error.png | Bin 0 -> 701 bytes themes/greydragon/images/ico-help.png | Bin 0 -> 786 bytes themes/greydragon/images/ico-info.png | Bin 0 -> 778 bytes themes/greydragon/images/ico-lock.png | Bin 0 -> 749 bytes themes/greydragon/images/ico-success-inactive.png | Bin 0 -> 261 bytes themes/greydragon/images/ico-success-passive.png | Bin 0 -> 561 bytes themes/greydragon/images/ico-success.png | Bin 0 -> 537 bytes themes/greydragon/images/ico-warning.png | Bin 0 -> 666 bytes themes/greydragon/images/missing-img.png | Bin 0 -> 33136 bytes themes/greydragon/js/gallery.ajax.custom.js | 14 + themes/greydragon/js/gallery.dialog.custom.js | 232 + themes/greydragon/js/jquery-ui.min.js | 7 + themes/greydragon/js/jquery.cycle.js | 1543 +++++ themes/greydragon/js/jquery.form.custom.js | 1076 +++ themes/greydragon/js/jquery.json.min.js | 31 + themes/greydragon/js/jquery.min.js | 4 + themes/greydragon/js/ui.support.js | 80 + themes/greydragon/libraries/MY_Theme_View.php | 925 +++ themes/greydragon/theme.info | 10 + themes/greydragon/thumbnail.png | Bin 0 -> 25791 bytes themes/greydragon/views/album.html.php | 68 + themes/greydragon/views/block.html.php | 29 + themes/greydragon/views/calpage.html.php | 257 + themes/greydragon/views/dynamic.html.php | 38 + themes/greydragon/views/exif_sidebar.html.php | 18 + themes/greydragon/views/info_block.html.php | 29 + themes/greydragon/views/login_ajax.html.php | 10 + themes/greydragon/views/movie.html.php | 38 + themes/greydragon/views/no_sidebar.html.php | 19 + themes/greydragon/views/page.html.php | 267 + themes/greydragon/views/paginator.html.php | 216 + themes/greydragon/views/photo.html.php | 129 + themes/greydragon/views/rootpage.html.php | 59 + themes/greydragon/views/rootpage.html.php_fix | 46 + themes/greydragon/views/rss_block.html.php | 30 + themes/greydragon/views/search.html.php | 35 + themes/greydragon/views/sidebar.html.php | 27 + themes/greydragon/views/user_profile.html.php | 44 + .../admin/controllers/admin_theme_options.php | 866 +++ .../admin/views/admin_theme_options.html.php | 38 + themes/greydragon_old/changelog.txt | 616 ++ themes/greydragon_old/controllers/greydragon.php | 14 + themes/greydragon_old/css/base.css | 321 + .../css/colorpacks/blackhawk/css/colors.css | 170 + .../colorpacks/blackhawk/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../css/colorpacks/blackhawk/images/colorpack.png | Bin 0 -> 307 bytes .../css/colorpacks/blackhawk/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/blackhawk/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/blackhawk/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/blackhawk/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/blackhawk/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/blackhawk/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/blackhawk/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/blackhawk/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/blackhawk/images/ui-icons.png | Bin 0 -> 13169 bytes .../blackhawk/images/view-calendar-b.png | Bin 0 -> 653 bytes .../colorpacks/blackhawk/images/view-calendar.png | Bin 0 -> 429 bytes .../blackhawk/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/blackhawk/images/view-comments.png | Bin 0 -> 492 bytes .../blackhawk/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../colorpacks/blackhawk/images/view-fullsize.png | Bin 0 -> 407 bytes .../colorpacks/blackhawk/images/view-info-b.png | Bin 0 -> 493 bytes .../colorpacks/blackhawk/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/blackhawk/images/view-info.png | Bin 0 -> 938 bytes .../blackhawk/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/carbon/css/colors.css | 188 + .../css/colorpacks/carbon/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../css/colorpacks/carbon/images/colorpack.png | Bin 0 -> 307 bytes .../css/colorpacks/carbon/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/carbon/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/carbon/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/carbon/images/ico-info.png | Bin 0 -> 778 bytes .../css/colorpacks/carbon/images/ico-warning.png | Bin 0 -> 666 bytes .../css/colorpacks/carbon/images/loading-large.gif | Bin 0 -> 8238 bytes .../css/colorpacks/carbon/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/carbon/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/carbon/images/section.png | Bin 0 -> 192 bytes .../css/colorpacks/carbon/images/ui-icons.png | Bin 0 -> 13162 bytes .../colorpacks/carbon/images/view-calendar-b.png | Bin 0 -> 653 bytes .../css/colorpacks/carbon/images/view-calendar.png | Bin 0 -> 429 bytes .../colorpacks/carbon/images/view-comments-b.png | Bin 0 -> 600 bytes .../css/colorpacks/carbon/images/view-comments.png | Bin 0 -> 492 bytes .../colorpacks/carbon/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../css/colorpacks/carbon/images/view-fullsize.png | Bin 0 -> 407 bytes .../css/colorpacks/carbon/images/view-info-b.png | Bin 0 -> 493 bytes .../css/colorpacks/carbon/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/carbon/images/view-info.png | Bin 0 -> 938 bytes .../colorpacks/carbon/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/greydragon/css/colors.css | 174 + .../colorpacks/greydragon/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../greydragon/images/background-bottom.gif | Bin 0 -> 1040 bytes .../greydragon/images/background-top.gif | Bin 0 -> 862 bytes .../colorpacks/greydragon/images/background.gif | Bin 0 -> 1098 bytes .../css/colorpacks/greydragon/images/colorpack.png | Bin 0 -> 414 bytes .../css/colorpacks/greydragon/images/footer.png | Bin 0 -> 376 bytes .../css/colorpacks/greydragon/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/greydragon/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/greydragon/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/greydragon/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/greydragon/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/greydragon/images/image-thumb.gif | Bin 0 -> 830 bytes .../colorpacks/greydragon/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/greydragon/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/greydragon/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/greydragon/images/section.png | Bin 0 -> 192 bytes .../css/colorpacks/greydragon/images/ui-icons.png | Bin 0 -> 13160 bytes .../greydragon/images/view-calendar-b.png | Bin 0 -> 653 bytes .../colorpacks/greydragon/images/view-calendar.png | Bin 0 -> 429 bytes .../greydragon/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/greydragon/images/view-comments.png | Bin 0 -> 492 bytes .../greydragon/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../colorpacks/greydragon/images/view-fullsize.png | Bin 0 -> 407 bytes .../colorpacks/greydragon/images/view-info-b.png | Bin 0 -> 493 bytes .../colorpacks/greydragon/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/greydragon/images/view-info.png | Bin 0 -> 938 bytes .../greydragon/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/roundrobin/css/colors.css | 193 + .../colorpacks/roundrobin/images/ajax-loading.gif | Bin 0 -> 2545 bytes .../css/colorpacks/roundrobin/images/bg-body.jpg | Bin 0 -> 13916 bytes .../css/colorpacks/roundrobin/images/bg-header.jpg | Bin 0 -> 13609 bytes .../css/colorpacks/roundrobin/images/colorpack.png | Bin 0 -> 475 bytes .../css/colorpacks/roundrobin/images/gallery.png | Bin 0 -> 553 bytes .../css/colorpacks/roundrobin/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/roundrobin/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/roundrobin/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/roundrobin/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/roundrobin/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/roundrobin/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/roundrobin/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/roundrobin/images/search.png | Bin 0 -> 550 bytes .../css/colorpacks/roundrobin/images/ui-icons.png | Bin 0 -> 14168 bytes .../roundrobin/images/view-calendar-b.png | Bin 0 -> 891 bytes .../colorpacks/roundrobin/images/view-calendar.png | Bin 0 -> 260 bytes .../roundrobin/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/roundrobin/images/view-comments.png | Bin 0 -> 298 bytes .../roundrobin/images/view-fullsize-b.png | Bin 0 -> 670 bytes .../colorpacks/roundrobin/images/view-fullsize.png | Bin 0 -> 275 bytes .../colorpacks/roundrobin/images/view-info-b.png | Bin 0 -> 698 bytes .../colorpacks/roundrobin/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/roundrobin/images/view-info.png | Bin 0 -> 286 bytes .../roundrobin/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/slateblue/css/colors.css | 177 + .../colorpacks/slateblue/images/ajax-loading.gif | Bin 0 -> 4782 bytes .../css/colorpacks/slateblue/images/background.jpg | Bin 0 -> 907 bytes .../css/colorpacks/slateblue/images/colorpack.png | Bin 0 -> 414 bytes .../css/colorpacks/slateblue/images/footer.png | Bin 0 -> 376 bytes .../css/colorpacks/slateblue/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/slateblue/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/slateblue/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/slateblue/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/slateblue/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/slateblue/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/slateblue/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/slateblue/images/search.png | Bin 0 -> 969 bytes .../css/colorpacks/slateblue/images/section.png | Bin 0 -> 192 bytes .../css/colorpacks/slateblue/images/ui-icons.png | Bin 0 -> 13160 bytes .../slateblue/images/view-calendar-b.png | Bin 0 -> 653 bytes .../colorpacks/slateblue/images/view-calendar.png | Bin 0 -> 429 bytes .../slateblue/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/slateblue/images/view-comments.png | Bin 0 -> 492 bytes .../slateblue/images/view-fullsize-b.png | Bin 0 -> 699 bytes .../colorpacks/slateblue/images/view-fullsize.png | Bin 0 -> 407 bytes .../colorpacks/slateblue/images/view-info-b.png | Bin 0 -> 493 bytes .../colorpacks/slateblue/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/slateblue/images/view-info.png | Bin 0 -> 938 bytes .../slateblue/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/whitehawk/css/colors.css | 174 + .../colorpacks/whitehawk/images/ajax-loading.gif | Bin 0 -> 2545 bytes .../css/colorpacks/whitehawk/images/colorpack.png | Bin 0 -> 417 bytes .../css/colorpacks/whitehawk/images/gallery.png | Bin 0 -> 1383 bytes .../css/colorpacks/whitehawk/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/whitehawk/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/whitehawk/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/whitehawk/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/whitehawk/images/ico-warning.png | Bin 0 -> 666 bytes .../colorpacks/whitehawk/images/loading-large.gif | Bin 0 -> 8238 bytes .../colorpacks/whitehawk/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/whitehawk/images/search.png | Bin 0 -> 571 bytes .../css/colorpacks/whitehawk/images/ui-icons.png | Bin 0 -> 9914 bytes .../whitehawk/images/view-calendar-b.png | Bin 0 -> 891 bytes .../colorpacks/whitehawk/images/view-calendar.png | Bin 0 -> 260 bytes .../whitehawk/images/view-comments-b.png | Bin 0 -> 600 bytes .../colorpacks/whitehawk/images/view-comments.png | Bin 0 -> 298 bytes .../whitehawk/images/view-fullsize-b.png | Bin 0 -> 670 bytes .../colorpacks/whitehawk/images/view-fullsize.png | Bin 0 -> 275 bytes .../colorpacks/whitehawk/images/view-info-b.png | Bin 0 -> 698 bytes .../colorpacks/whitehawk/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/whitehawk/images/view-info.png | Bin 0 -> 286 bytes .../whitehawk/images/view-slideshow-b.png | Bin 0 -> 1179 bytes .../css/colorpacks/wind/css/colors.css | 176 + .../css/colorpacks/wind/images/ajax-loading.gif | Bin 0 -> 2545 bytes .../css/colorpacks/wind/images/colorpack.png | Bin 0 -> 397 bytes .../css/colorpacks/wind/images/ico-album.png | Bin 0 -> 351 bytes .../css/colorpacks/wind/images/ico-error.png | Bin 0 -> 701 bytes .../css/colorpacks/wind/images/ico-help.png | Bin 0 -> 786 bytes .../css/colorpacks/wind/images/ico-info.png | Bin 0 -> 778 bytes .../colorpacks/wind/images/ico-view-fullsize.png | Bin 0 -> 1207 bytes .../wind/images/ico-view-slideshow-rtl.png | Bin 0 -> 1013 bytes .../colorpacks/wind/images/ico-view-slideshow.png | Bin 0 -> 854 bytes .../css/colorpacks/wind/images/ico-warning.png | Bin 0 -> 666 bytes .../css/colorpacks/wind/images/loading-large.gif | Bin 0 -> 8238 bytes .../css/colorpacks/wind/images/loading-small.gif | Bin 0 -> 673 bytes .../css/colorpacks/wind/images/section.png | Bin 0 -> 314 bytes .../css/colorpacks/wind/images/ui-icons.png | Bin 0 -> 10682 bytes .../css/colorpacks/wind/images/view-calendar.png | Bin 0 -> 637 bytes .../css/colorpacks/wind/images/view-comments-b.png | Bin 0 -> 892 bytes .../css/colorpacks/wind/images/view-comments.png | Bin 0 -> 347 bytes .../css/colorpacks/wind/images/view-fullsize.png | Bin 0 -> 365 bytes .../css/colorpacks/wind/images/view-info-b.png | Bin 0 -> 698 bytes .../css/colorpacks/wind/images/view-info-o.png | Bin 0 -> 259 bytes .../css/colorpacks/wind/images/view-info.png | Bin 0 -> 383 bytes .../colorpacks/wind/images/view-slideshow-b.png | Bin 0 -> 1179 bytes themes/greydragon_old/css/custom.css | 2 + themes/greydragon_old/css/forms.css | 113 + .../css/framepacks/android/css/frame.css | 32 + .../css/framepacks/android/images/thumb-dgt-e.png | Bin 0 -> 1482 bytes .../framepacks/android/images/thumb-dgt-eext.png | Bin 0 -> 1939 bytes .../framepacks/android/images/thumb-dgt-ext.png | Bin 0 -> 1791 bytes .../css/framepacks/android/images/thumb-dgt.png | Bin 0 -> 1447 bytes .../css/framepacks/android/images/thumb-flm-e.png | Bin 0 -> 1443 bytes .../framepacks/android/images/thumb-flm-eext.png | Bin 0 -> 1796 bytes .../framepacks/android/images/thumb-flm-ext.png | Bin 0 -> 1737 bytes .../css/framepacks/android/images/thumb-flm.png | Bin 0 -> 1413 bytes .../css/framepacks/android/images/thumb-sqr-e.png | Bin 0 -> 1579 bytes .../framepacks/android/images/thumb-sqr-eext.png | Bin 0 -> 1916 bytes .../framepacks/android/images/thumb-sqr-ext.png | Bin 0 -> 1900 bytes .../css/framepacks/android/images/thumb-sqr.png | Bin 0 -> 1544 bytes .../css/framepacks/android/images/thumb-wd-e.png | Bin 0 -> 1428 bytes .../framepacks/android/images/thumb-wd-eext.png | Bin 0 -> 1720 bytes .../css/framepacks/android/images/thumb-wd-ext.png | Bin 0 -> 1659 bytes .../css/framepacks/android/images/thumb-wd.png | Bin 0 -> 1379 bytes .../css/framepacks/book/css/frame.css | 59 + .../css/framepacks/book/images/a-thumb-dgt-e.png | Bin 0 -> 1130 bytes .../framepacks/book/images/a-thumb-dgt-eext.png | Bin 0 -> 1579 bytes .../css/framepacks/book/images/a-thumb-dgt-ext.png | Bin 0 -> 1513 bytes .../css/framepacks/book/images/a-thumb-dgt.png | Bin 0 -> 1062 bytes .../css/framepacks/book/images/a-thumb-flm-e.png | Bin 0 -> 1105 bytes .../framepacks/book/images/a-thumb-flm-eext.png | Bin 0 -> 1478 bytes .../css/framepacks/book/images/a-thumb-flm-ext.png | Bin 0 -> 1300 bytes .../css/framepacks/book/images/a-thumb-flm.png | Bin 0 -> 1030 bytes .../css/framepacks/book/images/a-thumb-sqr-e.png | Bin 0 -> 1283 bytes .../framepacks/book/images/a-thumb-sqr-eext.png | Bin 0 -> 1863 bytes .../css/framepacks/book/images/a-thumb-sqr-ext.png | Bin 0 -> 1733 bytes .../css/framepacks/book/images/a-thumb-sqr.png | Bin 0 -> 1225 bytes .../css/framepacks/book/images/a-thumb-wd-e.png | Bin 0 -> 1073 bytes .../css/framepacks/book/images/a-thumb-wd-eext.png | Bin 0 -> 1371 bytes .../css/framepacks/book/images/a-thumb-wd-ext.png | Bin 0 -> 1265 bytes .../css/framepacks/book/images/a-thumb-wd.png | Bin 0 -> 991 bytes .../css/framepacks/book/images/thumb-dgt-e.png | Bin 0 -> 619 bytes .../css/framepacks/book/images/thumb-dgt-eext.png | Bin 0 -> 802 bytes .../css/framepacks/book/images/thumb-dgt-ext.png | Bin 0 -> 773 bytes .../css/framepacks/book/images/thumb-dgt.png | Bin 0 -> 575 bytes .../css/framepacks/book/images/thumb-flm-e.png | Bin 0 -> 613 bytes .../css/framepacks/book/images/thumb-flm-eext.png | Bin 0 -> 783 bytes .../css/framepacks/book/images/thumb-flm-ext.png | Bin 0 -> 625 bytes .../css/framepacks/book/images/thumb-flm.png | Bin 0 -> 587 bytes .../css/framepacks/book/images/thumb-sqr-e.png | Bin 0 -> 676 bytes .../css/framepacks/book/images/thumb-sqr-eext.png | Bin 0 -> 902 bytes .../css/framepacks/book/images/thumb-sqr-ext.png | Bin 0 -> 864 bytes .../css/framepacks/book/images/thumb-sqr.png | Bin 0 -> 648 bytes .../css/framepacks/book/images/thumb-wd-e.png | Bin 0 -> 600 bytes .../css/framepacks/book/images/thumb-wd-eext.png | Bin 0 -> 727 bytes .../css/framepacks/book/images/thumb-wd-ext.png | Bin 0 -> 695 bytes .../css/framepacks/book/images/thumb-wd.png | Bin 0 -> 575 bytes .../css/framepacks/darkglass/css/frame.css | 32 + .../framepacks/darkglass/images/thumb-dgt-e.png | Bin 0 -> 1876 bytes .../framepacks/darkglass/images/thumb-dgt-eext.png | Bin 0 -> 2132 bytes .../framepacks/darkglass/images/thumb-dgt-ext.png | Bin 0 -> 2097 bytes .../css/framepacks/darkglass/images/thumb-dgt.png | Bin 0 -> 1873 bytes .../framepacks/darkglass/images/thumb-flm-e.png | Bin 0 -> 1814 bytes .../framepacks/darkglass/images/thumb-flm-eext.png | Bin 0 -> 2092 bytes .../framepacks/darkglass/images/thumb-flm-ext.png | Bin 0 -> 2048 bytes .../css/framepacks/darkglass/images/thumb-flm.png | Bin 0 -> 1811 bytes .../framepacks/darkglass/images/thumb-sqr-e.png | Bin 0 -> 1896 bytes .../framepacks/darkglass/images/thumb-sqr-eext.png | Bin 0 -> 2220 bytes .../framepacks/darkglass/images/thumb-sqr-ext.png | Bin 0 -> 2187 bytes .../css/framepacks/darkglass/images/thumb-sqr.png | Bin 0 -> 3675 bytes .../css/framepacks/darkglass/images/thumb-wd-e.png | Bin 0 -> 1787 bytes .../framepacks/darkglass/images/thumb-wd-eext.png | Bin 0 -> 2051 bytes .../framepacks/darkglass/images/thumb-wd-ext.png | Bin 0 -> 2006 bytes .../css/framepacks/darkglass/images/thumb-wd.png | Bin 0 -> 1767 bytes .../css/framepacks/greydragon/css/frame.css | 40 + .../css/framepacks/iphone/css/frame.css | 31 + .../css/framepacks/iphone/images/thumb-dgt-e.png | Bin 0 -> 2923 bytes .../framepacks/iphone/images/thumb-dgt-eext.png | Bin 0 -> 3544 bytes .../css/framepacks/iphone/images/thumb-dgt-ext.png | Bin 0 -> 3449 bytes .../css/framepacks/iphone/images/thumb-dgt.png | Bin 0 -> 2813 bytes .../css/framepacks/iphone/images/thumb-flm-e.png | Bin 0 -> 2734 bytes .../framepacks/iphone/images/thumb-flm-eext.png | Bin 0 -> 3414 bytes .../css/framepacks/iphone/images/thumb-flm-ext.png | Bin 0 -> 3272 bytes .../css/framepacks/iphone/images/thumb-flm.png | Bin 0 -> 2612 bytes .../css/framepacks/iphone/images/thumb-sqr-e.png | Bin 0 -> 2848 bytes .../framepacks/iphone/images/thumb-sqr-eext.png | Bin 0 -> 3930 bytes .../css/framepacks/iphone/images/thumb-sqr-ext.png | Bin 0 -> 3850 bytes .../css/framepacks/iphone/images/thumb-sqr.png | Bin 0 -> 2759 bytes .../css/framepacks/iphone/images/thumb-wd-e.png | Bin 0 -> 2769 bytes .../css/framepacks/iphone/images/thumb-wd-eext.png | Bin 0 -> 3108 bytes .../css/framepacks/iphone/images/thumb-wd-ext.png | Bin 0 -> 3012 bytes .../css/framepacks/iphone/images/thumb-wd.png | Bin 0 -> 2676 bytes .../css/framepacks/iphoto/css/frame.css | 31 + .../css/framepacks/iphoto/images/thumb-dgt-e.png | Bin 0 -> 1093 bytes .../framepacks/iphoto/images/thumb-dgt-eext.png | Bin 0 -> 1294 bytes .../css/framepacks/iphoto/images/thumb-dgt-ext.png | Bin 0 -> 1251 bytes .../css/framepacks/iphoto/images/thumb-dgt.png | Bin 0 -> 1065 bytes .../css/framepacks/iphoto/images/thumb-flm-e.png | Bin 0 -> 1071 bytes .../framepacks/iphoto/images/thumb-flm-eext.png | Bin 0 -> 1258 bytes .../css/framepacks/iphoto/images/thumb-flm-ext.png | Bin 0 -> 1218 bytes .../css/framepacks/iphoto/images/thumb-flm.png | Bin 0 -> 1045 bytes .../css/framepacks/iphoto/images/thumb-sqr-e.png | Bin 0 -> 1141 bytes .../framepacks/iphoto/images/thumb-sqr-eext.png | Bin 0 -> 1382 bytes .../css/framepacks/iphoto/images/thumb-sqr-ext.png | Bin 0 -> 1342 bytes .../css/framepacks/iphoto/images/thumb-sqr.png | Bin 0 -> 1110 bytes .../css/framepacks/iphoto/images/thumb-wd-e.png | Bin 0 -> 1056 bytes .../css/framepacks/iphoto/images/thumb-wd-eext.png | Bin 0 -> 1209 bytes .../css/framepacks/iphoto/images/thumb-wd-ext.png | Bin 0 -> 1168 bytes .../css/framepacks/iphoto/images/thumb-wd.png | Bin 0 -> 1015 bytes .../css/framepacks/iphoto/views/frame.html.php | 5 + .../css/framepacks/panel/css/frame.css | 35 + .../css/framepacks/roundcorners/css/frame.css | 42 + .../css/framepacks/simple/css/frame.css | 33 + .../css/framepacks/wall/css/frame.css | 46 + themes/greydragon_old/css/gd_common.css | 59 + themes/greydragon_old/css/ipad.css | 20 + themes/greydragon_old/css/layout.css | 67 + themes/greydragon_old/css/menus.css | 58 + themes/greydragon_old/css/modules.css | 164 + themes/greydragon_old/css/normalize.css | 431 ++ themes/greydragon_old/css/rtl.css | 47 + themes/greydragon_old/css/screen.css | 17 + themes/greydragon_old/helpers/exif_event.php | 45 + themes/greydragon_old/helpers/greydragon_event.php | 92 + .../helpers/greydragon_installer.php | 30 + themes/greydragon_old/helpers/greydragon_theme.php | 20 + themes/greydragon_old/images/apple-touch-icon.png | Bin 0 -> 9420 bytes themes/greydragon_old/images/arrows_left.png | Bin 0 -> 1895 bytes themes/greydragon_old/images/arrows_right.png | Bin 0 -> 1826 bytes themes/greydragon_old/images/avatar.jpg | Bin 0 -> 1442 bytes themes/greydragon_old/images/blue-grad.png | Bin 0 -> 337 bytes .../images/button-grad-active-vs.png | Bin 0 -> 152 bytes themes/greydragon_old/images/button-grad-vs.png | Bin 0 -> 155 bytes themes/greydragon_old/images/close.png | Bin 0 -> 564 bytes themes/greydragon_old/images/donate.png | Bin 0 -> 399 bytes themes/greydragon_old/images/gallery.png | Bin 0 -> 627 bytes themes/greydragon_old/images/ico-allowed.png | Bin 0 -> 715 bytes .../greydragon_old/images/ico-denied-inactive.png | Bin 0 -> 604 bytes .../greydragon_old/images/ico-denied-passive.png | Bin 0 -> 916 bytes themes/greydragon_old/images/ico-denied.png | Bin 0 -> 715 bytes themes/greydragon_old/images/ico-error.png | Bin 0 -> 701 bytes themes/greydragon_old/images/ico-help.png | Bin 0 -> 786 bytes themes/greydragon_old/images/ico-info.png | Bin 0 -> 778 bytes themes/greydragon_old/images/ico-lock.png | Bin 0 -> 749 bytes .../greydragon_old/images/ico-success-inactive.png | Bin 0 -> 261 bytes .../greydragon_old/images/ico-success-passive.png | Bin 0 -> 561 bytes themes/greydragon_old/images/ico-success.png | Bin 0 -> 537 bytes themes/greydragon_old/images/ico-warning.png | Bin 0 -> 666 bytes themes/greydragon_old/images/missing-img.png | Bin 0 -> 33136 bytes themes/greydragon_old/js/jquery.cycle.js | 1544 +++++ themes/greydragon_old/js/ui.support.js | 80 + themes/greydragon_old/libraries/MY_Theme_View.php | 902 +++ themes/greydragon_old/module.info | 9 + themes/greydragon_old/theme.info | 11 + themes/greydragon_old/thumbnail.png | Bin 0 -> 25791 bytes themes/greydragon_old/views/album.html.php | 68 + themes/greydragon_old/views/block.html.php | 29 + themes/greydragon_old/views/calpage.html.php | 257 + themes/greydragon_old/views/dynamic.html.php | 38 + themes/greydragon_old/views/exif_sidebar.html.php | 18 + .../greydragon_old/views/gd_admin_include.html.php | 180 + themes/greydragon_old/views/info_block.html.php | 29 + themes/greydragon_old/views/movie.html.php | 38 + themes/greydragon_old/views/no_sidebar.html.php | 19 + themes/greydragon_old/views/page.html.php | 259 + themes/greydragon_old/views/paginator.html.php | 194 + themes/greydragon_old/views/photo.html.php | 124 + themes/greydragon_old/views/rootpage.html.php | 59 + themes/greydragon_old/views/rootpage.html.php_fix | 46 + themes/greydragon_old/views/rss_block.html.php | 30 + themes/greydragon_old/views/search.html.php | 35 + themes/greydragon_old/views/sidebar.html.php | 27 + themes/greydragon_old/views/user_profile.html.php | 44 + themes/wind/css/fix-ie.css | 59 + themes/wind/css/screen-rtl.css | 333 + themes/wind/css/screen.css | 1066 +++ .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_55_fbec88_40x100.png | Bin 0 -> 182 bytes .../images/ui-bg_glass_75_d0e5f5_1x400.png | Bin 0 -> 124 bytes .../images/ui-bg_glass_85_dfeffc_1x400.png | Bin 0 -> 123 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../images/ui-bg_gloss-wave_55_5c9ccc_500x100.png | Bin 0 -> 4033 bytes .../images/ui-bg_inset-hard_100_f5f8f9_1x100.png | Bin 0 -> 104 bytes .../images/ui-bg_inset-hard_100_fcfdfd_1x100.png | Bin 0 -> 88 bytes .../themeroller/images/ui-icons_217bc0_256x240.png | Bin 0 -> 7638 bytes .../themeroller/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 7626 bytes .../themeroller/images/ui-icons_469bdd_256x240.png | Bin 0 -> 5399 bytes .../themeroller/images/ui-icons_6da8d5_256x240.png | Bin 0 -> 8447 bytes .../themeroller/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4379 bytes .../themeroller/images/ui-icons_d8e7f3_256x240.png | Bin 0 -> 4379 bytes .../themeroller/images/ui-icons_f9bd01_256x240.png | Bin 0 -> 4379 bytes themes/wind/css/themeroller/ui.base.css | 7 + themes/wind/css/themeroller/ui.core.css | 37 + themes/wind/css/themeroller/ui.datepicker.css | 62 + themes/wind/css/themeroller/ui.dialog.css | 13 + themes/wind/css/themeroller/ui.progressbar.css | 4 + themes/wind/css/themeroller/ui.resizable.css | 13 + themes/wind/css/themeroller/ui.tabs.css | 9 + themes/wind/css/themeroller/ui.theme.css | 243 + themes/wind/images/avatar.jpg | Bin 0 -> 1172 bytes themes/wind/images/ico-album.png | Bin 0 -> 397 bytes themes/wind/images/ico-denied-inactive.png | Bin 0 -> 604 bytes themes/wind/images/ico-denied-passive.png | Bin 0 -> 916 bytes themes/wind/images/ico-denied.png | Bin 0 -> 715 bytes themes/wind/images/ico-error.png | Bin 0 -> 701 bytes themes/wind/images/ico-help.png | Bin 0 -> 786 bytes themes/wind/images/ico-info.png | Bin 0 -> 778 bytes themes/wind/images/ico-lock.png | Bin 0 -> 749 bytes themes/wind/images/ico-print.png | Bin 0 -> 989 bytes themes/wind/images/ico-separator-rtl.gif | Bin 0 -> 106 bytes themes/wind/images/ico-separator.gif | Bin 0 -> 106 bytes themes/wind/images/ico-success-inactive.png | Bin 0 -> 476 bytes themes/wind/images/ico-success-passive.png | Bin 0 -> 617 bytes themes/wind/images/ico-success.png | Bin 0 -> 537 bytes themes/wind/images/ico-view-comments.png | Bin 0 -> 768 bytes themes/wind/images/ico-view-fullsize.png | Bin 0 -> 1046 bytes themes/wind/images/ico-view-slideshow-rtl.png | Bin 0 -> 1137 bytes themes/wind/images/ico-view-slideshow.png | Bin 0 -> 960 bytes themes/wind/images/ico-warning.png | Bin 0 -> 666 bytes themes/wind/images/loading-large.gif | Bin 0 -> 8238 bytes themes/wind/images/loading-small.gif | Bin 0 -> 673 bytes themes/wind/images/select-photos-backg.png | Bin 0 -> 1154 bytes themes/wind/js/ui.init.js | 118 + themes/wind/theme.info | 10 + themes/wind/thumbnail.png | Bin 0 -> 26927 bytes themes/wind/views/album.html.php | 47 + themes/wind/views/block.html.php | 10 + themes/wind/views/dynamic.html.php | 29 + themes/wind/views/movie.html.php | 19 + themes/wind/views/no_sidebar.html.php | 11 + themes/wind/views/page.html.php | 153 + themes/wind/views/paginator.html.php | 87 + themes/wind/views/photo.html.php | 51 + themes/wind/views/sidebar.html.php | 16 + 1476 files changed, 103573 insertions(+) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 LICENSE create mode 100644 README create mode 100644 application/Bootstrap.php create mode 100644 application/config/config.php create mode 100755 bin/.htaccess create mode 100644 bin/README create mode 100644 index.php create mode 100644 installer/cli.php create mode 100644 installer/database_config.php create mode 100644 installer/index.php create mode 100755 installer/init_var.php create mode 100644 installer/install.css create mode 100644 installer/install.sql create mode 100644 installer/installer.php create mode 100644 installer/views/already_installed.html.php create mode 100644 installer/views/db_not_empty.html.php create mode 100644 installer/views/environment_errors.html.php create mode 100644 installer/views/get_db_info.html.php create mode 100644 installer/views/install.html.php create mode 100644 installer/views/invalid_db_info.html.php create mode 100644 installer/views/invalid_db_version.html.php create mode 100644 installer/views/missing_db.html.php create mode 100644 installer/views/oops.html.php create mode 100644 installer/views/success.html.php create mode 100644 installer/web.php create mode 100644 lib/flowplayer.controls.swf.php create mode 100644 lib/flowplayer.js create mode 100644 lib/flowplayer.pseudostreaming-byterange.swf.php create mode 100644 lib/flowplayer.pseudostreaming.swf.php create mode 100644 lib/flowplayer.swf.php create mode 100644 lib/gallery.ajax.js create mode 100644 lib/gallery.common.js create mode 100644 lib/gallery.dialog.js create mode 100644 lib/gallery.in_place_edit.js create mode 100644 lib/gallery.panel.js create mode 100644 lib/gallery.show_full_size.js create mode 100644 lib/images/Galerie-Logo.png create mode 100644 lib/images/apple-touch-icon.png create mode 100644 lib/images/favicon.ico create mode 100644 lib/images/logo.png create mode 100644 lib/jquery-ui.js create mode 100644 lib/jquery.MultiFile.js create mode 100644 lib/jquery.autocomplete.css create mode 100644 lib/jquery.autocomplete.js create mode 100644 lib/jquery.cookie.js create mode 100644 lib/jquery.form.js create mode 100644 lib/jquery.jeditable.js create mode 100644 lib/jquery.js create mode 100644 lib/jquery.localscroll.js create mode 100644 lib/jquery.scrollTo.js create mode 100644 lib/json2-min.js create mode 100644 lib/superfish/css/superfish.css create mode 100644 lib/superfish/images/arrows-ffffff-rtl.png create mode 100644 lib/superfish/images/arrows-ffffff.png create mode 100644 lib/superfish/images/shadow.png create mode 100644 lib/superfish/js/superfish.js create mode 100644 lib/swfobject.js create mode 100644 lib/uploadify/cancel.png create mode 100644 lib/uploadify/jquery.uploadify.min.js create mode 100644 lib/uploadify/uploadify.allglyphs.swf.php create mode 100644 lib/uploadify/uploadify.css create mode 100644 lib/uploadify/uploadify.fla create mode 100644 lib/uploadify/uploadify.swf.php create mode 100644 lib/yui/base-min.css create mode 100644 lib/yui/reset-fonts-grids.css create mode 100644 modules/akismet/controllers/admin_akismet.php create mode 100644 modules/akismet/helpers/akismet.php create mode 100644 modules/akismet/helpers/akismet_event.php create mode 100644 modules/akismet/helpers/akismet_installer.php create mode 100644 modules/akismet/module.info create mode 100644 modules/akismet/views/admin_akismet.html.php create mode 100644 modules/akismet/views/admin_akismet_stats.html.php create mode 100644 modules/calendarview/controllers/calendarview.php create mode 100644 modules/calendarview/css/calendarview_calendar.css create mode 100644 modules/calendarview/css/calendarview_menu.css create mode 100644 modules/calendarview/helpers/calendarview.php create mode 100644 modules/calendarview/helpers/calendarview_block.php create mode 100644 modules/calendarview/helpers/calendarview_event.php create mode 100644 modules/calendarview/helpers/calendarview_installer.php create mode 100644 modules/calendarview/helpers/calendarview_theme.php create mode 100644 modules/calendarview/images/ico-view-calendarview.png create mode 100644 modules/calendarview/libraries/PHPCalendar.php create mode 100644 modules/calendarview/module.info create mode 100644 modules/calendarview/views/calendarview_sidebar.html.php create mode 100644 modules/calendarview/views/calendarview_year.html.php create mode 100644 modules/calendarview/views/user_profile_calendarview.html.php create mode 100644 modules/comment/controllers/admin_comments.php create mode 100644 modules/comment/controllers/admin_manage_comments.php create mode 100644 modules/comment/controllers/comments.php create mode 100644 modules/comment/css/comment.css create mode 100644 modules/comment/helpers/comment.php create mode 100644 modules/comment/helpers/comment_block.php create mode 100644 modules/comment/helpers/comment_event.php create mode 100644 modules/comment/helpers/comment_installer.php create mode 100644 modules/comment/helpers/comment_rest.php create mode 100644 modules/comment/helpers/comment_rss.php create mode 100644 modules/comment/helpers/comment_theme.php create mode 100644 modules/comment/helpers/comments_rest.php create mode 100644 modules/comment/helpers/item_comments_rest.php create mode 100644 modules/comment/js/comment.js create mode 100644 modules/comment/models/comment.php create mode 100644 modules/comment/module.info create mode 100644 modules/comment/views/admin_block_recent_comments.html.php create mode 100644 modules/comment/views/admin_comments.html.php create mode 100644 modules/comment/views/admin_manage_comments.html.php create mode 100644 modules/comment/views/admin_manage_comments_queue.html.php create mode 100644 modules/comment/views/comment.html.php create mode 100644 modules/comment/views/comment.mrss.php create mode 100644 modules/comment/views/comments.html.php create mode 100644 modules/comment/views/user_profile_comments.html.php create mode 100644 modules/downloadalbum/controllers/downloadalbum.php create mode 100644 modules/downloadalbum/css/downloadalbum_menu.css create mode 100644 modules/downloadalbum/helpers/downloadalbum_event.php create mode 100644 modules/downloadalbum/helpers/downloadalbum_theme.php create mode 100644 modules/downloadalbum/images/ico-view-downloadalbum.png create mode 100644 modules/downloadalbum/module.info create mode 100644 modules/downloadfullsize/controllers/admin_downloadfullsize.php create mode 100644 modules/downloadfullsize/controllers/downloadfullsize.php create mode 100644 modules/downloadfullsize/css/downloadfullsize_menu.css create mode 100644 modules/downloadfullsize/helpers/downloadfullsize_block.php create mode 100644 modules/downloadfullsize/helpers/downloadfullsize_event.php create mode 100644 modules/downloadfullsize/helpers/downloadfullsize_theme.php create mode 100644 modules/downloadfullsize/images/ico-view-downloadfullsize.png create mode 100644 modules/downloadfullsize/module.info create mode 100644 modules/downloadfullsize/views/admin_downloadfullsize.html.php create mode 100644 modules/downloadfullsize/views/downloadfullsize_block.html.php create mode 100644 modules/exif/controllers/exif.php create mode 100644 modules/exif/helpers/exif.php create mode 100644 modules/exif/helpers/exif_event.php create mode 100644 modules/exif/helpers/exif_installer.php create mode 100644 modules/exif/helpers/exif_task.php create mode 100644 modules/exif/helpers/exif_theme.php create mode 100644 modules/exif/lib/exif.php create mode 100644 modules/exif/lib/makers/canon.php create mode 100644 modules/exif/lib/makers/fujifilm.php create mode 100644 modules/exif/lib/makers/gps.php create mode 100644 modules/exif/lib/makers/nikon.php create mode 100644 modules/exif/lib/makers/olympus.php create mode 100644 modules/exif/lib/makers/panasonic.php create mode 100644 modules/exif/lib/makers/sanyo.php create mode 100644 modules/exif/models/exif_key.php create mode 100644 modules/exif/models/exif_record.php create mode 100644 modules/exif/module.info create mode 100644 modules/exif/views/exif_dialog.html.php create mode 100644 modules/exif/views/exif_sidebar.html.php create mode 100644 modules/familygallery/controllers/login.php.backup create mode 100644 modules/familygallery/helpers/familygallery_event.php create mode 100644 modules/familygallery/module.info create mode 100644 modules/forge/libraries/Forge.php create mode 100644 modules/forge/libraries/Form_Checkbox.php create mode 100644 modules/forge/libraries/Form_Checklist.php create mode 100644 modules/forge/libraries/Form_Dateselect.php create mode 100644 modules/forge/libraries/Form_Dropdown.php create mode 100644 modules/forge/libraries/Form_Group.php create mode 100644 modules/forge/libraries/Form_Hidden.php create mode 100644 modules/forge/libraries/Form_Input.php create mode 100644 modules/forge/libraries/Form_Password.php create mode 100644 modules/forge/libraries/Form_Phonenumber.php create mode 100644 modules/forge/libraries/Form_Radio.php create mode 100644 modules/forge/libraries/Form_Submit.php create mode 100644 modules/forge/libraries/Form_Textarea.php create mode 100644 modules/forge/libraries/Form_Upload.php create mode 100644 modules/g2_import/controllers/admin_g2_import.php create mode 100644 modules/g2_import/controllers/g2.php create mode 100755 modules/g2_import/data/broken-image.gif create mode 100644 modules/g2_import/helpers/g2_import.php create mode 100644 modules/g2_import/helpers/g2_import_event.php create mode 100644 modules/g2_import/helpers/g2_import_installer.php create mode 100644 modules/g2_import/helpers/g2_import_task.php create mode 100644 modules/g2_import/libraries/G2_Import_Exception.php create mode 100644 modules/g2_import/models/g2_map.php create mode 100644 modules/g2_import/module.info create mode 100644 modules/g2_import/views/admin_g2_import.html.php create mode 100644 modules/gallery/config/cache.php create mode 100644 modules/gallery/config/cookie.php create mode 100644 modules/gallery/config/database.php create mode 100644 modules/gallery/config/locale.php create mode 100644 modules/gallery/config/log_file.php create mode 100644 modules/gallery/config/routes.php create mode 100644 modules/gallery/config/session.php create mode 100644 modules/gallery/config/upload.php create mode 100644 modules/gallery/config/user_agents.php create mode 100644 modules/gallery/controllers/admin.php create mode 100644 modules/gallery/controllers/admin_advanced_settings.php create mode 100644 modules/gallery/controllers/admin_dashboard.php create mode 100644 modules/gallery/controllers/admin_graphics.php create mode 100644 modules/gallery/controllers/admin_languages.php create mode 100644 modules/gallery/controllers/admin_maintenance.php create mode 100644 modules/gallery/controllers/admin_modules.php create mode 100644 modules/gallery/controllers/admin_movies.php create mode 100644 modules/gallery/controllers/admin_sidebar.php create mode 100644 modules/gallery/controllers/admin_theme_options.php create mode 100644 modules/gallery/controllers/admin_themes.php create mode 100644 modules/gallery/controllers/admin_upgrade_checker.php create mode 100644 modules/gallery/controllers/albums.php create mode 100644 modules/gallery/controllers/combined.php create mode 100644 modules/gallery/controllers/file_proxy.php create mode 100644 modules/gallery/controllers/items.php create mode 100644 modules/gallery/controllers/l10n_client.php create mode 100644 modules/gallery/controllers/login.php create mode 100644 modules/gallery/controllers/logout.php create mode 100644 modules/gallery/controllers/movies.php create mode 100644 modules/gallery/controllers/packager.php create mode 100644 modules/gallery/controllers/permissions.php create mode 100644 modules/gallery/controllers/photos.php create mode 100644 modules/gallery/controllers/quick.php create mode 100644 modules/gallery/controllers/reauthenticate.php create mode 100644 modules/gallery/controllers/upgrader.php create mode 100644 modules/gallery/controllers/uploader.php create mode 100644 modules/gallery/controllers/user_profile.php create mode 100644 modules/gallery/controllers/welcome_message.php create mode 100644 modules/gallery/css/debug.css create mode 100644 modules/gallery/css/gallery.css create mode 100644 modules/gallery/css/l10n_client.css create mode 100644 modules/gallery/css/upgrader.css create mode 100644 modules/gallery/helpers/MY_html.php create mode 100644 modules/gallery/helpers/MY_num.php create mode 100644 modules/gallery/helpers/MY_remote.php create mode 100644 modules/gallery/helpers/MY_url.php create mode 100644 modules/gallery/helpers/MY_valid.php create mode 100644 modules/gallery/helpers/access.php create mode 100644 modules/gallery/helpers/ajax.php create mode 100644 modules/gallery/helpers/album.php create mode 100644 modules/gallery/helpers/auth.php create mode 100644 modules/gallery/helpers/batch.php create mode 100644 modules/gallery/helpers/block_manager.php create mode 100644 modules/gallery/helpers/data_rest.php create mode 100644 modules/gallery/helpers/dir.php create mode 100644 modules/gallery/helpers/encoding.php create mode 100644 modules/gallery/helpers/gallery.php create mode 100644 modules/gallery/helpers/gallery_block.php create mode 100644 modules/gallery/helpers/gallery_error.php create mode 100644 modules/gallery/helpers/gallery_event.php create mode 100644 modules/gallery/helpers/gallery_graphics.php create mode 100644 modules/gallery/helpers/gallery_installer.php create mode 100644 modules/gallery/helpers/gallery_rss.php create mode 100644 modules/gallery/helpers/gallery_task.php create mode 100644 modules/gallery/helpers/gallery_theme.php create mode 100644 modules/gallery/helpers/graphics.php create mode 100644 modules/gallery/helpers/identity.php create mode 100644 modules/gallery/helpers/item.php create mode 100644 modules/gallery/helpers/item_rest.php create mode 100644 modules/gallery/helpers/items_rest.php create mode 100644 modules/gallery/helpers/json.php create mode 100644 modules/gallery/helpers/l10n_client.php create mode 100644 modules/gallery/helpers/l10n_scanner.php create mode 100644 modules/gallery/helpers/legal_file.php create mode 100644 modules/gallery/helpers/locales.php create mode 100644 modules/gallery/helpers/log.php create mode 100644 modules/gallery/helpers/message.php create mode 100644 modules/gallery/helpers/model_cache.php create mode 100644 modules/gallery/helpers/module.php create mode 100644 modules/gallery/helpers/movie.php create mode 100644 modules/gallery/helpers/photo.php create mode 100644 modules/gallery/helpers/random.php create mode 100644 modules/gallery/helpers/site_status.php create mode 100644 modules/gallery/helpers/system.php create mode 100644 modules/gallery/helpers/task.php create mode 100644 modules/gallery/helpers/theme.php create mode 100644 modules/gallery/helpers/tree_rest.php create mode 100644 modules/gallery/helpers/upgrade_checker.php create mode 100644 modules/gallery/helpers/user_profile.php create mode 100644 modules/gallery/helpers/xml.php create mode 100644 modules/gallery/hooks/init_gallery.php create mode 100644 modules/gallery/images/ffmpeg.png create mode 100644 modules/gallery/images/gallery.png create mode 100644 modules/gallery/images/gd.png create mode 100644 modules/gallery/images/graphicsmagick.png create mode 100644 modules/gallery/images/imagemagick.jpg create mode 100644 modules/gallery/images/missing_album_cover.jpg create mode 100644 modules/gallery/images/missing_movie.jpg create mode 100644 modules/gallery/images/missing_photo.jpg create mode 100644 modules/gallery/js/albums_form_add.js create mode 100644 modules/gallery/js/item_form_delete.js create mode 100644 modules/gallery/js/l10n_client.js create mode 100644 modules/gallery/libraries/Admin_View.php create mode 100644 modules/gallery/libraries/Block.php create mode 100644 modules/gallery/libraries/Breadcrumb.php create mode 100644 modules/gallery/libraries/Form_Script.php create mode 100644 modules/gallery/libraries/Form_Uploadify.php create mode 100644 modules/gallery/libraries/Form_Uploadify_buttons.php create mode 100644 modules/gallery/libraries/Gallery_I18n.php create mode 100644 modules/gallery/libraries/Gallery_View.php create mode 100644 modules/gallery/libraries/IdentityProvider.php create mode 100644 modules/gallery/libraries/InPlaceEdit.php create mode 100644 modules/gallery/libraries/MY_Database.php create mode 100644 modules/gallery/libraries/MY_Forge.php create mode 100644 modules/gallery/libraries/MY_Input.php create mode 100644 modules/gallery/libraries/MY_Kohana_Exception.php create mode 100644 modules/gallery/libraries/MY_ORM.php create mode 100644 modules/gallery/libraries/MY_View.php create mode 100644 modules/gallery/libraries/Menu.php create mode 100644 modules/gallery/libraries/ORM_MPTT.php create mode 100644 modules/gallery/libraries/SafeString.php create mode 100644 modules/gallery/libraries/Sendmail.php create mode 100644 modules/gallery/libraries/Task_Definition.php create mode 100644 modules/gallery/libraries/Theme_View.php create mode 100644 modules/gallery/libraries/drivers/Cache/Database.php create mode 100644 modules/gallery/libraries/drivers/IdentityProvider.php create mode 100644 modules/gallery/models/access_cache.php create mode 100644 modules/gallery/models/access_intent.php create mode 100644 modules/gallery/models/cache.php create mode 100644 modules/gallery/models/failed_auth.php create mode 100644 modules/gallery/models/graphics_rule.php create mode 100644 modules/gallery/models/incoming_translation.php create mode 100644 modules/gallery/models/item.php create mode 100644 modules/gallery/models/log.php create mode 100644 modules/gallery/models/message.php create mode 100644 modules/gallery/models/module.php create mode 100644 modules/gallery/models/outgoing_translation.php create mode 100644 modules/gallery/models/permission.php create mode 100644 modules/gallery/models/task.php create mode 100644 modules/gallery/models/theme.php create mode 100644 modules/gallery/models/var.php create mode 100644 modules/gallery/module.info create mode 100644 modules/gallery/vendor/joomla/crypt.php create mode 100644 modules/gallery/views/admin_advanced_settings.html.php create mode 100644 modules/gallery/views/admin_block_log_entries.html.php create mode 100644 modules/gallery/views/admin_block_news.html.php create mode 100644 modules/gallery/views/admin_block_photo_stream.html.php create mode 100644 modules/gallery/views/admin_block_platform.html.php create mode 100644 modules/gallery/views/admin_block_stats.html.php create mode 100644 modules/gallery/views/admin_block_welcome.html.php create mode 100644 modules/gallery/views/admin_dashboard.html.php create mode 100644 modules/gallery/views/admin_graphics.html.php create mode 100644 modules/gallery/views/admin_graphics_gd.html.php create mode 100644 modules/gallery/views/admin_graphics_graphicsmagick.html.php create mode 100644 modules/gallery/views/admin_graphics_imagemagick.html.php create mode 100644 modules/gallery/views/admin_graphics_none.html.php create mode 100644 modules/gallery/views/admin_languages.html.php create mode 100644 modules/gallery/views/admin_maintenance.html.php create mode 100644 modules/gallery/views/admin_maintenance_show_log.html.php create mode 100644 modules/gallery/views/admin_maintenance_task.html.php create mode 100644 modules/gallery/views/admin_modules.html.php create mode 100644 modules/gallery/views/admin_modules_confirm.html.php create mode 100644 modules/gallery/views/admin_movies.html.php create mode 100644 modules/gallery/views/admin_sidebar.html.php create mode 100644 modules/gallery/views/admin_sidebar_blocks.html.php create mode 100644 modules/gallery/views/admin_theme_options.html.php create mode 100644 modules/gallery/views/admin_themes.html.php create mode 100644 modules/gallery/views/admin_themes_buttonset.html.php create mode 100644 modules/gallery/views/admin_themes_preview.html.php create mode 100644 modules/gallery/views/error.html.php create mode 100644 modules/gallery/views/error_404.html.php create mode 100644 modules/gallery/views/error_admin.html.php create mode 100644 modules/gallery/views/error_cli.txt.php create mode 100644 modules/gallery/views/error_user.html.php create mode 100644 modules/gallery/views/form.html.php create mode 100644 modules/gallery/views/form_uploadify.html.php create mode 100644 modules/gallery/views/form_uploadify_buttons.html.php create mode 100644 modules/gallery/views/in_place_edit.html.php create mode 100644 modules/gallery/views/kohana/error.php create mode 100644 modules/gallery/views/kohana_profiler.php create mode 100644 modules/gallery/views/l10n_client.html.php create mode 100644 modules/gallery/views/login_ajax.html.php create mode 100644 modules/gallery/views/login_current_user.html.php create mode 100644 modules/gallery/views/menu.html.php create mode 100644 modules/gallery/views/menu_ajax_link.html.php create mode 100644 modules/gallery/views/menu_dialog.html.php create mode 100644 modules/gallery/views/menu_link.html.php create mode 100644 modules/gallery/views/movieplayer.html.php create mode 100644 modules/gallery/views/permissions_browse.html.php create mode 100644 modules/gallery/views/permissions_form.html.php create mode 100644 modules/gallery/views/quick_delete_confirm.html.php create mode 100644 modules/gallery/views/reauthenticate.html.php create mode 100644 modules/gallery/views/upgrade_checker_block.html.php create mode 100644 modules/gallery/views/upgrader.html.php create mode 100644 modules/gallery/views/user_languages_block.html.php create mode 100644 modules/gallery/views/user_profile.html.php create mode 100644 modules/gallery/views/user_profile_info.html.php create mode 100644 modules/gallery/views/welcome_message.html.php create mode 100644 modules/gallery/views/welcome_message_loader.html.php create mode 100644 modules/greydragon/changelog.txt create mode 100644 modules/greydragon/css/gd_common.css create mode 100644 modules/greydragon/images/blue-grad.png create mode 100644 modules/greydragon/module.info create mode 100644 modules/greydragon/views/gd_admin_include.html.php create mode 100644 modules/html5_uploader/controllers/uploader.php create mode 100644 modules/html5_uploader/module.info create mode 100644 modules/image_block/controllers/image_block.php create mode 100644 modules/image_block/helpers/image_block_block.php create mode 100644 modules/image_block/helpers/image_block_installer.php create mode 100644 modules/image_block/module.info create mode 100644 modules/image_block/views/image_block_block.html.php create mode 100644 modules/info/helpers/info_block.php create mode 100644 modules/info/helpers/info_installer.php create mode 100644 modules/info/helpers/info_theme.php create mode 100644 modules/info/module.info create mode 100644 modules/info/views/info_block.html.php create mode 100644 modules/kbd_nav/changelog.txt create mode 100644 modules/kbd_nav/helpers/kbd_nav_theme.php create mode 100644 modules/kbd_nav/js/kbd_nav.js create mode 100644 modules/kbd_nav/module.info create mode 100644 modules/kohana23_compat/config/pagination.php create mode 100644 modules/kohana23_compat/libraries/MY_Database_Builder.php create mode 100644 modules/kohana23_compat/libraries/Pagination.php create mode 100644 modules/localprint/controllers/admin_localprint.php create mode 100644 modules/localprint/css/localprint_menu.css create mode 100644 modules/localprint/helpers/localprint_event.php create mode 100644 modules/localprint/helpers/localprint_installer.php create mode 100644 modules/localprint/helpers/localprint_theme.php create mode 100644 modules/localprint/images/localprint_logo.png create mode 100644 modules/localprint/js/localprint.js create mode 100644 modules/localprint/module.info create mode 100644 modules/localprint/views/admin_localprint.html.php create mode 100644 modules/localprint/views/localprint_code.html.php create mode 100644 modules/notification/controllers/notification.php create mode 100644 modules/notification/helpers/notification.php create mode 100644 modules/notification/helpers/notification_event.php create mode 100644 modules/notification/helpers/notification_installer.php create mode 100644 modules/notification/models/pending_notification.php create mode 100644 modules/notification/models/subscription.php create mode 100644 modules/notification/module.info create mode 100644 modules/notification/views/comment_published.html.php create mode 100644 modules/notification/views/item_added.html.php create mode 100644 modules/notification/views/item_deleted.html.php create mode 100644 modules/notification/views/item_updated.html.php create mode 100644 modules/notification/views/user_profile_notification.html.php create mode 100644 modules/organize/controllers/organize.php create mode 100644 modules/organize/css/organize_dialog.css create mode 100644 modules/organize/css/organize_frame.css create mode 100644 modules/organize/helpers/organize_event.php create mode 100644 modules/organize/helpers/organize_installer.php create mode 100644 modules/organize/module.info create mode 100644 modules/organize/vendor/ext/css/ext-all.css create mode 100644 modules/organize/vendor/ext/css/ux-all.css create mode 100644 modules/organize/vendor/ext/images/default/box/tb-blue.gif create mode 100644 modules/organize/vendor/ext/images/default/button/btn.gif create mode 100644 modules/organize/vendor/ext/images/default/dd/drop-no.gif create mode 100644 modules/organize/vendor/ext/images/default/dd/drop-yes.gif create mode 100644 modules/organize/vendor/ext/images/default/form/text-bg.gif create mode 100644 modules/organize/vendor/ext/images/default/form/trigger.gif create mode 100644 modules/organize/vendor/ext/images/default/grid/invalid_line.gif create mode 100644 modules/organize/vendor/ext/images/default/grid/loading.gif create mode 100644 modules/organize/vendor/ext/images/default/panel/tool-sprites.gif create mode 100644 modules/organize/vendor/ext/images/default/panel/white-top-bottom.gif create mode 100644 modules/organize/vendor/ext/images/default/progress/progress-bg.gif create mode 100644 modules/organize/vendor/ext/images/default/qtip/bg.gif create mode 100644 modules/organize/vendor/ext/images/default/s.gif create mode 100644 modules/organize/vendor/ext/images/default/shadow-c.png create mode 100644 modules/organize/vendor/ext/images/default/shadow-lr.png create mode 100644 modules/organize/vendor/ext/images/default/shadow.png create mode 100644 modules/organize/vendor/ext/images/default/toolbar/bg.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/arrows.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/drop-add.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/drop-between.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/drop-over.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/folder-open.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/folder.gif create mode 100644 modules/organize/vendor/ext/images/default/tree/loading.gif create mode 100644 modules/organize/vendor/ext/images/default/window/left-corners.png create mode 100644 modules/organize/vendor/ext/images/default/window/left-right.png create mode 100644 modules/organize/vendor/ext/images/default/window/right-corners.png create mode 100644 modules/organize/vendor/ext/images/default/window/top-bottom.png create mode 100644 modules/organize/vendor/ext/images/fam/delete.gif create mode 100644 modules/organize/vendor/ext/js/ext-organize-bundle.js create mode 100644 modules/organize/views/organize_dialog.html.php create mode 100644 modules/organize/views/organize_frame.html.php create mode 100644 modules/recaptcha/controllers/admin_recaptcha.php create mode 100644 modules/recaptcha/css/recaptcha.css create mode 100644 modules/recaptcha/helpers/recaptcha.php create mode 100644 modules/recaptcha/helpers/recaptcha_event.php create mode 100644 modules/recaptcha/helpers/recaptcha_installer.php create mode 100644 modules/recaptcha/helpers/recaptcha_theme.php create mode 100644 modules/recaptcha/libraries/Form_Recaptcha.php create mode 100644 modules/recaptcha/module.info create mode 100644 modules/recaptcha/views/admin_recaptcha.html.php create mode 100644 modules/recaptcha/views/form_recaptcha.html.php create mode 100644 modules/rest/controllers/rest.php create mode 100644 modules/rest/helpers/registry_rest.php create mode 100644 modules/rest/helpers/rest.php create mode 100644 modules/rest/helpers/rest_event.php create mode 100644 modules/rest/helpers/rest_installer.php create mode 100644 modules/rest/libraries/Rest_Exception.php create mode 100644 modules/rest/models/user_access_key.php create mode 100644 modules/rest/module.info create mode 100644 modules/rest/views/error_rest.json.php create mode 100644 modules/rest/views/reset_api_key_confirm.html.php create mode 100644 modules/rest/views/user_profile_rest.html.php create mode 100644 modules/rss/controllers/rss.php create mode 100644 modules/rss/helpers/rss.php create mode 100644 modules/rss/helpers/rss_block.php create mode 100644 modules/rss/module.info create mode 100644 modules/rss/views/feed.mrss.php create mode 100644 modules/rss/views/rss_block.html.php create mode 100644 modules/search/controllers/search.php create mode 100644 modules/search/helpers/search.php create mode 100644 modules/search/helpers/search_event.php create mode 100644 modules/search/helpers/search_installer.php create mode 100644 modules/search/helpers/search_task.php create mode 100644 modules/search/helpers/search_theme.php create mode 100644 modules/search/models/search_record.php create mode 100644 modules/search/module.info create mode 100644 modules/search/views/search.html.php create mode 100644 modules/search/views/search_link.html.php create mode 100644 modules/server_add/controllers/admin_server_add.php create mode 100644 modules/server_add/controllers/server_add.php create mode 100644 modules/server_add/css/server_add.css create mode 100644 modules/server_add/helpers/server_add.php create mode 100644 modules/server_add/helpers/server_add_event.php create mode 100644 modules/server_add/helpers/server_add_installer.php create mode 100644 modules/server_add/helpers/server_add_theme.php create mode 100644 modules/server_add/js/server_add.js create mode 100644 modules/server_add/models/server_add_entry.php create mode 100644 modules/server_add/module.info create mode 100644 modules/server_add/views/admin_server_add.html.php create mode 100644 modules/server_add/views/server_add_tree.html.php create mode 100644 modules/server_add/views/server_add_tree_dialog.html.php create mode 100644 modules/slideshow/helpers/slideshow_event.php create mode 100644 modules/slideshow/helpers/slideshow_installer.php create mode 100644 modules/slideshow/helpers/slideshow_theme.php create mode 100644 modules/slideshow/module.info create mode 100644 modules/tag/controllers/admin_tags.php create mode 100644 modules/tag/controllers/tag.php create mode 100644 modules/tag/controllers/tag_name.php create mode 100644 modules/tag/controllers/tags.php create mode 100644 modules/tag/css/tag.css create mode 100644 modules/tag/helpers/item_tags_rest.php create mode 100644 modules/tag/helpers/tag.php create mode 100644 modules/tag/helpers/tag_block.php create mode 100644 modules/tag/helpers/tag_event.php create mode 100644 modules/tag/helpers/tag_installer.php create mode 100644 modules/tag/helpers/tag_item_rest.php create mode 100644 modules/tag/helpers/tag_items_rest.php create mode 100644 modules/tag/helpers/tag_rest.php create mode 100644 modules/tag/helpers/tag_rss.php create mode 100644 modules/tag/helpers/tag_task.php create mode 100644 modules/tag/helpers/tag_theme.php create mode 100644 modules/tag/helpers/tags_rest.php create mode 100644 modules/tag/models/tag.php create mode 100644 modules/tag/module.info create mode 100644 modules/tag/views/admin_tags.html.php create mode 100644 modules/tag/views/tag_block.html.php create mode 100644 modules/tag/views/tag_cloud.html.php create mode 100644 modules/thumbnav/changelog.log create mode 100644 modules/thumbnav/controllers/admin_thumbnav.php create mode 100644 modules/thumbnav/css/thumbnav.css create mode 100644 modules/thumbnav/helpers/thumbnav_block.php create mode 100644 modules/thumbnav/helpers/thumbnav_event.php create mode 100644 modules/thumbnav/helpers/thumbnav_theme.php create mode 100644 modules/thumbnav/module.info create mode 100644 modules/thumbnav/views/admin_include.html.php create mode 100644 modules/thumbnav/views/admin_thumbnav.html.php create mode 100644 modules/thumbnav/views/thumbnav_block.html.php create mode 100644 modules/user/config/identity.php create mode 100644 modules/user/controllers/admin_users.php create mode 100644 modules/user/controllers/password.php create mode 100644 modules/user/controllers/users.php create mode 100644 modules/user/css/user.css create mode 100644 modules/user/helpers/group.php create mode 100644 modules/user/helpers/user.php create mode 100644 modules/user/helpers/user_event.php create mode 100644 modules/user/helpers/user_installer.php create mode 100644 modules/user/helpers/user_theme.php create mode 100644 modules/user/images/progressImg1.png create mode 100644 modules/user/js/password_strength.js create mode 100644 modules/user/lib/PasswordHash.php create mode 100644 modules/user/libraries/drivers/IdentityProvider/Gallery.php create mode 100644 modules/user/models/group.php create mode 100644 modules/user/models/user.php create mode 100644 modules/user/module.info create mode 100644 modules/user/views/admin_users.html.php create mode 100644 modules/user/views/admin_users_delete_user.html.php create mode 100644 modules/user/views/admin_users_group.html.php create mode 100644 modules/user/views/reset_password.html.php create mode 100644 modules/watermark/controllers/admin_watermarks.php create mode 100644 modules/watermark/helpers/watermark.php create mode 100644 modules/watermark/helpers/watermark_event.php create mode 100644 modules/watermark/helpers/watermark_installer.php create mode 100644 modules/watermark/module.info create mode 100644 modules/watermark/views/admin_watermarks.html.php create mode 100644 php.ini create mode 100644 robots.txt create mode 100644 serveradd/Raphael_2013-10-18_optimiert.mpg create mode 100644 system/KohanaLicense.html create mode 100644 system/config/cache.php create mode 100644 system/config/cookie.php create mode 100644 system/config/credit_cards.php create mode 100644 system/config/database.php create mode 100644 system/config/encryption.php create mode 100644 system/config/http.php create mode 100644 system/config/image.php create mode 100644 system/config/inflector.php create mode 100644 system/config/locale.php create mode 100644 system/config/log.php create mode 100644 system/config/mimes.php create mode 100644 system/config/profiler.php create mode 100644 system/config/routes.php create mode 100644 system/config/session.php create mode 100644 system/config/sql_types.php create mode 100644 system/config/upload.php create mode 100644 system/config/user_agents.php create mode 100644 system/config/view.php create mode 100644 system/controllers/template.php create mode 100644 system/core/Benchmark.php create mode 100644 system/core/Event.php create mode 100644 system/core/Kohana.php create mode 100644 system/core/Kohana_Config.php create mode 100644 system/core/Kohana_Exception.php create mode 100644 system/helpers/arr.php create mode 100644 system/helpers/cookie.php create mode 100644 system/helpers/date.php create mode 100644 system/helpers/db.php create mode 100644 system/helpers/download.php create mode 100644 system/helpers/expires.php create mode 100644 system/helpers/feed.php create mode 100644 system/helpers/file.php create mode 100644 system/helpers/form.php create mode 100644 system/helpers/format.php create mode 100644 system/helpers/html.php create mode 100644 system/helpers/inflector.php create mode 100644 system/helpers/num.php create mode 100644 system/helpers/remote.php create mode 100644 system/helpers/request.php create mode 100644 system/helpers/security.php create mode 100644 system/helpers/text.php create mode 100644 system/helpers/upload.php create mode 100644 system/helpers/url.php create mode 100644 system/helpers/utf8.php create mode 100644 system/helpers/valid.php create mode 100644 system/libraries/Cache.php create mode 100644 system/libraries/Cache_Exception.php create mode 100644 system/libraries/Controller.php create mode 100644 system/libraries/Database.php create mode 100644 system/libraries/Database_Builder.php create mode 100644 system/libraries/Database_Cache_Result.php create mode 100644 system/libraries/Database_Exception.php create mode 100644 system/libraries/Database_Expression.php create mode 100644 system/libraries/Database_Mysql.php create mode 100644 system/libraries/Database_Mysql_Result.php create mode 100644 system/libraries/Database_Mysqli.php create mode 100644 system/libraries/Database_Mysqli_Result.php create mode 100644 system/libraries/Database_Query.php create mode 100644 system/libraries/Database_Result.php create mode 100644 system/libraries/Encrypt.php create mode 100644 system/libraries/I18n.php create mode 100644 system/libraries/Image.php create mode 100644 system/libraries/Input.php create mode 100644 system/libraries/Kohana_404_Exception.php create mode 100644 system/libraries/Kohana_Log.php create mode 100644 system/libraries/Kohana_PHP_Exception.php create mode 100644 system/libraries/Kohana_User_Exception.php create mode 100644 system/libraries/Model.php create mode 100644 system/libraries/ORM.php create mode 100644 system/libraries/ORM_Iterator.php create mode 100644 system/libraries/ORM_Validation_Exception.php create mode 100644 system/libraries/Profiler.php create mode 100644 system/libraries/Profiler_Table.php create mode 100644 system/libraries/Router.php create mode 100644 system/libraries/Session.php create mode 100644 system/libraries/URI.php create mode 100644 system/libraries/Validation.php create mode 100644 system/libraries/View.php create mode 100644 system/libraries/drivers/Cache.php create mode 100644 system/libraries/drivers/Cache/File.php create mode 100644 system/libraries/drivers/Cache/Memcache.php create mode 100644 system/libraries/drivers/Cache/Xcache.php create mode 100644 system/libraries/drivers/Config.php create mode 100644 system/libraries/drivers/Config/Array.php create mode 100644 system/libraries/drivers/Image.php create mode 100644 system/libraries/drivers/Image/GD.php create mode 100644 system/libraries/drivers/Image/GraphicsMagick.php create mode 100644 system/libraries/drivers/Image/ImageMagick.php create mode 100644 system/libraries/drivers/Log.php create mode 100644 system/libraries/drivers/Log/Database.php create mode 100644 system/libraries/drivers/Log/File.php create mode 100644 system/libraries/drivers/Log/Syslog.php create mode 100644 system/libraries/drivers/Session.php create mode 100644 system/libraries/drivers/Session/Cache.php create mode 100644 system/libraries/drivers/Session/Cookie.php create mode 100644 system/libraries/drivers/Session/Database.php create mode 100644 system/messages/kohana/core.php create mode 100644 system/messages/validation/default.php create mode 100644 system/views/kohana/error.php create mode 100644 system/views/kohana/error_disabled.php create mode 100644 system/views/kohana/template.php create mode 100644 system/views/profiler/profiler.php create mode 100644 system/views/profiler/table.css create mode 100644 system/views/profiler/table.php create mode 100644 themes/admin_wind/css/fix-ie.css create mode 100644 themes/admin_wind/css/screen-rtl.css create mode 100644 themes/admin_wind/css/screen.css create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_flat_55_fbec88_40x100.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_glass_75_d0e5f5_1x400.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_glass_85_dfeffc_1x400.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_f5f8f9_1x100.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_fcfdfd_1x100.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_217bc0_256x240.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_2e83ff_256x240.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_469bdd_256x240.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_6da8d5_256x240.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_cd0a0a_256x240.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_d8e7f3_256x240.png create mode 100644 themes/admin_wind/css/themeroller/images/ui-icons_f9bd01_256x240.png create mode 100644 themes/admin_wind/css/themeroller/ui.base.css create mode 100644 themes/admin_wind/css/themeroller/ui.core.css create mode 100644 themes/admin_wind/css/themeroller/ui.datepicker.css create mode 100644 themes/admin_wind/css/themeroller/ui.dialog.css create mode 100644 themes/admin_wind/css/themeroller/ui.progressbar.css create mode 100644 themes/admin_wind/css/themeroller/ui.resizable.css create mode 100644 themes/admin_wind/css/themeroller/ui.tabs.css create mode 100644 themes/admin_wind/css/themeroller/ui.theme.css create mode 100644 themes/admin_wind/images/avatar.jpg create mode 100644 themes/admin_wind/images/ico-denied-inactive.png create mode 100644 themes/admin_wind/images/ico-denied-passive.png create mode 100644 themes/admin_wind/images/ico-denied.png create mode 100644 themes/admin_wind/images/ico-error.png create mode 100644 themes/admin_wind/images/ico-info.png create mode 100644 themes/admin_wind/images/ico-lock.png create mode 100644 themes/admin_wind/images/ico-separator-rtl.gif create mode 100644 themes/admin_wind/images/ico-separator.gif create mode 100644 themes/admin_wind/images/ico-success-inactive.png create mode 100644 themes/admin_wind/images/ico-success-passive.png create mode 100644 themes/admin_wind/images/ico-success.png create mode 100644 themes/admin_wind/images/ico-warning.png create mode 100644 themes/admin_wind/images/loading-large.gif create mode 100644 themes/admin_wind/images/loading-small.gif create mode 100644 themes/admin_wind/js/ui.init.js create mode 100644 themes/admin_wind/theme.info create mode 100644 themes/admin_wind/thumbnail.png create mode 100644 themes/admin_wind/views/admin.html.php create mode 100644 themes/admin_wind/views/block.html.php create mode 100644 themes/admin_wind/views/paginator.html.php create mode 100644 themes/greydragon/admin/controllers/admin_theme_options.php create mode 100644 themes/greydragon/admin/views/admin_theme_options.html.php create mode 100644 themes/greydragon/changelog.txt create mode 100644 themes/greydragon/controllers/greydragon.php create mode 100644 themes/greydragon/css/base.css create mode 100644 themes/greydragon/css/colorpacks/blackhawk/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/search.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-calendar-b.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize-b.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/blackhawk/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/colorpacks/carbon/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/carbon/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/carbon/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/carbon/images/search.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/section.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-calendar-b.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-fullsize-b.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/carbon/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/background-bottom.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/background-top.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/background.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/footer.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/image-thumb.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/search.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/section.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-calendar-b.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-fullsize-b.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/greydragon/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/bg-body.jpg create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/bg-header.jpg create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/gallery.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/search.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-calendar-b.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize-b.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/roundrobin/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/background.jpg create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/footer.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/search.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/section.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-calendar-b.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-fullsize-b.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/slateblue/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/gallery.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/search.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-calendar-b.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize-b.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/whitehawk/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/colorpacks/wind/css/colors.css create mode 100644 themes/greydragon/css/colorpacks/wind/images/ajax-loading.gif create mode 100644 themes/greydragon/css/colorpacks/wind/images/colorpack.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-album.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-error.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-help.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-info.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow-rtl.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ico-warning.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/loading-large.gif create mode 100644 themes/greydragon/css/colorpacks/wind/images/loading-small.gif create mode 100644 themes/greydragon/css/colorpacks/wind/images/section.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/ui-icons.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-calendar.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-comments-b.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-comments.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-fullsize.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-info-b.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-info-o.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-info.png create mode 100644 themes/greydragon/css/colorpacks/wind/images/view-slideshow-b.png create mode 100644 themes/greydragon/css/custom.css create mode 100644 themes/greydragon/css/forms.css create mode 100644 themes/greydragon/css/framepacks/android/css/frame.css create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-dgt-e.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-dgt-eext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-dgt-ext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-dgt.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-flm-e.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-flm-eext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-flm-ext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-flm.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-sqr-e.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-sqr-eext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-sqr-ext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-sqr.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-wd-e.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-wd-eext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-wd-ext.png create mode 100644 themes/greydragon/css/framepacks/android/images/thumb-wd.png create mode 100644 themes/greydragon/css/framepacks/book/css/frame.css create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-dgt-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-dgt-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-dgt-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-dgt.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-flm-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-flm-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-flm-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-flm.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-sqr-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-sqr-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-sqr-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-sqr.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-wd-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-wd-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-wd-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/a-thumb-wd.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-dgt-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-dgt-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-dgt-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-dgt.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-flm-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-flm-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-flm-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-flm.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-sqr-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-sqr-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-sqr-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-sqr.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-wd-e.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-wd-eext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-wd-ext.png create mode 100644 themes/greydragon/css/framepacks/book/images/thumb-wd.png create mode 100644 themes/greydragon/css/framepacks/darkglass/css/frame.css create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-e.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-eext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-ext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-dgt.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-flm-e.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-flm-eext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-flm-ext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-flm.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-e.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-eext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-ext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-sqr.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-wd-e.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-wd-eext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-wd-ext.png create mode 100644 themes/greydragon/css/framepacks/darkglass/images/thumb-wd.png create mode 100644 themes/greydragon/css/framepacks/greydragon/css/frame.css create mode 100644 themes/greydragon/css/framepacks/iphone/css/frame.css create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-dgt-e.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-dgt-eext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-dgt-ext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-dgt.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-flm-e.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-flm-eext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-flm-ext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-flm.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-sqr-e.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-sqr-eext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-sqr-ext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-sqr.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-wd-e.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-wd-eext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-wd-ext.png create mode 100644 themes/greydragon/css/framepacks/iphone/images/thumb-wd.png create mode 100644 themes/greydragon/css/framepacks/iphoto/css/frame.css create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-e.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-eext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-ext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-dgt.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-flm-e.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-flm-eext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-flm-ext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-flm.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-e.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-eext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-ext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-sqr.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-wd-e.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-wd-eext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-wd-ext.png create mode 100644 themes/greydragon/css/framepacks/iphoto/images/thumb-wd.png create mode 100644 themes/greydragon/css/framepacks/iphoto/views/frame.html.php create mode 100644 themes/greydragon/css/framepacks/panel/css/frame.css create mode 100644 themes/greydragon/css/framepacks/roundcorners/css/frame.css create mode 100644 themes/greydragon/css/framepacks/simple/css/frame.css create mode 100644 themes/greydragon/css/framepacks/wall/css/frame.css create mode 100644 themes/greydragon/css/ipad.css create mode 100644 themes/greydragon/css/layout.css create mode 100644 themes/greydragon/css/menus.css create mode 100644 themes/greydragon/css/modules.css create mode 100644 themes/greydragon/css/normalize.css create mode 100644 themes/greydragon/css/rtl.css create mode 100644 themes/greydragon/css/screen.css create mode 100644 themes/greydragon/helpers/exif_event.php create mode 100644 themes/greydragon/helpers/greydragon_event.php create mode 100644 themes/greydragon/helpers/greydragon_installer.php create mode 100644 themes/greydragon/images/apple-touch-icon.png create mode 100644 themes/greydragon/images/arrows_left.png create mode 100644 themes/greydragon/images/arrows_right.png create mode 100644 themes/greydragon/images/avatar.jpg create mode 100644 themes/greydragon/images/blue-grad.png create mode 100644 themes/greydragon/images/button-grad-active-vs.png create mode 100644 themes/greydragon/images/button-grad-vs.png create mode 100644 themes/greydragon/images/close.png create mode 100644 themes/greydragon/images/donate.png create mode 100644 themes/greydragon/images/gallery.png create mode 100644 themes/greydragon/images/ico-allowed.png create mode 100644 themes/greydragon/images/ico-denied-inactive.png create mode 100644 themes/greydragon/images/ico-denied-passive.png create mode 100644 themes/greydragon/images/ico-denied.png create mode 100644 themes/greydragon/images/ico-error.png create mode 100644 themes/greydragon/images/ico-help.png create mode 100644 themes/greydragon/images/ico-info.png create mode 100644 themes/greydragon/images/ico-lock.png create mode 100644 themes/greydragon/images/ico-success-inactive.png create mode 100644 themes/greydragon/images/ico-success-passive.png create mode 100644 themes/greydragon/images/ico-success.png create mode 100644 themes/greydragon/images/ico-warning.png create mode 100644 themes/greydragon/images/missing-img.png create mode 100644 themes/greydragon/js/gallery.ajax.custom.js create mode 100644 themes/greydragon/js/gallery.dialog.custom.js create mode 100644 themes/greydragon/js/jquery-ui.min.js create mode 100644 themes/greydragon/js/jquery.cycle.js create mode 100644 themes/greydragon/js/jquery.form.custom.js create mode 100644 themes/greydragon/js/jquery.json.min.js create mode 100644 themes/greydragon/js/jquery.min.js create mode 100644 themes/greydragon/js/ui.support.js create mode 100644 themes/greydragon/libraries/MY_Theme_View.php create mode 100644 themes/greydragon/theme.info create mode 100644 themes/greydragon/thumbnail.png create mode 100644 themes/greydragon/views/album.html.php create mode 100644 themes/greydragon/views/block.html.php create mode 100644 themes/greydragon/views/calpage.html.php create mode 100644 themes/greydragon/views/dynamic.html.php create mode 100644 themes/greydragon/views/exif_sidebar.html.php create mode 100644 themes/greydragon/views/info_block.html.php create mode 100644 themes/greydragon/views/login_ajax.html.php create mode 100644 themes/greydragon/views/movie.html.php create mode 100644 themes/greydragon/views/no_sidebar.html.php create mode 100644 themes/greydragon/views/page.html.php create mode 100644 themes/greydragon/views/paginator.html.php create mode 100644 themes/greydragon/views/photo.html.php create mode 100644 themes/greydragon/views/rootpage.html.php create mode 100644 themes/greydragon/views/rootpage.html.php_fix create mode 100644 themes/greydragon/views/rss_block.html.php create mode 100644 themes/greydragon/views/search.html.php create mode 100644 themes/greydragon/views/sidebar.html.php create mode 100644 themes/greydragon/views/user_profile.html.php create mode 100644 themes/greydragon_old/admin/controllers/admin_theme_options.php create mode 100644 themes/greydragon_old/admin/views/admin_theme_options.html.php create mode 100644 themes/greydragon_old/changelog.txt create mode 100644 themes/greydragon_old/controllers/greydragon.php create mode 100644 themes/greydragon_old/css/base.css create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/search.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-calendar-b.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-fullsize-b.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/blackhawk/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/search.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/section.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-calendar-b.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-fullsize-b.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/carbon/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/background-bottom.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/background-top.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/background.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/footer.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/image-thumb.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/search.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/section.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-calendar-b.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-fullsize-b.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/greydragon/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/bg-body.jpg create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/bg-header.jpg create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/gallery.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/search.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-calendar-b.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-fullsize-b.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/roundrobin/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/background.jpg create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/footer.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/search.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/section.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-calendar-b.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-fullsize-b.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/slateblue/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/gallery.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/search.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-calendar-b.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-fullsize-b.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/whitehawk/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/css/colors.css create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ajax-loading.gif create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/colorpack.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-album.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-error.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-help.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-info.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-view-slideshow-rtl.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-view-slideshow.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ico-warning.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/loading-large.gif create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/loading-small.gif create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/section.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/ui-icons.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-calendar.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-comments-b.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-comments.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-fullsize.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-info-b.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-info-o.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-info.png create mode 100644 themes/greydragon_old/css/colorpacks/wind/images/view-slideshow-b.png create mode 100644 themes/greydragon_old/css/custom.css create mode 100644 themes/greydragon_old/css/forms.css create mode 100644 themes/greydragon_old/css/framepacks/android/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-dgt-e.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-dgt-eext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-dgt-ext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-dgt.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-flm-e.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-flm-eext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-flm-ext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-flm.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-sqr-e.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-sqr-eext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-sqr-ext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-sqr.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-wd-e.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-wd-eext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-wd-ext.png create mode 100644 themes/greydragon_old/css/framepacks/android/images/thumb-wd.png create mode 100644 themes/greydragon_old/css/framepacks/book/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-dgt-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-dgt-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-dgt-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-dgt.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-flm-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-flm-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-flm-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-flm.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-sqr-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-sqr-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-sqr-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-sqr.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-wd-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-wd-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-wd-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/a-thumb-wd.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-dgt-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-dgt-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-dgt-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-dgt.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-flm-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-flm-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-flm-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-flm.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-sqr-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-sqr-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-sqr-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-sqr.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-wd-e.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-wd-eext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-wd-ext.png create mode 100644 themes/greydragon_old/css/framepacks/book/images/thumb-wd.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-dgt-e.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-dgt-eext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-dgt-ext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-dgt.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-flm-e.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-flm-eext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-flm-ext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-flm.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-sqr-e.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-sqr-eext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-sqr-ext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-sqr.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-wd-e.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-wd-eext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-wd-ext.png create mode 100644 themes/greydragon_old/css/framepacks/darkglass/images/thumb-wd.png create mode 100644 themes/greydragon_old/css/framepacks/greydragon/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/iphone/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-dgt-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-dgt-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-dgt-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-dgt.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-flm-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-flm-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-flm-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-flm.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-sqr-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-sqr-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-sqr-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-sqr.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-wd-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-wd-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-wd-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphone/images/thumb-wd.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-dgt-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-dgt-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-dgt-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-dgt.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-flm-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-flm-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-flm-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-flm.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-sqr-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-sqr-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-sqr-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-sqr.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-wd-e.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-wd-eext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-wd-ext.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/images/thumb-wd.png create mode 100644 themes/greydragon_old/css/framepacks/iphoto/views/frame.html.php create mode 100644 themes/greydragon_old/css/framepacks/panel/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/roundcorners/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/simple/css/frame.css create mode 100644 themes/greydragon_old/css/framepacks/wall/css/frame.css create mode 100644 themes/greydragon_old/css/gd_common.css create mode 100644 themes/greydragon_old/css/ipad.css create mode 100644 themes/greydragon_old/css/layout.css create mode 100644 themes/greydragon_old/css/menus.css create mode 100644 themes/greydragon_old/css/modules.css create mode 100644 themes/greydragon_old/css/normalize.css create mode 100644 themes/greydragon_old/css/rtl.css create mode 100644 themes/greydragon_old/css/screen.css create mode 100644 themes/greydragon_old/helpers/exif_event.php create mode 100644 themes/greydragon_old/helpers/greydragon_event.php create mode 100644 themes/greydragon_old/helpers/greydragon_installer.php create mode 100644 themes/greydragon_old/helpers/greydragon_theme.php create mode 100644 themes/greydragon_old/images/apple-touch-icon.png create mode 100644 themes/greydragon_old/images/arrows_left.png create mode 100644 themes/greydragon_old/images/arrows_right.png create mode 100644 themes/greydragon_old/images/avatar.jpg create mode 100644 themes/greydragon_old/images/blue-grad.png create mode 100644 themes/greydragon_old/images/button-grad-active-vs.png create mode 100644 themes/greydragon_old/images/button-grad-vs.png create mode 100644 themes/greydragon_old/images/close.png create mode 100644 themes/greydragon_old/images/donate.png create mode 100644 themes/greydragon_old/images/gallery.png create mode 100644 themes/greydragon_old/images/ico-allowed.png create mode 100644 themes/greydragon_old/images/ico-denied-inactive.png create mode 100644 themes/greydragon_old/images/ico-denied-passive.png create mode 100644 themes/greydragon_old/images/ico-denied.png create mode 100644 themes/greydragon_old/images/ico-error.png create mode 100644 themes/greydragon_old/images/ico-help.png create mode 100644 themes/greydragon_old/images/ico-info.png create mode 100644 themes/greydragon_old/images/ico-lock.png create mode 100644 themes/greydragon_old/images/ico-success-inactive.png create mode 100644 themes/greydragon_old/images/ico-success-passive.png create mode 100644 themes/greydragon_old/images/ico-success.png create mode 100644 themes/greydragon_old/images/ico-warning.png create mode 100644 themes/greydragon_old/images/missing-img.png create mode 100644 themes/greydragon_old/js/jquery.cycle.js create mode 100644 themes/greydragon_old/js/ui.support.js create mode 100644 themes/greydragon_old/libraries/MY_Theme_View.php create mode 100644 themes/greydragon_old/module.info create mode 100644 themes/greydragon_old/theme.info create mode 100644 themes/greydragon_old/thumbnail.png create mode 100644 themes/greydragon_old/views/album.html.php create mode 100644 themes/greydragon_old/views/block.html.php create mode 100644 themes/greydragon_old/views/calpage.html.php create mode 100644 themes/greydragon_old/views/dynamic.html.php create mode 100644 themes/greydragon_old/views/exif_sidebar.html.php create mode 100644 themes/greydragon_old/views/gd_admin_include.html.php create mode 100644 themes/greydragon_old/views/info_block.html.php create mode 100644 themes/greydragon_old/views/movie.html.php create mode 100644 themes/greydragon_old/views/no_sidebar.html.php create mode 100644 themes/greydragon_old/views/page.html.php create mode 100644 themes/greydragon_old/views/paginator.html.php create mode 100644 themes/greydragon_old/views/photo.html.php create mode 100644 themes/greydragon_old/views/rootpage.html.php create mode 100644 themes/greydragon_old/views/rootpage.html.php_fix create mode 100644 themes/greydragon_old/views/rss_block.html.php create mode 100644 themes/greydragon_old/views/search.html.php create mode 100644 themes/greydragon_old/views/sidebar.html.php create mode 100644 themes/greydragon_old/views/user_profile.html.php create mode 100644 themes/wind/css/fix-ie.css create mode 100644 themes/wind/css/screen-rtl.css create mode 100644 themes/wind/css/screen.css create mode 100644 themes/wind/css/themeroller/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_flat_55_fbec88_40x100.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_glass_75_d0e5f5_1x400.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_glass_85_dfeffc_1x400.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_inset-hard_100_f5f8f9_1x100.png create mode 100644 themes/wind/css/themeroller/images/ui-bg_inset-hard_100_fcfdfd_1x100.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_217bc0_256x240.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_2e83ff_256x240.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_469bdd_256x240.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_6da8d5_256x240.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_cd0a0a_256x240.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_d8e7f3_256x240.png create mode 100644 themes/wind/css/themeroller/images/ui-icons_f9bd01_256x240.png create mode 100644 themes/wind/css/themeroller/ui.base.css create mode 100644 themes/wind/css/themeroller/ui.core.css create mode 100644 themes/wind/css/themeroller/ui.datepicker.css create mode 100644 themes/wind/css/themeroller/ui.dialog.css create mode 100644 themes/wind/css/themeroller/ui.progressbar.css create mode 100644 themes/wind/css/themeroller/ui.resizable.css create mode 100644 themes/wind/css/themeroller/ui.tabs.css create mode 100644 themes/wind/css/themeroller/ui.theme.css create mode 100644 themes/wind/images/avatar.jpg create mode 100644 themes/wind/images/ico-album.png create mode 100644 themes/wind/images/ico-denied-inactive.png create mode 100644 themes/wind/images/ico-denied-passive.png create mode 100644 themes/wind/images/ico-denied.png create mode 100644 themes/wind/images/ico-error.png create mode 100644 themes/wind/images/ico-help.png create mode 100644 themes/wind/images/ico-info.png create mode 100644 themes/wind/images/ico-lock.png create mode 100644 themes/wind/images/ico-print.png create mode 100644 themes/wind/images/ico-separator-rtl.gif create mode 100644 themes/wind/images/ico-separator.gif create mode 100644 themes/wind/images/ico-success-inactive.png create mode 100644 themes/wind/images/ico-success-passive.png create mode 100644 themes/wind/images/ico-success.png create mode 100644 themes/wind/images/ico-view-comments.png create mode 100644 themes/wind/images/ico-view-fullsize.png create mode 100644 themes/wind/images/ico-view-slideshow-rtl.png create mode 100644 themes/wind/images/ico-view-slideshow.png create mode 100644 themes/wind/images/ico-warning.png create mode 100644 themes/wind/images/loading-large.gif create mode 100644 themes/wind/images/loading-small.gif create mode 100644 themes/wind/images/select-photos-backg.png create mode 100644 themes/wind/js/ui.init.js create mode 100644 themes/wind/theme.info create mode 100755 themes/wind/thumbnail.png create mode 100644 themes/wind/views/album.html.php create mode 100644 themes/wind/views/block.html.php create mode 100644 themes/wind/views/dynamic.html.php create mode 100644 themes/wind/views/movie.html.php create mode 100644 themes/wind/views/no_sidebar.html.php create mode 100644 themes/wind/views/page.html.php create mode 100644 themes/wind/views/paginator.html.php create mode 100644 themes/wind/views/photo.html.php create mode 100644 themes/wind/views/sidebar.html.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..186857b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +var diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..1df3359 --- /dev/null +++ b/.htaccess @@ -0,0 +1,82 @@ +# Set some reasonable defaults for PHP. Most of these cannot be set +# inside the script itself. For hosts that don't have .htaccess +# support but do support per-dir php.ini files, these settings are +# mirrored in php.ini +# + + php_flag short_open_tag On + php_flag magic_quotes_gpc Off + php_flag magic_quotes_sybase Off + php_flag magic_quotes_runtime Off + php_flag register_globals Off + php_flag session.auto_start Off + php_flag suhosin.session.encrypt Off + php_value upload_max_filesize 100M + php_value post_max_size 100M + + +# Try to disable the parts of mod_security that interfere with the Flash uploader +# + + SecFilterEngine Off + SecFilterScanPOST Off + + +# Increase security by uncommenting this block. It keeps browsers +# from seeing support files that they shouldn't have access to. We +# comment this out because Apache2 requires some minor configuration +# in order for you to use it. You must specify "AllowOverride Limit" +# in your Apache2 config file before you uncomment this block or +# you'll get an "Internal Server Error". +# +# +# Order deny,allow +# Deny from all +# +# +# Order allow,deny +# Allow from all +# + + +# Improve performance by uncommenting this block. It tells the +# browser that your images don't change very often so it won't keep +# asking for them. If you get an error after uncommenting this, make +# sure you specify "AuthConfig Indexes" in your Apache config file. +# +# +# ExpiresActive On +# # Cache all files for a month after access (A). +# ExpiresDefault A2678400 +# # Do not cache dynamically generated pages. +# ExpiresByType text/html A1 +# + +# You can use the mod_rewrite Apache module to get rid of the +# "index.php" from your Gallery 3 urls. Uncomment the block below +# inside the ... lines and then edit the +# RewriteBase line to match your Gallery 3 URL. +# +# Here are some RewriteBase values: +# Gallery 3 URL RewriteBase line +# ============= ==================== +# http://example.com/gallery3 RewriteBase /gallery3 +# http://example.com/~bob/photos RewriteBase /~bob/photos +# http://gallery3.example.com/ RewriteBase / +# +# Then just use your Gallery 3 without the index.php in the url. +# +# NOTE: future upgrades of Gallery 3 will overwrite this file! If you +# want these changes to be persistent, talk to your system admin about +# putting this block into your Apache config files. +# +# +# Options +FollowSymLinks +# RewriteEngine On +# RewriteBase / +# RewriteCond %{REQUEST_FILENAME} !-f +# RewriteCond %{REQUEST_FILENAME} !-d +# RewriteRule ^(.*)$ index.php?kohana_uri=$1 [QSA,PT,L] +# RewriteRule ^$ index.php?kohana_uri=$1 [QSA,PT,L] +# RewriteRule ^index.php/(.*) $1 [QSA,R,L] +# diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..d9dc88a --- /dev/null +++ b/README @@ -0,0 +1,85 @@ +Gallery 3.0.9 (Chartres) +=========================== + +About +----- + +Gallery 3 is a web based software product that lets you manage your +photos on your own website. You must have your own website with PHP +and database support in order to install and use it. With Gallery you +can easily create and share albums of photos via an intuitive +interface. + +Intended Audience +----------------- + +This version is intended for anybody who has a website. We stand +ready to support the product and help you to make the most of it. We +welcome theme and module developers to play with this release and +start turning out slick new designs for our happy users. If you have +questions or problems, you can get help in the Gallery forums: + + http://galleryproject.org/forum/96 + +Security +-------- + +We've contracted a professional security audit, received their results +and resolved all the issues they found. + +Did you find a security flaw? Please email security@galleryproject.org +with the details and we'll fix it ASAP! + +Supported Configuration +----------------------- + + - Platform: Linux / Unix. + - Web server: Apache 2.2 and newer. + - PHP 5.2.3 and newer (PHP's safe_mode must be disabled and simplexml, + filter, and json must be installed). + - Database: MySQL 5 and newer. + +For complete system requirements, please refer to: + + http://codex.galleryproject.org/Gallery3:Requirements + +Installing and Upgrading Instructions +------------------------------------- + +For comprehensive instructions, The online User Guide is your best resource: + + http://codex.galleryproject.org/Gallery3:User_guide + +There are also simple instructions below. NOTE: You can upgrade from +beta 1 and beyond, but not from alpha releases. + + INSTALLATION VIA THE WEB + +Point your web browser at gallery3/installer/ and follow the +instructions. + + INSTALLATION FROM THE COMMAND LINE + +php installer/index.php [-h host] [-u user] [-p pass] [-d dbname] + + Command line parameters: + + -h Database host (default: localhost) + -u Database user (default: root) + -p Database user password (default: ) + -d Database name (default: gallery3) + -x Table prefix (default: ) + +Bugs? +----- + +Go to http://apps.sourceforge.net/trac/gallery/ click the "login" link +and log in with your SourceForge username and password, then click the +"new ticket" button. + +Questions, Problems +------------------- + + - Check out the Gallery 3 FAQ: http://codex.galleryproject.org/Gallery3:FAQ + - Post to the Gallery 3 forums: http://galleryproject.org/forum/96 + - Email gallery-devel@lists.sourceforge.net diff --git a/application/Bootstrap.php b/application/Bootstrap.php new file mode 100644 index 0000000..93353b4 --- /dev/null +++ b/application/Bootstrap.php @@ -0,0 +1,67 @@ + +RewriteEngine off + +Order allow,deny +Deny from all diff --git a/bin/README b/bin/README new file mode 100644 index 0000000..ec09639 --- /dev/null +++ b/bin/README @@ -0,0 +1,5 @@ +This directory contains utility software that Gallery uses to perform +image manipulation and other useful functions. It should not be +accessible from a web browser, and by default it's empty. Gallery +will instruct you when it's appropriate to download software and +install it here. diff --git a/index.php b/index.php new file mode 100644 index 0000000..e6636cf --- /dev/null +++ b/index.php @@ -0,0 +1,116 @@ +getMessage()); + } +} + +function oops($message) { + print "Oops! Something went wrong during the installation:\n\n"; + + print "==> " . $message; + print "\n"; + print "For help you can try:\n"; + print " * The Gallery 3 FAQ - http://codex.galleryproject.org/Gallery3:FAQ\n"; + print " * The Gallery Forums - http://galleryproject.org/forum\n"; + print "\n\n** INSTALLATION FAILED **\n"; + exit(1); +} + +function parse_cli_params() { + $config = array("host" => "localhost", + "user" => "root", + "password" => "", + "dbname" => "gallery3", + "prefix" => "", + "g3_password" => "", + "type" => function_exists("mysqli_set_charset") ? "mysqli" : "mysql"); + + $argv = $_SERVER["argv"]; + for ($i = 1; $i < count($argv); $i++) { + switch (strtolower($argv[$i])) { + case "-d": + $config["dbname"] = $argv[++$i]; + break; + case "-h": + list ($config["host"], $config["port"]) = explode(":", $argv[++$i]); + break; + case "-u": + $config["user"] = $argv[++$i]; + break; + case "-p": + $config["password"] = $argv[++$i]; + break; + case "-x": + $config["prefix"] = $argv[++$i]; + break; + case "-g3p": + $config["g3_password"] = $argv[++$i]; + break; + } + } + + return $config; +} diff --git a/installer/database_config.php b/installer/database_config.php new file mode 100644 index 0000000..fb7dd11 --- /dev/null +++ b/installer/database_config.php @@ -0,0 +1,46 @@ + + defined("SYSPATH") or die("No direct script access."); + +/** + * @package Database + * + * Database connection settings, defined as arrays, or "groups". If no group + * name is used when loading the database library, the group named "default" + * will be used. + * + * Each group can be connected to independently, and multiple groups can be + * connected at once. + * + * Group Options: + * benchmark - Enable or disable database benchmarking + * persistent - Enable or disable a persistent connection + * connection - Array of connection specific parameters; alternatively, + * you can use a DSN though it is not as fast and certain + * characters could create problems (like an '@' character + * in a password): + * 'connection' => 'mysql://dbuser:secret@localhost/kohana' + * character_set - Database character set + * table_prefix - Database table prefix + * object - Enable or disable object results + * cache - Enable or disable query caching + * escape - Enable automatic query builder escaping + */ +$config['default'] = array( + 'benchmark' => false, + 'persistent' => false, + 'connection' => array( + 'type' => '', + 'user' => '', + 'pass' => '', + 'host' => '', + 'port' => '' false, + 'socket' => false, + 'database' => '', + 'params' => null, + ), + 'character_set' => 'utf8', + 'table_prefix' => '', + 'object' => true, + 'cache' => false, + 'escape' => true +); \ No newline at end of file diff --git a/installer/index.php b/installer/index.php new file mode 100644 index 0000000..651edc9 --- /dev/null +++ b/installer/index.php @@ -0,0 +1,40 @@ + +%gallery_version'); +INSERT INTO {vars} VALUES (NULL,'gallery','simultaneous_upload_limit','5'); +INSERT INTO {vars} VALUES (NULL,'gallery','admin_area_timeout','5400'); +INSERT INTO {vars} VALUES (NULL,'gallery','maintenance_mode','0'); +INSERT INTO {vars} VALUES (NULL,'gallery','visible_title_length','15'); +INSERT INTO {vars} VALUES (NULL,'gallery','favicon_url','lib/images/favicon.ico'); +INSERT INTO {vars} VALUES (NULL,'gallery','apple_touch_icon_url','lib/images/apple-touch-icon.png'); +INSERT INTO {vars} VALUES (NULL,'gallery','email_from','unknown@unknown.com'); +INSERT INTO {vars} VALUES (NULL,'gallery','email_reply_to','unknown@unknown.com'); +INSERT INTO {vars} VALUES (NULL,'gallery','email_line_length','70'); +INSERT INTO {vars} VALUES (NULL,'gallery','email_header_separator','s:1:\"\n\";'); +INSERT INTO {vars} VALUES (NULL,'gallery','show_user_profiles_to','registered_users'); +INSERT INTO {vars} VALUES (NULL,'gallery','extra_binary_paths','/usr/local/bin:/opt/local/bin:/opt/bin'); +INSERT INTO {vars} VALUES (NULL,'gallery','timezone',NULL); +INSERT INTO {vars} VALUES (NULL,'gallery','lock_timeout','1'); +INSERT INTO {vars} VALUES (NULL,'gallery','movie_extract_frame_time','3'); +INSERT INTO {vars} VALUES (NULL,'gallery','movie_allow_uploads','autodetect'); +INSERT INTO {vars} VALUES (NULL,'gallery','blocks_site_sidebar','a:4:{i:10;a:2:{i:0;s:7:\"gallery\";i:1;s:8:\"language\";}i:11;a:2:{i:0;s:4:\"info\";i:1;s:8:\"metadata\";}i:12;a:2:{i:0;s:3:\"rss\";i:1;s:9:\"rss_feeds\";}i:13;a:2:{i:0;s:3:\"tag\";i:1;s:3:\"tag\";}}'); +INSERT INTO {vars} VALUES (NULL,'gallery','identity_provider','user'); +INSERT INTO {vars} VALUES (NULL,'user','minimum_password_length','5'); +INSERT INTO {vars} VALUES (NULL,'comment','spam_caught','0'); +INSERT INTO {vars} VALUES (NULL,'comment','access_permissions','everybody'); +INSERT INTO {vars} VALUES (NULL,'comment','rss_visible','all'); +INSERT INTO {vars} VALUES (NULL,'info','show_title','1'); +INSERT INTO {vars} VALUES (NULL,'info','show_description','1'); +INSERT INTO {vars} VALUES (NULL,'info','show_owner','1'); +INSERT INTO {vars} VALUES (NULL,'info','show_name','1'); +INSERT INTO {vars} VALUES (NULL,'info','show_captured','1'); +INSERT INTO {vars} VALUES (NULL,'slideshow','max_scale','0'); +INSERT INTO {vars} VALUES (NULL,'tag','tag_cloud_size','30'); diff --git a/installer/installer.php b/installer/installer.php new file mode 100644 index 0000000..434d8e5 --- /dev/null +++ b/installer/installer.php @@ -0,0 +1,270 @@ +connect_error because of bugs before PHP 5.2.9 + $error = mysqli_connect_error(); + return empty($error); + } + function mysql_query($query) { + return installer::$mysqli->query($query); + } + function mysql_num_rows($result) { + return $result->num_rows; + } + function mysql_error() { + return installer::$mysqli->error; + } + function mysql_select_db($db) { + return installer::$mysqli->select_db($db); + } + } + + $host = empty($config["port"]) ? $config['host'] : "{$config['host']}:{$config['port']}"; + return @mysql_connect($host, $config["user"], $config["password"]); + } + + static function select_db($config) { + if (mysql_select_db($config["dbname"])) { + return true; + } + + return mysql_query("CREATE DATABASE `{$config['dbname']}`") && + mysql_select_db($config["dbname"]); + } + + static function verify_mysql_version($config) { + return version_compare(installer::mysql_version($config), "5.0.0", ">="); + } + + static function mysql_version($config) { + $result = mysql_query("SHOW VARIABLES WHERE variable_name = \"version\""); + $row = mysql_fetch_object($result); + return $row->Value; + } + + static function db_empty($config) { + $query = "SHOW TABLES LIKE '{$config['prefix']}items'"; + $results = mysql_query($query); + if ($results === false) { + $msg = mysql_error(); + return $msg; + } + return mysql_num_rows($results) === 0; + } + + static function create_admin($config) { + $salt = ""; + for ($i = 0; $i < 4; $i++) { + $char = mt_rand(48, 109); + $char += ($char > 90) ? 13 : ($char > 57) ? 7 : 0; + $salt .= chr($char); + } + if (!$password = $config["g3_password"]) { + $password = substr(md5(time() . mt_rand()), 0, 6); + } + // Escape backslash in preparation for our UPDATE statement. + $hashed_password = str_replace("\\", "\\\\", $salt . md5($salt . $password)); + $sql = self::prepend_prefix($config["prefix"], + "UPDATE {users} SET `password` = '$hashed_password' WHERE `id` = 2"); + if (mysql_query($sql)) { + } else { + throw new Exception(mysql_error()); + } + + return array("admin", $password); + } + + static function create_admin_session($config) { + $session_id = md5(time() . mt_rand()); + $user_agent = $_SERVER["HTTP_USER_AGENT"]; + $user_agent_len = strlen($user_agent); + $now = time(); + $data = "session_id|s:32:\"$session_id\""; + $data .= ";user_agent|s:{$user_agent_len}:\"$user_agent\""; + $data .= ";user|i:2"; + $data .= ";after_install|i:1"; + $data .= ";last_activity|i:$now"; + $data = base64_encode($data); + $sql = "INSERT INTO {sessions}(`session_id`, `last_activity`, `data`) " . + "VALUES('$session_id', $now, '$data')"; + $sql = self::prepend_prefix($config["prefix"], $sql); + if (mysql_query($sql)) { + setcookie("g3sid", $session_id, 0, "/", "", false, false); + } else { + throw new Exception(mysql_error()); + } + } + + static function create_private_key($config) { + $key = md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true)); + $sql = self::prepend_prefix($config["prefix"], + "INSERT INTO {vars} VALUES(NULL, 'gallery', 'private_key', '$key')"); + if (mysql_query($sql)) { + } else { + throw new Exception(mysql_error()); + } + } + + static function prepend_prefix($prefix, $sql) { + return preg_replace("#{([a-zA-Z0-9_]+)}#", "`{$prefix}$1`", $sql); + } + + static function check_environment() { + if (!function_exists("mysql_query") && !function_exists("mysqli_set_charset")) { + $errors[] = "Gallery 3 requires a MySQL database, but PHP doesn't have either the MySQL or the MySQLi extension."; + } + + if (!preg_match("/^.$/u", "ñ")) { + $errors[] = "PHP is missing Perl-Compatible Regular Expression with UTF-8 support."; + } else if (!preg_match("/^\pL$/u", "ñ")) { + $errors[] = "PHP is missing Perl-Compatible Regular Expression with Unicode support."; + } + + if (!(function_exists("spl_autoload_register"))) { + $errors[] = "PHP is missing Standard PHP Library (SPL) support"; + } + + if (!(class_exists("ReflectionClass"))) { + $errors[] = "PHP is missing reflection support"; + } + + if (!(function_exists("filter_list"))) { + $errors[] = "PHP is missing the filter extension"; + } + + if (!(extension_loaded("iconv"))) { + $errors[] = "PHP is missing the iconv extension"; + } + + if (!(extension_loaded("xml"))) { + $errors[] = "PHP is missing the XML Parser extension"; + } + + if (!(extension_loaded("simplexml"))) { + $errors[] = "PHP is missing the SimpleXML extension"; + } + + if (!extension_loaded("mbstring")) { + $errors[] = "PHP is missing the mbstring extension"; + } else if (ini_get("mbstring.func_overload") & MB_OVERLOAD_STRING) { + $errors[] = "The mbstring extension is overloading PHP's native string functions. Please disable it."; + } + + if (!function_exists("json_encode")) { + $errors[] = "PHP is missing the JavaScript Object Notation (JSON) extension. Please install it."; + } + + if (!ini_get("short_open_tag")) { + $errors[] = "Gallery requires short_open_tag to be on. Please enable it in your php.ini."; + } + + if (!function_exists("ctype_alpha")) { + $errors[] = "Gallery requires the PHP Ctype extension. Please install it."; + } + + if (self::ini_get_bool("safe_mode")) { + $errors[] = "Gallery cannot function when PHP is in Safe Mode. Please disable safe mode."; + } + + return @$errors; + } + + /** + * Convert any possible boolean ini value to true/false. + * On = on = 1 = true + * Off = off = 0 = false + */ + static function ini_get_bool($varname) { + $value = ini_get($varname); + + if (!strcasecmp("on", $value) || $value == 1 || $value === true) { + return true; + } + + if (!strcasecmp("off", $value) || $value == 0 || $value === false) { + return false; + } + + return false; + } + +} diff --git a/installer/views/already_installed.html.php b/installer/views/already_installed.html.php new file mode 100644 index 0000000..f6ac1bf --- /dev/null +++ b/installer/views/already_installed.html.php @@ -0,0 +1,5 @@ + +

+ Your Gallery 3 install is complete. +

+ diff --git a/installer/views/db_not_empty.html.php b/installer/views/db_not_empty.html.php new file mode 100644 index 0000000..bc45458 --- /dev/null +++ b/installer/views/db_not_empty.html.php @@ -0,0 +1,8 @@ + +

Uh oh!

+

+ The database you provided already has Gallery 3 tables in it. + Continuing with the install would overwrite your existing install. + Please either use a different database, delete the old tables, + or choose a different table prefix. +

diff --git a/installer/views/environment_errors.html.php b/installer/views/environment_errors.html.php new file mode 100644 index 0000000..318be61 --- /dev/null +++ b/installer/views/environment_errors.html.php @@ -0,0 +1,20 @@ + +

Whoa there!

+ +

+ There are some problems with your web hosting environment + that need to be fixed before you can successfully install + Gallery 3. +

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

+ Check again +

diff --git a/installer/views/get_db_info.html.php b/installer/views/get_db_info.html.php new file mode 100644 index 0000000..c9f57e1 --- /dev/null +++ b/installer/views/get_db_info.html.php @@ -0,0 +1,89 @@ + +

Let's get going!

+

+ Installing Gallery is easy. We just need a place to put your photos + and info about your MySQL database. +

+ + +
+ Photo Storage + +

+ We're having trouble creating a place for your photos. Can you + help? We need you to create a directory called var in + your gallery3 directory. This sample code works for most users. + Run it in the gallery3 directory: + + mkdir var
+ chmod 777 var +
+ Check again +

+ +

+ We've found a place to store your photos: + +

+ +
+ + +
+
+ Database +

+ Gallery 3 needs a MySQL database. The values provided work for + most setups, so if you're confused try clicking continue. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ Database name + + +
+ User + + +
+ Password + + +
+ Host + + +
+ Table prefix (optional) + + +
+ +
+
+
+ diff --git a/installer/views/install.html.php b/installer/views/install.html.php new file mode 100644 index 0000000..7a30561 --- /dev/null +++ b/installer/views/install.html.php @@ -0,0 +1,24 @@ + + + + Gallery 3 Installer + + + +
+ +
+ +
+ +
+ + diff --git a/installer/views/invalid_db_info.html.php b/installer/views/invalid_db_info.html.php new file mode 100644 index 0000000..242a28a --- /dev/null +++ b/installer/views/invalid_db_info.html.php @@ -0,0 +1,6 @@ + +

Uh oh!

+

+ We were unable to connect to your MySQL server with the username and + password that you provided. Please go back and try again! +

diff --git a/installer/views/invalid_db_version.html.php b/installer/views/invalid_db_version.html.php new file mode 100644 index 0000000..5b021ba --- /dev/null +++ b/installer/views/invalid_db_version.html.php @@ -0,0 +1,5 @@ + +

Uh oh!

+

+ Gallery requires at least MySQL version 5.0.0. You're using version +

diff --git a/installer/views/missing_db.html.php b/installer/views/missing_db.html.php new file mode 100644 index 0000000..fa42fde --- /dev/null +++ b/installer/views/missing_db.html.php @@ -0,0 +1,13 @@ + +

Can't find that database!

+

+ We were able to connect to your MySQL server, yay! But the database + name you gave us doesn't exist and we don't have permissions to + create it for you. Please create the database manually, then go + back and try again. +

+ +

+ If you're having trouble creating the database, please contact your + web host or system administrator for assistance. +

diff --git a/installer/views/oops.html.php b/installer/views/oops.html.php new file mode 100644 index 0000000..9c6b165 --- /dev/null +++ b/installer/views/oops.html.php @@ -0,0 +1,10 @@ + +

Oops!

+

+ Something unexpected happened and we can't finish your install. + We'll try to provide some details about the specific problem below. +

+

+ +

+ diff --git a/installer/views/success.html.php b/installer/views/success.html.php new file mode 100644 index 0000000..e9ee981 --- /dev/null +++ b/installer/views/success.html.php @@ -0,0 +1,23 @@ + +

Success!

+

+ Your Gallery 3 install is complete! +

+ + +

Before you start using it...

+

+ We've created an account for you to use: +
+ username: +
+ password: +
+
+ Save this information in a safe place, or change your admin password + right away! +

+ + +

Start using Gallery

+ diff --git a/installer/web.php b/installer/web.php new file mode 100644 index 0000000..5fa8541 --- /dev/null +++ b/installer/web.php @@ -0,0 +1,94 @@ + $errors)); + } else { + $content = render("get_db_info.html.php"); + } + break; + + case "save_db_info": + $config = array("host" => $_POST["dbhost"], + "user" => $_POST["dbuser"], + "password" => $_POST["dbpass"], + "dbname" => $_POST["dbname"], + "prefix" => $_POST["prefix"], + "type" => function_exists("mysqli_set_charset") ? "mysqli" : "mysql"); + list ($config["host"], $config["port"]) = explode(":", $config["host"] . ":"); + foreach ($config as $k => $v) { + if ($k == "password") { + $config[$k] = str_replace(array("'", "\\"), array("\\'", "\\\\"), $v); + } else { + $config[$k] = strtr($v, "'`\\", "___"); + } + } + + if (!installer::connect($config)) { + $content = render("invalid_db_info.html.php"); + } else if (!installer::verify_mysql_version($config)) { + $content = render("invalid_db_version.html.php"); + } else if (!installer::select_db($config)) { + $content = render("missing_db.html.php"); + } else if (is_string($count = installer::db_empty($config)) || !$count) { + if (is_string($count)) { + $content = oops($count); + } else { + $content = render("db_not_empty.html.php"); + } + } else if (!installer::unpack_var()) { + $content = oops("Unable to create files inside the var directory"); + } else if (!installer::unpack_sql($config)) { + $content = oops("Failed to create tables in your database:" . mysql_error()); + } else if (!installer::create_database_config($config)) { + $content = oops("Couldn't create var/database.php"); + } else { + try { + list ($user, $password) = installer::create_admin($config); + installer::create_admin_session($config); + $content = render("success.html.php", array("user" => $user, "password" => $password)); + + installer::create_private_key($config); + } catch (Exception $e) { + $content = oops($e->getMessage()); + } + } + break; + } +} + +include("views/install.html.php"); + +function render($view, $args=array()) { + ob_start(); + extract($args); + include(DOCROOT . "installer/views/" . $view); + return ob_get_clean(); +} + +function oops($error) { + return render("oops.html.php", array("error" => $error)); +} diff --git a/lib/flowplayer.controls.swf.php b/lib/flowplayer.controls.swf.php new file mode 100644 index 0000000..0ef7a46 --- /dev/null +++ b/lib/flowplayer.controls.swf.php @@ -0,0 +1,31 @@ +1){var params=arguments[1],conf=(arguments.length==3)?arguments[2]:{};if(typeof params=='string'){params={src:params};} +params=extend({bgcolor:"#000000",version:[10,1],expressInstall:"http://releases.flowplayer.org/swf/expressinstall.swf",cachebusting:false},params);if(typeof arg=='string'){if(arg.indexOf(".")!=-1){var instances=[];each(select(arg),function(){instances.push(new Player(this,clone(params),clone(conf)));});return new Iterator(instances);}else{var node=el(arg);return new Player(node!==null?node:clone(arg),clone(params),clone(conf));}}else if(arg){return new Player(arg,clone(params),clone(conf));}} +return null;};extend(window.$f,{fireEvent:function(){var a=[].slice.call(arguments);var p=$f(a[0]);return p?p._fireEvent(a.slice(1)):null;},addPlugin:function(name,fn){Player.prototype[name]=fn;return $f;},each:each,extend:extend});if(typeof jQuery=='function'){jQuery.fn.flowplayer=function(params,conf){if(!arguments.length||typeof arguments[0]=='number'){var arr=[];this.each(function(){var p=$f(this);if(p){arr.push(p);}});return arguments.length?arr[arguments[0]]:new Iterator(arr);} +return this.each(function(){$f(this,clone(params),conf?clone(conf):{});});};}}();!function(){var IE=document.all,URL='http://get.adobe.com/flashplayer',JQUERY=typeof jQuery=='function',RE=/(\d+)[^\d]+(\d+)[^\d]*(\d*)/,GLOBAL_OPTS={width:'100%',height:'100%',id:"_"+(""+Math.random()).slice(9),allowfullscreen:true,allowscriptaccess:'always',quality:'high',version:[3,0],onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};});} +function extend(to,from){if(from){for(var key in from){if(from.hasOwnProperty(key)){to[key]=from[key];}}} +return to;} +function map(arr,func){var newArr=[];for(var i in arr){if(arr.hasOwnProperty(i)){newArr[i]=func(arr[i]);}} +return newArr;} +window.flashembed=function(root,opts,conf){if(typeof root=='string'){root=document.getElementById(root.replace("#",""));} +if(!root){return;} +if(typeof opts=='string'){opts={src:opts};} +return new Flash(root,extend(extend({},GLOBAL_OPTS),opts),conf);};var f=extend(window.flashembed,{conf:GLOBAL_OPTS,getVersion:function(){var fo,ver;try{ver=navigator.plugins["Shockwave Flash"].description.slice(16);}catch(e){try{fo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");ver=fo&&fo.GetVariable("$version");}catch(err){try{fo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");ver=fo&&fo.GetVariable("$version");}catch(err2){}}} +ver=RE.exec(ver);return ver?[1*ver[1],1*ver[(ver[1]*1>9?2:3)]*1]:[0,0];},asString:function(obj){if(obj===null||obj===undefined){return null;} +var type=typeof obj;if(type=='object'&&obj.push){type='array';} +switch(type){case'string':return string2JsonString(obj);case'array':return'['+map(obj,function(el){return f.asString(el);}).join(',')+']';case'function':return'"function()"';case'object':var str=[];for(var prop in obj){if(obj.hasOwnProperty(prop)){str.push('"'+prop+'":'+f.asString(obj[prop]));}} +return'{'+str.join(',')+'}';} +return String(obj).replace(/\s/g," ").replace(/\'/g,"\"");},getHTML:function(opts,conf){opts=extend({},opts);var html='';} +opts.width=opts.height=opts.id=opts.w3c=opts.src=null;opts.onFail=opts.version=opts.expressInstall=null;for(var key in opts){if(opts[key]){html+='';}} +var vars="";if(conf){for(var k in conf){if(conf[k]){var val=conf[k];vars+=encodeURIComponent(k)+'=' ++encodeURIComponent(/function|object/.test(typeof val)?f.asString(val):val) ++'&';}} +vars=vars.slice(0,-1);html+='';} +html+="";return html;},isSupported:function(ver){return VERSION[0]>ver[0]||VERSION[0]==ver[0]&&VERSION[1]>=ver[1];}});var VERSION=f.getVersion();function Flash(root,opts,conf){if(f.isSupported(opts.version)){root.innerHTML=f.getHTML(opts,conf);}else if(opts.expressInstall&&f.isSupported([6,65])){root.innerHTML=f.getHTML(extend(opts,{src:opts.expressInstall}),{MMredirectURL:encodeURIComponent(location.href),MMplayerType:'PlugIn',MMdoctitle:document.title});}else{if(!root.innerHTML.replace(/\s/g,'')){root.innerHTML="

Flash version "+opts.version+" or greater is required

"+"

"+ +(VERSION[0]>0?"Your version is "+VERSION:"You have no flash plugin installed")+"

"+ +(root.tagName=='A'?"

Click here to download latest version

":"

Download latest version from here

");if(root.tagName=='A'||root.tagName=="DIV"){root.onclick=function(){location.href=URL;};}} +if(opts.onFail){var ret=opts.onFail.call(this);if(typeof ret=='string'){root.innerHTML=ret;}}} +if(IE){window[opts.id]=document.getElementById(opts.id);} +extend(this,{getRoot:function(){return root;},getOptions:function(){return opts;},getConf:function(){return conf;},getApi:function(){return root.firstChild;}});} +var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function string2JsonString(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';} +if(JQUERY){jQuery.tools=jQuery.tools||{version:'@VERSION'};jQuery.tools.flashembed={conf:GLOBAL_OPTS};jQuery.fn.flashembed=function(opts,conf){return this.each(function(){$(this).data("flashembed",flashembed(this,opts,conf));});};}}();$f.addPlugin("ipad",function(options){var STATE_UNLOADED=-1;var STATE_LOADED=0;var STATE_UNSTARTED=1;var STATE_BUFFERING=2;var STATE_PLAYING=3;var STATE_PAUSED=4;var STATE_ENDED=5;var self=this;var currentVolume=1;var onStartFired=false;var stopping=false;var playAfterSeek=false;var activeIndex=0;var activePlaylist=[];var lastSecondTimer;var endTime=null;var startTime=0;var clipDefaults={accelerated:false,autoBuffering:false,autoPlay:true,baseUrl:null,bufferLength:3,connectionProvider:null,cuepointMultiplier:1000,cuepoints:[],controls:{},duration:0,extension:'',fadeInSpeed:1000,fadeOutSpeed:1000,image:false,linkUrl:null,linkWindow:'_self',live:false,metaData:{},originalUrl:null,position:0,playlist:[],provider:'http',scaling:'scale',seekableOnBegin:false,start:0,url:null,urlResolvers:[]};var currentState=STATE_UNLOADED;var previousState=STATE_UNLOADED;var isiDevice=/iPad|iPhone|iPod/i.test(navigator.userAgent);var video=null;function extend(to,from,includeFuncs){if(from){for(key in from){if(key){if(from[key]&&typeof from[key]=="function"&&!includeFuncs) +continue;if(from[key]&&typeof from[key]=="object"&&from[key].length===undefined){var cp={};extend(cp,from[key]);to[key]=cp;}else{to[key]=from[key];}}}} +return to;} +var opts={simulateiDevice:false,controlsSizeRatio:1.5,controls:true,debug:false,validExtensions:'mov|m4v|mp4|avi|mp3|m4a|aac|m3u8|m3u|pls',posterExtensions:'png|jpg'};extend(opts,options);var validExtensions=opts.validExtensions?new RegExp('^\.('+opts.validExtensions+')$','i'):null;var posterExtensions=new RegExp('^\.('+opts.posterExtensions+')$','i');function log(){if(opts.debug){if(isiDevice){var str=[].splice.call(arguments,0).join(', ');console.log.apply(console,[str]);}else{console.log.apply(console,arguments);}}} +function stateDescription(state){switch(state){case-1:return"UNLOADED";case 0:return"LOADED";case 1:return"UNSTARTED";case 2:return"BUFFERING";case 3:return"PLAYING";case 4:return"PAUSED";case 5:return"ENDED";} +return"UNKOWN";} +function actionAllowed(eventName){var ret=$f.fireEvent(self.id(),"onBefore"+eventName,activeIndex);return ret!==false;} +function stopEvent(e){e.stopPropagation();e.preventDefault();return false;} +function setState(state,force){if(currentState==STATE_UNLOADED&&!force) +return;previousState=currentState;currentState=state;stopPlayTimeTracker();if(state==STATE_PLAYING) +startPlayTimeTracker();log(stateDescription(state));} +function resetState(){video.fp_stop();onStartFired=false;stopping=false;playAfterSeek=false;setState(STATE_UNSTARTED);setState(STATE_UNSTARTED);} +var _playTimeTracker=null;function startPlayTimeTracker(){if(_playTimeTracker) +return;console.log("starting tracker");_playTimeTracker=setInterval(onTimeTracked,100);onTimeTracked();} +function stopPlayTimeTracker(){clearInterval(_playTimeTracker);_playTimeTracker=null;} +function onTimeTracked(){var currentTime=Math.floor(video.fp_getTime()*10)*100;var duration=Math.floor(video.duration*10)*100;var fireTime=(new Date()).time;function fireCuePointsIfNeeded(time,cues){time=time>=0?time:duration-Math.abs(time);for(var i=0;ifireTime){cues[i].lastTimeFired=-1;}else if(cues[i].lastTimeFired+500>fireTime){continue;}else{if(time==currentTime||(currentTime-500time)){cues[i].lastTimeFired=fireTime;$f.fireEvent(self.id(),'onCuepoint',activeIndex,cues[i].fnId,cues[i].parameters);}}}} +$f.each(self.getCommonClip().cuepoints,fireCuePointsIfNeeded);$f.each(activePlaylist[activeIndex].cuepoints,fireCuePointsIfNeeded);} +function replay(){resetState();playAfterSeek=true;video.fp_seek(0);} +function scaleVideo(clip){} +function addAPI(){console.log(video);function fixClip(clip){var extendedClip={};extend(extendedClip,clipDefaults);extend(extendedClip,self.getCommonClip());extend(extendedClip,clip);if(extendedClip.ipadUrl) +url=decodeURIComponent(extendedClip.ipadUrl);else if(extendedClip.url) +url=extendedClip.url;if(url&&url.indexOf('://')==-1&&extendedClip.ipadBaseUrl) +url=extendedClip.ipadBaseUrl+'/'+url;else if(url&&url.indexOf('://')==-1&&extendedClip.baseUrl) +url=extendedClip.baseUrl+'/'+url;extendedClip.originalUrl=extendedClip.url;extendedClip.completeUrl=url;extendedClip.extension=extendedClip.completeUrl.substr(extendedClip.completeUrl.lastIndexOf('.'));var queryIndex=extendedClip.extension.indexOf('?');if(queryIndex>-1) +extendedClip.extension=extendedClip.extension.substr(0,queryIndex);extendedClip.type='video';delete extendedClip.index;log("fixed clip",extendedClip);return extendedClip;} +video.fp_play=function(clip,inStream,forcePlay,poster){var url=null;var autoBuffering=true;var autoPlay=true;log("Calling play() "+clip,clip);if(inStream){log("ERROR: inStream clips not yet supported");return;} +if(clip!==undefined){if(typeof clip=="number"){if(activeIndex>=activePlaylist.length) +return;activeIndex=clip;clip=activePlaylist[activeIndex];}else{if(typeof clip=="string"){clip={url:clip};} +video.fp_setPlaylist(clip.length!==undefined?clip:[clip]);} +if(activeIndex==0&&activePlaylist.length>1&&posterExtensions.test(activePlaylist[activeIndex].extension)){var poster=activePlaylist[activeIndex].url;console.log("Poster image available with url "+poster);++activeIndex;console.log("Not last clip in the playlist, moving to next one");video.fp_play(activeIndex,false,true,poster);return;} +if(validExtensions&&!validExtensions.test(activePlaylist[activeIndex].extension)){return;} +clip=activePlaylist[activeIndex];url=clip.completeUrl;if(clip.autoBuffering!==undefined&&clip.autoBuffering===false) +autoBuffering=false;if(clip.autoPlay===undefined||clip.autoPlay===true||forcePlay===true){autoBuffering=true;autoPlay=true;}else{autoPlay=false;}}else{log("clip was not given, simply calling video.play, if not already buffering");if(currentState!=STATE_BUFFERING){video.play();} +return;} +log("about to play "+url,autoBuffering,autoPlay);resetState();if(url){log("Changing SRC attribute"+url);video.setAttribute('src',url);} +if(autoBuffering){if(!actionAllowed('Begin')) +return false;if(poster){autoPlay=clip.autoPlay;video.setAttribute('poster',poster);video.setAttribute('preload',"none");} +$f.fireEvent(self.id(),'onBegin',activeIndex);log("calling video.load()");video.load();} +if(autoPlay){log("calling video.play()");video.play();}} +video.fp_pause=function(){log("pause called");if(!actionAllowed('Pause')) +return false;video.pause();};video.fp_resume=function(){log("resume called");if(!actionAllowed('Resume')) +return false;video.play();};video.fp_stop=function(){log("stop called");if(!actionAllowed('Stop')) +return false;stopping=true;video.pause();try{video.currentTime=0;}catch(ignored){}};video.fp_seek=function(position){log("seek called "+position);if(!actionAllowed('Seek')) +return false;var seconds=0;var position=position+"";if(position.charAt(position.length-1)=='%'){var percentage=parseInt(position.substr(0,position.length-1))/100;var duration=video.duration;seconds=duration*percentage;}else{seconds=position;} +try{video.currentTime=seconds;}catch(e){log("Wrong seek time");}};video.fp_getTime=function(){return video.currentTime;};video.fp_mute=function(){log("mute called");if(!actionAllowed('Mute')) +return false;currentVolume=video.volume;video.volume=0;};video.fp_unmute=function(){if(!actionAllowed('Unmute')) +return false;video.volume=currentVolume;};video.fp_getVolume=function(){return video.volume*100;};video.fp_setVolume=function(volume){if(!actionAllowed('Volume')) +return false;video.volume=volume/100;};video.fp_toggle=function(){log('toggle called');if(self.getState()==STATE_ENDED){replay();return;} +if(video.paused) +video.fp_play();else +video.fp_pause();};video.fp_isPaused=function(){return video.paused;};video.fp_isPlaying=function(){return!video.paused;};video.fp_getPlugin=function(name){if(name=='canvas'||name=='controls'){var config=self.getConfig();return config['plugins']&&config['plugins'][name]?config['plugins'][name]:null;} +log("ERROR: no support for "+name+" plugin on iDevices");return null;};video.fp_close=function(){setState(STATE_UNLOADED);video.parentNode.removeChild(video);video=null;};video.fp_getStatus=function(){var bufferStart=0;var bufferEnd=0;try{bufferStart=video.buffered.start();bufferEnd=video.buffered.end();}catch(ignored){} +return{bufferStart:bufferStart,bufferEnd:bufferEnd,state:currentState,time:video.fp_getTime(),muted:video.muted,volume:video.fp_getVolume()};};video.fp_getState=function(){return currentState;};video.fp_startBuffering=function(){if(currentState==STATE_UNSTARTED) +video.load();};video.fp_setPlaylist=function(playlist){log("Setting playlist");activeIndex=0;for(var i=0;i0){clipDuration=activePlaylist[activeIndex].duration;endTime=clipDuration+startTime;}else{clipDuration=video.duration;endTime=null;} +video.fp_updateClip({duration:clipDuration,metaData:{duration:video.duration}},activeIndex);activePlaylist[activeIndex].duration=video.duration;activePlaylist[activeIndex].metaData={duration:video.duration};$f.fireEvent(self.id(),'onMetaData',activeIndex,activePlaylist[activeIndex]);};video.addEventListener('loadedmetadata',onMetaData,false);video.addEventListener('durationchange',onMetaData,false);var onTimeUpdate=function(e){if(endTime&&video.currentTime>endTime){video.fp_seek(startTime);resetState();return stopEvent(e);}};video.addEventListener("timeupdate",onTimeUpdate,false);var onStart=function(e){if(currentState==STATE_PAUSED){if(!actionAllowed('Resume')){log("Resume disallowed, pausing");video.fp_pause();return stopEvent(e);} +$f.fireEvent(self.id(),'onResume',activeIndex);} +setState(STATE_PLAYING);if(!onStartFired){onStartFired=true;$f.fireEvent(self.id(),'onStart',activeIndex);}};video.addEventListener('playing',onStart,false);var onPlay=function(e){startLastSecondTimer();} +video.addEventListener('play',onPlay,false);var onFinish=function(e){if(!actionAllowed('Finish')){if(activePlaylist.length==1){log("Active playlist only has one clip, onBeforeFinish returned false. Replaying");replay();}else if(activeIndex!=(activePlaylist.length-1)){log("Not the last clip in the playlist, but onBeforeFinish returned false. Returning to the beginning of current clip");video.fp_seek(0);}else{log("Last clip in playlist, but onBeforeFinish returned false, start again from the beginning");video.fp_play(0);} +return stopEvent(e);} +setState(STATE_ENDED);$f.fireEvent(self.id(),'onFinish',activeIndex);if(activePlaylist.length>1&&activeIndex<(activePlaylist.length-1)){log("Not last clip in the playlist, moving to next one");video.fp_play(++activeIndex,false,true);}};video.addEventListener('ended',onFinish,false);var onError=function(e){setState(STATE_LOADED,true);$f.fireEvent(self.id(),'onError',activeIndex,201);if(opts.onFail&&opts.onFail instanceof Function) +opts.onFail.apply(self,[]);};video.addEventListener('error',onError,false);var onPause=function(e){log("got pause event from player"+self.id());if(stopping) +return;if(currentState==STATE_BUFFERING&&previousState==STATE_UNSTARTED){log("forcing play");setTimeout(function(){video.play();},0);return;} +if(!actionAllowed('Pause')){video.fp_resume();return stopEvent(e);} +stopLastSecondTimer();setState(STATE_PAUSED);$f.fireEvent(self.id(),'onPause',activeIndex);} +video.addEventListener('pause',onPause,false);var onSeek=function(e){$f.fireEvent(self.id(),'onBeforeSeek',activeIndex);};video.addEventListener('seeking',onSeek,false);var onSeekDone=function(e){if(stopping){stopping=false;$f.fireEvent(self.id(),'onStop',activeIndex);} +else +$f.fireEvent(self.id(),'onSeek',activeIndex);log("seek done, currentState",stateDescription(currentState));if(playAfterSeek){playAfterSeek=false;video.fp_play();}else if(currentState!=STATE_PLAYING) +video.fp_pause();};video.addEventListener('seeked',onSeekDone,false);var onVolumeChange=function(e){$f.fireEvent(self.id(),'onVolume',video.fp_getVolume());};video.addEventListener('volumechange',onVolumeChange,false);} +function startLastSecondTimer(){lastSecondTimer=setInterval(function(){if(video.fp_getTime()>=video.duration-1){$f.fireEvent(self.id(),'onLastSecond',activeIndex);stopLastSecondTimer();}},100);} +function stopLastSecondTimer(){clearInterval(lastSecondTimer);} +function onPlayerLoaded(){video.fp_play(0);} +function installControlbar(){} +if(isiDevice||opts.simulateiDevice){if(!window.flashembed.__replaced){var realFlashembed=window.flashembed;window.flashembed=function(root,opts,conf){if(typeof root=='string'){root=document.getElementById(root.replace("#",""));} +if(!root){return;} +var style=window.getComputedStyle(root,null);var width=parseInt(style.width);var height=parseInt(style.height);while(root.firstChild) +root.removeChild(root.firstChild);var container=document.createElement('div');var api=document.createElement('video');container.appendChild(api);root.appendChild(container);container.style.height=height+'px';container.style.width=width+'px';container.style.display='block';container.style.position='relative';container.style.background='-webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.7)))';container.style.cursor='default';container.style.webkitUserDrag='none';api.style.height='100%';api.style.width='100%';api.style.display='block';api.id=opts.id;api.name=opts.id;api.style.cursor='pointer';api.style.webkitUserDrag='none';api.type="video/mp4";api.playerConfig=conf.config;$f.fireEvent(conf.config.playerId,'onLoad','player');};flashembed.getVersion=realFlashembed.getVersion;flashembed.asString=realFlashembed.asString;flashembed.isSupported=function(){return true;} +flashembed.__replaced=true;} +var __fireEvent=self._fireEvent;self._fireEvent=function(a){if(a[0]=='onLoad'&&a[1]=='player'){video=self.getParent().querySelector('video');if(opts.controls) +video.controls="controls";addAPI();addListeners();setState(STATE_LOADED,true);video.fp_setPlaylist(video.playerConfig.playlist);onPlayerLoaded();__fireEvent.apply(self,[a]);} +var shouldFireEvent=currentState!=STATE_UNLOADED;if(currentState==STATE_UNLOADED&&typeof a=='string') +shouldFireEvent=true;if(shouldFireEvent) +return __fireEvent.apply(self,[a]);} +self._swfHeight=function(){return parseInt(video.style.height);} +self.hasiPadSupport=function(){return true;}} +return self;}); \ No newline at end of file diff --git a/lib/flowplayer.pseudostreaming-byterange.swf.php b/lib/flowplayer.pseudostreaming-byterange.swf.php new file mode 100644 index 0000000..8fc591a --- /dev/null +++ b/lib/flowplayer.pseudostreaming-byterange.swf.php @@ -0,0 +1,31 @@ + tallest_height) { + tallest_height = $(this).height(); + } + }); + return $(this).height(tallest_height); + }; + + // Vertically align a block element's content + $.fn.gallery_valign = function(container) { + return this.each(function(i){ + if (container == null) { + container = 'div'; + } + var el = $(this).find(".g-valign"); + if (!el.length) { + $(this).html("<" + container + " class=\"g-valign\">" + $(this).html() + + ""); + el = $(this).children(container + ".g-valign"); + } + var elh = $(el).height(); + var ph = $(this).height(); + var nh = (ph - elh) / 2; + if (nh < 1) { var nh = 0; } + $(el).css('margin-top', nh); + }); + }; + + // Get the viewport size + $.gallery_get_viewport_size = function() { + return { + width : function() { + return $(window).width(); + }, + height : function() { + return $(window).height(); + } + }; + }; + + /** + * Toggle the processing indicator, both large and small + * @param elementID Target ID, including #, to apply .g-loading-size + */ + $.fn.gallery_show_loading = function() { + return this.each(function(i){ + var size; + switch ($(this).attr("id")) { + case "#g-dialog": + case "#g-panel": + size = "large"; + break; + default: + size = "small"; + break; + } + $(this).toggleClass("g-loading-" + size); + }); + }; + + /** + * Reduce the width of an image if it's wider than its parent container + * @param elementID The image container's ID + */ + $.fn.gallery_fit_photo = function() { + return this.each(function(i) { + var container_width = $(this).width(); + var photo = $(this).gallery_get_photo(); + var photo_width = photo.width(); + if (container_width < photo_width) { + var proportion = container_width / photo_width; + photo.width(container_width); + photo.height(proportion * photo.height()); + } + }); + }; + + /** + * Get a thumbnail or resize photo within a container + * @param elementID The image container's ID + * @return object + */ + $.fn.gallery_get_photo = function() { + var photo = $(this).find("img,object").filter(function() { + return this.id.match(/g-(photo|movie)-id-\d+/); + }); + return photo; + }; + + /** + * Get the sum of an element's height, margin-top, and margin-bottom + * @param elementID the element's ID + * @return int + */ + $.fn.gallery_height = function() { + var ht = $(this).height(); + var mt = parseInt($(this).css("margin-top")); + var mb = parseInt($(this).css("margin-bottom")); + return ht + parseInt(mt) + parseInt(mb); + }; + + // Add hover state to buttons + $.fn.gallery_hover_init = function() { + $(".ui-state-default").hover( + function(){ + $(this).addClass("ui-state-hover"); + }, + function(){ + $(this).removeClass("ui-state-hover"); + } + ); + }; + + // Ajax handler for replacing an image, used in Ajax thumbnail rotation + $.gallery_replace_image = function(data, img_selector) { + $(img_selector).attr({src: data.src, width: data.width, height: data.height}); + $(img_selector).trigger("gallery.change"); + }; + + // Initialize context menus + $.fn.gallery_context_menu = function() { + if ($(".g-context-menu li").length) { + var hover_target = $(this).find(".g-context-menu"); + if (hover_target.attr("context_menu_initialized")) { + return; + } + var list = $(hover_target).find("ul"); + hover_target.find("*").removeAttr('title'); + list.hide(); + hover_target.hover( + function() { + list.stop(false, true).slideDown("fast"); + $(this).find(".g-dialog-link").gallery_dialog(); + $(this).find(".g-ajax-link").gallery_ajax(); + }, + function() { + list.stop(true, true).slideUp("slow"); + } + ); + hover_target.attr("context_menu_initialized", 1); + } + }; + + // Size a container to fit within the browser window + $.gallery_auto_fit_window = function(imageWidth, imageHeight) { + var size = $.gallery_get_viewport_size(); + var width = size.width() - 6, + height = size.height() - 6; + + var ratio = width / imageWidth; + imageWidth *= ratio; + imageHeight *= ratio; + + /* after scaling the width, check that the height fits */ + if (imageHeight > height) { + ratio = height / imageHeight; + imageWidth *= ratio; + imageHeight *= ratio; + } + + // handle the case where the calculation is almost zero (2.14e-14) + return { + top: Math.round((height - imageHeight) / 2), + left: Math.round((width - imageWidth) / 2), + width: Math.round(imageWidth), + height: Math.round(imageHeight) + }; + }; + + // Initialize a short form. Short forms may contain only one text input. + $.fn.gallery_short_form = function() { + return this.each(function(i){ + var label = $(this).find("label:first"); + var input = $(this).find("input[type=text]:first"); + var button = $(this).find("input[type=submit]"); + + $(".g-short-form").addClass("ui-helper-clearfix"); + + // Place button's on the left for RTL languages + if ($(".rtl").length) { + $(".g-short-form input[type=text]").addClass("ui-corner-right"); + $(".g-short-form input[type=submit]").addClass("ui-state-default ui-corner-left"); + } else { + $(".g-short-form input[type=text]").addClass("ui-corner-left"); + $(".g-short-form input[type=submit]").addClass("ui-state-default ui-corner-right"); + } + + // Set the input value equal to label text + if (input.val() == "") { + input.val(label.html()); + button.enable(false); + } + + // Attach event listeners to the input + input.bind("focus", function(e) { + // Empty input value if it equals it's label + if ($(this).val() == label.html()) { + $(this).val(""); + } + button.enable(true); + }); + + input.bind("blur", function(e){ + // Reset the input value if it's empty + if ($(this).val() == "") { + $(this).val(label.html()); + button.enable(false); + } + }); + }); + }; + + // Augment jQuery autocomplete to expect the first response line to + // be a tag that protects against UTF-7 attacks. + $.fn.gallery_autocomplete = function(url, options) { + // Drop the first response - it should be a meta tag + options.parse = function(data) { + var parsed = []; + var rows = data.split("\n"); + if (rows[0].indexOf(" tag in first line of autocomplete response'; + } + rows.shift(); // drop tag + for (var i=0; i < rows.length; i++) { + var row = $.trim(rows[i]); + if (row) { + row = row.split("|"); + parsed[parsed.length] = { + data: row, + value: row[0], + result: row[0] + }; + } + } + return parsed; + }; + + $(this).autocomplete(url, options); + }; + +})(jQuery); diff --git a/lib/gallery.dialog.js b/lib/gallery.dialog.js new file mode 100644 index 0000000..3115532 --- /dev/null +++ b/lib/gallery.dialog.js @@ -0,0 +1,221 @@ + +(function($) { + $.widget("ui.gallery_dialog", { + _init: function() { + var self = this; + if (!self.options.immediate) { + this.element.click(function(event) { + event.preventDefault(); + self._show($(event.currentTarget).attr("href")); + return false; + }); + } else { + self._show(this.element.attr("href")); + } + }, + + _show: function(sHref) { + var self = this; + var eDialog = '
'; + + if ($("#g-dialog").length) { + $("#g-dialog").dialog("close"); + } + $("body").append(eDialog); + + if (!self.options.close) { + self.options.close = self.close_dialog; + } + $("#g-dialog").dialog(self.options); + + $("#g-dialog").gallery_show_loading(); + + $.ajax({ + url: sHref, + type: "GET", + beforeSend: function(xhr) { + // Until we convert to jquery 1.4, we need to save the XMLHttpRequest object so that we + // can detect the mime type of the reply + this.xhrData = xhr; + }, + success: function(data, textStatus, xhr) { + // Pre jquery 1.4, get the saved XMLHttpRequest object + if (xhr == undefined) { + xhr = this.xhrData; + } + var mimeType = /^(\w+\/\w+)\;?/.exec(xhr.getResponseHeader("Content-Type")); + + var content = ""; + if (mimeType[1] == "application/json") { + data = JSON.parse(data); + content = data.html; + } else { + content = data; + } + + $("#g-dialog").html(content).gallery_show_loading(); + + if ($("#g-dialog form").length) { + self.form_loaded(null, $("#g-dialog form")); + } + self._layout(); + + $("#g-dialog").dialog("open"); + self._set_title(); + + if ($("#g-dialog form").length) { + self._ajaxify_dialog(); + } + } + }); + $("#g-dialog").dialog("option", "self", self); + }, + + error: function(xhr, textStatus, errorThrown) { + $("#g-dialog").html(xhr.responseText); + self._set_title(); + self._layout(); + }, + + _layout: function() { + var dialogWidth; + var dialogHeight = $("#g-dialog").height(); + var cssWidth = new String($("#g-dialog form").css("width")); + var childWidth = cssWidth.replace(/[^0-9]/g,""); + var size = $.gallery_get_viewport_size(); + if ($("#g-dialog iframe").length) { + dialogWidth = size.width() - 100; + // Set the iframe width and height + $("#g-dialog iframe").width("100%").height(size.height() - 100); + } else if ($("#g-dialog .g-dialog-panel").length) { + dialogWidth = size.width() - 100; + $("#g-dialog").dialog("option", "height", size.height() - 100); + } else if (childWidth == "" || childWidth > 300) { + dialogWidth = 500; + } + $("#g-dialog").dialog('option', 'width', dialogWidth); + }, + + form_loaded: function(event, ui) { + // Should be defined (and localized) in the theme + MSG_CANCEL = MSG_CANCEL || 'Cancel'; + var eCancel = '' + MSG_CANCEL + ''; + if ($("#g-dialog .submit").length) { + $("#g-dialog .submit").addClass("ui-state-default ui-corner-all"); + $.fn.gallery_hover_init(); + $("#g-dialog .submit").parent().append(eCancel); + $("#g-dialog .g-cancel").click(function(event) { + $("#g-dialog").dialog("close"); + event.preventDefault(); + }); + } + $("#g-dialog .ui-state-default").hover( + function() { + $(this).addClass("ui-state-hover"); + }, + function() { + $(this).removeClass("ui-state-hover"); + } + ); + }, + + close_dialog: function(event, ui) { + var self = $("#g-dialog").dialog("option", "self"); + if ($("#g-dialog form").length) { + self._trigger("form_closing", null, $("#g-dialog form")); + } + self._trigger("dialog_closing", null, $("#g-dialog")); + $("#g-dialog").dialog("destroy").remove(); + }, + + _ajaxify_dialog: function() { + var self = this; + $("#g-dialog form").ajaxForm({ + beforeSubmit: function(formData, form, options) { + form.find(":submit") + .addClass("ui-state-disabled") + .attr("disabled", "disabled"); + return true; + }, + beforeSend: function(xhr) { + // Until we convert to jquery 1.4, we need to save the XMLHttpRequest object so that we + // can detect the mime type of the reply + this.xhrData = xhr; + }, + success: function(data) { + // Pre jquery 1.4, get the saved XMLHttpRequest object + xhr = this.xhrData; + if (xhr) { + var mimeType = /^(\w+\/\w+)\;?/.exec(xhr.getResponseHeader("Content-Type")); + + var content = ""; + if (mimeType[1] == "application/json") { + data = JSON.parse(data); + } else { + data = {"html": escape(data)}; + } + } else { + // Uploading files (eg: watermark) uses a fake xhr in jquery.form.js so + // all we have is in the data field, which should be some very simple JSON. + // Weirdly enough in Chrome the result gets wrapped in a
 element and
+             // looks like this:
+             //   
{"result":"success",
+             //   "location":"\/~bharat\/gallery3\/index.php\/admin\/watermarks"}
+ // bizarre. Strip that off before parsing. + data = JSON.parse(data.match("({.*})")[0]); + } + + if (data.html) { + $("#g-dialog").html(data.html); + $("#g-dialog").dialog("option", "position", "center"); + $("#g-dialog form :submit").removeClass("ui-state-disabled") + .attr("disabled", null); + self._set_title(); + self._ajaxify_dialog(); + self.form_loaded(null, $("#g-dialog form")); + if (typeof data.reset == 'function') { + eval(data.reset + '()'); + } + } + if (data.result == "success") { + if (data.location) { + window.location = data.location; + } else { + window.location.reload(); + } + } + }, + error: function(xhr, textStatus, errorThrown) { + $("#g-dialog").html(xhr.responseText); + self._set_title(); + self._layout(); + } + }); + }, + + _set_title: function() { + // Remove titlebar for progress dialogs or set title + if ($("#g-dialog #g-progress").length) { + $(".ui-dialog-titlebar").remove(); + } else if ($("#g-dialog h1").length) { + $("#g-dialog").dialog('option', 'title', $("#g-dialog h1:eq(0)").html()); + $("#g-dialog h1:eq(0)").hide(); + } else if ($("#g-dialog fieldset legend").length) { + $("#g-dialog").dialog('option', 'title', $("#g-dialog fieldset legend:eq(0)").html()); + } + }, + + form_closing: function(event, ui) {}, + dialog_closing: function(event, ui) {} + }); + + $.extend($.ui.gallery_dialog, { + defaults: { + autoOpen: false, + autoResize: true, + modal: true, + resizable: false, + position: "center" + } + }); +})(jQuery); diff --git a/lib/gallery.in_place_edit.js b/lib/gallery.in_place_edit.js new file mode 100644 index 0000000..5a815da --- /dev/null +++ b/lib/gallery.in_place_edit.js @@ -0,0 +1,75 @@ +(function($) { + $.widget("ui.gallery_in_place_edit", { + _init: function() { + var self = this; + this.element.click(function(event) { + event.preventDefault(); + self._show(event.currentTarget); + return false; + }); + }, + + _show: function(target) { + if ($(target).data("gallery_in_place_edit") == true) { + return; + } + $(target).data("gallery_in_place_edit", true); + var self = this; + var tag_width = $(target).width(); + $(self).data("tag_width", tag_width); + + var form = $("#g-in-place-edit-form"); + if (form.length > 0) { + self._cancel(); + } + + $.get(self.options.form_url.replace("__ID__", $(target).attr('rel')), function(data) { + var parent = $(target).parent(); + parent.children().hide(); + parent.append(data); + self._setup_form(parent.find("form")); + }); + }, + + _setup_form: function(form) { + var self = this; + var width = $(self).data("tag_width"); + form.find(":text").width(width).focus(); + form.find(".g-cancel").click(function(event) { + self._cancel(); + event.preventDefault(); + return false; + }); + $(".g-short-form").gallery_short_form(); + this._ajaxify_edit(); + }, + + _cancel: function() { + var parent = $("#g-in-place-edit-form").parent(); + $("#g-in-place-edit-form").remove(); + $(parent).children().show(); + $(parent).find(".g-editable").data("gallery_in_place_edit", false); + }, + + _ajaxify_edit: function() { + var self = this; + var form = $("#g-in-place-edit-form"); + $(form).ajaxForm({ + dataType: "json", + success: function(data) { + if (data.result == "success") { + window.location.reload(); + } else { + var parent = $(form).parent(); + $(form).replaceWith(data.form); + self._setup_form(parent.find("form")); + } + } + }); + } + }); + + $.extend($.ui.gallery_in_place_edit, { + defaults: {} + }); +})(jQuery); diff --git a/lib/gallery.panel.js b/lib/gallery.panel.js new file mode 100644 index 0000000..0683c53 --- /dev/null +++ b/lib/gallery.panel.js @@ -0,0 +1,100 @@ +(function($) { + $.widget("ui.gallery_panel", { + _init: function() { + var self = this; + this.element.click(function(event) { + event.preventDefault(); + var element = event.currentTarget; + var parent = $(element).parent().parent(); + var sHref = $(element).attr("href"); + var parentClass = $(parent).attr("class"); + var ePanel = ""; + + // We keep track of the open vs. closed state by looking to see if there' + // an orig_text attr. If that attr is missing, then the panel is closed + // and we want to open it + var should_open = !$(element).attr("orig_text"); + + // Close any open panels and reset their button text + if ($("#g-panel").length) { + $("#g-panel").slideUp("slow").remove(); + $.each($(".g-panel-link"), + function() { + if ($(this).attr("orig_text")) { + $(this).children(".g-button-text").text($(this).attr("orig_text")); + $(this).attr("orig_text", ""); + } + } + ); + } + + if (should_open) { + $(parent).after(ePanel); + $("#g-panel td").html(sHref); + $.ajax({ + url: sHref, + type: "GET", + beforeSend: function(xhr) { + // Until we convert to jquery 1.4, we need to save the + // XMLHttpRequest object + this.xhrData = xhr; + }, + success: function(data, textStatus, xhr) { + // Pre jquery 1.4, get the saved XMLHttpRequest object + if (xhr == undefined) { + xhr = this.xhrData; + } + var mimeType = /^(\w+\/\w+)\;?/.exec(xhr.getResponseHeader("Content-Type")); + var content = ""; + if (mimeType[1] == "application/json") { + data = JSON.parse(data); + content = data.html; + } else { + content = data; + } + + $("#g-panel td").html(content); + self._ajaxify_panel(); + if ($(element).attr("open_text")) { + $(element).attr("orig_text", $(element).children(".g-button-text").text()); + $(element).children(".g-button-text").text($(element).attr("open_text")); + } + $("#g-panel").addClass(parentClass).show().slideDown("slow"); + } + }); + } + + return false; + }); + }, + + _ajaxify_panel: function () { + var self = this; + $("#g-panel td form").ajaxForm({ + dataType: "json", + beforeSubmit: function(formData, form, options) { + form.find(":submit") + .addClass("ui-state-disabled") + .attr("disabled", "disabled"); + return true; + }, + success: function(data) { + if (data.html) { + $("#g-panel td").html(data.html); + self._ajaxify_panel(); + } + if (data.result == "success") { + self._trigger("success", null, {}); + if (data.location) { + window.location = data.location; + } else { + window.location.reload(); + } + } + } + }); + }, + + success: function(event, ui) {} + }); + })(jQuery); diff --git a/lib/gallery.show_full_size.js b/lib/gallery.show_full_size.js new file mode 100644 index 0000000..0baee88 --- /dev/null +++ b/lib/gallery.show_full_size.js @@ -0,0 +1,58 @@ +(function($) { + /** + * @todo Move inline CSS out to external style sheet (theme style sheet) + */ + $.gallery_show_full_size = function(image_url, image_width, image_height) { + var width = $(document).width(); + var height = $(document).height(); + var size = $.gallery_get_viewport_size(); + + $("body").append('
'); + + var image_size; + if (image_width >= size.width() - 6 || image_height >= size.height() - 6) { + image_size = $.gallery_auto_fit_window(image_width, image_height); + } else { + image_size = { + top: 12, + left: Math.round((width - image_width) / 2), + width: Math.round(image_width), + height: Math.round(image_height) + }; + } + + $("body").append('
' + + '
'); + + $().click(function() { + $("#g-fullsize-overlay*").remove(); + $("#g-fullsize").remove(); + }); + $().bind("keypress", function() { + $("#g-fullsize-overlay*").remove(); + $("#g-fullsize").remove(); + }); + $(window).resize(function() { + $("#g-fullsize-overlay").width($(document).width()).height($(document).height()); + image_size = $.gallery_auto_fit_window(image_width, image_height); + $("#g-fullsize").height(image_size.height) + .width(image_size.width) + .css("top", image_size.top) + .css("left", image_size.left); + $("#g-fullsize-image").height(image_size.height).width(image_size.width); + }); + }; +})(jQuery); diff --git a/lib/images/Galerie-Logo.png b/lib/images/Galerie-Logo.png new file mode 100644 index 0000000..95c7007 Binary files /dev/null and b/lib/images/Galerie-Logo.png differ diff --git a/lib/images/apple-touch-icon.png b/lib/images/apple-touch-icon.png new file mode 100644 index 0000000..d15ce1a Binary files /dev/null and b/lib/images/apple-touch-icon.png differ diff --git a/lib/images/favicon.ico b/lib/images/favicon.ico new file mode 100644 index 0000000..66531d8 Binary files /dev/null and b/lib/images/favicon.ico differ diff --git a/lib/images/logo.png b/lib/images/logo.png new file mode 100644 index 0000000..7d7b9b9 Binary files /dev/null and b/lib/images/logo.png differ diff --git a/lib/jquery-ui.js b/lib/jquery-ui.js new file mode 100644 index 0000000..74515a5 --- /dev/null +++ b/lib/jquery-ui.js @@ -0,0 +1,328 @@ +;jQuery.ui||(function($){var _remove=$.fn.remove,isFF2=$.browser.mozilla&&(parseFloat($.browser.version)<1.9);$.ui={version:"1.7.2",plugin:{add:function(module,option,set){var proto=$.ui[module].prototype;for(var i in set){proto.plugins[i]=proto.plugins[i]||[];proto.plugins[i].push([option,set[i]]);}},call:function(instance,name,args){var set=instance.plugins[name];if(!set||!instance.element[0].parentNode){return;} +for(var i=0;i0){return true;} +el[scroll]=1;has=(el[scroll]>0);el[scroll]=0;return has;},isOverAxis:function(x,reference,size){return(x>reference)&&(x<(reference+size));},isOver:function(y,x,top,left,height,width){return $.ui.isOverAxis(y,top,height)&&$.ui.isOverAxis(x,left,width);},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(isFF2){var attr=$.attr,removeAttr=$.fn.removeAttr,ariaNS="http://www.w3.org/2005/07/aaa",ariaState=/^aria-/,ariaRole=/^wairole:/;$.attr=function(elem,name,value){var set=value!==undefined;return(name=='role'?(set?attr.call(this,elem,name,"wairole:"+value):(attr.apply(this,arguments)||"").replace(ariaRole,"")):(ariaState.test(name)?(set?elem.setAttributeNS(ariaNS,name.replace(ariaState,"aaa:"),value):attr.call(this,elem,name.replace(ariaState,"aaa:"))):attr.apply(this,arguments)));};$.fn.removeAttr=function(name){return(ariaState.test(name)?this.each(function(){this.removeAttributeNS(ariaNS,name.replace(ariaState,""));}):removeAttr.call(this,name));};} +$.fn.extend({remove:function(){$("*",this).add(this).each(function(){$(this).triggerHandler("remove");});return _remove.apply(this,arguments);},enableSelection:function(){return this.attr('unselectable','off').css('MozUserSelect','').unbind('selectstart.ui');},disableSelection:function(){return this.attr('unselectable','on').css('MozUserSelect','none').bind('selectstart.ui',function(){return false;});},scrollParent:function(){var scrollParent;if(($.browser.msie&&(/(static|relative)/).test(this.css('position')))||(/absolute/).test(this.css('position'))){scrollParent=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test($.curCSS(this,'position',1))&&(/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));}).eq(0);}else{scrollParent=this.parents().filter(function(){return(/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));}).eq(0);} +return(/fixed/).test(this.css('position'))||!scrollParent.length?$(document):scrollParent;}});$.extend($.expr[':'],{data:function(elem,i,match){return!!$.data(elem,match[3]);},focusable:function(element){var nodeName=element.nodeName.toLowerCase(),tabIndex=$.attr(element,'tabindex');return(/input|select|textarea|button|object/.test(nodeName)?!element.disabled:'a'==nodeName||'area'==nodeName?element.href||!isNaN(tabIndex):!isNaN(tabIndex))&&!$(element)['area'==nodeName?'parents':'closest'](':hidden').length;},tabbable:function(element){var tabIndex=$.attr(element,'tabindex');return(isNaN(tabIndex)||tabIndex>=0)&&$(element).is(':focusable');}});function getter(namespace,plugin,method,args){function getMethods(type){var methods=$[namespace][plugin][type]||[];return(typeof methods=='string'?methods.split(/,?\s+/):methods);} +var methods=getMethods('getter');if(args.length==1&&typeof args[0]=='string'){methods=methods.concat(getMethods('getterSetter'));} +return($.inArray(method,methods)!=-1);} +$.widget=function(name,prototype){var namespace=name.split(".")[0];name=name.split(".")[1];$.fn[name]=function(options){var isMethodCall=(typeof options=='string'),args=Array.prototype.slice.call(arguments,1);if(isMethodCall&&options.substring(0,1)=='_'){return this;} +if(isMethodCall&&getter(namespace,name,options,args)){var instance=$.data(this[0],name);return(instance?instance[options].apply(instance,args):undefined);} +return this.each(function(){var instance=$.data(this,name);(!instance&&!isMethodCall&&$.data(this,name,new $[namespace][name](this,options))._init());(instance&&isMethodCall&&$.isFunction(instance[options])&&instance[options].apply(instance,args));});};$[namespace]=$[namespace]||{};$[namespace][name]=function(element,options){var self=this;this.namespace=namespace;this.widgetName=name;this.widgetEventPrefix=$[namespace][name].eventPrefix||name;this.widgetBaseClass=namespace+'-'+name;this.options=$.extend({},$.widget.defaults,$[namespace][name].defaults,$.metadata&&$.metadata.get(element)[name],options);this.element=$(element).bind('setData.'+name,function(event,key,value){if(event.target==element){return self._setData(key,value);}}).bind('getData.'+name,function(event,key){if(event.target==element){return self._getData(key);}}).bind('remove',function(){return self.destroy();});};$[namespace][name].prototype=$.extend({},$.widget.prototype,prototype);$[namespace][name].getterSetter='option';};$.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+'-disabled'+' '+this.namespace+'-state-disabled').removeAttr('aria-disabled');},option:function(key,value){var options=key,self=this;if(typeof key=="string"){if(value===undefined){return this._getData(key);} +options={};options[key]=value;} +$.each(options,function(key,value){self._setData(key,value);});},_getData:function(key){return this.options[key];},_setData:function(key,value){this.options[key]=value;if(key=='disabled'){this.element +[value?'addClass':'removeClass'](this.widgetBaseClass+'-disabled'+' '+ +this.namespace+'-state-disabled').attr("aria-disabled",value);}},enable:function(){this._setData('disabled',false);},disable:function(){this._setData('disabled',true);},_trigger:function(type,event,data){var callback=this.options[type],eventName=(type==this.widgetEventPrefix?type:this.widgetEventPrefix+type);event=$.Event(event);event.type=eventName;if(event.originalEvent){for(var i=$.event.props.length,prop;i;){prop=$.event.props[--i];event[prop]=event.originalEvent[prop];}} +this.element.trigger(event,data);return!($.isFunction(callback)&&callback.call(this.element[0],event,data)===false||event.isDefaultPrevented());}};$.widget.defaults={disabled:false};$.ui.mouse={_mouseInit:function(){var self=this;this.element.bind('mousedown.'+this.widgetName,function(event){return self._mouseDown(event);}).bind('click.'+this.widgetName,function(event){if(self._preventClickEvent){self._preventClickEvent=false;event.stopImmediatePropagation();return false;}});if($.browser.msie){this._mouseUnselectable=this.element.attr('unselectable');this.element.attr('unselectable','on');} +this.started=false;},_mouseDestroy:function(){this.element.unbind('.'+this.widgetName);($.browser.msie&&this.element.attr('unselectable',this._mouseUnselectable));},_mouseDown:function(event){event.originalEvent=event.originalEvent||{};if(event.originalEvent.mouseHandled){return;} +(this._mouseStarted&&this._mouseUp(event));this._mouseDownEvent=event;var self=this,btnIsLeft=(event.which==1),elIsCancel=(typeof this.options.cancel=="string"?$(event.target).parents().add(event.target).filter(this.options.cancel).length:false);if(!btnIsLeft||elIsCancel||!this._mouseCapture(event)){return true;} +this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){self.mouseDelayMet=true;},this.options.delay);} +if(this._mouseDistanceMet(event)&&this._mouseDelayMet(event)){this._mouseStarted=(this._mouseStart(event)!==false);if(!this._mouseStarted){event.preventDefault();return true;}} +this._mouseMoveDelegate=function(event){return self._mouseMove(event);};this._mouseUpDelegate=function(event){return self._mouseUp(event);};$(document).bind('mousemove.'+this.widgetName,this._mouseMoveDelegate).bind('mouseup.'+this.widgetName,this._mouseUpDelegate);($.browser.safari||event.preventDefault());event.originalEvent.mouseHandled=true;return true;},_mouseMove:function(event){if($.browser.msie&&!(document.documentMode>=9)&&!event.button){return this._mouseUp(event);} +if(this._mouseStarted){this._mouseDrag(event);return event.preventDefault();} +if(this._mouseDistanceMet(event)&&this._mouseDelayMet(event)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,event)!==false);(this._mouseStarted?this._mouseDrag(event):this._mouseUp(event));} +return!this._mouseStarted;},_mouseUp:function(event){$(document).unbind('mousemove.'+this.widgetName,this._mouseMoveDelegate).unbind('mouseup.'+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(event.target==this._mouseDownEvent.target);this._mouseStop(event);} +return false;},_mouseDistanceMet:function(event){return(Math.max(Math.abs(this._mouseDownEvent.pageX-event.pageX),Math.abs(this._mouseDownEvent.pageY-event.pageY))>=this.options.distance);},_mouseDelayMet:function(event){return this.mouseDelayMet;},_mouseStart:function(event){},_mouseDrag:function(event){},_mouseStop:function(event){},_mouseCapture:function(event){return true;}};$.ui.mouse.defaults={cancel:null,distance:1,delay:0};})(jQuery);(function($){$.widget("ui.draggable",$.extend({},$.ui.mouse,{_init:function(){if(this.options.helper=='original'&&!(/^(?:r|a|f)/).test(this.element.css("position"))) +this.element[0].style.position='relative';(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit();},destroy:function(){if(!this.element.data('draggable'))return;this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable" ++" ui-draggable-dragging" ++" ui-draggable-disabled");this._mouseDestroy();},_mouseCapture:function(event){var o=this.options;if(this.helper||o.disabled||$(event.target).is('.ui-resizable-handle')) +return false;this.handle=this._getHandle(event);if(!this.handle) +return false;return true;},_mouseStart:function(event){var o=this.options;this.helper=this._createHelper(event);this._cacheHelperProportions();if($.ui.ddmanager) +$.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};$.extend(this.offset,{click:{left:event.pageX-this.offset.left,top:event.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(event);this.originalPageX=event.pageX;this.originalPageY=event.pageY;if(o.cursorAt) +this._adjustOffsetFromHelper(o.cursorAt);if(o.containment) +this._setContainment();this._trigger("start",event);this._cacheHelperProportions();if($.ui.ddmanager&&!o.dropBehaviour) +$.ui.ddmanager.prepareOffsets(this,event);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(event,true);return true;},_mouseDrag:function(event,noPropagation){this.position=this._generatePosition(event);this.positionAbs=this._convertPositionTo("absolute");if(!noPropagation){var ui=this._uiHash();this._trigger('drag',event,ui);this.position=ui.position;} +if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+'px';if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+'px';if($.ui.ddmanager)$.ui.ddmanager.drag(this,event);return false;},_mouseStop:function(event){var dropped=false;if($.ui.ddmanager&&!this.options.dropBehaviour) +dropped=$.ui.ddmanager.drop(this,event);if(this.dropped){dropped=this.dropped;this.dropped=false;} +if((this.options.revert=="invalid"&&!dropped)||(this.options.revert=="valid"&&dropped)||this.options.revert===true||($.isFunction(this.options.revert)&&this.options.revert.call(this.element,dropped))){var self=this;$(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){self._trigger("stop",event);self._clear();});}else{this._trigger("stop",event);this._clear();} +return false;},_getHandle:function(event){var handle=!this.options.handle||!$(this.options.handle,this.element).length?true:false;$(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==event.target)handle=true;});return handle;},_createHelper:function(event){var o=this.options;var helper=$.isFunction(o.helper)?$(o.helper.apply(this.element[0],[event])):(o.helper=='clone'?this.element.clone():this.element);if(!helper.parents('body').length) +helper.appendTo((o.appendTo=='parent'?this.element[0].parentNode:o.appendTo));if(helper[0]!=this.element[0]&&!(/(fixed|absolute)/).test(helper.css("position"))) +helper.css("position","absolute");return helper;},_adjustOffsetFromHelper:function(obj){if(obj.left!=undefined)this.offset.click.left=obj.left+this.margins.left;if(obj.right!=undefined)this.offset.click.left=this.helperProportions.width-obj.right+this.margins.left;if(obj.top!=undefined)this.offset.click.top=obj.top+this.margins.top;if(obj.bottom!=undefined)this.offset.click.top=this.helperProportions.height-obj.bottom+this.margins.top;},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var po=this.offsetParent.offset();if(this.cssPosition=='absolute'&&this.scrollParent[0]!=document&&$.ui.contains(this.scrollParent[0],this.offsetParent[0])){po.left+=this.scrollParent.scrollLeft();po.top+=this.scrollParent.scrollTop();} +if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=='html'&&$.browser.msie)) +po={top:0,left:0};return{top:po.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:po.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)};},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var p=this.element.position();return{top:p.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:p.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()};}else{return{top:0,left:0};}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)};},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()};},_setContainment:function(){var o=this.options;if(o.containment=='parent')o.containment=this.helper[0].parentNode;if(o.containment=='document'||o.containment=='window')this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,$(o.containment=='document'?document:window).width()-this.helperProportions.width-this.margins.left,($(o.containment=='document'?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!(/^(document|window|parent)$/).test(o.containment)&&o.containment.constructor!=Array){var ce=$(o.containment)[0];if(!ce)return;var co=$(o.containment).offset();var over=($(ce).css("overflow")!='hidden');this.containment=[co.left+(parseInt($(ce).css("borderLeftWidth"),10)||0)+(parseInt($(ce).css("paddingLeft"),10)||0)-this.margins.left,co.top+(parseInt($(ce).css("borderTopWidth"),10)||0)+(parseInt($(ce).css("paddingTop"),10)||0)-this.margins.top,co.left+(over?Math.max(ce.scrollWidth,ce.offsetWidth):ce.offsetWidth)-(parseInt($(ce).css("borderLeftWidth"),10)||0)-(parseInt($(ce).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,co.top+(over?Math.max(ce.scrollHeight,ce.offsetHeight):ce.offsetHeight)-(parseInt($(ce).css("borderTopWidth"),10)||0)-(parseInt($(ce).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top];}else if(o.containment.constructor==Array){this.containment=o.containment;}},_convertPositionTo:function(d,pos){if(!pos)pos=this.position;var mod=d=="absolute"?1:-1;var o=this.options,scroll=this.cssPosition=='absolute'&&!(this.scrollParent[0]!=document&&$.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,scrollIsRootNode=(/(html|body)/i).test(scroll[0].tagName);return{top:(pos.top ++this.offset.relative.top*mod ++this.offset.parent.top*mod +-($.browser.safari&&this.cssPosition=='fixed'?0:(this.cssPosition=='fixed'?-this.scrollParent.scrollTop():(scrollIsRootNode?0:scroll.scrollTop()))*mod)),left:(pos.left ++this.offset.relative.left*mod ++this.offset.parent.left*mod +-($.browser.safari&&this.cssPosition=='fixed'?0:(this.cssPosition=='fixed'?-this.scrollParent.scrollLeft():scrollIsRootNode?0:scroll.scrollLeft())*mod))};},_generatePosition:function(event){var o=this.options,scroll=this.cssPosition=='absolute'&&!(this.scrollParent[0]!=document&&$.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,scrollIsRootNode=(/(html|body)/i).test(scroll[0].tagName);if(this.cssPosition=='relative'&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset();} +var pageX=event.pageX;var pageY=event.pageY;if(this.originalPosition){if(this.containment){if(event.pageX-this.offset.click.leftthis.containment[2])pageX=this.containment[2]+this.offset.click.left;if(event.pageY-this.offset.click.top>this.containment[3])pageY=this.containment[3]+this.offset.click.top;} +if(o.grid){var top=this.originalPageY+Math.round((pageY-this.originalPageY)/o.grid[1])*o.grid[1];pageY=this.containment?(!(top-this.offset.click.topthis.containment[3])?top:(!(top-this.offset.click.topthis.containment[2])?left:(!(left-this.offset.click.left').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css($(this).offset()).appendTo("body");});},stop:function(event,ui){$("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this);});}});$.ui.plugin.add("draggable","opacity",{start:function(event,ui){var t=$(ui.helper),o=$(this).data('draggable').options;if(t.css("opacity"))o._opacity=t.css("opacity");t.css('opacity',o.opacity);},stop:function(event,ui){var o=$(this).data('draggable').options;if(o._opacity)$(ui.helper).css('opacity',o._opacity);}});$.ui.plugin.add("draggable","scroll",{start:function(event,ui){var i=$(this).data("draggable");if(i.scrollParent[0]!=document&&i.scrollParent[0].tagName!='HTML')i.overflowOffset=i.scrollParent.offset();},drag:function(event,ui){var i=$(this).data("draggable"),o=i.options,scrolled=false;if(i.scrollParent[0]!=document&&i.scrollParent[0].tagName!='HTML'){if(!o.axis||o.axis!='x'){if((i.overflowOffset.top+i.scrollParent[0].offsetHeight)-event.pageY=0;i--){var l=inst.snapElements[i].left,r=l+inst.snapElements[i].width,t=inst.snapElements[i].top,b=t+inst.snapElements[i].height;if(!((l-d=t&&y1<=b)||(y2>=t&&y2<=b)||(y1b))&&((x1>=l&&x1<=r)||(x2>=l&&x2<=r)||(x1r));break;default:return false;break;}};$.ui.ddmanager={current:null,droppables:{'default':[]},prepareOffsets:function(t,event){var m=$.ui.ddmanager.droppables[t.options.scope];var type=event?event.type:null;var list=(t.currentItem||t.element).find(":data(droppable)").andSelf();droppablesLoop:for(var i=0;i').css({position:this.element.css('position'),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css('top'),left:this.element.css('left')}));this.element=this.element.parent().data("resizable",this.element.data('resizable'));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css('resize');this.originalElement.css('resize','none');this._proportionallyResizeElements.push(this.originalElement.css({position:'static',zoom:1,display:'block'}));this.originalElement.css({margin:this.originalElement.css('margin')});this._proportionallyResize();} +this.handles=o.handles||(!$('.ui-resizable-handle',this.element).length?"e,s,se":{n:'.ui-resizable-n',e:'.ui-resizable-e',s:'.ui-resizable-s',w:'.ui-resizable-w',se:'.ui-resizable-se',sw:'.ui-resizable-sw',ne:'.ui-resizable-ne',nw:'.ui-resizable-nw'});if(this.handles.constructor==String){if(this.handles=='all')this.handles='n,e,s,w,se,sw,ne,nw';var n=this.handles.split(",");this.handles={};for(var i=0;i');if(/sw|se|ne|nw/.test(handle))axis.css({zIndex:++o.zIndex});if('se'==handle){axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');};this.handles[handle]='.ui-resizable-'+handle;this.element.append(axis);}} +this._renderAxis=function(target){target=target||this.element;for(var i in this.handles){if(this.handles[i].constructor==String) +this.handles[i]=$(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var axis=$(this.handles[i],this.element),padWrapper=0;padWrapper=/sw|ne|nw|se|n|s/.test(i)?axis.outerHeight():axis.outerWidth();var padPos=['padding',/ne|nw|n/.test(i)?'Top':/se|sw|s/.test(i)?'Bottom':/^e$/.test(i)?'Right':'Left'].join("");target.css(padPos,padWrapper);this._proportionallyResize();} +if(!$(this.handles[i]).length) +continue;}};this._renderAxis(this.element);this._handles=$('.ui-resizable-handle',this.element).disableSelection();this._handles.mouseover(function(){if(!self.resizing){if(this.className) +var axis=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);self.axis=axis&&axis[1]?axis[1]:'se';}});if(o.autoHide){this._handles.hide();$(this.element).addClass("ui-resizable-autohide").hover(function(){$(this).removeClass("ui-resizable-autohide");self._handles.show();},function(){if(!self.resizing){$(this).addClass("ui-resizable-autohide");self._handles.hide();}});} +this._mouseInit();},destroy:function(){this._mouseDestroy();var _destroy=function(exp){$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove();};if(this.elementIsWrapper){_destroy(this.element);var wrapper=this.element;wrapper.parent().append(this.originalElement.css({position:wrapper.css('position'),width:wrapper.outerWidth(),height:wrapper.outerHeight(),top:wrapper.css('top'),left:wrapper.css('left')})).end().remove();} +this.originalElement.css('resize',this.originalResizeStyle);_destroy(this.originalElement);},_mouseCapture:function(event){var handle=false;for(var i in this.handles){if($(this.handles[i])[0]==event.target)handle=true;} +return this.options.disabled||!!handle;},_mouseStart:function(event){var o=this.options,iniPos=this.element.position(),el=this.element;this.resizing=true;this.documentScroll={top:$(document).scrollTop(),left:$(document).scrollLeft()};if(el.is('.ui-draggable')||(/absolute/).test(el.css('position'))){el.css({position:'absolute',top:iniPos.top,left:iniPos.left});} +if($.browser.opera&&(/relative/).test(el.css('position'))) +el.css({position:'relative',top:'auto',left:'auto'});this._renderProxy();var curleft=num(this.helper.css('left')),curtop=num(this.helper.css('top'));if(o.containment){curleft+=$(o.containment).scrollLeft()||0;curtop+=$(o.containment).scrollTop()||0;} +this.offset=this.helper.offset();this.position={left:curleft,top:curtop};this.size=this._helper?{width:el.outerWidth(),height:el.outerHeight()}:{width:el.width(),height:el.height()};this.originalSize=this._helper?{width:el.outerWidth(),height:el.outerHeight()}:{width:el.width(),height:el.height()};this.originalPosition={left:curleft,top:curtop};this.sizeDiff={width:el.outerWidth()-el.width(),height:el.outerHeight()-el.height()};this.originalMousePosition={left:event.pageX,top:event.pageY};this.aspectRatio=(typeof o.aspectRatio=='number')?o.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var cursor=$('.ui-resizable-'+this.axis).css('cursor');$('body').css('cursor',cursor=='auto'?this.axis+'-resize':cursor);el.addClass("ui-resizable-resizing");this._propagate("start",event);return true;},_mouseDrag:function(event){var el=this.helper,o=this.options,props={},self=this,smp=this.originalMousePosition,a=this.axis;var dx=(event.pageX-smp.left)||0,dy=(event.pageY-smp.top)||0;var trigger=this._change[a];if(!trigger)return false;var data=trigger.apply(this,[event,dx,dy]),ie6=$.browser.msie&&$.browser.version<7,csdif=this.sizeDiff;if(this._aspectRatio||event.shiftKey) +data=this._updateRatio(data,event);data=this._respectSize(data,event);this._propagate("resize",event);el.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length) +this._proportionallyResize();this._updateCache(data);this._trigger('resize',event,this.ui());return false;},_mouseStop:function(event){this.resizing=false;var o=this.options,self=this;if(this._helper){var pr=this._proportionallyResizeElements,ista=pr.length&&(/textarea/i).test(pr[0].nodeName),soffseth=ista&&$.ui.hasScroll(pr[0],'left')?0:self.sizeDiff.height,soffsetw=ista?0:self.sizeDiff.width;var s={width:(self.size.width-soffsetw),height:(self.size.height-soffseth)},left=(parseInt(self.element.css('left'),10)+(self.position.left-self.originalPosition.left))||null,top=(parseInt(self.element.css('top'),10)+(self.position.top-self.originalPosition.top))||null;if(!o.animate) +this.element.css($.extend(s,{top:top,left:left}));self.helper.height(self.size.height);self.helper.width(self.size.width);if(this._helper&&!o.animate)this._proportionallyResize();} +$('body').css('cursor','auto');this.element.removeClass("ui-resizable-resizing");this._propagate("stop",event);if(this._helper)this.helper.remove();return false;},_updateCache:function(data){var o=this.options;this.offset=this.helper.offset();if(isNumber(data.left))this.position.left=data.left;if(isNumber(data.top))this.position.top=data.top;if(isNumber(data.height))this.size.height=data.height;if(isNumber(data.width))this.size.width=data.width;},_updateRatio:function(data,event){var o=this.options,cpos=this.position,csize=this.size,a=this.axis;if(data.height)data.width=(csize.height*this.aspectRatio);else if(data.width)data.height=(csize.width/this.aspectRatio);if(a=='sw'){data.left=cpos.left+(csize.width-data.width);data.top=null;} +if(a=='nw'){data.top=cpos.top+(csize.height-data.height);data.left=cpos.left+(csize.width-data.width);} +return data;},_respectSize:function(data,event){var el=this.helper,o=this.options,pRatio=this._aspectRatio||event.shiftKey,a=this.axis,ismaxw=isNumber(data.width)&&o.maxWidth&&(o.maxWidthdata.width),isminh=isNumber(data.height)&&o.minHeight&&(o.minHeight>data.height);if(isminw)data.width=o.minWidth;if(isminh)data.height=o.minHeight;if(ismaxw)data.width=o.maxWidth;if(ismaxh)data.height=o.maxHeight;var dw=this.originalPosition.left+this.originalSize.width,dh=this.position.top+this.size.height;var cw=/sw|nw|w/.test(a),ch=/nw|ne|n/.test(a);if(isminw&&cw)data.left=dw-o.minWidth;if(ismaxw&&cw)data.left=dw-o.maxWidth;if(isminh&&ch)data.top=dh-o.minHeight;if(ismaxh&&ch)data.top=dh-o.maxHeight;var isNotwh=!data.width&&!data.height;if(isNotwh&&!data.left&&data.top)data.top=null;else if(isNotwh&&!data.top&&data.left)data.left=null;return data;},_proportionallyResize:function(){var o=this.options;if(!this._proportionallyResizeElements.length)return;var element=this.helper||this.element;for(var i=0;i');var ie6=$.browser.msie&&$.browser.version<7,ie6offset=(ie6?1:0),pxyoffset=(ie6?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+pxyoffset,height:this.element.outerHeight()+pxyoffset,position:'absolute',left:this.elementOffset.left-ie6offset+'px',top:this.elementOffset.top-ie6offset+'px',zIndex:++o.zIndex});this.helper.appendTo("body").disableSelection();}else{this.helper=this.element;}},_change:{e:function(event,dx,dy){return{width:this.originalSize.width+dx};},w:function(event,dx,dy){var o=this.options,cs=this.originalSize,sp=this.originalPosition;return{left:sp.left+dx,width:cs.width-dx};},n:function(event,dx,dy){var o=this.options,cs=this.originalSize,sp=this.originalPosition;return{top:sp.top+dy,height:cs.height-dy};},s:function(event,dx,dy){return{height:this.originalSize.height+dy};},se:function(event,dx,dy){return $.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[event,dx,dy]));},sw:function(event,dx,dy){return $.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[event,dx,dy]));},ne:function(event,dx,dy){return $.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[event,dx,dy]));},nw:function(event,dx,dy){return $.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[event,dx,dy]));}},_propagate:function(n,event){$.ui.plugin.call(this,n,[event,this.ui()]);(n!="resize"&&this._trigger(n,event,this.ui()));},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition};}}));$.extend($.ui.resizable,{version:"1.7.2",eventPrefix:"resize",defaults:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,cancel:":input,option",containment:false,delay:0,distance:1,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000}});$.ui.plugin.add("resizable","alsoResize",{start:function(event,ui){var self=$(this).data("resizable"),o=self.options;_store=function(exp){$(exp).each(function(){$(this).data("resizable-alsoresize",{width:parseInt($(this).width(),10),height:parseInt($(this).height(),10),left:parseInt($(this).css('left'),10),top:parseInt($(this).css('top'),10)});});};if(typeof(o.alsoResize)=='object'&&!o.alsoResize.parentNode){if(o.alsoResize.length){o.alsoResize=o.alsoResize[0];_store(o.alsoResize);} +else{$.each(o.alsoResize,function(exp,c){_store(exp);});}}else{_store(o.alsoResize);}},resize:function(event,ui){var self=$(this).data("resizable"),o=self.options,os=self.originalSize,op=self.originalPosition;var delta={height:(self.size.height-os.height)||0,width:(self.size.width-os.width)||0,top:(self.position.top-op.top)||0,left:(self.position.left-op.left)||0},_alsoResize=function(exp,c){$(exp).each(function(){var el=$(this),start=$(this).data("resizable-alsoresize"),style={},css=c&&c.length?c:['width','height','top','left'];$.each(css||['width','height','top','left'],function(i,prop){var sum=(start[prop]||0)+(delta[prop]||0);if(sum&&sum>=0) +style[prop]=sum||null;});if(/relative/.test(el.css('position'))&&$.browser.opera){self._revertToRelativePosition=true;el.css({position:'absolute',top:'auto',left:'auto'});} +el.css(style);});};if(typeof(o.alsoResize)=='object'&&!o.alsoResize.nodeType){$.each(o.alsoResize,function(exp,c){_alsoResize(exp,c);});}else{_alsoResize(o.alsoResize);}},stop:function(event,ui){var self=$(this).data("resizable");if(self._revertToRelativePosition&&$.browser.opera){self._revertToRelativePosition=false;el.css({position:'relative'});} +$(this).removeData("resizable-alsoresize-start");}});$.ui.plugin.add("resizable","animate",{stop:function(event,ui){var self=$(this).data("resizable"),o=self.options;var pr=self._proportionallyResizeElements,ista=pr.length&&(/textarea/i).test(pr[0].nodeName),soffseth=ista&&$.ui.hasScroll(pr[0],'left')?0:self.sizeDiff.height,soffsetw=ista?0:self.sizeDiff.width;var style={width:(self.size.width-soffsetw),height:(self.size.height-soffseth)},left=(parseInt(self.element.css('left'),10)+(self.position.left-self.originalPosition.left))||null,top=(parseInt(self.element.css('top'),10)+(self.position.top-self.originalPosition.top))||null;self.element.animate($.extend(style,top&&left?{top:top,left:left}:{}),{duration:o.animateDuration,easing:o.animateEasing,step:function(){var data={width:parseInt(self.element.css('width'),10),height:parseInt(self.element.css('height'),10),top:parseInt(self.element.css('top'),10),left:parseInt(self.element.css('left'),10)};if(pr&&pr.length)$(pr[0]).css({width:data.width,height:data.height});self._updateCache(data);self._propagate("resize",event);}});}});$.ui.plugin.add("resizable","containment",{start:function(event,ui){var self=$(this).data("resizable"),o=self.options,el=self.element;var oc=o.containment,ce=(oc instanceof $)?oc.get(0):(/parent/.test(oc))?el.parent().get(0):oc;if(!ce)return;self.containerElement=$(ce);if(/document/.test(oc)||oc==document){self.containerOffset={left:0,top:0};self.containerPosition={left:0,top:0};self.parentData={element:$(document),left:0,top:0,width:$(document).width(),height:$(document).height()||document.body.parentNode.scrollHeight};} +else{var element=$(ce),p=[];$(["Top","Right","Left","Bottom"]).each(function(i,name){p[i]=num(element.css("padding"+name));});self.containerOffset=element.offset();self.containerPosition=element.position();self.containerSize={height:(element.innerHeight()-p[3]),width:(element.innerWidth()-p[1])};var co=self.containerOffset,ch=self.containerSize.height,cw=self.containerSize.width,width=($.ui.hasScroll(ce,"left")?ce.scrollWidth:cw),height=($.ui.hasScroll(ce)?ce.scrollHeight:ch);self.parentData={element:ce,left:co.left,top:co.top,width:width,height:height};}},resize:function(event,ui){var self=$(this).data("resizable"),o=self.options,ps=self.containerSize,co=self.containerOffset,cs=self.size,cp=self.position,pRatio=self._aspectRatio||event.shiftKey,cop={top:0,left:0},ce=self.containerElement;if(ce[0]!=document&&(/static/).test(ce.css('position')))cop=co;if(cp.left<(self._helper?co.left:0)){self.size.width=self.size.width+(self._helper?(self.position.left-co.left):(self.position.left-cop.left));if(pRatio)self.size.height=self.size.width/o.aspectRatio;self.position.left=o.helper?co.left:0;} +if(cp.top<(self._helper?co.top:0)){self.size.height=self.size.height+(self._helper?(self.position.top-co.top):self.position.top);if(pRatio)self.size.width=self.size.height*o.aspectRatio;self.position.top=self._helper?co.top:0;} +self.offset.left=self.parentData.left+self.position.left;self.offset.top=self.parentData.top+self.position.top;var woset=Math.abs((self._helper?self.offset.left-cop.left:(self.offset.left-cop.left))+self.sizeDiff.width),hoset=Math.abs((self._helper?self.offset.top-cop.top:(self.offset.top-co.top))+self.sizeDiff.height);var isParent=self.containerElement.get(0)==self.element.parent().get(0),isOffsetRelative=/relative|absolute/.test(self.containerElement.css('position'));if(isParent&&isOffsetRelative)woset-=self.parentData.left;if(woset+self.size.width>=self.parentData.width){self.size.width=self.parentData.width-woset;if(pRatio)self.size.height=self.size.width/self.aspectRatio;} +if(hoset+self.size.height>=self.parentData.height){self.size.height=self.parentData.height-hoset;if(pRatio)self.size.width=self.size.height*self.aspectRatio;}},stop:function(event,ui){var self=$(this).data("resizable"),o=self.options,cp=self.position,co=self.containerOffset,cop=self.containerPosition,ce=self.containerElement;var helper=$(self.helper),ho=helper.offset(),w=helper.outerWidth()-self.sizeDiff.width,h=helper.outerHeight()-self.sizeDiff.height;if(self._helper&&!o.animate&&(/relative/).test(ce.css('position'))) +$(this).css({left:ho.left-cop.left-co.left,width:w,height:h});if(self._helper&&!o.animate&&(/static/).test(ce.css('position'))) +$(this).css({left:ho.left-cop.left-co.left,width:w,height:h});}});$.ui.plugin.add("resizable","ghost",{start:function(event,ui){var self=$(this).data("resizable"),o=self.options,cs=self.size;self.ghost=self.originalElement.clone();self.ghost.css({opacity:.25,display:'block',position:'relative',height:cs.height,width:cs.width,margin:0,left:0,top:0}).addClass('ui-resizable-ghost').addClass(typeof o.ghost=='string'?o.ghost:'');self.ghost.appendTo(self.helper);},resize:function(event,ui){var self=$(this).data("resizable"),o=self.options;if(self.ghost)self.ghost.css({position:'relative',height:self.size.height,width:self.size.width});},stop:function(event,ui){var self=$(this).data("resizable"),o=self.options;if(self.ghost&&self.helper)self.helper.get(0).removeChild(self.ghost.get(0));}});$.ui.plugin.add("resizable","grid",{resize:function(event,ui){var self=$(this).data("resizable"),o=self.options,cs=self.size,os=self.originalSize,op=self.originalPosition,a=self.axis,ratio=o._aspectRatio||event.shiftKey;o.grid=typeof o.grid=="number"?[o.grid,o.grid]:o.grid;var ox=Math.round((cs.width-os.width)/(o.grid[0]||1))*(o.grid[0]||1),oy=Math.round((cs.height-os.height)/(o.grid[1]||1))*(o.grid[1]||1);if(/^(se|s|e)$/.test(a)){self.size.width=os.width+ox;self.size.height=os.height+oy;} +else if(/^(ne)$/.test(a)){self.size.width=os.width+ox;self.size.height=os.height+oy;self.position.top=op.top-oy;} +else if(/^(sw)$/.test(a)){self.size.width=os.width+ox;self.size.height=os.height+oy;self.position.left=op.left-ox;} +else{self.size.width=os.width+ox;self.size.height=os.height+oy;self.position.top=op.top-oy;self.position.left=op.left-ox;}}});var num=function(v){return parseInt(v,10)||0;};var isNumber=function(value){return!isNaN(parseInt(value,10));};})(jQuery);(function($){$.widget("ui.selectable",$.extend({},$.ui.mouse,{_init:function(){var self=this;this.element.addClass("ui-selectable");this.dragged=false;var selectees;this.refresh=function(){selectees=$(self.options.filter,self.element[0]);selectees.each(function(){var $this=$(this);var pos=$this.offset();$.data(this,"selectable-item",{element:this,$element:$this,left:pos.left,top:pos.top,right:pos.left+$this.outerWidth(),bottom:pos.top+$this.outerHeight(),startselected:false,selected:$this.hasClass('ui-selected'),selecting:$this.hasClass('ui-selecting'),unselecting:$this.hasClass('ui-unselecting')});});};this.refresh();this.selectees=selectees.addClass("ui-selectee");this._mouseInit();this.helper=$(document.createElement('div')).css({border:'1px dotted black'}).addClass("ui-selectable-helper");this.element.bind("mousedown.selectable",function(event){if(event.pageX>self.element[0].scrollWidth+self.element.offset().left){return;} +var selectee=self._targetIsSelectable(event.target);if(!selectee){return;} +var test=$(".ui-selected").length;if(!event.ctrlKey&&$(".ui-selected").length>1&&$(selectee).hasClass("ui-selected")){return(self._listenForMouseUp=1);} +if(!event.ctrlKey){$(".ui-selected").each(function(){self._removeSelection(this,event);});} +self._toggleSelection(selectee,event);event.preventDefault();}).bind("mouseup.selectable",function(event){if(self._listenForMouseUp){self._listenForMouseUp=0;var selectee=self._targetIsSelectable(event.target);if(!selectee){return;} +self._addSelection(selectee,event);event.preventDefault();}})},_addSelection:function(selectee,event){$(selectee).addClass("ui-selecting");this._trigger("selecting",event,{selecting:selectee});$(selectee).removeClass('ui-selecting').addClass('ui-selected');this._trigger("selected",event,{selected:selectee});},_removeSelection:function(selected,event){$(selected).removeClass('ui-selected').addClass('ui-unselecting');this._trigger("unselecting",event,{unselecting:selected});$(selected).removeClass('ui-unselecting');this._trigger("unselected",event,{unselected:selected});},_toggleSelection:function(selectee,event){if($(selectee).hasClass("ui-selected")){this._removeSelection(selectee,event);}else{this._addSelection(selectee,event);}},_targetIsSelectable:function(target){var found=$(target).parents().andSelf().filter(".ui-selectee");return found.length&&found;},destroy:function(){this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();},_mouseStart:function(event){var self=this;if(event.pageX>this.element[0].scrollWidth+this.element.offset().left){this.opos=null;return;} +this.opos=[event.pageX,event.pageY];if(this.options.disabled) +return;var options=this.options,appendTo=$(options.appendTo),parentOffset=appendTo.css('position')=='static'?appendTo.offsetParent().offset():appendTo.offset();this.selectees=$(options.filter,this.element[0]);this._trigger("start",event);appendTo.append(this.helper);this.helper.css({"z-index":100,"position":"absolute","left":event.clientX-parentOffset.left,"top":event.clientY-parentOffset.top,"width":0,"height":0});if(options.autoRefresh){this.refresh();} +this.selectees.filter('.ui-selected').each(function(){var selectee=$.data(this,"selectable-item");selectee.startselected=true;if(!event.metaKey){selectee.$element.removeClass('ui-selected');selectee.selected=false;selectee.$element.addClass('ui-unselecting');selectee.unselecting=true;self._trigger("unselecting",event,{unselecting:selectee.element});}});$(event.target).parents().andSelf().each(function(){var selectee=$.data(this,"selectable-item");if(selectee){selectee.$element.removeClass("ui-unselecting").addClass('ui-selecting');selectee.unselecting=false;selectee.selecting=true;selectee.selected=true;self._trigger("selecting",event,{selecting:selectee.element});return false;}});},_mouseDrag:function(event){var self=this;if(!this.opos){return;} +this.dragged=true;if(this.options.disabled) +return;var options=this.options;var x1=this.opos[0],y1=this.opos[1],x2=event.pageX,y2=event.pageY,appendTo=$(options.appendTo),parentOffset=appendTo.css('position')=='static'?appendTo.offsetParent().offset():appendTo.offset();if(x1>x2){var tmp=x2;x2=x1;x1=tmp;} +if(y1>y2){var tmp=y2;y2=y1;y1=tmp;} +this.helper.css({left:x1-parentOffset.left,top:y1-parentOffset.top,width:x2-x1,height:y2-y1});this.selectees.each(function(){var selectee=$.data(this,"selectable-item");if(!selectee||selectee.element==self.element[0]) +return;var hit=false;if(options.tolerance=='touch'){hit=(!(selectee.left>x2||selectee.righty2||selectee.bottomx1&&selectee.righty1&&selectee.bottom=0;i--) +this.items[i].item.removeData("sortable-item");},_mouseCapture:function(event,overrideHandle){if(this.reverting){return false;} +if(this.options.disabled||this.options.type=='static')return false;this._refreshItems(event);var currentItem=null,self=this,nodes=$(event.target).parents().each(function(){if($.data(this,'sortable-item')==self){currentItem=$(this);return false;}});if($.data(event.target,'sortable-item')==self)currentItem=$(event.target);if(!currentItem)return false;if(this.options.handle&&!overrideHandle){var validHandle=false;$(this.options.handle,currentItem).find("*").andSelf().each(function(){if(this==event.target)validHandle=true;});if(!validHandle)return false;} +this.currentItem=currentItem;this._removeCurrentsFromItems();return true;},_mouseStart:function(event,overrideHandle,noActivation){var o=this.options,self=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(event);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");$.extend(this.offset,{click:{left:event.pageX-this.offset.left,top:event.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(event);this.originalPageX=event.pageX;this.originalPageY=event.pageY;if(o.cursorAt) +this._adjustOffsetFromHelper(o.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!=this.currentItem[0]){this.currentItem.hide();} +this._createPlaceholder();if(o.containment) +this._setContainment();if(o.cursor){if($('body').css("cursor"))this._storedCursor=$('body').css("cursor");$('body').css("cursor",o.cursor);} +if(o.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",o.opacity);} +if(o.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",o.zIndex);} +if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!='HTML') +this.overflowOffset=this.scrollParent.offset();this._trigger("start",event,this._uiHash());if(!this._preserveHelperProportions) +this._cacheHelperProportions();if(!noActivation){for(var i=this.containers.length-1;i>=0;i--){this.containers[i]._trigger("activate",event,self._uiHash(this));}} +if($.ui.ddmanager) +$.ui.ddmanager.current=this;if($.ui.ddmanager&&!o.dropBehaviour) +$.ui.ddmanager.prepareOffsets(this,event);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(event);return true;},_mouseDrag:function(event){this.position=this._generatePosition(event);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs;} +if(this.options.scroll){var o=this.options,scrolled=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!='HTML'){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-event.pageY=0;i--){var item=this.items[i],itemElement=item.item[0],intersection=this._intersectsWithPointer(item);if(!intersection)continue;if(itemElement!=this.currentItem[0]&&this.placeholder[intersection==1?"next":"prev"]()[0]!=itemElement&&!$.ui.contains(this.placeholder[0],itemElement)&&(this.options.type=='semi-dynamic'?!$.ui.contains(this.element[0],itemElement):true)){this.direction=intersection==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(item)){this._rearrange(event,item);}else{break;} +this._trigger("change",event,this._uiHash());break;}} +this._contactContainers(event);if($.ui.ddmanager)$.ui.ddmanager.drag(this,event);this._trigger('sort',event,this._uiHash());this.lastPositionAbs=this.positionAbs;return false;},_mouseStop:function(event,noPropagation){if(!event)return;if($.ui.ddmanager&&!this.options.dropBehaviour) +$.ui.ddmanager.drop(this,event);if(this.options.revert){var self=this;var cur=self.placeholder.offset();self.reverting=true;$(this.helper).animate({left:cur.left-this.offset.parent.left-self.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:cur.top-this.offset.parent.top-self.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){self._clear(event);});}else{this._clear(event,noPropagation);} +return false;},cancel:function(){var self=this;if(this.dragging){this._mouseUp();if(this.options.helper=="original") +this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");else +this.currentItem.show();for(var i=this.containers.length-1;i>=0;i--){this.containers[i]._trigger("deactivate",null,self._uiHash(this));if(this.containers[i].containerCache.over){this.containers[i]._trigger("out",null,self._uiHash(this));this.containers[i].containerCache.over=0;}}} +if(this.placeholder[0].parentNode)this.placeholder[0].parentNode.removeChild(this.placeholder[0]);if(this.options.helper!="original"&&this.helper&&this.helper[0].parentNode)this.helper.remove();$.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){$(this.domPosition.prev).after(this.currentItem);}else{$(this.domPosition.parent).prepend(this.currentItem);} +return true;},serialize:function(o){var items=this._getItemsAsjQuery(o&&o.connected);var str=[];o=o||{};$(items).each(function(){var res=($(o.item||this).attr(o.attribute||'id')||'').match(o.expression||(/(.+)[-=_](.+)/));if(res)str.push((o.key||res[1]+'[]')+'='+(o.key&&o.expression?res[1]:res[2]));});return str.join('&');},toArray:function(o){var items=this._getItemsAsjQuery(o&&o.connected);var ret=[];o=o||{};items.each(function(){ret.push($(o.item||this).attr(o.attribute||'id')||'');});return ret;},_intersectsWith:function(item){var x1=this.positionAbs.left,x2=x1+this.helperProportions.width,y1=this.positionAbs.top,y2=y1+this.helperProportions.height;var l=item.left,r=l+item.width,t=item.top,b=t+item.height;var dyClick=this.offset.click.top,dxClick=this.offset.click.left;var isOverElement=(y1+dyClick)>t&&(y1+dyClick)l&&(x1+dxClick)item[this.floating?'width':'height'])){return isOverElement;}else{return(l0?"down":"up");},_getDragHorizontalDirection:function(){var delta=this.positionAbs.left-this.lastPositionAbs.left;return delta!=0&&(delta>0?"right":"left");},refresh:function(event){this._refreshItems(event);this.refreshPositions();},_connectWith:function(){var options=this.options;return options.connectWith.constructor==String?[options.connectWith]:options.connectWith;},_getItemsAsjQuery:function(connected){var self=this;var items=[];var queries=[];var connectWith=this._connectWith();if(connectWith&&connected){for(var i=connectWith.length-1;i>=0;i--){var cur=$(connectWith[i]);for(var j=cur.length-1;j>=0;j--){var inst=$.data(cur[j],'sortable');if(inst&&inst!=this&&!inst.options.disabled){queries.push([$.isFunction(inst.options.items)?inst.options.items.call(inst.element):$(inst.options.items,inst.element).not(".ui-sortable-helper"),inst]);}};};} +queries.push([$.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):$(this.options.items,this.element).not(".ui-sortable-helper"),this]);for(var i=queries.length-1;i>=0;i--){queries[i][0].each(function(){items.push(this);});};return $(items);},_removeCurrentsFromItems:function(){var list=this.currentItem.find(":data(sortable-item)");for(var i=0;i=0;i--){var cur=$(connectWith[i]);for(var j=cur.length-1;j>=0;j--){var inst=$.data(cur[j],'sortable');if(inst&&inst!=this&&!inst.options.disabled){queries.push([$.isFunction(inst.options.items)?inst.options.items.call(inst.element[0],event,{item:this.currentItem}):$(inst.options.items,inst.element),inst]);this.containers.push(inst);}};};} +for(var i=queries.length-1;i>=0;i--){var targetData=queries[i][1];var _queries=queries[i][0];for(var j=0,queriesLength=_queries.length;j=0;i--){var item=this.items[i];if(item.instance!=this.currentContainer&&this.currentContainer&&item.item[0]!=this.currentItem[0]) +continue;var t=this.options.toleranceElement?$(this.options.toleranceElement,item.item):item.item;if(!fast){item.width=t.outerWidth();item.height=t.outerHeight();} +var p=t.offset();item.left=p.left;item.top=p.top;};if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this);}else{for(var i=this.containers.length-1;i>=0;i--){var p=this.containers[i].element.offset();this.containers[i].containerCache.left=p.left;this.containers[i].containerCache.top=p.top;this.containers[i].containerCache.width=this.containers[i].element.outerWidth();this.containers[i].containerCache.height=this.containers[i].element.outerHeight();};}},_createPlaceholder:function(that){var self=that||this,o=self.options;if(!o.placeholder||o.placeholder.constructor==String){var className=o.placeholder;o.placeholder={element:function(){var el=$(document.createElement(self.currentItem[0].nodeName)).addClass(className||self.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!className) +el.style.visibility="hidden";return el;},update:function(container,p){if(className&&!o.forcePlaceholderSize)return;if(!p.height()){p.height(self.currentItem.innerHeight()-parseInt(self.currentItem.css('paddingTop')||0,10)-parseInt(self.currentItem.css('paddingBottom')||0,10));};if(!p.width()){p.width(self.currentItem.innerWidth()-parseInt(self.currentItem.css('paddingLeft')||0,10)-parseInt(self.currentItem.css('paddingRight')||0,10));};}};} +self.placeholder=$(o.placeholder.element.call(self.element,self.currentItem));self.currentItem.after(self.placeholder);o.placeholder.update(self,self.placeholder);},_contactContainers:function(event){for(var i=this.containers.length-1;i>=0;i--){if(this._intersectsWith(this.containers[i].containerCache)){if(!this.containers[i].containerCache.over){if(this.currentContainer!=this.containers[i]){var dist=10000;var itemWithLeastDistance=null;var base=this.positionAbs[this.containers[i].floating?'left':'top'];for(var j=this.items.length-1;j>=0;j--){if(!$.ui.contains(this.containers[i].element[0],this.items[j].item[0]))continue;var cur=this.items[j][this.containers[i].floating?'left':'top'];if(Math.abs(cur-base)this.containment[2])pageX=this.containment[2]+this.offset.click.left;if(event.pageY-this.offset.click.top>this.containment[3])pageY=this.containment[3]+this.offset.click.top;} +if(o.grid){var top=this.originalPageY+Math.round((pageY-this.originalPageY)/o.grid[1])*o.grid[1];pageY=this.containment?(!(top-this.offset.click.topthis.containment[3])?top:(!(top-this.offset.click.topthis.containment[2])?left:(!(left-this.offset.click.left=0;i--){if($.ui.contains(this.containers[i].element[0],this.currentItem[0])&&!noPropagation){delayedTriggers.push((function(c){return function(event){c._trigger("receive",event,this._uiHash(this));};}).call(this,this.containers[i]));delayedTriggers.push((function(c){return function(event){c._trigger("update",event,this._uiHash(this));};}).call(this,this.containers[i]));}};};for(var i=this.containers.length-1;i>=0;i--){if(!noPropagation)delayedTriggers.push((function(c){return function(event){c._trigger("deactivate",event,this._uiHash(this));};}).call(this,this.containers[i]));if(this.containers[i].containerCache.over){delayedTriggers.push((function(c){return function(event){c._trigger("out",event,this._uiHash(this));};}).call(this,this.containers[i]));this.containers[i].containerCache.over=0;}} +if(this._storedCursor)$('body').css("cursor",this._storedCursor);if(this._storedOpacity)this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=='auto'?'':this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!noPropagation){this._trigger("beforeStop",event,this._uiHash());for(var i=0;i *',opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000}});})(jQuery);(function($){$.widget("ui.accordion",{_init:function(){var o=this.options,self=this;this.running=0;if(o.collapsible==$.ui.accordion.defaults.collapsible&&o.alwaysOpen!=$.ui.accordion.defaults.alwaysOpen){o.collapsible=!o.alwaysOpen;} +if(o.navigation){var current=this.element.find("a").filter(o.navigationFilter);if(current.length){if(current.filter(o.header).length){this.active=current;}else{this.active=current.parent().parent().prev();current.addClass("ui-accordion-content-active");}}} +this.element.addClass("ui-accordion ui-widget ui-helper-reset");if(this.element[0].nodeName=="UL"){this.element.children("li").addClass("ui-accordion-li-fix");} +this.headers=this.element.find(o.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){$(this).addClass('ui-state-hover');}).bind("mouseleave.accordion",function(){$(this).removeClass('ui-state-hover');}).bind("focus.accordion",function(){$(this).addClass('ui-state-focus');}).bind("blur.accordion",function(){$(this).removeClass('ui-state-focus');});this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");this.active=this._findActive(this.active||o.active).toggleClass("ui-state-default").toggleClass("ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");this.active.next().addClass('ui-accordion-content-active');$("").addClass("ui-icon "+o.icons.header).prependTo(this.headers);this.active.find(".ui-icon").toggleClass(o.icons.header).toggleClass(o.icons.headerSelected);if($.browser.msie){this.element.find('a').css('zoom','1');} +this.resize();this.element.attr('role','tablist');this.headers.attr('role','tab').bind('keydown',function(event){return self._keydown(event);}).next().attr('role','tabpanel');this.headers.not(this.active||"").attr('aria-expanded','false').attr("tabIndex","-1").next().hide();if(!this.active.length){this.headers.eq(0).attr('tabIndex','0');}else{this.active.attr('aria-expanded','true').attr('tabIndex','0');} +if(!$.browser.safari) +this.headers.find('a').attr('tabIndex','-1');if(o.event){this.headers.bind((o.event)+".accordion",function(event){return self._clickHandler.call(self,event,this);});}},destroy:function(){var o=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role").unbind('.accordion').removeData('accordion');this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabindex");this.headers.find("a").removeAttr("tabindex");this.headers.children(".ui-icon").remove();var contents=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active");if(o.autoHeight||o.fillHeight){contents.css("height","");}},_setData:function(key,value){if(key=='alwaysOpen'){key='collapsible';value=!value;} +$.widget.prototype._setData.apply(this,arguments);},_keydown:function(event){var o=this.options,keyCode=$.ui.keyCode;if(o.disabled||event.altKey||event.ctrlKey) +return;var length=this.headers.length;var currentIndex=this.headers.index(event.target);var toFocus=false;switch(event.keyCode){case keyCode.RIGHT:case keyCode.DOWN:toFocus=this.headers[(currentIndex+1)%length];break;case keyCode.LEFT:case keyCode.UP:toFocus=this.headers[(currentIndex-1+length)%length];break;case keyCode.SPACE:case keyCode.ENTER:return this._clickHandler({target:event.target},event.target);} +if(toFocus){$(event.target).attr('tabIndex','-1');$(toFocus).attr('tabIndex','0');toFocus.focus();return false;} +return true;},resize:function(){var o=this.options,maxHeight;if(o.fillSpace){if($.browser.msie){var defOverflow=this.element.parent().css('overflow');this.element.parent().css('overflow','hidden');} +maxHeight=this.element.parent().height();if($.browser.msie){this.element.parent().css('overflow',defOverflow);} +this.headers.each(function(){maxHeight-=$(this).outerHeight();});var maxPadding=0;this.headers.next().each(function(){maxPadding=Math.max(maxPadding,$(this).innerHeight()-$(this).height());}).height(Math.max(0,maxHeight-maxPadding)).css('overflow','auto');}else if(o.autoHeight){maxHeight=0;this.headers.next().each(function(){maxHeight=Math.max(maxHeight,$(this).outerHeight());}).height(maxHeight);}},activate:function(index){var active=this._findActive(index)[0];this._clickHandler({target:active},active);},_findActive:function(selector){return selector?typeof selector=="number"?this.headers.filter(":eq("+selector+")"):this.headers.not(this.headers.not(selector)):selector===false?$([]):this.headers.filter(":eq(0)");},_clickHandler:function(event,target){var o=this.options;if(o.disabled)return false;if(!event.target&&o.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(o.icons.headerSelected).addClass(o.icons.header);this.active.next().addClass('ui-accordion-content-active');var toHide=this.active.next(),data={options:o,newHeader:$([]),oldHeader:o.active,newContent:$([]),oldContent:toHide},toShow=(this.active=$([]));this._toggle(toShow,toHide,data);return false;} +var clicked=$(event.currentTarget||target);var clickedIsActive=clicked[0]==this.active[0];if(this.running||(!o.collapsible&&clickedIsActive)){return false;} +this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(o.icons.headerSelected).addClass(o.icons.header);this.active.next().addClass('ui-accordion-content-active');if(!clickedIsActive){clicked.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").find(".ui-icon").removeClass(o.icons.header).addClass(o.icons.headerSelected);clicked.next().addClass('ui-accordion-content-active');} +var toShow=clicked.next(),toHide=this.active.next(),data={options:o,newHeader:clickedIsActive&&o.collapsible?$([]):clicked,oldHeader:this.active,newContent:clickedIsActive&&o.collapsible?$([]):toShow.find('> *'),oldContent:toHide.find('> *')},down=this.headers.index(this.active[0])>this.headers.index(clicked[0]);this.active=clickedIsActive?$([]):clicked;this._toggle(toShow,toHide,data,clickedIsActive,down);return false;},_toggle:function(toShow,toHide,data,clickedIsActive,down){var o=this.options,self=this;this.toShow=toShow;this.toHide=toHide;this.data=data;var complete=function(){if(!self)return;return self._completed.apply(self,arguments);};this._trigger("changestart",null,this.data);this.running=toHide.size()===0?toShow.size():toHide.size();if(o.animated){var animOptions={};if(o.collapsible&&clickedIsActive){animOptions={toShow:$([]),toHide:toHide,complete:complete,down:down,autoHeight:o.autoHeight||o.fillSpace};}else{animOptions={toShow:toShow,toHide:toHide,complete:complete,down:down,autoHeight:o.autoHeight||o.fillSpace};} +if(!o.proxied){o.proxied=o.animated;} +if(!o.proxiedDuration){o.proxiedDuration=o.duration;} +o.animated=$.isFunction(o.proxied)?o.proxied(animOptions):o.proxied;o.duration=$.isFunction(o.proxiedDuration)?o.proxiedDuration(animOptions):o.proxiedDuration;var animations=$.ui.accordion.animations,duration=o.duration,easing=o.animated;if(!animations[easing]){animations[easing]=function(options){this.slide(options,{easing:easing,duration:duration||700});};} +animations[easing](animOptions);}else{if(o.collapsible&&clickedIsActive){toShow.toggle();}else{toHide.hide();toShow.show();} +complete(true);} +toHide.prev().attr('aria-expanded','false').attr("tabIndex","-1").blur();toShow.prev().attr('aria-expanded','true').attr("tabIndex","0").focus();},_completed:function(cancel){var o=this.options;this.running=cancel?0:--this.running;if(this.running)return;if(o.clearStyle){this.toShow.add(this.toHide).css({height:"",overflow:""});} +this._trigger('change',null,this.data);}});$.extend($.ui.accordion,{version:"1.7.2",defaults:{active:null,alwaysOpen:true,animated:'slide',autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()==location.href.toLowerCase();}},animations:{slide:function(options,additions){options=$.extend({easing:"swing",duration:300},options,additions);if(!options.toHide.size()){options.toShow.animate({height:"show"},options);return;} +if(!options.toShow.size()){options.toHide.animate({height:"hide"},options);return;} +var overflow=options.toShow.css('overflow'),percentDone,showProps={},hideProps={},fxAttrs=["height","paddingTop","paddingBottom"],originalWidth;var s=options.toShow;originalWidth=s[0].style.width;s.width(parseInt(s.parent().width(),10)-parseInt(s.css("paddingLeft"),10)-parseInt(s.css("paddingRight"),10)-(parseInt(s.css("borderLeftWidth"),10)||0)-(parseInt(s.css("borderRightWidth"),10)||0));$.each(fxAttrs,function(i,prop){hideProps[prop]='hide';var parts=(''+$.css(options.toShow[0],prop)).match(/^([\d+-.]+)(.*)$/);showProps[prop]={value:parts[1],unit:parts[2]||'px'};});options.toShow.css({height:0,overflow:'hidden'}).show();options.toHide.filter(":hidden").each(options.complete).end().filter(":visible").animate(hideProps,{step:function(now,settings){if(settings.prop=='height'){percentDone=(settings.now-settings.start)/(settings.end-settings.start);} +options.toShow[0].style[settings.prop]=(percentDone*showProps[settings.prop].value)+showProps[settings.prop].unit;},duration:options.duration,easing:options.easing,complete:function(){if(!options.autoHeight){options.toShow.css("height","");} +options.toShow.css("width",originalWidth);options.toShow.css({overflow:overflow});options.complete();}});},bounceslide:function(options){this.slide(options,{easing:options.down?"easeOutBounce":"swing",duration:options.down?1000:200});},easeslide:function(options){this.slide(options,{easing:"easeinout",duration:700});}}});})(jQuery);(function($){var setDataSwitch={dragStart:"start.draggable",drag:"drag.draggable",dragStop:"stop.draggable",maxHeight:"maxHeight.resizable",minHeight:"minHeight.resizable",maxWidth:"maxWidth.resizable",minWidth:"minWidth.resizable",resizeStart:"start.resizable",resize:"drag.resizable",resizeStop:"stop.resizable"},uiDialogClasses='ui-dialog '+'ui-widget '+'ui-widget-content '+'ui-corner-all ';$.widget("ui.dialog",{_init:function(){this.originalTitle=this.element.attr('title');var self=this,options=this.options,title=options.title||this.originalTitle||' ',titleId=$.ui.dialog.getTitleId(this.element),uiDialog=(this.uiDialog=$('
')).appendTo(document.body).hide().addClass(uiDialogClasses+options.dialogClass).css({position:'absolute',overflow:'hidden',zIndex:options.zIndex}).attr('tabIndex',-1).css('outline',0).keydown(function(event){(options.closeOnEscape&&event.keyCode&&event.keyCode==$.ui.keyCode.ESCAPE&&self.close(event));}).attr({role:'dialog','aria-labelledby':titleId}).mousedown(function(event){self.moveToTop(false,event);}),uiDialogContent=this.element.show().removeAttr('title').addClass('ui-dialog-content '+'ui-widget-content').appendTo(uiDialog),uiDialogTitlebar=(this.uiDialogTitlebar=$('
')).addClass('ui-dialog-titlebar '+'ui-widget-header '+'ui-corner-all '+'ui-helper-clearfix').prependTo(uiDialog),uiDialogTitlebarClose=$('').addClass('ui-dialog-titlebar-close '+'ui-corner-all').attr('role','button').hover(function(){uiDialogTitlebarClose.addClass('ui-state-hover');},function(){uiDialogTitlebarClose.removeClass('ui-state-hover');}).focus(function(){uiDialogTitlebarClose.addClass('ui-state-focus');}).blur(function(){uiDialogTitlebarClose.removeClass('ui-state-focus');}).mousedown(function(ev){ev.stopPropagation();}).click(function(event){self.close(event);return false;}).appendTo(uiDialogTitlebar),uiDialogTitlebarCloseText=(this.uiDialogTitlebarCloseText=$('')).addClass('ui-icon '+'ui-icon-closethick').text(options.closeText).appendTo(uiDialogTitlebarClose),uiDialogTitle=$('').addClass('ui-dialog-title').attr('id',titleId).html(title).prependTo(uiDialogTitlebar);uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();(options.draggable&&$.fn.draggable&&this._makeDraggable());(options.resizable&&$.fn.resizable&&this._makeResizable());this._createButtons(options.buttons);this._isOpen=false;(options.bgiframe&&$.fn.bgiframe&&uiDialog.bgiframe());(options.autoOpen&&this.open());},destroy:function(){(this.overlay&&this.overlay.destroy());this.uiDialog.hide();this.element.unbind('.dialog').removeData('dialog').removeClass('ui-dialog-content ui-widget-content').hide().appendTo('body');this.uiDialog.remove();(this.originalTitle&&this.element.attr('title',this.originalTitle));},close:function(event){var self=this;if(false===self._trigger('beforeclose',event)){return;} +(self.overlay&&self.overlay.destroy());self.uiDialog.unbind('keypress.ui-dialog');(self.options.hide?self.uiDialog.hide(self.options.hide,function(){self._trigger('close',event);}):self.uiDialog.hide()&&self._trigger('close',event));$.ui.dialog.overlay.resize();self._isOpen=false;if(self.options.modal){var maxZ=0;$('.ui-dialog').each(function(){if(this!=self.uiDialog[0]){maxZ=Math.max(maxZ,$(this).css('z-index'));}});$.ui.dialog.maxZ=maxZ;}},isOpen:function(){return this._isOpen;},moveToTop:function(force,event){if((this.options.modal&&!force)||(!this.options.stack&&!this.options.modal)){return this._trigger('focus',event);} +if(this.options.zIndex>$.ui.dialog.maxZ){$.ui.dialog.maxZ=this.options.zIndex;} +(this.overlay&&this.overlay.$el.css('z-index',$.ui.dialog.overlay.maxZ=++$.ui.dialog.maxZ));var saveScroll={scrollTop:this.element.attr('scrollTop'),scrollLeft:this.element.attr('scrollLeft')};this.uiDialog.css('z-index',++$.ui.dialog.maxZ);this.element.attr(saveScroll);this._trigger('focus',event);},open:function(){if(this._isOpen){return;} +var options=this.options,uiDialog=this.uiDialog;this.overlay=options.modal?new $.ui.dialog.overlay(this):null;(uiDialog.next().length&&uiDialog.appendTo('body'));this._size();this._position(options.position);uiDialog.show(options.show);this.moveToTop(true);(options.modal&&uiDialog.bind('keypress.ui-dialog',function(event){if(event.keyCode!=$.ui.keyCode.TAB){return;} +var tabbables=$(':tabbable',this),first=tabbables.filter(':first')[0],last=tabbables.filter(':last')[0];if(event.target==last&&!event.shiftKey){setTimeout(function(){first.focus();},1);}else if(event.target==first&&event.shiftKey){setTimeout(function(){last.focus();},1);}}));$([]).add(uiDialog.find('.ui-dialog-content :tabbable:first')).add(uiDialog.find('.ui-dialog-buttonpane :tabbable:first')).add(uiDialog).filter(':first').focus();this._trigger('open');this._isOpen=true;},_createButtons:function(buttons){var self=this,hasButtons=false,uiDialogButtonPane=$('
').addClass('ui-dialog-buttonpane '+'ui-widget-content '+'ui-helper-clearfix');this.uiDialog.find('.ui-dialog-buttonpane').remove();(typeof buttons=='object'&&buttons!==null&&$.each(buttons,function(){return!(hasButtons=true);}));if(hasButtons){$.each(buttons,function(name,fn){$('').addClass('ui-state-default '+'ui-corner-all').text(name).click(function(){fn.apply(self.element[0],arguments);}).hover(function(){$(this).addClass('ui-state-hover');},function(){$(this).removeClass('ui-state-hover');}).focus(function(){$(this).addClass('ui-state-focus');}).blur(function(){$(this).removeClass('ui-state-focus');}).appendTo(uiDialogButtonPane);});uiDialogButtonPane.appendTo(this.uiDialog);}},_makeDraggable:function(){var self=this,options=this.options,heightBeforeDrag;this.uiDialog.draggable({cancel:'.ui-dialog-content',handle:'.ui-dialog-titlebar',containment:'document',start:function(){heightBeforeDrag=options.height;$(this).height($(this).height()).addClass("ui-dialog-dragging");(options.dragStart&&options.dragStart.apply(self.element[0],arguments));},drag:function(){(options.drag&&options.drag.apply(self.element[0],arguments));},stop:function(){$(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag);(options.dragStop&&options.dragStop.apply(self.element[0],arguments));$.ui.dialog.overlay.resize();}});},_makeResizable:function(handles){handles=(handles===undefined?this.options.resizable:handles);var self=this,options=this.options,resizeHandles=typeof handles=='string'?handles:'n,e,s,w,se,sw,ne,nw';this.uiDialog.resizable({cancel:'.ui-dialog-content',alsoResize:this.element,maxWidth:options.maxWidth,maxHeight:options.maxHeight,minWidth:options.minWidth,minHeight:options.minHeight,start:function(){$(this).addClass("ui-dialog-resizing");(options.resizeStart&&options.resizeStart.apply(self.element[0],arguments));},resize:function(){(options.resize&&options.resize.apply(self.element[0],arguments));},handles:resizeHandles,stop:function(){$(this).removeClass("ui-dialog-resizing");options.height=$(this).height();options.width=$(this).width();(options.resizeStop&&options.resizeStop.apply(self.element[0],arguments));$.ui.dialog.overlay.resize();}}).find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se');},_position:function(pos){var wnd=$(window),doc=$(document),pTop=doc.scrollTop(),pLeft=doc.scrollLeft(),minTop=pTop;if($.inArray(pos,['center','top','right','bottom','left'])>=0){pos=[pos=='right'||pos=='left'?pos:'center',pos=='top'||pos=='bottom'?pos:'middle'];} +if(pos.constructor!=Array){pos=['center','middle'];} +if(pos[0].constructor==Number){pLeft+=pos[0];}else{switch(pos[0]){case'left':pLeft+=0;break;case'right':pLeft+=wnd.width()-this.uiDialog.outerWidth();break;default:case'center':pLeft+=(wnd.width()-this.uiDialog.outerWidth())/2;}} +if(pos[1].constructor==Number){pTop+=pos[1];}else{switch(pos[1]){case'top':pTop+=0;break;case'bottom':pTop+=wnd.height()-this.uiDialog.outerHeight();break;default:case'middle':pTop+=(wnd.height()-this.uiDialog.outerHeight())/2;}} +pTop=Math.max(pTop,minTop);this.uiDialog.css({top:pTop,left:pLeft});},_setData:function(key,value){(setDataSwitch[key]&&this.uiDialog.data(setDataSwitch[key],value));switch(key){case"buttons":this._createButtons(value);break;case"closeText":this.uiDialogTitlebarCloseText.text(value);break;case"dialogClass":this.uiDialog.removeClass(this.options.dialogClass).addClass(uiDialogClasses+value);break;case"draggable":(value?this._makeDraggable():this.uiDialog.draggable('destroy'));break;case"height":this.uiDialog.height(value);break;case"position":this._position(value);break;case"resizable":var uiDialog=this.uiDialog,isResizable=this.uiDialog.is(':data(resizable)');(isResizable&&!value&&uiDialog.resizable('destroy'));(isResizable&&typeof value=='string'&&uiDialog.resizable('option','handles',value));(isResizable||this._makeResizable(value));break;case"title":$(".ui-dialog-title",this.uiDialogTitlebar).html(value||' ');break;case"width":this.uiDialog.width(value);break;} +$.widget.prototype._setData.apply(this,arguments);},_size:function(){var options=this.options;this.element.css({height:0,minHeight:0,width:'auto'});var nonContentHeight=this.uiDialog.css({height:'auto',width:options.width}).height();this.element.css({minHeight:Math.max(options.minHeight-nonContentHeight,0),height:options.height=='auto'?'auto':Math.max(options.height-nonContentHeight,0)});}});$.extend($.ui.dialog,{version:"1.7.2",defaults:{autoOpen:true,bgiframe:false,buttons:{},closeOnEscape:true,closeText:'close',dialogClass:'',draggable:true,hide:null,height:'auto',maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:'center',resizable:true,show:null,stack:true,title:'',width:300,zIndex:1000},getter:'isOpen',uuid:0,maxZ:0,getTitleId:function($el){return'ui-dialog-title-'+($el.attr('id')||++this.uuid);},overlay:function(dialog){this.$el=$.ui.dialog.overlay.create(dialog);}});$.extend($.ui.dialog.overlay,{instances:[],maxZ:0,events:$.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','),function(event){return event+'.dialog-overlay';}).join(' '),create:function(dialog){if(this.instances.length===0){setTimeout(function(){if($.ui.dialog.overlay.instances.length){$(document).bind($.ui.dialog.overlay.events,function(event){var dialogZ=$(event.target).parents('.ui-dialog').css('zIndex')||0;return(dialogZ>$.ui.dialog.overlay.maxZ);});}},1);$(document).bind('keydown.dialog-overlay',function(event){(dialog.options.closeOnEscape&&event.keyCode&&event.keyCode==$.ui.keyCode.ESCAPE&&dialog.close(event));});$(window).bind('resize.dialog-overlay',$.ui.dialog.overlay.resize);} +var $el=$('
').appendTo(document.body).addClass('ui-widget-overlay').css({width:this.width(),height:this.height()});(dialog.options.bgiframe&&$.fn.bgiframe&&$el.bgiframe());this.instances.push($el);return $el;},destroy:function($el){this.instances.splice($.inArray(this.instances,$el),1);if(this.instances.length===0){$([document,window]).unbind('.dialog-overlay');} +$el.remove();var maxZ=0;$.each(this.instances,function(){maxZ=Math.max(maxZ,this.css('z-index'));});this.maxZ=maxZ;},height:function(){if($.browser.msie&&$.browser.version<7){var scrollHeight=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);var offsetHeight=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);if(scrollHeight=0&&this.anchors[o.selected])||o.selected<0)?o.selected:0;o.disabled=$.unique(o.disabled.concat($.map(this.lis.filter('.ui-state-disabled'),function(n,i){return self.lis.index(n);}))).sort();if($.inArray(o.selected,o.disabled)!=-1){o.disabled.splice($.inArray(o.selected,o.disabled),1);} +this.panels.addClass('ui-tabs-hide');this.lis.removeClass('ui-tabs-selected ui-state-active');if(o.selected>=0&&this.anchors.length){this.panels.eq(o.selected).removeClass('ui-tabs-hide');this.lis.eq(o.selected).addClass('ui-tabs-selected ui-state-active');self.element.queue("tabs",function(){self._trigger('show',null,self._ui(self.anchors[o.selected],self.panels[o.selected]));});this.load(o.selected);} +$(window).bind('unload',function(){self.lis.add(self.anchors).unbind('.tabs');self.lis=self.anchors=self.panels=null;});} +else{o.selected=this.lis.index(this.lis.filter('.ui-tabs-selected'));} +this.element[o.collapsible?'addClass':'removeClass']('ui-tabs-collapsible');if(o.cookie){this._cookie(o.selected,o.cookie);} +for(var i=0,li;(li=this.lis[i]);i++){$(li)[$.inArray(i,o.disabled)!=-1&&!$(li).hasClass('ui-tabs-selected')?'addClass':'removeClass']('ui-state-disabled');} +if(o.cache===false){this.anchors.removeData('cache.tabs');} +this.lis.add(this.anchors).unbind('.tabs');if(o.event!='mouseover'){var addState=function(state,el){if(el.is(':not(.ui-state-disabled)')){el.addClass('ui-state-'+state);}};var removeState=function(state,el){el.removeClass('ui-state-'+state);};this.lis.bind('mouseover.tabs',function(){addState('hover',$(this));});this.lis.bind('mouseout.tabs',function(){removeState('hover',$(this));});this.anchors.bind('focus.tabs',function(){addState('focus',$(this).closest('li'));});this.anchors.bind('blur.tabs',function(){removeState('focus',$(this).closest('li'));});} +var hideFx,showFx;if(o.fx){if($.isArray(o.fx)){hideFx=o.fx[0];showFx=o.fx[1];} +else{hideFx=showFx=o.fx;}} +function resetStyle($el,fx){$el.css({display:''});if($.browser.msie&&fx.opacity){$el[0].style.removeAttribute('filter');}} +var showTab=showFx?function(clicked,$show){$(clicked).closest('li').removeClass('ui-state-default').addClass('ui-tabs-selected ui-state-active');$show.hide().removeClass('ui-tabs-hide').animate(showFx,showFx.duration||'normal',function(){resetStyle($show,showFx);self._trigger('show',null,self._ui(clicked,$show[0]));});}:function(clicked,$show){$(clicked).closest('li').removeClass('ui-state-default').addClass('ui-tabs-selected ui-state-active');$show.removeClass('ui-tabs-hide');self._trigger('show',null,self._ui(clicked,$show[0]));};var hideTab=hideFx?function(clicked,$hide){$hide.animate(hideFx,hideFx.duration||'normal',function(){self.lis.removeClass('ui-tabs-selected ui-state-active').addClass('ui-state-default');$hide.addClass('ui-tabs-hide');resetStyle($hide,hideFx);self.element.dequeue("tabs");});}:function(clicked,$hide,$show){self.lis.removeClass('ui-tabs-selected ui-state-active').addClass('ui-state-default');$hide.addClass('ui-tabs-hide');self.element.dequeue("tabs");};this.anchors.bind(o.event+'.tabs',function(){var el=this,$li=$(this).closest('li'),$hide=self.panels.filter(':not(.ui-tabs-hide)'),$show=$(self._sanitizeSelector(this.hash));if(($li.hasClass('ui-tabs-selected')&&!o.collapsible)||$li.hasClass('ui-state-disabled')||$li.hasClass('ui-state-processing')||self._trigger('select',null,self._ui(this,$show[0]))===false){this.blur();return false;} +o.selected=self.anchors.index(this);self.abort();if(o.collapsible){if($li.hasClass('ui-tabs-selected')){o.selected=-1;if(o.cookie){self._cookie(o.selected,o.cookie);} +self.element.queue("tabs",function(){hideTab(el,$hide);}).dequeue("tabs");this.blur();return false;} +else if(!$hide.length){if(o.cookie){self._cookie(o.selected,o.cookie);} +self.element.queue("tabs",function(){showTab(el,$show);});self.load(self.anchors.index(this));this.blur();return false;}} +if(o.cookie){self._cookie(o.selected,o.cookie);} +if($show.length){if($hide.length){self.element.queue("tabs",function(){hideTab(el,$hide);});} +self.element.queue("tabs",function(){showTab(el,$show);});self.load(self.anchors.index(this));} +else{throw'jQuery UI Tabs: Mismatching fragment identifier.';} +if($.browser.msie){this.blur();}});this.anchors.bind('click.tabs',function(){return false;});},destroy:function(){var o=this.options;this.abort();this.element.unbind('.tabs').removeClass('ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible').removeData('tabs');this.list.removeClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');this.anchors.each(function(){var href=$.data(this,'href.tabs');if(href){this.href=href;} +var $this=$(this).unbind('.tabs');$.each(['href','load','cache'],function(i,prefix){$this.removeData(prefix+'.tabs');});});this.lis.unbind('.tabs').add(this.panels).each(function(){if($.data(this,'destroy.tabs')){$(this).remove();} +else{$(this).removeClass(['ui-state-default','ui-corner-top','ui-tabs-selected','ui-state-active','ui-state-hover','ui-state-focus','ui-state-disabled','ui-tabs-panel','ui-widget-content','ui-corner-bottom','ui-tabs-hide'].join(' '));}});if(o.cookie){this._cookie(null,o.cookie);}},add:function(url,label,index){if(index===undefined){index=this.anchors.length;} +var self=this,o=this.options,$li=$(o.tabTemplate.replace(/#\{href\}/g,url).replace(/#\{label\}/g,label)),id=!url.indexOf('#')?url.replace('#',''):this._tabId($('a',$li)[0]);$li.addClass('ui-state-default ui-corner-top').data('destroy.tabs',true);var $panel=$('#'+id);if(!$panel.length){$panel=$(o.panelTemplate).attr('id',id).data('destroy.tabs',true);} +$panel.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide');if(index>=this.lis.length){$li.appendTo(this.list);$panel.appendTo(this.list[0].parentNode);} +else{$li.insertBefore(this.lis[index]);$panel.insertBefore(this.panels[index]);} +o.disabled=$.map(o.disabled,function(n,i){return n>=index?++n:n;});this._tabify();if(this.anchors.length==1){$li.addClass('ui-tabs-selected ui-state-active');$panel.removeClass('ui-tabs-hide');this.element.queue("tabs",function(){self._trigger('show',null,self._ui(self.anchors[0],self.panels[0]));});this.load(0);} +this._trigger('add',null,this._ui(this.anchors[index],this.panels[index]));},remove:function(index){var o=this.options,$li=this.lis.eq(index).remove(),$panel=this.panels.eq(index).remove();if($li.hasClass('ui-tabs-selected')&&this.anchors.length>1){this.select(index+(index+1=index?--n:n;});this._tabify();this._trigger('remove',null,this._ui($li.find('a')[0],$panel[0]));},enable:function(index){var o=this.options;if($.inArray(index,o.disabled)==-1){return;} +this.lis.eq(index).removeClass('ui-state-disabled');o.disabled=$.grep(o.disabled,function(n,i){return n!=index;});this._trigger('enable',null,this._ui(this.anchors[index],this.panels[index]));},disable:function(index){var self=this,o=this.options;if(index!=o.selected){this.lis.eq(index).addClass('ui-state-disabled');o.disabled.push(index);o.disabled.sort();this._trigger('disable',null,this._ui(this.anchors[index],this.panels[index]));}},select:function(index){if(typeof index=='string'){index=this.anchors.index(this.anchors.filter('[href$='+index+']'));} +else if(index===null){index=-1;} +if(index==-1&&this.options.collapsible){index=this.options.selected;} +this.anchors.eq(index).trigger(this.options.event+'.tabs');},load:function(index){var self=this,o=this.options,a=this.anchors.eq(index)[0],url=$.data(a,'load.tabs');this.abort();if(!url||this.element.queue("tabs").length!==0&&$.data(a,'cache.tabs')){this.element.dequeue("tabs");return;} +this.lis.eq(index).addClass('ui-state-processing');if(o.spinner){var span=$('span',a);span.data('label.tabs',span.html()).html(o.spinner);} +this.xhr=$.ajax($.extend({},o.ajaxOptions,{url:url,success:function(r,s){$(self._sanitizeSelector(a.hash)).html(r);self._cleanup();if(o.cache){$.data(a,'cache.tabs',true);} +self._trigger('load',null,self._ui(self.anchors[index],self.panels[index]));try{o.ajaxOptions.success(r,s);} +catch(e){} +self.element.dequeue("tabs");}}));},abort:function(){this.element.queue([]);this.panels.stop(false,true);if(this.xhr){this.xhr.abort();delete this.xhr;} +this._cleanup();},url:function(index,url){this.anchors.eq(index).removeData('cache.tabs').data('load.tabs',url);},length:function(){return this.anchors.length;}});$.extend($.ui.tabs,{version:'1.7.2',getter:'length',defaults:{ajaxOptions:null,cache:false,cookie:null,collapsible:false,disabled:[],event:'click',fx:null,idPrefix:'ui-tabs-',panelTemplate:'
',spinner:'Loading…',tabTemplate:'
  • #{label}
  • '}});$.extend($.ui.tabs.prototype,{rotation:null,rotate:function(ms,continuing){var self=this,o=this.options;var rotate=self._rotate||(self._rotate=function(e){clearTimeout(self.rotation);self.rotation=setTimeout(function(){var t=o.selected;self.select(++t
    ').appendTo(this.element);this._refreshValue();},destroy:function(){this.element.removeClass("ui-progressbar" ++" ui-widget" ++" ui-widget-content" ++" ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow").removeData("progressbar").unbind(".progressbar");this.valueDiv.remove();$.widget.prototype.destroy.apply(this,arguments);},value:function(newValue){if(newValue===undefined){return this._value();} +this._setData('value',newValue);return this;},_setData:function(key,value){switch(key){case'value':this.options.value=value;this._refreshValue();this._trigger('change',null,{});break;} +$.widget.prototype._setData.apply(this,arguments);},_value:function(){var val=this.options.value;if(valthis._valueMax())val=this._valueMax();return val;},_valueMin:function(){var valueMin=0;return valueMin;},_valueMax:function(){var valueMax=100;return valueMax;},_refreshValue:function(){var value=this.value();this.valueDiv[value==this._valueMax()?'addClass':'removeClass']("ui-corner-right");this.valueDiv.width(value+'%');this.element.attr("aria-valuenow",value);}});$.extend($.ui.progressbar,{version:"1.7.2",defaults:{value:0}});})(jQuery);;jQuery.effects||(function($){$.effects={version:"1.7.2",save:function(element,set){for(var i=0;i');var wrapper=element.parent();if(element.css('position')=='static'){wrapper.css({position:'relative'});element.css({position:'relative'});}else{var top=element.css('top');if(isNaN(parseInt(top,10)))top='auto';var left=element.css('left');if(isNaN(parseInt(left,10)))left='auto';wrapper.css({position:element.css('position'),top:top,left:left,zIndex:element.css('z-index')}).show();element.css({position:'relative',top:0,left:0});} +wrapper.css(props);return wrapper;},removeWrapper:function(element){if(element.parent().is('.ui-effects-wrapper')) +return element.parent().replaceWith(element);return element;},setTransition:function(element,list,factor,value){value=value||{};$.each(list,function(i,x){unit=element.cssUnit(x);if(unit[0]>0)value[x]=unit[0]*factor+unit[1];});return value;},animateClass:function(value,duration,easing,callback){var cb=(typeof easing=="function"?easing:(callback?callback:null));var ea=(typeof easing=="string"?easing:null);return this.each(function(){var offset={};var that=$(this);var oldStyleAttr=that.attr("style")||'';if(typeof oldStyleAttr=='object')oldStyleAttr=oldStyleAttr["cssText"];if(value.toggle){that.hasClass(value.toggle)?value.remove=value.toggle:value.add=value.toggle;} +var oldStyle=$.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(value.add)that.addClass(value.add);if(value.remove)that.removeClass(value.remove);var newStyle=$.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(value.add)that.removeClass(value.add);if(value.remove)that.addClass(value.remove);for(var n in newStyle){if(typeof newStyle[n]!="function"&&newStyle[n]&&n.indexOf("Moz")==-1&&n.indexOf("length")==-1&&newStyle[n]!=oldStyle[n]&&(n.match(/color/i)||(!n.match(/color/i)&&!isNaN(parseInt(newStyle[n],10))))&&(oldStyle.position!="static"||(oldStyle.position=="static"&&!n.match(/left|top|bottom|right/))))offset[n]=newStyle[n];} +that.animate(offset,duration,ea,function(){if(typeof $(this).attr("style")=='object'){$(this).attr("style")["cssText"]="";$(this).attr("style")["cssText"]=oldStyleAttr;}else $(this).attr("style",oldStyleAttr);if(value.add)$(this).addClass(value.add);if(value.remove)$(this).removeClass(value.remove);if(cb)cb.apply(this,arguments);});});}};function _normalizeArguments(a,m){var o=a[1]&&a[1].constructor==Object?a[1]:{};if(m)o.mode=m;var speed=a[1]&&a[1].constructor!=Object?a[1]:(o.duration?o.duration:a[2]);speed=$.fx.off?0:typeof speed==="number"?speed:$.fx.speeds[speed]||$.fx.speeds._default;var callback=o.callback||($.isFunction(a[1])&&a[1])||($.isFunction(a[2])&&a[2])||($.isFunction(a[3])&&a[3]);return[a[0],o,speed,callback];} +$.fn.extend({_show:$.fn.show,_hide:$.fn.hide,__toggle:$.fn.toggle,_addClass:$.fn.addClass,_removeClass:$.fn.removeClass,_toggleClass:$.fn.toggleClass,effect:function(fx,options,speed,callback){return $.effects[fx]?$.effects[fx].call(this,{method:fx,options:options||{},duration:speed,callback:callback}):null;},show:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))) +return this._show.apply(this,arguments);else{return this.effect.apply(this,_normalizeArguments(arguments,'show'));}},hide:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))) +return this._hide.apply(this,arguments);else{return this.effect.apply(this,_normalizeArguments(arguments,'hide'));}},toggle:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))||($.isFunction(arguments[0])||typeof arguments[0]=='boolean')){return this.__toggle.apply(this,arguments);}else{return this.effect.apply(this,_normalizeArguments(arguments,'toggle'));}},addClass:function(classNames,speed,easing,callback){return speed?$.effects.animateClass.apply(this,[{add:classNames},speed,easing,callback]):this._addClass(classNames);},removeClass:function(classNames,speed,easing,callback){return speed?$.effects.animateClass.apply(this,[{remove:classNames},speed,easing,callback]):this._removeClass(classNames);},toggleClass:function(classNames,speed,easing,callback){return((typeof speed!=="boolean")&&speed)?$.effects.animateClass.apply(this,[{toggle:classNames},speed,easing,callback]):this._toggleClass(classNames,speed);},morph:function(remove,add,speed,easing,callback){return $.effects.animateClass.apply(this,[{add:add,remove:remove},speed,easing,callback]);},switchClass:function(){return this.morph.apply(this,arguments);},cssUnit:function(key){var style=this.css(key),val=[];$.each(['em','px','%','pt'],function(i,unit){if(style.indexOf(unit)>0) +val=[parseFloat(style),unit];});return val;}});$.each(['backgroundColor','borderBottomColor','borderLeftColor','borderRightColor','borderTopColor','color','outlineColor'],function(i,attr){$.fx.step[attr]=function(fx){if(fx.state==0){fx.start=getColor(fx.elem,attr);fx.end=getRGB(fx.end);} +fx.elem.style[attr]="rgb("+[Math.max(Math.min(parseInt((fx.pos*(fx.end[0]-fx.start[0]))+fx.start[0],10),255),0),Math.max(Math.min(parseInt((fx.pos*(fx.end[1]-fx.start[1]))+fx.start[1],10),255),0),Math.max(Math.min(parseInt((fx.pos*(fx.end[2]-fx.start[2]))+fx.start[2],10),255),0)].join(",")+")";};});function getRGB(color){var result;if(color&&color.constructor==Array&&color.length==3) +return color;if(result=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) +return[parseInt(result[1],10),parseInt(result[2],10),parseInt(result[3],10)];if(result=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) +return[parseFloat(result[1])*2.55,parseFloat(result[2])*2.55,parseFloat(result[3])*2.55];if(result=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) +return[parseInt(result[1],16),parseInt(result[2],16),parseInt(result[3],16)];if(result=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) +return[parseInt(result[1]+result[1],16),parseInt(result[2]+result[2],16),parseInt(result[3]+result[3],16)];if(result=/rgba\(0, 0, 0, 0\)/.exec(color)) +return colors['transparent'];return colors[$.trim(color).toLowerCase()];} +function getColor(elem,attr){var color;do{color=$.curCSS(elem,attr);if(color!=''&&color!='transparent'||$.nodeName(elem,"body")) +break;attr="backgroundColor";}while(elem=elem.parentNode);return getRGB(color);};var colors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};$.easing.jswing=$.easing.swing;$.extend($.easing,{def:'easeOutQuad',swing:function(x,t,b,c,d){return $.easing[$.easing.def](x,t,b,c,d);},easeInQuad:function(x,t,b,c,d){return c*(t/=d)*t+b;},easeOutQuad:function(x,t,b,c,d){return-c*(t/=d)*(t-2)+b;},easeInOutQuad:function(x,t,b,c,d){if((t/=d/2)<1)return c/2*t*t+b;return-c/2*((--t)*(t-2)-1)+b;},easeInCubic:function(x,t,b,c,d){return c*(t/=d)*t*t+b;},easeOutCubic:function(x,t,b,c,d){return c*((t=t/d-1)*t*t+1)+b;},easeInOutCubic:function(x,t,b,c,d){if((t/=d/2)<1)return c/2*t*t*t+b;return c/2*((t-=2)*t*t+2)+b;},easeInQuart:function(x,t,b,c,d){return c*(t/=d)*t*t*t+b;},easeOutQuart:function(x,t,b,c,d){return-c*((t=t/d-1)*t*t*t-1)+b;},easeInOutQuart:function(x,t,b,c,d){if((t/=d/2)<1)return c/2*t*t*t*t+b;return-c/2*((t-=2)*t*t*t-2)+b;},easeInQuint:function(x,t,b,c,d){return c*(t/=d)*t*t*t*t+b;},easeOutQuint:function(x,t,b,c,d){return c*((t=t/d-1)*t*t*t*t+1)+b;},easeInOutQuint:function(x,t,b,c,d){if((t/=d/2)<1)return c/2*t*t*t*t*t+b;return c/2*((t-=2)*t*t*t*t+2)+b;},easeInSine:function(x,t,b,c,d){return-c*Math.cos(t/d*(Math.PI/2))+c+b;},easeOutSine:function(x,t,b,c,d){return c*Math.sin(t/d*(Math.PI/2))+b;},easeInOutSine:function(x,t,b,c,d){return-c/2*(Math.cos(Math.PI*t/d)-1)+b;},easeInExpo:function(x,t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b;},easeOutExpo:function(x,t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b;},easeInOutExpo:function(x,t,b,c,d){if(t==0)return b;if(t==d)return b+c;if((t/=d/2)<1)return c/2*Math.pow(2,10*(t-1))+b;return c/2*(-Math.pow(2,-10*--t)+2)+b;},easeInCirc:function(x,t,b,c,d){return-c*(Math.sqrt(1-(t/=d)*t)-1)+b;},easeOutCirc:function(x,t,b,c,d){return c*Math.sqrt(1-(t=t/d-1)*t)+b;},easeInOutCirc:function(x,t,b,c,d){if((t/=d/2)<1)return-c/2*(Math.sqrt(1-t*t)-1)+b;return c/2*(Math.sqrt(1-(t-=2)*t)+1)+b;},easeInElastic:function(x,t,b,c,d){var s=1.70158;var p=0;var a=c;if(t==0)return b;if((t/=d)==1)return b+c;if(!p)p=d*.3;if(a').appendTo(document.body).addClass(o.options.className).css({top:startPosition.top,left:startPosition.left,height:elem.innerHeight(),width:elem.innerWidth(),position:'absolute'}).animate(animation,o.duration,o.options.easing,function(){transfer.remove();(o.callback&&o.callback.apply(elem[0],arguments));elem.dequeue();});});};})(jQuery); \ No newline at end of file diff --git a/lib/jquery.MultiFile.js b/lib/jquery.MultiFile.js new file mode 100644 index 0000000..c74083e --- /dev/null +++ b/lib/jquery.MultiFile.js @@ -0,0 +1,11 @@ +/* + ### jQuery Multiple File Upload Plugin v1.3 - 2008-09-30 ### + * http://www.fyneworks.com/ - diego@fyneworks.com + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + ### + Project: http://jquery.com/plugins/project/MultiFile/ + Website: http://www.fyneworks.com/jquery/multiple-file-upload/ +*/ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}(';2(K.1k)(3($){$.B($,{6:3(o){7 $("16:q.2L").6(o)}});$.B($.6,{18:{j:\'\',l:-1,1u:3(s){2($.1F){$.1F({2j:s.x(/\\n/m,\'<2i/>\'),19:{17:\'2K\',2s:\'2J\',2y:\'12.2I\',21:\'#1Q\',1P:\'#1O\',20:\'.8\',\'-1Z-17-1j\':\'1G\',\'-2f-17-1j\':\'1G\'}});K.2e($.2d,2h)}1m{2g(s)}},1p:\'$H\',y:{J:\'J\',1v:\'2r 2n 2q a $W q.\\2v 2x...\',T:\'2D T: $q\',1E:\'2E q 2H 1L 1J T:\\n$q\'}}});$.B($.6,{Z:3(a){p o=[];$(\'16:q\').M(3(){2($(5).X()==\'\')o[o.11]=5});7 $(o).M(3(){5.P=U}).1t(a||\'1w\')},1a:3(a){a=a||\'1w\';7 $(\'16:q.\'+a).1W(a).M(3(){5.P=t})},O:[\'28\',\'24\',\'27\'],1b:{},1H:3(b,c,d){p e,k;d=d||[];2(d.1l.1s().1o("1n")<0)d=[d];2(14(b)==\'3\'){$.6.Z();k=b.1r(c||K,d);$.6.1a();7 k};2(b.1l.1s().1o("1n")<0)b=[b];1q(p i=0;i0)){o.l=g.E.D(\'2w\');2(!(o.l>0)){o.l=(u(g.e.1D.C(/\\b(l|2A)\\-([0-9]+)\\b/m)||[\'\']).C(/[0-9]+/m)||[\'\'])[0];2(!(o.l>0))o.l=-1;1m o.l=u(o.l).C(/[0-9]+/m)[0]}};o.l=Y 2C(o.l);o.j=o.j||g.E.D(\'j\')||\'\';2(!o.j){o.j=(g.e.1D.C(/\\b(j\\-[\\w\\|]+)\\b/m))||\'\';o.j=Y u(o.j).x(/^(j|W)\\-/i,\'\')};$.B(g,o||{});g.y=$.B({},$.6.18.y,g.y);$.B(g,{n:0,F:[],2G:[],1d:g.e.A||\'6\'+u(e),1e:3(z){7 g.1d+(z>0?\'1I\'+u(z):\'\')},G:3(a,b){p c=g[a],k=$(b).D(\'k\');2(c){p d=c(b,k,g);2(d!=L)7 d}7 U}});2(u(g.j).11>1){g.1f=Y 1K(\'\\\\.(\'+(g.j?g.j:\'\')+\')$\',\'m\')};g.I=g.1d+\'1N\';g.E.1M(\'\');g.1g=$(\'#\'+g.I+\'\');g.e.H=g.e.H||\'q\'+e+\'[]\';g.1g.10(\'\');g.13=$(\'#\'+g.I+\'1h\');g.V=3(c,d){g.n++;c.1i=g;c.i=d;2(c.i>0)c.A=c.H=L;c.A=c.A||g.1e(c.i);c.H=u(g.1p.x(/\\$H/m,g.E.D(\'H\')).x(/\\$A/m,g.E.D(\'A\')).x(/\\$g/m,(e>0?e:\'\')).x(/\\$i/m,(d>0?d:\'\')));$(c).X(\'\').D(\'k\',\'\')[0].k=\'\';2((g.l>0)&&((g.n-1)>(g.l)))c.P=U;g.Q=g.F[c.i]=c;c=$(c);$(c).1R(3(){$(5).1T();2(!g.G(\'1S\',5,g))7 t;p a=\'\',v=u(5.k||\'\');2(g.j&&v&&!v.C(g.1f))a=g.y.1v.x(\'$W\',u(v.C(/\\.\\w{1,4}$/m)));1q(p f 1U g.F)2(g.F[f]&&g.F[f]!=5)2(g.F[f].k==v)a=g.y.1E.x(\'$q\',v.C(/[^\\/\\\\]+$/m));p b=$(g.N).N();b.1t(\'6\');2(a!=\'\'){g.1u(a);g.n--;g.V(b[0],5.i);c.1y().1V(b);c.J();7 t};$(5).19({1A:\'1Y\',1z:\'-1X\'});g.13.22(b);g.1C(5);g.V(b[0],5.i+1);2(!g.G(\'23\',5,g))7 t})};g.1C=3(c){2(!g.G(\'2z\',c,g))7 t;p r=$(\'\'),v=u(c.k||\'\'),a=$(\'\'+v.C(/[^\\/\\\\]+$/m)[0]+\'\'),b=$(\'\'+g.y.J+\'\');g.13.10(r.10(\'[\',b,\']&2b;\',a));b.29(3(){2(!g.G(\'2a\',c,g))7 t;g.n--;g.Q.P=t;g.F[c.i]=L;$(c).J();$(5).1y().J();$(g.Q).19({1A:\'\',1z:\'\'});$(g.Q).1c().X(\'\').D(\'k\',\'\')[0].k=\'\';2(!g.G(\'2F\',c,g))7 t;7 t});2(!g.G(\'2c\',c,g))7 t};2(!g.1i)g.V(g.e,0);g.n++})}});$(3(){$.6()})})(1k);',62,172,'||if|function||this|MultiFile|return||||||||||||accept|value|max|gi|||var|file|||false|String|||replace|STRING||id|extend|match|attr||slaves|trigger|name|wrapID|remove|window|null|each|clone|autoIntercept|disabled|current|span|div|selected|true|addSlave|ext|val|new|disableEmpty|append|length||labels|typeof|fn|input|border|options|css|reEnableEmpty|intercepted|reset|instanceKey|generateID|rxAccept|wrapper|_labels|MF|radius|jQuery|constructor|else|Array|indexOf|namePattern|for|apply|toString|addClass|error|denied|mfD|_MultiFile|parent|top|position|metadata|addToList|className|duplicate|blockUI|10px|intercept|_F|been|RegExp|already|wrap|_wrap|fff|color|900|change|onFileSelect|blur|in|prepend|removeClass|3000px|absolute|webkit|opacity|backgroundColor|before|afterFileSelect|ajaxSubmit|class|title|validate|submit|click|onFileRemove|nbsp|afterFileAppend|unblockUI|setTimeout|moz|alert|2000|br|message|catch|try|arguments|cannot|string|number|select|You|padding|data|meta|nTry|maxlength|again|size|onFileAppend|limit|href|Number|File|This|afterFileRemove|files|has|0pt|15px|none|multi'.split('|'),0,{})) \ No newline at end of file diff --git a/lib/jquery.autocomplete.css b/lib/jquery.autocomplete.css new file mode 100644 index 0000000..ca42582 --- /dev/null +++ b/lib/jquery.autocomplete.css @@ -0,0 +1,49 @@ +.ac_results { + padding: 0px; + border: 1px solid black; + background-color: white; + overflow: hidden; + z-index: 99999; + text-align: left; +} + +.ac_results ul { + width: 100%; + list-style-position: outside; + list-style: none; + padding: 0; + margin: 0; +} + +.ac_results li { + margin: 0px; + padding: 2px 5px; + cursor: default; + display: block; + /* + if width will be 100% horizontal scrollbar will apear + when scroll mode will be used + */ + /*width: 100%;*/ + font: menu; + font-size: 12px; + /* + it is very important, if line-height not setted or setted + in relative units scroll will be broken in firefox + */ + line-height: 16px; + overflow: hidden; +} + +.ac_loading { + background: white url('images/loading-small.gif') right center no-repeat; +} + +.ac_odd { + background-color: #eee; +} + +.ac_over { + background-color: #0A246A; + color: white; +} diff --git a/lib/jquery.autocomplete.js b/lib/jquery.autocomplete.js new file mode 100644 index 0000000..02f2b54 --- /dev/null +++ b/lib/jquery.autocomplete.js @@ -0,0 +1,13 @@ +/* + * Autocomplete - jQuery plugin 1.0.2 + * + * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $ + * + */ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}(';(3($){$.31.1o({12:3(b,d){5 c=Y b=="1w";d=$.1o({},$.D.1L,{11:c?b:14,w:c?14:b,1D:c?$.D.1L.1D:10,Z:d&&!d.1x?10:3U},d);d.1t=d.1t||3(a){6 a};d.1q=d.1q||d.1K;6 I.K(3(){1E $.D(I,d)})},M:3(a){6 I.X("M",a)},1y:3(a){6 I.15("1y",[a])},20:3(){6 I.15("20")},1Y:3(a){6 I.15("1Y",[a])},1X:3(){6 I.15("1X")}});$.D=3(o,r){5 t={2N:38,2I:40,2D:46,2x:9,2v:13,2q:27,2d:3x,2j:33,2o:34,2e:8};5 u=$(o).3f("12","3c").P(r.24);5 p;5 m="";5 n=$.D.2W(r);5 s=0;5 k;5 h={1z:B};5 l=$.D.2Q(r,o,1U,h);5 j;$.1T.2L&&$(o.2K).X("3S.12",3(){4(j){j=B;6 B}});u.X(($.1T.2L?"3Q":"3N")+".12",3(a){k=a.2F;3L(a.2F){Q t.2N:a.1d();4(l.L()){l.2y()}A{W(0,C)}N;Q t.2I:a.1d();4(l.L()){l.2u()}A{W(0,C)}N;Q t.2j:a.1d();4(l.L()){l.2t()}A{W(0,C)}N;Q t.2o:a.1d();4(l.L()){l.2s()}A{W(0,C)}N;Q r.19&&$.1p(r.R)==","&&t.2d:Q t.2x:Q t.2v:4(1U()){a.1d();j=C;6 B}N;Q t.2q:l.U();N;3A:1I(p);p=1H(W,r.1D);N}}).1G(3(){s++}).3v(3(){s=0;4(!h.1z){2k()}}).2i(3(){4(s++>1&&!l.L()){W(0,C)}}).X("1y",3(){5 c=(1n.7>1)?1n[1]:14;3 23(q,a){5 b;4(a&&a.7){16(5 i=0;i1){v=a.17(0,a.7-1).2Z(r.R)+r.R+v}v+=r.R}u.J(v);1l();u.15("M",[b.w,b.H]);6 C}3 W(b,c){4(k==t.2D){l.U();6}5 a=u.J();4(!c&&a==m)6;m=a;a=1k(a);4(a.7>=r.22){u.P(r.21);4(!r.1C)a=a.O();1R(a,2V,1l)}A{1B();l.U()}};3 1g(b){4(!b){6[""]}5 d=b.1Z(r.R);5 c=[];$.K(d,3(i,a){4($.1p(a))c[i]=$.1p(a)});6 c}3 1k(a){4(!r.19)6 a;5 b=1g(a);6 b[b.7-1]}3 1A(q,a){4(r.1A&&(1k(u.J()).O()==q.O())&&k!=t.2e){u.J(u.J()+a.48(1k(m).7));$.D.1N(o,m.7,m.7+a.7)}};3 2k(){1I(p);p=1H(1l,47)};3 1l(){5 c=l.L();l.U();1I(p);1B();4(r.2U){u.1y(3(a){4(!a){4(r.19){5 b=1g(u.J()).17(0,-1);u.J(b.2Z(r.R)+(b.7?r.R:""))}A u.J("")}})}4(c)$.D.1N(o,o.H.7,o.H.7)};3 2V(q,a){4(a&&a.7&&s){1B();l.2T(a,q);1A(q,a[0].H);l.1W()}A{1l()}};3 1R(f,d,g){4(!r.1C)f=f.O();5 e=n.2S(f);4(e&&e.7){d(f,e)}A 4((Y r.11=="1w")&&(r.11.7>0)){5 c={45:+1E 44()};$.K(r.2R,3(a,b){c[a]=Y b=="3"?b():b});$.43({42:"41",3Z:"12"+o.3Y,2M:r.2M,11:r.11,w:$.1o({q:1k(f),3X:r.Z},c),3W:3(a){5 b=r.1r&&r.1r(a)||1r(a);n.1h(f,b);d(f,b)}})}A{l.2J();g(f)}};3 1r(c){5 d=[];5 b=c.1Z("\\n");16(5 i=0;i]*)("+a.2C(/([\\^\\$\\(\\)\\[\\]\\{\\}\\*\\.\\+\\?\\|\\\\])/2A,"\\\\$1")+")(?![^<>]*>)(?![^&;]+;)","2A"),"<2z>$1")},1x:C,1s:3I};$.D.2W=3(g){5 h={};5 j=0;3 1a(s,a){4(!g.1C)s=s.O();5 i=s.3H(a);4(i==-1)6 B;6 i==0||g.1V};3 1h(q,a){4(j>g.1j){18()}4(!h[q]){j++}h[q]=a}3 1f(){4(!g.w)6 B;5 f={},2w=0;4(!g.11)g.1j=1;f[""]=[];16(5 i=0,30=g.w.7;i<30;i++){5 c=g.w[i];c=(Y c=="1w")?[c]:c;5 d=g.1q(c,i+1,g.w.7);4(d===B)1P;5 e=d.3G(0).O();4(!f[e])f[e]=[];5 b={H:d,w:c,M:g.1v&&g.1v(c)||d};f[e].1O(b);4(2w++0){5 c=h[k];$.K(c,3(i,x){4(1a(x.H,q)){a.1O(x)}})}}6 a}A 4(h[q]){6 h[q]}A 4(g.1a){16(5 i=q.7-1;i>=g.22;i--){5 c=h[q.3F(0,i)];4(c){5 a=[];$.K(c,3(i,x){4(1a(x.H,q)){a[a.7]=x}});6 a}}}6 14}}};$.D.2Q=3(e,g,f,k){5 h={G:"3E"};5 j,y=-1,w,1m="",1M=C,F,z;3 2r(){4(!1M)6;F=$("<3D/>").U().P(e.2H).T("3C","3B").1J(2p.2n);z=$("<3z/>").1J(F).3y(3(a){4(V(a).2m&&V(a).2m.3w()==\'2l\'){y=$("1F",z).1e(h.G).3u(V(a));$(V(a)).P(h.G)}}).2i(3(a){$(V(a)).P(h.G);f();g.1G();6 B}).3t(3(){k.1z=C}).3s(3(){k.1z=B});4(e.E>0)F.T("E",e.E);1M=B}3 V(a){5 b=a.V;3r(b&&b.3q!="2l")b=b.3p;4(!b)6[];6 b}3 S(b){j.17(y,y+1).1e(h.G);2h(b);5 a=j.17(y,y+1).P(h.G);4(e.1x){5 c=0;j.17(0,y).K(3(){c+=I.1i});4((c+a[0].1i-z.1c())>z[0].3o){z.1c(c+a[0].1i-z.3n())}A 4(c=j.1b()){y=0}}3 2g(a){6 e.Z&&e.Z").3m(e.1t(a,1m)).P(i%2==0?"3l":"3k").1J(z)[0];$.w(c,"2c",w[i])}j=z.3j("1F");4(e.1S){j.17(0,1).P(h.G);y=0}4($.31.2b)z.2b()}6{2T:3(d,q){2r();w=d;1m=q;2f()},2u:3(){S(1)},2y:3(){S(-1)},2t:3(){4(y!=0&&y-8<0){S(-y)}A{S(-8)}},2s:3(){4(y!=j.1b()-1&&y+8>j.1b()){S(j.1b()-1-y)}A{S(8)}},U:3(){F&&F.U();j&&j.1e(h.G);y=-1},L:3(){6 F&&F.3i(":L")},3h:3(){6 I.L()&&(j.2a("."+h.G)[0]||e.1S&&j[0])},1W:3(){5 a=$(g).3g();F.T({E:Y e.E=="1w"||e.E>0?e.E:$(g).E(),2E:a.2E+g.1i,1Q:a.1Q}).1W();4(e.1x){z.1c(0);z.T({29:e.1s,3e:\'3d\'});4($.1T.3b&&Y 2p.2n.3T.29==="3a"){5 c=0;j.K(3(){c+=I.1i});5 b=c>e.1s;z.T(\'3V\',b?e.1s:c);4(!b){j.E(z.E()-28(j.T("32-1Q"))-28(j.T("32-39")))}}}},26:3(){5 a=j&&j.2a("."+h.G).1e(h.G);6 a&&a.7&&$.w(a[0],"2c")},2J:3(){z&&z.2B()},1u:3(){F&&F.37()}}};$.D.1N=3(b,a,c){4(b.2O){5 d=b.2O();d.36(C);d.35("2P",a);d.4c("2P",c);d.4b()}A 4(b.2Y){b.2Y(a,c)}A{4(b.2X){b.2X=a;b.4a=c}}b.1G()}})(49);',62,261,'|||function|if|var|return|length|||||||||||||||||||||||||data||active|list|else|false|true|Autocompleter|width|element|ACTIVE|value|this|val|each|visible|result|break|toLowerCase|addClass|case|multipleSeparator|moveSelect|css|hide|target|onChange|bind|typeof|max||url|autocomplete||null|trigger|for|slice|flush|multiple|matchSubset|size|scrollTop|preventDefault|removeClass|populate|trimWords|add|offsetHeight|cacheLength|lastWord|hideResultsNow|term|arguments|extend|trim|formatMatch|parse|scrollHeight|highlight|unbind|formatResult|string|scroll|search|mouseDownOnSelect|autoFill|stopLoading|matchCase|delay|new|li|focus|setTimeout|clearTimeout|appendTo|formatItem|defaults|needsInit|Selection|push|continue|left|request|selectFirst|browser|selectCurrent|matchContains|show|unautocomplete|setOptions|split|flushCache|loadingClass|minChars|findValueCallback|inputClass||selected||parseInt|maxHeight|filter|bgiframe|ac_data|COMMA|BACKSPACE|fillList|limitNumberOfItems|movePosition|click|PAGEUP|hideResults|LI|nodeName|body|PAGEDOWN|document|ESC|init|pageDown|pageUp|next|RETURN|nullData|TAB|prev|strong|gi|empty|replace|DEL|top|keyCode|in|resultsClass|DOWN|emptyList|form|opera|dataType|UP|createTextRange|character|Select|extraParams|load|display|mustMatch|receiveData|Cache|selectionStart|setSelectionRange|join|ol|fn|padding|||moveStart|collapse|remove||right|undefined|msie|off|auto|overflow|attr|offset|current|is|find|ac_odd|ac_even|html|innerHeight|clientHeight|parentNode|tagName|while|mouseup|mousedown|index|blur|toUpperCase|188|mouseover|ul|default|absolute|position|div|ac_over|substr|charAt|indexOf|180|RegExp|100|switch|400|keydown|ac_loading|ac_results|keypress|ac_input|submit|style|150|height|success|limit|name|port||abort|mode|ajax|Date|timestamp||200|substring|jQuery|selectionEnd|select|moveEnd'.split('|'),0,{})); diff --git a/lib/jquery.cookie.js b/lib/jquery.cookie.js new file mode 100644 index 0000000..6df1fac --- /dev/null +++ b/lib/jquery.cookie.js @@ -0,0 +1,96 @@ +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // CAUTION: Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; \ No newline at end of file diff --git a/lib/jquery.form.js b/lib/jquery.form.js new file mode 100644 index 0000000..40fb8b1 --- /dev/null +++ b/lib/jquery.form.js @@ -0,0 +1,645 @@ +/* + * jQuery Form Plugin + * version: 2.28 (10-MAY-2009) + * @requires jQuery v1.2.2 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function() { + $(this).ajaxSubmit({ + target: '#output' + }); + return false; // <-- important! + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') + options = { success: options }; + + var url = $.trim(this.attr('action')); + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + url = url || window.location.href || '' + + options = $.extend({ + url: url, + type: this.attr('method') || 'GET' + }, options || {}); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (var n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) + a.push( { name: n, value: options.data[n][k] } ); + } + else + a.push( { name: n, value: options.data[n] } ); + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + $(options.target).html(data).each(oldSuccess, arguments); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i].apply(options, [data, status, $form]); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + var multipart = false; +// var mp = 'multipart/form-data'; +// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + // options.iframe allows user to force iframe mode + if (options.iframe || found || multipart) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) + $.get(options.closeKeepAlive, fileUpload); + else + fileUpload(); + } + else + $.ajax(options); + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + /* (this breaks the watermark form uploader, turn it off for now) + if ($(':input[name=submit]', form).length) { + alert('Error: Form elements must not be named "submit".'); + return; + } + */ + + var opts = $.extend({}, $.ajaxSettings, options); + var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); + + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $(' + diff --git a/modules/calendarview/controllers/calendarview.php b/modules/calendarview/controllers/calendarview.php new file mode 100644 index 0000000..b1bae80 --- /dev/null +++ b/modules/calendarview/controllers/calendarview.php @@ -0,0 +1,296 @@ +set_global( + array("calendar_user" => $display_user, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($display_year, url::site("calendarview/calendar/" . $display_year))->set_last()))); + $template->page_title = t("Gallery :: Calendar"); + $template->content = new View("calendarview_year.html"); + $template->content->calendar_year = $display_year; + $template->content->calendar_user = $display_user; + $template->content->calendar_user_year_form = $this->_get_calenderprefs_form($display_year, $display_user); + $template->content->title = t("Calendar") . ": " . $display_year; + print $template; + } + + public function day($display_year, $display_user, $display_month, $display_day) { + // Display all images for the specified day. + + // Set up default search conditions for retrieving all photos from the specified day. + $where = array(array("type", "!=", "album")); + if ($display_user != "-1") { + $where[] = array("owner_id", "=", $display_user); + } + $where[] = array("captured", ">=", mktime(0, 0, 0, $display_month, $display_day, $display_year)); + $where[] = array("captured", "<", mktime(0, 0, 0, $display_month, ($display_day + 1), $display_year)); + + // Figure out the total number of photos to display. + $day_count = calendarview::get_items_count($where); + + // Figure out paging stuff. + $page_size = module::get_var("gallery", "page_size", 9); + $page = (int) Input::instance()->get("page", "1"); + $offset = ($page-1) * $page_size; + $max_pages = max(ceil($day_count / $page_size), 1); + + // Make sure that the page references a valid offset + if (($page < 1) || ($page > $max_pages)) { + throw new Kohana_404_Exception(); + } + + // Figure out which photos go on this page. + $children = calendarview::get_items($page_size, $offset, $where); + + // Create and display the page. + $root = item::root(); + $template = new Theme_View("page.html", "collection", "CalendarDayView"); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "children" => $children, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($display_year, url::site("calendarview/calendar/" . $display_year . "/" . $display_user)), + Breadcrumb::instance(t(date("F", mktime(0, 0, 0, $display_month, $display_day, $display_year))), url::site("calendarview/month/" . $display_year . "/" . $display_user . "/" . $display_month)), + Breadcrumb::instance($display_day, url::site("calendarview/month/" . $display_year . "/" . $display_user . "/" . $display_month . "/" . $display_day))->set_last()), + "children_count" => $day_count)); + $template->page_title = t("Gallery :: Calendar"); + $template->content = new View("dynamic.html"); + $template->content->title = t("Photos From ") . date("d", mktime(0, 0, 0, $display_month, $display_day, $display_year)) . " " . t(date("F", mktime(0, 0, 0, $display_month, $display_day, $display_year))) . " " . date("Y", mktime(0, 0, 0, $display_month, $display_day, $display_year)); + print $template; + + // Set breadcrumbs on the photo pages to point back to the calendar day view. + item::set_display_context_callback("CalendarView_Controller::get_display_day_context", $display_user, $display_year, $display_month, $display_day); + } + + static function get_display_day_context($item, $display_user, $display_year, $display_month, $display_day) { + // Set up default search conditions for retrieving all photos from the specified day. + $where = array(array("type", "!=", "album")); + if ($display_user != "-1") { + $where[] = array("owner_id", "=", $display_user); + } + $where[] = array("captured", ">=", mktime(0, 0, 0, $display_month, $display_day, $display_year)); + $where[] = array("captured", "<", mktime(0, 0, 0, $display_month, ($display_day + 1), $display_year)); + + // Generate breadcrumbs for the photo page. + $root = item::root(); + $breadcrumbs = array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($display_year, url::site("calendarview/calendar/" . $display_year . "/" . $display_user)), + Breadcrumb::instance(t(date("F", mktime(0, 0, 0, $display_month, $display_day, $display_year))), url::site("calendarview/month/" . $display_year . "/" . $display_user . "/" . $display_month)), + Breadcrumb::instance($display_day, url::site("calendarview/month/" . $display_year . "/" . $display_user . "/" . $display_month . "/" . $display_day)), + Breadcrumb::instance($item->title, $item->url())->set_last() + ); + + return CalendarView_Controller::get_display_context($item, $where, $breadcrumbs); + } + + public function month($display_year, $display_user, $display_month) { + // Display all images for the specified month. + + // Set up default search conditions for retrieving all photos from the specified month. + $where = array(array("type", "!=", "album")); + if ($display_user != "-1") { + $where[] = array("owner_id", "=", $display_user); + } + $where[] = array("captured", ">=", mktime(0, 0, 0, $display_month, 1, $display_year)); + $where[] = array("captured", "<", mktime(0, 0, 0, $display_month+1, 1, $display_year)); + + // Figure out the total number of photos to display. + $day_count = calendarview::get_items_count($where); + + // Figure out paging stuff. + $page_size = module::get_var("gallery", "page_size", 9); + $page = (int) Input::instance()->get("page", "1"); + $offset = ($page-1) * $page_size; + $max_pages = max(ceil($day_count / $page_size), 1); + + // Make sure that the page references a valid offset + if (($page < 1) || ($page > $max_pages)) { + throw new Kohana_404_Exception(); + } + + // Figure out which photos go on this page. + $children = calendarview::get_items($page_size, $offset, $where); + + // Create and display the page. + $root = item::root(); + $template = new Theme_View("page.html", "collection", "CalendarMonthView"); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($display_year, url::site("calendarview/calendar/" . $display_year . "/" . $display_user)), + Breadcrumb::instance(t(date("F", mktime(0, 0, 0, $display_month, 1, $display_year))), url::site("calendarview/month/" . $display_year . "/" . $display_user . "/" . $display_month))->set_last()), + "children" => $children, + "children_count" => $day_count)); + $template->page_title = t("Gallery :: Calendar"); + $template->content = new View("dynamic.html"); + $template->content->title = t("Photos From ") . t(date("F", mktime(0, 0, 0, $display_month, 1, $display_year))) . " " . date("Y", mktime(0, 0, 0, $display_month, 1, $display_year)); + print $template; + + // Set up breadcrumbs for the photo pages to point back to the calendar month view. + item::set_display_context_callback("CalendarView_Controller::get_display_month_context", $display_user, $display_year, $display_month); + } + + static function get_display_month_context($item, $display_user, $display_year, $display_month) { + // Set up default search conditions for retrieving all photos from the specified month. + $where = array(array("type", "!=", "album")); + if ($display_user != "-1") { + $where[] = array("owner_id", "=", $display_user); + } + $where[] = array("captured", ">=", mktime(0, 0, 0, $display_month, 1, $display_year)); + $where[] = array("captured", "<", mktime(0, 0, 0, $display_month+1, 1, $display_year)); + + // Generate breadcrumbs for the photo page. + $root = item::root(); + $breadcrumbs = array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($display_year, url::site("calendarview/calendar/" . $display_year . "/" . $display_user)), + Breadcrumb::instance(t(date("F", mktime(0, 0, 0, $display_month, 1, $display_year))), url::site("calendarview/month/" . $display_year . "/" . $display_user . "/" . $display_month)), + Breadcrumb::instance($item->title, $item->url())->set_last() + ); + + return CalendarView_Controller::get_display_context($item, $where, $breadcrumbs); + } + + private function get_display_context($item, $where=array(), $breadcrumbs=array()) { + // Set up previous / next / breadcrumbs / etc to point to CalendarView instead of the album. + + // Figure out the photo's position. + $position = calendarview::get_position($item, $where); + + // Figure out the previous and next items. + if ($position > 1) { + list ($previous_item, $ignore, $next_item) = calendarview::get_items(3, $position - 2, $where); + } else { + list ($next_item) = calendarview::get_items(1, $position, $where); + } + + // Figure out the total number of photos. + $sibling_count = calendarview::get_items_count($where); + + return array("position" => $position, + "previous_item" => $previous_item, + "next_item" => $next_item, + "sibling_count" => $sibling_count, + "breadcrumbs" => $breadcrumbs); + } + + private function _get_calenderprefs_form($display_year, $display_user) { + // Generate a form to allow the visitor to select a year and a gallery photo owner. + $calendar_group = new Forge("calendarview/setprefs", "", "post", + array("id" => "g-view-calendar-form")); + + // Generate a list of all Gallery users who have uploaded photos. + $valid_users[-1] = "(All Users)"; + $gallery_users = ORM::factory("user")->find_all(); + foreach ($gallery_users as $one_user) { + $count = ORM::factory("item") + ->viewable() + ->where("owner_id", "=", $one_user->id) + ->where("type", "!=", "album") + ->where("captured", "!=", "") + ->find_all() + ->count(); + if ($count > 0) { + $valid_users[$one_user->id] = $one_user->full_name; + } + } + + // Generate a list of years, starting with the year the earliest photo was + // taken, and ending with the year of the most recent photo. + $valid_years = Array(); + if ($display_user == -1) { + $all_photos = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->where("captured", "!=", "") + ->order_by("captured", "DESC") + ->find_all(); + $counter = date('Y', $all_photos[count($all_photos)-1]->captured); + while ($counter <= date('Y', $all_photos[0]->captured)) { + $valid_years[$counter] = $counter; + $counter++; + } + } else { + $all_photos = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->where("captured", "!=", "") + ->where("owner_id", "=", $display_user) + ->order_by("captured", "DESC") + ->find_all(); + $counter = date('Y', $all_photos[count($all_photos)-1]->captured); + while ($counter <= date('Y', $all_photos[0]->captured)) { + $valid_years[$counter] = $counter; + $counter++; + } + } + + // Create the form. + $calendar_group->dropdown('cal_user') + ->label(t("Display Photos From User: ")) + ->id('cal_user') + ->options($valid_users) + ->selected($display_user); + $calendar_group->dropdown('cal_year') + ->label(t("For Year: ")) + ->id('cal_year') + ->options($valid_years) + ->selected($display_year); + + // Add a save button to the form. + $calendar_group->submit("SaveSettings")->value(t("Go"))->id('cal_go'); + + // Return the newly generated form. + return $calendar_group; + } + + public function setprefs() { + // Change the calendar year and / or user. + + // Prevent Cross Site Request Forgery + access::verify_csrf(); + + // Get user specified settings. + $str_user_id = Input::instance()->post("cal_user"); + $str_year_id = Input::instance()->post("cal_year"); + + // redirect to the currect page. + url::redirect(url::site("calendarview/calendar/" . $str_year_id . "/" . $str_user_id, request::protocol())); + } +} diff --git a/modules/calendarview/css/calendarview_calendar.css b/modules/calendarview/css/calendarview_calendar.css new file mode 100644 index 0000000..82dfaa9 --- /dev/null +++ b/modules/calendarview/css/calendarview_calendar.css @@ -0,0 +1,59 @@ +/* Grid view ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#g-calendar-grid { + position: relative; + align: center; + float: left; + width: 200px; + height: 220px; + margin: 10px 10px 10px 10px; +} + +#g-calendar-profile-grid { + position: relative; + align: center; + float: left; + width: 200px; + height: 145px; + margin: 10px 10px 10px 10px; +} + +/* Search form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#cal_user { + top: 0px; + left: 60px; + display: inline; +} +#cal_year { + top: 0px; + left: 240px; + display: inline; +} +#cal_go { + top: 0px; + left: 328px; + display: inline; +} + +/* Content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +table.calendar { + text-align: center; +} + +table.calendar caption { + font-size: 1.5em; + padding: 0.2em; +} + +table.calendar th, table.calendar td { + padding: 0.2em; + border: 0px; +} + +table.calendar td:hover { + background: #ddf; +} + +/* For RTL Languages ~~~~~~~~~~~~~~~~~~~~~~~ */ +.rtl #g-calendar-grid { + float: right; +} diff --git a/modules/calendarview/css/calendarview_menu.css b/modules/calendarview/css/calendarview_menu.css new file mode 100644 index 0000000..600cc15 --- /dev/null +++ b/modules/calendarview/css/calendarview_menu.css @@ -0,0 +1,3 @@ +#g-view-menu #g-calendarview-link { + background-image: url('../images/ico-view-calendarview.png'); +} diff --git a/modules/calendarview/helpers/calendarview.php b/modules/calendarview/helpers/calendarview.php new file mode 100644 index 0000000..4807bc0 --- /dev/null +++ b/modules/calendarview/helpers/calendarview.php @@ -0,0 +1,48 @@ +viewable() + ->merge_where($where) + ->order_by("captured", "ASC") + ->count_all(); + } + + static function get_items($limit=null, $offset=null, $where=array()) { + // Returns the items identified by $where, up to $limit, and starting at $offset. + return ORM::factory("item") + ->viewable() + ->merge_where($where) + ->order_by("captured", "ASC") + ->find_all($limit, $offset); + } + + static function get_position($item, $where=array()) { + // Get's $item's position within $where. + return ORM::factory("item") + ->viewable() + ->merge_where($where) + ->where("items.id", "<=", $item->id) + ->order_by("captured", "ASC") + ->count_all(); + } +} diff --git a/modules/calendarview/helpers/calendarview_block.php b/modules/calendarview/helpers/calendarview_block.php new file mode 100644 index 0000000..05e470d --- /dev/null +++ b/modules/calendarview/helpers/calendarview_block.php @@ -0,0 +1,72 @@ + t("More Photos From This Date")); + } + + static function get($block_id, $theme) { + $block = ""; + + // Make sure the current page belongs to an item. + if (!$theme->item()) { + return; + } + $item = $theme->item; + + $display_date = ""; + if (isset($item->captured)) { + $display_date = $item->captured; + }elseif (isset($item->created)) { + $display_date = $item->created; + } + + // Make sure there are photo's to display. + $day_count = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->where("captured", ">=", mktime(0, 0, 0, date("n", $display_date), date("j", $display_date), date("Y", $display_date))) + ->where("captured", "<", mktime(0, 0, 0, date("n", $display_date), date("j", $display_date)+1, date("Y", $display_date))) + ->find_all() + ->count(); + $month_count = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->where("captured", ">=", mktime(0, 0, 0, date("n", $display_date), 1, date("Y", $display_date))) + ->where("captured", "<", mktime(0, 0, 0, date("n", $display_date)+1, 1, date("Y", $display_date))) + ->find_all() + ->count(); + + switch ($block_id) { + case "calendarview_photo": + if ( ($display_date != "") && (($day_count > 0) || ($month_count > 0)) ) { + $block = new Block(); + $block->css_id = "g-calendarview-sidebar"; + $block->title = t("Calendar"); + $block->content = new View("calendarview_sidebar.html"); + $block->content->date = $display_date; + $block->content->day_count = $day_count; + $block->content->month_count = $month_count; + } + break; + } + return $block; + } +} diff --git a/modules/calendarview/helpers/calendarview_event.php b/modules/calendarview/helpers/calendarview_event.php new file mode 100644 index 0000000..e98415c --- /dev/null +++ b/modules/calendarview/helpers/calendarview_event.php @@ -0,0 +1,99 @@ +append(Menu::factory("link") + ->id("calendarview") + ->label(t("View Calendar")) + ->url(url::site("calendarview/calendar/")) + ->css_id("g-calendarview-link")); + } + + static function movie_menu($menu, $theme) { + $menu->append(Menu::factory("link") + ->id("calendarview") + ->label(t("View Calendar")) + ->url(url::site("calendarview/calendar/")) + ->css_id("g-calendarview-link")); + } + + static function album_menu($menu, $theme) { + $menu->append(Menu::factory("link") + ->id("calendarview") + ->label(t("View Calendar")) + ->url(url::site("calendarview/calendar/")) + ->css_id("g-calendarview-link")); + } + + static function tag_menu($menu, $theme) { + $menu->append(Menu::factory("link") + ->id("calendarview") + ->label(t("View Calendar")) + ->url(url::site("calendarview/calendar/")) + ->css_id("g-calendarview-link")); + } + + static function pre_deactivate($data) { + // If the admin is about to deactivate EXIF, warn them that this module requires it. + if ($data->module == "exif") { + $data->messages["warn"][] = t("The CalendarView module requires the EXIF module."); + } + } + + static function module_change($changes) { + // If EXIF is deactivated, display a warning that it is required for this module to function properly. + if (!module::is_active("exif") || in_array("exif", $changes->deactivate)) { + site_status::warning( + t("The CalendarView module requires the EXIF module. " . + "Activate the EXIF module now", + array("url" => html::mark_clean(url::site("admin/modules")))), + "calendarview_needs_exif"); + } else { + site_status::clear("calendarview_needs_exif"); + } + } + + static function show_user_profile($data) { + // Display a few months on the user profile screen. + $v = new View("user_profile_calendarview.html"); + $v->user_id = $data->user->id; + + // Figure out what month the users newest photo was taken it. + // Make that the last month to display. + // If a user hasn't uploaded anything, make the current month + // the last to be displayed. + $latest_photo = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->where("captured", "!=", "") + ->where("owner_id", "=", $data->user->id) + ->order_by("captured", "DESC") + ->find_all(1); + if (count($latest_photo) > 0) { + $v->user_year = date('Y', $latest_photo[0]->captured); + $v->user_month = date('n', $latest_photo[0]->captured); + } else { + $v->user_year = date('Y'); + $v->user_month = date('n'); + } + + $data->content[] = (object) array("title" => t("User calendar"), "view" => $v); + } +} diff --git a/modules/calendarview/helpers/calendarview_installer.php b/modules/calendarview/helpers/calendarview_installer.php new file mode 100644 index 0000000..986a13f --- /dev/null +++ b/modules/calendarview/helpers/calendarview_installer.php @@ -0,0 +1,40 @@ +css("calendarview_calendar.css") . + $theme->css("calendarview_menu.css"); + } +} diff --git a/modules/calendarview/images/ico-view-calendarview.png b/modules/calendarview/images/ico-view-calendarview.png new file mode 100644 index 0000000..5e564b8 Binary files /dev/null and b/modules/calendarview/images/ico-view-calendarview.png differ diff --git a/modules/calendarview/libraries/PHPCalendar.php b/modules/calendarview/libraries/PHPCalendar.php new file mode 100644 index 0000000..2d1df2b --- /dev/null +++ b/modules/calendarview/libraries/PHPCalendar.php @@ -0,0 +1,87 @@ +month = (int) $month; + $this->year = (int) $year; + $this->month_url = $url; + } + + public function event($day_of_the_week, $event_url = NULL, $css_id = NULL, $custom_text = NULL) + { + $this->event_data += Array($day_of_the_week => Array($event_url, $css_id, $custom_text)); + } + + public function render() + { + return $this->generate_calendar($this->year, $this->month, $this->event_data, 2, $this->month_url, $this->week_start, NULL); + } + + # PHP Calendar (version 2.3), written by Keith Devens + # http://keithdevens.com/software/php_calendar + # see example at http://keithdevens.com/weblog + # License: http://keithdevens.com/software/license + function generate_calendar($year, $month, $days = array(), $day_name_length = 3, $month_href = NULL, $first_day = 0, $pn = array()) + { + $first_of_month = gmmktime(0,0,0,$month,1,$year); + #remember that mktime will automatically correct if invalid dates are entered + # for instance, mktime(0,0,0,12,32,1997) will be the date for Jan 1, 1998 + # this provides a built in "rounding" feature to generate_calendar() + + if ($first_day == 0) $day_names = array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); + if ($first_day == 1) $day_names = array("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"); + + list($month, $year, $month_name, $weekday) = explode(',',gmstrftime('%m,%Y,%B,%w',$first_of_month)); + $weekday = ($weekday + 7 - $first_day) % 7; #adjust for $first_day + $title = t(date("F", mktime(0, 0, 0, $month, 1, $year))) . ' ' . $year; + + #Begin calendar. Uses a real . See http://diveintomark.org/archives/2002/07/03 + @list($p, $pl) = each($pn); @list($n, $nl) = each($pn); #previous and next links, if applicable + if($p) $p = ''.($pl ? ''.$p.'' : $p).' '; + if($n) $n = ' '.($nl ? ''.$n.'' : $n).''; + $calendar = ''."\n". + '\n"; + + if($day_name_length){ #if the day names should be shown ($day_name_length > 0) + #if day_name_length is >3, the full name of the day will be printed + foreach($day_names as $d) + $calendar .= ''; + $calendar .= "\n"; + } + + if($weekday > 0) $calendar .= ''; #initial 'empty' days + for($day=1,$days_in_month=gmdate('t',$first_of_month); $day<=$days_in_month; $day++,$weekday++){ + if($weekday == 7){ + $weekday = 0; #start a new week + $calendar .= "\n"; + } + if(isset($days[$day]) and is_array($days[$day])){ + @list($link, $classes, $content) = $days[$day]; + if(is_null($content)) $content = $day; + $calendar .= '' : '>'). + ($link ? ''.$content.'' : $content).''; + } + else $calendar .= ""; + } + if($weekday != 7) $calendar .= ''; #remaining "empty" days + + return $calendar."\n
    '.$p.($month_href ? ''.$title.'' : $title).$n."
    '.t($day_name_length < 4 ? substr($d,0,$day_name_length) : $d) . '
     
    $day 
    \n"; + } +} +?> \ No newline at end of file diff --git a/modules/calendarview/module.info b/modules/calendarview/module.info new file mode 100644 index 0000000..f51f162 --- /dev/null +++ b/modules/calendarview/module.info @@ -0,0 +1,7 @@ +name = "CalendarView" +description = "View your photos by the date they were taken." +version = 1 +author_name = "rWatcher" +author_url = "http://codex.gallery2.org/User:RWatcher" +info_url = "http://codex.gallery2.org/Gallery3:Modules:calendarview" +discuss_url = "http://gallery.menalto.com/node/92405" diff --git a/modules/calendarview/views/calendarview_sidebar.html.php b/modules/calendarview/views/calendarview_sidebar.html.php new file mode 100644 index 0000000..b4f1f6f --- /dev/null +++ b/modules/calendarview/views/calendarview_sidebar.html.php @@ -0,0 +1,9 @@ + +
      + 0): ?> +
    • ">
    • + + 0): ?> +
    • ">
    • + +
    \ No newline at end of file diff --git a/modules/calendarview/views/calendarview_year.html.php b/modules/calendarview/views/calendarview_year.html.php new file mode 100644 index 0000000..b40d7d5 --- /dev/null +++ b/modules/calendarview/views/calendarview_year.html.php @@ -0,0 +1,95 @@ + +
    +
    + dynamic_top() ?> +
    +

    +
    + +


    + +viewable() + ->where("type", "!=", "album") + ->where("captured", ">=", mktime(0, 0, 0, 1, 1, $calendar_year)) + ->where("captured", "<", mktime(0, 0, 0, 1, 1, ($calendar_year + 1))) + ->order_by("captured") + ->find_all(); + } else { + $items_for_year = ORM::factory("item") + ->viewable() + ->where("owner_id", "=", $calendar_user) + ->where("type", "!=", "album") + ->where("captured", ">=", mktime(0, 0, 0, 1, 1, $calendar_year)) + ->where("captured", "<", mktime(0, 0, 0, 1, 1, ($calendar_year + 1))) + ->order_by("captured") + ->find_all(); + } + + // Set up some initial variables. + $counter_months = 1; + $counter_days = 0; + $counter = 0; + + // Set up the January Calendar. + // Check and see if any photos were taken in January, + // If so, make the month title into a clickable link. + print "
    "; + if ((count($items_for_year) > 0) && (date("n", $items_for_year[$counter]->captured) == 1)) { + $month_url = url::site("calendarview/month/" . $calendar_year . "/" . $calendar_user . "/" . $counter_months . "/"); + } else { + $month_url = ""; + } + $calendar = new PHPCalendar($counter_months, $calendar_year, $month_url); + + // Loop through each photo taken during this year, and see what month and day they were taken on. + // Make the corresponding dates on the calendars into clickable links. + while ($counter < (count($items_for_year))) { + + // Check and see if we've switched to a new month. + // If so, render the current calendar and set up a new one. + while (date("n", $items_for_year[$counter]->captured) > $counter_months) { + echo $calendar->render(); + print "
    "; + $counter_months++; + $counter_days = 0; + print "
    "; + if (date("n", $items_for_year[$counter]->captured) == $counter_months) { + $month_url = url::site("calendarview/month/" . $calendar_year . "/" . $calendar_user . "/" . $counter_months . "/"); + } else { + $month_url = ""; + } + $calendar = new PHPCalendar($counter_months, $calendar_year, $month_url); + } + + // If the day of the current photo is different then the day of the previous photo, + // then add a link to the calendar for this date and set the current day to this day. + if (date("j", $items_for_year[$counter]->captured) > $counter_days) { + $counter_days = date("j", $items_for_year[$counter]->captured); + $calendar->event($counter_days, url::site("calendarview/day/" . $calendar_year . "/" . $calendar_user . "/" . $counter_months . "/" . $counter_days)); + } + + // Move onto the next photo. + $counter++; + } + + // Print out the last calendar to be generated. + echo $calendar->render(); + print "
    "; + $counter_months++; + + // If the calendar that was previously rendered was not December, + // then print out a few empty months for the rest of the year. + while ($counter_months < 13) { + print "
    "; + $month_url = ""; + $calendar = new PHPCalendar($counter_months, $calendar_year, $month_url); + echo $calendar->render(); + print "
    "; + $counter_months++; + } +?> +dynamic_bottom() ?> diff --git a/modules/calendarview/views/user_profile_calendarview.html.php b/modules/calendarview/views/user_profile_calendarview.html.php new file mode 100644 index 0000000..4d9d68a --- /dev/null +++ b/modules/calendarview/views/user_profile_calendarview.html.php @@ -0,0 +1,86 @@ + +viewable() + ->where("owner_id", "=", $user_id) + ->where("type", "!=", "album") + ->where("captured", ">=", mktime(0, 0, 0, $user_month-2, 1, $user_year)) + ->where("captured", "<", mktime(0, 0, 0, $user_month+1, 1, ($user_year))) + ->order_by("captured") + ->find_all(); + + // Set up some initial variables. + $calendar_year = $user_year; + $counter_months = $user_month - 2; + if ($counter_months < 1) { + $counter_months += 12; + $calendar_year--; + } + $counter_days = 0; + $counter = 0; + + // Print the first month. + print "
    "; + if ((count($items) > 0) && (date("n", $items[$counter]->captured) == $counter_months)) { + $month_url = url::site("calendarview/month/" . $calendar_year . "/" . $user_id . "/" . $counter_months . "/"); + } else { + $month_url = ""; + } + $calendar = new PHPCalendar($counter_months, $calendar_year, $month_url); + + // Loop through each photo taken during the 3 month time frame, and see what month and day they were taken on. + // Make the corresponding dates on the calendars into clickable links. + while ($counter < (count($items))) { + + // Check and see if we've switched to a new month. + // If so, render the current calendar and set up a new one. + // Continue printing empty months until we reach the next photo or the last month. + while (date("n", $items[$counter]->captured) != $counter_months) { + echo $calendar->render(); + print "
    "; + $counter_months++; + if ($counter_months == 13) { + $counter_months = 1; + $calendar_year++; + } + $counter_days = 0; + print "
    "; + if (date("n", $items[$counter]->captured) == $counter_months) { + $month_url = url::site("calendarview/month/" . $calendar_year . "/" . $user_id . "/" . $counter_months . "/"); + } else { + $month_url = ""; + } + $calendar = new PHPCalendar($counter_months, $calendar_year, $month_url); + } + + // If the day of the current photo is different then the day of the previous photo, + // then add a link to the calendar for this date and set the current day to this day. + if (date("j", $items[$counter]->captured) > $counter_days) { + $counter_days = date("j", $items[$counter]->captured); + $calendar->event($counter_days, url::site("calendarview/day/" . $calendar_year . "/" . $user_id . "/" . $counter_months . "/" . $counter_days)); + } + + // Move onto the next photo. + $counter++; + } + + // Print out the last calendar to be generated. + echo $calendar->render(); + print "
    "; + $counter_months++; + + // If the calendar that was previously rendered was not the final month, + // then print out a few empty months to fill the remaining space. + while ($counter_months < $user_month + 1) { + print "
    "; + $month_url = ""; + $calendar = new PHPCalendar($counter_months, $calendar_year, $month_url); + echo $calendar->render(); + print "
    "; + $counter_months++; + } + +?> +


    + diff --git a/modules/comment/controllers/admin_comments.php b/modules/comment/controllers/admin_comments.php new file mode 100644 index 0000000..3018340 --- /dev/null +++ b/modules/comment/controllers/admin_comments.php @@ -0,0 +1,60 @@ +page_title = t("Comment settings"); + $view->content = new View("admin_comments.html"); + $view->content->form = $this->_get_admin_form(); + print $view; + } + + public function save() { + access::verify_csrf(); + $form = $this->_get_admin_form(); + $form->validate(); + module::set_var("comment", "access_permissions", + $form->comment_settings->access_permissions->value); + module::set_var("comment", "rss_visible", + $form->comment_settings->rss_visible->value); + message::success(t("Comment settings updated")); + url::redirect("admin/comments"); + } + + private function _get_admin_form() { + $form = new Forge("admin/comments/save", "", "post", + array("id" => "g-comments-admin-form")); + $comment_settings = $form->group("comment_settings")->label(t("Permissions")); + $comment_settings->dropdown("access_permissions") + ->label(t("Who can leave comments?")) + ->options(array("everybody" => t("Everybody"), + "registered_users" => t("Only registered users"))) + ->selected(module::get_var("comment", "access_permissions")); + $comment_settings->dropdown("rss_visible") + ->label(t("Which RSS feeds can users see?")) + ->options(array("all" => t("All comment feeds"), + "newest" => t("New comments feed only"), + "per_item" => t("Comments on photos, movies and albums only"))) + ->selected(module::get_var("comment", "rss_visible")); + $comment_settings->submit("save")->value(t("Save")); + return $form; + } +} + diff --git a/modules/comment/controllers/admin_manage_comments.php b/modules/comment/controllers/admin_manage_comments.php new file mode 100644 index 0000000..ef31c95 --- /dev/null +++ b/modules/comment/controllers/admin_manage_comments.php @@ -0,0 +1,144 @@ +delete("comments") + ->where("state", "IN", array("deleted", "spam")) + ->where("updated", "<", db::expr("UNIX_TIMESTAMP() - 86400 * 7")) + ->execute(); + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_manage_comments.html"); + $view->content->menu = $this->_menu($this->_counts()); + print $view; + } + + public function menu_labels() { + $menu = $this->_menu($this->_counts()); + json::reply(array((string) $menu->get("unpublished")->label, + (string) $menu->get("published")->label, + (string) $menu->get("spam")->label, + (string) $menu->get("deleted")->label)); + } + + public function queue($state) { + $page = max(Input::instance()->get("page"), 1); + + $view = new Gallery_View("admin_manage_comments_queue.html"); + $view->counts = $this->_counts(); + $view->menu = $this->_menu($view->counts); + $view->state = $state; + $view->comments = ORM::factory("comment") + ->order_by("created", "DESC") + ->order_by("id", "DESC") + ->where("state", "=", $state) + ->limit(self::$items_per_page) + ->offset(($page - 1) * self::$items_per_page) + ->find_all(); + + // This view is not themed so we can't use $theme->url() in the view and have to + // reproduce Gallery_View::url() logic here. + $atn = theme::$admin_theme_name; + $view->fallback_avatar_url = url::abs_file("themes/$atn/images/avatar.jpg"); + + $view->page = $page; + $view->page_type = "collection"; + $view->page_subtype = "admin_comments"; + $view->page_size = self::$items_per_page; + $view->children_count = $this->_counts()->$state; + $view->max_pages = ceil($view->children_count / $view->page_size); + + // Also we want to use $theme->paginator() so we need a dummy theme + $view->theme = $view; + + print $view; + } + + private function _menu($counts) { + return Menu::factory("root") + ->append(Menu::factory("link") + ->id("unpublished") + ->label(t2("Awaiting Moderation (%count)", + "Awaiting Moderation (%count)", + $counts->unpublished)) + ->url(url::site("admin/manage_comments/queue/unpublished"))) + ->append(Menu::factory("link") + ->id("published") + ->label(t2("Approved (%count)", + "Approved (%count)", + $counts->published)) + ->url(url::site("admin/manage_comments/queue/published"))) + ->append(Menu::factory("link") + ->id("spam") + ->label(t2("Spam (%count)", + "Spam (%count)", + $counts->spam)) + ->url(url::site("admin/manage_comments/queue/spam"))) + ->append(Menu::factory("link") + ->id("deleted") + ->label(t2("Recently Deleted (%count)", + "Recently Deleted (%count)", + $counts->deleted)) + ->url(url::site("admin/manage_comments/queue/deleted"))); + } + + private function _counts() { + $counts = new stdClass(); + $counts->unpublished = 0; + $counts->published = 0; + $counts->spam = 0; + $counts->deleted = 0; + foreach (db::build() + ->select("state") + ->select(array("c" => 'COUNT("*")')) + ->from("comments") + ->group_by("state") + ->execute() as $row) { + $counts->{$row->state} = $row->c; + } + return $counts; + } + + public function set_state($id, $state) { + access::verify_csrf(); + + $comment = ORM::factory("comment", $id); + $orig = clone $comment; + if ($comment->loaded()) { + $comment->state = $state; + $comment->save(); + } + } + + public function delete_all_spam() { + access::verify_csrf(); + + db::build() + ->delete("comments") + ->where("state", "=", "spam") + ->execute(); + url::redirect("admin/manage_comments/queue/spam"); + } +} + diff --git a/modules/comment/controllers/comments.php b/modules/comment/controllers/comments.php new file mode 100644 index 0000000..64aa0b4 --- /dev/null +++ b/modules/comment/controllers/comments.php @@ -0,0 +1,81 @@ +validate(); + $comment = ORM::factory("comment"); + $comment->item_id = $id; + $comment->author_id = identity::active_user()->id; + $comment->text = $form->add_comment->text->value; + $comment->guest_name = $form->add_comment->inputs["name"]->value; + $comment->guest_email = $form->add_comment->email->value; + $comment->guest_url = $form->add_comment->url->value; + $comment->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + switch ($key) { + case "guest_name": $key = "name"; break; + case "guest_email": $key = "email"; break; + case "guest_url": $key = "url"; break; + } + $form->add_comment->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $comment->save(); + $view = new Theme_View("comment.html", "other", "comment-fragment"); + $view->comment = $comment; + + json::reply(array("result" => "success", + "view" => (string)$view, + "form" => (string)comment::get_add_form($item))); + } else { + $form = comment::prefill_add_form($form); + json::reply(array("result" => "error", "form" => (string)$form)); + } + } + + /** + * Present a form for adding a new comment to this item or editing an existing comment. + */ + public function form_add($item_id) { + $item = ORM::factory("item", $item_id); + access::required("view", $item); + if (!comment::can_comment()) { + access::forbidden(); + } + + print comment::prefill_add_form(comment::get_add_form($item)); + } +} diff --git a/modules/comment/css/comment.css b/modules/comment/css/comment.css new file mode 100644 index 0000000..db096f2 --- /dev/null +++ b/modules/comment/css/comment.css @@ -0,0 +1,45 @@ +#g-content #g-comment-form { + margin-top: 2em; +} + +#g-content #g-comments { + margin-top: 2em; + position: relative; +} + +#g-content #g-comments ul li { + margin: 1em 0; +} + +#g-content #g-comments .g-author { + border-bottom: 1px solid #ccc; + color: #999; + height: 32px; + line-height: 32px; +} + +#g-content #g-comments ul li div { + padding: 0 8px 8px 43px; +} + +#g-content #g-comments .g-avatar { + height: 32px; + margin-right: .4em; + width: 32px; +} + +#g-add-comment { + position: absolute; + right: 0; + top: 2px; +} + +#g-admin-comments-menu { + margin: 1em 0; +} + +#g-admin-comments-menu a { + margin: 0; + padding: .2em .6em; +} + diff --git a/modules/comment/helpers/comment.php b/modules/comment/helpers/comment.php new file mode 100644 index 0000000..0d922eb --- /dev/null +++ b/modules/comment/helpers/comment.php @@ -0,0 +1,71 @@ +id}", "", "post", array("id" => "g-comment-form")); + $group = $form->group("add_comment")->label(t("Add comment")); + $group->input("name") + ->label(t("Name")) + ->id("g-author") + ->error_messages("required", t("You must enter a name for yourself")); + $group->input("email") + ->label(t("Email (hidden)")) + ->id("g-email") + ->error_messages("required", t("You must enter a valid email address")) + ->error_messages("invalid", t("You must enter a valid email address")); + $group->input("url") + ->label(t("Website (hidden)")) + ->id("g-url") + ->error_messages("url", t("You must enter a valid url")); + $group->textarea("text") + ->label(t("Comment")) + ->id("g-text") + ->error_messages("required", t("You must enter a comment")); + $group->hidden("item_id")->value($item->id); + module::event("comment_add_form", $form); + module::event("captcha_protect_form", $form); + $group->submit("")->value(t("Add"))->class("ui-state-default ui-corner-all"); + + return $form; + } + + static function prefill_add_form($form) { + $active = identity::active_user(); + if (!$active->guest) { + $group = $form->add_comment; + $group->inputs["name"]->value($active->full_name)->disabled("disabled"); + $group->email->value($active->email)->disabled("disabled"); + $group->url->value($active->url)->disabled("disabled"); + } + return $form; + } + + static function can_comment() { + return !identity::active_user()->guest || + module::get_var("comment", "access_permissions") == "everybody"; + } +} + diff --git a/modules/comment/helpers/comment_block.php b/modules/comment/helpers/comment_block.php new file mode 100644 index 0000000..b602595 --- /dev/null +++ b/modules/comment/helpers/comment_block.php @@ -0,0 +1,39 @@ + t("Recent comments")); + } + + static function get($block_id) { + $block = new Block(); + switch ($block_id) { + case "recent_comments": + $block->css_id = "g-recent-comments"; + $block->title = t("Recent comments"); + $block->content = new View("admin_block_recent_comments.html"); + $block->content->comments = + ORM::factory("comment")->order_by("created", "DESC")->limit(5)->find_all(); + break; + } + + return $block; + } +} \ No newline at end of file diff --git a/modules/comment/helpers/comment_event.php b/modules/comment/helpers/comment_event.php new file mode 100644 index 0000000..f73e545 --- /dev/null +++ b/modules/comment/helpers/comment_event.php @@ -0,0 +1,97 @@ +delete("comments") + ->where("item_id", "=", $item->id) + ->execute(); + } + + static function user_deleted($user) { + $guest = identity::guest(); + if (!empty($guest)) { // could be empty if there is not identity provider + db::build() + ->update("comments") + ->set("author_id", $guest->id) + ->set("guest_email", null) + ->set("guest_name", "guest") + ->set("guest_url", null) + ->where("author_id", "=", $user->id) + ->execute(); + } + } + + static function identity_provider_changed($old_provider, $new_provider) { + $guest = identity::guest(); + db::build() + ->update("comments") + ->set("author_id", $guest->id) + ->set("guest_email", null) + ->set("guest_name", "guest") + ->set("guest_url", null) + ->execute(); + } + + static function admin_menu($menu, $theme) { + $menu->get("settings_menu") + ->append(Menu::factory("link") + ->id("comment") + ->label(t("Comments")) + ->url(url::site("admin/comments"))); + + $menu->get("content_menu") + ->append(Menu::factory("link") + ->id("comments") + ->label(t("Comments")) + ->url(url::site("admin/manage_comments"))); + } + + static function photo_menu($menu, $theme) { + $menu + ->append(Menu::factory("link") + ->id("comments") + ->label(t("View comments on this item")) + ->url("#comments") + ->css_id("g-comments-link")); + } + + static function item_index_data($item, $data) { + foreach (db::build() + ->select("text") + ->from("comments") + ->where("item_id", "=", $item->id) + ->execute() as $row) { + $data[] = $row->text; + } + } + + static function show_user_profile($data) { + $view = new View("user_profile_comments.html"); + $view->comments = ORM::factory("comment") + ->order_by("created", "DESC") + ->where("state", "=", "published") + ->where("author_id", "=", $data->user->id) + ->find_all(); + if ($view->comments->count()) { + $data->content[] = (object)array("title" => t("Comments"), "view" => $view); + } + } +} diff --git a/modules/comment/helpers/comment_installer.php b/modules/comment/helpers/comment_installer.php new file mode 100644 index 0000000..136f96e --- /dev/null +++ b/modules/comment/helpers/comment_installer.php @@ -0,0 +1,118 @@ +query("CREATE TABLE IF NOT EXISTS {comments} ( + `author_id` int(9) default NULL, + `created` int(9) NOT NULL, + `guest_email` varchar(128) default NULL, + `guest_name` varchar(128) default NULL, + `guest_url` varchar(255) default NULL, + `id` int(9) NOT NULL auto_increment, + `item_id` int(9) NOT NULL, + `server_http_accept_charset` varchar(64) default NULL, + `server_http_accept_encoding` varchar(64) default NULL, + `server_http_accept_language` varchar(64) default NULL, + `server_http_accept` varchar(128) default NULL, + `server_http_connection` varchar(64) default NULL, + `server_http_host` varchar(64) default NULL, + `server_http_referer` varchar(255) default NULL, + `server_http_user_agent` varchar(128) default NULL, + `server_query_string` varchar(64) default NULL, + `server_remote_addr` varchar(40) default NULL, + `server_remote_host` varchar(255) default NULL, + `server_remote_port` varchar(16) default NULL, + `state` varchar(15) default 'unpublished', + `text` text, + `updated` int(9) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + + module::set_var("comment", "spam_caught", 0); + module::set_var("comment", "access_permissions", "everybody"); + module::set_var("comment", "rss_visible", "all"); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("ALTER TABLE {comments} CHANGE `state` `state` varchar(15) default 'unpublished'"); + module::set_version("comment", $version = 2); + } + + if ($version == 2) { + module::set_var("comment", "access_permissions", "everybody"); + module::set_version("comment", $version = 3); + } + + if ($version == 3) { + // 40 bytes for server_remote_addr is enough to swallow the longest + // representation of an IPv6 addy. + // + // 255 bytes for server_remote_host is enough to swallow the longest + // legit DNS entry, with a few bytes to spare. + $db->query( + "ALTER TABLE {comments} CHANGE `server_remote_addr` `server_remote_addr` varchar(40)"); + $db->query( + "ALTER TABLE {comments} CHANGE `server_remote_host` `server_remote_host` varchar(255)"); + module::set_version("comment", $version = 4); + } + + if ($version == 4) { + module::set_var("comment", "rss_visible", "all"); + module::set_version("comment", $version = 5); + } + + // In version 5 we accidentally set the installer variable to rss_available when it should + // have been rss_visible. Migrate it over now, if necessary. + if ($version == 5) { + if (!module::get_var("comment", "rss_visible")) { + module::set_var("comment", "rss_visible", module::get_var("comment", "rss_available")); + } + module::clear_var("comment", "rss_available"); + module::set_version("comment", $version = 6); + } + + // In version 6 we accidentally left the install value of "rss_visible" to "both" when it + // should have been "all" + if ($version == 6) { + if (module::get_var("comment", "rss_visible") == "both") { + module::set_var("comment", "rss_visible", "all"); + } + module::set_version("comment", $version = 7); + } + } + + static function uninstall() { + $db = Database::instance(); + + // Notify listeners that we're deleting some data. This is probably going to be very + // inefficient for large uninstalls, and we could make it better by doing things like passing + // a SQL fragment through so that the listeners could use subselects. But by using a single, + // simple event API we lighten the load on module developers. + foreach (ORM::factory("item") + ->join("comments", "items.id", "comments.item_id") + ->find_all() as $item) { + module::event("item_related_update", $item); + } + $db->query("DROP TABLE IF EXISTS {comments};"); + } +} diff --git a/modules/comment/helpers/comment_rest.php b/modules/comment/helpers/comment_rest.php new file mode 100644 index 0000000..1971edc --- /dev/null +++ b/modules/comment/helpers/comment_rest.php @@ -0,0 +1,74 @@ +url); + access::required("view", $comment->item()); + + return array( + "url" => $request->url, + "entity" => $comment->as_restful_array(), + "relationships" => rest::relationships("comment", $comment)); + } + + static function put($request) { + // Only admins can edit comments, for now + if (!identity::active_user()->admin) { + access::forbidden(); + } + + $comment = rest::resolve($request->url); + $comment = ORM::factory("comment"); + $comment->text = $request->params->text; + $comment->save(); + } + + static function delete($request) { + if (!identity::active_user()->admin) { + access::forbidden(); + } + + $comment = rest::resolve($request->url); + access::required("edit", $comment->item()); + + $comment->delete(); + } + + static function relationships($resource_type, $resource) { + switch ($resource_type) { + case "item": + return array( + "comments" => array( + "url" => rest::url("item_comments", $resource))); + } + } + + static function resolve($id) { + $comment = ORM::factory("comment", $id); + if (!access::can("view", $comment->item())) { + throw new Kohana_404_Exception(); + } + return $comment; + } + + static function url($comment) { + return url::abs_site("rest/comment/{$comment->id}"); + } +} diff --git a/modules/comment/helpers/comment_rss.php b/modules/comment/helpers/comment_rss.php new file mode 100644 index 0000000..924710f --- /dev/null +++ b/modules/comment/helpers/comment_rss.php @@ -0,0 +1,92 @@ +id"] = + t("Comments on %title", array("title" => html::purify($item->title))); + } + return $feeds; + } + + static function feed($feed_id, $offset, $limit, $id) { + if (!comment_rss::feed_visible($feed_id)) { + return; + } + + $comments = ORM::factory("comment") + ->viewable() + ->where("comments.state", "=", "published") + ->order_by("comments.created", "DESC"); + + if ($feed_id == "item") { + $item = ORM::factory("item", $id); + $comments + ->where("items.left_ptr", ">=", $item->left_ptr) + ->where("items.right_ptr", "<=", $item->right_ptr); + } + + $feed = new stdClass(); + $feed->view = "comment.mrss"; + $feed->comments = array(); + foreach ($comments->find_all($limit, $offset) as $comment) { + $item = $comment->item(); + $feed->comments[] = new ArrayObject( + array("pub_date" => date("D, d M Y H:i:s O", $comment->created), + "text" => nl2br(html::purify($comment->text)), + "thumb_url" => $item->thumb_url(), + "thumb_height" => $item->thumb_height, + "thumb_width" => $item->thumb_width, + "item_uri" => url::abs_site("{$item->type}s/$item->id"), + "title" => ( + ($item->id == item::root()->id) ? + html::purify($item->title) : + t("%site_title - %item_title", + array("site_title" => item::root()->title, + "item_title" => $item->title))), + "author" => html::clean($comment->author_name())), + ArrayObject::ARRAY_AS_PROPS); + } + + $feed->max_pages = ceil($comments->count_all() / $limit); + $feed->title = html::purify(t("%site_title - Recent Comments", + array("site_title" => item::root()->title))); + $feed->uri = url::abs_site("albums/" . (empty($id) ? "1" : $id)); + $feed->description = t("Recent comments"); + + return $feed; + } +} diff --git a/modules/comment/helpers/comment_theme.php b/modules/comment/helpers/comment_theme.php new file mode 100644 index 0000000..1c2d7c5 --- /dev/null +++ b/modules/comment/helpers/comment_theme.php @@ -0,0 +1,46 @@ +css("comment.css") + . $theme->script("comment.js"); + } + + static function admin_head($theme) { + return $theme->css("comment.css"); + } + + static function photo_bottom($theme) { + $block = new Block; + $block->css_id = "g-comments"; + $block->title = t("Comments"); + $block->anchor = "comments"; + + $view = new View("comments.html"); + $view->comments = ORM::factory("comment") + ->where("item_id", "=", $theme->item()->id) + ->where("state", "=", "published") + ->order_by("created", "ASC") + ->find_all(); + + $block->content = $view; + return $block; + } +} \ No newline at end of file diff --git a/modules/comment/helpers/comments_rest.php b/modules/comment/helpers/comments_rest.php new file mode 100644 index 0000000..6fc86ad --- /dev/null +++ b/modules/comment/helpers/comments_rest.php @@ -0,0 +1,62 @@ +params; + $num = isset($p->num) ? min((int)$p->num, 100) : 10; + $start = isset($p->start) ? (int)$p->start : 0; + + foreach (ORM::factory("comment")->viewable()->find_all($num, $start) as $comment) { + $comments[] = rest::url("comment", $comment); + } + return array("url" => rest::url("comments"), + "members" => $comments); + } + + + static function post($request) { + $entity = $request->params->entity; + + $item = rest::resolve($entity->item); + access::required("edit", $item); + + $comment = ORM::factory("comment"); + $comment->author_id = identity::active_user()->id; + $comment->item_id = $item->id; + $comment->text = $entity->text; + $comment->save(); + + return array("url" => rest::url("comment", $comment)); + } + + static function url() { + return url::abs_site("rest/comments"); + } +} diff --git a/modules/comment/helpers/item_comments_rest.php b/modules/comment/helpers/item_comments_rest.php new file mode 100644 index 0000000..f6f8930 --- /dev/null +++ b/modules/comment/helpers/item_comments_rest.php @@ -0,0 +1,50 @@ +url); + access::required("view", $item); + + $comments = array(); + foreach (ORM::factory("comment") + ->viewable() + ->where("item_id", "=", $item->id) + ->order_by("created", "DESC") + ->find_all() as $comment) { + $comments[] = rest::url("comment", $comment); + } + + return array( + "url" => $request->url, + "members" => $comments); + } + + static function resolve($id) { + $item = ORM::factory("item", $id); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + return $item; + } + + static function url($item) { + return url::abs_site("rest/item_comments/{$item->id}"); + } +} diff --git a/modules/comment/js/comment.js b/modules/comment/js/comment.js new file mode 100644 index 0000000..2487c5f --- /dev/null +++ b/modules/comment/js/comment.js @@ -0,0 +1,45 @@ +$("document").ready(function() { + $("#g-add-comment").click(function(event) { + event.preventDefault(); + if (!$("#g-comment-form").length) { + $.get($(this).attr("href"), + {}, + function(data) { + $("#g-comment-detail").append(data); + ajaxify_comment_form(); + $.scrollTo("#g-comment-form-anchor", 800); + }); + } + }); + $(".g-no-comments a").click(function(event) { + event.preventDefault(); + if (!$("#g-comment-form").length) { + $.get($(this).attr("href"), + {}, + function(data) { + $("#g-comment-detail").append(data); + ajaxify_comment_form(); + }); + $(".g-no-comments").remove(); + } + }); +}); + +function ajaxify_comment_form() { + $("#g-comments form").ajaxForm({ + dataType: "json", + success: function(data) { + if (data.result == "success") { + $("#g-comments #g-comment-detail ul").append(data.view); + $("#g-comments #g-comment-detail ul li:last").effect("highlight", {color: "#cfc"}, 8000); + $("#g-comment-form").hide(2000).remove(); + $("#g-no-comments").hide(2000); + } else { + if (data.form) { + $("#g-comments form").replaceWith(data.form); + ajaxify_comment_form(); + } + } + } + }); +} diff --git a/modules/comment/models/comment.php b/modules/comment/models/comment.php new file mode 100644 index 0000000..62ab8bc --- /dev/null +++ b/modules/comment/models/comment.php @@ -0,0 +1,194 @@ +item_id); + } + + function author() { + return identity::lookup_user($this->author_id); + } + + function author_name() { + $author = $this->author(); + if ($author->guest) { + return $this->guest_name; + } else { + return $author->display_name(); + } + } + + function author_email() { + $author = $this->author(); + if ($author->guest) { + return $this->guest_email; + } else { + return $author->email; + } + } + + function author_url() { + $author = $this->author(); + if ($author->guest) { + return $this->guest_url; + } else { + return $author->url; + } + } + + /** + * Add some custom per-instance rules. + */ + public function validate(Validation $array=null) { + // validate() is recursive, only modify the rules on the outermost call. + if (!$array) { + $this->rules = array( + "guest_name" => array("callbacks" => array(array($this, "valid_author"))), + "guest_email" => array("callbacks" => array(array($this, "valid_email"))), + "guest_url" => array("rules" => array("url")), + "item_id" => array("callbacks" => array(array($this, "valid_item"))), + "state" => array("rules" => array("Comment_Model::valid_state")), + "text" => array("rules" => array("required")), + ); + } + + parent::validate($array); + } + + /** + * @see ORM::save() + */ + public function save() { + $this->updated = time(); + if (!$this->loaded()) { + // New comment + $this->created = $this->updated; + if (empty($this->state)) { + $this->state = "published"; + } + + // These values are useful for spam fighting, so save them with the comment. It's painful to + // check each one to see if it already exists before setting it, so just use server_http_host + // as a semaphore for now (we use that in g2_import.php) + if (empty($this->server_http_host)) { + $input = Input::instance(); + $this->server_http_accept = substr($input->server("HTTP_ACCEPT"), 0, 128); + $this->server_http_accept_charset = substr($input->server("HTTP_ACCEPT_CHARSET"), 0, 64); + $this->server_http_accept_encoding = substr($input->server("HTTP_ACCEPT_ENCODING"), 0, 64); + $this->server_http_accept_language = substr($input->server("HTTP_ACCEPT_LANGUAGE"), 0, 64); + $this->server_http_connection = substr($input->server("HTTP_CONNECTION"), 0, 64); + $this->server_http_host = substr($input->server("HTTP_HOST"), 0, 64); + $this->server_http_referer = substr($input->server("HTTP_REFERER"), 0, 255); + $this->server_http_user_agent = substr($input->server("HTTP_USER_AGENT"), 0, 128); + $this->server_query_string = substr($input->server("QUERY_STRING"), 0, 64); + $this->server_remote_addr = substr($input->server("REMOTE_ADDR"), 0, 40); + $this->server_remote_host = substr($input->server("REMOTE_HOST"), 0, 255); + $this->server_remote_port = substr($input->server("REMOTE_PORT"), 0, 16); + } + + $visible_change = $this->state == "published"; + parent::save(); + module::event("comment_created", $this); + } else { + // Updated comment + $original = ORM::factory("comment", $this->id); + $visible_change = $original->state == "published" || $this->state == "published"; + parent::save(); + module::event("comment_updated", $original, $this); + } + + // We only notify on the related items if we're making a visible change. + if ($visible_change) { + $item = $this->item(); + module::event("item_related_update", $item); + } + + return $this; + } + + /** + * Add a set of restrictions to any following queries to restrict access only to items + * viewable by the active user. + * @chainable + */ + public function viewable() { + $this->join("items", "items.id", "comments.item_id"); + return item::viewable($this); + } + + /** + * Make sure we have an appropriate author id set, or a guest name. + */ + public function valid_author(Validation $v, $field) { + if (empty($this->author_id)) { + $v->add_error("author_id", "required"); + } else if ($this->author_id == identity::guest()->id && empty($this->guest_name)) { + $v->add_error("guest_name", "required"); + } + } + + /** + * Make sure that the email address is legal. + */ + public function valid_email(Validation $v, $field) { + if ($this->author_id == identity::guest()->id) { + if (empty($v->guest_email)) { + $v->add_error("guest_email", "required"); + } else if (!valid::email($v->guest_email)) { + $v->add_error("guest_email", "invalid"); + } + } + } + + /** + * Make sure we have a valid associated item id. + */ + public function valid_item(Validation $v, $field) { + if (db::build() + ->from("items") + ->where("id", "=", $this->item_id) + ->count_records() != 1) { + $v->add_error("item_id", "invalid"); + } + } + + /** + * Make sure that the state is legal. + */ + static function valid_state($value) { + return in_array($value, array("published", "unpublished", "spam", "deleted")); + } + + /** + * Same as ORM::as_array() but convert id fields into their RESTful form. + */ + public function as_restful_array() { + $data = array(); + foreach ($this->as_array() as $key => $value) { + if (strncmp($key, "server_", 7)) { + $data[$key] = $value; + } + } + $data["item"] = rest::url("item", $this->item()); + unset($data["item_id"]); + + return $data; + } +} diff --git a/modules/comment/module.info b/modules/comment/module.info new file mode 100644 index 0000000..b69379f --- /dev/null +++ b/modules/comment/module.info @@ -0,0 +1,7 @@ +name = "Comments" +description = "Allows users and guests to leave comments on photos and albums." +version = 7 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:comment" +discuss_url = "http://galleryproject.org/forum_module_comment" diff --git a/modules/comment/views/admin_block_recent_comments.html.php b/modules/comment/views/admin_block_recent_comments.html.php new file mode 100644 index 0000000..4017e4f --- /dev/null +++ b/modules/comment/views/admin_block_recent_comments.html.php @@ -0,0 +1,23 @@ + +
      + +
    • "> + " + class="g-avatar" + alt="author_name()) ?>" + width="32" + height="32" /> + created) ?> + author()->guest): ?> + %comment_text', + array("author_name" => html::clean($comment->author_name()), + "comment_text" => text::limit_words(nl2br(html::purify($comment->text)), 50))); ?> + + %author_name said %comment_text', + array("author_name" => html::clean($comment->author_name()), + "url" => user_profile::url($comment->author_id), + "comment_text" => text::limit_words(nl2br(html::purify($comment->text)), 50))); ?> + +
    • + +
    diff --git a/modules/comment/views/admin_comments.html.php b/modules/comment/views/admin_comments.html.php new file mode 100644 index 0000000..dc6985b --- /dev/null +++ b/modules/comment/views/admin_comments.html.php @@ -0,0 +1,7 @@ + +
    +

    +
    + +
    +
    diff --git a/modules/comment/views/admin_manage_comments.html.php b/modules/comment/views/admin_manage_comments.html.php new file mode 100644 index 0000000..e3c8546 --- /dev/null +++ b/modules/comment/views/admin_manage_comments.html.php @@ -0,0 +1,46 @@ + + + +
    + render() ?> +
    diff --git a/modules/comment/views/admin_manage_comments_queue.html.php b/modules/comment/views/admin_manage_comments_queue.html.php new file mode 100644 index 0000000..d847d72 --- /dev/null +++ b/modules/comment/views/admin_manage_comments_queue.html.php @@ -0,0 +1,157 @@ + +
    + +
    + + 0): ?> +

    + +

    + +

    + spam): ?> + spam) ?> +

    +

    + "> + + + + + +

    +
    + + + +
    +

    + +

    +
    + + +
    + paginator() ?> +
    + + + + + + + + "> + + + + + +
    + + + + + +
    + + <?= html::clean_attribute($comment->author_name()) ?> + +

    + + author_name()) ?> + +

    +
    + +

    created) ?>

    + text)) ?> +
    +
      + state != "unpublished" && $comment->state != "deleted"): ?> +
    • + + + + +
    • + + state != "published"): ?> +
    • + + + + + + + + +
    • + + state != "spam"): ?> +
    • + + + + +
    • + + + state != "deleted"): ?> +
    • + + + + +
    • + +
    +
    + +
    + paginator() ?> +
    +
    diff --git a/modules/comment/views/comment.html.php b/modules/comment/views/comment.html.php new file mode 100644 index 0000000..263e5f9 --- /dev/null +++ b/modules/comment/views/comment.html.php @@ -0,0 +1,25 @@ + +
  • +

    + + " + class="g-avatar" + alt="author_name()) ?>" + width="40" + height="40" /> + + author()->guest): ?> + gallery::date_time($comment->created), + "name" => html::clean($comment->author_name()))) ?> + + %name said", + array("date_time" => gallery::date_time($comment->created), + "url" => user_profile::url($comment->author_id), + "name" => html::clean($comment->author_name()))) ?> + +

    +
    + text)) ?> +
    +
  • diff --git a/modules/comment/views/comment.mrss.php b/modules/comment/views/comment.mrss.php new file mode 100644 index 0000000..809e789 --- /dev/null +++ b/modules/comment/views/comment.mrss.php @@ -0,0 +1,43 @@ + +" ?> + + + Gallery 3 + <?= html::clean($feed->title) ?> + uri ?> + description) ?> + en-us + + + previous_page_uri)): ?> + + + next_page_uri)): ?> + + diff --git a/modules/comment/views/comments.html.php b/modules/comment/views/comments.html.php new file mode 100644 index 0000000..b524f5d --- /dev/null +++ b/modules/comment/views/comments.html.php @@ -0,0 +1,56 @@ + + +id}") ?>#comment-form" id="g-add-comment" + class="g-button ui-corner-all ui-icon-left ui-state-default"> + + + + + +
    + count()): ?> +

    + + comment!", + array("attrs" => html::mark_clean("href=\"" . url::site("form/add/comments/{$item->id}") . "\" class=\"showCommentForm\""))) ?> + + + +

    +
      +
    •  
    • +
    + + + count()): ?> +
      + +
    • +

      + + " + class="g-avatar" + alt="author_name()) ?>" + width="40" + height="40" /> + + author()->guest): ?> + gallery::date_time($comment->created), + "name" => html::clean($comment->author_name()))); ?> + + %name said', + array("date" => gallery::date_time($comment->created), + "url" => user_profile::url($comment->author_id), + "name" => html::clean($comment->author_name()))); ?> + +

      +
      + text)) ?> +
      +
    • + +
    + + +
    diff --git a/modules/comment/views/user_profile_comments.html.php b/modules/comment/views/user_profile_comments.html.php new file mode 100644 index 0000000..377b2d9 --- /dev/null +++ b/modules/comment/views/user_profile_comments.html.php @@ -0,0 +1,20 @@ + +
    + +
    diff --git a/modules/downloadalbum/controllers/downloadalbum.php b/modules/downloadalbum/controllers/downloadalbum.php new file mode 100644 index 0000000..f352763 --- /dev/null +++ b/modules/downloadalbum/controllers/downloadalbum.php @@ -0,0 +1,300 @@ +is_album()) { + throw new Kohana_Exception('container is not an album: '.$container->relative_path()); + } + + $zipname = (empty($container->name)) + ? 'Gallery.zip' // @todo purified_version_of($container->title).'.zip' + : $container->name.'.zip'; + break; + + case "tag": + // @todo: if the module is not installed, it crash + $container = ORM::factory("tag", $id); + if (is_null($container->name)) { + throw new Kohana_Exception('container is not a tag: '.$id); + } + + $zipname = $container->name.'.zip'; + break; + + default: + throw new Kohana_Exception('unhandled container type: '.$container_type); + } + + $files = $this->getFilesList($container); + + // Calculate ZIP size (look behind for details) + $zipsize = 22; + foreach($files as $f_name => $f_path) { + $zipsize += 76 + 2*strlen($f_name) + filesize($f_path); + } + + // Send headers + $this->prepareOutput(); + $this->sendHeaders($zipname, $zipsize); + + // Generate and send ZIP file + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT (v6.3.2) + $lfh_offset = 0; + $cds = ''; + $cds_offset = 0; + foreach($files as $f_name => $f_path) { + $f_namelen = strlen($f_name); + $f_size = filesize($f_path); + $f_mtime = $this->unix2dostime(filemtime($f_path)); + $f_crc32 = $this->fixBug45028(hexdec(hash_file('crc32b', $f_path, false))); + + // Local file header + echo pack('VvvvVVVVvva' . $f_namelen, + 0x04034b50, // local file header signature (4 bytes) + 0x0a, // version needed to extract (2 bytes) => 1.0 + 0x0800, // general purpose bit flag (2 bytes) => UTF-8 + 0x00, // compression method (2 bytes) => store + $f_mtime, // last mod file time and date (4 bytes) + $f_crc32, // crc-32 (4 bytes) + $f_size, // compressed size (4 bytes) + $f_size, // uncompressed size (4 bytes) + $f_namelen, // file name length (2 bytes) + 0, // extra field length (2 bytes) + + $f_name // file name (variable size) + // extra field (variable size) => n/a + ); + + // File data + readfile($f_path); + + // Data descriptor (n/a) + + // Central directory structure: File header + $cds .= pack('VvvvvVVVVvvvvvVVa' . $f_namelen, + 0x02014b50, // central file header signature (4 bytes) + 0x031e, // version made by (2 bytes) => v3 / Unix + 0x0a, // version needed to extract (2 bytes) => 1.0 + 0x0800, // general purpose bit flag (2 bytes) => UTF-8 + 0x00, // compression method (2 bytes) => store + $f_mtime, // last mod file time and date (4 bytes) + $f_crc32, // crc-32 (4 bytes) + $f_size, // compressed size (4 bytes) + $f_size, // uncompressed size (4 bytes) + $f_namelen, // file name length (2 bytes) + 0, // extra field length (2 bytes) + 0, // file comment length (2 bytes) + 0, // disk number start (2 bytes) + 0, // internal file attributes (2 bytes) + 0x81b40000, // external file attributes (4 bytes) => chmod 664 + $lfh_offset, // relative offset of local header (4 bytes) + + $f_name // file name (variable size) + // extra field (variable size) => n/a + // file comment (variable size) => n/a + ); + + // Update local file header/central directory structure offset + $cds_offset = $lfh_offset += 30 + $f_namelen + $f_size; + } + + // Archive decryption header (n/a) + // Archive extra data record (n/a) + + // Central directory structure: Digital signature (n/a) + echo $cds; // send Central directory structure + + // Zip64 end of central directory record (n/a) + // Zip64 end of central directory locator (n/a) + + // End of central directory record + $numfile = count($files); + $cds_len = strlen($cds); + echo pack('VvvvvVVv', + 0x06054b50, // end of central dir signature (4 bytes) + 0, // number of this disk (2 bytes) + 0, // number of the disk with the start of + // the central directory (2 bytes) + $numfile, // total number of entries in the + // central directory on this disk (2 bytes) + $numfile, // total number of entries in the + // central directory (2 bytes) + $cds_len, // size of the central directory (4 bytes) + $cds_offset, // offset of start of central directory + // with respect to the + // starting disk number (4 bytes) + 0 // .ZIP file comment length (2 bytes) + // .ZIP file comment (variable size) + ); + } + + + /** + * Return the files that must be included in the archive. + */ + private function getFilesList($container) { + $files = array(); + + if( $container instanceof Item_Model && $container->is_album() ) { + $container_realpath = realpath($container->file_path().'/../'); + + $items = $container->viewable() + ->descendants(null, null, array(array("type", "<>", "album"))); + foreach($items as $i) { + if (!access::can('view_full', $i)) { + continue; + } + + $i_realpath = realpath($i->file_path()); + if (!is_readable($i_realpath)) { + continue; + } + + $i_relative_path = str_replace($container_realpath.DIRECTORY_SEPARATOR, '', $i_realpath); + $i_relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $i_relative_path); + $files[$i_relative_path] = $i_realpath; + } + + } else if( $container instanceof Tag_Model ) { + $items = $container->items(); + foreach($items as $i) { + if (!access::can('view_full', $i)) { + continue; + } + + if( $i->is_album() ) { + foreach($this->getFilesList($i) as $f_name => $f_path) { + $files[$container->name.'/'.$f_name] = $f_path; + } + + } else { + $i_realpath = realpath($i->file_path()); + if (!is_readable($i_realpath)) { + continue; + } + + $i_relative_path = $container->name.'/'.$i->name; + $files[$i_relative_path] = $i_realpath; + } + } + } + + if (count($files) === 0) { + throw new Kohana_Exception('no zippable files in ['.$container->name.']'); + } + + return $files; + } + + + /** + * See system/helpers/download.php + */ + private function prepareOutput() { + // Close output buffers + Kohana::close_buffers(FALSE); + // Clear any output + Event::add('system.display', create_function('', 'Kohana::$output = "";')); + } + + /** + * See system/helpers/download.php + */ + private function sendHeaders($filename, $filesize = null) { + if (!is_null($filesize)) { + header('Content-Length: '.$filesize); + } + + // Retrieve MIME type by extension + $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1))); + $mime = empty($mime) ? 'application/octet-stream' : $mime[0]; + header("Content-Type: $mime"); + header('Content-Transfer-Encoding: binary'); + + // Send headers necessary to invoke a "Save As" dialog + header('Content-Disposition: attachment; filename="'.$filename.'"'); + + // Prevent caching + header('Expires: Thu, 01 Jan 1970 00:00:00 GMT'); + + $pragma = 'no-cache'; + $cachecontrol = 'no-cache, max-age=0'; + + // request::user_agent('browser') seems bugged + if (request::user_agent('browser') === 'Internet Explorer' + || stripos(request::user_agent(), 'msie') !== false + || stripos(request::user_agent(), 'internet explorer') !== false) + { + if (request::protocol() === 'https') { + // See http://support.microsoft.com/kb/323308/en-us + $pragma = 'cache'; + $cachecontrol = 'private'; + + } else if (request::user_agent('version') <= '6.0') { + $pragma = ''; + $cachecontrol = 'must-revalidate, post-check=0, pre-check=0'; + } + } + + header('Pragma: '.$pragma); + header('Cache-Control: '.$cachecontrol); + } + + /** + * @return integer DOS date and time + * @param integer _timestamp Unix timestamp + * @desc returns DOS date and time of the timestamp + */ + private function unix2dostime($timestamp) + { + $timebit = getdate($timestamp); + + if ($timebit['year'] < 1980) { + return (1 << 21 | 1 << 16); + } + + $timebit['year'] -= 1980; + + return ($timebit['year'] << 25 | $timebit['mon'] << 21 | + $timebit['mday'] << 16 | $timebit['hours'] << 11 | + $timebit['minutes'] << 5 | $timebit['seconds'] >> 1); + } + + /** + * See http://bugs.php.net/bug.php?id=45028 + */ + private function fixBug45028($hash) { + $output = $hash; + + if( version_compare(PHP_VERSION, '5.2.7', '<') ) { + $str = str_pad(dechex($hash), 8, '0', STR_PAD_LEFT); + $output = hexdec($str{6}.$str{7}.$str{4}.$str{5}.$str{2}.$str{3}.$str{0}.$str{1}); + } + + return $output; + } +} diff --git a/modules/downloadalbum/css/downloadalbum_menu.css b/modules/downloadalbum/css/downloadalbum_menu.css new file mode 100644 index 0000000..e3c4c67 --- /dev/null +++ b/modules/downloadalbum/css/downloadalbum_menu.css @@ -0,0 +1,3 @@ +#g-view-menu #g-download-album-link { + background-image: url('../images/ico-view-downloadalbum.png'); +} diff --git a/modules/downloadalbum/helpers/downloadalbum_event.php b/modules/downloadalbum/helpers/downloadalbum_event.php new file mode 100644 index 0000000..c5e875c --- /dev/null +++ b/modules/downloadalbum/helpers/downloadalbum_event.php @@ -0,0 +1,40 @@ +item->id}"); + $menu + ->append(Menu::factory("link") + ->id("downloadalbum") + ->label(t("Download Album")) + ->url($downloadLink) + ->css_id("g-download-album-link")); + } + + static function tag_menu($menu, $theme) { + $downloadLink = url::site("downloadalbum/zip/tag/{$theme->tag()->id}"); + $menu + ->append(Menu::factory("link") + ->id("downloadalbum") + ->label(t("Download Album")) + ->url($downloadLink) + ->css_id("g-download-album-link")); + } +} diff --git a/modules/downloadalbum/helpers/downloadalbum_theme.php b/modules/downloadalbum/helpers/downloadalbum_theme.php new file mode 100644 index 0000000..800ab3f --- /dev/null +++ b/modules/downloadalbum/helpers/downloadalbum_theme.php @@ -0,0 +1,24 @@ +css("downloadalbum_menu.css"); + } +} diff --git a/modules/downloadalbum/images/ico-view-downloadalbum.png b/modules/downloadalbum/images/ico-view-downloadalbum.png new file mode 100644 index 0000000..ce7804d Binary files /dev/null and b/modules/downloadalbum/images/ico-view-downloadalbum.png differ diff --git a/modules/downloadalbum/module.info b/modules/downloadalbum/module.info new file mode 100644 index 0000000..f547fd4 --- /dev/null +++ b/modules/downloadalbum/module.info @@ -0,0 +1,7 @@ +name = "DownloadAlbum" +description = "Displays a link to download a ZIP archive of the current album." +version = 2 +author_name = "" +author_url = "" +info_url = "http://codex.gallery2.org/Gallery3:Modules:downloadalbum" +discuss_url = "http://gallery.menalto.com/forum_module_downloadalbum" diff --git a/modules/downloadfullsize/controllers/admin_downloadfullsize.php b/modules/downloadfullsize/controllers/admin_downloadfullsize.php new file mode 100644 index 0000000..3befd33 --- /dev/null +++ b/modules/downloadfullsize/controllers/admin_downloadfullsize.php @@ -0,0 +1,93 @@ +content = new View("admin_downloadfullsize.html"); + $view->content->downloadlinks_form = $this->_get_admin_form(); + print $view; + } + + public function saveprefs() { + // Prevent Cross Site Request Forgery + access::verify_csrf(); + + // Figure out which boxes where checked + $dlLinks_array = Input::instance()->post("DownloadLinkOptions"); + $fButton = false; + $download_original_button = false; + for ($i = 0; $i < count($dlLinks_array); $i++) { + if ($dlLinks_array[$i] == "fButton") { + $fButton = true; + } + } + + if (module::is_active("keeporiginal")) { + $keeporiginal_array = Input::instance()->post("DownloadOriginalOptions"); + for ($i = 0; $i < count($keeporiginal_array); $i++) { + if ($keeporiginal_array[$i] == "DownloadOriginalImage") { + $download_original_button = true; + } + } + module::set_var("downloadfullsize", "DownloadOriginalImage", $download_original_button); + } + + // Save Settings. + module::set_var("downloadfullsize", "fButton", $fButton); + message::success(t("Your Selection Has Been Saved.")); + + // Load Admin page. + $view = new Admin_View("admin.html"); + $view->content = new View("admin_downloadfullsize.html"); + $view->content->downloadlinks_form = $this->_get_admin_form(); + print $view; + + } + + private function _get_admin_form() { + // Make a new Form. + $form = new Forge("admin/downloadfullsize/saveprefs", "", "post", + array("id" => "g-download-fullsize-adminForm")); + + // Make an array for the different types of download links. + $linkOptions["fButton"] = array(t("Show Floppy Disk Picture Link"), module::get_var("downloadfullsize", "fButton")); + + // Setup a few checkboxes on the form. + $add_links = $form->group("DownloadLinks"); + $add_links->checklist("DownloadLinkOptions") + ->options($linkOptions); + + if (module::is_active("keeporiginal")) { + $KeepOriginalOptions["DownloadOriginalImage"] = array(t("Allow visitors to download the original image when available?"), module::get_var("downloadfullsize", "DownloadOriginalImage")); + $keeporiginal_group = $form->group("KeepOriginalPrefs") + ->label(t("KeepOriginal Preferences")); + $keeporiginal_group->checklist("DownloadOriginalOptions") + ->options($KeepOriginalOptions); + } + + // Add a save button to the form. + $form->submit("SaveLinks")->value(t("Save")); + + // Return the newly generated form. + return $form; + } +} \ No newline at end of file diff --git a/modules/downloadfullsize/controllers/downloadfullsize.php b/modules/downloadfullsize/controllers/downloadfullsize.php new file mode 100644 index 0000000..9fa43f7 --- /dev/null +++ b/modules/downloadfullsize/controllers/downloadfullsize.php @@ -0,0 +1,37 @@ +is_photo() && module::get_var("downloadfullsize", "DownloadOriginalImage")) { + $original_image = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $item->file_path()); + if (file_exists($original_image)) { + download::force($original_image); + } else { + download::force($item->file_path()); + } + } else { + download::force($item->file_path()); + } + } +} diff --git a/modules/downloadfullsize/css/downloadfullsize_menu.css b/modules/downloadfullsize/css/downloadfullsize_menu.css new file mode 100644 index 0000000..a608a66 --- /dev/null +++ b/modules/downloadfullsize/css/downloadfullsize_menu.css @@ -0,0 +1,3 @@ +#g-view-menu #g-download-fullsize-link { + background-image: url('../images/ico-view-downloadfullsize.png'); +} diff --git a/modules/downloadfullsize/helpers/downloadfullsize_block.php b/modules/downloadfullsize/helpers/downloadfullsize_block.php new file mode 100644 index 0000000..f894442 --- /dev/null +++ b/modules/downloadfullsize/helpers/downloadfullsize_block.php @@ -0,0 +1,51 @@ + t("Download Item")); + } + + static function get($block_id, $theme) { + $item = $theme->item; + switch ($block_id) { + case "downloadfullsize": + + // If Item is movie then... + if ($item && $item->is_movie() && access::can("view_full", $item)) { + $block = new Block(); + $block->css_id = "g-download-fullsize"; + $block->title = t("Download Movie"); + $block->content = new View("downloadfullsize_block.html"); + return $block; + } + + // If Item is photo then... + if ($item && $item->is_photo() && access::can("view_full", $item)) { + $block = new Block(); + $block->css_id = "g-download-fullsize"; + $block->title = t("Download Photo"); + $block->content = new View("downloadfullsize_block.html"); + return $block; + } + } + return ""; + } + +} diff --git a/modules/downloadfullsize/helpers/downloadfullsize_event.php b/modules/downloadfullsize/helpers/downloadfullsize_event.php new file mode 100644 index 0000000..9c4b990 --- /dev/null +++ b/modules/downloadfullsize/helpers/downloadfullsize_event.php @@ -0,0 +1,57 @@ +item)) { + if (module::get_var("downloadfullsize", "fButton")) { + $downloadLink = url::site("downloadfullsize/send/{$theme->item->id}"); + $menu + ->append(Menu::factory("link") + ->id("downloadfullsize") + ->label(t("Download Fullsize Image")) + ->url($downloadLink) + ->css_id("g-download-fullsize-link")); + } + } + } + + static function movie_menu($menu, $theme) { + if (access::can("view_full", $theme->item)) { + if (module::get_var("downloadfullsize", "fButton")) { + $downloadLink = url::site("downloadfullsize/send/{$theme->item->id}"); + $menu + ->append(Menu::factory("link") + ->id("downloadfullsize") + ->label(t("Download Video")) + ->url($downloadLink) + ->css_id("g-download-fullsize-link")); + } + } + } + + static function admin_menu($menu, $theme) { + $menu->get("settings_menu") + ->append(Menu::factory("link") + ->id("downloadfullsize") + ->label(t("Download Photo Links")) + ->url(url::site("admin/downloadfullsize"))); + } + +} diff --git a/modules/downloadfullsize/helpers/downloadfullsize_theme.php b/modules/downloadfullsize/helpers/downloadfullsize_theme.php new file mode 100644 index 0000000..080ff91 --- /dev/null +++ b/modules/downloadfullsize/helpers/downloadfullsize_theme.php @@ -0,0 +1,26 @@ +item && access::can("view_full", $theme->item)) { + return $theme->css("downloadfullsize_menu.css"); + } + } +} diff --git a/modules/downloadfullsize/images/ico-view-downloadfullsize.png b/modules/downloadfullsize/images/ico-view-downloadfullsize.png new file mode 100644 index 0000000..ce7804d Binary files /dev/null and b/modules/downloadfullsize/images/ico-view-downloadfullsize.png differ diff --git a/modules/downloadfullsize/module.info b/modules/downloadfullsize/module.info new file mode 100644 index 0000000..b2cc391 --- /dev/null +++ b/modules/downloadfullsize/module.info @@ -0,0 +1,7 @@ +name = "DownloadFullsize" +description = "Displays a link to download the fullsize version of the current photo." +version = 1 +author_name = "rWatcher" +author_url = "http://codex.gallery2.org/User:RWatcher" +info_url = "http://codex.gallery2.org/Gallery3:Modules:downloadfullsize" +discuss_url = "http://gallery.menalto.com/node/103278" diff --git a/modules/downloadfullsize/views/admin_downloadfullsize.html.php b/modules/downloadfullsize/views/admin_downloadfullsize.html.php new file mode 100644 index 0000000..5dc5cef --- /dev/null +++ b/modules/downloadfullsize/views/admin_downloadfullsize.html.php @@ -0,0 +1,5 @@ + +
    +

    + +
    diff --git a/modules/downloadfullsize/views/downloadfullsize_block.html.php b/modules/downloadfullsize/views/downloadfullsize_block.html.php new file mode 100644 index 0000000..a9ccc2a --- /dev/null +++ b/modules/downloadfullsize/views/downloadfullsize_block.html.php @@ -0,0 +1,18 @@ + + +item->is_photo()) { ?> + + + +item->is_movie()) { ?> + + + diff --git a/modules/exif/controllers/exif.php b/modules/exif/controllers/exif.php new file mode 100644 index 0000000..aea8012 --- /dev/null +++ b/modules/exif/controllers/exif.php @@ -0,0 +1,33 @@ +details = exif::get($item); + + print $view; + } +} diff --git a/modules/exif/helpers/exif.php b/modules/exif/helpers/exif.php new file mode 100644 index 0000000..b17f460 --- /dev/null +++ b/modules/exif/helpers/exif.php @@ -0,0 +1,167 @@ +is_photo() && $item->mime_type == "image/jpeg") { + $data = array(); + require_once(MODPATH . "exif/lib/exif.php"); + $exif_raw = read_exif_data_raw($item->file_path(), false); + if (isset($exif_raw['ValidEXIFData'])) { + foreach(self::_keys() as $field => $exifvar) { + if (isset($exif_raw[$exifvar[0]][$exifvar[1]])) { + $value = $exif_raw[$exifvar[0]][$exifvar[1]]; + $value = encoding::convert_to_utf8($value); + $keys[$field] = Input::clean($value); + + if ($field == "DateTime") { + $time = strtotime($value); + if ($time > 0) { + $item->captured = $time; + } + } else if ($field == "Caption" && !$item->description) { + $item->description = $value; + } + } + } + } + + $size = getimagesize($item->file_path(), $info); + if (is_array($info) && !empty($info["APP13"])) { + $iptc = iptcparse($info["APP13"]); + foreach (array("Keywords" => "2#025", "Caption" => "2#120") as $keyword => $iptc_key) { + if (!empty($iptc[$iptc_key])) { + $value = implode(" ", $iptc[$iptc_key]); + $value = encoding::convert_to_utf8($value); + $keys[$keyword] = Input::clean($value); + + if ($keyword == "Caption" && !$item->description) { + $item->description = $value; + } + } + } + } + } + $item->save(); + + $record = ORM::factory("exif_record")->where("item_id", "=", $item->id)->find(); + if (!$record->loaded()) { + $record->item_id = $item->id; + } + $record->data = serialize($keys); + $record->key_count = count($keys); + $record->dirty = 0; + $record->save(); + } + + static function get($item) { + $exif = array(); + $record = ORM::factory("exif_record") + ->where("item_id", "=", $item->id) + ->find(); + if (!$record->loaded()) { + return array(); + } + + $definitions = self::_keys(); + $keys = unserialize($record->data); + foreach ($keys as $key => $value) { + $exif[] = array("caption" => $definitions[$key][2], "value" => $value); + } + + return $exif; + } + + private static function _keys() { + if (!isset(self::$exif_keys)) { + self::$exif_keys = array( + "Make" => array("IFD0", "Make", t("Camera Maker"), ), + "Model" => array("IFD0", "Model", t("Camera Model"), ), + "Aperture" => array("SubIFD", "FNumber", t("Aperture"), ), + "ColorSpace" => array("SubIFD", "ColorSpace", t("Color Space"), ), + "ExposureBias" => array("SubIFD", "ExposureBiasValue", t("Exposure Value"), ), + "ExposureProgram" => array("SubIFD", "ExposureProgram", t("Exposure Program"), ), + "ExposureTime" => array("SubIFD", "ExposureTime", t("Exposure Time"), ), + "Flash" => array("SubIFD", "Flash", t("Flash"), ), + "FocalLength" => array("SubIFD", "FocalLength", t("Focal Length"), ), + "ISO" => array("SubIFD", "ISOSpeedRatings", t("ISO"), ), + "MeteringMode" => array("SubIFD", "MeteringMode", t("Metering Mode"), ), + "DateTime" => array("SubIFD", "DateTimeOriginal", t("Date/Time"), ), + "Copyright" => array("IFD0", "Copyright", t("Copyright"), ), + "ImageType" => array("IFD0", "ImageType", t("Image Type"), ), + "Orientation" => array("IFD0", "Orientation", t("Orientation"), ), + "ResolutionUnit" => array("IFD0", "ResolutionUnit", t("Resolution Unit"), ), + "xResolution" => array("IFD0", "xResolution", t("X Resolution"), ), + "yResolution" => array("IFD0", "yResolution", t("Y Resolution"), ), + "Compression" => array("IFD1", "Compression", t("Compression"), ), + "BrightnessValue" => array("SubIFD", "BrightnessValue", t("Brightness Value"), ), + "Contrast" => array("SubIFD", "Contrast", t("Contrast"), ), + "ExposureMode" => array("SubIFD", "ExposureMode", t("Exposure Mode"), ), + "FlashEnergy" => array("SubIFD", "FlashEnergy", t("Flash Energy"), ), + "Saturation" => array("SubIFD", "Saturation", t("Saturation"), ), + "SceneType" => array("SubIFD", "SceneType", t("Scene Type"), ), + "Sharpness" => array("SubIFD", "Sharpness", t("Sharpness"), ), + "SubjectDistance" => array("SubIFD", "SubjectDistance", t("Subject Distance"), ), + "Caption" => array("IPTC", "Caption", t("Caption"), ), + "Keywords" => array("IPTC", "Keywords", t("Keywords"), ) + ); + } + return self::$exif_keys; + } + + static function stats() { + $missing_exif = db::build() + ->select("items.id") + ->from("items") + ->join("exif_records", "items.id", "exif_records.item_id", "left") + ->where("type", "=", "photo") + ->and_open() + ->where("exif_records.item_id", "IS", null) + ->or_where("exif_records.dirty", "=", 1) + ->close() + ->execute() + ->count(); + + $total_items = ORM::factory("item")->where("type", "=", "photo")->count_all(); + if (!$total_items) { + return array(0, 0, 0); + } + return array($missing_exif, $total_items, + round(100 * (($total_items - $missing_exif) / $total_items))); + } + + static function check_index() { + list ($remaining) = exif::stats(); + if ($remaining) { + site_status::warning( + t('Your Exif index needs to be updated. Fix this now', + array("url" => html::mark_clean(url::site("admin/maintenance/start/exif_task::update_index?csrf=__CSRF__")))), + "exif_index_out_of_date"); + } + } +} diff --git a/modules/exif/helpers/exif_event.php b/modules/exif/helpers/exif_event.php new file mode 100644 index 0000000..cd5068f --- /dev/null +++ b/modules/exif/helpers/exif_event.php @@ -0,0 +1,39 @@ +is_album()) { + exif::extract($item); + } + } + + static function item_updated_data_file($item) { + if (!$item->is_album()) { + exif::extract($item); + } + } + + static function item_deleted($item) { + db::build() + ->delete("exif_records") + ->where("item_id", "=", $item->id) + ->execute(); + } +} diff --git a/modules/exif/helpers/exif_installer.php b/modules/exif/helpers/exif_installer.php new file mode 100644 index 0000000..75d0f83 --- /dev/null +++ b/modules/exif/helpers/exif_installer.php @@ -0,0 +1,45 @@ +query("CREATE TABLE IF NOT EXISTS {exif_records} ( + `id` int(9) NOT NULL auto_increment, + `item_id` INTEGER(9) NOT NULL, + `key_count` INTEGER(9) default 0, + `data` TEXT, + `dirty` BOOLEAN default 1, + PRIMARY KEY (`id`), + KEY(`item_id`)) + DEFAULT CHARSET=utf8;"); + } + + static function activate() { + exif::check_index(); + } + + static function deactivate() { + site_status::clear("exif_index_out_of_date"); + } + + static function uninstall() { + Database::instance()->query("DROP TABLE IF EXISTS {exif_records};"); + } +} diff --git a/modules/exif/helpers/exif_task.php b/modules/exif/helpers/exif_task.php new file mode 100644 index 0000000..f8a108a --- /dev/null +++ b/modules/exif/helpers/exif_task.php @@ -0,0 +1,88 @@ +delete("exif_records") + ->where("item_id", "NOT IN", + db::build()->select("id")->from("items")->where("type", "=", "photo")) + ->execute(); + + list ($remaining, $total, $percent) = exif::stats(); + return array(Task_Definition::factory() + ->callback("exif_task::update_index") + ->name(t("Extract Exif data")) + ->description($remaining + ? t2("1 photo needs to be scanned", + "%count (%percent%) of your photos need to be scanned", + $remaining, array("percent" => (100 - $percent))) + : t("Exif data is up-to-date")) + ->severity($remaining ? log::WARNING : log::SUCCESS)); + } + + static function update_index($task) { + try { + $completed = $task->get("completed", 0); + + $start = microtime(true); + foreach (ORM::factory("item") + ->join("exif_records", "items.id", "exif_records.item_id", "left") + ->where("type", "=", "photo") + ->and_open() + ->where("exif_records.item_id", "IS", null) + ->or_where("exif_records.dirty", "=", 1) + ->close() + ->find_all(100) as $item) { + // The query above can take a long time, so start the timer after its done + // to give ourselves a little time to actually process rows. + if (!isset($start)) { + $start = microtime(true); + } + + exif::extract($item); + $completed++; + + if (microtime(true) - $start > .75) { + break; + } + } + + list ($remaining, $total, $percent) = exif::stats(); + $task->set("completed", $completed); + if ($remaining == 0 || !($remaining + $completed)) { + $task->done = true; + $task->state = "success"; + site_status::clear("exif_index_out_of_date"); + $task->percent_complete = 100; + } else { + $task->percent_complete = round(100 * $completed / ($remaining + $completed)); + } + $task->status = t2("one record updated, index is %percent% up-to-date", + "%count records updated, index is %percent% up-to-date", + $completed, array("percent" => $percent)); + } catch (Exception $e) { + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + $task->log((string)$e); + } + } +} diff --git a/modules/exif/helpers/exif_theme.php b/modules/exif/helpers/exif_theme.php new file mode 100644 index 0000000..df7c6f4 --- /dev/null +++ b/modules/exif/helpers/exif_theme.php @@ -0,0 +1,38 @@ +item(); + if ($item && $item->is_photo()) { + $record = db::build() + ->select("key_count") + ->from("exif_records") + ->where("item_id", "=", $item->id) + ->execute() + ->current(); + if ($record && $record->key_count) { + $view = new View("exif_sidebar.html"); + $view->item = $item; + return $view; + } + } + return null; + } +} diff --git a/modules/exif/lib/exif.php b/modules/exif/lib/exif.php new file mode 100644 index 0000000..8ba85c8 --- /dev/null +++ b/modules/exif/lib/exif.php @@ -0,0 +1,1135 @@ + 1000) { // an unreasonable length, override it. + $len = 1000; + } + for($i = 0; $i <= $len; $i += 2) { + $cache[$intel] .= substr($intel, $len-$i, 2); + } + return $cache[$intel]; +} + + +//================================================================================================ +// Looks up the name of the tag +//================================================================================================ +function lookup_tag($tag) { + switch($tag) { + // used by IFD0 'Camera Tags' + case '000b': $tag = 'ACDComment'; break; // text string up to 999 bytes long + case '00fe': $tag = 'ImageType'; break; // integer -2147483648 to 2147483647 + case '0106': $tag = 'PhotometricInterpret'; break; // ?? Please send sample image with this tag + case '010e': $tag = 'ImageDescription'; break; // text string up to 999 bytes long + case '010f': $tag = 'Make'; break; // text string up to 999 bytes long + case '0110': $tag = 'Model'; break; // text string up to 999 bytes long + case '0112': $tag = 'Orientation'; break; // integer values 1-9 + case '0115': $tag = 'SamplePerPixel'; break; // integer 0-65535 + case '011a': $tag = 'xResolution'; break; // positive rational number + case '011b': $tag = 'yResolution'; break; // positive rational number + case '011c': $tag = 'PlanarConfig'; break; // integer values 1-2 + case '0128': $tag = 'ResolutionUnit'; break; // integer values 1-3 + case '0131': $tag = 'Software'; break; // text string up to 999 bytes long + case '0132': $tag = 'DateTime'; break; // YYYY:MM:DD HH:MM:SS + case '013b': $tag = 'Artist'; break; // text string up to 999 bytes long + case '013c': $tag = 'HostComputer'; break; // text string + case '013e': $tag = 'WhitePoint'; break; // two positive rational numbers + case '013f': $tag = 'PrimaryChromaticities'; break; // six positive rational numbers + case '0211': $tag = 'YCbCrCoefficients'; break; // three positive rational numbers + case '0213': $tag = 'YCbCrPositioning'; break; // integer values 1-2 + case '0214': $tag = 'ReferenceBlackWhite'; break; // six positive rational numbers + case '8298': $tag = 'Copyright'; break; // text string up to 999 bytes long + case '8649': $tag = 'PhotoshopSettings'; break; // ?? + case '8769': $tag = 'ExifOffset'; break; // positive integer + case '8825': $tag = 'GPSInfoOffset'; break; + case '9286': $tag = 'UserCommentOld'; break; // ?? + // used by Exif SubIFD 'Image Tags' + case '829a': $tag = 'ExposureTime'; break; // seconds or fraction of seconds 1/x + case '829d': $tag = 'FNumber'; break; // positive rational number + case '8822': $tag = 'ExposureProgram'; break; // integer value 1-9 + case '8824': $tag = 'SpectralSensitivity'; break; // ?? + case '8827': $tag = 'ISOSpeedRatings'; break; // integer 0-65535 + case '9000': $tag = 'ExifVersion'; break; // ?? + case '9003': $tag = 'DateTimeOriginal'; break; // YYYY:MM:DD HH:MM:SS + case '9004': $tag = 'DateTimeDigitized'; break; // YYYY:MM:DD HH:MM:SS + case '9101': $tag = 'ComponentsConfiguration'; break; // ?? + case '9102': $tag = 'CompressedBitsPerPixel'; break; // positive rational number + case '9201': $tag = 'ShutterSpeedValue'; break; // seconds or fraction of seconds 1/x + case '9202': $tag = 'ApertureValue'; break; // positive rational number + case '9203': $tag = 'BrightnessValue'; break; // positive rational number + case '9204': $tag = 'ExposureBiasValue'; break; // positive rational number (EV) + case '9205': $tag = 'MaxApertureValue'; break; // positive rational number + case '9206': $tag = 'SubjectDistance'; break; // positive rational number (meters) + case '9207': $tag = 'MeteringMode'; break; // integer 1-6 and 255 + case '9208': $tag = 'LightSource'; break; // integer 1-255 + case '9209': $tag = 'Flash'; break; // integer 1-255 + case '920a': $tag = 'FocalLength'; break; // positive rational number (mm) + case '9213': $tag = 'ImageHistory'; break; // text string up to 999 bytes long + case '927c': $tag = 'MakerNote'; break; // a bunch of data + case '9286': $tag = 'UserComment'; break; // text string + case '9290': $tag = 'SubsecTime'; break; // text string up to 999 bytes long + case '9291': $tag = 'SubsecTimeOriginal'; break; // text string up to 999 bytes long + case '9292': $tag = 'SubsecTimeDigitized'; break; // text string up to 999 bytes long + case 'a000': $tag = 'FlashPixVersion'; break; // ?? + case 'a001': $tag = 'ColorSpace'; break; // values 1 or 65535 + case 'a002': $tag = 'ExifImageWidth'; break; // ingeter 1-65535 + case 'a003': $tag = 'ExifImageHeight'; break; // ingeter 1-65535 + case 'a004': $tag = 'RelatedSoundFile'; break; // text string 12 bytes long + case 'a005': $tag = 'ExifInteroperabilityOffset'; break; // positive integer + case 'a20c': $tag = 'SpacialFreqResponse'; break; // ?? + case 'a20b': $tag = 'FlashEnergy'; break; // positive rational number + case 'a20e': $tag = 'FocalPlaneXResolution'; break; // positive rational number + case 'a20f': $tag = 'FocalPlaneYResolution'; break; // positive rational number + case 'a210': $tag = 'FocalPlaneResolutionUnit'; break; // values 1-3 + case 'a214': $tag = 'SubjectLocation'; break; // two integers 0-65535 + case 'a215': $tag = 'ExposureIndex'; break; // positive rational number + case 'a217': $tag = 'SensingMethod'; break; // values 1-8 + case 'a300': $tag = 'FileSource'; break; // integer + case 'a301': $tag = 'SceneType'; break; // integer + case 'a302': $tag = 'CFAPattern'; break; // undefined data type + case 'a401': $tag = 'CustomerRender'; break; // values 0 or 1 + case 'a402': $tag = 'ExposureMode'; break; // values 0-2 + case 'a403': $tag = 'WhiteBalance'; break; // values 0 or 1 + case 'a404': $tag = 'DigitalZoomRatio'; break; // positive rational number + case 'a405': $tag = 'FocalLengthIn35mmFilm'; break; + case 'a406': $tag = 'SceneCaptureMode'; break; // values 0-3 + case 'a407': $tag = 'GainControl'; break; // values 0-4 + case 'a408': $tag = 'Contrast'; break; // values 0-2 + case 'a409': $tag = 'Saturation'; break; // values 0-2 + case 'a40a': $tag = 'Sharpness'; break; // values 0-2 + + // used by Interoperability IFD + case '0001': $tag = 'InteroperabilityIndex'; break; // text string 3 bytes long + case '0002': $tag = 'InteroperabilityVersion'; break; // datatype undefined + case '1000': $tag = 'RelatedImageFileFormat'; break; // text string up to 999 bytes long + case '1001': $tag = 'RelatedImageWidth'; break; // integer in range 0-65535 + case '1002': $tag = 'RelatedImageLength'; break; // integer in range 0-65535 + + // used by IFD1 'Thumbnail' + case '0100': $tag = 'ImageWidth'; break; // integer in range 0-65535 + case '0101': $tag = 'ImageLength'; break; // integer in range 0-65535 + case '0102': $tag = 'BitsPerSample'; break; // integers in range 0-65535 + case '0103': $tag = 'Compression'; break; // values 1 or 6 + case '0106': $tag = 'PhotometricInterpretation'; break;// values 0-4 + case '010e': $tag = 'ThumbnailDescription'; break; // text string up to 999 bytes long + case '010f': $tag = 'ThumbnailMake'; break; // text string up to 999 bytes long + case '0110': $tag = 'ThumbnailModel'; break; // text string up to 999 bytes long + case '0111': $tag = 'StripOffsets'; break; // ?? + case '0112': $tag = 'ThumbnailOrientation'; break; // integer 1-9 + case '0115': $tag = 'SamplesPerPixel'; break; // ?? + case '0116': $tag = 'RowsPerStrip'; break; // ?? + case '0117': $tag = 'StripByteCounts'; break; // ?? + case '011a': $tag = 'ThumbnailXResolution'; break; // positive rational number + case '011b': $tag = 'ThumbnailYResolution'; break; // positive rational number + case '011c': $tag = 'PlanarConfiguration'; break; // values 1 or 2 + case '0128': $tag = 'ThumbnailResolutionUnit'; break; // values 1-3 + case '0201': $tag = 'JpegIFOffset'; break; + case '0202': $tag = 'JpegIFByteCount'; break; + case '0212': $tag = 'YCbCrSubSampling'; break; + + // misc + case '00ff': $tag = 'SubfileType'; break; + case '012d': $tag = 'TransferFunction'; break; + case '013d': $tag = 'Predictor'; break; + case '0142': $tag = 'TileWidth'; break; + case '0143': $tag = 'TileLength'; break; + case '0144': $tag = 'TileOffsets'; break; + case '0145': $tag = 'TileByteCounts'; break; + case '014a': $tag = 'SubIFDs'; break; + case '015b': $tag = 'JPEGTables'; break; + case '828d': $tag = 'CFARepeatPatternDim'; break; + case '828e': $tag = 'CFAPattern'; break; + case '828f': $tag = 'BatteryLevel'; break; + case '83bb': $tag = 'IPTC/NAA'; break; + case '8773': $tag = 'InterColorProfile'; break; + + case '8828': $tag = 'OECF'; break; + case '8829': $tag = 'Interlace'; break; + case '882a': $tag = 'TimeZoneOffset'; break; + case '882b': $tag = 'SelfTimerMode'; break; + case '920b': $tag = 'FlashEnergy'; break; + case '920c': $tag = 'SpatialFrequencyResponse'; break; + case '920d': $tag = 'Noise'; break; + case '9211': $tag = 'ImageNumber'; break; + case '9212': $tag = 'SecurityClassification'; break; + case '9214': $tag = 'SubjectLocation'; break; + case '9215': $tag = 'ExposureIndex'; break; + case '9216': $tag = 'TIFF/EPStandardID'; break; + case 'a20b': $tag = 'FlashEnergy'; break; + + default: $tag = 'unknown:'.$tag; break; + } + return $tag; + +} + + +//================================================================================================ +// Looks up the datatype +//================================================================================================ +function lookup_type(&$type,&$size) { + switch($type) { + case '0001': $type = 'UBYTE'; $size=1; break; + case '0002': $type = 'ASCII'; $size=1; break; + case '0003': $type = 'USHORT'; $size=2; break; + case '0004': $type = 'ULONG'; $size=4; break; + case '0005': $type = 'URATIONAL'; $size=8; break; + case '0006': $type = 'SBYTE'; $size=1; break; + case '0007': $type = 'UNDEFINED'; $size=1; break; + case '0008': $type = 'SSHORT'; $size=2; break; + case '0009': $type = 'SLONG'; $size=4; break; + case '000a': $type = 'SRATIONAL'; $size=8; break; + case '000b': $type = 'FLOAT'; $size=4; break; + case '000c': $type = 'DOUBLE'; $size=8; break; + default: $type = 'error:'.$type; $size=0; break; + } + return $type; +} + +//================================================================================================ +// processes a irrational number +//================================================================================================ +function unRational($data, $type, $intel) { + $data = bin2hex($data); + if ($intel == 1) { + $data = intel2Moto($data); + $top = hexdec(substr($data,8,8)); // intel stores them bottom-top + $bottom = hexdec(substr($data,0,8)); // intel stores them bottom-top + } else { + $top = hexdec(substr($data,0,8)); // motorola stores them top-bottom + $bottom = hexdec(substr($data,8,8)); // motorola stores them top-bottom + } + + if ($type == 'SRATIONAL' && $top > 2147483647) $top = $top - 4294967296; // this makes the number signed instead of unsigned + if ($bottom != 0) + $data=$top/$bottom; + else + if ($top == 0) + $data = 0; + else + $data = $top.'/'.$bottom; + return $data; +} + +//================================================================================================ +// processes a rational number +//================================================================================================ +function rational($data,$type,$intel) { + if (($type == 'USHORT' || $type == 'SSHORT')) { + $data = substr($data,0,2); + } + $data = bin2hex($data); + if ($intel == 1) { + $data = intel2Moto($data); + } + $data = hexdec($data); + if ($type == 'SSHORT' && $data > 32767) $data = $data - 65536; // this makes the number signed instead of unsigned + if ($type == 'SLONG' && $data > 2147483647) $data = $data - 4294967296; // this makes the number signed instead of unsigned + return $data; +} + +//================================================================================================ +// Formats Data for the data type +//================================================================================================ +function formatData($type,$tag,$intel,$data) { + switch ($type) { + case 'ASCII': + if (($pos = strpos($data, chr(0))) !== false) { // Search for a null byte and stop there. + $data = substr($data, 0, $pos); + } + if ($tag == '010f') $data = ucwords(strtolower(trim($data))); // Format certain kinds of strings nicely (Camera make etc.) + break; + case 'URATIONAL': + case 'SRATIONAL': + switch ($tag) { + case '011a': // XResolution + case '011b': // YResolution + $data = round(unRational($data,$type,$intel)).' dots per ResolutionUnit'; + break; + case '829a': // Exposure Time + $data = formatExposure(unRational($data,$type,$intel)); + break; + case '829d': // FNumber + $data = 'f/'.unRational($data,$type,$intel); + break; + case '9204': // ExposureBiasValue + $data = round(unRational($data,$type,$intel), 2) . ' EV'; + break; + case '9205': // ApertureValue + case '9202': // MaxApertureValue + // ApertureValue is given in the APEX Mode. Many thanks to Matthieu Froment for this code + // The formula is : Aperture = 2*log2(FNumber) <=> FNumber = e((Aperture.ln(2))/2) + $datum = exp((unRational($data,$type,$intel)*log(2))/2); + $data = round($datum, 1);// Focal is given with a precision of 1 digit. + $data='f/'.$datum; + break; + case '920a': // FocalLength + $data = unRational($data,$type,$intel).' mm'; + break; + case '9201': // ShutterSpeedValue + // The ShutterSpeedValue is given in the APEX mode. Many thanks to Matthieu Froment for this code + // The formula is : Shutter = - log2(exposureTime) (Appendix C of EXIF spec.) + // Where shutter is in APEX, log2(exposure) = ln(exposure)/ln(2) + // So final formula is : exposure = exp(-ln(2).shutter) + // The formula can be developed : exposure = 1/(exp(ln(2).shutter)) + $datum = exp(unRational($data,$type,$intel) * log(2)); + if ($datum != 0) $datum = 1/$datum; + $data = formatExposure($datum); + break; + default: + $data = unRational($data,$type,$intel); + break; + } + break; + case 'USHORT': + case 'SSHORT': + case 'ULONG': + case 'SLONG': + case 'FLOAT': + case 'DOUBLE': + $data = rational($data,$type,$intel); + switch ($tag) { + case '0112': // Orientation + // Example of how all of these tag formatters should be... + switch ($data) { + case 0 : // not set, presume normal + case 1 : $data = (string) t('1: Normal (0 deg)'); break; + case 2 : $data = (string) t('2: Mirrored'); break; + case 3 : $data = (string) t('3: Upside-down'); break; + case 4 : $data = (string) t('4: Upside-down Mirrored'); break; + case 5 : $data = (string) t('5: 90 deg CW Mirrored'); break; + case 6 : $data = (string) t('6: 90 deg CCW'); break; + case 7 : $data = (string) t('7: 90 deg CCW Mirrored'); break; + case 8 : $data = (string) t('8: 90 deg CW'); break; + default : $data = sprintf((string) t('%d: Unknown'),$data); break; + } + break; + case '0128': // ResolutionUnit + case 'a210': // FocalPlaneResolutionUnit + case '0128': // ThumbnailResolutionUnit + switch ($data) { + case 1: $data = (string) t('No Unit'); break; + case 2: $data = (string) t('Inch'); break; + case 3: $data = (string) t('Centimeter'); break; + } + break; + case '0213': // YCbCrPositioning + switch ($data) { + case 1: $data = (string) t('Center of Pixel Array'); break; + case 2: $data = (string) t('Datum Point'); break; + } + break; + case '8822': // ExposureProgram + switch ($data) { + case 1: $data = (string) t('Manual'); break; + case 2: $data = (string) t('Program'); break; + case 3: $data = (string) t('Aperture Priority'); break; + case 4: $data = (string) t('Shutter Priority'); break; + case 5: $data = (string) t('Program Creative'); break; + case 6: $data = (string) t('Program Action'); break; + case 7: $data = (string) t('Portrait'); break; + case 8: $data = (string) t('Landscape'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + case '9207': // MeteringMode + switch ($data) { + case 1: $data = (string) t('Average'); break; + case 2: $data = (string) t('Center Weighted Average'); break; + case 3: $data = (string) t('Spot'); break; + case 4: $data = (string) t('Multi-Spot'); break; + case 5: $data = (string) t('Pattern'); break; + case 6: $data = (string) t('Partial'); break; + case 255: $data = (string) t('Other'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + case '9208': // LightSource + switch ($data) { + case 1: $data = (string) t('Daylight'); break; + case 2: $data = (string) t('Fluorescent'); break; + case 3: $data = (string) t('Tungsten'); break; // 3 Tungsten (Incandescent light) + // 4 Flash + // 9 Fine Weather + case 10: $data = (string) t('Flash'); break; // 10 Cloudy Weather + // 11 Shade + // 12 Daylight Fluorescent (D 5700 - 7100K) + // 13 Day White Fluorescent (N 4600 - 5400K) + // 14 Cool White Fluorescent (W 3900 -4500K) + // 15 White Fluorescent (WW 3200 - 3700K) + // 10 Flash + case 17: $data = (string) t('Standard Light A'); break; + case 18: $data = (string) t('Standard Light B'); break; + case 19: $data = (string) t('Standard Light C'); break; + case 20: $data = (string) t('D55'); break; + case 21: $data = (string) t('D65'); break; + case 22: $data = (string) t('D75'); break; + case 23: $data = (string) t('D50'); break; + case 24: $data = (string) t('ISO Studio Tungsten'); break; + case 255: $data = (string) t('Other'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + case '9209': // Flash + switch ($data) { + case 0: $data = (string) t('No Flash'); break; + case 1: $data = (string) t('Flash'); break; + case 5: $data = (string) t('Flash, strobe return light not detected'); break; + case 7: $data = (string) t('Flash, strobe return light detected'); break; + case 9: $data = (string) t('Compulsory Flash'); break; + case 13: $data = (string) t('Compulsory Flash, Return light not detected'); break; + case 15: $data = (string) t('Compulsory Flash, Return light detected'); break; + case 16: $data = (string) t('No Flash'); break; + case 24: $data = (string) t('No Flash'); break; + case 25: $data = (string) t('Flash, Auto-Mode'); break; + case 29: $data = (string) t('Flash, Auto-Mode, Return light not detected'); break; + case 31: $data = (string) t('Flash, Auto-Mode, Return light detected'); break; + case 32: $data = (string) t('No Flash'); break; + case 65: $data = (string) t('Red Eye'); break; + case 69: $data = (string) t('Red Eye, Return light not detected'); break; + case 71: $data = (string) t('Red Eye, Return light detected'); break; + case 73: $data = (string) t('Red Eye, Compulsory Flash'); break; + case 77: $data = (string) t('Red Eye, Compulsory Flash, Return light not detected'); break; + case 79: $data = (string) t('Red Eye, Compulsory Flash, Return light detected'); break; + case 89: $data = (string) t('Red Eye, Auto-Mode'); break; + case 93: $data = (string) t('Red Eye, Auto-Mode, Return light not detected'); break; + case 95: $data = (string) t('Red Eye, Auto-Mode, Return light detected'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + case 'a001': // ColorSpace + if ($data == 1) $data = (string) t('sRGB'); + else $data = (string) t('Uncalibrated'); + break; + case 'a002': // ExifImageWidth + case 'a003': // ExifImageHeight + $data = $data. ' '.(string) t('pixels'); + break; + case '0103': // Compression + switch ($data) { + case 1: $data = (string) t('No Compression'); break; + case 6: $data = (string) t('Jpeg Compression'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + case 'a217': // SensingMethod + switch ($data) { + case 1: $data = (string) t('Not defined'); break; + case 2: $data = (string) t('One Chip Color Area Sensor'); break; + case 3: $data = (string) t('Two Chip Color Area Sensor'); break; + case 4: $data = (string) t('Three Chip Color Area Sensor'); break; + case 5: $data = (string) t('Color Sequential Area Sensor'); break; + case 7: $data = (string) t('Trilinear Sensor'); break; + case 8: $data = (string) t('Color Sequential Linear Sensor'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + case '0106': // PhotometricInterpretation + switch ($data) { + case 1: $data = (string) t('Monochrome'); break; + case 2: $data = (string) t('RGB'); break; + case 6: $data = (string) t('YCbCr'); break; + default: $data = (string) t('Unknown').': '.$data; break; + } + break; + //case "a408": // Contrast + //case "a40a": //Sharpness + // switch($data) { + // case 0: $data="Normal"; break; + // case 1: $data="Soft"; break; + // case 2: $data="Hard"; break; + // default: $data="Unknown"; break; + // } + // break; + //case "a409": // Saturation + // switch($data) { + // case 0: $data="Normal"; break; + // case 1: $data="Low saturation"; break; + // case 2: $data="High saturation"; break; + // default: $data="Unknown"; break; + // } + // break; + //case "a402": // Exposure Mode + // switch($data) { + // case 0: $data="Auto exposure"; break; + // case 1: $data="Manual exposure"; break; + // case 2: $data="Auto bracket"; break; + // default: $data="Unknown"; break; + // } + // break; + } + break; + case 'UNDEFINED': + switch ($tag) { + case '9000': // ExifVersion + case 'a000': // FlashPixVersion + case '0002': // InteroperabilityVersion + $data=(string) t('version').' '.$data/100; + break; + case 'a300': // FileSource + $data = bin2hex($data); + $data = str_replace('00','',$data); + $data = str_replace('03',(string) t('Digital Still Camera'),$data); + break; + case 'a301': // SceneType + $data = bin2hex($data); + $data = str_replace('00','',$data); + $data = str_replace('01',(string) t('Directly Photographed'),$data); + break; + case '9101': // ComponentsConfiguration + $data = bin2hex($data); + $data = str_replace('01','Y',$data); + $data = str_replace('02','Cb',$data); + $data = str_replace('03','Cr',$data); + $data = str_replace('04','R',$data); + $data = str_replace('05','G',$data); + $data = str_replace('06','B',$data); + $data = str_replace('00','',$data); + break; + //case "9286": //UserComment + // $encoding = rtrim(substr($data, 0, 8)); + // $data = rtrim(substr($data, 8)); + // break; + } + break; + default: + $data = bin2hex($data); + if ($intel == 1) $data = intel2Moto($data); + break; + } + return $data; +} + +function formatExposure($data) { + if (strpos($data,'/')===false) { + if ($data > 1) { + return round($data, 2).' '.(string) t('sec'); + } else { + $n=0; $d=0; + ConvertToFraction($data, $n, $d); + return $n.'/'.$d.' '.(string) t('sec'); + } + } else { + return (string) t('Bulb'); + } +} + +//================================================================================================ +// Reads one standard IFD entry +//================================================================================================ +function read_entry(&$result,$in,$seek,$intel,$ifd_name,$globalOffset) { + + if (feof($in)) { // test to make sure we can still read. + $result['Errors'] = $result['Errors']+1; + return; + } + + // 2 byte tag + $tag = bin2hex(fread($in, 2)); + if ($intel == 1) $tag = intel2Moto($tag); + $tag_name = lookup_tag($tag); + + // 2 byte datatype + $type = bin2hex(fread($in, 2)); + if ($intel == 1) $type = intel2Moto($type); + lookup_type($type, $size); + + if (strpos($tag_name, 'unknown:') !== false && strpos($type, 'error:') !== false) { // we have an error + $result['Errors'] = $result['Errors']+1; + return; + } + + // 4 byte number of elements + $count = bin2hex(fread($in, 4)); + if ($intel == 1) $count = intel2Moto($count); + $bytesofdata = $size*hexdec($count); + + // 4 byte value or pointer to value if larger than 4 bytes + $value = fread( $in, 4 ); + + if ($bytesofdata <= 4) { // if datatype is 4 bytes or less, its the value + $data = $value; + } else if ($bytesofdata < 100000) { // otherwise its a pointer to the value, so lets go get it + $value = bin2hex($value); + if ($intel == 1) $value = intel2Moto($value); + $v = fseek($seek, $globalOffset+hexdec($value)); // offsets are from TIFF header which is 12 bytes from the start of the file + if ($v == 0) { + $data = fread($seek, $bytesofdata); + } else if ($v == -1) { + $result['Errors'] = $result['Errors']+1; + } + } else { // bytesofdata was too big, so the exif had an error + $result['Errors'] = $result['Errors']+1; + return; + } + if ($tag_name == 'MakerNote') { // if its a maker tag, we need to parse this specially + $make = $result['IFD0']['Make']; + + if ($result['VerboseOutput'] == 1) { + $result[$ifd_name]['MakerNote']['RawData'] = $data; + } + if (preg_match('/NIKON/i',$make)) { + require_once(dirname(__FILE__).'/makers/nikon.php'); + parseNikon($data,$result); + $result[$ifd_name]['KnownMaker'] = 1; + } else if (preg_match('/OLYMPUS/i',$make)) { + require_once(dirname(__FILE__).'/makers/olympus.php'); + parseOlympus($data,$result,$seek,$globalOffset); + $result[$ifd_name]['KnownMaker'] = 1; + } else if (preg_match('/Canon/i',$make)) { + require_once(dirname(__FILE__).'/makers/canon.php'); + parseCanon($data,$result,$seek,$globalOffset); + $result[$ifd_name]['KnownMaker'] = 1; + } else if (preg_match('/FUJIFILM/i',$make)) { + require_once(dirname(__FILE__).'/makers/fujifilm.php'); + parseFujifilm($data,$result); + $result[$ifd_name]['KnownMaker'] = 1; + } else if (preg_match('/SANYO/i',$make)) { + require_once(dirname(__FILE__).'/makers/sanyo.php'); + parseSanyo($data,$result,$seek,$globalOffset); + $result[$ifd_name]['KnownMaker'] = 1; + } else if (preg_match('/Panasonic/i',$make)) { + require_once(dirname(__FILE__).'/makers/panasonic.php'); + parsePanasonic($data,$result,$seek,$globalOffset); + $result[$ifd_name]['KnownMaker'] = 1; + } else { + $result[$ifd_name]['KnownMaker'] = 0; + } + } else if ($tag_name == 'GPSInfoOffset') { + require_once(dirname(__FILE__).'/makers/gps.php'); + $formated_data = formatData($type,$tag,$intel,$data); + $result[$ifd_name]['GPSInfo'] = $formated_data; + parseGPS($data,$result,$formated_data,$seek,$globalOffset); + } else { + // Format the data depending on the type and tag + $formated_data = formatData($type,$tag,$intel,$data); + + $result[$ifd_name][$tag_name] = $formated_data; + + if ($result['VerboseOutput'] == 1) { + if ($type == 'URATIONAL' || $type == 'SRATIONAL' || $type == 'USHORT' || $type == 'SSHORT' || $type == 'ULONG' || $type == 'SLONG' || $type == 'FLOAT' || $type == 'DOUBLE') { + $data = bin2hex($data); + if ($intel == 1) $data = intel2Moto($data); + } + $result[$ifd_name][$tag_name.'_Verbose']['RawData'] = $data; + $result[$ifd_name][$tag_name.'_Verbose']['Type'] = $type; + $result[$ifd_name][$tag_name.'_Verbose']['Bytes'] = $bytesofdata; + } + } +} + + +//================================================================================================ +// Pass in a file and this reads the EXIF data +// +// Usefull resources +// http:// www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html +// http:// www.w3.org/Graphics/JPEG/jfif.txt +// http:// exif.org/ +// http:// www.ozhiker.com/electronics/pjmt/library/list_contents.php4 +// http:// www.ozhiker.com/electronics/pjmt/jpeg_info/makernotes.html +// http:// pel.sourceforge.net/ +// http:// us2.php.net/manual/en/function.exif-read-data.php +//================================================================================================ +function read_exif_data_raw($path,$verbose) { + + if ($path == '' || $path == 'none') return; + + $in = @fopen($path, 'rb'); // the b is for windows machines to open in binary mode + $seek = @fopen($path, 'rb'); // There may be an elegant way to do this with one file handle. + + $globalOffset = 0; + + if (!isset($verbose)) $verbose=0; + + $result['VerboseOutput'] = $verbose; + $result['Errors'] = 0; + + if (!$in || !$seek) { // if the path was invalid, this error will catch it + $result['Errors'] = 1; + $result['Error'][$result['Errors']] = (string) t('The file could not be found.'); + return $result; + } + + $GLOBALS['exiferFileSize'] = filesize($path); + + // First 2 bytes of JPEG are 0xFFD8 + $data = bin2hex(fread( $in, 2 )); + if ($data == 'ffd8') { + $result['ValidJpeg'] = 1; + } else { + $result['ValidJpeg'] = 0; + fseek($in, 0); + } + + $result['ValidIPTCData'] = 0; + $result['ValidJFIFData'] = 0; + $result['ValidEXIFData'] = 0; + $result['ValidAPP2Data'] = 0; + $result['ValidCOMData'] = 0; + +if ($result['ValidJpeg'] == 1) { + // Next 2 bytes are MARKER tag (0xFFE#) + $data = bin2hex(fread( $in, 2 )); + $size = bin2hex(fread( $in, 2 )); + + // LOOP THROUGH MARKERS TILL YOU GET TO FFE1 (exif marker) + $abortCount = 0; + while(!feof($in) && $data!='ffe1' && $data!='ffc0' && $data!='ffd9' && ++$abortCount < 200) { + if ($data == 'ffe0') { // JFIF Marker + $result['ValidJFIFData'] = 1; + $result['JFIF']['Size'] = hexdec($size); + + if (hexdec($size)-2 > 0) { + $data = fread( $in, hexdec($size)-2); + $result['JFIF']['Data'] = $data; + } + + $result['JFIF']['Identifier'] = substr($data,0,5);; + $result['JFIF']['ExtensionCode'] = bin2hex(substr($data,6,1)); + + $globalOffset+=hexdec($size)+2; + + } else if ($data == 'ffed') { // IPTC Marker + $result['ValidIPTCData'] = 1; + $result['IPTC']['Size'] = hexdec($size); + + if (hexdec($size)-2 > 0) { + $data = fread( $in, hexdec($size)-2); + $result['IPTC']['Data'] = $data ; + } + $globalOffset+=hexdec($size)+2; + + } else if ($data == 'ffe2') { // EXIF extension Marker + $result['ValidAPP2Data'] = 1; + $result['APP2']['Size'] = hexdec($size); + + if (hexdec($size)-2 > 0) { + $data = fread( $in, hexdec($size)-2); + $result['APP2']['Data'] = $data ; + } + $globalOffset+=hexdec($size)+2; + + } else if ($data == 'fffe') { // COM extension Marker + $result['ValidCOMData'] = 1; + $result['COM']['Size'] = hexdec($size); + + if (hexdec($size)-2 > 0) { + $data = fread( $in, hexdec($size)-2); + $result['COM']['Data'] = $data ; + } + $globalOffset+=hexdec($size)+2; + + } else if ($data == 'ffe1') { + $result['ValidEXIFData'] = 1; + } + + $data = bin2hex(fread( $in, 2 )); + $size = bin2hex(fread( $in, 2 )); + } + // END MARKER LOOP + + if ($data == 'ffe1') { + $result['ValidEXIFData'] = 1; + } else { + fclose($in); + fclose($seek); + return $result; + } + + // Size of APP1 + $result['APP1Size'] = hexdec($size); + + // Start of APP1 block starts with 'Exif' header (6 bytes) + $header = fread( $in, 6 ); + +} // END IF ValidJpeg + + // Then theres a TIFF header with 2 bytes of endieness (II or MM) + $header = fread( $in, 2 ); + if ($header==='II') { + $intel=1; + $result['Endien'] = 'Intel'; + } else if ($header==='MM') { + $intel=0; + $result['Endien'] = 'Motorola'; + } else { + $intel=1; // not sure what the default should be, but this seems reasonable + $result['Endien'] = 'Unknown'; + } + + // 2 bytes of 0x002a + $tag = bin2hex(fread( $in, 2 )); + + // Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header) + $offset = bin2hex(fread( $in, 4 )); + if ($intel == 1) $offset = intel2Moto($offset); + + // Check for extremely large values here + if (hexdec($offset) > 100000) { + $result['ValidEXIFData'] = 0; + fclose($in); + fclose($seek); + return $result; + } + + if (hexdec($offset)>8) $unknown = fread( $in, hexdec($offset)-8); // fixed this bug in 1.3 + + // add 12 to the offset to account for TIFF header + if ($result['ValidJpeg'] == 1) { + $globalOffset+=12; + } + + + //=========================================================== + // Start of IFD0 + $num = bin2hex(fread( $in, 2 )); + if ($intel == 1) $num = intel2Moto($num); + $num = hexdec($num); + $result['IFD0NumTags'] = $num; + + if ($num<1000) { // 1000 entries is too much and is probably an error. + for($i=0; $i<$num; $i++) { + read_entry($result,$in,$seek,$intel,'IFD0',$globalOffset); + } + } else { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = 'Illegal size for IFD0'; + } + + // store offset to IFD1 + $offset = bin2hex(fread( $in, 4 )); + if ($intel == 1) $offset = intel2Moto($offset); + $result['IFD1Offset'] = hexdec($offset); + + // Check for SubIFD + if (!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset'] == 0) { + fclose($in); + fclose($seek); + return $result; + } + + // seek to SubIFD (Value of ExifOffset tag) above. + $ExitOffset = $result['IFD0']['ExifOffset']; + $v = fseek($in,$globalOffset+$ExitOffset); + if ($v == -1) { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = (string) t('Could not Find SubIFD'); + } + + //=========================================================== + // Start of SubIFD + $num = bin2hex(fread( $in, 2 )); + if ($intel == 1) $num = intel2Moto($num); + $num = hexdec($num); + $result['SubIFDNumTags'] = $num; + + if ($num<1000) { // 1000 entries is too much and is probably an error. + for($i=0; $i<$num; $i++) { + read_entry($result,$in,$seek,$intel,'SubIFD',$globalOffset); + } + } else { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = (string) t('Illegal size for SubIFD'); + } + + // Add the 35mm equivalent focal length: + if (isset($result['IFD0']['FocalLengthIn35mmFilm']) && !isset($result['SubIFD']['FocalLengthIn35mmFilm'])) { // found in the wrong place + $result['SubIFD']['FocalLengthIn35mmFilm'] = $result['IFD0']['FocalLengthIn35mmFilm']; + } + if (!isset($result['SubIFD']['FocalLengthIn35mmFilm'])) { + $result['SubIFD']['FocalLengthIn35mmFilm'] = get35mmEquivFocalLength($result); + } + + // Check for IFD1 + if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) { + fclose($in); + fclose($seek); + return $result; + } + // seek to IFD1 + $v = fseek($in,$globalOffset+$result['IFD1Offset']); + if ($v == -1) { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = (string) t('Could not Find IFD1'); + } + + //=========================================================== + // Start of IFD1 + $num = bin2hex(fread( $in, 2 )); + if ($intel == 1) $num = intel2Moto($num); + $num = hexdec($num); + $result['IFD1NumTags'] = $num; + + if ($num<1000) { // 1000 entries is too much and is probably an error. + for($i=0; $i<$num; $i++) { + read_entry($result,$in,$seek,$intel,'IFD1',$globalOffset); + } + } else { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = (string) t('Illegal size for IFD1'); + } + // If verbose output is on, include the thumbnail raw data... + if ($result['VerboseOutput'] == 1 && $result['IFD1']['JpegIFOffset']>0 && $result['IFD1']['JpegIFByteCount']>0) { + $v = fseek($seek,$globalOffset+$result['IFD1']['JpegIFOffset']); + if ($v == 0) { + $data = fread($seek, $result['IFD1']['JpegIFByteCount']); + } else if ($v == -1) { + $result['Errors'] = $result['Errors']+1; + } + $result['IFD1']['ThumbnailData'] = $data; + } + + + // Check for Interoperability IFD + if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset'] == 0) { + fclose($in); + fclose($seek); + return $result; + } + // Seek to InteroperabilityIFD + $v = fseek($in,$globalOffset+$result['SubIFD']['ExifInteroperabilityOffset']); + if ($v == -1) { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = (string) t('Could not Find InteroperabilityIFD'); + } + + //=========================================================== + // Start of InteroperabilityIFD + $num = bin2hex(fread( $in, 2 )); + if ($intel == 1) $num = intel2Moto($num); + $num = hexdec($num); + $result['InteroperabilityIFDNumTags'] = $num; + + if ($num<1000) { // 1000 entries is too much and is probably an error. + for($i=0; $i<$num; $i++) { + read_entry($result,$in,$seek,$intel,'InteroperabilityIFD',$globalOffset); + } + } else { + $result['Errors'] = $result['Errors']+1; + $result['Error'][$result['Errors']] = (string) t('Illegal size for InteroperabilityIFD'); + } + fclose($in); + fclose($seek); + return $result; +} + +//================================================================================================ +// Converts a floating point number into a fraction. Many thanks to Matthieu Froment for this code +//================================================================================================ +function ConvertToFraction($v, &$n, &$d) +{ + if ($v == 0) { + $n = 0; + $d = 1; + return; + } + for ($n=1; $n<100; $n++) { + $v1 = 1/$v*$n; + $d = round($v1, 0); + if (abs($d - $v1) < 0.02) return;// within tolarance + } +} + +//================================================================================================ +// Calculates the 35mm-equivalent focal length from the reported sensor resolution, by Tristan Harward. +//================================================================================================ +function get35mmEquivFocalLength(&$result) { + if (isset($result['SubIFD']['ExifImageWidth'])) { + $width = $result['SubIFD']['ExifImageWidth']; + } else { + $width = 0; + } + if (isset($result['SubIFD']['FocalPlaneResolutionUnit'])) { + $units = $result['SubIFD']['FocalPlaneResolutionUnit']; + } else { + $units = ''; + } + $unitfactor = 1; + switch ($units) { + case 'Inch' : $unitfactor = 25.4; break; + case 'Centimeter' : $unitfactor = 10; break; + case 'No Unit' : $unitfactor = 25.4; break; + default : $unitfactor = 25.4; + } + if (isset($result['SubIFD']['FocalPlaneXResolution'])) { + $xres = $result['SubIFD']['FocalPlaneXResolution']; + } else { + $xres = ''; + } + if (isset($result['SubIFD']['FocalLength'])) { + $fl = $result['SubIFD']['FocalLength']; + } else { + $fl = 0; + } + + if (($width != 0) && !empty($units) && !empty($xres) && !empty($fl) && !empty($width)) { + $ccdwidth = ($width * $unitfactor) / $xres; + $equivfl = $fl / $ccdwidth*36+0.5; + return $equivfl; + } + return null; +} + +?> diff --git a/modules/exif/lib/makers/canon.php b/modules/exif/lib/makers/canon.php new file mode 100644 index 0000000..aecd266 --- /dev/null +++ b/modules/exif/lib/makers/canon.php @@ -0,0 +1,426 @@ + \ No newline at end of file diff --git a/modules/exif/lib/makers/fujifilm.php b/modules/exif/lib/makers/fujifilm.php new file mode 100644 index 0000000..a1f2f41 --- /dev/null +++ b/modules/exif/lib/makers/fujifilm.php @@ -0,0 +1,247 @@ + \ No newline at end of file diff --git a/modules/exif/lib/makers/gps.php b/modules/exif/lib/makers/gps.php new file mode 100644 index 0000000..462aae6 --- /dev/null +++ b/modules/exif/lib/makers/gps.php @@ -0,0 +1,218 @@ + 1024) { + $result['Errors'] = $result['Errors']++; + $data = ''; + $type = 'ASCII'; + } else { + $value = bin2hex($value); + if($intel==1) $value = intel2Moto($value); + $v = fseek($seek,$globalOffset+hexdec($value)); //offsets are from TIFF header which is 12 bytes from the start of the file + if($v==0) { + $data = fread($seek, $bytesofdata); + } else { + $result['Errors'] = $result['Errors']++; + $data = ''; + $type = 'ASCII'; + } + } + } + if($result['VerboseOutput']==1) { + $result['GPS'][$tag_name] = formatGPSData($type,$tag,$intel,$data); + $result['GPS'][$tag_name."_Verbose"]['RawData'] = bin2hex($data); + $result['GPS'][$tag_name."_Verbose"]['Type'] = $type; + $result['GPS'][$tag_name."_Verbose"]['Bytes'] = $bytesofdata; + } else { + $result['GPS'][$tag_name] = formatGPSData($type,$tag,$intel,$data); + } + } +} + + +?> diff --git a/modules/exif/lib/makers/nikon.php b/modules/exif/lib/makers/nikon.php new file mode 100644 index 0000000..d2fff9a --- /dev/null +++ b/modules/exif/lib/makers/nikon.php @@ -0,0 +1,411 @@ +8) $place+=$offset-8; + + //Get number of tags (2 bytes) + $num = bin2hex(substr($block,$place,2));$place+=2; + if($intel==1) $num = intel2Moto($num); + + //loop thru all tags Each field is 12 bytes + for($i=0;$i \ No newline at end of file diff --git a/modules/exif/lib/makers/olympus.php b/modules/exif/lib/makers/olympus.php new file mode 100644 index 0000000..3382fc7 --- /dev/null +++ b/modules/exif/lib/makers/olympus.php @@ -0,0 +1,189 @@ + \ No newline at end of file diff --git a/modules/exif/lib/makers/panasonic.php b/modules/exif/lib/makers/panasonic.php new file mode 100644 index 0000000..47a0599 --- /dev/null +++ b/modules/exif/lib/makers/panasonic.php @@ -0,0 +1,292 @@ + \ No newline at end of file diff --git a/modules/exif/lib/makers/sanyo.php b/modules/exif/lib/makers/sanyo.php new file mode 100644 index 0000000..3eef201 --- /dev/null +++ b/modules/exif/lib/makers/sanyo.php @@ -0,0 +1,158 @@ + \ No newline at end of file diff --git a/modules/exif/models/exif_key.php b/modules/exif/models/exif_key.php new file mode 100644 index 0000000..5c45669 --- /dev/null +++ b/modules/exif/models/exif_key.php @@ -0,0 +1,21 @@ + + +

    +
    + + + + + + + + + + + + + + + + +
    diff --git a/modules/exif/views/exif_sidebar.html.php b/modules/exif/views/exif_sidebar.html.php new file mode 100644 index 0000000..8af2eb1 --- /dev/null +++ b/modules/exif/views/exif_sidebar.html.php @@ -0,0 +1,6 @@ + +id}") ?>" title="for_html_attr() ?>" + class="g-dialog-link g-button ui-icon-left ui-state-default ui-corner-all"> + + + diff --git a/modules/familygallery/controllers/login.php.backup b/modules/familygallery/controllers/login.php.backup new file mode 100644 index 0000000..4c5d558 --- /dev/null +++ b/modules/familygallery/controllers/login.php.backup @@ -0,0 +1,10 @@ +page_title = t("Familien Fotogalerie"); + $view->content = new View("login_ajax.html"); + $view->content->form = auth::get_login_form("login/auth_html"); + print $view; + } +} diff --git a/modules/familygallery/helpers/familygallery_event.php b/modules/familygallery/helpers/familygallery_event.php new file mode 100644 index 0000000..6192455 --- /dev/null +++ b/modules/familygallery/helpers/familygallery_event.php @@ -0,0 +1,20 @@ +guest) { + // disable the default login +// $menu->remove('user_menu_login'); + // add ours +/* $menu->append(Menu::factory("dialog") + ->id("user_menu_fam") + ->css_id("g-fam-menu") + ->url(url::site("familygallery/html")) + ->label(t("Login")));*/ + } + } +} diff --git a/modules/familygallery/module.info b/modules/familygallery/module.info new file mode 100644 index 0000000..0f4906d --- /dev/null +++ b/modules/familygallery/module.info @@ -0,0 +1,7 @@ +name = "Family Gallery Module" +description = "This module contains all our customizations" +version = 1 +author_name = "" +author_url = "" +info_url = "" +discuss_url = "" diff --git a/modules/forge/libraries/Forge.php b/modules/forge/libraries/Forge.php new file mode 100644 index 0000000..9179aae --- /dev/null +++ b/modules/forge/libraries/Forge.php @@ -0,0 +1,323 @@ + '', + 'class' => '', + 'open' => '', + 'close' => '', + ); + + // Form attributes + protected $attr = array(); + + // Form inputs and hidden inputs + public $inputs = array(); + public $hidden = array(); + + // Error message format, only used with custom templates + public $error_format = '

    {message}

    '; + public $newline_char = "\n"; + + /** + * Form constructor. Sets the form action, title, method, and attributes. + * + * @return void + */ + public function __construct($action = NULL, $title = '', $method = NULL, $attr = array()) + { + // Set form attributes + $this->attr['action'] = $action; + $this->attr['method'] = empty($method) ? 'post' : $method; + + // Set template variables + $this->template['title'] = $title; + + // Empty attributes sets the class to "form" + empty($attr) and $attr = array('class' => 'form'); + + // String attributes is the class name + is_string($attr) and $attr = array('class' => $attr); + + // Extend the template with the attributes + $this->attr += $attr; + } + + /** + * Magic __get method. Returns the specified form element. + * + * @param string unique input name + * @return object + */ + public function __get($key) + { + if (isset($this->inputs[$key])) + { + return $this->inputs[$key]; + } + elseif (isset($this->hidden[$key])) + { + return $this->hidden[$key]; + } + } + + /** + * Magic __call method. Creates a new form element object. + * + * @throws Kohana_Exception + * @param string input type + * @param string input name + * @return object + */ + public function __call($method, $args) + { + // Class name + $input = 'Form_'.ucfirst($method); + + // Create the input + switch (count($args)) + { + case 1: + $input = new $input($args[0]); + break; + case 2: + $input = new $input($args[0], $args[1]); + break; + default: + throw new Kohana_Exception('forge.invalid_input', $input); + } + + if ( ! ($input instanceof Form_Input) AND ! ($input instanceof Forge)) + throw new Kohana_Exception('forge.unknown_input', get_class($input)); + + $input->method = $this->attr['method']; + + if ($name = $input->name) + { + // Assign by name + if ($method == 'hidden') + { + $this->hidden[$name] = $input; + } + else + { + $this->inputs[$name] = $input; + } + } + else + { + // No name, these are unretrievable + $this->inputs[] = $input; + } + + return $input; + } + + /** + * Set a form attribute. This method is chainable. + * + * @param string|array attribute name, or an array of attributes + * @param string attribute value + * @return object + */ + public function set_attr($key, $val = NULL) + { + if (is_array($key)) + { + // Merge the new attributes with the old ones + $this->attr = array_merge($this->attr, $key); + } + else + { + // Set the new attribute + $this->attr[$key] = $val; + } + + return $this; + } + + /** + * Validates the form by running each inputs validation rules. + * + * @return bool + */ + public function validate() + { + $status = TRUE; + + $inputs = array_merge($this->hidden, $this->inputs); + + foreach ($inputs as $input) + { + if ($input->validate() == FALSE) + { + $status = FALSE; + } + } + + return $status; + } + + /** + * Returns the form as an array of input names and values. + * + * @return array + */ + public function as_array() + { + $data = array(); + foreach (array_merge($this->hidden, $this->inputs) as $input) + { + if (is_object($input->name)) // It's a Forge_Group object (hopefully) + { + foreach ($input->inputs as $group_input) + { + if ($name = $group_input->name) + { + $data[$name] = $group_input->value; + } + } + } + else if (is_array($input->inputs)) + { + foreach ($input->inputs as $group_input) + { + if ($name = $group_input->name) + { + $data[$name] = $group_input->value; + } + } + } + else if ($name = $input->name) // It's a normal input + { + // Return only named inputs + $data[$name] = $input->value; + } + } + return $data; + } + + /** + * Changes the error message format. Your message formatting must + * contain a {message} placeholder. + * + * @throws Kohana_Exception + * @param string new message format + * @return void + */ + public function error_format($string = '') + { + if (strpos((string) $string, '{message}') === FALSE) + throw new Kohana_Exception('validation.error_format'); + + $this->error_format = $string; + } + + /** + * Creates the form HTML + * + * @param string form view template name + * @param boolean use a custom view + * @return string + */ + public function render($template = 'forge_template', $custom = FALSE) + { + // Load template + $form = new View($template); + + if ($custom) + { + // Using a custom view + + $data = array(); + foreach (array_merge($this->hidden, $this->inputs) as $input) + { + $data[$input->name] = $input; + + // Groups will never have errors, so skip them + if ($input instanceof Form_Group) + continue; + + // Compile the error messages for this input + $messages = ''; + $errors = $input->error_messages(); + if (is_array($errors) AND ! empty($errors)) + { + foreach ($errors as $error) + { + // Replace the message with the error in the html error string + $messages .= str_replace('{message}', $error, $this->error_format).$this->newline_char; + } + } + + $data[$input->name.'_errors'] = $messages; + } + + $form->set($data); + } + else + { + // Using a template view + + $form->set($this->template); + $hidden = array(); + if ( ! empty($this->hidden)) + { + foreach ($this->hidden as $input) + { + $hidden['name'] = $input->name; + $hidden['value'] = $input->value; + } + } + + $form_type = 'open'; + // See if we need a multipart form + $check_inputs = array($this->inputs); + while ($check_inputs) { + foreach (array_shift($check_inputs) as $input) { + if ($input instanceof Form_Upload) + { + $form_type = 'open_multipart'; + } + if ($input instanceof Form_Group) + { + $check_inputs += array($input->inputs); + } + } + } + + // Set the form open and close + $form->open = form::$form_type(arr::remove('action', $this->attr), $this->attr); + foreach ($this->hidden as $hidden) { + $form->open .= form::hidden($hidden->name, $hidden->value); + } + $form->close = ""; + + // Set the inputs + $form->inputs = $this->inputs; + } + + return $form; + } + + /** + * Returns the form HTML + */ + public function __toString() + { + return (string) $this->render(); + } + +} // End Forge diff --git a/modules/forge/libraries/Form_Checkbox.php b/modules/forge/libraries/Form_Checkbox.php new file mode 100644 index 0000000..aded4fd --- /dev/null +++ b/modules/forge/libraries/Form_Checkbox.php @@ -0,0 +1,83 @@ + 'checkbox', + 'class' => 'checkbox', + 'value' => '1', + 'checked' => FALSE, + ); + + protected $protect = array('type'); + + public function __get($key) + { + if ($key == 'value') + { + // Return the value if the checkbox is checked + return $this->data['checked'] ? $this->data['value'] : NULL; + } + + return parent::__get($key); + } + + public function label($val = NULL) + { + if ($val === NULL) + { + // Do not display labels for checkboxes, labels wrap checkboxes + return ''; + } + else + { + $this->data['label'] = ($val === TRUE) ? utf8::ucwords(inflector::humanize($this->name)) : $val; + return $this; + } + } + + protected function html_element() + { + // Import the data + $data = $this->data; + + if (empty($data['checked'])) + { + // Not checked + unset($data['checked']); + } + else + { + // Is checked + $data['checked'] = 'checked'; + } + + if ($label = arr::remove('label', $data)) + { + // There must be one space before the text + $label = ' '.ltrim($label); + } + + return ''; + } + + protected function load_value() + { + if (is_bool($this->valid)) + return; + + // Makes the box checked if the value from POST is the same as the current value + $this->data['checked'] = ($this->input_value($this->name) == $this->data['value']); + } + +} // End Form Checkbox \ No newline at end of file diff --git a/modules/forge/libraries/Form_Checklist.php b/modules/forge/libraries/Form_Checklist.php new file mode 100644 index 0000000..4536d39 --- /dev/null +++ b/modules/forge/libraries/Form_Checklist.php @@ -0,0 +1,92 @@ + '', + 'type' => 'checkbox', + 'class' => 'checklist', + 'options' => array(), + ); + + protected $protect = array('name', 'type'); + + public function __construct($name) + { + $this->data['name'] = $name; + } + + public function __get($key) + { + if ($key == 'value') + { + // Return the currently checked values + $array = array(); + foreach ($this->data['options'] as $id => $opt) + { + // Return the options that are checked + ($opt[1] === TRUE) and $array[] = $id; + } + return $array; + } + + return parent::__get($key); + } + + public function render() + { + // Import base data + $base_data = $this->data; + + // Make it an array + $base_data['name'] .= '[]'; + + // Newline + $nl = "\n"; + + $checklist = '
      '.$nl; + foreach (arr::remove('options', $base_data) as $val => $opt) + { + // New set of input data + $data = $base_data; + + // Get the title and checked status + list ($title, $checked) = $opt; + + // Set the name, value, and checked status + $data['value'] = $val; + $data['checked'] = $checked; + + $checklist .= '
    • '.$nl; + } + $checklist .= '
    '; + + return $checklist; + } + + protected function load_value() + { + foreach ($this->data['options'] as $val => $checked) + { + if ($input = $this->input_value($this->data['name'])) + { + $this->data['options'][$val][1] = in_array($val, $input); + } + else + { + $this->data['options'][$val][1] = FALSE; + } + } + } + +} // End Form Checklist \ No newline at end of file diff --git a/modules/forge/libraries/Form_Dateselect.php b/modules/forge/libraries/Form_Dateselect.php new file mode 100644 index 0000000..a6e752e --- /dev/null +++ b/modules/forge/libraries/Form_Dateselect.php @@ -0,0 +1,138 @@ + '', + 'class' => 'dropdown', + ); + + protected $protect = array('type'); + + // Precision for the parts, you can use @ to insert a literal @ symbol + protected $parts = array + ( + 'month' => array(), + 'day' => array(1), + 'year' => array(), + ' @ ', + 'hour' => array(), + ':', + 'minute' => array(5), + 'am_pm' => array(), + ); + + public function __construct($name) + { + // Set name + $this->data['name'] = $name; + + // Default to the current time + $this->data['value'] = time(); + } + + public function __call($method, $args) + { + if (isset($this->parts[substr($method, 0, -1)])) + { + // Set options for date generation + $this->parts[substr($method, 0, -1)] = $args; + return $this; + } + + return parent::__call($method, $args); + } + + public function html_element() + { + // Import base data + $data = $this->data; + + // Get the options and default selection + $time = $this->time_array(arr::remove('value', $data)); + + // No labels or values + unset($data['label']); + + $input = ''; + foreach ($this->parts as $type => $val) + { + if (is_int($type)) + { + // Just add the separators + $input .= $val; + continue; + } + + // Set this input name + $data['name'] = $this->data['name'].'['.$type.']'; + + // Set the selected option + $selected = $time[$type]; + + if ($type == 'am_pm') + { + // Options are static + $options = array('AM' => 'AM', 'PM' => 'PM'); + } + else + { + // minute(s), hour(s), etc + $type .= 's'; + + // Use the date helper to generate the options + $options = empty($val) ? date::$type() : call_user_func_array(array('date', $type), $val); + } + + $input .= form::dropdown($data, $options, $selected); + } + + return $input; + } + + protected function time_array($timestamp) + { + $time = array_combine + ( + array('month', 'day', 'year', 'hour', 'minute', 'am_pm'), + explode('--', date('n--j--Y--g--i--A', $timestamp)) + ); + + // Minutes should always be in 5 minute increments + $time['minute'] = num::round($time['minute'], current($this->parts['minute'])); + + return $time; + } + + protected function load_value() + { + if (is_bool($this->valid)) + return; + + $time = $this->input_value($this->name); + + // Make sure all the required inputs keys are set + $time += $this->time_array(time()); + + $this->data['value'] = mktime + ( + date::adjust($time['hour'], $time['am_pm']), + $time['minute'], + 0, + $time['month'], + $time['day'], + $time['year'] + ); + } + +} // End Form Dateselect \ No newline at end of file diff --git a/modules/forge/libraries/Form_Dropdown.php b/modules/forge/libraries/Form_Dropdown.php new file mode 100644 index 0000000..9aa87a6 --- /dev/null +++ b/modules/forge/libraries/Form_Dropdown.php @@ -0,0 +1,78 @@ + '', + 'class' => 'dropdown', + ); + + protected $protect = array('type'); + + public function __get($key) + { + if ($key == 'value') + { + return $this->selected; + } + + return parent::__get($key); + } + + public function html_element() + { + // Import base data + $base_data = $this->data; + + unset($base_data['label']); + + // Get the options and default selection + $options = arr::remove('options', $base_data); + $selected = arr::remove('selected', $base_data); + + return form::dropdown($base_data, $options, $selected); + } + + protected function load_value() + { + if (is_bool($this->valid)) + return; + + $this->data['selected'] = $this->input_value($this->name); + } + + public function validate() + { + // Validation has already run + if (is_bool($this->is_valid)) + return $this->is_valid; + + if ($this->input_value() == FALSE) + { + // No data to validate + return $this->is_valid = FALSE; + } + + // Load the submitted value + $this->load_value(); + + if ( ! array_key_exists($this->value, $this->data['options'])) + { + // Value does not exist in the options + return $this->is_valid = FALSE; + } + + return parent::validate(); + } + +} // End Form Dropdown \ No newline at end of file diff --git a/modules/forge/libraries/Form_Group.php b/modules/forge/libraries/Form_Group.php new file mode 100644 index 0000000..0a04912 --- /dev/null +++ b/modules/forge/libraries/Form_Group.php @@ -0,0 +1,89 @@ + 'group', + 'name' => '', + 'class' => 'group', + 'label' => '', + 'message' => '' + ); + + // Input method + public $method; + + public function __construct($name = NULL, $class = 'group') + { + $this->data['name'] = $name; + $this->data['class'] = $class; + + // Set dummy data so we don't get errors + $this->attr['action'] = ''; + $this->attr['method'] = 'post'; + } + + public function __get($key) + { + if ($key == 'type' || $key == 'name' || $key == 'label') + { + return $this->data[$key]; + } + return parent::__get($key); + } + + public function __set($key, $val) + { + if ($key == 'method') + { + $this->attr['method'] = $val; + } + $this->$key = $val; + } + + public function label($val = NULL) + { + if ($val === NULL) + { + if ($label = $this->data['label']) + { + return html::purify($this->data['label']); + } + } + else + { + $this->data['label'] = ($val === TRUE) ? ucwords(inflector::humanize($this->data['name'])) : $val; + return $this; + } + } + + public function message($val = NULL) + { + if ($val === NULL) + { + return $this->data['message']; + } + else + { + $this->data['message'] = $val; + return $this; + } + } + + public function render($template = 'forge_template', $custom = FALSE) + { + // No Sir, we don't want any html today thank you + return; + } + +} // End Form Group \ No newline at end of file diff --git a/modules/forge/libraries/Form_Hidden.php b/modules/forge/libraries/Form_Hidden.php new file mode 100644 index 0000000..423f626 --- /dev/null +++ b/modules/forge/libraries/Form_Hidden.php @@ -0,0 +1,25 @@ + '', + 'value' => '', + ); + + public function render() + { + return form::hidden($this->data['name'], $this->data['value']); + } + +} // End Form Hidden \ No newline at end of file diff --git a/modules/forge/libraries/Form_Input.php b/modules/forge/libraries/Form_Input.php new file mode 100644 index 0000000..0c57801 --- /dev/null +++ b/modules/forge/libraries/Form_Input.php @@ -0,0 +1,555 @@ + 'text', + 'class' => 'textbox', + 'value' => '' + ); + + // Protected data keys + protected $protect = array(); + + // Validation rules, matches, and callbacks + protected $rules = array(); + protected $matches = array(); + protected $callbacks = array(); + + // Validation check + protected $is_valid; + + // Errors + protected $errors = array(); + protected $error_messages = array(); + + /** + * Sets the input element name. + */ + public function __construct($name) + { + $this->data['name'] = $name; + } + + /** + * Sets form attributes, or return rules. + */ + public function __call($method, $args) + { + if ($method == 'rules') + { + if (empty($args)) + return $this->rules; + + // Set rules and action + $rules = $args[0]; + $action = substr($rules, 0, 1); + + if (in_array($action, array('-', '+', '='))) + { + // Remove the action from the rules + $rules = substr($rules, 1); + } + else + { + // Default action is append + $action = ''; + } + + $this->add_rules(explode('|', $rules), $action); + } + elseif ($method == 'name') + { + // Do nothing. The name should stay static once it is set. + } + else + { + $this->data[$method] = $args[0]; + } + + return $this; + } + + /** + * Returns form attributes. + * + * @param string attribute name + * @return string + */ + public function __get($key) + { + if (isset($this->data[$key])) + { + return $this->data[$key]; + } + } + + /** + * Sets a form element that this element must match the value of. + * + * @chainable + * @param object another Forge input + * @return object + */ + public function matches($input) + { + if ( ! in_array($input, $this->matches, TRUE)) + { + $this->matches[] = $input; + } + + return $this; + } + + /** + * Sets a callback method as a rule for this input. + * + * @chainable + * @param callback + * @return object + */ + public function callback($callback) + { + if ( ! in_array($callback, $this->callbacks, TRUE)) + { + $this->callbacks[] = $callback; + } + + return $this; + } + + /** + * Sets or returns the input label. + * + * @chainable + * @param string label to set + * @return string|object + */ + public function label($val = NULL) + { + if ($val === NULL) + { + if (isset($this->data['name']) AND isset($this->data['label'])) + { + return form::label($this->data['name'], $this->data['label']); + } + return FALSE; + } + else + { + $this->data['label'] = ($val === TRUE) ? utf8::ucwords(inflector::humanize($this->name)) : $val; + return $this; + } + } + + /** + * Set or return the error message. + * + * @chainable + * @param string error message + * @return strong|object + */ + public function message($val = NULL) + { + if ($val === NULL) + { + if (isset($this->data['message'])) + return $this->data['message']; + } + else + { + $this->data['message'] = $val; + return $this; + } + } + + /** + * Runs validation and returns the element HTML. + * + * @return string + */ + public function render() + { + // Make sure validation runs + $this->validate(); + + return $this->html_element(); + } + + /** + * Returns the form input HTML. + * + * @return string + */ + protected function html_element() + { + $data = $this->data; + + unset($data['label']); + unset($data['message']); + + return form::input($data); + } + + /** + * Replace, remove, or append rules. + * + * @param array rules to change + * @param string action to use: replace, remove, append + */ + protected function add_rules( array $rules, $action) + { + if ($action === '=') + { + // Just replace the rules + $this->rules = $rules; + return; + } + + foreach ($rules as $rule) + { + if ($action === '-') + { + if (($key = array_search($rule, $this->rules)) !== FALSE) + { + // Remove the rule + unset($this->rules[$key]); + } + } + else + { + if ( ! in_array($rule, $this->rules)) + { + if ($action == '+') + { + array_unshift($this->rules, $rule); + } + else + { + $this->rules[] = $rule; + } + } + } + } + } + + /** + * Add an error to the input. + * + * @chainable + * @return object + */ + public function add_error($key, $val) + { + if ( ! isset($this->errors[$key])) + { + $this->errors[$key] = $val; + } + + return $this; + } + + /** + * Set or return the error messages. + * + * @chainable + * @param string|array failed validation function, or an array of messages + * @param string error message + * @return object|array + */ + public function error_messages($func = NULL, $message = NULL) + { + // Set custom error messages + if ( ! empty($func)) + { + if (is_array($func)) + { + // Replace all + $this->error_messages = $func; + } + else + { + if (empty($message)) + { + // Single error, replaces all others + $this->error_messages = $func; + } + else + { + // Add custom error + $this->error_messages[$func] = $message; + } + } + return $this; + } + + // Make sure validation runs + is_null($this->is_valid) and $this->validate(); + + // Return single error + if ( ! is_array($this->error_messages) AND ! empty($this->errors)) + return array($this->error_messages); + + $messages = array(); + foreach ($this->errors as $func => $args) + { + if (is_string($args)) + { + $error = $args; + } + else + { + // Force args to be an array + $args = is_array($args) ? $args : array(); + + // Add the label or name to the beginning of the args + array_unshift($args, $this->label ? mb_strtolower($this->label) : $this->name); + + if (isset($this->error_messages[$func])) + { + // Use custom error message + $error = vsprintf($this->error_messages[$func], $args); + } + else + { + // Get the proper i18n entry, very hacky but it works + switch ($func) + { + case 'valid_url': + case 'valid_email': + case 'valid_ip': + // Fetch an i18n error message + $error = 'validation.'.$func; + break; + case substr($func, 0, 6) === 'valid_': + // Strip 'valid_' from func name + $func = (substr($func, 0, 6) === 'valid_') ? substr($func, 6) : $func; + case 'alpha': + case 'alpha_dash': + case 'digit': + case 'numeric': + // i18n strings have to be inserted into valid_type + $args[] = 'validation.'.$func; + $error = 'validation.valid_type'; + break; + default: + $error = 'validation.'.$func; + } + } + } + + // Add error to list + $messages[] = $error; + } + + return $messages; + } + + /** + * Get the global input value. + * + * @return string|bool + */ + protected function input_value($name = array()) + { + // Get the Input instance + $input = Input::instance(); + + // Fetch the method for this object + $method = $this->method; + + return $input->$method($name, NULL); + } + + /** + * Load the value of the input, if form data is present. + * + * @return void + */ + protected function load_value() + { + if (is_bool($this->is_valid)) + return; + + if ($name = $this->name) + { + // Load POSTed value, but only for named inputs + $this->data['value'] = $this->input_value($name); + } + + if (is_string($this->data['value'])) + { + // Trim string values + $this->data['value'] = trim($this->data['value']); + } + } + + /** + * Validate this input based on the set rules. + * + * @return bool + */ + public function validate() + { + // Validation has already run + if (is_bool($this->is_valid)) + return $this->is_valid; + + // No data to validate + if ($this->input_value() == FALSE) + return $this->is_valid = FALSE; + + // Load the submitted value + $this->load_value(); + + // No rules to validate + if (count($this->rules) == 0 AND count($this->matches) == 0 AND count($this->callbacks) == 0) + return $this->is_valid = TRUE; + + if ( ! empty($this->rules)) + { + foreach ($this->rules as $rule) + { + if (($offset = strpos($rule, '[')) !== FALSE) + { + // Get the args + $args = preg_split('/, ?/', trim(substr($rule, $offset), '[]')); + + // Remove the args from the rule + $rule = substr($rule, 0, $offset); + } + + if (substr($rule, 0, 6) === 'valid_' AND method_exists('valid', substr($rule, 6))) + { + $func = substr($rule, 6); + + if ($this->value AND ! valid::$func($this->value)) + { + $this->errors[$rule] = TRUE; + } + } + elseif (method_exists($this, 'rule_'.$rule)) + { + // The rule function is always prefixed with rule_ + $rule = 'rule_'.$rule; + + if (isset($args)) + { + // Manually call up to 2 args for speed + switch (count($args)) + { + case 1: + $this->$rule($args[0]); + break; + case 2: + $this->$rule($args[0], $args[1]); + break; + default: + call_user_func_array(array($this, $rule), $args); + break; + } + } + else + { + // Just call the rule + $this->$rule(); + } + + // Prevent args from being re-used + unset($args); + } + else + { + throw new Kohana_Exception('validation.invalid_rule', $rule); + } + + // Stop when an error occurs + if ( ! empty($this->errors)) + break; + } + } + + if ( ! empty($this->matches)) + { + foreach ($this->matches as $input) + { + if ($this->value != $input->value) + { + // Field does not match + $this->errors['matches'] = array($input->label ? mb_strtolower($input->label) : $input->name); + break; + } + } + } + + if ( ! empty($this->callbacks)) + { + foreach ($this->callbacks as $callback) + { + call_user_func($callback, $this); + + // Stop when an error occurs + if ( ! empty($this->errors)) + break; + } + } + + // If there are errors, validation failed + return $this->is_valid = empty($this->errors); + } + + /** + * Validate required. + */ + protected function rule_required() + { + if ($this->value === '' OR $this->value === NULL) + { + $this->errors['required'] = TRUE; + } + } + + /** + * Validate length. + */ + protected function rule_length($min, $max = NULL) + { + // Get the length, return if zero + if (($length = mb_strlen($this->value)) === 0) + return; + + if ($max == NULL) + { + if ($length != $min) + { + $this->errors['exact_length'] = array($min); + } + } + else + { + if ($length < $min) + { + $this->errors['min_length'] = array($min); + } + elseif ($length > $max) + { + $this->errors['max_length'] = array($max); + } + } + } + +} // End Form Input \ No newline at end of file diff --git a/modules/forge/libraries/Form_Password.php b/modules/forge/libraries/Form_Password.php new file mode 100644 index 0000000..2622ae5 --- /dev/null +++ b/modules/forge/libraries/Form_Password.php @@ -0,0 +1,23 @@ + 'password', + 'class' => 'password', + 'value' => '', + ); + + protected $protect = array('type'); + +} // End Form Password \ No newline at end of file diff --git a/modules/forge/libraries/Form_Phonenumber.php b/modules/forge/libraries/Form_Phonenumber.php new file mode 100644 index 0000000..2befef5 --- /dev/null +++ b/modules/forge/libraries/Form_Phonenumber.php @@ -0,0 +1,98 @@ + '', + 'class' => 'phone_number', + ); + + protected $protect = array('type'); + + // Precision for the parts, you can use @ to insert a literal @ symbol + protected $parts = array + ( + 'area_code' => '', + 'exchange' => '', + 'last_four' => '', + ); + + public function __construct($name) + { + // Set name + $this->data['name'] = $name; + } + + public function __call($method, $args) + { + if (isset($this->parts[substr($method, 0, -1)])) + { + // Set options for date generation + $this->parts[substr($method, 0, -1)] = $args; + return $this; + } + + return parent::__call($method, $args); + } + + public function html_element() + { + // Import base data + $data = $this->data; + + $input = ''; + foreach ($this->parts as $type => $val) + { + isset($data['value']) OR $data['value'] = ''; + $temp = $data; + $temp['name'] = $this->data['name'].'['.$type.']'; + $offset = (strlen($data['value']) == 10) ? 0 : 3; + switch ($type) + { + case 'area_code': + if (strlen($data['value']) == 10) + { + $temp['value'] = substr($data['value'], 0, 3); + } + else + $temp['value'] = ''; + $temp['class'] = 'area_code'; + $input .= form::input(array_merge(array('value' => $val), $temp)).'-'; + break; + case 'exchange': + $temp['value'] = substr($data['value'], (3-$offset), 3); + $temp['class'] = 'exchange'; + $input .= form::input(array_merge(array('value' => $val), $temp)).'-'; + break; + case 'last_four': + $temp['value'] = substr($data['value'], (6-$offset), 4); + $temp['class'] = 'last_four'; + $input .= form::input(array_merge(array('value' => $val), $temp)); + break; + } + + } + + return $input; + } + + protected function load_value() + { + if (is_bool($this->valid)) + return; + + $data = $this->input_value($this->name, $this->data['name']); + + $this->data['value'] = $data['area_code'].$data['exchange'].$data['last_four']; + } +} // End Form Phonenumber \ No newline at end of file diff --git a/modules/forge/libraries/Form_Radio.php b/modules/forge/libraries/Form_Radio.php new file mode 100644 index 0000000..6648fd2 --- /dev/null +++ b/modules/forge/libraries/Form_Radio.php @@ -0,0 +1,22 @@ + 'radio', + 'class' => 'radio', + 'value' => '1', + 'checked' => FALSE, + ); + +} // End Form_Radio \ No newline at end of file diff --git a/modules/forge/libraries/Form_Submit.php b/modules/forge/libraries/Form_Submit.php new file mode 100644 index 0000000..6908f6f --- /dev/null +++ b/modules/forge/libraries/Form_Submit.php @@ -0,0 +1,30 @@ + 'submit', + 'class' => 'submit' + ); + + protected $protect = array('type'); + + public function render() + { + $data = $this->data; + unset($data['label']); + + return form::submit($data); + } + +} // End Form Submit \ No newline at end of file diff --git a/modules/forge/libraries/Form_Textarea.php b/modules/forge/libraries/Form_Textarea.php new file mode 100644 index 0000000..279ea56 --- /dev/null +++ b/modules/forge/libraries/Form_Textarea.php @@ -0,0 +1,31 @@ + 'textarea', + 'value' => '', + ); + + protected $protect = array('type'); + + protected function html_element() + { + $data = $this->data; + + unset($data['label']); + + return form::textarea($data); + } + +} // End Form Textarea \ No newline at end of file diff --git a/modules/forge/libraries/Form_Upload.php b/modules/forge/libraries/Form_Upload.php new file mode 100644 index 0000000..eda9c8a --- /dev/null +++ b/modules/forge/libraries/Form_Upload.php @@ -0,0 +1,191 @@ + 'upload', + 'value' => '', + ); + + protected $protect = array('type', 'label', 'value'); + + // Upload data + protected $upload; + + // Upload directory and filename + protected $directory; + protected $filename; + + public function __construct($name, $filename = FALSE) + { + parent::__construct($name); + + if ( ! empty($_FILES[$name])) + { + if (empty($_FILES[$name]['tmp_name']) OR is_uploaded_file($_FILES[$name]['tmp_name'])) + { + // Cache the upload data in this object + $this->upload = $_FILES[$name]; + + // Hack to allow file-only inputs, where no POST data is present + $_POST[$name] = $this->upload['name']; + + // Set the filename + $this->filename = empty($filename) ? FALSE : $filename; + } + else + { + // Attempt to delete the invalid file + is_writable($_FILES[$name]['tmp_name']) and unlink($_FILES[$name]['tmp_name']); + + // Invalid file upload, possible hacking attempt + unset($_FILES[$name]); + } + } + } + + /** + * Sets the upload directory. + * + * @param string upload directory + * @return void + */ + public function directory($dir = NULL) + { + // Use the global upload directory by default + empty($dir) and $dir = Kohana::config('upload.directory'); + + // Make the path asbolute and normalize it + $directory = str_replace('\\', '/', realpath($dir)).'/'; + + // Make sure the upload director is valid and writable + if ($directory === '/' OR ! is_dir($directory) OR ! is_writable($directory)) + throw new Kohana_Exception('upload.not_writable', $dir); + + $this->directory = $directory; + } + + public function validate() + { + // The upload directory must always be set + empty($this->directory) and $this->directory(); + + // By default, there is no uploaded file + $filename = ''; + + if ($status = parent::validate() AND $this->upload['error'] === UPLOAD_ERR_OK) + { + // Set the filename to the original name + $filename = $this->upload['name']; + + if (Kohana::config('upload.remove_spaces')) + { + // Remove spaces, due to global upload configuration + $filename = preg_replace('/\s+/', '_', $this->data['value']); + } + + if (file_exists($filepath = $this->directory.$filename)) + { + if ($this->filename !== TRUE OR ! is_writable($filepath)) + { + // Prefix the file so that the filename is unique + $filepath = $this->directory.'uploadfile-'.uniqid(time()).'-'.$this->upload['name']; + } + } + + // Move the uploaded file to the upload directory + move_uploaded_file($this->upload['tmp_name'], $filepath); + } + + if ( ! empty($_POST[$this->data['name']])) + { + // Reset the POST value to the new filename + $this->data['value'] = $_POST[$this->data['name']] = empty($filepath) ? '' : $filepath; + } + + return $status; + } + + protected function rule_required() + { + if (empty($this->upload) OR $this->upload['error'] === UPLOAD_ERR_NO_FILE) + { + $this->errors['required'] = TRUE; + } + } + + public function rule_allow() + { + if (empty($this->upload['tmp_name']) OR count($types = func_get_args()) == 0) + return; + + if (($mime = file::mime($this->upload['tmp_name'])) === FALSE) + { + // Trust the browser + $mime = $this->upload['type']; + } + + // Get rid of the ";charset=binary" that can occasionally occur and is + // legal via RFC2045 + $mime = preg_replace('/; charset=binary/', '', $mime); + + // Allow nothing by default + $allow = FALSE; + + foreach ($types as $type) + { + // Load the mime types + $type = Kohana::config('mimes.'.$type); + + if (is_array($type) AND in_array($mime, $type)) + { + // Type is valid + $allow = TRUE; + break; + } + } + + if ($allow === FALSE) + { + $this->errors['invalid_type'] = TRUE; + } + } + + public function rule_size($size) + { + // Skip the field if it is empty + if (empty($this->upload) OR $this->upload['error'] === UPLOAD_ERR_NO_FILE) + return; + + $bytes = (int) $size; + + switch (substr($size, -2)) + { + case 'GB': $bytes *= 1024; + case 'MB': $bytes *= 1024; + case 'KB': $bytes *= 1024; + default: break; + } + + if (empty($this->upload['size']) OR $this->upload['size'] > $bytes) + { + $this->errors['max_size'] = array($size); + } + } + + protected function html_element() + { + return form::upload($this->data); + } + +} // End Form Upload diff --git a/modules/g2_import/controllers/admin_g2_import.php b/modules/g2_import/controllers/admin_g2_import.php new file mode 100644 index 0000000..c4f0390 --- /dev/null +++ b/modules/g2_import/controllers/admin_g2_import.php @@ -0,0 +1,136 @@ +page_title = t("Gallery 2 import"); + $view->content = new View("admin_g2_import.html"); + + if (class_exists("GalleryCoreApi")) { + $view->content->g2_stats = $g2_stats = g2_import::g2_stats(); + $view->content->g3_stats = $g3_stats = g2_import::g3_stats(); + $view->content->g2_sizes = g2_import::common_sizes(); + $view->content->g2_version = g2_import::version(); + + // Don't count tags because we don't track them in g2_map + $view->content->g2_resource_count = + $g2_stats["users"] + $g2_stats["groups"] + $g2_stats["albums"] + + $g2_stats["photos"] + $g2_stats["movies"] + $g2_stats["comments"]; + $view->content->g3_resource_count = + $g3_stats["user"] + $g3_stats["group"] + $g3_stats["album"] + + $g3_stats["item"] + $g3_stats["comment"] + $g3_stats["tag"]; + } + + $view->content->form = $this->_get_import_form(); + $view->content->version = ""; + $view->content->thumb_size = module::get_var("gallery", "thumb_size"); + $view->content->resize_size = module::get_var("gallery", "resize_size"); + + if (g2_import::is_initialized()) { + if ((bool)ini_get("eaccelerator.enable") || (bool)ini_get("xcache.cacher")) { + message::warning(t("The eAccelerator and XCache PHP performance extensions are known to cause issues. If you're using either of those and are having problems, please disable them while you do your import. Add the following lines:
    %lines
    to gallery3/.htaccess and remove them when the import is done.", array("lines" => "\n\n php_value eaccelerator.enable 0\n php_value xcache.cacher off\n php_value xcache.optimizer off\n\n"))); + } + + foreach (array("notification", "search", "exif") as $module_id) { + if (module::is_active($module_id)) { + message::warning( + t("Deactivating the %module_id module during your import will make it faster", + array("url" => url::site("admin/modules"), "module_id" => $module_id))); + } + } + if (module::is_active("akismet")) { + message::warning( + t("The Akismet module may mark some or all of your imported comments as spam. Deactivate it to avoid that outcome.", + array("url" => url::site("admin/modules")))); + } + } else if (g2_import::is_configured()) { + $view->content->form->configure_g2_import->embed_path->add_error("invalid", 1); + } + g2_import::restore_error_reporting(); + print $view; + } + + public function save() { + access::verify_csrf(); + g2_import::lower_error_reporting(); + + $form = $this->_get_import_form(); + if ($form->validate()) { + $embed_path = $form->configure_g2_import->embed_path->value; + if (!is_file($embed_path) && file_exists("$embed_path/embed.php")) { + $embed_path = "$embed_path/embed.php"; + } + + if (($g2_init_error = g2_import::is_valid_embed_path($embed_path)) == "ok") { + message::success(t("Gallery 2 path saved")); + module::set_var("g2_import", "embed_path", $embed_path); + url::redirect("admin/g2_import"); + } else { + $form->configure_g2_import->embed_path->add_error($g2_init_error, 1); + } + } + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_g2_import.html"); + $view->content->form = $form; + g2_import::restore_error_reporting(); + print $view; + } + + public function autocomplete() { + $directories = array(); + $path_prefix = Input::instance()->get("q"); + foreach (glob("{$path_prefix}*") as $file) { + if (is_dir($file) && !is_link($file)) { + $file = html::clean($file); + $directories[] = $file; + + // If we find an embed.php, include it as well + if (file_exists("$file/embed.php")) { + $directories[] = "$file/embed.php"; + } + } + } + + ajax::response(implode("\n", $directories)); + } + + private function _get_import_form() { + $embed_path = module::get_var("g2_import", "embed_path", ""); + $form = new Forge( + "admin/g2_import/save", "", "post", array("id" => "g-admin-configure-g2-import-form")); + $group = $form->group("configure_g2_import")->label(t("Configure Gallery 2 Import")); + $group->input("embed_path")->label(t("Filesystem path to your Gallery 2 embed.php file")) + ->value($embed_path); + $group->embed_path->error_messages( + "invalid", t("The path you entered is not a Gallery 2 installation.")); + $group->embed_path->error_messages( + "broken", t("Your Gallery 2 install isn't working properly. Please verify it!")); + $group->embed_path->error_messages( + "missing", t("The path you entered does not exist.")); + $group->submit("")->value($embed_path ? t("Change") : t("Continue")); + return $form; + } +} \ No newline at end of file diff --git a/modules/g2_import/controllers/g2.php b/modules/g2_import/controllers/g2.php new file mode 100644 index 0000000..c24d52e --- /dev/null +++ b/modules/g2_import/controllers/g2.php @@ -0,0 +1,121 @@ +get("path"); + $id = $input->get("g2_itemId"); + $view = $input->get("g2_view"); + + // Tags did not have mappings created, so we need to catch them first. However, if a g2_itemId was + // passed, we'll want to show lookup the mapping anyway + if (($path && 0 === strpos($path, "tag/")) || $view == "tags.VirtualAlbum") { + if (0 === strpos($path, "tag/")) { + $tag_name = substr($path, 4); + } + if ($view == "tags.VirtualAlbum") { + $tag_name = $input->get("g2_tagName"); + } + + if (!$id) { + url::redirect("tag_name/$tag_name", 301); + } + + $tag = ORM::factory("tag")->where("name", "=", $tag_name)->find(); + if ($tag->loaded()) { + item::set_display_context_callback("Tag_Controller::get_display_context", $tag->id); + // We want to show the item as part of the tag virtual album. Most of this code is below; we'll + // change $path and $view to let it fall through + $view = ""; + $path = ""; + } + } + + if (($path && $path != 'index.php' && $path != 'main.php') || $id) { + if ($id) { + // Requests by id are either core.DownloadItem or core.ShowItem requests. Later versions of + // Gallery 2 don't specify g2_view if it's the default (core.ShowItem). And in some cases + // (bbcode, embedding) people are using the id style URLs although URL rewriting is enabled. + $where = array(array("g2_id", "=", $id)); + if ($view == "core.DownloadItem") { + $where[] = array("resource_type", "IN", array("file", "resize", "thumbnail", "full")); + } else if ($view) { + $where[] = array("g2_url", "LIKE", "%" . Database::escape_for_like("g2_view=$view") . "%"); + } // else: Assuming that the first search hit is sufficiently good. + } else if ($path) { + $where = array(array("g2_url", "IN", array($path, str_replace(" ", "+", $path)))); + } else { + throw new Kohana_404_Exception(); + } + + $g2_map = ORM::factory("g2_map") + ->merge_where($where) + ->find(); + + if (!$g2_map->loaded()) { + throw new Kohana_404_Exception(); + } + + $item = ORM::factory("item", $g2_map->g3_id); + if (!$item->loaded()) { + throw new Kohana_404_Exception(); + } + $resource_type = $g2_map->resource_type; + } else { + $item = item::root(); + $resource_type = "album"; + } + access::required("view", $item); + + + // Redirect the user to the new url + switch ($resource_type) { + case "thumbnail": + url::redirect($item->thumb_url(true), 301); + + case "resize": + url::redirect($item->resize_url(true), 301); + + case "file": + case "full": + url::redirect($item->file_url(true), 301); + + case "item": + case "album": + url::redirect($item->abs_url(), 301); + + case "group": + case "user": + default: + throw new Kohana_404_Exception(); + } + } +} diff --git a/modules/g2_import/data/broken-image.gif b/modules/g2_import/data/broken-image.gif new file mode 100755 index 0000000..fb9c824 Binary files /dev/null and b/modules/g2_import/data/broken-image.gif differ diff --git a/modules/g2_import/helpers/g2_import.php b/modules/g2_import/helpers/g2_import.php new file mode 100644 index 0000000..82850e8 --- /dev/null +++ b/modules/g2_import/helpers/g2_import.php @@ -0,0 +1,1375 @@ + 200) { + // Regular install + $base_dir = $config_dir; + } else { + // Multisite install. Line 2 of embed.php will be something like: + // require('/usr/home/bharat/public_html/gallery2/embed.php'); + $lines = file($embed_path); + preg_match("#require\('(.*)/embed.php'\);#", $lines[2], $matches); + $base_dir = $matches[1]; + } + + file_put_contents( + "$mod_path/embed.php", + str_replace( + array( + "require_once(dirname(__FILE__) . '/modules/core/classes/GalleryDataCache.class');", + "require(dirname(__FILE__) . '/modules/core/classes/GalleryEmbed.class');"), + array( + "require_once('$base_dir/modules/core/classes/GalleryDataCache.class');", + "require('$base_dir/modules/core/classes/GalleryEmbed.class');"), + array_merge( + array("\n"), + file("$base_dir/embed.php")))); + + file_put_contents( + "$mod_path/main.php", + str_replace( + array( + "include(dirname(__FILE__) . '/bootstrap.inc');", + "require_once(dirname(__FILE__) . '/init.inc');"), + array( + "include(dirname(__FILE__) . '/bootstrap.inc');", + "require_once('$base_dir/init.inc');"), + array_merge( + array("\n"), + file("$base_dir/main.php")))); + + file_put_contents( + "$mod_path/bootstrap.inc", + str_replace( + array( + "require_once(dirname(__FILE__) . '/modules/core/classes/Gallery.class');", + "require_once(dirname(__FILE__) . '/modules/core/classes/GalleryDataCache.class');", + "define('GALLERY_CONFIG_DIR', dirname(__FILE__));", + "\$gallery =& new Gallery();", + "\$GLOBALS['gallery'] =& new Gallery();", + "\$gallery = new Gallery();"), + array( + "require_once(dirname(__FILE__) . '/Gallery.class');", + "require_once('$base_dir/modules/core/classes/GalleryDataCache.class');", + "define('GALLERY_CONFIG_DIR', '$config_dir');", + "\$gallery =& new G2_Gallery();", + "\$GLOBALS['gallery'] =& new G2_Gallery();", + "\$gallery = new G2_Gallery();"), + array_merge( + array("\n"), + file("$base_dir/bootstrap.inc")))); + + file_put_contents( + "$mod_path/Gallery.class", + str_replace( + array("class Gallery", + "function Gallery"), + array("class G2_Gallery", + "function G2_Gallery"), + array_merge( + array("\n"), + file("$base_dir/modules/core/classes/Gallery.class")))); + } else { + // Ok, this is a good one. If you're running a bytecode accelerator and you move your + // Gallery install, these files sometimes get cached with the wrong path and then fail to + // load properly. + // Documented in https://sourceforge.net/apps/trac/gallery/ticket/1253 + touch("$mod_path/embed.php"); + touch("$mod_path/main.php"); + touch("$mod_path/bootstrap.inc"); + touch("$mod_path/Gallery.class.inc"); + } + + require("$mod_path/embed.php"); + if (!class_exists("GalleryEmbed")) { + return "invalid"; + } + + $ret = GalleryEmbed::init(); + if ($ret) { + Kohana_Log::add("error", "Gallery 2 call failed with: " . $ret->getAsText()); + return "broken"; + } + + $admin_group_id = g2(GalleryCoreApi::getPluginParameter("module", "core", "id.adminGroup")); + $admins = g2(GalleryCoreApi::fetchUsersForGroup($admin_group_id, 1)); + $admin_id = current(array_flip($admins)); + $admin = g2(GalleryCoreApi::loadEntitiesById($admin_id)); + $GLOBALS["gallery"]->setActiveUser($admin); + + // Make sure we have an embed location so that embedded url generation comes out ok. Without + // this, the Gallery2 ModRewrite code won't try to do url generation. + $g2_embed_location = + g2(GalleryCoreApi::getPluginParameter("module", "rewrite", "modrewrite.embeddedLocation")); + + if (empty($g2_embed_location)) { + $g2_embed_location = + g2(GalleryCoreApi::getPluginParameter("module", "rewrite", "modrewrite.galleryLocation")); + g2(GalleryCoreApi::setPluginParameter("module", "rewrite", "modrewrite.embeddedLocation", + $g2_embed_location)); + g2($gallery->getStorage()->checkPoint()); + } + + if ($g2_embed_location) { + self::$g2_base_url = $g2_embed_location; + } else { + self::$g2_base_url = $GLOBALS["gallery"]->getUrlGenerator()->generateUrl( + array(), + array("forceSessionId" => false, + "htmlEntities" => false, + "urlEncode" => false, + "useAuthToken" => false)); + } + } catch (ErrorException $e) { + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + return "broken"; + } + + return "ok"; + } + + /** + * Return the version of Gallery 2 (eg "2.3") + */ + static function version() { + $core = g2(GalleryCoreApi::loadPlugin("module", "core")); + $versions = $core->getInstalledVersions(); + return $versions["gallery"]; + } + + /** + * Return true if the given Gallery 2 module is active. + */ + static function g2_module_active($module) { + static $plugin_list; + if (!$plugin_list) { + $plugin_list = g2(GalleryCoreApi::fetchPluginList("module")); + } + + return @$plugin_list[$module]["active"]; + } + + /** + * Return a set of statistics about the number of users, groups, albums, photos, movies and + * comments available for import from the Gallery 2 instance. + */ + static function g2_stats() { + global $gallery; + $root_album_id = g2(GalleryCoreApi::getDefaultAlbumId()); + $stats["users"] = g2(GalleryCoreApi::fetchUserCount()); + $stats["groups"] = g2(GalleryCoreApi::fetchGroupCount()); + $stats["albums"] = g2(GalleryCoreApi::fetchItemIdCount("GalleryAlbumItem")); + $stats["photos"] = g2(GalleryCoreApi::fetchItemIdCount("GalleryPhotoItem")); + $stats["movies"] = g2(GalleryCoreApi::fetchItemIdCount("GalleryMovieItem")); + + if (g2_import::g2_module_active("comment") && module::is_active("comment")) { + GalleryCoreApi::requireOnce("modules/comment/classes/GalleryCommentHelper.class"); + list (, $stats["comments"]) = g2(GalleryCommentHelper::fetchAllComments($root_album_id, 1)); + } else { + $stats["comments"] = 0; + } + + if (g2_import::g2_module_active("tags") && module::is_active("tag")) { + $result = + g2($gallery->search("SELECT COUNT(DISTINCT([TagItemMap::itemId])) FROM [TagItemMap]")) + ->nextResult(); + $stats["tags"] = $result[0]; + } else { + $stats["tags"] = 0; + } + + return $stats; + } + + /** + * Return a set of statistics about the number of users, groups, albums, photos, movies and + * comments already imported into the Gallery 3 instance. + */ + static function g3_stats() { + $g3_stats = array( + "album" => 0, "comment" => 0, "item" => 0, "user" => 0, "group" => 0, "tag" => 0); + foreach (db::build() + ->select("resource_type") + ->select(array("C" => 'COUNT("*")')) + ->from("g2_maps") + ->where("resource_type", "IN", array("album", "comment", "item", "user", "group")) + ->group_by("resource_type") + ->execute() as $row) { + $g3_stats[$row->resource_type] = $row->C; + } + return $g3_stats; + } + + /** + * Import a single group. + */ + static function import_group(&$queue) { + $messages = array(); + $g2_group_id = array_shift($queue); + if (self::map($g2_group_id)) { + return; + } + + try { + $g2_group = g2(GalleryCoreApi::loadEntitiesById($g2_group_id)); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to import Gallery 2 group with id: %id,", + array("id" => $g2_group_id)), + $e); + } + + switch ($g2_group->getGroupType()) { + case GROUP_NORMAL: + try { + $group = identity::create_group($g2_group->getGroupName()); + $messages[] = t("Group '%name' was imported", + array("name" => $g2_group->getGroupname())); + } catch (Exception $e) { + // Did it fail because of a duplicate group name? + $group = identity::lookup_group_by_name($g2_group->getGroupname()); + if ($group) { + $messages[] = t("Group '%name' was mapped to the existing group group of the same name.", + array("name" => $g2_group->getGroupname())); + } else { + throw new G2_Import_Exception( + t("Failed to import group '%name'", + array("name" => $g2_group->getGroupname())), + $e); + } + } + + break; + + case GROUP_ALL_USERS: + $group = identity::registered_users(); + $messages[] = t("Group 'Registered' was converted to '%name'", array("name" => $group->name)); + break; + + case GROUP_SITE_ADMINS: + $messages[] = t("Group 'Admin' does not exist in Gallery 3, skipping"); + break; // This is not a group in G3 + + case GROUP_EVERYBODY: + $group = identity::everybody(); + $messages[] = t("Group 'Everybody' was converted to '%name'", array("name" => $group->name)); + break; + } + + if (isset($group)) { + self::set_map($g2_group->getId(), $group->id, "group"); + } + + return $messages; + } + + /** + * Import a single user. + */ + static function import_user(&$queue) { + $messages = array(); + $g2_user_id = array_shift($queue); + if (self::map($g2_user_id)) { + return t("User with id: %id already imported, skipping", + array("id" => $g2_user_id)); + } + + if (g2(GalleryCoreApi::isAnonymousUser($g2_user_id))) { + self::set_map($g2_user_id, identity::guest()->id, "group"); + return t("Skipping anonymous user"); + } + + $g2_admin_group_id = + g2(GalleryCoreApi::getPluginParameter("module", "core", "id.adminGroup")); + try { + $g2_user = g2(GalleryCoreApi::loadEntitiesById($g2_user_id)); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to import Gallery 2 user with id: %id\n%exception", + array("id" => $g2_user_id, "exception" => (string)$e)), + $e); + } + $g2_groups = g2(GalleryCoreApi::fetchGroupsForUser($g2_user->getId())); + + $user = identity::lookup_user_by_name($g2_user->getUsername()); + if ($user) { + $messages[] = t("Loaded existing user: '%name'.", array("name" => $user->name)); + } else { + $email = $g2_user->getEmail(); + if (empty($email) || !valid::email($email)) { + $email = "unknown@unknown.com"; + } + try { + $user = identity::create_user($g2_user->getUserName(), $g2_user->getFullName(), + // Note: The API expects a password in cleartext. + // Just use the hashed password as an unpredictable + // value here. The user will have to reset the password. + $g2_user->getHashedPassword(), $email); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to create user: '%name' (id: %id)", + array("name" => $g2_user->getUserName(), "id" => $g2_user_id)), + $e, $messages); + } + if (class_exists("User_Model") && $user instanceof User_Model) { + // This will work if G2's password is a PasswordHash password as well. + $user->hashed_password = $g2_user->getHashedPassword(); + } + $messages[] = t("Created user: '%name'.", array("name" => $user->name)); + if ($email == "unknown@unknown.com") { + $messages[] = t("Fixed invalid email (was '%invalid_email')", + array("invalid_email" => $g2_user->getEmail())); + } + } + + $user->locale = $g2_user->getLanguage(); + foreach ($g2_groups as $g2_group_id => $g2_group_name) { + if ($g2_group_id == $g2_admin_group_id) { + $user->admin = true; + $messages[] = t("Added 'admin' flag to user"); + } else { + $group = identity::lookup_group(self::map($g2_group_id)); + $user->add($group); + $messages[] = t("Added user to group '%group'.", array("group" => $group->name)); + } + } + + try { + $user->save(); + self::set_map($g2_user->getId(), $user->id, "user"); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to create user: '%name'", array("name" => $user->name)), + $e, $messages); + } + + return $messages; + } + + /** + * Import a single album. + */ + static function import_album(&$queue) { + // The queue is a set of nested associative arrays where the key is the album id and the + // value is an array of similar arrays. We'll do a breadth first tree traversal using the + // queue to keep our state. Doing it breadth first means that the parent will be created by + // the time we get to the child. + + // Dequeue the current album and enqueue its children + list($g2_album_id, $children) = each($queue); + unset($queue[$g2_album_id]); + foreach ($children as $key => $value) { + $queue[$key] = $value; + } + + if (self::map($g2_album_id)) { + return; + } + + try { + // Load the G2 album item, and figure out its parent in G3. + $g2_album = g2(GalleryCoreApi::loadEntitiesById($g2_album_id)); + } catch (Exception $e) { + return t("Failed to load Gallery 2 album with id: %id\n%exception", + array("id" => $g2_album_id, "exception" => (string)$e)); + } + + if ($g2_album->getParentId() == null) { + $album = item::root(); + } else { + $parent_album = ORM::factory("item", self::map($g2_album->getParentId())); + + $album = ORM::factory("item"); + $album->type = "album"; + $album->parent_id = self::map($g2_album->getParentId()); + + g2_import::set_album_values($album, $g2_album); + + try { + $album->save(); + } catch (Exception $e) { + throw new G2_Import_Exception( + t("Failed to import Gallery 2 album with id %id and name %name.", + array("id" => $g2_album_id, "name" => $album->name)), + $e); + } + + self::import_keywords_as_tags($g2_album->getKeywords(), $album); + } + + self::set_map( + $g2_album_id, $album->id, + "album", + self::g2_url(array("view" => "core.ShowItem", "itemId" => $g2_album->getId()))); + + self::_import_permissions($g2_album, $album); + } + + /** + * Transfer over all the values from a G2 album to a G3 album. + */ + static function set_album_values($album, $g2_album) { + $album->name = $g2_album->getPathComponent(); + $album->title = self::_decode_html_special_chars($g2_album->getTitle()); + $album->title or $album->title = $album->name; + $album->description = self::_decode_html_special_chars(self::extract_description($g2_album)); + $album->owner_id = self::map($g2_album->getOwnerId()); + try { + $album->view_count = (int) g2(GalleryCoreApi::fetchItemViewCount($g2_album->getId())); + } catch (Exception $e) { + // @todo log + $album->view_count = 0; + } + $album->created = $g2_album->getCreationTimestamp(); + + $order_map = array( + "originationTimestamp" => "captured", + "creationTimestamp" => "created", + "description" => "description", + "modificationTimestamp" => "updated", + "orderWeight" => "weight", + "pathComponent" => "name", + "summary" => "description", + "title" => "title", + "viewCount" => "view_count"); + $direction_map = array( + 1 => "ASC", + ORDER_ASCENDING => "ASC", + ORDER_DESCENDING => "DESC"); + // G2 sorts can either be or |. Right now we can't + // map presorts so ignore them. + $g2_order = explode("|", $g2_album->getOrderBy() . ""); + $g2_order = end($g2_order); + if (empty($g2_order)) { + $g2_order = g2(GalleryCoreApi::getPluginParameter('module', 'core', 'default.orderBy')); + } + $g2_order_direction = explode("|", $g2_album->getOrderDirection() . ""); + $g2_order_direction = $g2_order_direction[0]; + if (empty($g2_order_direction)) { + $g2_order_direction = + g2(GalleryCoreApi::getPluginParameter('module', 'core', 'default.orderDirection')); + } + if (array_key_exists($g2_order, $order_map)) { + $album->sort_column = $order_map[$g2_order]; + $album->sort_order = $direction_map[$g2_order_direction]; + } + } + + /** + * Set the highlight properly for a single album + */ + static function set_album_highlight(&$queue) { + // Dequeue the current album and enqueue its children + list($g2_album_id, $children) = each($queue); + unset($queue[$g2_album_id]); + if (!empty($children)) { + foreach ($children as $key => $value) { + $queue[$key] = $value; + } + } + + $messages = array(); + $g3_album_id = self::map($g2_album_id); + if (!$g3_album_id) { + return t("Album with id: %id not imported", array("id" => $g3_album_id)); + } + + $table = g2(GalleryCoreApi::fetchThumbnailsByItemIds(array($g2_album_id))); + if (isset($table[$g2_album_id])) { + // Backtrack the source id to an item + $orig_g2_source = $g2_source = $table[$g2_album_id]; + while (GalleryUtilities::isA($g2_source, "GalleryDerivative")) { + $g2_source = g2(GalleryCoreApi::loadEntitiesById($g2_source->getDerivativeSourceId())); + } + $item_id = self::map($g2_source->getId()); + if ($item_id) { + $item = ORM::factory("item", $item_id); + $g3_album = ORM::factory("item", $g3_album_id); + $g3_album->album_cover_item_id = $item->id; + $g3_album->thumb_dirty = 1; + try { + $g3_album->view_count = (int) g2(GalleryCoreApi::fetchItemViewCount($g2_album_id)); + } catch (Exception $e) { + $g3_album->view_count = 0; + } + try { + $g3_album->save(); + graphics::generate($g3_album); + } catch (Exception $e) { + return (string) new G2_Import_Exception( + t("Failed to generate an album highlight for album '%name'.", + array("name" => $g3_album->name)), + $e); + } + + self::set_map( + $orig_g2_source->getId(), $g3_album->id, + "thumbnail", + self::g2_url(array("view" => "core.DownloadItem", "itemId" => $orig_g2_source->getId()))); + } + } + } + + /** + * Import a single photo or movie. + */ + static function import_item(&$queue) { + $g2_item_id = array_shift($queue); + + if (self::map($g2_item_id)) { + return; + } + + try { + self::$current_g2_item = $g2_item = g2(GalleryCoreApi::loadEntitiesById($g2_item_id)); + $g2_path = g2($g2_item->fetchPath()); + } catch (Exception $e) { + return t("Failed to import Gallery 2 item with id: %id\n%exception", + array("id" => $g2_item_id, "exception" => (string)$e)); + } + + $parent = ORM::factory("item", self::map($g2_item->getParentId())); + + $g2_type = $g2_item->getEntityType(); + $corrupt = 0; + if (!file_exists($g2_path)) { + // If the Gallery 2 source image isn't available, this operation is going to fail. That can + // happen in cases where there's corruption in the source Gallery 2. In that case, fall + // back on using a broken image. It's important that we import *something* otherwise + // anything that refers to this item in Gallery 2 will have a dangling pointer in Gallery 3 + // + // Note that this will change movies to be photos, if there's a broken movie. Hopefully + // this case is rare enough that we don't need to take any heroic action here. + g2_import::log( + t("%path missing in import; replacing it with a placeholder", array("path" => $g2_path))); + $g2_path = MODPATH . "g2_import/data/broken-image.gif"; + $g2_type = "GalleryPhotoItem"; + $corrupt = 1; + } + + $messages = array(); + switch ($g2_type) { + case "GalleryPhotoItem": + if (!in_array($g2_item->getMimeType(), array("image/jpeg", "image/gif", "image/png"))) { + Kohana_Log::add("alert", "$g2_path is an unsupported image type; using a placeholder gif"); + $messages[] = t("'%path' is an unsupported image type, using a placeholder", + array("path" => $g2_path)); + $g2_path = MODPATH . "g2_import/data/broken-image.gif"; + $corrupt = 1; + } + try { + $item = ORM::factory("item"); + $item->type = "photo"; + $item->parent_id = $parent->id; + $item->set_data_file($g2_path); + $item->name = $g2_item->getPathComponent(); + $item->title = self::_decode_html_special_chars($g2_item->getTitle()); + $item->title or $item->title = $item->name; + $item->description = self::_decode_html_special_chars(self::extract_description($g2_item)); + $item->owner_id = self::map($g2_item->getOwnerId()); + $item->save(); + + // If the item has a preferred derivative with a rotation, then rotate this image + // accordingly. Should we obey scale rules as well? I vote no because rotation is less + // destructive -- you lose too much data from scaling. + $g2_preferred = g2(GalleryCoreApi::fetchPreferredSource($g2_item)); + if ($g2_preferred && $g2_preferred instanceof GalleryDerivative) { + if (preg_match("/rotate\|(-?\d+)/", $g2_preferred->getDerivativeOperations(), $matches)) { + $tmpfile = tempnam(TMPPATH, "rotate"); + gallery_graphics::rotate($item->file_path(), $tmpfile, array("degrees" => $matches[1]), $item); + $item->set_data_file($tmpfile); + $item->save(); + unlink($tmpfile); + } + } + } catch (Exception $e) { + $exception_info = (string) new G2_Import_Exception( + t("Corrupt image '%path'", array("path" => $g2_path)), + $e, $messages); + Kohana_Log::add("alert", "Corrupt image $g2_path\n" . $exception_info); + $messages[] = $exception_info; + $corrupt = 1; + $item = null; + } + break; + + case "GalleryMovieItem": + // @todo we should transcode other types into FLV + if (in_array($g2_item->getMimeType(), array("video/mp4", "video/x-flv"))) { + try { + $item = ORM::factory("item"); + $item->type = "movie"; + $item->parent_id = $parent->id; + $item->set_data_file($g2_path); + $item->name = $g2_item->getPathComponent(); + $item->title = self::_decode_html_special_chars($g2_item->getTitle()); + $item->title or $item->title = $item->name; + $item->description = self::_decode_html_special_chars(self::extract_description($g2_item)); + $item->owner_id = self::map($g2_item->getOwnerId()); + $item->save(); + } catch (Exception $e) { + $exception_info = (string) new G2_Import_Exception( + t("Corrupt movie '%path'", array("path" => $g2_path)), + $e, $messages); + Kohana_Log::add("alert", "Corrupt movie $g2_path\n" . $exception_info); + $messages[] = $exception_info; + $corrupt = 1; + $item = null; + } + } else { + Kohana_Log::add("alert", "$g2_path is an unsupported movie type"); + $messages[] = t("'%path' is an unsupported movie type", array("path" => $g2_path)); + $corrupt = 1; + } + + break; + + default: + // Ignore + break; + } + + if (!empty($item)) { + self::import_keywords_as_tags($g2_item->getKeywords(), $item); + } + + $g2_item_url = self::g2_url(array("view" => "core.ShowItem", "itemId" => $g2_item->getId())); + if (isset($item)) { + try { + $item->view_count = (int) g2(GalleryCoreApi::fetchItemViewCount($g2_item_id)); + } catch (Exception $e) { + $view_count = 1; + } + $item->save(); + + self::set_map($g2_item_id, $item->id, "item", $g2_item_url); + + self::set_map($g2_item_id, $item->id, "file", + self::g2_url(array("view" => "core.DownloadItem", "itemId" => $g2_item_id))); + + $derivatives = g2(GalleryCoreApi::fetchDerivativesByItemIds(array($g2_item_id))); + if (!empty($derivatives[$g2_item_id])) { + foreach ($derivatives[$g2_item_id] as $derivative) { + switch ($derivative->getDerivativeType()) { + case DERIVATIVE_TYPE_IMAGE_THUMBNAIL: $resource_type = "thumbnail"; break; + case DERIVATIVE_TYPE_IMAGE_RESIZE: $resource_type = "resize"; break; + case DERIVATIVE_TYPE_IMAGE_PREFERRED: $resource_type = "full"; break; + } + + self::set_map( + $derivative->getId(), $item->id, + $resource_type, + self::g2_url(array("view" => "core.DownloadItem", "itemId" => $derivative->getId()))); + } + } + } + + if ($corrupt) { + if (!empty($item)) { + $title = $g2_item->getTitle(); + $title or $title = $g2_item->getPathComponent(); + $messages[] = + t("%title from Gallery 2 could not be processed; (imported as %title)", + array("g2_url" => $g2_item_url, + "g3_url" => $item->url(), + "title" => $title)); + } else { + $messages[] = + t("%title from Gallery 2 could not be processed", + array("g2_url" => $g2_item_url, "title" => $g2_item->getTitle())); + } + } + + self::$current_g2_item = null; + return $messages; + } + + /** + * g2 encoded'&', '"', '<' and '>' as '&', '"', '<' and '>' respectively. + * This function undoes that encoding. + */ + private static function _decode_html_special_chars($value) { + return str_replace(array("&", """, "<", ">"), + array("&", "\"", "<", ">"), $value); + } + + private static $_permission_map = array( + "core.view" => "view", + "core.viewSource" => "view_full", + "core.edit" => "edit", + "core.addDataItem" => "add", + "core.addAlbumItem" => "add"); + + /** + * Imports G2 permissions, mapping G2's permission model to G3's + * much simplified permissions. + * + * - Ignores user permissions, G3 only supports group permissions. + * - Ignores item permissions, G3 only supports album permissions. + * + * G2 permission -> G3 permission + * --------------------------------- + * core.view view + * core.viewSource view_full + * core.edit edit + * core.addDataItem add + * core.addAlbumItem add + * core.viewResizes + * core.delete + * comment.* + */ + private static function _import_permissions($g2_album, $g3_album) { + // No need to do anything if this album has the same G2 ACL as its parent. + if ($g2_album->getParentId() != null && + g2(GalleryCoreApi::fetchAccessListId($g2_album->getId())) == + g2(GalleryCoreApi::fetchAccessListId($g2_album->getParentId()))) { + return; + } + + $granted_permissions = self::_map_permissions($g2_album->getId()); + + if ($g2_album->getParentId() == null) { + // Compare to current permissions, and change them if necessary. + $g3_parent_album = item::root(); + } else { + $g3_parent_album = $g3_album->parent(); + } + $granted_parent_permissions = array(); + $perm_ids = array_unique(array_values(self::$_permission_map)); + foreach (identity::groups() as $group) { + $granted_parent_permissions[$group->id] = array(); + foreach ($perm_ids as $perm_id) { + if (access::group_can($group, $perm_id, $g3_parent_album)) { + $granted_parent_permissions[$group->id][$perm_id] = 1; + } + } + } + + // Note: Only registering permissions if they're not the same as + // the inherited ones. + foreach ($granted_permissions as $group_id => $permissions) { + if (!isset($granted_parent_permissions[$group_id])) { + foreach (array_keys($permissions) as $perm_id) { + access::allow(identity::lookup_group($group_id), $perm_id, $g3_album); + } + } else if ($permissions != $granted_parent_permissions[$group_id]) { + $parent_permissions = $granted_parent_permissions[$group_id]; + // @todo Probably worth caching the group instances. + $group = identity::lookup_group($group_id); + // Note: Cannot use array_diff_key. + foreach (array_keys($permissions) as $perm_id) { + if (!isset($parent_permissions[$perm_id])) { + access::allow($group, $perm_id, $g3_album); + } + } + foreach (array_keys($parent_permissions) as $perm_id) { + if (!isset($permissions[$perm_id])) { + access::deny($group, $perm_id, $g3_album); + } + } + } + } + + foreach ($granted_parent_permissions as $group_id => $parent_permissions) { + if (isset($granted_permissions[$group_id])) { + continue; // handled above + } + $group = identity::lookup_group($group_id); + foreach (array_keys($parent_permissions) as $perm_id) { + access::deny($group, $perm_id, $g3_album); + } + } + } + + /** + * Loads all the granted group G2 permissions for a specific + * album and returns an array with G3 groups ids and G3 permission ids. + */ + private static function _map_permissions($g2_album_id) { + $g2_permissions = g2(GalleryCoreApi::fetchAllPermissionsForItem($g2_album_id)); + $permissions = array(); + foreach ($g2_permissions as $entry) { + // @todo Do something about user permissions? E.g. map G2's user albums + // to a user-specific group in G3? + if (!isset($entry["groupId"])) { + continue; + } + $g2_permission_id = $entry["permission"]; + if (!isset(self::$_permission_map[$g2_permission_id])) { + continue; + } + $group_id = self::map($entry["groupId"]); + if ($group_id == null) { + // E.g. the G2 admin group isn't mapped. + continue; + } + $permission_id = self::$_permission_map[$g2_permission_id]; + if (!isset($permissions[$group_id])) { + $permissions[$group_id] = array(); + } + $permissions[$group_id][$permission_id] = 1; + } + return $permissions; + } + + /** + * Import a single comment. + */ + static function import_comment(&$queue) { + $g2_comment_id = array_shift($queue); + + try { + $g2_comment = g2(GalleryCoreApi::loadEntitiesById($g2_comment_id)); + } catch (Exception $e) { + return t("Failed to load Gallery 2 comment with id: %id\%exception", + array("id" => $g2_comment_id, "exception" => (string)$e)); + } + + if ($id = self::map($g2_comment->getId())) { + if (ORM::factory("comment", $id)->loaded()) { + // Already imported and still exists + return; + } + // This comment was already imported, but now it no longer exists. Import it again, per + // ticket #1736. + } + + $item_id = self::map($g2_comment->getParentId()); + if (empty($item_id)) { + // Item was not mapped. + return; + } + + $text = join("\n", array($g2_comment->getSubject(), $g2_comment->getComment())); + $text = html_entity_decode($text); + + // Just import the fields we know about. Do this outside of the comment API for now so that + // we don't trigger spam filtering events + $comment = ORM::factory("comment"); + $comment->author_id = self::map($g2_comment->getCommenterId()); + $comment->guest_name = ""; + if ($comment->author_id == identity::guest()->id) { + $comment->guest_name = $g2_comment->getAuthor(); + $comment->guest_name or $comment->guest_name = (string) t("Anonymous coward"); + $comment->guest_email = "unknown@nobody.com"; + } + $comment->item_id = $item_id; + $comment->text = self::_transform_bbcode($text); + $comment->state = "published"; + $comment->server_http_host = $g2_comment->getHost(); + try { + $comment->save(); + } catch (Exception $e) { + return (string) new G2_Import_Exception( + t("Failed to import comment with id: %id.", + array("id" => $g2_comment_id)), + $e); + } + + self::set_map($g2_comment->getId(), $comment->id, "comment"); + + // Backdate the creation date. We can't do this at creation time because + // Comment_Model::save() will override it. Leave the updated date alone + // so that if the comments get marked as spam, they don't immediately get + // flushed (see ticket #1736) + db::update("comments") + ->set("created", $g2_comment->getDate()) + ->where("id", "=", $comment->id) + ->execute(); + } + + /** + * Import all the tags for a single item + */ + static function import_tags_for_item(&$queue) { + if (!module::is_active("tag")) { + return t("Gallery 3 tag module is inactive, no tags will be imported"); + } + + GalleryCoreApi::requireOnce("modules/tags/classes/TagsHelper.class"); + $g2_item_id = array_shift($queue); + $g3_item = ORM::factory("item", self::map($g2_item_id)); + if (!$g3_item->loaded()) { + return; + } + + try { + $tag_names = array_values(g2(TagsHelper::getTagsByItemId($g2_item_id))); + } catch (Exception $e) { + return t("Failed to import Gallery 2 tags for item with id: %id\n%exception", + array("id" => $g2_item_id, "exception" => (string)$e)); + } + + foreach ($tag_names as $tag_name) { + tag::add($g3_item, $tag_name); + } + + // Tag operations are idempotent so we don't need to map them. Which is good because we don't + // have an id for each individual tag mapping anyway so it'd be hard to set up the mapping. + } + + static function import_keywords_as_tags($keywords, $item) { + // Keywords in G2 are free form. So we don't know what our user used as a separator. Try to + // be smart about it. If we see a comma or a semicolon, expect the keywords to be separated + // by that delimeter. Otherwise, use space as the delimiter. + if (strpos($keywords, ";")) { + $delim = ";"; + } else if (strpos($keywords, ",")) { + $delim = ","; + } else { + $delim = " "; + } + + foreach (preg_split("/$delim/", $keywords) as $keyword) { + $keyword = trim($keyword); + if ($keyword) { + tag::add($item, $keyword); + } + } + } + + /** + * If the thumbnails and resizes created for the Gallery 2 photo match the dimensions of the + * ones we expect to create for Gallery 3, then copy the files over instead of recreating them. + */ + static function copy_matching_thumbnails_and_resizes($item) { + // We only operate on items that are being imported + if (empty(self::$current_g2_item)) { + return; + } + + // Precaution: if the Gallery 2 item was watermarked, or we have the Gallery 3 watermark module + // active then we'd have to do something a lot more sophisticated here. For now, just skip + // this step in those cases. + // @todo we should probably use an API here, eventually. + if (module::is_active("watermark") && module::get_var("watermark", "name")) { + return; + } + + // For now just do the copy for photos and movies. Albums are tricky because we're may not + // yet be setting their album cover properly. + // @todo implement this for albums also + if (!$item->is_movie() && !$item->is_photo()) { + return; + } + + $g2_item_id = self::$current_g2_item->getId(); + $derivatives = g2(GalleryCoreApi::fetchDerivativesByItemIds(array($g2_item_id))); + + $target_thumb_size = module::get_var("gallery", "thumb_size"); + $target_resize_size = module::get_var("gallery", "resize_size"); + if (!empty($derivatives[$g2_item_id])) { + foreach ($derivatives[$g2_item_id] as $derivative) { + if ($derivative->getPostFilterOperations()) { + // Let's assume for now that this is a watermark operation, which we can't handle. + continue; + } + + if ($derivative->getDerivativeType() == DERIVATIVE_TYPE_IMAGE_THUMBNAIL && + $item->thumb_dirty && + ($derivative->getWidth() == $target_thumb_size || + $derivative->getHeight() == $target_thumb_size)) { + if (@copy(g2($derivative->fetchPath()), $item->thumb_path())) { + $item->thumb_height = $derivative->getHeight(); + $item->thumb_width = $derivative->getWidth(); + $item->thumb_dirty = 0; + } + } + + if ($derivative->getDerivativeType() == DERIVATIVE_TYPE_IMAGE_RESIZE && + $item->resize_dirty && + ($derivative->getWidth() == $target_resize_size || + $derivative->getHeight() == $target_resize_size)) { + if (@copy(g2($derivative->fetchPath()), $item->resize_path())) { + $item->resize_height = $derivative->getHeight(); + $item->resize_width = $derivative->getWidth(); + $item->resize_dirty = 0; + } + } + } + } + try { + $item->save(); + } catch (Exception $e) { + return (string) new G2_Import_Exception( + t("Failed to copy thumbnails and resizes for item '%name' (Gallery 2 id: %id)", + array("name" => $item->name, "id" => $g2_item_id)), + $e); + } + } + + /** + * Figure out the most common resize and thumb sizes in Gallery 2 so that we can tell the admin + * what theme settings to set to make the import go faster. If we match up the sizes then we + * can just copy over derivatives instead of running graphics toolkit operations. + */ + static function common_sizes() { + global $gallery; + foreach (array("resize" => DERIVATIVE_TYPE_IMAGE_RESIZE, + "thumb" => DERIVATIVE_TYPE_IMAGE_THUMBNAIL) as $type => $g2_enum) { + $results = g2($gallery->search( + "SELECT COUNT(*) AS c, [GalleryDerivativeImage::width] " . + "FROM [GalleryDerivativeImage], [GalleryDerivative] " . + "WHERE [GalleryDerivativeImage::id] = [GalleryDerivative::id] " . + " AND [GalleryDerivative::derivativeType] = ? " . + " AND [GalleryDerivativeImage::width] >= [GalleryDerivativeImage::height] " . + "GROUP BY [GalleryDerivativeImage::width] " . + "ORDER by c DESC", + array($g2_enum), + array("limit" => array(1)))); + $row = $results->nextResult(); + $sizes[$type] = array("size" => $row[1], "count" => $row[0]); + + $results = g2($gallery->search( + "SELECT COUNT(*) AS c, [GalleryDerivativeImage::height] " . + "FROM [GalleryDerivativeImage], [GalleryDerivative] " . + "WHERE [GalleryDerivativeImage::id] = [GalleryDerivative::id] " . + " AND [GalleryDerivative::derivativeType] = ? " . + " AND [GalleryDerivativeImage::height] > [GalleryDerivativeImage::width] " . + "GROUP BY [GalleryDerivativeImage::height] " . + "ORDER by c DESC", + array($g2_enum), + array("limit" => array(1)))); + $row = $results->nextResult(); + // Compare the counts. If the best fitting height does not match the best fitting width, + // then pick the one with the largest count. Otherwise, sum them. + if ($sizes[$type]["size"] != $row[1]) { + if ($row[0] > $sizes[$type]["count"]) { + $sizes[$type] = array("size" => $row[1], "count" => $row[0]); + } + } else { + $sizes[$type]["count"] += $row[0]; + } + + $results = g2($gallery->search( + "SELECT COUNT(*) FROM [GalleryDerivative] WHERE [GalleryDerivative::derivativeType] = ?", + array($g2_enum))); + $row = $results->nextResult(); + $sizes[$type]["total"] = $row[0]; + } + + return $sizes; + } + + /** + * Sensibly concatenate Gallery 2 summary and descriptions into a single field. + */ + static function extract_description($g2_item) { + // If the summary is a subset of the description just import the description, else import both. + $g2_summary = $g2_item->getSummary(); + $g2_description = $g2_item->getDescription(); + if (!$g2_summary || + $g2_summary == $g2_description || + strstr($g2_description, $g2_summary) !== false) { + $description = $g2_description; + } else { + $description = $g2_summary . " " . $g2_description; + } + return self::_transform_bbcode($description); + } + + static $bbcode_mappings = array( + "#\\[b\\](.*?)\\[/b\\]#" => "$1", + "#\\[i\\](.*?)\\[/i\\]#" => "$1", + "#\\[u\\](.*?)\\[/u\\]#" => "$1", + "#\\[s\\](.*?)\\[/s\\]#" => "$1", + "#\\[url\\](.*?)\[/url\\]#" => "$1", + "#\\[url=(.*?)\\](.*?)\[/url\\]#" => "$2", + "#\\[img\\](.*?)\\[/img\\]#" => "", + "#\\[quote\\](.*?)\\[/quote\\]#" => "

    $1

    ", + "#\\[code\\](.*?)\\[/code\\]#" => "
    $1
    ", + "#\\[size=([^\\[]*)\\]([^\\[]*)\\[/size\\]#" => "$2", + "#\\[color=([^\\[]*)\\]([^\\[]*)\\[/color\\]#" => "$2/font>", + "#\\[ul\\](.*?)\\/ul\\]#" => "
      $1
    ", + "#\\[li\\](.*?)\\[/li\\]#" => "
  • $1
  • ", + ); + private static function _transform_bbcode($text) { + if (strpos($text, "[") !== false) { + $text = preg_replace(array_keys(self::$bbcode_mappings), array_values(self::$bbcode_mappings), + $text); + } + return $text; + } + + /** + * Get a set of photo and movie ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of photos/movies to import. + */ + static function get_item_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryItem::id] " . + "FROM [GalleryEntity], [GalleryItem] " . + "WHERE [GalleryEntity::id] = [GalleryItem::id] " . + "AND [GalleryEntity::entityType] IN ('GalleryPhotoItem', 'GalleryMovieItem') " . + "AND [GalleryItem::id] > ? " . + "ORDER BY [GalleryItem::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of comment ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of comments to import. + */ + static function get_comment_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryComment::id] " . + "FROM [GalleryComment] " . + "WHERE [GalleryComment::publishStatus] = 0 " . // 0 == COMMENT_PUBLISH_STATUS_PUBLISHED + "AND [GalleryComment::id] > ? " . + "ORDER BY [GalleryComment::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of comment ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of comments to import. + */ + static function get_tag_item_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT DISTINCT([TagItemMap::itemId]) FROM [TagItemMap] " . + "WHERE [TagItemMap::itemId] > ? " . + "ORDER BY [TagItemMap::itemId] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of user ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of users to import. + */ + static function get_user_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryUser::id] " . + "FROM [GalleryUser] " . + "WHERE [GalleryUser::id] > ? " . + "ORDER BY [GalleryUser::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Get a set of group ids from Gallery 2 greater than $min_id. We use this to get the + * next chunk of groups to import. + */ + static function get_group_ids($min_id) { + global $gallery; + + $ids = array(); + $results = g2($gallery->search( + "SELECT [GalleryGroup::id] " . + "FROM [GalleryGroup] " . + "WHERE [GalleryGroup::id] > ? " . + "ORDER BY [GalleryGroup::id] ASC", + array($min_id), + array("limit" => array("count" => 100)))); + while ($result = $results->nextResult()) { + $ids[] = $result[0]; + } + return $ids; + } + + /** + * Look in our map to find the corresponding Gallery 3 id for the given Gallery 2 id. + */ + static function map($g2_id) { + if (!array_key_exists($g2_id, self::$map)) { + $g2_map = ORM::factory("g2_map")->where("g2_id", "=", $g2_id)->find(); + self::$map[$g2_id] = $g2_map->loaded() ? $g2_map->g3_id : null; + } + + return self::$map[$g2_id]; + } + + /** + * Associate a Gallery 2 id with a Gallery 3 item id. + */ + static function set_map($g2_id, $g3_id, $resource_type, $g2_url=null) { + self::clear_map($g2_id, $resource_type); + $g2_map = ORM::factory("g2_map"); + $g2_map->g3_id = $g3_id; + $g2_map->g2_id = $g2_id; + $g2_map->resource_type = $resource_type; + + if (strpos($g2_url, self::$g2_base_url) === 0) { + $g2_url = substr($g2_url, strlen(self::$g2_base_url)); + } + + $g2_map->g2_url = $g2_url; + $g2_map->save(); + self::$map[$g2_id] = $g3_id; + } + + /** + * Remove all map entries associated with the given Gallery 2 id. + */ + static function clear_map($g2_id, $resource_type) { + db::build() + ->delete("g2_maps") + ->where("g2_id", "=", $g2_id) + ->where("resource_type", "=", $resource_type) + ->execute(); + } + + static function log($msg) { + message::warning($msg); + Kohana_Log::add("alert", $msg); + } + + static function g2_url($params) { + global $gallery; + return $gallery->getUrlGenerator()->generateUrl( + $params, + array("forceSessionId" => false, + "htmlEntities" => false, + "urlEncode" => false, + "useAuthToken" => false)); + } + + static function lower_error_reporting() { + // Gallery 2 was not designed to run in E_STRICT mode and will barf out errors. So dial down + // the error reporting when we make G2 calls. + self::$error_reporting = error_reporting(error_reporting() & ~E_STRICT); + } + + static function restore_error_reporting() { + error_reporting(self::$error_reporting); + } +} + +/** + * Wrapper around Gallery 2 calls. We expect the first response to be a GalleryStatus object. If + * it's not null, then throw an exception. Strip the GalleryStatus object out of the result and + * if there's only an array of 1 return value, turn it into a scalar. This allows us to simplify + * this pattern: + * list ($ret, $foo) = GalleryCoreApi::someCall(); + * if ($ret) { handle_error(); } + * + * to: + * $foo = g2(GalleryCoreApi::someCall()); + */ +function g2() { + $args = func_get_arg(0); + $ret = is_array($args) ? array_shift($args) : $args; + if ($ret) { + Kohana_Log::add("error", "Gallery 2 call failed with: " . $ret->getAsText()); + throw new Exception("@todo G2_FUNCTION_FAILED"); + } + if (count($args) == 1) { + return $args[0]; + } else { + return $args; + } +} diff --git a/modules/g2_import/helpers/g2_import_event.php b/modules/g2_import/helpers/g2_import_event.php new file mode 100644 index 0000000..b985281 --- /dev/null +++ b/modules/g2_import/helpers/g2_import_event.php @@ -0,0 +1,40 @@ +delete("g2_maps") + ->where("g3_id", "=", $item->id) + ->execute(); + } + + static function item_created($item) { + g2_import::copy_matching_thumbnails_and_resizes($item); + } + + static function admin_menu($menu, $theme) { + $menu + ->get("settings_menu") + ->append(Menu::factory("link") + ->id("g2_import") + ->label(t("Gallery 2 import")) + ->url(url::site("admin/g2_import"))); + } +} diff --git a/modules/g2_import/helpers/g2_import_installer.php b/modules/g2_import/helpers/g2_import_installer.php new file mode 100644 index 0000000..c756981 --- /dev/null +++ b/modules/g2_import/helpers/g2_import_installer.php @@ -0,0 +1,50 @@ +query("CREATE TABLE IF NOT EXISTS {g2_maps} ( + `id` int(9) NOT NULL auto_increment, + `g2_id` int(9) NOT NULL, + `g3_id` int(9) NOT NULL, + `g2_url` varchar(255) default NULL, + `resource_type` varchar(64) default NULL, + PRIMARY KEY (`id`), + KEY `g2_url` (`g2_url`), + KEY `g2_id` (`g2_id`)) + DEFAULT CHARSET=utf8;"); + + mkdir(VARPATH . "modules/g2_import"); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("ALTER TABLE {g2_maps} ADD COLUMN `g2_url` VARCHAR(255)"); + $db->query("ALTER TABLE {g2_maps} ADD COLUMN `resource_type` VARCHAR(64)"); + $db->query("ALTER TABLE {g2_maps} ADD KEY `g2_url` (`g2_url`)"); + module::set_version("g2_import", $version = 2); + } + } + + static function uninstall() { + @dir::unlink(VARPATH . "modules/g2_import"); + } +} diff --git a/modules/g2_import/helpers/g2_import_task.php b/modules/g2_import/helpers/g2_import_task.php new file mode 100644 index 0000000..07eacc4 --- /dev/null +++ b/modules/g2_import/helpers/g2_import_task.php @@ -0,0 +1,225 @@ +callback("g2_import_task::import") + ->name(t("Import from Gallery 2")) + ->description( + t("Gallery %version detected", array("version" => $version))) + ->severity(log::SUCCESS)); + } + + return array(); + } + + static function import($task) { + g2_import::lower_error_reporting(); + + $start = microtime(true); + g2_import::init(); + + $stats = $task->get("stats"); + $done = $task->get("done"); + $total = $task->get("total"); + $completed = $task->get("completed"); + $mode = $task->get("mode"); + $queue = $task->get("queue"); + if (!isset($mode)) { + $stats = g2_import::g2_stats(); + $stats["items"] = $stats["photos"] + $stats["movies"]; + unset($stats["photos"]); + unset($stats["movies"]); + $stats["highlights"] = $stats["albums"]; + $task->set("stats", $stats); + + $task->set("total", $total = array_sum(array_values($stats))); + $completed = 0; + $mode = 0; + + $done = array(); + foreach (array_keys($stats) as $key) { + $done[$key] = 0; + } + $task->set("done", $done); + + // Ensure G2 ACLs are compacted to speed up import. + g2(GalleryCoreApi::compactAccessLists()); + } + + $modes = array("groups", "users", "albums", "items", "comments", "tags", "highlights", "done"); + while (!$task->done && microtime(true) - $start < 1.5) { + if ($done[$modes[$mode]] == $stats[$modes[$mode]]) { + // Nothing left to do for this mode. Advance. + $mode++; + $task->set("last_id", 0); + $queue = array(); + + // Start the loop from the beginning again. This way if we get to a mode that requires no + // actions (eg, if the G2 comments module isn't installed) we won't try to do any comments + // queries.. in the next iteration we'll just skip over that mode. + if ($modes[$mode] != "done") { + continue; + } + } + + switch($modes[$mode]) { + case "groups": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_group_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_group($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing groups (%count of %total)", + array("count" => $done["groups"] + 1, "total" => $stats["groups"])); + break; + + case "users": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_user_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_user($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing users (%count of %total)", + array("count" => $done["users"] + 1, "total" => $stats["users"])); + break; + + case "albums": + if (empty($queue)) { + $g2_root_id = g2(GalleryCoreApi::getDefaultAlbumId()); + $tree = g2(GalleryCoreApi::fetchAlbumTree()); + $task->set("queue", $queue = array($g2_root_id => $tree)); + + // Update the root album to reflect the Gallery2 root album. + $root_album = item::root(); + g2_import::set_album_values( + $root_album, g2(GalleryCoreApi::loadEntitiesById($g2_root_id))); + $root_album->save(); + } + $log_message = g2_import::import_album($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing albums (%count of %total)", + array("count" => $done["albums"] + 1, "total" => $stats["albums"])); + break; + + case "items": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_item_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_item($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing photos (%count of %total)", + array("count" => $done["items"] + 1, "total" => $stats["items"])); + break; + + case "comments": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_comment_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_comment($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing comments (%count of %total)", + array("count" => $done["comments"] + 1, "total" => $stats["comments"])); + + break; + + case "tags": + if (empty($queue)) { + $task->set("queue", $queue = g2_import::get_tag_item_ids($task->get("last_id", 0))); + $task->set("last_id", end($queue)); + } + $log_message = g2_import::import_tags_for_item($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Importing tags (%count of %total)", + array("count" => $done["tags"] + 1, "total" => $stats["tags"])); + + break; + + case "highlights": + if (empty($queue)) { + $task->set("queue", $queue = g2(GalleryCoreApi::fetchAlbumTree())); + } + $log_message = g2_import::set_album_highlight($queue); + if ($log_message) { + $task->log($log_message); + } + $task->status = t( + "Album highlights (%count of %total)", + array("count" => $done["highlights"] + 1, "total" => $stats["highlights"])); + + break; + + case "done": + $task->status = t("Import complete"); + $task->done = true; + $task->state = "success"; + break; + } + + if (!$task->done) { + $done[$modes[$mode]]++; + $completed++; + } + } + + $task->percent_complete = 100 * ($completed / $total); + $task->set("completed", $completed); + $task->set("mode", $mode); + $task->set("queue", $queue); + $task->set("done", $done); + + g2_import::restore_error_reporting(); + } +} diff --git a/modules/g2_import/libraries/G2_Import_Exception.php b/modules/g2_import/libraries/G2_Import_Exception.php new file mode 100644 index 0000000..7732cfd --- /dev/null +++ b/modules/g2_import/libraries/G2_Import_Exception.php @@ -0,0 +1,39 @@ +validation->errors(), true); + } + if ($previous) { + $message .= "\n" . (string) $previous; + } + // The $previous parameter is supported in PHP 5.3.0+. + parent::__construct($message); + } +} \ No newline at end of file diff --git a/modules/g2_import/models/g2_map.php b/modules/g2_import/models/g2_map.php new file mode 100644 index 0000000..5fb566c --- /dev/null +++ b/modules/g2_import/models/g2_map.php @@ -0,0 +1,21 @@ + +css("jquery.autocomplete.css") ?> +script("jquery.autocomplete.js") ?> + + +
    +

    +

    + +

    + + + +
    diff --git a/modules/gallery/config/cache.php b/modules/gallery/config/cache.php new file mode 100644 index 0000000..746e95c --- /dev/null +++ b/modules/gallery/config/cache.php @@ -0,0 +1,50 @@ + File cache is fast and reliable, but requires many filesystem lookups. + * > Database cache can be used to cache items remotely, but is slower. + * > Memcache is very high performance, but prevents cache tags from being used. + * + * params - Driver parameters, specific to each driver. + * + * lifetime - Default lifetime of caches in seconds. By default caches are stored for + * thirty minutes. Specific lifetime can also be set when creating a new cache. + * Setting this to 0 will never automatically delete caches. + * + * requests - Average number of cache requests that will processed before all expired + * caches are deleted. This is commonly referred to as "garbage collection". + * Setting this to 0 or a negative number will disable automatic garbage collection. + */ +$config["default"] = array ( + "driver" => "database", + "params" => null, + "lifetime" => 84600, + "requests" => 1000, + "prefix" => null, +); diff --git a/modules/gallery/config/cookie.php b/modules/gallery/config/cookie.php new file mode 100644 index 0000000..b164701 --- /dev/null +++ b/modules/gallery/config/cookie.php @@ -0,0 +1,48 @@ +admin) { + if (identity::active_user()->guest) { + Session::instance()->set("continue_url", url::abs_current(true)); + url::redirect("login"); + } else { + access::forbidden(); + } + } + + parent::__construct(); + } + + public function __call($controller_name, $args) { + if (Input::instance()->get("reauth_check")) { + return self::_reauth_check(); + } + if (auth::must_reauth_for_admin_area()) { + return self::_prompt_for_reauth($controller_name, $args); + } + + if (request::method() == "post") { + access::verify_csrf(); + } + + if ($controller_name == "index") { + $controller_name = "dashboard"; + } + $controller_name = "Admin_{$controller_name}_Controller"; + if ($args) { + $method = array_shift($args); + } else { + $method = "index"; + } + + if (!class_exists($controller_name) || !method_exists($controller_name, $method)) { + throw new Kohana_404_Exception(); + } + + call_user_func_array(array(new $controller_name, $method), $args); + } + + private static function _reauth_check() { + $session = Session::instance(); + $last_active_auth = $session->get("active_auth_timestamp", 0); + $last_admin_area_activity = $session->get("admin_area_activity_timestamp", 0); + $admin_area_timeout = module::get_var("gallery", "admin_area_timeout"); + + $time_remaining = max($last_active_auth, $last_admin_area_activity) + + $admin_area_timeout - time(); + + $result = new stdClass(); + $result->result = "success"; + if ($time_remaining < 30) { + message::success(t("Automatically logged out of the admin area for your security")); + $result->location = url::abs_site(""); + } + + json::reply($result); + } + + private static function _prompt_for_reauth($controller_name, $args) { + if (request::method() == "get") { + // Avoid anti-phishing protection by passing the url as session variable. + Session::instance()->set("continue_url", url::abs_current(true)); + } + // Save the is_ajax value as we lose it, if set, when we redirect + Session::instance()->set("is_ajax_request", request::is_ajax()); + url::redirect("reauthenticate"); + } +} + diff --git a/modules/gallery/controllers/admin_advanced_settings.php b/modules/gallery/controllers/admin_advanced_settings.php new file mode 100644 index 0000000..267ff14 --- /dev/null +++ b/modules/gallery/controllers/admin_advanced_settings.php @@ -0,0 +1,57 @@ +page_title = t("Advanced settings"); + $view->content = new View("admin_advanced_settings.html"); + $view->content->vars = ORM::factory("var") + ->order_by("module_name") + ->order_by("name") + ->find_all(); + print $view; + } + + public function edit($module_name, $var_name) { + if (module::is_installed($module_name)) { + $value = module::get_var($module_name, $var_name); + $form = new Forge("admin/advanced_settings/save/$module_name/$var_name", "", "post"); + $group = $form->group("edit_var")->label(t("Edit setting")); + $group->input("module_name")->label(t("Module"))->value($module_name)->disabled(1); + $group->input("var_name")->label(t("Setting"))->value($var_name)->disabled(1); + $group->textarea("value")->label(t("Value"))->value($value); + $group->submit("")->value(t("Save")); + print $form; + } + } + + public function save($module_name, $var_name) { + access::verify_csrf(); + + if (module::is_installed($module_name)) { + module::set_var($module_name, $var_name, Input::instance()->post("value")); + message::success( + t("Saved value for %var (%module_name)", + array("var" => $var_name, "module_name" => $module_name))); + + json::reply(array("result" => "success")); + } + } +} diff --git a/modules/gallery/controllers/admin_dashboard.php b/modules/gallery/controllers/admin_dashboard.php new file mode 100644 index 0000000..5317210 --- /dev/null +++ b/modules/gallery/controllers/admin_dashboard.php @@ -0,0 +1,97 @@ +page_title = t("Dashboard"); + $view->content = new View("admin_dashboard.html"); + $view->content->blocks = block_manager::get_html("dashboard_center"); + $view->sidebar = "
    " . + block_manager::get_html("dashboard_sidebar") . + "
    "; + $view->content->obsolete_modules_message = module::get_obsolete_modules_message(); + print $view; + } + + public function add_block() { + access::verify_csrf(); + + $form = gallery_block::get_add_block_form(); + if ($form->validate()) { + list ($module_name, $id) = explode(":", $form->add_block->id->value); + $available = block_manager::get_available_admin_blocks(); + + if ($form->add_block->center->value) { + block_manager::add("dashboard_center", $module_name, $id); + message::success( + t("Added %title block to the dashboard center", + array("title" => $available["$module_name:$id"]))); + } else { + block_manager::add("dashboard_sidebar", $module_name, $id); + message::success( + t("Added %title to the dashboard sidebar", + array("title" => $available["$module_name:$id"]))); + } + } + url::redirect("admin/dashboard"); + } + + public function remove_block($id) { + access::verify_csrf(); + + $blocks_center = block_manager::get_active("dashboard_center"); + $blocks_sidebar = block_manager::get_active("dashboard_sidebar"); + + if (array_key_exists($id, $blocks_sidebar)) { + $deleted = $blocks_sidebar[$id]; + block_manager::remove("dashboard_sidebar", $id); + } else if (array_key_exists($id, $blocks_center)) { + $deleted = $blocks_center[$id]; + block_manager::remove("dashboard_center", $id); + } + + if (!empty($deleted)) { + $available = block_manager::get_available_admin_blocks(); + $title = $available[join(":", $deleted)]; + message::success(t("Removed %title block", array("title" => $title))); + } + + url::redirect("admin"); + } + + public function reorder() { + access::verify_csrf(); + + $active_set = array(); + foreach (array("dashboard_sidebar", "dashboard_center") as $location) { + foreach (block_manager::get_active($location) as $id => $info) { + $active_set[$id] = $info; + } + } + + foreach (array("dashboard_sidebar", "dashboard_center") as $location) { + $new_blocks = array(); + foreach (Input::instance()->get($location, array()) as $id) { + $new_blocks[$id] = $active_set[$id]; + } + block_manager::set_active($location, $new_blocks); + } + } +} diff --git a/modules/gallery/controllers/admin_graphics.php b/modules/gallery/controllers/admin_graphics.php new file mode 100644 index 0000000..a24486f --- /dev/null +++ b/modules/gallery/controllers/admin_graphics.php @@ -0,0 +1,50 @@ +page_title = t("Graphics settings"); + $view->content = new View("admin_graphics.html"); + $view->content->tk = graphics::detect_toolkits(); + $view->content->active = module::get_var("gallery", "graphics_toolkit", "none"); + print $view; + } + + public function choose($toolkit_id) { + access::verify_csrf(); + + if ($toolkit_id != module::get_var("gallery", "graphics_toolkit")) { + $tk = graphics::detect_toolkits(); + module::set_var("gallery", "graphics_toolkit", $toolkit_id); + module::set_var("gallery", "graphics_toolkit_path", $tk->$toolkit_id->dir); + + site_status::clear("missing_graphics_toolkit"); + + $msg = t("Changed graphics toolkit to: %toolkit", array("toolkit" => $tk->$toolkit_id->name)); + message::success($msg); + log::success("graphics", $msg); + + module::event("graphics_toolkit_change", $toolkit_id); + } + + url::redirect("admin/graphics"); + } +} + diff --git a/modules/gallery/controllers/admin_languages.php b/modules/gallery/controllers/admin_languages.php new file mode 100644 index 0000000..50ddc67 --- /dev/null +++ b/modules/gallery/controllers/admin_languages.php @@ -0,0 +1,135 @@ +page_title = t("Languages and translations"); + $v->content = new View("admin_languages.html"); + $v->content->available_locales = locales::available(); + $v->content->installed_locales = locales::installed(); + $v->content->default_locale = module::get_var("gallery", "default_locale"); + + if (empty($share_translations_form)) { + $share_translations_form = $this->_share_translations_form(); + } + $v->content->share_translations_form = $share_translations_form; + $this->_outgoing_translations_count(); + print $v; + } + + public function save() { + access::verify_csrf(); + + $input = Input::instance(); + locales::update_installed($input->post("installed_locales")); + + $installed_locales = array_keys(locales::installed()); + $new_default_locale = $input->post("default_locale"); + if (!in_array($new_default_locale, $installed_locales)) { + if (!empty($installed_locales)) { + $new_default_locale = $installed_locales[0]; + } else { + $new_default_locale = "en_US"; + } + } + module::set_var("gallery", "default_locale", $new_default_locale); + + json::reply(array("result" => "success")); + } + + public function share() { + access::verify_csrf(); + + $form = $this->_share_translations_form(); + if (!$form->validate()) { + // Show the page with form errors + return $this->index($form); + } + + if (Input::instance()->post("share")) { + l10n_client::submit_translations(); + message::success(t("Translations submitted")); + } else { + return $this->_save_api_key($form); + } + url::redirect("admin/languages"); + } + + private function _save_api_key($form) { + $new_key = $form->sharing->api_key->value; + if ($new_key) { + list($connected, $valid) = l10n_client::validate_api_key($new_key); + if (!$valid) { + $form->sharing->api_key->add_error($connected ? "invalid" : "no_connection", 1); + } + } else { + $valid = true; + } + + if ($valid) { + $old_key = l10n_client::api_key(); + l10n_client::api_key($new_key); + if ($old_key && !$new_key) { + message::success(t("Your API key has been cleared.")); + } else if ($old_key && $new_key && $old_key != $new_key) { + message::success(t("Your API key has been changed.")); + } else if (!$old_key && $new_key) { + message::success(t("Your API key has been saved.")); + } else if ($old_key && $new_key && $old_key == $new_key) { + message::info(t("Your API key was not changed.")); + } + + log::success(t("gallery"), t("l10n_client API key changed.")); + url::redirect("admin/languages"); + } else { + // Show the page with form errors + $this->index($form); + } + } + + private function _outgoing_translations_count() { + return ORM::factory("outgoing_translation")->count_all(); + } + + private function _share_translations_form() { + $form = new Forge("admin/languages/share", "", "post", array("id" => "g-share-translations-form")); + $group = $form->group("sharing") + ->label("Translations API Key"); + $api_key = l10n_client::api_key(); + $server_link = l10n_client::server_api_key_url(); + $group->input("api_key") + ->label(empty($api_key) + ? t("This is a unique key that will allow you to send translations to the remote + server. To get your API key go to %server-link.", + array("server-link" => html::mark_clean(html::anchor($server_link)))) + : t("API key")) + ->value($api_key) + ->error_messages("invalid", t("The API key you provided is invalid.")) + ->error_messages( + "no_connection", t("Could not connect to remote server to validate the API key.")); + $group->submit("save")->value(t("Save settings")); + if ($api_key && $this->_outgoing_translations_count()) { + // TODO: UI improvement: hide API key / save button when API key is set. + $group->submit("share")->value(t("Submit translations")); + } + return $form; + } +} + diff --git a/modules/gallery/controllers/admin_maintenance.php b/modules/gallery/controllers/admin_maintenance.php new file mode 100644 index 0000000..32f2078 --- /dev/null +++ b/modules/gallery/controllers/admin_maintenance.php @@ -0,0 +1,243 @@ +update("tasks") + ->set("state", "stalled") + ->where("done", "=", 0) + ->where("state", "<>", "stalled") + ->where(db::expr("UNIX_TIMESTAMP(NOW()) - `updated` > 15")) + ->execute(); + $stalled_count = $query->count(); + if ($stalled_count) { + log::warning("tasks", + t2("One task is stalled", + "%count tasks are stalled", + $stalled_count), + t('view', + array("url" => html::mark_clean(url::site("admin/maintenance"))))); + } + + $view = new Admin_View("admin.html"); + $view->page_title = t("Maintenance tasks"); + $view->content = new View("admin_maintenance.html"); + $view->content->task_definitions = task::get_definitions(); + $view->content->running_tasks = ORM::factory("task") + ->where("done", "=", 0)->order_by("updated", "DESC")->find_all(); + $view->content->finished_tasks = ORM::factory("task") + ->where("done", "=", 1)->order_by("updated", "DESC")->find_all(); + print $view; + + // Do some maintenance while we're in here + db::build() + ->delete("caches") + ->where("expiration", "<>", 0) + ->where("expiration", "<=", time()) + ->execute(); + module::deactivate_missing_modules(); + } + + /** + * Start a new task + * @param string $task_callback + */ + public function start($task_callback) { + access::verify_csrf(); + + $task = task::start($task_callback); + $view = new View("admin_maintenance_task.html"); + $view->task = $task; + + log::info("tasks", t("Task %task_name started (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor("admin/maintenance", t("maintenance"))); + print $view; + } + + /** + * Resume a stalled task + * @param string $task_id + */ + public function resume($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded()) { + throw new Exception("@todo MISSING_TASK"); + } + $view = new View("admin_maintenance_task.html"); + $view->task = $task; + + $task->log(t("Task %task_name resumed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id))); + log::info("tasks", t("Task %task_name resumed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor("admin/maintenance", t("maintenance"))); + print $view; + } + + /** + * Show the task log + * @param string $task_id + */ + public function show_log($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded()) { + throw new Exception("@todo MISSING_TASK"); + } + $view = new View("admin_maintenance_show_log.html"); + $view->task = $task; + + print $view; + } + + /** + * Save the task log + * @param string $task_id + */ + public function save_log($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded()) { + throw new Exception("@todo MISSING_TASK"); + } + + header("Content-Type: application/text"); + header("Content-Disposition: filename=gallery3_task_log.txt"); + print $task->get_log(); + } + + /** + * Cancel a task. + * @param string $task_id + */ + public function cancel($task_id) { + access::verify_csrf(); + + task::cancel($task_id); + + message::success(t("Task cancelled")); + url::redirect("admin/maintenance"); + } + + public function cancel_running_tasks() { + access::verify_csrf(); + db::build() + ->update("tasks") + ->set("done", 1) + ->set("state", "cancelled") + ->where("done", "=", 0) + ->execute(); + message::success(t("All running tasks cancelled")); + url::redirect("admin/maintenance"); + } + + /** + * Remove a task. + * @param string $task_id + */ + public function remove($task_id) { + access::verify_csrf(); + + task::remove($task_id); + + message::success(t("Task removed")); + url::redirect("admin/maintenance"); + } + + public function remove_finished_tasks() { + access::verify_csrf(); + + // Do it the long way so we can call delete and remove the cache. + $finished = ORM::factory("task") + ->where("done", "=", 1) + ->find_all(); + foreach ($finished as $task) { + task::remove($task->id); + } + message::success(t("All finished tasks removed")); + url::redirect("admin/maintenance"); + } + + /** + * Run a task. This will trigger the task to do a small amount of work, then it will report + * back with status on the task. + * @param string $task_id + */ + public function run($task_id) { + access::verify_csrf(); + + try { + $task = task::run($task_id); + } catch (Exception $e) { + Kohana_Log::add( + "error", + sprintf( + "%s in %s at line %s:\n%s", $e->getMessage(), $e->getFile(), + $e->getLine(), $e->getTraceAsString())); + throw $e; + } + + if ($task->done) { + switch ($task->state) { + case "success": + log::success("tasks", t("Task %task_name completed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor("admin/maintenance", t("maintenance"))); + message::success(t("Task completed successfully")); + break; + + case "error": + log::error("tasks", t("Task %task_name failed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor("admin/maintenance", t("maintenance"))); + message::success(t("Task failed")); + break; + } + // Using sprintf("%F") to avoid comma as decimal separator. + json::reply(array("result" => "success", + "task" => array( + "percent_complete" => sprintf("%F", $task->percent_complete), + "status" => (string) $task->status, + "done" => (bool) $task->done), + "location" => url::site("admin/maintenance"))); + + } else { + json::reply(array("result" => "in_progress", + "task" => array( + "percent_complete" => sprintf("%F", $task->percent_complete), + "status" => (string) $task->status, + "done" => (bool) $task->done))); + } + } + + public function maintenance_mode($value) { + access::verify_csrf(); + module::set_var("gallery", "maintenance_mode", $value); + url::redirect("admin/maintenance"); + } +} diff --git a/modules/gallery/controllers/admin_modules.php b/modules/gallery/controllers/admin_modules.php new file mode 100644 index 0000000..177a925 --- /dev/null +++ b/modules/gallery/controllers/admin_modules.php @@ -0,0 +1,119 @@ +page_title = t("Modules"); + $view->content = new View("admin_modules.html"); + $view->content->available = module::available(); + $view->content->obsolete_modules_message = module::get_obsolete_modules_message(); + print $view; + } + + + public function confirm() { + access::verify_csrf(); + + $messages = array("error" => array(), "warn" => array()); + $desired_list = array(); + foreach (module::available() as $module_name => $info) { + if ($info->locked) { + continue; + } + + if ($desired = Input::instance()->post($module_name) == 1) { + $desired_list[] = $module_name; + } + if ($info->active && !$desired && module::is_active($module_name)) { + $messages = array_merge($messages, module::can_deactivate($module_name)); + } else if (!$info->active && $desired && !module::is_active($module_name)) { + $messages = array_merge($messages, module::can_activate($module_name)); + } + } + + if (empty($messages["error"]) && empty($messages["warn"])) { + $this->_do_save(); + $result["reload"] = 1; + } else { + $v = new View("admin_modules_confirm.html"); + $v->messages = $messages; + $v->modules = $desired_list; + $result["dialog"] = (string)$v; + $result["allow_continue"] = empty($messages["error"]); + } + json::reply($result); + } + + public function save() { + access::verify_csrf(); + + $this->_do_save(); + url::redirect("admin/modules"); + } + + private function _do_save() { + $changes = new stdClass(); + $changes->activate = array(); + $changes->deactivate = array(); + $activated_names = array(); + $deactivated_names = array(); + foreach (module::available() as $module_name => $info) { + if ($info->locked) { + continue; + } + + try { + $desired = Input::instance()->post($module_name) == 1; + if ($info->active && !$desired && module::is_active($module_name)) { + module::deactivate($module_name); + $changes->deactivate[] = $module_name; + $deactivated_names[] = t($info->name); + } else if (!$info->active && $desired && !module::is_active($module_name)) { + if (module::is_installed($module_name)) { + module::upgrade($module_name); + } else { + module::install($module_name); + } + module::activate($module_name); + $changes->activate[] = $module_name; + $activated_names[] = t($info->name); + } + } catch (Exception $e) { + message::warning(t("An error occurred while installing the %module_name module", + array("module_name" => $info->name))); + Kohana_Log::add("error", (string)$e); + } + } + + module::event("module_change", $changes); + + // @todo this type of collation is questionable from an i18n perspective + if ($activated_names) { + message::success(t("Activated: %names", array("names" => join(", ", $activated_names)))); + } + if ($deactivated_names) { + message::success(t("Deactivated: %names", array("names" => join(", ", $deactivated_names)))); + } + } +} + diff --git a/modules/gallery/controllers/admin_movies.php b/modules/gallery/controllers/admin_movies.php new file mode 100644 index 0000000..38fa44a --- /dev/null +++ b/modules/gallery/controllers/admin_movies.php @@ -0,0 +1,72 @@ +_get_admin_form(); + $this->_print_view($form); + } + + public function save() { + access::verify_csrf(); + $form = $this->_get_admin_form(); + if ($form->validate()) { + module::set_var("gallery", "movie_allow_uploads", $form->settings->allow_uploads->value); + if ($form->settings->rebuild_thumbs->value) { + graphics::mark_dirty(true, false, "movie"); + } + // All done - redirect with message. + message::success(t("Movies settings updated successfully")); + url::redirect("admin/movies"); + } + // Something went wrong - print view from existing form. + $this->_print_view($form); + } + + private function _print_view($form) { + list ($ffmpeg_version, $ffmpeg_date) = movie::get_ffmpeg_version(); + $ffmpeg_version = $ffmpeg_date ? "{$ffmpeg_version} ({$ffmpeg_date})" : $ffmpeg_version; + $ffmpeg_path = movie::find_ffmpeg(); + $ffmpeg_dir = substr($ffmpeg_path, 0, strrpos($ffmpeg_path, "/")); + + $view = new Admin_View("admin.html"); + $view->page_title = t("Movies settings"); + $view->content = new View("admin_movies.html"); + $view->content->form = $form; + $view->content->ffmpeg_dir = $ffmpeg_dir; + $view->content->ffmpeg_version = $ffmpeg_version; + print $view; + } + + private function _get_admin_form() { + $form = new Forge("admin/movies/save", "", "post", array("id" => "g-movies-admin-form")); + $group = $form->group("settings")->label(t("Settings")); + $group->dropdown("allow_uploads") + ->label(t("Allow movie uploads into Gallery (does not affect existing movies)")) + ->options(array("autodetect"=>t("only if FFmpeg is detected (default)"), + "always"=>t("always"), "never"=>t("never"))) + ->selected(module::get_var("gallery", "movie_allow_uploads", "autodetect")); + $group->checkbox("rebuild_thumbs") + ->label(t("Rebuild all movie thumbnails (once FFmpeg is installed, use this to update existing movie thumbnails)")) + ->checked(false); // always set as false + $form->submit("save")->value(t("Save")); + return $form; + } +} diff --git a/modules/gallery/controllers/admin_sidebar.php b/modules/gallery/controllers/admin_sidebar.php new file mode 100644 index 0000000..f150f85 --- /dev/null +++ b/modules/gallery/controllers/admin_sidebar.php @@ -0,0 +1,69 @@ +page_title = t("Manage sidebar"); + $view->content = new View("admin_sidebar.html"); + $view->content->csrf = access::csrf_token(); + $view->content->available = new View("admin_sidebar_blocks.html"); + $view->content->active = new View("admin_sidebar_blocks.html"); + list($view->content->available->blocks, $view->content->active->blocks) = $this->_get_blocks(); + print $view; + } + + public function update() { + access::verify_csrf(); + + $available_blocks = block_manager::get_available_site_blocks(); + + $active_blocks = array(); + foreach (Input::instance()->get("block", array()) as $block_id) { + $active_blocks[md5($block_id)] = explode(":", (string) $block_id); + } + block_manager::set_active("site_sidebar", $active_blocks); + + $result = array("result" => "success"); + list($available, $active) = $this->_get_blocks(); + $v = new View("admin_sidebar_blocks.html"); + $v->blocks = $available; + $result["available"] = $v->render(); + $v = new View("admin_sidebar_blocks.html"); + $v->blocks = $active; + $result["active"] = $v->render(); + $message = t("Updated sidebar blocks"); + $result["message"] = (string) $message; + json::reply($result); + } + + private function _get_blocks() { + $active_blocks = array(); + $available_blocks = block_manager::get_available_site_blocks(); + foreach (block_manager::get_active("site_sidebar") as $block) { + $id = "{$block[0]}:{$block[1]}"; + if (!empty($available_blocks[$id])) { + $active_blocks[$id] = $available_blocks[$id]; + unset($available_blocks[$id]); + } + } + return array($available_blocks, $active_blocks); + } +} + diff --git a/modules/gallery/controllers/admin_theme_options.php b/modules/gallery/controllers/admin_theme_options.php new file mode 100644 index 0000000..38d2b0a --- /dev/null +++ b/modules/gallery/controllers/admin_theme_options.php @@ -0,0 +1,120 @@ +page_title = t("Theme options"); + $view->content = new View("admin_theme_options.html"); + $view->content->form = $this->_get_edit_form_admin(); + print $view; + } + + public function save() { + access::verify_csrf(); + + $form = $this->_get_edit_form_admin(); + if ($form->validate()) { + module::set_var("gallery", "page_size", $form->edit_theme->page_size->value); + + $thumb_size = $form->edit_theme->thumb_size->value; + if (module::get_var("gallery", "thumb_size") != $thumb_size) { + graphics::remove_rule("gallery", "thumb", "gallery_graphics::resize"); + graphics::add_rule( + "gallery", "thumb", "gallery_graphics::resize", + array("width" => $thumb_size, "height" => $thumb_size, "master" => Image::AUTO), + 100); + module::set_var("gallery", "thumb_size", $thumb_size); + } + + $resize_size = $form->edit_theme->resize_size->value; + if (module::get_var("gallery", "resize_size") != $resize_size) { + graphics::remove_rule("gallery", "resize", "gallery_graphics::resize"); + graphics::add_rule( + "gallery", "resize", "gallery_graphics::resize", + array("width" => $resize_size, "height" => $resize_size, "master" => Image::AUTO), + 100); + module::set_var("gallery", "resize_size", $resize_size); + } + + module::set_var("gallery", "header_text", $form->edit_theme->header_text->value); + module::set_var("gallery", "footer_text", $form->edit_theme->footer_text->value); + module::set_var("gallery", "show_credits", $form->edit_theme->show_credits->value); + module::set_var("gallery", "favicon_url", $form->edit_theme->favicon_url->value); + module::set_var("gallery", "apple_touch_icon_url", $form->edit_theme->apple_touch_icon_url->value); + + module::event("theme_edit_form_completed", $form); + + message::success(t("Updated theme details")); + url::redirect("admin/theme_options"); + } else { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_theme_options.html"); + $view->content->form = $form; + print $view; + } + } + + private function _get_edit_form_admin() { + $form = new Forge("admin/theme_options/save/", "", null, array("id" =>"g-theme-options-form")); + $group = $form->group("edit_theme")->label(t("Theme layout")); + $group->input("page_size")->label(t("Items per page"))->id("g-page-size") + ->rules("required|valid_digit") + ->callback(array($this, "_valididate_page_size")) + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->error_messages("valid_min_value", t("The value must be greater than zero")) + ->value(module::get_var("gallery", "page_size")); + $group->input("thumb_size")->label(t("Thumbnail size (in pixels)"))->id("g-thumb-size") + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("gallery", "thumb_size")); + $group->input("resize_size")->label(t("Resized image size (in pixels)"))->id("g-resize-size") + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("gallery", "resize_size")); + $group->input("favicon_url")->label(t("URL (or relative path) to your favicon.ico")) + ->id("g-favicon") + ->value(module::get_var("gallery", "favicon_url")); + $group->input("apple_touch_icon_url")->label(t("URL (or relative path) to your Apple Touch icon")) + ->id("g-apple-touch") + ->value(module::get_var("gallery", "apple_touch_icon_url")); + $group->textarea("header_text")->label(t("Header text"))->id("g-header-text") + ->value(module::get_var("gallery", "header_text")); + $group->textarea("footer_text")->label(t("Footer text"))->id("g-footer-text") + ->value(module::get_var("gallery", "footer_text")); + $group->checkbox("show_credits")->label(t("Show site credits"))->id("g-footer-text") + ->checked(module::get_var("gallery", "show_credits")); + + module::event("theme_edit_form", $form); + + $group->submit("")->value(t("Save")); + return $form; + } + + function _valididate_page_size($input) { + if ($input->value < 1) { + $input->add_error("valid_min_value", true); + } + + } +} + diff --git a/modules/gallery/controllers/admin_themes.php b/modules/gallery/controllers/admin_themes.php new file mode 100644 index 0000000..4ab994f --- /dev/null +++ b/modules/gallery/controllers/admin_themes.php @@ -0,0 +1,80 @@ +page_title = t("Theme choice"); + $view->content = new View("admin_themes.html"); + $view->content->admin = module::get_var("gallery", "active_admin_theme"); + $view->content->site = module::get_var("gallery", "active_site_theme"); + $view->content->themes = $this->_get_themes(); + + site_status::clear("missing_site_theme"); + site_status::clear("missing_admin_theme"); + print $view; + } + + private function _get_themes() { + $themes = array(); + foreach (scandir(THEMEPATH) as $theme_name) { + if ($theme_name[0] == ".") { + continue; + } + $theme_name = preg_replace("/[^a-zA-Z0-9\._-]/", "", $theme_name); + if (file_exists(THEMEPATH . "$theme_name/theme.info")) { + + $themes[$theme_name] = theme::get_info($theme_name); + } + } + return $themes; + } + + public function preview($type, $theme_name) { + $view = new View("admin_themes_preview.html"); + $view->info = theme::get_info($theme_name); + $view->theme_name = t($theme_name); + $view->type = $type; + if ($type == "admin") { + $view->url = url::site("admin?theme=$theme_name"); + } else { + $view->url = item::root()->url("theme=$theme_name"); + } + print $view; + } + + public function choose($type, $theme_name) { + access::verify_csrf(); + + $info = theme::get_info($theme_name); + + if ($type == "admin" && $info->admin) { + module::set_var("gallery", "active_admin_theme", $theme_name); + message::success(t("Successfully changed your admin theme to %theme_name", + array("theme_name" => $info->name))); + } else if ($type == "site" && $info->site) { + module::set_var("gallery", "active_site_theme", $theme_name); + message::success(t("Successfully changed your Gallery theme to %theme_name", + array("theme_name" => $info->name))); + } + + url::redirect("admin/themes"); + } +} + diff --git a/modules/gallery/controllers/admin_upgrade_checker.php b/modules/gallery/controllers/admin_upgrade_checker.php new file mode 100644 index 0000000..3f0bdac --- /dev/null +++ b/modules/gallery/controllers/admin_upgrade_checker.php @@ -0,0 +1,57 @@ +(remind me later)", + array("hide-url" => url::site("admin/upgrade_checker/remind_me_later?csrf=__CSRF__"))); + site_status::info($message, "upgrade_checker"); + } else { + site_status::clear("upgrade_checker"); + } + url::redirect("admin/dashboard"); + } + + function remind_me_later() { + access::verify_csrf(); + site_status::clear("upgrade_checker"); + if ($referer = Input::instance()->server("HTTP_REFERER")) { + url::redirect($referer); + } else { + url::redirect(item::root()->abs_url()); + } + } + + function set_auto($val) { + access::verify_csrf(); + module::set_var("gallery", "upgrade_checker_auto_enabled", (bool)$val); + + if ((bool)$val) { + message::success(t("Automatic upgrade checking is enabled.")); + } else { + message::success(t("Automatic upgrade checking is disabled.")); + } + url::redirect("admin/dashboard"); + } +} diff --git a/modules/gallery/controllers/albums.php b/modules/gallery/controllers/albums.php new file mode 100644 index 0000000..0fb033a --- /dev/null +++ b/modules/gallery/controllers/albums.php @@ -0,0 +1,207 @@ +show(ORM::factory("item", 1)); + } + + public function show($album) { + if (!is_object($album)) { + // show() must be public because we route to it in url::parse_url(), so make + // sure that we're actually receiving an object + throw new Kohana_404_Exception(); + } + + access::required("view", $album); + + $page_size = module::get_var("gallery", "page_size", 9); + $input = Input::instance(); + $show = $input->get("show"); + + if ($show) { + $child = ORM::factory("item", $show); + $index = item::get_position($child); + if ($index) { + $page = ceil($index / $page_size); + if ($page == 1) { + url::redirect($album->abs_url()); + } else { + url::redirect($album->abs_url("page=$page")); + } + } + } + + $page = $input->get("page", "1"); + $children_count = $album->viewable()->children_count(); + $offset = ($page - 1) * $page_size; + $max_pages = max(ceil($children_count / $page_size), 1); + + // Make sure that the page references a valid offset + if ($page < 1) { + url::redirect($album->abs_url()); + } else if ($page > $max_pages) { + url::redirect($album->abs_url("page=$max_pages")); + } + + $template = new Theme_View("page.html", "collection", "album"); + $template->set_global( + array("page" => $page, + "page_title" => null, + "max_pages" => $max_pages, + "page_size" => $page_size, + "item" => $album, + "children" => $album->viewable()->children($page_size, $offset), + "parents" => $album->parents()->as_array(), // view calls empty() on this + "breadcrumbs" => Breadcrumb::array_from_item_parents($album), + "children_count" => $children_count)); + $template->content = new View("album.html"); + $album->increment_view_count(); + + print $template; + item::set_display_context_callback("Albums_Controller::get_display_context"); + } + + static function get_display_context($item) { + $where = array(array("type", "!=", "album")); + $position = item::get_position($item, $where); + if ($position > 1) { + list ($previous_item, $ignore, $next_item) = + $item->parent()->viewable()->children(3, $position - 2, $where); + } else { + $previous_item = null; + list ($next_item) = $item->parent()->viewable()->children(1, $position, $where); + } + + return array("position" => $position, + "previous_item" => $previous_item, + "next_item" => $next_item, + "sibling_count" => $item->parent()->viewable()->children_count($where), + "siblings_callback" => array("Albums_Controller::get_siblings", array($item)), + "parents" => $item->parents()->as_array(), + "breadcrumbs" => Breadcrumb::array_from_item_parents($item)); + } + + static function get_siblings($item, $limit=null, $offset=null) { + // @todo consider creating Item_Model::siblings() if we use this more broadly. + return $item->parent()->viewable()->children($limit, $offset); + } + + public function create($parent_id) { + access::verify_csrf(); + $album = ORM::factory("item", $parent_id); + access::required("view", $album); + access::required("add", $album); + + $form = album::get_add_form($album); + try { + $valid = $form->validate(); + $album = ORM::factory("item"); + $album->type = "album"; + $album->parent_id = $parent_id; + $album->name = $form->add_album->inputs["name"]->value; + $album->title = $form->add_album->title->value ? + $form->add_album->title->value : $form->add_album->inputs["name"]->value; + $album->description = $form->add_album->description->value; + $album->slug = $form->add_album->slug->value; + $album->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->add_album->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $album->save(); + module::event("album_add_form_completed", $album, $form); + log::success("content", "Created an album", + html::anchor("albums/$album->id", "view album")); + message::success(t("Created album %album_title", + array("album_title" => html::purify($album->title)))); + + json::reply(array("result" => "success", "location" => $album->url())); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function update($album_id) { + access::verify_csrf(); + $album = ORM::factory("item", $album_id); + access::required("view", $album); + access::required("edit", $album); + + $form = album::get_edit_form($album); + try { + $valid = $form->validate(); + $album->title = $form->edit_item->title->value; + $album->description = $form->edit_item->description->value; + $album->sort_column = $form->edit_item->sort_order->column->value; + $album->sort_order = $form->edit_item->sort_order->direction->value; + if (array_key_exists("name", $form->edit_item->inputs)) { + $album->name = $form->edit_item->inputs["name"]->value; + } + $album->slug = $form->edit_item->slug->value; + $album->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_item->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $album->save(); + module::event("item_edit_form_completed", $album, $form); + + log::success("content", "Updated album", "id\">view"); + message::success(t("Saved album %album_title", + array("album_title" => html::purify($album->title)))); + + if ($form->from_id->value == $album->id) { + // Use the new url; it might have changed. + json::reply(array("result" => "success", "location" => $album->url())); + } else { + // Stay on the same page + json::reply(array("result" => "success")); + } + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function form_add($album_id) { + $album = ORM::factory("item", $album_id); + access::required("view", $album); + access::required("add", $album); + + print album::get_add_form($album); + } + + public function form_edit($album_id) { + $album = ORM::factory("item", $album_id); + access::required("view", $album); + access::required("edit", $album); + + print album::get_edit_form($album); + } +} diff --git a/modules/gallery/controllers/combined.php b/modules/gallery/controllers/combined.php new file mode 100644 index 0000000..3cf7f87 --- /dev/null +++ b/modules/gallery/controllers/combined.php @@ -0,0 +1,96 @@ +_emit("javascript", $key); + } + + /** + * Return the combined CSS bundle associated with the given key. + */ + public function css($key) { + return $this->_emit("css", $key); + } + + /** + * Print out a cached entry. + * @param string the combined entry type (either "javascript" or "css") + * @param string the key (typically an md5 sum) + */ + private function _emit($type, $key) { + $input = Input::instance(); + + // We don't need to save the session for this request + Session::instance()->abort_save(); + + // Our data is immutable, so if they already have a copy then it needs no updating. + if ($input->server("HTTP_IF_MODIFIED_SINCE")) { + header('HTTP/1.0 304 Not Modified'); + header("Expires: Tue, 19 Jan 2038 00:00:00 GMT"); + header("Cache-Control: public,max-age=2678400"); + header('Pragma: public'); + Kohana::close_buffers(false); + return ""; + } + + if (empty($key)) { + throw new Kohana_404_Exception(); + } + + $cache = Cache::instance(); + $use_gzip = function_exists("gzencode") && + stripos($input->server("HTTP_ACCEPT_ENCODING"), "gzip") !== false && + (int) ini_get("zlib.output_compression") === 0; + + if ($use_gzip && $content = $cache->get("{$key}_gz")) { + header("Content-Encoding: gzip"); + header("Vary: Accept-Encoding"); + } else { + // Fall back to non-gzipped if we have to + $content = $cache->get($key); + } + if (empty($content)) { + throw new Kohana_404_Exception(); + } + + // $type is either 'javascript' or 'css' + if ($type == "javascript") { + header("Content-Type: application/javascript; charset=UTF-8"); + } else { + header("Content-Type: text/css; charset=UTF-8"); + } + header("Expires: Tue, 19 Jan 2038 00:00:00 GMT"); + header("Cache-Control: public,max-age=2678400"); + header("Pragma: public"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s T", time())); + header("Content-Length: " . strlen($content)); + + Kohana::close_buffers(false); + print $content; + } + +} + diff --git a/modules/gallery/controllers/file_proxy.php b/modules/gallery/controllers/file_proxy.php new file mode 100644 index 0000000..34f6b8c --- /dev/null +++ b/modules/gallery/controllers/file_proxy.php @@ -0,0 +1,174 @@ +server("REQUEST_URI")); + + // get rid of query parameters + // request_uri: gallery3/var/albums/foo/bar.jpg + $request_uri = preg_replace("/\?.*/", "", $request_uri); + + // var_uri: gallery3/var/ + $var_uri = url::file("var/"); + + // Make sure that the request is for a file inside var + $offset = strpos(rawurldecode($request_uri), $var_uri); + if ($offset !== 0) { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 1; + throw $e; + } + + // file_uri: albums/foo/bar.jpg + $file_uri = substr($request_uri, strlen($var_uri)); + + // type: albums + // path: foo/bar.jpg + list ($type, $path) = explode("/", $file_uri, 2); + if ($type != "resizes" && $type != "albums" && $type != "thumbs") { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 2; + throw $e; + } + + // If the last element is .album.jpg, pop that off since it's not a real item + $path = preg_replace("|/.album.jpg$|", "", $path); + + $item = item::find_by_path($path); + if (!$item->loaded()) { + // We didn't turn it up. If we're looking for a .jpg then it's it's possible that we're + // requesting the thumbnail for a movie. In that case, the movie file would + // have been converted to a .jpg. So try some alternate types: + if (preg_match('/.jpg$/', $path)) { + foreach (legal_file::get_movie_extensions() as $ext) { + $movie_path = preg_replace('/.jpg$/', ".$ext", $path); + $item = item::find_by_path($movie_path); + if ($item->loaded()) { + break; + } + } + } + } + + if (!$item->loaded()) { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 3; + throw $e; + } + + // Make sure we have access to the item + if (!access::can("view", $item)) { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 4; + throw $e; + } + + // Make sure we have view_full access to the original + if ($type == "albums" && !access::can("view_full", $item)) { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 5; + throw $e; + } + + // Don't try to load a directory + if ($type == "albums" && $item->is_album()) { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 6; + throw $e; + } + + // Note: this code is roughly duplicated in data_rest, so if you modify this, please look to + // see if you should make the same change there as well. + + if ($type == "albums") { + $file = $item->file_path(); + } else if ($type == "resizes") { + $file = $item->resize_path(); + } else { + $file = $item->thumb_path(); + } + + if (!file_exists($file)) { + $e = new Kohana_404_Exception(); + $e->test_fail_code = 7; + throw $e; + } + + if (gallery::show_profiler()) { + Profiler::enable(); + $profiler = new Profiler(); + $profiler->render(); + exit; + } + + header("Content-Length: " . filesize($file)); + + header("Pragma:"); + // Check that the content hasn't expired or it wasn't changed since cached + expires::check(2592000, $item->updated); + + // We don't need to save the session for this request + Session::instance()->abort_save(); + + expires::set(2592000, $item->updated); // 30 days + + // Dump out the image. If the item is a movie or album, then its thumbnail will be a JPG. + if (($item->is_movie() || $item->is_album()) && $type == "thumbs") { + header("Content-Type: image/jpeg"); + } else { + header("Content-Type: $item->mime_type"); + } + + if (TEST_MODE) { + return $file; + } else { + // Don't use Kohana::close_buffers(false) here because that only closes all the buffers + // that Kohana started. We want to close *all* buffers at this point because otherwise we're + // going to buffer up whatever file we're proxying (and it may be very large). This may + // affect embedding or systems with PHP's output_buffering enabled. + while (ob_get_level()) { + if (!@ob_end_clean()) { + // ob_end_clean() can return false if the buffer can't be removed for some reason + // (zlib output compression buffers sometimes cause problems). + break; + } + } + readfile($file); + } + } +} diff --git a/modules/gallery/controllers/items.php b/modules/gallery/controllers/items.php new file mode 100644 index 0000000..845df76 --- /dev/null +++ b/modules/gallery/controllers/items.php @@ -0,0 +1,43 @@ +loaded()) { + throw new Kohana_404_Exception(); + } + + // Redirect to the more specific resource type, since it will render differently. We can't + // delegate here because we may have gotten to this page via /items/ which means that we + // don't have a type-specific controller. Also, we want to drive a single canonical resource + // mapping where possible. + access::required("view", $item); + url::redirect($item->abs_url()); + } + + // Return the width/height dimensions for the given item + public function dimensions($id) { + $item = ORM::factory("item", $id); + access::required("view", $item); + json::reply(array("thumb" => array((int)$item->thumb_width, (int)$item->thumb_height), + "resize" => array((int)$item->resize_width, (int)$item->resize_height), + "full" => array((int)$item->width, (int)$item->height))); + } +} diff --git a/modules/gallery/controllers/l10n_client.php b/modules/gallery/controllers/l10n_client.php new file mode 100644 index 0000000..993dfb3 --- /dev/null +++ b/modules/gallery/controllers/l10n_client.php @@ -0,0 +1,179 @@ +admin) { + access::forbidden(); + } + + $locale = Gallery_I18n::instance()->locale(); + $input = Input::instance(); + $key = $input->post("l10n-message-key"); + + $root_message = ORM::factory("incoming_translation") + ->where("key", "=", $key) + ->where("locale", "=", "root") + ->find(); + + if (!$root_message->loaded()) { + throw new Exception("@todo bad request data / illegal state"); + } + $is_plural = Gallery_I18n::is_plural_message(unserialize($root_message->message)); + + $is_empty = true; + if ($is_plural) { + $plural_forms = l10n_client::plural_forms($locale); + $translation = array(); + foreach($plural_forms as $plural_form) { + $value = $input->post("l10n-edit-plural-translation-$plural_form"); + if (null === $value || !is_string($value)) { + throw new Exception("@todo bad request data"); + } + $translation[$plural_form] = $value; + $is_empty = $is_empty && empty($value); + } + } else { + $translation = $input->post("l10n-edit-translation"); + $is_empty = empty($translation); + if (null === $translation || !is_string($translation)) { + throw new Exception("@todo bad request data"); + } + } + + $entry = ORM::factory("outgoing_translation") + ->where("key", "=", $key) + ->where("locale", "=", $locale) + ->find(); + + if ($is_empty) { + if ($entry->loaded()) { + $entry->delete(); + } + } else { + if (!$entry->loaded()) { + $entry->key = $key; + $entry->locale = $locale; + $entry->message = $root_message->message; + $entry->base_revision = null; + } + + $entry->translation = serialize($translation); + + $entry_from_incoming = ORM::factory("incoming_translation") + ->where("key", "=", $key) + ->where("locale", "=", $locale) + ->find(); + + if (!$entry_from_incoming->loaded()) { + $entry->base_revision = $entry_from_incoming->revision; + } + + $entry->save(); + } + + Gallery_I18n::clear_cache($locale); + + json::reply(new stdClass()); + } + + public function toggle_l10n_mode() { + access::verify_csrf(); + if (!identity::active_user()->admin) { + access::forbidden(); + } + + $session = Session::instance(); + $l10n_mode = $session->get("l10n_mode", false); + $session->set("l10n_mode", !$l10n_mode); + + $redirect_url = "admin/languages"; + if (!$l10n_mode) { + $redirect_url .= "#l10n-client"; + } + + url::redirect($redirect_url); + } + + private static function _l10n_client_search_form() { + $form = new Forge("#", "", "post", array("id" => "g-l10n-search-form")); + $group = $form->group("l10n_search"); + $group->input("l10n-search")->id("g-l10n-search"); + + return $form; + } + + public static function l10n_form() { + if (Input::instance()->get("show_all_l10n_messages")) { + $calls = array(); + foreach (db::build() + ->select("key", "message") + ->from("incoming_translations") + ->where("locale", "=", "root") + ->execute() as $row) { + $calls[$row->key] = array(unserialize($row->message), array()); + } + } else { + $calls = Gallery_I18n::instance()->call_log(); + } + $locale = Gallery_I18n::instance()->locale(); + + if ($calls) { + $translations = array(); + foreach (db::build() + ->select("key", "translation") + ->from("incoming_translations") + ->where("locale", "=", $locale) + ->execute() as $row) { + $translations[$row->key] = unserialize($row->translation); + } + // Override incoming with outgoing... + foreach (db::build() + ->select("key", "translation") + ->from("outgoing_translations") + ->where("locale", "=", $locale) + ->execute() as $row) { + $translations[$row->key] = unserialize($row->translation); + } + + $string_list = array(); + $cache = array(); + foreach ($calls as $key => $call) { + list ($message, $options) = $call; + // Ensure that the message is in the DB + l10n_scanner::process_message($message, $cache); + // Note: Not interpolating placeholders for the actual translation input field. + // TODO: Might show a preview w/ interpolations (using $options) + $translation = isset($translations[$key]) ? $translations[$key] : ''; + $string_list[] = array('source' => $message, + 'key' => $key, + 'translation' => $translation); + } + + $v = new View('l10n_client.html'); + $v->string_list = $string_list; + $v->l10n_search_form = self::_l10n_client_search_form(); + $v->plural_forms = l10n_client::plural_forms($locale); + return $v; + } + + return ''; + } +} diff --git a/modules/gallery/controllers/login.php b/modules/gallery/controllers/login.php new file mode 100644 index 0000000..9da575b --- /dev/null +++ b/modules/gallery/controllers/login.php @@ -0,0 +1,90 @@ +form = auth::get_login_form("login/auth_ajax"); + print $view; + } + + public function auth_ajax() { + access::verify_csrf(); + + list ($valid, $form) = $this->_auth("login/auth_ajax"); + if ($valid) { + json::reply(array("result" => "success")); + } else { + $view = new View("login_ajax.html"); + $view->form = $form; + json::reply(array("result" => "error", "html" => (string)$view)); + } + } + + public function html() { + $view = new Theme_View("page.html", "other", "login"); + $view->page_title = t("Log in to Gallery"); + $view->content = new View("login_ajax.html"); + $view->content->form = auth::get_login_form("login/auth_html"); + print $view; + } + + public function auth_html() { + access::verify_csrf(); + + list ($valid, $form) = $this->_auth("login/auth_html"); + if ($valid) { + $continue_url = $form->continue_url->value; + url::redirect($continue_url ? $continue_url : item::root()->abs_url()); + } else { + $view = new Theme_View("page.html", "other", "login"); + $view->page_title = t("Log in to Gallery"); + $view->content = new View("login_ajax.html"); + $view->content->form = $form; + print $view; + } + } + + private function _auth($url) { + $form = auth::get_login_form($url); + $valid = $form->validate(); + if ($valid) { + $user = identity::lookup_user_by_name($form->login->inputs["name"]->value); + if (empty($user) || !identity::is_correct_password($user, $form->login->password->value)) { + $form->login->inputs["name"]->add_error("invalid_login", 1); + $name = $form->login->inputs["name"]->value; + log::warning("user", t("Failed login for %name", array("name" => $name))); + module::event("user_auth_failed", $name); + $valid = false; + } + } + + if ($valid) { + auth::login($user); + } + + // Either way, regenerate the session id to avoid session trapping + Session::instance()->regenerate(); + + return array($valid, $form); + } +} \ No newline at end of file diff --git a/modules/gallery/controllers/logout.php b/modules/gallery/controllers/logout.php new file mode 100644 index 0000000..9a24d1b --- /dev/null +++ b/modules/gallery/controllers/logout.php @@ -0,0 +1,29 @@ +get("continue_url")) { + url::redirect($continue_url); + } + url::redirect(item::root()->abs_url()); + } +} \ No newline at end of file diff --git a/modules/gallery/controllers/movies.php b/modules/gallery/controllers/movies.php new file mode 100644 index 0000000..5607571 --- /dev/null +++ b/modules/gallery/controllers/movies.php @@ -0,0 +1,91 @@ +set_global(array("item" => $movie, + "children" => array(), + "children_count" => 0)); + $template->set_global(item::get_display_context($movie)); + $template->content = new View("movie.html"); + + $movie->increment_view_count(); + + print $template; + } + + public function update($movie_id) { + access::verify_csrf(); + $movie = ORM::factory("item", $movie_id); + access::required("view", $movie); + access::required("edit", $movie); + + $form = movie::get_edit_form($movie); + try { + $valid = $form->validate(); + $movie->title = $form->edit_item->title->value; + $movie->description = $form->edit_item->description->value; + $movie->slug = $form->edit_item->slug->value; + $movie->name = $form->edit_item->inputs["name"]->value; + $movie->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_item->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $movie->save(); + module::event("item_edit_form_completed", $movie, $form); + + log::success("content", "Updated movie", "url()}\">view"); + message::success( + t("Saved movie %movie_title", array("movie_title" => html::purify($movie->title)))); + + if ($form->from_id->value == $movie->id) { + // Use the new url; it might have changed. + json::reply(array("result" => "success", "location" => $movie->url())); + } else { + // Stay on the same page + json::reply(array("result" => "success")); + } + } else { + json::reply(array("result" => "error", "html" => (string) $form)); + } + } + + public function form_edit($movie_id) { + $movie = ORM::factory("item", $movie_id); + access::required("view", $movie); + access::required("edit", $movie); + + print movie::get_edit_form($movie); + } +} diff --git a/modules/gallery/controllers/packager.php b/modules/gallery/controllers/packager.php new file mode 100644 index 0000000..d7e3cf4 --- /dev/null +++ b/modules/gallery/controllers/packager.php @@ -0,0 +1,191 @@ +_reset(); // empty and reinstall the standard modules + $this->_dump_database(); // Dump the database + $this->_dump_var(); // Dump the var directory + } catch (Exception $e) { + print $e->getMessage() . "\n" . $e->getTraceAsString(); + return; + } + + print "Successfully wrote install.sql and init_var.php\n"; + } + + private function _reset() { + // Drop all tables + foreach (Database::instance()->list_tables() as $table) { + Database::instance()->query("DROP TABLE IF EXISTS {{$table}}"); + } + + // Clean out data + dir::unlink(VARPATH . "uploads"); + dir::unlink(VARPATH . "albums"); + dir::unlink(VARPATH . "resizes"); + dir::unlink(VARPATH . "thumbs"); + dir::unlink(VARPATH . "modules"); + dir::unlink(VARPATH . "tmp"); + + Database::instance()->clear_cache(); + module::$modules = array(); + module::$active = array(); + + // Use a known random seed so that subsequent packaging runs will reuse the same random + // numbers, keeping our install.sql file more stable. + srand(0); + + foreach (array("gallery", "user", "comment", "organize", "info", + "rss", "search", "slideshow", "tag") as $module_name) { + module::install($module_name); + module::activate($module_name); + } + } + + private function _dump_database() { + // We now have a clean install with just the packages that we want. Make sure that the + // database is clean too. + $i = 1; + foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $key) { + $blocks = array(); + foreach (unserialize(module::get_var("gallery", "blocks_{$key}")) as $rnd => $value) { + $blocks[++$i] = $value; + } + module::set_var("gallery", "blocks_{$key}", serialize($blocks)); + } + + Database::instance()->query("TRUNCATE {caches}"); + Database::instance()->query("TRUNCATE {sessions}"); + Database::instance()->query("TRUNCATE {logs}"); + db::build()->update("users") + ->set(array("password" => "")) + ->where("id", "in", array(1, 2)) + ->execute(); + + $dbconfig = Kohana::config('database.default'); + $conn = $dbconfig["connection"]; + $sql_file = DOCROOT . "installer/install.sql"; + if (!is_writable($sql_file)) { + print "$sql_file is not writeable"; + return; + } + $command = sprintf( + "mysqldump --compact --skip-extended-insert --add-drop-table %s %s %s %s > $sql_file", + escapeshellarg("-h{$conn['host']}"), + escapeshellarg("-u{$conn['user']}"), + $conn['pass'] ? escapeshellarg("-p{$conn['pass']}") : "", + escapeshellarg($conn['database'])); + exec($command, $output, $status); + if ($status) { + print "
    ";
    +      print "$command\n";
    +      print "Failed to dump database\n";
    +      print implode("\n", $output);
    +      return;
    +    }
    +
    +    // Post-process the sql file
    +    $buf = "";
    +    $root = ORM::factory("item", 1);
    +    $root_created_timestamp = $root->created;
    +    $root_updated_timestamp = $root->updated;
    +    $table_name = "";
    +    foreach (file($sql_file) as $line) {
    +      // Prefix tables
    +      $line = preg_replace(
    +        "/(CREATE TABLE|IF EXISTS|INSERT INTO) `{$dbconfig['table_prefix']}(\w+)`/", "\\1 {\\2}",
    +        $line);
    +
    +      if (preg_match("/CREATE TABLE {(\w+)}/", $line, $matches)) {
    +        $table_name = $matches[1];
    +      }
    +      // Normalize dates
    +      $line = preg_replace("/,$root_created_timestamp,/", ",UNIX_TIMESTAMP(),", $line);
    +      $line = preg_replace("/,$root_updated_timestamp,/", ",UNIX_TIMESTAMP(),", $line);
    +
    +      // Remove ENGINE= specifications execpt for search records, it always needs to be MyISAM
    +      if ($table_name != "search_records") {
    +        $line = preg_replace("/ENGINE=\S+ /", "", $line);
    +      }
    +
    +      // Null out ids in the vars table since it's an auto_increment table and this will result in
    +      // more stable values so we'll have less churn in install.sql.
    +      $line = preg_replace(
    +        "/^INSERT INTO {vars} VALUES \(\d+/", "INSERT INTO {vars} VALUES (NULL", $line);
    +
    +      $buf .= $line;
    +    }
    +    $fd = fopen($sql_file, "wb");
    +    fwrite($fd, $buf);
    +    fclose($fd);
    +  }
    +
    +  private function _dump_var() {
    +    $objects = new RecursiveIteratorIterator(
    +      new RecursiveDirectoryIterator(VARPATH),
    +      RecursiveIteratorIterator::SELF_FIRST);
    +
    +    $var_file = DOCROOT . "installer/init_var.php";
    +    if (!is_writable($var_file)) {
    +      print "$var_file is not writeable";
    +      return;
    +    }
    +
    +    $paths = array();
    +    foreach($objects as $name => $file){
    +      $path = $file->getPath();
    +      $basename = $file->getBasename();
    +      if ($basename == "database.php" || $basename == "." || $basename == "..") {
    +        continue;
    +      } else if (basename($path) == "logs" && $basename != ".htaccess") {
    +        continue;
    +      }
    +
    +      if ($file->isDir()) {
    +        $paths[] = "VARPATH . \"" . substr($name, strlen(VARPATH)) . "\"";
    +      } else {
    +        // @todo: serialize non-directories
    +        $files["VARPATH . \"" . substr($name, strlen(VARPATH)) . "\""] =
    +          base64_encode(file_get_contents($name));
    +      }
    +    }
    +    // Sort the paths so that the var file is stable
    +    sort($paths);
    +
    +    $fd = fopen($var_file, "w");
    +    fwrite($fd, "\n");
    +    fwrite($fd, " $contents) {
    +      fwrite($fd, "file_put_contents($file, base64_decode(\"$contents\"));\n");
    +    }
    +    fclose($fd);
    +  }
    +}
    \ No newline at end of file
    diff --git a/modules/gallery/controllers/permissions.php b/modules/gallery/controllers/permissions.php
    new file mode 100644
    index 0000000..2513f86
    --- /dev/null
    +++ b/modules/gallery/controllers/permissions.php
    @@ -0,0 +1,91 @@
    +is_album()) {
    +      access::forbidden();
    +    }
    +
    +    $view = new View("permissions_browse.html");
    +    $view->htaccess_works = access::htaccess_works();
    +    $view->item = $item;
    +    $view->parents = $item->parents();
    +    $view->form = $this->_get_form($item);
    +
    +    print $view;
    +  }
    +
    +  function form($id) {
    +    $item = ORM::factory("item", $id);
    +    access::required("view", $item);
    +    access::required("edit", $item);
    +
    +    if (!$item->is_album()) {
    +      access::forbidden();
    +    }
    +
    +    print $this->_get_form($item);
    +  }
    +
    +  function change($command, $group_id, $perm_id, $item_id) {
    +    access::verify_csrf();
    +
    +    $group = identity::lookup_group($group_id);
    +    $perm = ORM::factory("permission", $perm_id);
    +    $item = ORM::factory("item", $item_id);
    +    access::required("view", $item);
    +    access::required("edit", $item);
    +
    +    if (!empty($group) && $perm->loaded() && $item->loaded()) {
    +      switch($command) {
    +      case "allow":
    +        access::allow($group, $perm->name, $item);
    +        break;
    +
    +      case "deny":
    +        access::deny($group, $perm->name, $item);
    +        break;
    +
    +      case "reset":
    +        access::reset($group, $perm->name, $item);
    +        break;
    +      }
    +
    +      // If the active user just took away their own edit permissions, give it back.
    +      if ($perm->name == "edit") {
    +        if (!access::user_can(identity::active_user(), "edit", $item)) {
    +          access::allow($group, $perm->name, $item);
    +        }
    +      }
    +    }
    +  }
    +
    +  private function _get_form($item) {
    +    $view = new View("permissions_form.html");
    +    $view->item = $item;
    +    $view->groups = identity::groups();
    +    $view->permissions = ORM::factory("permission")->find_all();
    +    return $view;
    +  }
    +}
    diff --git a/modules/gallery/controllers/photos.php b/modules/gallery/controllers/photos.php
    new file mode 100644
    index 0000000..96a22c5
    --- /dev/null
    +++ b/modules/gallery/controllers/photos.php
    @@ -0,0 +1,91 @@
    +set_global(array("item" => $photo,
    +                                "children" => array(),
    +                                "children_count" => 0));
    +    $template->set_global(item::get_display_context($photo));
    +    $template->content = new View("photo.html");
    +
    +    $photo->increment_view_count();
    +
    +    print $template;
    +  }
    +
    +  public function update($photo_id) {
    +    access::verify_csrf();
    +    $photo = ORM::factory("item", $photo_id);
    +    access::required("view", $photo);
    +    access::required("edit", $photo);
    +
    +    $form = photo::get_edit_form($photo);
    +    try {
    +      $valid = $form->validate();
    +      $photo->title = $form->edit_item->title->value;
    +      $photo->description = $form->edit_item->description->value;
    +      $photo->slug = $form->edit_item->slug->value;
    +      $photo->name = $form->edit_item->inputs["name"]->value;
    +      $photo->validate();
    +    } catch (ORM_Validation_Exception $e) {
    +      // Translate ORM validation errors into form error messages
    +      foreach ($e->validation->errors() as $key => $error) {
    +        $form->edit_item->inputs[$key]->add_error($error, 1);
    +      }
    +      $valid = false;
    +    }
    +
    +    if ($valid) {
    +      $photo->save();
    +      module::event("item_edit_form_completed", $photo, $form);
    +
    +      log::success("content", "Updated photo", "url()}\">view");
    +      message::success(
    +        t("Saved photo %photo_title", array("photo_title" => html::purify($photo->title))));
    +
    +      if ($form->from_id->value == $photo->id) {
    +        // Use the new url; it might have changed.
    +        json::reply(array("result" => "success", "location" => $photo->url()));
    +      } else {
    +        // Stay on the same page
    +        json::reply(array("result" => "success"));
    +      }
    +    } else {
    +      json::reply(array("result" => "error", "html" => (string)$form));
    +    }
    +  }
    +
    +  public function form_edit($photo_id) {
    +    $photo = ORM::factory("item", $photo_id);
    +    access::required("view", $photo);
    +    access::required("edit", $photo);
    +
    +    print photo::get_edit_form($photo);
    +  }
    +}
    diff --git a/modules/gallery/controllers/quick.php b/modules/gallery/controllers/quick.php
    new file mode 100644
    index 0000000..4b21d9e
    --- /dev/null
    +++ b/modules/gallery/controllers/quick.php
    @@ -0,0 +1,144 @@
    +file_path(), PATHINFO_EXTENSION));
    +      gallery_graphics::rotate($item->file_path(), $tmpfile, array("degrees" => $degrees), $item);
    +      $item->set_data_file($tmpfile);
    +      $item->save();
    +    }
    +
    +    if (Input::instance()->get("page_type") == "collection") {
    +      json::reply(
    +        array("src" => $item->thumb_url(),
    +              "width" => $item->thumb_width,
    +              "height" => $item->thumb_height));
    +    } else {
    +      json::reply(
    +        array("src" => $item->resize_url(),
    +              "width" => $item->resize_width,
    +              "height" => $item->resize_height));
    +    }
    +  }
    +
    +  public function make_album_cover($id) {
    +    access::verify_csrf();
    +
    +    $item = model_cache::get("item", $id);
    +    access::required("view", $item);
    +    access::required("view", $item->parent());
    +    access::required("edit", $item->parent());
    +
    +    $msg = t("Made %title this album's cover", array("title" => html::purify($item->title)));
    +
    +    item::make_album_cover($item);
    +    message::success($msg);
    +
    +    json::reply(array("result" => "success", "reload" => 1));
    +  }
    +
    +  public function form_delete($id) {
    +    $item = model_cache::get("item", $id);
    +    access::required("view", $item);
    +    access::required("edit", $item);
    +
    +    $v = new View("quick_delete_confirm.html");
    +    $v->item = $item;
    +    $v->form = item::get_delete_form($item);
    +    print $v;
    +  }
    +
    +  public function delete($id) {
    +    access::verify_csrf();
    +    $item = model_cache::get("item", $id);
    +    access::required("view", $item);
    +    access::required("edit", $item);
    +
    +    if ($item->is_album()) {
    +      $msg = t("Deleted album %title", array("title" => html::purify($item->title)));
    +    } else {
    +      $msg = t("Deleted photo %title", array("title" => html::purify($item->title)));
    +    }
    +
    +    $parent = $item->parent();
    +
    +    if ($item->is_album()) {
    +      // Album delete will trigger deletes for all children.  Do this in a batch so that we can be
    +      // smart about notifications, album cover updates, etc.
    +      batch::start();
    +      $item->delete();
    +      batch::stop();
    +    } else {
    +      $item->delete();
    +    }
    +    message::success($msg);
    +
    +    $from_id = Input::instance()->get("from_id");
    +    if (Input::instance()->get("page_type") == "collection" &&
    +        $from_id != $id /* deleted the item we were viewing */) {
    +      json::reply(array("result" => "success", "reload" => 1));
    +    } else {
    +      json::reply(array("result" => "success", "location" => $parent->url()));
    +    }
    +  }
    +
    +  public function form_edit($id) {
    +    $item = model_cache::get("item", $id);
    +    access::required("view", $item);
    +    access::required("edit", $item);
    +
    +    switch ($item->type) {
    +    case "album":
    +      $form = album::get_edit_form($item);
    +      break;
    +
    +    case "photo":
    +      $form = photo::get_edit_form($item);
    +      break;
    +
    +    case "movie":
    +      $form = movie::get_edit_form($item);
    +      break;
    +    }
    +
    +    // Pass on the source item where this form was generated, so we have an idea where to return to.
    +    $form->hidden("from_id")->value((int)Input::instance()->get("from_id", 0));
    +
    +    print $form;
    +  }
    +}
    diff --git a/modules/gallery/controllers/reauthenticate.php b/modules/gallery/controllers/reauthenticate.php
    new file mode 100644
    index 0000000..c79b76e
    --- /dev/null
    +++ b/modules/gallery/controllers/reauthenticate.php
    @@ -0,0 +1,105 @@
    +get_once("is_ajax_request", request::is_ajax());
    +    if (!identity::active_user()->admin) {
    +      if ($is_ajax) {
    +        // We should never be able to get here since Admin_Controller::_reauth_check() won't work
    +        // for non-admins.
    +        access::forbidden();
    +      } else {
    +        url::redirect(item::root()->abs_url());
    +      }
    +    }
    +
    +    // On redirects from the admin controller, the ajax request indicator is lost,
    +    // so we store it in the session.
    +    if ($is_ajax) {
    +      $v = new View("reauthenticate.html");
    +      $v->form = self::_form();
    +      $v->user_name = identity::active_user()->name;
    +      print $v;
    +    } else {
    +      self::_show_form(self::_form());
    +    }
    +  }
    +
    +  public function auth() {
    +    if (!identity::active_user()->admin) {
    +      access::forbidden();
    +    }
    +    access::verify_csrf();
    +
    +    $form = self::_form();
    +    $valid = $form->validate();
    +    $user = identity::active_user();
    +    if ($valid) {
    +      module::event("user_auth", $user);
    +      if (!request::is_ajax()) {
    +        message::success(t("Successfully re-authenticated!"));
    +      }
    +      url::redirect(Session::instance()->get_once("continue_url"));
    +    } else {
    +      $name = $user->name;
    +      log::warning("user", t("Failed re-authentication for %name", array("name" => $name)));
    +      module::event("user_auth_failed", $name);
    +      if (request::is_ajax()) {
    +        $v = new View("reauthenticate.html");
    +        $v->form = $form;
    +        $v->user_name = identity::active_user()->name;
    +        json::reply(array("html" => (string)$v));
    +      } else {
    +        self::_show_form($form);
    +      }
    +    }
    +  }
    +
    +  private static function _show_form($form) {
    +    $view = new Theme_View("page.html", "other", "reauthenticate");
    +    $view->page_title = t("Re-authenticate");
    +    $view->content = new View("reauthenticate.html");
    +    $view->content->form = $form;
    +    $view->content->user_name = identity::active_user()->name;
    +
    +    print $view;
    +  }
    +
    +  private static function _form() {
    +    $form = new Forge("reauthenticate/auth", "", "post", array("id" => "g-reauthenticate-form"));
    +    $form->set_attr("class", "g-narrow");
    +    $group = $form->group("reauthenticate")->label(t("Re-authenticate"));
    +    $group->password("password")->label(t("Password"))->id("g-password")->class(null)
    +      ->callback("auth::validate_too_many_failed_auth_attempts")
    +      ->callback("Reauthenticate_Controller::valid_password")
    +      ->error_messages("invalid_password", t("Incorrect password"))
    +      ->error_messages(
    +        "too_many_failed_auth_attempts",
    +        t("Too many incorrect passwords.  Try again later"));
    +    $group->submit("")->value(t("Submit"));
    +    return $form;
    +  }
    +
    +  static function valid_password($password_input) {
    +    if (!identity::is_correct_password(identity::active_user(), $password_input->value)) {
    +      $password_input->add_error("invalid_password", 1);
    +    }
    +  }
    +}
    diff --git a/modules/gallery/controllers/upgrader.php b/modules/gallery/controllers/upgrader.php
    new file mode 100644
    index 0000000..6b3a9ef
    --- /dev/null
    +++ b/modules/gallery/controllers/upgrader.php
    @@ -0,0 +1,118 @@
    +get("upgrade_token", null))) {
    +      $session->set("upgrade_token", $upgrade_token = random::hash());
    +    }
    +
    +    // If the upgrade token exists, then bless this session
    +    if (file_exists(TMPPATH . $upgrade_token)) {
    +      $session->set("can_upgrade", true);
    +      @unlink(TMPPATH . $upgrade_token);
    +    }
    +
    +    $available_upgrades = 0;
    +    foreach (module::available() as $module) {
    +      if ($module->version && $module->version != $module->code_version) {
    +        $available_upgrades++;
    +      }
    +    }
    +
    +    $failed = Input::instance()->get("failed");
    +    $view = new View("upgrader.html");
    +    $view->can_upgrade = identity::active_user()->admin || $session->get("can_upgrade");
    +    $view->upgrade_token = $upgrade_token;
    +    $view->available = module::available();
    +    $view->failed = $failed ? explode(",", $failed) : array();
    +    $view->done = $available_upgrades == 0;
    +    $view->obsolete_modules_message = module::get_obsolete_modules_message();
    +    print $view;
    +  }
    +
    +  public function upgrade() {
    +    if (php_sapi_name() == "cli") {
    +      // @todo this may screw up some module installers, but we don't have a better answer at
    +      // this time.
    +      $_SERVER["HTTP_HOST"] = "example.com";
    +    } else {
    +      if (!identity::active_user()->admin && !Session::instance()->get("can_upgrade", false)) {
    +        access::forbidden();
    +      }
    +
    +      try {
    +        access::verify_csrf();
    +      } catch (Exception $e) {
    +        url::redirect("upgrader");
    +      }
    +    }
    +
    +    $available = module::available();
    +    // Upgrade gallery first
    +    $gallery = $available["gallery"];
    +    if ($gallery->code_version != $gallery->version) {
    +      module::upgrade("gallery");
    +      module::activate("gallery");
    +    }
    +
    +    // Then upgrade the rest
    +    $failed = array();
    +    foreach (module::available() as $id => $module) {
    +      if ($id == "gallery") {
    +        continue;
    +      }
    +
    +      if ($module->active && $module->code_version != $module->version) {
    +        try {
    +          module::upgrade($id);
    +        } catch (Exception $e) {
    +          // @todo assume it's MODULE_FAILED_TO_UPGRADE for now
    +          $failed[] = $id;
    +        }
    +      }
    +    }
    +
    +    // If the upgrade failed, this will get recreated
    +    site_status::clear("upgrade_now");
    +
    +    // Clear any upgrade check strings, we are probably up to date.
    +    site_status::clear("upgrade_checker");
    +
    +    if (php_sapi_name() == "cli") {
    +      if ($failed) {
    +        print "Upgrade completed ** WITH FAILURES **\n";
    +        print "The following modules were not successfully upgraded:\n";
    +        print "  " . implode($failed, "\n  ") . "\n";
    +        print "Try getting newer versions or deactivating those modules\n";
    +      } else {
    +        print "Upgrade complete\n";
    +      }
    +    } else {
    +      if ($failed) {
    +        url::redirect("upgrader?failed=" . join(",", $failed));
    +      } else {
    +        url::redirect("upgrader");
    +      }
    +    }
    +  }
    +}
    diff --git a/modules/gallery/controllers/uploader.php b/modules/gallery/controllers/uploader.php
    new file mode 100644
    index 0000000..8e09dbe
    --- /dev/null
    +++ b/modules/gallery/controllers/uploader.php
    @@ -0,0 +1,133 @@
    +is_album()) {
    +      $item = $item->parent();
    +    }
    +
    +    print $this->_get_add_form($item);
    +  }
    +
    +  public function start() {
    +    access::verify_csrf();
    +    batch::start();
    +  }
    +
    +  public function add_photo($id) {
    +    $album = ORM::factory("item", $id);
    +    access::required("view", $album);
    +    access::required("add", $album);
    +    access::verify_csrf();
    +
    +    // The Flash uploader not call /start directly, so simulate it here for now.
    +    if (!batch::in_progress()) {
    +      batch::start();
    +    }
    +
    +    $form = $this->_get_add_form($album);
    +
    +    // Uploadify adds its own field to the form, so validate that separately.
    +    $file_validation = new Validation($_FILES);
    +    $file_validation->add_rules(
    +      "Filedata", "upload::valid",  "upload::required",
    +      "upload::type[" . implode(",", legal_file::get_extensions()) . "]");
    +
    +    if ($form->validate() && $file_validation->validate()) {
    +      $temp_filename = upload::save("Filedata");
    +      system::delete_later($temp_filename);
    +      try {
    +        $item = ORM::factory("item");
    +        $item->name = substr(basename($temp_filename), 10);  // Skip unique identifier Kohana adds
    +        $item->title = item::convert_filename_to_title($item->name);
    +        $item->parent_id = $album->id;
    +        $item->set_data_file($temp_filename);
    +
    +        $path_info = @pathinfo($temp_filename);
    +        if (array_key_exists("extension", $path_info) &&
    +            legal_file::get_movie_extensions($path_info["extension"])) {
    +          $item->type = "movie";
    +          $item->save();
    +          log::success("content", t("Added a movie"),
    +                       html::anchor("movies/$item->id", t("view movie")));
    +        } else {
    +          $item->type = "photo";
    +          $item->save();
    +          log::success("content", t("Added a photo"),
    +                       html::anchor("photos/$item->id", t("view photo")));
    +        }
    +
    +        module::event("add_photos_form_completed", $item, $form);
    +      } catch (Exception $e) {
    +        // The Flash uploader has no good way of reporting complex errors, so just keep it simple.
    +        Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
    +
    +        // Ugh.  I hate to use instanceof, But this beats catching the exception separately since
    +        // we mostly want to treat it the same way as all other exceptions
    +        if ($e instanceof ORM_Validation_Exception) {
    +          Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1));
    +        }
    +
    +        header("HTTP/1.1 500 Internal Server Error");
    +        print "ERROR: " . $e->getMessage();
    +        return;
    +      }
    +      print "FILEID: $item->id";
    +    } else {
    +      header("HTTP/1.1 400 Bad Request");
    +      print "ERROR: " . t("Invalid upload");
    +    }
    +  }
    +
    +  public function status($success_count, $error_count) {
    +    if ($error_count) {
    +      // The "errors" won't be properly pluralized :-/
    +      print t2("Uploaded %count photo (%error errors)",
    +               "Uploaded %count photos (%error errors)",
    +               (int)$success_count,
    +               array("error" => (int)$error_count));
    +    } else {
    +      print t2("Uploaded %count photo", "Uploaded %count photos", $success_count);}
    +  }
    +
    +  public function finish() {
    +    access::verify_csrf();
    +
    +    batch::stop();
    +    json::reply(array("result" => "success"));
    +  }
    +
    +  private function _get_add_form($album)  {
    +    $form = new Forge("uploader/finish", "", "post", array("id" => "g-add-photos-form"));
    +    $group = $form->group("add_photos")
    +      ->label(t("Add photos to %album_title", array("album_title" => html::purify($album->title))));
    +    $group->uploadify("uploadify")->album($album);
    +
    +    $group = $form->group("actions");
    +    $group->uploadify_buttons("");
    +
    +    module::event("add_photos_form", $album, $form);
    +
    +    return $form;
    +  }
    +}
    diff --git a/modules/gallery/controllers/user_profile.php b/modules/gallery/controllers/user_profile.php
    new file mode 100644
    index 0000000..cf589da
    --- /dev/null
    +++ b/modules/gallery/controllers/user_profile.php
    @@ -0,0 +1,108 @@
    +_can_view_profile_pages($user)) {
    +      throw new Kohana_404_Exception();
    +    }
    +
    +    $v = new Theme_View("page.html", "other", "profile");
    +    $v->page_title = t("%name Profile", array("name" => $user->display_name()));
    +    $v->content = new View("user_profile.html");
    +
    +    $v->content->user = $user;
    +    $v->content->contactable =
    +      !$user->guest && $user->id != identity::active_user()->id && $user->email;
    +    $v->content->editable =
    +      identity::is_writable() && !$user->guest && $user->id == identity::active_user()->id;
    +
    +    $event_data = (object)array("user" => $user, "content" => array());
    +    module::event("show_user_profile", $event_data);
    +    $v->content->info_parts = $event_data->content;
    +
    +    print $v;
    +  }
    +
    +  public function contact($id) {
    +    $user = identity::lookup_user($id);
    +    if (!$this->_can_view_profile_pages($user)) {
    +      throw new Kohana_404_Exception();
    +    }
    +
    +    print user_profile::get_contact_form($user);
    +  }
    +
    +  public function send($id) {
    +    access::verify_csrf();
    +    $user = identity::lookup_user($id);
    +    if (!$this->_can_view_profile_pages($user)) {
    +      throw new Kohana_404_Exception();
    +    }
    +
    +    $form = user_profile::get_contact_form($user);
    +    if ($form->validate()) {
    +      Sendmail::factory()
    +        ->to($user->email)
    +        ->subject(html::clean($form->message->subject->value))
    +        ->header("Mime-Version", "1.0")
    +        ->header("Content-type", "text/html; charset=UTF-8")
    +        ->reply_to($form->message->reply_to->value)
    +        ->message(html::purify($form->message->message->value))
    +        ->send();
    +      message::success(t("Sent message to %user_name", array("user_name" => $user->display_name())));
    +      json::reply(array("result" => "success"));
    +    } else {
    +      json::reply(array("result" => "error", "html" => (string)$form));
    +    }
    +  }
    +
    +  private function _can_view_profile_pages($user) {
    +    if (!$user->loaded()) {
    +      return false;
    +    }
    +
    +    if ($user->id == identity::active_user()->id) {
    +      // You can always view your own profile
    +      return true;
    +    }
    +
    +    switch (module::get_var("gallery", "show_user_profiles_to")) {
    +    case "admin_users":
    +      return identity::active_user()->admin;
    +
    +    case "registered_users":
    +      return !identity::active_user()->guest;
    +
    +    case "everybody":
    +      return true;
    +
    +    default:
    +      // Fail in private mode on an invalid setting
    +      return false;
    +    }
    +  }
    +}
    diff --git a/modules/gallery/controllers/welcome_message.php b/modules/gallery/controllers/welcome_message.php
    new file mode 100644
    index 0000000..bde5e94
    --- /dev/null
    +++ b/modules/gallery/controllers/welcome_message.php
    @@ -0,0 +1,30 @@
    +admin) {
    +      url::redirect(item::root()->abs_url());
    +    }
    +
    +    $v = new View("welcome_message.html");
    +    $v->user = identity::active_user();
    +    print $v;
    +  }
    +}
    diff --git a/modules/gallery/css/debug.css b/modules/gallery/css/debug.css
    new file mode 100644
    index 0000000..6808da0
    --- /dev/null
    +++ b/modules/gallery/css/debug.css
    @@ -0,0 +1,28 @@
    +.g-annotated-theme-block {
    +  border: 1px solid #C00;
    +  clear: both;
    +  margin: 1em;
    +  padding: 1em;
    +  position: relative;
    +}
    +
    +.g-annotated-theme-block_album_top {
    +  float: right;
    +}
    +
    +.g-annotated-theme-block_header_bottom {
    +  float: right;
    +}
    +
    +.g-annotated-theme-block div.title {
    +  background: #C00;
    +  border: 1px solid black;
    +  color: white;
    +  font-size: 110%;
    +  padding: 4px;
    +  position: absolute;
    +  right: -1em;
    +  top: -1em;
    +  text-align: left;
    +  -moz-border-radius: 5%;
    +}
    diff --git a/modules/gallery/css/gallery.css b/modules/gallery/css/gallery.css
    new file mode 100644
    index 0000000..7e71115
    --- /dev/null
    +++ b/modules/gallery/css/gallery.css
    @@ -0,0 +1,213 @@
    +/**
    + * Gallery 3 core module styles
    + *
    + * Sheet organization:
    + *  1)  End-user
    + *  2)  Admin
    + *  3)  Right to left language styles
    + */
    +
    +/** *******************************************************************
    + * 1) End-user
    + **********************************************************************/
    +
    +/* Uploader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
    +
    +#g-add-photos-canvas,
    +#g-add-photos-status {
    +  width: 469px;
    +}
    +
    +#g-add-photos-canvas {
    +  border: 1px solid  #ccc;
    +  height: 200px;
    +  margin: .5em 0;
    +  padding: 2.8em 0 0 0;
    +  overflow: auto;
    +  position: relative;
    +}
    +
    +#g-add-photos-canvas object,
    +#g-add-photos-button {
    +  left: 90px;
    +  margin: .5em 0;
    +  padding: .4em 1em;
    +  position: absolute;
    +  top: 0;
    +  width: 300px;
    +}
    +
    +#g-add-photos-canvas object {
    +  z-index: 100;
    +  padding: 0em;
    +}
    +
    +#g-add-photos-canvas .uploadifyQueue {
    +  margin-top: .5em;
    +}
    +
    +#g-add-photos-canvas .uploadifyQueueItem {
    +  margin: 0;
    +}
    +
    +#g-add-photos-button {
    +  z-index: 1;
    +}
    +
    +#g-add-photos-status {
    +  border: 1px solid #ccc;
    +  height: 125px;
    +  margin: .4em 0;
    +  overflow: auto;
    +}
    +
    +#g-add-photos-status .g-message-block {
    +  border: none;
    +}
    +
    +#g-add-photos-status #g-action-status li {
    +  margin: 0 0 1px 0;
    +  padding-top: .7em;
    +  width: 433px;
    +}
    +
    +#g-add-photos-form .g-breadcrumbs {
    +  margin: 0;
    +}
    +
    +#g-add-photos-form p {
    +  margin-bottom: 0
    +}
    +
    +#g-add-photos-status-message {
    +  float: right;
    +}
    +
    +/* Permissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
    +
    +#g-edit-permissions-form {
    +  clear: both;
    +}
    +
    +#g-edit-permissions-form th {
    +  text-align: center;
    +}
    +
    +#g-permissions .g-denied,
    +#g-permissions .g-allowed {
    +  text-align: center;
    +  vertical-align: middle;
    +}
    +
    +/* Move items ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
    +
    +#g-move ul {
    +  padding-left: 1em;
    +}
    +
    +#g-move .selected {
    +  background: #999;
    +}
    +
    +/* In-place edit ~~~~~~~~~~~~~~~~~~~~~~~~~ */
    +
    +#g-in-place-edit-form ul {
    +  margin: 0;
    +}
    +
    +/* User profile ~~~~~~~~~~~~~~~~~~~~~~~~~ */
    +
    +#g-user-profile h1 {
    +  margin: 1em 0;
    +}
    +
    +#g-user-profile .g-avatar {
    +  margin-right: .6em;
    +}
    +
    +#g-user-profile .g-block {
    +  margin-top: 0;
    +}
    +
    +#g-user-profile .g-block-content {
    +  margin-top: 0;
    +}
    +
    +#g-user-profile th,
    +#g-user-profile td {
    +  border: none;
    +}
    +
    +#g-user-profile th {
    +  white-space: nowrap;
    +  width: 1%;
    +}
    +
    +/* Unsupported movie download link ~~~~~~~~~~~~~~~~~~~~~~~~~ */
    +
    +.g-movie-download-link {
    +  text-align: center;
    +}
    +
    +/** *******************************************************************
    + * 2) Admin
    + **********************************************************************/
    +
    +.g-task-log {
    +  border: 1px solid #000;
    +  height: 400px;
    +  margin: .6em 0;
    +  overflow: auto;
    +  padding: .4em
    +}
    +
    +#g-languages-form table {
    +  width: 40%;
    +  margin: 0 3em 1em 0;
    +}
    +
    +#g-languages-form input {
    +  clear: both;
    +}
    +
    +#g-translations ol {
    +  margin: 0 0 1em 2em;
    +}
    +#g-translations ol li {
    +  list-style-type: decimal;
    +  line-height: 150%;
    +}
    +
    +#g-translations .g-button {
    +  padding: .5em;
    +  margin-bottom: 1em;
    +}
    +
    +/** *******************************************************************
    + * 3) Right to left language styles
    + **********************************************************************/
    +
    +.rtl #g-add-photos-status #g-action-status li {
    +  width: 407px;
    +}
    +
    +.rtl #g-block-admin .g-left {
    +  margin-left: 1em;
    +  margin-right: 0;
    +}
    +
    +.rtl #g-user-profile .g-avatar {
    +  margin-left: .6em;
    +}
    +
    +.rtl #g-languages-form table {
    +  margin: 0 0 1em 3em;
    +}
    +
    +.rtl #g-translations ol {
    +  margin: 0 2em 1em 0;
    +}
    +
    +.rtl #g-add-photos-status-message {
    +  float: left;
    +}
    diff --git a/modules/gallery/css/l10n_client.css b/modules/gallery/css/l10n_client.css
    new file mode 100644
    index 0000000..90034d0
    --- /dev/null
    +++ b/modules/gallery/css/l10n_client.css
    @@ -0,0 +1,206 @@
    +/**
    + * TODO(andy_st): Add original copyright notice from Drupal l10_client.
    + * TODO(andy_st): Add G3 copyright notice.
    + * TODO(andy_st): clean up formatting to match our other CSS files.
    + */
    +
    +/* $Id: l10n_client.css,v 1.6 2008/09/09 10:48:20 goba Exp $ */
    +
    +/* width percentages add to 99% rather than 100% to prevent float
    +   overflows from occurring in an unnamed browser that can't decide
    +   how it wants to round. */
    +
    +/* l10n_client container */
    +#l10n-client {
    +  text-align:left;
    +  z-index:99;
    +  line-height:1em;
    +  color:#000; background:#fff;
    +  position:fixed;
    +  width:100%; height: 2em;
    +  bottom:0px; left:0px;
    +  overflow:hidden;}
    +
    +* html #l10n-client {
    +  position:static;}
    +
    +#l10n-client-string-select .string-list,
    +#l10n-client-string-editor .source,
    +#l10n-client-string-editor .editor {
    +  height:20em;}
    +
    +#l10n-client .labels {
    +  overflow:hidden;
    +  position:relative;
    +  height:2em;
    +  color:#fff;
    +  background:#37a;}
    +
    +#l10n-client .labels .label {
    +  display:none;}
    +
    +/* Panel toggle button (span) */
    +#l10n-client-toggler {
    +  cursor:pointer;
    +  display:block;
    +  position:absolute; right:0em;
    +  height:2em; line-height:2em;
    +  text-align:center; background:#000;
    +}
    +#l10n-client-toggler a {
    +	font-size: 1em;
    +	padding: .5em;
    +}
    +#l10n-client-toggler #g-minimize-l10n {
    +  border-right: 1px solid #ffffff;
    +}
    +
    +/* Panel labels */
    +#l10n-client h2 {
    +  border-left:1px solid #fff;
    +  height:1em; line-height:1em;
    +  padding: .5em; margin:0px;
    +  font-size:1em;
    +}
    +
    +#l10n-client .strings h2 {
    +  border:0px;}
    +
    +/* 25 + 37 + 37 = 99 */
    +#l10n-client .strings {
    +  width:25%; float:left;}
    +
    +#l10n-client .source {
    +  width:37%; float:left;}
    +
    +#l10n-client .translation {
    +  width:37%; float:left;}
    +
    +/* Translatable string list */
    +#l10n-client-string-select {
    +  display:none;
    +  float:left;
    +  width:25%;
    +  direction: ltr;
    +}
    +
    +#l10n-client .string-list {
    +  height: 16em;
    +  overflow:auto;
    +  list-style:none; list-style-image:none;
    +  margin:0em; padding:0em;}
    +
    +#l10n-client .string-list li {
    +  font-size:.9em;
    +  line-height:1.5em;
    +  cursor:default;
    +  background:transparent;
    +  list-style:none; list-style-image:none;
    +  border-bottom:1px solid #ddd;
    +  padding:.25em .5em;
    +  margin:0em;}
    +
    +/* Green for translated */
    +#l10n-client .string-list li.translated {
    +  border-bottom-color:#9c3;
    +  background:#cf6; color:#360;}
    +
    +#l10n-client .string-list li.translated:hover {
    +  background: #df8;}
    +
    +#l10n-client .string-list li.translated:active {
    +  background: #9c3;}
    +
    +#l10n-client .string-list li.hidden {
    +  display:none;}
    +
    +/* Gray + Blue hover for untranslated */
    +#l10n-client .string-list li.untranslated {}
    +
    +#l10n-client .string-list li.untranslated:hover {
    +  background: #ace;}
    +
    +#l10n-client .string-list li.untranslated:active {
    +  background: #8ac;}
    +
    +/* Selected string is indicated by bold text */
    +#l10n-client .string-list li.active {
    +  font-weight:bold;}
    +
    +#l10n-client #g-l10n-search-form {
    +  background: #eee;
    +  margin: 0em;
    +  padding: .25em .25em;
    +}
    +
    +#l10n-client #g-l10n-search-form .form-item,
    +#l10n-client #g-l10n-search-form input.form-text,
    +#l10n-client #g-l10n-search-form #search-filter-go,
    +#l10n-client #g-l10n-search-form #search-filter-clear {
    +  display:inline;
    +  vertical-align:middle;
    +}
    +
    +#l10n-client #g-l10n-search-form .form-item {
    +  margin:0em;
    +  padding:0em;
    +}
    +
    +#l10n-client #g-l10n-search-form fieldset {
    +  margin-bottom: 0;
    +  padding: .25em .25em;
    +}
    +
    +
    +#l10n-client #g-l10n-search-form input {
    +  width: 96.75%;
    +}
    +
    +#l10n-client #g-l10n-search-form #search-filter-clear {
    +  width:10%;
    +  margin:0em;
    +}
    +
    +#l10n-client-string-editor {
    +  display:none;
    +  float:left;
    +  width:74%;}
    +
    +#l10n-client-string-editor .source {
    +  overflow:hidden;
    +  width:50%;
    +  float:left;
    +}
    +
    +#l10n-client-string-editor .source .source-text {
    +  line-height:1.5em;
    +  background:#eee;
    +  font-family: monospace;
    +  text-align: left;
    +  height:16em; margin:1em; padding:1em;
    +  overflow:auto;
    +  direction: ltr;
    +}
    +
    +#l10n-client-string-editor .translation {
    +  overflow-y:auto;
    +  overflow-x: hidden;
    +  height: 20em;
    +  width:49%;
    +  float: right;
    +}
    +
    +#g-l10n-client-save-form {
    +  padding: 0em;
    +}
    +
    +#l10n-client form ul,
    +#l10n-client form li,
    +#l10n-client form input[type=submit],
    +#l10n-client form input[type=text] {
    +  display: inline ! important ;
    +}
    +
    +#l10n-client form .hidden {
    +  display: none;
    +}
    diff --git a/modules/gallery/css/upgrader.css b/modules/gallery/css/upgrader.css
    new file mode 100644
    index 0000000..8610016
    --- /dev/null
    +++ b/modules/gallery/css/upgrader.css
    @@ -0,0 +1,183 @@
    +body {
    +  background: #eee;
    +  font-family: 'Trebuchet MS';
    +  font-size: 1.1em;
    +}
    +
    +h1 {
    +  font-size: 1.4em;
    +}
    +
    +div#outer {
    +  background: #fff;
    +  border: 1px solid #999;
    +  margin: 0 auto;
    +  width: 650px;
    +}
    +
    +div#inner {
    +  margin: 0;
    +  padding: 0 1em;
    +}
    +
    +div#footer {
    +  border-top: 1px solid #ccc;
    +  margin: 1em;
    +}
    +
    +table {
    +  width: 600px;
    +  margin-bottom: 10px;
    +}
    +
    +th.name,
    +td.name {
    +  text-align: left;
    +  padding-left: 30px;
    +}
    +
    +td {
    +  text-align: center;
    +  border-bottom: 1px solid #eee;
    +}
    +
    +tr.current td {
    +  opacity: 0.5;
    +  font-style: italic;
    +}
    +
    +tr.current td.gallery {
    +  color: #00d;
    +}
    +
    +tr.upgradeable td {
    +  font-weight: bold;
    +}
    +
    +tr.upgradeable td.gallery {
    +  color: #00d;
    +}
    +
    +tr.failed td {
    +  color: red;
    +}
    +
    +p {
    +  font-size: .9em;
    +}
    +
    +ul {
    +  font-size: .9em;
    +  list-style: none;
    +}
    +
    +li {
    +  display: inline;
    +}
    +
    +li:before {
    +  content: "\00BB \0020";
    +}
    +
    +div.button {
    +  margin: 0 auto;
    +  width: 120px;
    +  text-align: center;
    +  border: 1px solid #999;
    +  background: #eee;
    +}
    +
    +div.button a {
    +  text-decoration: none;
    +}
    +
    +div.button-active:hover {
    +  background: #ccc;
    +}
    +
    +div#dialog {
    +  width: 340px;
    +  height: 200px;
    +  position: absolute;
    +  background: blue;
    +  z-index: 1000;
    +  margin: 10px;
    +  text-align: center;
    +}
    +
    +div#dialog a.close {
    +  float: right;
    +  padding: 10px;
    +  text-decoration: none;
    +}
    +
    +div#dialog div {
    +  width: 292px;
    +  height: 152px;
    +  margin: 2px;
    +  padding: 20px;
    +  border: 2px solid #999;
    +  background: #eee;
    +}
    +
    +.muted {
    +  opacity: 0.5;
    +}
    +
    +.failed {
    +  color: red;
    +}
    +
    +pre {
    +  display: inline;
    +  margin: 0px;
    +  padding: 0px;
    +}
    +
    +div#upgrade_button {
    +  margin-bottom: 20px;
    +}
    +
    +div#welcome_message {
    +  margin-left: 30px;
    +}
    +
    +#logo {
    +  margin-left: 14px;
    +}
    +
    +.rtl {
    +  direction: rtl;
    +}
    +
    +.rtl th.name,
    +.rtl td.name {
    +  text-align: right;
    +  padding-right: 30px;
    +}
    +
    +
    +.rtl li:before {
    +  content: "";
    +}
    +
    +.rtl li:after {
    +  content: "\00BB \0020";
    +}
    +
    +.rtl ul {
    +  margin-right: 0;
    +  padding-right: 0;
    +}
    +
    +.rtl div#dialog a.close {
    +  float: left;
    +}
    +
    +.rtl div#welcome_message {
    +  padding-right: 30px;
    +}
    +
    +.rtl #logo {
    +  padding-right: 12px;
    +}
    diff --git a/modules/gallery/helpers/MY_html.php b/modules/gallery/helpers/MY_html.php
    new file mode 100644
    index 0000000..767fe3f
    --- /dev/null
    +++ b/modules/gallery/helpers/MY_html.php
    @@ -0,0 +1,91 @@
    +
    +   *   
    + *
    + */ + static function clean($html) { + return new SafeString($html); + } + + /** + * Returns a string that is safe to be used in HTML (XSS protection), + * purifying (filtering) the given HTML to ensure that the result contains + * only non-malicious HTML. + * + * Example:
    +   *   
    title) ?> + *
    + */ + static function purify($html) { + return SafeString::purify($html); + } + + /** + * Flags the given string as safe to be used in HTML (free of malicious HTML/JS). + * + * Example:
    +   *   // Parameters to t() are automatically escaped by default.
    +   *   // If the parameter is marked as clean, it won't get escaped.
    +   *   t('Go there',
    +   *     array("url" => html::mark_clean(url::current())))
    +   * 
    + */ + static function mark_clean($html) { + return SafeString::of_safe_html($html); + } + + /** + * Escapes the given string for use in JavaScript. + * + * Example:
    +   *   
    +   * 
    + */ + static function js_string($string) { + return SafeString::of($string)->for_js(); + } + + /** + * Returns a string safe for use in HTML element attributes. + * + * Assumes that the HTML element attribute is already + * delimited by single or double quotes + * + * Example:
    +   *     ;
    +   *   
    +   * 
    + * @return the string escaped for use in HTML attributes. + */ + static function clean_attribute($string) { + return html::clean($string)->for_html_attr(); + } +} diff --git a/modules/gallery/helpers/MY_num.php b/modules/gallery/helpers/MY_num.php new file mode 100644 index 0000000..a550a1a --- /dev/null +++ b/modules/gallery/helpers/MY_num.php @@ -0,0 +1,54 @@ + 1e9, "M" => 1e6, "K" => 1e3) as $k => $v) { + if ($num > $v) { + $num = round($num / $v) . $k; + } + } + return $num; + } +} diff --git a/modules/gallery/helpers/MY_remote.php b/modules/gallery/helpers/MY_remote.php new file mode 100644 index 0000000..59804b9 --- /dev/null +++ b/modules/gallery/helpers/MY_remote.php @@ -0,0 +1,167 @@ + $value) { + if (!empty($post_data_raw)) { + $post_data_raw .= '&'; + } + $post_data_raw .= urlencode($key) . '=' . urlencode($value); + } + + $extra_headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $extra_headers['Content-Length'] = strlen($post_data_raw); + + return $post_data_raw; + } + + /** + * A single request, without following redirects + * + * @todo: Handle redirects? If so, only for GET (i.e. not for POST), and use G2's + * WebHelper_simple::_parseLocation logic. + */ + static function do_request($url, $method='GET', $headers=array(), $body='') { + if (!array_key_exists("User-Agent", $headers)) { + $headers["User-Agent"] = "Gallery3"; + } + /* Convert illegal characters */ + $url = str_replace(' ', '%20', $url); + + $url_components = self::_parse_url_for_fsockopen($url); + $handle = fsockopen( + $url_components['fsockhost'], $url_components['port'], $errno, $errstr, 5); + if (empty($handle)) { + // log "Error $errno: '$errstr' requesting $url"; + return array(null, null, null); + } + + $header_lines = array('Host: ' . $url_components['host']); + foreach ($headers as $key => $value) { + $header_lines[] = $key . ': ' . $value; + } + + $success = fwrite($handle, sprintf("%s %s HTTP/1.0\r\n%s\r\n\r\n%s", + $method, + $url_components['uri'], + implode("\r\n", $header_lines), + $body)); + if (!$success) { + // Zero bytes written or false was returned + // log "fwrite failed in requestWebPage($url)" . ($success === false ? ' - false' : '' + return array(null, null, null); + } + fflush($handle); + + /* + * Read the status line. fgets stops after newlines. The first line is the protocol + * version followed by a numeric status code and its associated textual phrase. + */ + $response_status = trim(fgets($handle, 4096)); + if (empty($response_status)) { + // 'Empty http response code, maybe timeout' + return array(null, null, null); + } + + /* Read the headers */ + $response_headers = array(); + while (!feof($handle)) { + $line = trim(fgets($handle, 4096)); + if (empty($line)) { + break; + } + + /* Normalize the line endings */ + $line = str_replace("\r", '', $line); + + list ($key, $value) = explode(':', $line, 2); + if (isset($response_headers[$key])) { + if (!is_array($response_headers[$key])) { + $response_headers[$key] = array($response_headers[$key]); + } + $response_headers[$key][] = trim($value); + } else { + $response_headers[$key] = trim($value); + } + } + + /* Read the body */ + $response_body = ''; + while (!feof($handle)) { + $response_body .= fread($handle, 4096); + } + fclose($handle); + + return array($response_status, $response_headers, $response_body); + } + + /** + * Prepare for fsockopen call. + * @param string $url + * @return array url components + * @access private + */ + private static function _parse_url_for_fsockopen($url) { + $url_components = parse_url($url); + if (strtolower($url_components['scheme']) == 'https') { + $url_components['fsockhost'] = 'ssl://' . $url_components['host']; + $default_port = 443; + } else { + $url_components['fsockhost'] = $url_components['host']; + $default_port = 80; + } + if (empty($url_components['port'])) { + $url_components['port'] = $default_port; + } + if (empty($url_components['path'])) { + $url_components['path'] = '/'; + } + $uri = $url_components['path'] + . (empty($url_components['query']) ? '' : '?' . $url_components['query']); + /* Unescape ampersands, since if the url comes from form input it will be escaped */ + $url_components['uri'] = str_replace('&', '&', $uri); + return $url_components; + } +} + diff --git a/modules/gallery/helpers/MY_url.php b/modules/gallery/helpers/MY_url.php new file mode 100644 index 0000000..eba08b2 --- /dev/null +++ b/modules/gallery/helpers/MY_url.php @@ -0,0 +1,92 @@ +loaded()) { + Router::$controller = "{$item->type}s"; + Router::$controller_path = MODPATH . "gallery/controllers/{$item->type}s.php"; + Router::$method = "show"; + Router::$arguments = array($item); + } + } + + /** + * Just like url::file() except that it returns an absolute URI + */ + static function abs_file($path) { + return url::base(false, request::protocol()) . $path; + } + + /** + * Just like url::site() except that it returns an absolute URI and + * doesn't take a protocol parameter. + */ + static function abs_site($path) { + return url::site($path, request::protocol()); + } + + /** + * Just like url::current except that it returns an absolute URI + */ + static function abs_current($qs=false) { + return self::abs_site(url::current($qs)); + } + + /** + * Just like url::merge except that it escapes any XSS in the path. + */ + static function merge(array $arguments) { + return htmlspecialchars(parent::merge($arguments)); + } + + /** + * Just like url::current except that it escapes any XSS in the path. + */ + static function current($qs=false, $suffix=false) { + return htmlspecialchars(parent::current($qs, $suffix)); + } + + /** + * Merge extra an query string onto a given url safely. + * @param string the original url + * @param array the query string data in key=value form + */ + static function merge_querystring($url, $query_params) { + $qs = implode("&", $query_params); + if (strpos($url, "?") === false) { + return $url . "?$qs"; + } else { + return $url . "&$qs"; + } + } +} diff --git a/modules/gallery/helpers/MY_valid.php b/modules/gallery/helpers/MY_valid.php new file mode 100644 index 0000000..f1dd9c3 --- /dev/null +++ b/modules/gallery/helpers/MY_valid.php @@ -0,0 +1,26 @@ + tuples. It would be inefficient to check + * these tuples every time we want to do a lookup, so we use these intents to create an entire + * table of permissions for easy lookup in the Access_Cache_Model. There's a 1:1 mapping + * between Item_Model and Access_Cache_Model entries. + * + * o For efficiency, we create columns in Access_Intent_Model and Access_Cache_Model for each of + * the possible Group_Model and Permission_Model combinations. This may lead to performance + * issues for very large Gallery installs, but for small to medium sized ones (5-10 groups, 5-10 + * permissions) it's especially efficient because there's a single field value for each + * group/permission/item combination. + * + * o For efficiency, we store the cache columns for view permissions directly in the Item_Model. + * This means that we can filter items by group/permission combination without doing any table + * joins making for an especially efficient permission check at the expense of having to + * maintain extra columns for each item. + * + * o If at any time the Access_Cache_Model becomes invalid, we can rebuild the entire table from + * the Access_Intent_Model + */ +class access_Core { + const DENY = "0"; + const ALLOW = "1"; + const INHERIT = null; // access_intent + const UNKNOWN = null; // cache (access_cache, items) + + /** + * Does the active user have this permission on this item? + * + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function can($perm_name, $item) { + return access::user_can(identity::active_user(), $perm_name, $item); + } + + /** + * Does the user have this permission on this item? + * + * @param User_Model $user + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function user_can($user, $perm_name, $item) { + if (!$item->loaded()) { + return false; + } + + if ($user->admin) { + return true; + } + + // Use the nearest parent album (including the current item) so that we take advantage + // of the cache when checking many items in a single album. + $id = ($item->type == "album") ? $item->id : $item->parent_id; + $resource = $perm_name == "view" ? + $item : model_cache::get("access_cache", $id, "item_id"); + + foreach ($user->groups() as $group) { + if ($resource->__get("{$perm_name}_{$group->id}") === access::ALLOW) { + return true; + } + } + return false; + } + + /** + * If the active user does not have this permission, failed with an access::forbidden(). + * + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function required($perm_name, $item) { + if (!access::can($perm_name, $item)) { + if ($perm_name == "view") { + // Treat as if the item didn't exist, don't leak any information. + throw new Kohana_404_Exception(); + } else { + access::forbidden(); + } + } + } + + /** + * Does this group have this permission on this item? + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function group_can($group, $perm_name, $item) { + // Use the nearest parent album (including the current item) so that we take advantage + // of the cache when checking many items in a single album. + $id = ($item->type == "album") ? $item->id : $item->parent_id; + $resource = $perm_name == "view" ? + $item : model_cache::get("access_cache", $id, "item_id"); + + return $resource->__get("{$perm_name}_{$group->id}") === access::ALLOW; + } + + /** + * Return this group's intent for this permission on this item. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return boolean access::ALLOW, access::DENY or access::INHERIT (null) for no intent + */ + static function group_intent($group, $perm_name, $item) { + $intent = model_cache::get("access_intent", $item->id, "item_id"); + return $intent->__get("{$perm_name}_{$group->id}"); + } + + /** + * Is the permission on this item locked by a parent? If so return the nearest parent that + * locks it. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return ORM_Model item that locks this one + */ + static function locked_by($group, $perm_name, $item) { + if ($perm_name != "view") { + return null; + } + + // For view permissions, if any parent is access::DENY, then those parents lock this one. + // Return + $lock = ORM::factory("item") + ->where("left_ptr", "<=", $item->left_ptr) + ->where("right_ptr", ">=", $item->right_ptr) + ->where("items.id", "<>", $item->id) + ->join("access_intents", "items.id", "access_intents.item_id") + ->where("access_intents.view_$group->id", "=", access::DENY) + ->order_by("level", "DESC") + ->limit(1) + ->find(); + + if ($lock->loaded()) { + return $lock; + } else { + return null; + } + } + + /** + * Terminate immediately with an HTTP 403 Forbidden response. + */ + static function forbidden() { + throw new Kohana_Exception("@todo FORBIDDEN", null, 403); + } + + /** + * Internal method to set a permission + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @param boolean $value + */ + private static function _set(Group_Definition $group, $perm_name, $album, $value) { + if (!($group instanceof Group_Definition)) { + throw new Exception("@todo PERMISSIONS_ONLY_WORK_ON_GROUPS"); + } + if (!$album->loaded()) { + throw new Exception("@todo INVALID_ALBUM $album->id"); + } + if (!$album->is_album()) { + throw new Exception("@todo INVALID_ALBUM_TYPE not an album"); + } + $access = model_cache::get("access_intent", $album->id, "item_id"); + $access->__set("{$perm_name}_{$group->id}", $value); + $access->save(); + + if ($perm_name == "view") { + self::_update_access_view_cache($group, $album); + } else { + self::_update_access_non_view_cache($group, $perm_name, $album); + } + + access::update_htaccess_files($album, $group, $perm_name, $value); + model_cache::clear(); + } + + /** + * Allow a group to have a permission on an item. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + */ + static function allow($group, $perm_name, $item) { + self::_set($group, $perm_name, $item, self::ALLOW); + } + + /** + * Deny a group the given permission on an item. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + */ + static function deny($group, $perm_name, $item) { + self::_set($group, $perm_name, $item, self::DENY); + } + + /** + * Unset the given permission for this item and use inherited values + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + */ + static function reset($group, $perm_name, $item) { + if ($item->id == 1) { + throw new Exception("@todo CANT_RESET_ROOT_PERMISSION"); + } + self::_set($group, $perm_name, $item, self::INHERIT); + } + + /** + * Recalculate the permissions for an album's hierarchy. + */ + static function recalculate_album_permissions($album) { + foreach (self::_get_all_groups() as $group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + if ($perm->name == "view") { + self::_update_access_view_cache($group, $album); + } else { + self::_update_access_non_view_cache($group, $perm->name, $album); + } + } + } + model_cache::clear(); + } + + /** + * Recalculate the permissions for a single photo. + */ + static function recalculate_photo_permissions($photo) { + $parent = $photo->parent(); + $parent_access_cache = ORM::factory("access_cache")->where("item_id", "=", $parent->id)->find(); + $photo_access_cache = ORM::factory("access_cache")->where("item_id", "=", $photo->id)->find(); + foreach (self::_get_all_groups() as $group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + $field = "{$perm->name}_{$group->id}"; + if ($perm->name == "view") { + $photo->$field = $parent->$field; + } else { + $photo_access_cache->$field = $parent_access_cache->$field; + } + } + } + $photo_access_cache->save(); + $photo->save(); + model_cache::clear(); + } + + /** + * Register a permission so that modules can use it. + * + * @param string $name The internal name for for this permission + * @param string $display_name The internationalized version of the displayable name + * @return void + */ + static function register_permission($name, $display_name) { + $permission = ORM::factory("permission", $name); + if ($permission->loaded()) { + throw new Exception("@todo PERMISSION_ALREADY_EXISTS $name"); + } + $permission->name = $name; + $permission->display_name = $display_name; + $permission->save(); + + foreach (self::_get_all_groups() as $group) { + self::_add_columns($name, $group); + } + } + + /** + * Delete a permission. + * + * @param string $perm_name + * @return void + */ + static function delete_permission($name) { + foreach (self::_get_all_groups() as $group) { + self::_drop_columns($name, $group); + } + $permission = ORM::factory("permission")->where("name", "=", $name)->find(); + if ($permission->loaded()) { + $permission->delete(); + } + } + + /** + * Add the appropriate columns for a new group + * + * @param Group_Model $group + * @return void + */ + static function add_group($group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + self::_add_columns($perm->name, $group); + } + } + + /** + * Remove a group's permission columns (usually when it's deleted) + * + * @param Group_Model $group + * @return void + */ + static function delete_group($group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + self::_drop_columns($perm->name, $group); + } + } + + /** + * Add new access rows when a new item is added. + * + * @param Item_Model $item + * @return void + */ + static function add_item($item) { + $access_intent = ORM::factory("access_intent", $item->id); + if ($access_intent->loaded()) { + throw new Exception("@todo ITEM_ALREADY_ADDED $item->id"); + } + $access_intent = ORM::factory("access_intent"); + $access_intent->item_id = $item->id; + $access_intent->save(); + + // Create a new access cache entry and copy the parents values. + $access_cache = ORM::factory("access_cache"); + $access_cache->item_id = $item->id; + if ($item->id != 1) { + $parent_access_cache = + ORM::factory("access_cache")->where("item_id", "=", $item->parent()->id)->find(); + foreach (self::_get_all_groups() as $group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + $field = "{$perm->name}_{$group->id}"; + if ($perm->name == "view") { + $item->$field = $item->parent()->$field; + } else { + $access_cache->$field = $parent_access_cache->$field; + } + } + } + } + $item->save(); + $access_cache->save(); + } + + /** + * Delete appropriate access rows when an item is deleted. + * + * @param Item_Model $item + * @return void + */ + static function delete_item($item) { + ORM::factory("access_intent")->where("item_id", "=", $item->id)->find()->delete(); + ORM::factory("access_cache")->where("item_id", "=", $item->id)->find()->delete(); + } + + /** + * Verify our Cross Site Request Forgery token is valid, else throw an exception. + */ + static function verify_csrf() { + $input = Input::instance(); + if ($input->post("csrf", $input->get("csrf", null)) !== Session::instance()->get("csrf")) { + access::forbidden(); + } + } + + /** + * Get the Cross Site Request Forgery token for this session. + * @return string + */ + static function csrf_token() { + $session = Session::instance(); + $csrf = $session->get("csrf"); + if (empty($csrf)) { + $csrf = random::hash(); + $session->set("csrf", $csrf); + } + return $csrf; + } + + /** + * Generate an element containing the Cross Site Request Forgery token for this session. + * @return string + */ + static function csrf_form_field() { + return ""; + } + + /** + * Internal method to get all available groups. + * + * @return ORM_Iterator + */ + private static function _get_all_groups() { + // When we build the gallery package, it's possible that there is no identity provider + // installed yet. This is ok at packaging time, so work around it. + if (module::is_active(module::get_var("gallery", "identity_provider", "user"))) { + return identity::groups(); + } else { + return array(); + } + } + + /** + * Internal method to remove Permission/Group columns + * + * @param Group_Model $group + * @param string $perm_name + * @return void + */ + private static function _drop_columns($perm_name, $group) { + $field = "{$perm_name}_{$group->id}"; + $cache_table = $perm_name == "view" ? "items" : "access_caches"; + Database::instance()->query("ALTER TABLE {{$cache_table}} DROP `$field`"); + Database::instance()->query("ALTER TABLE {access_intents} DROP `$field`"); + model_cache::clear(); + ORM::factory("access_intent")->clear_cache(); + } + + /** + * Internal method to add Permission/Group columns + * + * @param Group_Model $group + * @param string $perm_name + * @return void + */ + private static function _add_columns($perm_name, $group) { + $field = "{$perm_name}_{$group->id}"; + $cache_table = $perm_name == "view" ? "items" : "access_caches"; + $not_null = $cache_table == "items" ? "" : "NOT NULL"; + Database::instance()->query( + "ALTER TABLE {{$cache_table}} ADD `$field` BINARY $not_null DEFAULT FALSE"); + Database::instance()->query( + "ALTER TABLE {access_intents} ADD `$field` BINARY DEFAULT NULL"); + db::build() + ->update("access_intents") + ->set($field, access::DENY) + ->where("item_id", "=", 1) + ->execute(); + model_cache::clear(); + ORM::factory("access_intent")->clear_cache(); + } + + /** + * Update the Access_Cache model based on information from the Access_Intent model for view + * permissions only. + * + * @todo: use database locking + * + * @param Group_Model $group + * @param Item_Model $item + * @return void + */ + private static function _update_access_view_cache($group, $item) { + $access = ORM::factory("access_intent")->where("item_id", "=", $item->id)->find(); + $field = "view_{$group->id}"; + + // With view permissions, deny values in the parent can override allow values in the child, + // so start from the bottom of the tree and work upwards overlaying negative on top of + // positive. + // + // If the item's intent is ALLOW or DEFAULT, it's possible that some ancestor has specified + // DENY and this ALLOW cannot be obeyed. So in that case, back up the tree and find any + // non-DEFAULT and non-ALLOW parent and propagate from there. If we can't find a matching + // item, then its safe to propagate from here. + if ($access->$field !== access::DENY) { + $tmp_item = ORM::factory("item") + ->where("left_ptr", "<", $item->left_ptr) + ->where("right_ptr", ">", $item->right_ptr) + ->join("access_intents", "access_intents.item_id", "items.id") + ->where("access_intents.$field", "=", access::DENY) + ->order_by("left_ptr", "DESC") + ->limit(1) + ->find(); + if ($tmp_item->loaded()) { + $item = $tmp_item; + } + } + + // We will have a problem if we're trying to change a DENY to an ALLOW because the + // access_caches table will already contain DENY values and we won't be able to overwrite + // them according the rule above. So mark every permission below this level as UNKNOWN so + // that we can tell which permissions have been changed, and which ones need to be updated. + db::build() + ->update("items") + ->set($field, access::UNKNOWN) + ->where("left_ptr", ">=", $item->left_ptr) + ->where("right_ptr", "<=", $item->right_ptr) + ->execute(); + + $query = ORM::factory("access_intent") + ->select(array("access_intents.$field", "items.left_ptr", "items.right_ptr", "items.id")) + ->join("items", "items.id", "access_intents.item_id") + ->where("left_ptr", ">=", $item->left_ptr) + ->where("right_ptr", "<=", $item->right_ptr) + ->where("type", "=", "album") + ->where("access_intents.$field", "IS NOT", access::INHERIT) + ->order_by("level", "DESC") + ->find_all(); + foreach ($query as $row) { + if ($row->$field == access::ALLOW) { + // Propagate ALLOW for any row that is still UNKNOWN. + db::build() + ->update("items") + ->set($field, $row->$field) + ->where($field, "IS", access::UNKNOWN) // UNKNOWN is NULL so we have to use IS + ->where("left_ptr", ">=", $row->left_ptr) + ->where("right_ptr", "<=", $row->right_ptr) + ->execute(); + } else if ($row->$field == access::DENY) { + // DENY overwrites everything below it + db::build() + ->update("items") + ->set($field, $row->$field) + ->where("left_ptr", ">=", $row->left_ptr) + ->where("right_ptr", "<=", $row->right_ptr) + ->execute(); + } + } + + // Finally, if our intent is DEFAULT at this point it means that we were unable to find a + // DENY parent in the hierarchy to propagate from. So we'll still have a UNKNOWN values in + // the hierarchy, and all of those are safe to change to ALLOW. + db::build() + ->update("items") + ->set($field, access::ALLOW) + ->where($field, "IS", access::UNKNOWN) // UNKNOWN is NULL so we have to use IS + ->where("left_ptr", ">=", $item->left_ptr) + ->where("right_ptr", "<=", $item->right_ptr) + ->execute(); + } + + /** + * Update the Access_Cache model based on information from the Access_Intent model for non-view + * permissions. + * + * @todo: use database locking + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return void + */ + private static function _update_access_non_view_cache($group, $perm_name, $item) { + $access = ORM::factory("access_intent")->where("item_id", "=", $item->id)->find(); + + $field = "{$perm_name}_{$group->id}"; + + // If the item's intent is DEFAULT, then we need to back up the chain to find the nearest + // parent with an intent and propagate from there. + // + // @todo To optimize this, we wouldn't need to propagate from the parent, we could just + // propagate from here with the parent's intent. + if ($access->$field === access::INHERIT) { + $tmp_item = ORM::factory("item") + ->join("access_intents", "items.id", "access_intents.item_id") + ->where("left_ptr", "<", $item->left_ptr) + ->where("right_ptr", ">", $item->right_ptr) + ->where($field, "IS NOT", access::UNKNOWN) // UNKNOWN is NULL so we have to use IS NOT + ->order_by("left_ptr", "DESC") + ->limit(1) + ->find(); + if ($tmp_item->loaded()) { + $item = $tmp_item; + } + } + + // With non-view permissions, each level can override any permissions that came above it + // so start at the top and work downwards, overlaying permissions as we go. + $query = ORM::factory("access_intent") + ->select(array("access_intents.$field", "items.left_ptr", "items.right_ptr")) + ->join("items", "items.id", "access_intents.item_id") + ->where("left_ptr", ">=", $item->left_ptr) + ->where("right_ptr", "<=", $item->right_ptr) + ->where($field, "IS NOT", access::INHERIT) + ->order_by("level", "ASC") + ->find_all(); + foreach ($query as $row) { + $value = ($row->$field === access::ALLOW) ? true : false; + db::build() + ->update("access_caches") + ->set($field, $value) + ->where("item_id", "IN", + db::build() + ->select("id") + ->from("items") + ->where("left_ptr", ">=", $row->left_ptr) + ->where("right_ptr", "<=", $row->right_ptr)) + ->execute(); + } + } + + /** + * Rebuild the .htaccess files that prevent direct access to albums, resizes and thumbnails. We + * call this internally any time we change the view or view_full permissions for guest users. + * This function is only public because we use it in maintenance tasks. + * + * @param Item_Model the album + * @param Group_Model the group whose permission is changing + * @param string the permission name + * @param string the new permission value (eg access::DENY) + */ + static function update_htaccess_files($album, $group, $perm_name, $value) { + if ($group->id != identity::everybody()->id || + !($perm_name == "view" || $perm_name == "view_full")) { + return; + } + + $dirs = array($album->file_path()); + if ($perm_name == "view") { + $dirs[] = dirname($album->resize_path()); + $dirs[] = dirname($album->thumb_path()); + } + + $base_url = url::base(true); + $sep = "?"; + if (strpos($base_url, "?") !== false) { + $sep = "&"; + } + $base_url .= $sep . "kohana_uri=/file_proxy"; + // Replace "/index.php/?kohana..." with "/index.php?koahan..." + // Doesn't apply to "/?kohana..." or "/foo/?kohana..." + // Can't check for "index.php" since the file might be renamed, and + // there might be more Apache aliases / rewrites at work. + $url_path = parse_url($base_url, PHP_URL_PATH); + // Does the URL path have a file component? + if (preg_match("#[^/]+\.php#i", $url_path)) { + $base_url = str_replace("/?", "?", $base_url); + } + + foreach ($dirs as $dir) { + if ($value === access::DENY) { + $fp = fopen("$dir/.htaccess", "w+"); + fwrite($fp, "\n"); + fwrite($fp, " RewriteEngine On\n"); + fwrite($fp, " RewriteRule (.*) $base_url/\$1 [L]\n"); + fwrite($fp, "\n"); + fwrite($fp, "\n"); + fwrite($fp, " Order Deny,Allow\n"); + fwrite($fp, " Deny from All\n"); + fwrite($fp, "\n"); + fclose($fp); + } else { + @unlink($dir . "/.htaccess"); + } + } + } + + static function private_key() { + return module::get_var("gallery", "private_key"); + } + + /** + * Verify that our htaccess based permission system actually works. Create a temporary + * directory containing an .htaccess file that uses mod_rewrite to redirect /verify to + * /success. Then request that url. If we retrieve it successfully, then our redirects are + * working and our permission system works. + */ + static function htaccess_works() { + $success_url = url::file("var/security_test/success"); + + @mkdir(VARPATH . "security_test"); + try { + if ($fp = @fopen(VARPATH . "security_test/.htaccess", "w+")) { + fwrite($fp, "Options +FollowSymLinks\n"); + fwrite($fp, "RewriteEngine On\n"); + fwrite($fp, "RewriteRule verify $success_url [L]\n"); + fclose($fp); + } + + if ($fp = @fopen(VARPATH . "security_test/success", "w+")) { + fwrite($fp, "success"); + fclose($fp); + } + + // Proxy our authorization headers so that if the entire Gallery is covered by Basic Auth + // this callback will still work. + $headers = array(); + if (function_exists("apache_request_headers")) { + $arh = apache_request_headers(); + if (!empty($arh["Authorization"])) { + $headers["Authorization"] = $arh["Authorization"]; + } + } + list ($status, $headers, $body) = + remote::do_request(url::abs_file("var/security_test/verify"), "GET", $headers); + $works = ($status == "HTTP/1.1 200 OK") && ($body == "success"); + } catch (Exception $e) { + @dir::unlink(VARPATH . "security_test"); + throw $e; + } + @dir::unlink(VARPATH . "security_test"); + + return $works; + } +} diff --git a/modules/gallery/helpers/ajax.php b/modules/gallery/helpers/ajax.php new file mode 100644 index 0000000..0c69fe7 --- /dev/null +++ b/modules/gallery/helpers/ajax.php @@ -0,0 +1,31 @@ +\n"; + print $content; + } +} diff --git a/modules/gallery/helpers/album.php b/modules/gallery/helpers/album.php new file mode 100644 index 0000000..23aed8a --- /dev/null +++ b/modules/gallery/helpers/album.php @@ -0,0 +1,126 @@ +id}", "", "post", array("id" => "g-add-album-form")); + $group = $form->group("add_album") + ->label(t("Add an album to %album_title", array("album_title" => $parent->title))); + $group->input("title")->label(t("Title")) + ->error_messages("required", t("You must provide a title")) + ->error_messages("length", t("Your title is too long")); + $group->textarea("description")->label(t("Description")); + $group->input("name")->label(t("Directory name")) + ->error_messages("no_slashes", t("The directory name can't contain the \"/\" character")) + ->error_messages("required", t("You must provide a directory name")) + ->error_messages("length", t("Your directory name is too long")) + ->error_messages("conflict", t("There is already a movie, photo or album with this name")); + $group->input("slug")->label(t("Internet Address")) + ->error_messages( + "reserved", t("This address is reserved and can't be used.")) + ->error_messages( + "not_url_safe", + t("The internet address should contain only letters, numbers, hyphens and underscores")) + ->error_messages("required", t("You must provide an internet address")) + ->error_messages("length", t("Your internet address is too long")); + $group->hidden("type")->value("album"); + + module::event("album_add_form", $parent, $form); + + $group->submit("")->value(t("Create")); + $form->script("") + ->url(url::abs_file("modules/gallery/js/albums_form_add.js")); + + return $form; + } + + static function get_edit_form($parent) { + $form = new Forge( + "albums/update/{$parent->id}", "", "post", array("id" => "g-edit-album-form")); + $form->hidden("from_id")->value($parent->id); + $group = $form->group("edit_item")->label(t("Edit Album")); + + $group->input("title")->label(t("Title"))->value($parent->title) + ->error_messages("required", t("You must provide a title")) + ->error_messages("length", t("Your title is too long")); + $group->textarea("description")->label(t("Description"))->value($parent->description); + if ($parent->id != 1) { + $group->input("name")->label(t("Directory Name"))->value($parent->name) + ->error_messages("conflict", t("There is already a movie, photo or album with this name")) + ->error_messages("no_slashes", t("The directory name can't contain a \"/\"")) + ->error_messages("no_trailing_period", t("The directory name can't end in \".\"")) + ->error_messages("required", t("You must provide a directory name")) + ->error_messages("length", t("Your directory name is too long")); + $group->input("slug")->label(t("Internet Address"))->value($parent->slug) + ->error_messages( + "conflict", t("There is already a movie, photo or album with this internet address")) + ->error_messages( + "reserved", t("This address is reserved and can't be used.")) + ->error_messages( + "not_url_safe", + t("The internet address should contain only letters, numbers, hyphens and underscores")) + ->error_messages("required", t("You must provide an internet address")) + ->error_messages("length", t("Your internet address is too long")); + } else { + $group->hidden("name")->value($parent->name); + $group->hidden("slug")->value($parent->slug); + } + + $sort_order = $group->group("sort_order", array("id" => "g-album-sort-order")) + ->label(t("Sort Order")); + + $sort_order->dropdown("column", array("id" => "g-album-sort-column")) + ->label(t("Sort by")) + ->options(album::get_sort_order_options()) + ->selected($parent->sort_column); + $sort_order->dropdown("direction", array("id" => "g-album-sort-direction")) + ->label(t("Order")) + ->options(array("ASC" => t("Ascending"), + "DESC" => t("Descending"))) + ->selected($parent->sort_order); + + module::event("item_edit_form", $parent, $form); + + $group = $form->group("buttons")->label(""); + $group->hidden("type")->value("album"); + $group->submit("")->value(t("Modify")); + return $form; + } + + /** + * Return a structured set of all the possible sort orders. + */ + static function get_sort_order_options() { + return array("weight" => t("Manual"), + "captured" => t("Date captured"), + "created" => t("Date uploaded"), + "title" => t("Title"), + "name" => t("File name"), + "updated" => t("Date modified"), + "view_count" => t("Number of views"), + "rand_key" => t("Random")); + } +} diff --git a/modules/gallery/helpers/auth.php b/modules/gallery/helpers/auth.php new file mode 100644 index 0000000..2eb3c25 --- /dev/null +++ b/modules/gallery/helpers/auth.php @@ -0,0 +1,134 @@ + "g-login-form")); + $form->set_attr("class", "g-narrow"); + $form->hidden("continue_url")->value(Session::instance()->get("continue_url")); + $group = $form->group("login")->label(t("Login")); + $group->input("name")->label(t("Username"))->id("g-username")->class(null) + ->callback("auth::validate_too_many_failed_logins") + ->error_messages( + "too_many_failed_logins", t("Too many failed login attempts. Try again later")); + $group->password("password")->label(t("Password"))->id("g-password")->class(null); + $group->inputs["name"]->error_messages("invalid_login", t("Invalid name or password")); + $group->submit("")->value(t("Login")); + return $form; + } + + static function login($user) { + identity::set_active_user($user); + if (identity::is_writable()) { + $user->login_count += 1; + $user->last_login = time(); + $user->save(); + } + log::info("user", t("User %name logged in", array("name" => $user->name))); + module::event("user_login", $user); + } + + static function logout() { + $user = identity::active_user(); + if (!$user->guest) { + try { + Session::instance()->destroy(); + } catch (Exception $e) { + Kohana_Log::add("error", $e); + } + module::event("user_logout", $user); + } + log::info("user", t("User %name logged out", array("name" => $user->name)), + t('%user_name', + array("url" => user_profile::url($user->id), + "user_name" => html::clean($user->name)))); + } + + /** + * After there have been 5 failed auth attempts, any failure leads to getting locked out for a + * minute. + */ + static function too_many_failures($name) { + $failed = ORM::factory("failed_auth") + ->where("name", "=", $name) + ->find(); + return ($failed->loaded() && + $failed->count > 5 && + (time() - $failed->time < 60)); + } + + static function validate_too_many_failed_logins($name_input) { + if (auth::too_many_failures($name_input->value)) { + $name_input->add_error("too_many_failed_logins", 1); + } + } + + static function validate_too_many_failed_auth_attempts($form_input) { + if (auth::too_many_failures(identity::active_user()->name)) { + $form_input->add_error("too_many_failed_auth_attempts", 1); + } + } + + /** + * Record a failed authentication for this user + */ + static function record_failed_attempt($name) { + $failed = ORM::factory("failed_auth") + ->where("name", "=", $name) + ->find(); + if (!$failed->loaded()) { + $failed->name = $name; + } + $failed->time = time(); + $failed->count++; + $failed->save(); + } + + /** + * Clear any failed logins for this user + */ + static function clear_failed_attempts($user) { + ORM::factory("failed_auth") + ->where("name", "=", $user->name) + ->delete_all(); + } + + /** + * Checks whether the current user (= admin) must + * actively re-authenticate before access is given + * to the admin area. + */ + static function must_reauth_for_admin_area() { + if (!identity::active_user()->admin) { + access::forbidden(); + } + + $session = Session::instance(); + $last_active_auth = $session->get("active_auth_timestamp", 0); + $last_admin_area_activity = $session->get("admin_area_activity_timestamp", 0); + $admin_area_timeout = module::get_var("gallery", "admin_area_timeout"); + + if (max($last_active_auth, $last_admin_area_activity) + $admin_area_timeout < time()) { + return true; + } + + $session->set("admin_area_activity_timestamp", time()); + return false; + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/batch.php b/modules/gallery/helpers/batch.php new file mode 100644 index 0000000..bf2425e --- /dev/null +++ b/modules/gallery/helpers/batch.php @@ -0,0 +1,40 @@ +set("batch_level", $session->get("batch_level", 0) + 1); + } + + static function stop() { + $session = Session::instance(); + $batch_level = $session->get("batch_level", 0) - 1; + if ($batch_level > 0) { + $session->set("batch_level", $batch_level); + } else { + $session->delete("batch_level"); + module::event("batch_complete"); + } + } + + static function in_progress() { + return Session::instance()->get("batch_level", 0) > 0; + } +} diff --git a/modules/gallery/helpers/block_manager.php b/modules/gallery/helpers/block_manager.php new file mode 100644 index 0000000..a227946 --- /dev/null +++ b/modules/gallery/helpers/block_manager.php @@ -0,0 +1,115 @@ + $block) { + if ($block[0] == $module_name) { + unset($blocks[$key]); + } + } + block_manager::set_active($location, $blocks); + } + + static function deactivate_blocks($module_name) { + $block_class = "{$module_name}_block"; + if (class_exists($block_class) && method_exists($block_class, "get_site_list")) { + $blocks = call_user_func(array($block_class, "get_site_list")); + foreach (array_keys($blocks) as $block_id) { + block_manager::remove_blocks_for_module("site_sidebar", $module_name); + } + } + + if (class_exists($block_class) && method_exists($block_class, "get_admin_list")) { + $blocks = call_user_func(array($block_class, "get_admin_list")); + foreach (array("dashboard_sidebar", "dashboard_center") as $location) { + block_manager::remove_blocks_for_module($location, $module_name); + } + } + } + + static function get_available_admin_blocks() { + return self::_get_blocks("get_admin_list"); + } + + static function get_available_site_blocks() { + return self::_get_blocks("get_site_list"); + } + + private static function _get_blocks($function) { + $blocks = array(); + + foreach (module::active() as $module) { + $class_name = "{$module->name}_block"; + if (class_exists($class_name) && method_exists($class_name, $function)) { + foreach (call_user_func(array($class_name, $function)) as $id => $title) { + $blocks["{$module->name}:$id"] = $title; + } + } + } + return $blocks; + } + + static function get_html($location, $theme=null) { + $active = block_manager::get_active($location); + $result = ""; + foreach ($active as $id => $desc) { + if (class_exists("$desc[0]_block") && method_exists("$desc[0]_block", "get")) { + $block = call_user_func(array("$desc[0]_block", "get"), $desc[1], $theme); + if (!empty($block)) { + $block->id = $id; + $result .= $block; + } + } + } + return $result; + } +} diff --git a/modules/gallery/helpers/data_rest.php b/modules/gallery/helpers/data_rest.php new file mode 100644 index 0000000..a0a225f --- /dev/null +++ b/modules/gallery/helpers/data_rest.php @@ -0,0 +1,115 @@ +url); + + $p = $request->params; + if (!isset($p->size) || !in_array($p->size, array("thumb", "resize", "full"))) { + throw new Rest_Exception("Bad Request", 400, array("errors" => array("size" => "invalid"))); + } + + // Note: this code is roughly duplicated in file_proxy, so if you modify this, please look to + // see if you should make the same change there as well. + + if ($p->size == "full") { + if ($item->is_album()) { + throw new Kohana_404_Exception(); + } + access::required("view_full", $item); + $file = $item->file_path(); + } else if ($p->size == "resize") { + access::required("view", $item); + $file = $item->resize_path(); + } else { + access::required("view", $item); + $file = $item->thumb_path(); + } + + if (!file_exists($file)) { + throw new Kohana_404_Exception(); + } + + header("Content-Length: " . filesize($file)); + + if (isset($p->m)) { + header("Pragma:"); + // Check that the content hasn't expired or it wasn't changed since cached + expires::check(2592000, $item->updated); + + expires::set(2592000, $item->updated); // 30 days + } + + // We don't need to save the session for this request + Session::instance()->abort_save(); + + // Dump out the image. If the item is a movie or album, then its thumbnail will be a JPG. + if (($item->is_movie() || $item->is_album()) && $p->size == "thumb") { + header("Content-Type: image/jpeg"); + } else { + header("Content-Type: $item->mime_type"); + } + + if (TEST_MODE) { + return $file; + } else { + Kohana::close_buffers(false); + + if (isset($p->encoding) && $p->encoding == "base64") { + print base64_encode(file_get_contents($file)); + } else { + readfile($file); + } + } + + // We must exit here to keep the regular REST framework reply code from adding more bytes on + // at the end or tinkering with headers. + exit; + } + + static function resolve($id) { + $item = ORM::factory("item", $id); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + return $item; + } + + static function url($item, $size) { + if ($size == "full") { + $file = $item->file_path(); + } else if ($size == "resize") { + $file = $item->resize_path(); + } else { + $file = $item->thumb_path(); + } + if (!file_exists($file)) { + throw new Kohana_404_Exception(); + } + + return url::abs_site("rest/data/{$item->id}?size=$size&m=" . filemtime($file)); + } +} + diff --git a/modules/gallery/helpers/dir.php b/modules/gallery/helpers/dir.php new file mode 100644 index 0000000..807f7bd --- /dev/null +++ b/modules/gallery/helpers/dir.php @@ -0,0 +1,40 @@ +isDot()) { + unset($resource); + continue; + } else if ($resource->isFile()) { + unlink($resource->getPathName()); + } else if ($resource->isDir()) { + dir::unlink($resource->getRealPath()); + } + unset($resource); + } + return @rmdir($path); + } + return false; + } + + +} diff --git a/modules/gallery/helpers/encoding.php b/modules/gallery/helpers/encoding.php new file mode 100644 index 0000000..073aef9 --- /dev/null +++ b/modules/gallery/helpers/encoding.php @@ -0,0 +1,35 @@ +admin) { + try { + $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller'); + $allowed = $class->getConstant("ALLOW_MAINTENANCE_MODE") === true; + } catch (ReflectionClass $e) { + $allowed = false; + } + if (!$allowed) { + if (Router::$controller == "admin") { + // At this point we're in the admin theme and it doesn't have a themed login page, so + // we can't just swap in the login controller and have it work. So redirect back to the + // root item where we'll run this code again with the site theme. + url::redirect(item::root()->abs_url()); + } else { + Session::instance()->set("continue_url", url::abs_site("admin/maintenance")); + Router::$controller = "login"; + Router::$controller_path = MODPATH . "gallery/controllers/login.php"; + Router::$method = "html"; + } + } + } + } + + /** + * If the gallery is only available to registered users and the user is not logged in, present + * the login page. + */ + static function private_gallery() { + if (identity::active_user()->guest && + !access::user_can(identity::guest(), "view", item::root()) && + php_sapi_name() != "cli") { + try { + $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller'); + $allowed = $class->getConstant("ALLOW_PRIVATE_GALLERY") === true; + } catch (ReflectionClass $e) { + $allowed = false; + } + if (!$allowed) { + if (Router::$controller == "admin") { + // At this point we're in the admin theme and it doesn't have a themed login page, so + // we can't just swap in the login controller and have it work. So redirect back to the + // root item where we'll run this code again with the site theme. + url::redirect(item::root()->abs_url()); + } else { + Session::instance()->set("continue_url", url::abs_current()); + Router::$controller = "login"; + Router::$controller_path = MODPATH . "gallery/controllers/login.php"; + Router::$method = "html"; + } + } + } + } + + /** + * This function is called when the Gallery is fully initialized. We relay it to modules as the + * "gallery_ready" event. Any module that wants to perform an action at the start of every + * request should implement the _event::gallery_ready() handler. + */ + static function ready() { + // Don't keep a session for robots; it's a waste of database space. + if (request::user_agent("robot")) { + Session::instance()->abort_save(); + } + + module::event("gallery_ready"); + } + + /** + * This function is called right before the Kohana framework shuts down. We relay it to modules + * as the "gallery_shutdown" event. Any module that wants to perform an action at the start of + * every request should implement the _event::gallery_shutdown() handler. + */ + static function shutdown() { + module::event("gallery_shutdown"); + } + + /** + * Return a unix timestamp in a user specified format including date and time. + * @param $timestamp unix timestamp + * @return string + */ + static function date_time($timestamp) { + return date(module::get_var("gallery", "date_time_format"), $timestamp); + } + + /** + * Return a unix timestamp in a user specified format that's just the date. + * @param $timestamp unix timestamp + * @return string + */ + static function date($timestamp) { + return date(module::get_var("gallery", "date_format"), $timestamp); + } + + /** + * Return a unix timestamp in a user specified format that's just the time. + * @param $timestamp unix timestamp + * @return string + */ + static function time($timestamp) { + return date(module::get_var("gallery", "time_format", "H:i:s"), $timestamp); + } + + /** + * Provide a wrapper function for Kohana::find_file that first strips the extension and + * then calls the Kohana::find_file and supplies the extension as the type. + * @param string directory to search in + * @param string filename to look for + * @param boolean file required (optional: default false) + * @return array if the extension is config, i18n or l10n + * @return string if the file is found (relative to the DOCROOT) + * @return false if the file is not found + */ + static function find_file($directory, $file, $required=false) { + $file_name = substr($file, 0, -strlen($ext = strrchr($file, '.'))); + $file_name = Kohana::find_file($directory, $file_name, $required, substr($ext, 1)); + if (!$file_name) { + if (file_exists(DOCROOT . "lib/$directory/$file")) { + return "lib/$directory/$file"; + } else if (file_exists(DOCROOT . "lib/$file")) { + return "lib/$file"; + } + } + + if (is_string($file_name)) { + // make relative to DOCROOT + $parts = explode("/", $file_name); + $count = count($parts); + foreach ($parts as $idx => $part) { + // If this part is "modules" or "themes" make sure that the part 2 after this + // is the target directory, and if it is then we're done. This check makes + // sure that if Gallery is installed in a directory called "modules" or "themes" + // We don't parse the directory structure incorrectly. + if (in_array($part, array("modules", "themes")) && + $idx + 2 < $count && + $parts[$idx + 2] == $directory) { + break; + } + unset($parts[$idx]); + } + $file_name = implode("/", $parts); + } + return $file_name; + } + + /** + * Set the PATH environment variable to the paths specified. + * @param array Array of paths. Each array entry can contain a colon separated list of paths. + */ + static function set_path_env($paths) { + $path_env = array(); + foreach ($paths as $path) { + if ($path) { + array_push($path_env, $path); + } + } + putenv("PATH=" . implode(":", $path_env)); + } + + /** + * Return a string describing this version of Gallery and the type of release. + */ + static function version_string() { + if (gallery::RELEASE_CHANNEL == "git") { + $build_number = gallery::build_number(); + return sprintf( + "%s (branch %s, %s)", gallery::VERSION, gallery::RELEASE_BRANCH, + $build_number ? " build $build_number" : "unknown build number"); + } else { + return sprintf("%s (%s)", gallery::VERSION, gallery::CODE_NAME); + } + } + + /** + * Return the contents of the .build_number file, which should be a single integer + * or return null if the .build_number file is missing. + */ + static function build_number() { + $build_file = DOCROOT . ".build_number"; + if (file_exists($build_file)) { + $result = parse_ini_file(DOCROOT . ".build_number"); + return $result["build_number"]; + } + return null; + } + + /** + * Return true if we should show the profiler at the bottom of the page. Note that this + * function is called at database setup time so it cannot rely on the database. + */ + static function show_profiler() { + return file_exists(VARPATH . "PROFILE"); + } + + /** + * Return true if we should allow Javascript and CSS combining for performance reasons. + * Typically we want this, but it's convenient for developers to be able to disable it. + */ + static function allow_css_and_js_combining() { + return !file_exists(VARPATH . "DONT_COMBINE"); + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/gallery_block.php b/modules/gallery/helpers/gallery_block.php new file mode 100644 index 0000000..5ac4d74 --- /dev/null +++ b/modules/gallery/helpers/gallery_block.php @@ -0,0 +1,145 @@ + t("Welcome to Gallery 3!"), + "photo_stream" => t("Photo stream"), + "log_entries" => t("Log entries"), + "stats" => t("Gallery stats"), + "platform_info" => t("Platform information"), + "project_news" => t("Gallery project news"), + "upgrade_checker" => t("Check for Gallery upgrades") + ); + } + + static function get_site_list() { + return array("language" => t("Language preference")); + } + + static function get($block_id) { + $block = new Block(); + switch ($block_id) { + case "welcome": + $block->css_id = "g-welcome"; + $block->title = t("Welcome to Gallery 3"); + $block->content = new View("admin_block_welcome.html"); + break; + + case "photo_stream": + $block->css_id = "g-photo-stream"; + $block->title = t("Photo stream"); + $block->content = new View("admin_block_photo_stream.html"); + $block->content->photos = ORM::factory("item") + ->where("type", "=", "photo")->order_by("created", "DESC")->find_all(10); + break; + + case "log_entries": + $block->css_id = "g-log-entries"; + $block->title = t("Log entries"); + $block->content = new View("admin_block_log_entries.html"); + $block->content->entries = ORM::factory("log") + ->order_by(array("timestamp" => "DESC", "id" => "DESC"))->find_all(5); + break; + + case "stats": + $block->css_id = "g-stats"; + $block->title = t("Gallery stats"); + $block->content = new View("admin_block_stats.html"); + $block->content->album_count = + ORM::factory("item")->where("type", "=", "album")->where("id", "<>", 1)->count_all(); + $block->content->photo_count = ORM::factory("item")->where("type", "=", "photo")->count_all(); + break; + + case "platform_info": + $block->css_id = "g-platform"; + $block->title = t("Platform information"); + $block->content = new View("admin_block_platform.html"); + break; + + case "project_news": + $block->css_id = "g-project-news"; + $block->title = t("Gallery project news"); + $block->content = new View("admin_block_news.html"); + $block->content->feed = feed::parse("http://galleryproject.org/node/feed", 3); + break; + + case "block_adder": + if ($form = gallery_block::get_add_block_form()) { + $block->css_id = "g-block-adder"; + $block->title = t("Dashboard content"); + $block->content = $form; + } else { + $block = ""; + } + break; + + case "language": + $locales = locales::installed(); + if (count($locales) > 1) { + foreach ($locales as $locale => $display_name) { + $locales[$locale] = SafeString::of_safe_html($display_name); + } + $block = new Block(); + $block->css_id = "g-user-language-block"; + $block->title = t("Language preference"); + $block->content = new View("user_languages_block.html"); + $block->content->installed_locales = array_merge(array("" => t("« none »")), $locales); + $block->content->selected = (string) locales::cookie_locale(); + } else { + $block = ""; + } + break; + + case "upgrade_checker": + $block = new Block(); + $block->css_id = "g-upgrade-available-block"; + $block->title = t("Check for Gallery upgrades"); + $block->content = new View("upgrade_checker_block.html"); + $block->content->version_info = upgrade_checker::version_info(); + $block->content->auto_check_enabled = upgrade_checker::auto_check_enabled(); + $block->content->new_version = upgrade_checker::get_upgrade_message(); + $block->content->build_number = gallery::build_number(); + } + return $block; + } + + static function get_add_block_form() { + $available_blocks = block_manager::get_available_admin_blocks(); + + $active = array(); + foreach (array_merge(block_manager::get_active("dashboard_sidebar"), + block_manager::get_active("dashboard_center")) as $b) { + unset($available_blocks[implode(":", $b)]); + } + + if (!$available_blocks) { + return; + } + + $form = new Forge("admin/dashboard/add_block", "", "post", + array("id" => "g-add-dashboard-block-form")); + $group = $form->group("add_block")->label(t("Add Block")); + $group->dropdown("id")->label(t("Available blocks"))->options($available_blocks); + $group->submit("center")->value(t("Add to center")); + $group->submit("sidebar")->value(t("Add to sidebar")); + return $form; + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/gallery_error.php b/modules/gallery/helpers/gallery_error.php new file mode 100644 index 0000000..76c8ca9 --- /dev/null +++ b/modules/gallery/helpers/gallery_error.php @@ -0,0 +1,30 @@ +update("tasks") + ->set("owner_id", $admin->id) + ->where("owner_id", "=", $user->id) + ->execute(); + db::build() + ->update("items") + ->set("owner_id", $admin->id) + ->where("owner_id", "=", $user->id) + ->execute(); + db::build() + ->update("logs") + ->set("user_id", $admin->id) + ->where("user_id", "=", $user->id) + ->execute(); + } + } + + static function identity_provider_changed($old_provider, $new_provider) { + $admin = identity::admin_user(); + db::build() + ->update("tasks") + ->set("owner_id", $admin->id) + ->execute(); + db::build() + ->update("items") + ->set("owner_id", $admin->id) + ->execute(); + db::build() + ->update("logs") + ->set("user_id", $admin->id) + ->execute(); + module::set_var("gallery", "email_from", $admin->email); + module::set_var("gallery", "email_reply_to", $admin->email); + } + + static function group_created($group) { + access::add_group($group); + } + + static function group_deleted($group) { + access::delete_group($group); + } + + static function item_created($item) { + access::add_item($item); + + // Build our thumbnail/resizes. + try { + graphics::generate($item); + } catch (Exception $e) { + log::error("graphics", t("Couldn't create a thumbnail or resize for %item_title", + array("item_title" => $item->title)), + html::anchor($item->abs_url(), t("details"))); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + + if ($item->is_photo() || $item->is_movie()) { + // If the parent has no cover item, make this it. + $parent = $item->parent(); + if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { + item::make_album_cover($item); + } + } + } + + static function item_deleted($item) { + access::delete_item($item); + + // Find any other albums that had the deleted item as the album cover and null it out. + // In some cases this may leave us with a missing album cover up in this item's parent + // hierarchy, but in most cases it'll work out fine. + foreach (ORM::factory("item") + ->where("album_cover_item_id", "=", $item->id) + ->find_all() as $parent) { + item::remove_album_cover($parent); + } + + $parent = $item->parent(); + if (!$parent->album_cover_item_id) { + // Assume that we deleted the album cover + if (batch::in_progress()) { + // Remember that this parent is missing an album cover, for later. + $batch_missing_album_cover = Session::instance()->get("batch_missing_album_cover", array()); + $batch_missing_album_cover[$parent->id] = 1; + Session::instance()->set("batch_missing_album_cover", $batch_missing_album_cover); + } else { + // Choose the first viewable child as the new cover. + if ($child = $parent->viewable()->children(1)->current()) { + item::make_album_cover($child); + } + } + } + } + + static function item_updated_data_file($item) { + graphics::generate($item); + + // Update any places where this is the album cover + foreach (ORM::factory("item") + ->where("album_cover_item_id", "=", $item->id) + ->find_all() as $target) { + $target->thumb_dirty = 1; + $target->save(); + graphics::generate($target); + } + } + + static function batch_complete() { + // Set the album covers for any items that where we probably deleted the album cover during + // this batch. The item may have been deleted, so don't count on it being around. Choose the + // first child as the new album cover. + // NOTE: if the first child doesn't have an album cover, then this won't work. + foreach (array_keys(Session::instance()->get("batch_missing_album_cover", array())) as $id) { + $item = ORM::factory("item", $id); + if ($item->loaded() && !$item->album_cover_item_id) { + if ($child = $item->children(1)->current()) { + item::make_album_cover($child); + } + } + } + Session::instance()->delete("batch_missing_album_cover"); + } + + static function item_moved($item, $old_parent) { + if ($item->is_album()) { + access::recalculate_album_permissions($item->parent()); + } else { + access::recalculate_photo_permissions($item); + } + + // If the new parent doesn't have an album cover, make this it. + if (!$item->parent()->album_cover_item_id) { + item::make_album_cover($item); + } + } + + static function user_login($user) { + // If this user is an admin, check to see if there are any post-install tasks that we need + // to run and take care of those now. + if ($user->admin && module::get_var("gallery", "choose_default_tookit", null)) { + graphics::choose_default_toolkit(); + module::clear_var("gallery", "choose_default_tookit"); + } + Session::instance()->set("active_auth_timestamp", time()); + auth::clear_failed_attempts($user); + } + + static function user_auth_failed($name) { + auth::record_failed_attempt($name); + } + + static function user_auth($user) { + auth::clear_failed_attempts($user); + Session::instance()->set("active_auth_timestamp", time()); + } + + static function item_index_data($item, $data) { + $data[] = $item->description; + $data[] = $item->name; + $data[] = $item->title; + } + + static function user_menu($menu, $theme) { + if ($theme->page_subtype != "login") { + $user = identity::active_user(); + if ($user->guest) { + $menu->append(Menu::factory("dialog") + ->id("user_menu_login") + ->css_id("g-login-link") + ->url(url::site("login/ajax")) + ->label(t("Login"))); + } else { + $csrf = access::csrf_token(); + $menu->append(Menu::factory("link") + ->id("user_menu_edit_profile") + ->css_id("g-user-profile-link") + ->view("login_current_user.html") + ->url(user_profile::url($user->id)) + ->label($user->display_name())); + + if (Router::$controller == "admin") { + $continue_url = url::abs_site(""); + } else if ($item = $theme->item()) { + if (access::user_can(identity::guest(), "view", $theme->item)) { + $continue_url = $item->abs_url(); + } else { + $continue_url = item::root()->abs_url(); + } + } else { + $continue_url = url::abs_current(); + } + + $menu->append(Menu::factory("link") + ->id("user_menu_logout") + ->css_id("g-logout-link") + ->url(url::site("logout?csrf=$csrf&continue_url=" . urlencode($continue_url))) + ->label(t("Logout"))); + } + } + } + + static function site_menu($menu, $theme, $item_css_selector) { + if ($theme->page_subtype != "login") { + $menu->append(Menu::factory("link") + ->id("home") + ->label(t("Home")) + ->url(item::root()->url())); + + + $item = $theme->item(); + + if (!empty($item)) { + $can_edit = $item && access::can("edit", $item); + $can_add = $item && access::can("add", $item); + + if ($can_add) { + $menu->append($add_menu = Menu::factory("submenu") + ->id("add_menu") + ->label(t("Add"))); + $is_album_writable = + is_writable($item->is_album() ? $item->file_path() : $item->parent()->file_path()); + if ($is_album_writable) { + $add_menu->append(Menu::factory("dialog") + ->id("add_photos_item") + ->label(t("Add photos")) + ->url(url::site("uploader/index/$item->id"))); + if ($item->is_album()) { + $add_menu->append(Menu::factory("dialog") + ->id("add_album_item") + ->label(t("Add an album")) + ->url(url::site("form/add/albums/$item->id?type=album"))); + } + } else { + message::warning(t("The album '%album_name' is not writable.", + array("album_name" => $item->title))); + } + } + + switch ($item->type) { + case "album": + $option_text = t("Album options"); + $edit_text = t("Edit album"); + $delete_text = t("Delete album"); + break; + case "movie": + $option_text = t("Movie options"); + $edit_text = t("Edit movie"); + $delete_text = t("Delete movie"); + break; + default: + $option_text = t("Photo options"); + $edit_text = t("Edit photo"); + $delete_text = t("Delete photo"); + } + + $menu->append($options_menu = Menu::factory("submenu") + ->id("options_menu") + ->label($option_text)); + if ($item && ($can_edit || $can_add)) { + if ($can_edit) { + $options_menu->append(Menu::factory("dialog") + ->id("edit_item") + ->label($edit_text) + ->url(url::site("form/edit/{$item->type}s/$item->id?from_id={$item->id}"))); + } + + if ($item->is_album()) { + if ($can_edit) { + $options_menu->append(Menu::factory("dialog") + ->id("edit_permissions") + ->label(t("Edit permissions")) + ->url(url::site("permissions/browse/$item->id"))); + } + } + } + + $csrf = access::csrf_token(); + $page_type = $theme->page_type(); + if ($can_edit && $item->is_photo() && graphics::can("rotate")) { + $options_menu + ->append( + Menu::factory("ajax_link") + ->id("rotate_ccw") + ->label(t("Rotate 90° counter clockwise")) + ->css_class("ui-icon-rotate-ccw") + ->ajax_handler("function(data) { " . + "\$.gallery_replace_image(data, \$('$item_css_selector')) }") + ->url(url::site("quick/rotate/$item->id/ccw?csrf=$csrf&from_id={$item->id}&page_type=$page_type"))) + ->append( + Menu::factory("ajax_link") + ->id("rotate_cw") + ->label(t("Rotate 90° clockwise")) + ->css_class("ui-icon-rotate-cw") + ->ajax_handler("function(data) { " . + "\$.gallery_replace_image(data, \$('$item_css_selector')) }") + ->url(url::site("quick/rotate/$item->id/cw?csrf=$csrf&from_id={$item->id}&page_type=$page_type"))); + } + + if ($item->id != item::root()->id) { + $parent = $item->parent(); + if (access::can("edit", $parent)) { + // We can't make this item the highlight if it's an album with no album cover, or if it's + // already the album cover. + if (($item->type == "album" && empty($item->album_cover_item_id)) || + ($item->type == "album" && $parent->album_cover_item_id == $item->album_cover_item_id) || + $parent->album_cover_item_id == $item->id) { + $disabledState = "ui-state-disabled"; + } else { + $disabledState = ""; + } + + if ($item->parent()->id != 1) { + $options_menu + ->append( + Menu::factory("ajax_link") + ->id("make_album_cover") + ->label(t("Choose as the album cover")) + ->css_class("ui-icon-star $disabledState") + ->ajax_handler("function(data) { window.location.reload() }") + ->url(url::site("quick/make_album_cover/$item->id?csrf=$csrf"))); + } + $options_menu + ->append( + Menu::factory("dialog") + ->id("delete") + ->label($delete_text) + ->css_class("ui-icon-trash") + ->css_class("g-quick-delete") + ->url(url::site("quick/form_delete/$item->id?csrf=$csrf&from_id={$item->id}&page_type=$page_type"))); + } + } + } + + if (identity::active_user()->admin) { + $menu->append($admin_menu = Menu::factory("submenu") + ->id("admin_menu") + ->label(t("Admin"))); + module::event("admin_menu", $admin_menu, $theme); + + $settings_menu = $admin_menu->get("settings_menu"); + uasort($settings_menu->elements, array("Menu", "title_comparator")); + } + } + } + + static function admin_menu($menu, $theme) { + $menu + ->append(Menu::factory("link") + ->id("dashboard") + ->label(t("Dashboard")) + ->url(url::site("admin"))) + ->append(Menu::factory("submenu") + ->id("settings_menu") + ->label(t("Settings")) + ->append(Menu::factory("link") + ->id("graphics_toolkits") + ->label(t("Graphics")) + ->url(url::site("admin/graphics"))) + ->append(Menu::factory("link") + ->id("movies_settings") + ->label(t("Movies")) + ->url(url::site("admin/movies"))) + ->append(Menu::factory("link") + ->id("languages") + ->label(t("Languages")) + ->url(url::site("admin/languages"))) + ->append(Menu::factory("link") + ->id("advanced") + ->label(t("Advanced")) + ->url(url::site("admin/advanced_settings")))) + ->append(Menu::factory("link") + ->id("modules") + ->label(t("Modules")) + ->url(url::site("admin/modules"))) + ->append(Menu::factory("submenu") + ->id("content_menu") + ->label(t("Content"))) + ->append(Menu::factory("submenu") + ->id("appearance_menu") + ->label(t("Appearance")) + ->append(Menu::factory("link") + ->id("themes") + ->label(t("Theme choice")) + ->url(url::site("admin/themes"))) + ->append(Menu::factory("link") + ->id("theme_options") + ->label(t("Theme options")) + ->url(url::site("admin/theme_options"))) + ->append(Menu::factory("link") + ->id("sidebar") + ->label(t("Manage sidebar")) + ->url(url::site("admin/sidebar")))) + ->append(Menu::factory("submenu") + ->id("statistics_menu") + ->label(t("Statistics"))) + ->append(Menu::factory("link") + ->id("maintenance") + ->label(t("Maintenance")) + ->url(url::site("admin/maintenance"))); + return $menu; + } + + static function context_menu($menu, $theme, $item, $thumb_css_selector) { + $menu->append($options_menu = Menu::factory("submenu") + ->id("options_menu") + ->label(t("Options")) + ->css_class("ui-icon-carat-1-n")); + + $page_type = $theme->page_type(); + if (access::can("edit", $item)) { + switch ($item->type) { + case "movie": + $edit_title = t("Edit this movie"); + $delete_title = t("Delete this movie"); + break; + + case "album": + $edit_title = t("Edit this album"); + $delete_title = t("Delete this album"); + break; + + default: + $edit_title = t("Edit this photo"); + $delete_title = t("Delete this photo"); + break; + } + $cover_title = t("Choose as the album cover"); + + $csrf = access::csrf_token(); + + $theme_item = $theme->item(); + $options_menu->append(Menu::factory("dialog") + ->id("edit") + ->label($edit_title) + ->css_class("ui-icon-pencil") + ->url(url::site("quick/form_edit/$item->id?from_id={$theme_item->id}"))); + + if ($item->is_photo() && graphics::can("rotate")) { + $options_menu + ->append( + Menu::factory("ajax_link") + ->id("rotate_ccw") + ->label(t("Rotate 90° counter clockwise")) + ->css_class("ui-icon-rotate-ccw") + ->ajax_handler("function(data) { " . + "\$.gallery_replace_image(data, \$('$thumb_css_selector')) }") + ->url(url::site("quick/rotate/$item->id/ccw?csrf=$csrf&from_id={$theme_item->id}&page_type=$page_type"))) + ->append( + Menu::factory("ajax_link") + ->id("rotate_cw") + ->label(t("Rotate 90° clockwise")) + ->css_class("ui-icon-rotate-cw") + ->ajax_handler("function(data) { " . + "\$.gallery_replace_image(data, \$('$thumb_css_selector')) }") + ->url(url::site("quick/rotate/$item->id/cw?csrf=$csrf&from_id={$theme_item->id}&page_type=$page_type"))); + } + + $parent = $item->parent(); + if (access::can("edit", $parent)) { + // We can't make this item the highlight if it's an album with no album cover, or if it's + // already the album cover. + if (($item->type == "album" && empty($item->album_cover_item_id)) || + ($item->type == "album" && $parent->album_cover_item_id == $item->album_cover_item_id) || + $parent->album_cover_item_id == $item->id) { + $disabledState = "ui-state-disabled"; + } else { + $disabledState = ""; + } + if ($item->parent()->id != 1) { + $options_menu + ->append(Menu::factory("ajax_link") + ->id("make_album_cover") + ->label($cover_title) + ->css_class("ui-icon-star $disabledState") + ->ajax_handler("function(data) { window.location.reload() }") + ->url(url::site("quick/make_album_cover/$item->id?csrf=$csrf"))); + } + $options_menu + ->append(Menu::factory("dialog") + ->id("delete") + ->label($delete_title) + ->css_class("ui-icon-trash") + ->url(url::site("quick/form_delete/$item->id?csrf=$csrf&" . + "from_id={$theme_item->id}&page_type=$page_type"))); + } + + if ($item->is_album()) { + $options_menu + ->append(Menu::factory("dialog") + ->id("add_item") + ->label(t("Add a photo")) + ->css_class("ui-icon-plus") + ->url(url::site("uploader/index/$item->id"))) + ->append(Menu::factory("dialog") + ->id("add_album") + ->label(t("Add an album")) + ->css_class("ui-icon-note") + ->url(url::site("form/add/albums/$item->id?type=album"))) + ->append(Menu::factory("dialog") + ->id("edit_permissions") + ->label(t("Edit permissions")) + ->css_class("ui-icon-key") + ->url(url::site("permissions/browse/$item->id"))); + } + } + } + + static function show_user_profile($data) { + $v = new View("user_profile_info.html"); + + $fields = array("name" => t("Name"), "locale" => t("Language Preference"), + "email" => t("Email"), "full_name" => t("Full name"), "url" => t("Web site")); + if (!$data->user->guest) { + $fields = array("name" => t("Name"), "full_name" => t("Full name"), "url" => t("Web site")); + } + $v->user_profile_data = array(); + foreach ($fields as $field => $label) { + if (!empty($data->user->$field)) { + $value = $data->user->$field; + if ($field == "locale") { + $value = locales::display_name($value); + } else if ($field == "url") { + $value = html::mark_clean(html::anchor(html::clean($data->user->$field))); + } + $v->user_profile_data[(string) $label] = $value; + } + } + $data->content[] = (object) array("title" => t("User information"), "view" => $v); + + } + + static function user_updated($original_user, $updated_user) { + // If the default from/reply-to email address is set to the install time placeholder value + // of unknown@unknown.com then adopt the value from the first admin to set their own email + // address so that we at least have a valid address for the Gallery. + if ($updated_user->admin) { + $email = module::get_var("gallery", "email_from", ""); + if ($email == "unknown@unknown.com") { + module::set_var("gallery", "email_from", $updated_user->email); + module::set_var("gallery", "email_reply_to", $updated_user->email); + } + } + } +} diff --git a/modules/gallery/helpers/gallery_graphics.php b/modules/gallery/helpers/gallery_graphics.php new file mode 100644 index 0000000..eb76353 --- /dev/null +++ b/modules/gallery/helpers/gallery_graphics.php @@ -0,0 +1,183 @@ + 0) { + // A graphics_rotate event made an image - move it to output_file and use it. + @rename($temp_file, $output_file); + } else { + // No events made an image - proceed with standard process. + if (@filesize($input_file) == 0) { + throw new Exception("@todo EMPTY_INPUT_FILE"); + } + + if (!isset($options["degrees"])) { + $options["degrees"] = 0; + } + + // Rotate the image. This also implicitly converts its format if needed. + Image::factory($input_file) + ->quality(module::get_var("gallery", "image_quality")) + ->rotate($options["degrees"]) + ->save($output_file); + } + + module::event("graphics_rotate_completed", $input_file, $output_file, $options, $item); + } + + /** + * Resize an image. Valid options are width, height and master. Master is one of the Image + * master dimension constants. + * + * @param string $input_file + * @param string $output_file + * @param array $options + * @param Item_Model $item (optional) + */ + static function resize($input_file, $output_file, $options, $item=null) { + graphics::init_toolkit(); + + $temp_file = system::temp_filename("resize_", pathinfo($output_file, PATHINFO_EXTENSION)); + module::event("graphics_resize", $input_file, $temp_file, $options, $item); + + if (@filesize($temp_file) > 0) { + // A graphics_resize event made an image - move it to output_file and use it. + @rename($temp_file, $output_file); + } else { + // No events made an image - proceed with standard process. + if (@filesize($input_file) == 0) { + throw new Exception("@todo EMPTY_INPUT_FILE"); + } + + list ($input_width, $input_height, $input_mime, $input_extension) = + photo::get_file_metadata($input_file); + if ($input_width && $input_height && + (empty($options["width"]) || empty($options["height"]) || empty($options["master"]) || + (max($input_width, $input_height) <= min($options["width"], $options["height"])))) { + // Photo dimensions well-defined, but options not well-defined or would upscale the image. + // Do not resize. Check mimes to see if we can copy the file or if we need to convert it. + // (checking mimes avoids needlessly converting jpg to jpeg, etc.) + $output_mime = legal_file::get_photo_types_by_extension(pathinfo($output_file, PATHINFO_EXTENSION)); + if ($input_mime && $output_mime && ($input_mime == $output_mime)) { + // Mimes well-defined and identical - copy input to output + copy($input_file, $output_file); + } else { + // Mimes not well-defined or not the same - convert input to output + $image = Image::factory($input_file) + ->quality(module::get_var("gallery", "image_quality")) + ->save($output_file); + } + } else { + // Resize the image. This also implicitly converts its format if needed. + $image = Image::factory($input_file) + ->resize($options["width"], $options["height"], $options["master"]) + ->quality(module::get_var("gallery", "image_quality")); + if (graphics::can("sharpen")) { + $image->sharpen(module::get_var("gallery", "image_sharpen")); + } + $image->save($output_file); + } + } + + module::event("graphics_resize_completed", $input_file, $output_file, $options, $item); + } + + /** + * Overlay an image on top of the input file. + * + * Valid options are: file, position, transparency, padding + * + * Valid positions: northwest, north, northeast, + * west, center, east, + * southwest, south, southeast + * + * padding is in pixels + * + * @param string $input_file + * @param string $output_file + * @param array $options + * @param Item_Model $item (optional) + */ + static function composite($input_file, $output_file, $options, $item=null) { + try { + graphics::init_toolkit(); + + $temp_file = system::temp_filename("composite_", pathinfo($output_file, PATHINFO_EXTENSION)); + module::event("graphics_composite", $input_file, $temp_file, $options, $item); + + if (@filesize($temp_file) > 0) { + // A graphics_composite event made an image - move it to output_file and use it. + @rename($temp_file, $output_file); + } else { + // No events made an image - proceed with standard process. + + list ($width, $height) = photo::get_file_metadata($input_file); + list ($w_width, $w_height) = photo::get_file_metadata($options["file"]); + + $pad = isset($options["padding"]) ? $options["padding"] : 10; + $top = $pad; + $left = $pad; + $y_center = max($height / 2 - $w_height / 2, $pad); + $x_center = max($width / 2 - $w_width / 2, $pad); + $bottom = max($height - $w_height - $pad, $pad); + $right = max($width - $w_width - $pad, $pad); + + switch ($options["position"]) { + case "northwest": $x = $left; $y = $top; break; + case "north": $x = $x_center; $y = $top; break; + case "northeast": $x = $right; $y = $top; break; + case "west": $x = $left; $y = $y_center; break; + case "center": $x = $x_center; $y = $y_center; break; + case "east": $x = $right; $y = $y_center; break; + case "southwest": $x = $left; $y = $bottom; break; + case "south": $x = $x_center; $y = $bottom; break; + case "southeast": $x = $right; $y = $bottom; break; + } + + Image::factory($input_file) + ->composite($options["file"], $x, $y, $options["transparency"]) + ->quality(module::get_var("gallery", "image_quality")) + ->save($output_file); + } + + module::event("graphics_composite_completed", $input_file, $output_file, $options, $item); + } catch (ErrorException $e) { + // Unlike rotate and resize, composite catches its exceptions here. This is because + // composite is typically called for watermarks. If during thumb/resize generation + // the watermark fails, we'd still like the image resized, just without its watermark. + // If the exception isn't caught here, graphics::generate will replace it with a + // placeholder. + Kohana_Log::add("error", $e->getMessage()); + } + } +} diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php new file mode 100644 index 0000000..d49be83 --- /dev/null +++ b/modules/gallery/helpers/gallery_installer.php @@ -0,0 +1,844 @@ +query("CREATE TABLE {access_caches} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + PRIMARY KEY (`id`), + KEY (`item_id`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {access_intents} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + + // Using a simple index instead of a unique key for the + // key column to avoid handling of concurrency issues + // on insert. Thus allowing concurrent inserts on the + // same cache key, as does Memcache / xcache. + $db->query("CREATE TABLE {caches} ( + `id` int(9) NOT NULL auto_increment, + `key` varchar(255) NOT NULL, + `tags` varchar(255), + `expiration` int(9) NOT NULL, + `cache` longblob, + PRIMARY KEY (`id`), + UNIQUE KEY (`key`), + KEY (`tags`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {failed_auths} ( + `id` int(9) NOT NULL auto_increment, + `count` int(9) NOT NULL, + `name` varchar(255) NOT NULL, + `time` int(9) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {graphics_rules} ( + `id` int(9) NOT NULL auto_increment, + `active` BOOLEAN default 0, + `args` varchar(255) default NULL, + `module_name` varchar(64) NOT NULL, + `operation` varchar(64) NOT NULL, + `priority` int(9) NOT NULL, + `target` varchar(32) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {incoming_translations} ( + `id` int(9) NOT NULL auto_increment, + `key` char(32) NOT NULL, + `locale` char(10) NOT NULL, + `message` text NOT NULL, + `revision` int(9) DEFAULT NULL, + `translation` text, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`, `locale`), + KEY `locale_key` (`locale`, `key`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {items} ( + `id` int(9) NOT NULL auto_increment, + `album_cover_item_id` int(9) default NULL, + `captured` int(9) default NULL, + `created` int(9) default NULL, + `description` text default NULL, + `height` int(9) default NULL, + `left_ptr` int(9) NOT NULL, + `level` int(9) NOT NULL, + `mime_type` varchar(64) default NULL, + `name` varchar(255) default NULL, + `owner_id` int(9) default NULL, + `parent_id` int(9) NOT NULL, + `rand_key` decimal(11,10) default NULL, + `relative_path_cache` varchar(255) default NULL, + `relative_url_cache` varchar(255) default NULL, + `resize_dirty` boolean default 1, + `resize_height` int(9) default NULL, + `resize_width` int(9) default NULL, + `right_ptr` int(9) NOT NULL, + `slug` varchar(255) default NULL, + `sort_column` varchar(64) default NULL, + `sort_order` char(4) default 'ASC', + `thumb_dirty` boolean default 1, + `thumb_height` int(9) default NULL, + `thumb_width` int(9) default NULL, + `title` varchar(255) default NULL, + `type` varchar(32) NOT NULL, + `updated` int(9) default NULL, + `view_count` int(9) default 0, + `weight` int(9) NOT NULL default 0, + `width` int(9) default NULL, + PRIMARY KEY (`id`), + KEY `parent_id` (`parent_id`), + KEY `type` (`type`), + KEY `random` (`rand_key`), + KEY `weight` (`weight` DESC), + KEY `left_ptr` (`left_ptr`), + KEY `relative_path_cache` (`relative_path_cache`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {logs} ( + `id` int(9) NOT NULL auto_increment, + `category` varchar(64) default NULL, + `html` varchar(255) default NULL, + `message` text default NULL, + `referer` varchar(255) default NULL, + `severity` int(9) default 0, + `timestamp` int(9) default 0, + `url` varchar(255) default NULL, + `user_id` int(9) default 0, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {messages} ( + `id` int(9) NOT NULL auto_increment, + `key` varchar(255) default NULL, + `severity` varchar(32) default NULL, + `value` text default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {modules} ( + `id` int(9) NOT NULL auto_increment, + `active` BOOLEAN default 0, + `name` varchar(64) default NULL, + `version` int(9) default NULL, + `weight` int(9) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`), + KEY (`weight`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {outgoing_translations} ( + `id` int(9) NOT NULL auto_increment, + `base_revision` int(9) DEFAULT NULL, + `key` char(32) NOT NULL, + `locale` char(10) NOT NULL, + `message` text NOT NULL, + `translation` text, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`, `locale`), + KEY `locale_key` (`locale`, `key`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {permissions} ( + `id` int(9) NOT NULL auto_increment, + `display_name` varchar(64) default NULL, + `name` varchar(64) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {sessions} ( + `session_id` varchar(127) NOT NULL, + `data` text NOT NULL, + `last_activity` int(10) UNSIGNED NOT NULL, + PRIMARY KEY (`session_id`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {tasks} ( + `id` int(9) NOT NULL auto_increment, + `callback` varchar(128) default NULL, + `context` text NOT NULL, + `done` boolean default 0, + `name` varchar(128) default NULL, + `owner_id` int(9) default NULL, + `percent_complete` int(9) default 0, + `state` varchar(32) default NULL, + `status` varchar(255) default NULL, + `updated` int(9) default NULL, + PRIMARY KEY (`id`), + KEY (`owner_id`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {themes} ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(64) default NULL, + `version` int(9) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {vars} ( + `id` int(9) NOT NULL auto_increment, + `module_name` varchar(64) NOT NULL, + `name` varchar(64) NOT NULL, + `value` text, + PRIMARY KEY (`id`), + UNIQUE KEY(`module_name`, `name`)) + DEFAULT CHARSET=utf8;"); + + foreach (array("albums", "logs", "modules", "resizes", "thumbs", "tmp", "uploads") as $dir) { + @mkdir(VARPATH . $dir); + if (in_array($dir, array("logs", "tmp", "uploads"))) { + self::_protect_directory(VARPATH . $dir); + } + } + + access::register_permission("view", "View"); + access::register_permission("view_full", "View full size"); + access::register_permission("edit", "Edit"); + access::register_permission("add", "Add"); + + // Mark for translation (must be the same strings as used above) + t("View full size"); + t("View"); + t("Edit"); + t("Add"); + + // Hardcode the first item to sidestep ORM validation rules + $now = time(); + db::build()->insert( + "items", + array("created" => $now, + "description" => "", + "left_ptr" => 1, + "level" => 1, + "parent_id" => 0, + "resize_dirty" => 1, + "right_ptr" => 2, + "sort_column" => "weight", + "sort_order" => "ASC", + "thumb_dirty" => 1, + "title" => "Gallery", + "type" => "album", + "updated" => $now, + "weight" => 1)) + ->execute(); + $root = ORM::factory("item", 1); + access::add_item($root); + + module::set_var("gallery", "active_site_theme", "wind"); + module::set_var("gallery", "active_admin_theme", "admin_wind"); + module::set_var("gallery", "page_size", 9); + module::set_var("gallery", "thumb_size", 200); + module::set_var("gallery", "resize_size", 640); + module::set_var("gallery", "default_locale", "en_US"); + module::set_var("gallery", "image_quality", 75); + module::set_var("gallery", "image_sharpen", 15); + module::set_var("gallery", "upgrade_checker_auto_enabled", true); + + // Add rules for generating our thumbnails and resizes + graphics::add_rule( + "gallery", "thumb", "gallery_graphics::resize", + array("width" => 200, "height" => 200, "master" => Image::AUTO), + 100); + graphics::add_rule( + "gallery", "resize", "gallery_graphics::resize", + array("width" => 640, "height" => 640, "master" => Image::AUTO), + 100); + + // Instantiate default themes (site and admin) + foreach (array("wind", "admin_wind") as $theme_name) { + $theme_info = new ArrayObject(parse_ini_file(THEMEPATH . $theme_name . "/theme.info"), + ArrayObject::ARRAY_AS_PROPS); + $theme = ORM::factory("theme"); + $theme->name = $theme_name; + $theme->version = $theme_info->version; + $theme->save(); + } + + block_manager::add("dashboard_sidebar", "gallery", "block_adder"); + block_manager::add("dashboard_sidebar", "gallery", "stats"); + block_manager::add("dashboard_sidebar", "gallery", "platform_info"); + block_manager::add("dashboard_sidebar", "gallery", "project_news"); + block_manager::add("dashboard_center", "gallery", "welcome"); + block_manager::add("dashboard_center", "gallery", "upgrade_checker"); + block_manager::add("dashboard_center", "gallery", "photo_stream"); + block_manager::add("dashboard_center", "gallery", "log_entries"); + + module::set_var("gallery", "choose_default_tookit", 1); + module::set_var("gallery", "date_format", "Y-M-d"); + module::set_var("gallery", "date_time_format", "Y-M-d H:i:s"); + module::set_var("gallery", "time_format", "H:i:s"); + module::set_var("gallery", "show_credits", 1); + // Mark string for translation + $powered_by_string = t("Powered by %gallery_version", + array("locale" => "root")); + module::set_var("gallery", "credits", (string) $powered_by_string); + module::set_var("gallery", "simultaneous_upload_limit", 5); + module::set_var("gallery", "admin_area_timeout", 90 * 60); + module::set_var("gallery", "maintenance_mode", 0); + module::set_var("gallery", "visible_title_length", 15); + module::set_var("gallery", "favicon_url", "lib/images/favicon.ico"); + module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png"); + module::set_var("gallery", "email_from", ""); + module::set_var("gallery", "email_reply_to", ""); + module::set_var("gallery", "email_line_length", 70); + module::set_var("gallery", "email_header_separator", serialize("\n")); + module::set_var("gallery", "show_user_profiles_to", "registered_users"); + module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin"); + module::set_var("gallery", "timezone", null); + module::set_var("gallery", "lock_timeout", 1); + module::set_var("gallery", "movie_extract_frame_time", 3); + module::set_var("gallery", "movie_allow_uploads", "autodetect"); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + module::set_var("gallery", "date_format", "Y-M-d"); + module::set_var("gallery", "date_time_format", "Y-M-d H:i:s"); + module::set_var("gallery", "time_format", "H:i:s"); + module::set_version("gallery", $version = 2); + } + + if ($version == 2) { + module::set_var("gallery", "show_credits", 1); + module::set_version("gallery", $version = 3); + } + + if ($version == 3) { + $db->query("CREATE TABLE {caches} ( + `id` varchar(255) NOT NULL, + `tags` varchar(255), + `expiration` int(9) NOT NULL, + `cache` text, + PRIMARY KEY (`id`), + KEY (`tags`)) + DEFAULT CHARSET=utf8;"); + module::set_version("gallery", $version = 4); + } + + if ($version == 4) { + Cache::instance()->delete_all(); + $db->query("ALTER TABLE {caches} MODIFY COLUMN `cache` LONGBLOB"); + module::set_version("gallery", $version = 5); + } + + if ($version == 5) { + Cache::instance()->delete_all(); + $db->query("ALTER TABLE {caches} DROP COLUMN `id`"); + $db->query("ALTER TABLE {caches} ADD COLUMN `key` varchar(255) NOT NULL"); + $db->query("ALTER TABLE {caches} ADD COLUMN `id` int(9) NOT NULL auto_increment PRIMARY KEY"); + module::set_version("gallery", $version = 6); + } + + if ($version == 6) { + module::clear_var("gallery", "version"); + module::set_version("gallery", $version = 7); + } + + if ($version == 7) { + $groups = identity::groups(); + $permissions = ORM::factory("permission")->find_all(); + foreach($groups as $group) { + foreach($permissions as $permission) { + // Update access intents + $db->query("ALTER TABLE {access_intents} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT NULL"); + // Update access cache + if ($permission->name === "view") { + $db->query("ALTER TABLE {items} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT FALSE"); + } else { + $db->query("ALTER TABLE {access_caches} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) NOT NULL DEFAULT FALSE"); + } + } + } + module::set_version("gallery", $version = 8); + } + + if ($version == 8) { + $db->query("ALTER TABLE {items} CHANGE COLUMN `left` `left_ptr` INT(9) NOT NULL;"); + $db->query("ALTER TABLE {items} CHANGE COLUMN `right` `right_ptr` INT(9) NOT NULL;"); + module::set_version("gallery", $version = 9); + } + + if ($version == 9) { + $db->query("ALTER TABLE {items} ADD KEY `weight` (`weight` DESC);"); + + module::set_version("gallery", $version = 10); + } + + if ($version == 10) { + module::set_var("gallery", "image_sharpen", 15); + + module::set_version("gallery", $version = 11); + } + + if ($version == 11) { + $db->query("ALTER TABLE {items} ADD COLUMN `relative_url_cache` varchar(255) DEFAULT NULL"); + $db->query("ALTER TABLE {items} ADD COLUMN `slug` varchar(255) DEFAULT NULL"); + + // This is imperfect since some of the slugs may contain invalid characters, but it'll do + // for now because we don't want a lengthy operation here. + $db->query("UPDATE {items} SET `slug` = `name`"); + + // Flush all path caches because we're going to start urlencoding them. + $db->query("UPDATE {items} SET `relative_url_cache` = NULL, `relative_path_cache` = NULL"); + module::set_version("gallery", $version = 12); + } + + if ($version == 12) { + if (module::get_var("gallery", "active_site_theme") == "default") { + module::set_var("gallery", "active_site_theme", "wind"); + } + if (module::get_var("gallery", "active_admin_theme") == "admin_default") { + module::set_var("gallery", "active_admin_theme", "admin_wind"); + } + module::set_version("gallery", $version = 13); + } + + if ($version == 13) { + // Add rules for generating our thumbnails and resizes + Database::instance()->query( + "UPDATE {graphics_rules} SET `operation` = CONCAT('gallery_graphics::', `operation`);"); + module::set_version("gallery", $version = 14); + } + + if ($version == 14) { + $sidebar_blocks = block_manager::get_active("site_sidebar"); + if (empty($sidebar_blocks)) { + $available_blocks = block_manager::get_available_site_blocks(); + foreach (array_keys(block_manager::get_available_site_blocks()) as $id) { + $sidebar_blocks[] = explode(":", $id); + } + block_manager::set_active("site_sidebar", $sidebar_blocks); + } + module::set_version("gallery", $version = 15); + } + + if ($version == 15) { + module::set_var("gallery", "identity_provider", "user"); + module::set_version("gallery", $version = 16); + } + + // Convert block keys to an md5 hash of the module and block name + if ($version == 16) { + foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) { + $blocks = block_manager::get_active($location); + $new_blocks = array(); + foreach ($blocks as $block) { + $new_blocks[md5("{$block[0]}:{$block[1]}")] = $block; + } + block_manager::set_active($location, $new_blocks); + } + module::set_version("gallery", $version = 17); + } + + // We didn't like md5 hashes so convert block keys back to random keys to allow duplicates. + if ($version == 17) { + foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) { + $blocks = block_manager::get_active($location); + $new_blocks = array(); + foreach ($blocks as $block) { + $new_blocks[random::int()] = $block; + } + block_manager::set_active($location, $new_blocks); + } + module::set_version("gallery", $version = 18); + } + + // Rename blocks_site.sidebar to blocks_site_sidebar + if ($version == 18) { + $blocks = block_manager::get_active("site.sidebar"); + block_manager::set_active("site_sidebar", $blocks); + module::clear_var("gallery", "blocks_site.sidebar"); + module::set_version("gallery", $version = 19); + } + + // Set a default for the number of simultaneous uploads + // Version 20 was reverted in 57adefc5baa7a2b0dfcd3e736e80c2fa86d3bfa2, so skip it. + if ($version == 19 || $version == 20) { + module::set_var("gallery", "simultaneous_upload_limit", 5); + module::set_version("gallery", $version = 21); + } + + // Update the graphics rules table so that the maximum height for resizes is 640 not 480. + // Fixes ticket #671 + if ($version == 21) { + $resize_rule = ORM::factory("graphics_rule") + ->where("id", "=", "2") + ->find(); + // make sure it hasn't been changed already + $args = unserialize($resize_rule->args); + if ($args["height"] == 480 && $args["width"] == 640) { + $args["height"] = 640; + $resize_rule->args = serialize($args); + $resize_rule->save(); + } + module::set_version("gallery", $version = 22); + } + + // Update slug values to be legal. We should have done this in the 11->12 upgrader, but I was + // lazy. Mea culpa! + if ($version == 22) { + foreach (db::build() + ->from("items") + ->select("id", "slug") + ->where(db::expr("`slug` REGEXP '[^_A-Za-z0-9-]'"), "=", 1) + ->execute() as $row) { + $new_slug = item::convert_filename_to_slug($row->slug); + if (empty($new_slug)) { + $new_slug = random::int(); + } + db::build() + ->update("items") + ->set("slug", $new_slug) + ->set("relative_url_cache", null) + ->where("id", "=", $row->id) + ->execute(); + } + module::set_version("gallery", $version = 23); + } + + if ($version == 23) { + $db->query("CREATE TABLE {failed_logins} ( + `id` int(9) NOT NULL auto_increment, + `count` int(9) NOT NULL, + `name` varchar(255) NOT NULL, + `time` int(9) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + module::set_version("gallery", $version = 24); + } + + if ($version == 24) { + foreach (array("logs", "tmp", "uploads") as $dir) { + self::_protect_directory(VARPATH . $dir); + } + module::set_version("gallery", $version = 25); + } + + if ($version == 25) { + db::build() + ->update("items") + ->set("title", db::expr("`name`")) + ->and_open() + ->where("title", "IS", null) + ->or_where("title", "=", "") + ->close() + ->execute(); + module::set_version("gallery", $version = 26); + } + + if ($version == 26) { + if (in_array("failed_logins", Database::instance()->list_tables())) { + $db->query("RENAME TABLE {failed_logins} TO {failed_auths}"); + } + module::set_version("gallery", $version = 27); + } + + if ($version == 27) { + // Set the admin area timeout to 90 minutes + module::set_var("gallery", "admin_area_timeout", 90 * 60); + module::set_version("gallery", $version = 28); + } + + if ($version == 28) { + module::set_var("gallery", "credits", "Powered by %gallery_version"); + module::set_version("gallery", $version = 29); + } + + if ($version == 29) { + $db->query("ALTER TABLE {caches} ADD KEY (`key`);"); + module::set_version("gallery", $version = 30); + } + + if ($version == 30) { + module::set_var("gallery", "maintenance_mode", 0); + module::set_version("gallery", $version = 31); + } + + if ($version == 31) { + $db->query("ALTER TABLE {modules} ADD COLUMN `weight` int(9) DEFAULT NULL"); + $db->query("ALTER TABLE {modules} ADD KEY (`weight`)"); + db::update("modules") + ->set("weight", db::expr("`id`")) + ->execute(); + module::set_version("gallery", $version = 32); + } + + if ($version == 32) { + $db->query("ALTER TABLE {items} ADD KEY (`left_ptr`)"); + module::set_version("gallery", $version = 33); + } + + if ($version == 33) { + $db->query("ALTER TABLE {access_caches} ADD KEY (`item_id`)"); + module::set_version("gallery", $version = 34); + } + + if ($version == 34) { + module::set_var("gallery", "visible_title_length", 15); + module::set_version("gallery", $version = 35); + } + + if ($version == 35) { + module::set_var("gallery", "favicon_url", "lib/images/favicon.ico"); + module::set_version("gallery", $version = 36); + } + + if ($version == 36) { + module::set_var("gallery", "email_from", "admin@example.com"); + module::set_var("gallery", "email_reply_to", "public@example.com"); + module::set_var("gallery", "email_line_length", 70); + module::set_var("gallery", "email_header_separator", serialize("\n")); + module::set_version("gallery", $version = 37); + } + + // Changed our minds and decided that the initial value should be empty + // But don't just reset it blindly, only do it if the value is version 37 default + if ($version == 37) { + $email = module::get_var("gallery", "email_from", ""); + if ($email == "admin@example.com") { + module::set_var("gallery", "email_from", ""); + } + $email = module::get_var("gallery", "email_reply_to", ""); + if ($email == "admin@example.com") { + module::set_var("gallery", "email_reply_to", ""); + } + module::set_version("gallery", $version = 38); + } + + if ($version == 38) { + module::set_var("gallery", "show_user_profiles_to", "registered_users"); + module::set_version("gallery", $version = 39); + } + + if ($version == 39) { + module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin"); + module::set_version("gallery", $version = 40); + } + + if ($version == 40) { + module::clear_var("gallery", "_cache"); + module::set_version("gallery", $version = 41); + } + + if ($version == 41) { + $db->query("TRUNCATE TABLE {caches}"); + $db->query("ALTER TABLE {caches} DROP INDEX `key`, ADD UNIQUE `key` (`key`)"); + module::set_version("gallery", $version = 42); + } + + if ($version == 42) { + $db->query("ALTER TABLE {items} CHANGE `description` `description` text DEFAULT NULL"); + module::set_version("gallery", $version = 43); + } + + if ($version == 43) { + $db->query("ALTER TABLE {items} CHANGE `rand_key` `rand_key` DECIMAL(11, 10)"); + module::set_version("gallery", $version = 44); + } + + if ($version == 44) { + $db->query("ALTER TABLE {messages} CHANGE `value` `value` text default NULL"); + module::set_version("gallery", $version = 45); + } + + if ($version == 45) { + // Splice the upgrade_checker block into the admin dashboard at the top + // of the page, but under the welcome block if it's in the first position. + $blocks = block_manager::get_active("dashboard_center"); + $index = count($blocks) && current($blocks) == array("gallery", "welcome") ? 1 : 0; + array_splice($blocks, $index, 0, array(random::int() => array("gallery", "upgrade_checker"))); + block_manager::set_active("dashboard_center", $blocks); + + module::set_var("gallery", "upgrade_checker_auto_enabled", true); + module::set_version("gallery", $version = 46); + } + + if ($version == 46) { + module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png"); + module::set_version("gallery", $version = 47); + } + + if ($version == 47 || $version == 48) { + // Add configuration variable to set timezone. Defaults to the currently + // used timezone (from PHP configuration). Note that in v48 we were + // setting this value incorrectly, so we're going to stomp this value for v49. + module::set_var("gallery", "timezone", null); + module::set_version("gallery", $version = 49); + } + + if ($version == 49) { + // In v49 we changed the Item_Model validation code to disallow files with two dots in them, + // but we didn't rename any files which fail to validate, so as soon as you do anything to + // change those files (eg. as a side effect of getting the url or file path) it fails to + // validate. Fix those here. This might be slow, but if it times out it can just pick up + // where it left off. + foreach (db::build() + ->from("items") + ->select("id") + ->where("type", "<>", "album") + ->where(db::expr("`name` REGEXP '\\\\..*\\\\.'"), "=", 1) + ->order_by("id", "asc") + ->execute() as $row) { + set_time_limit(30); + $item = ORM::factory("item", $row->id); + $item->name = legal_file::smash_extensions($item->name); + $item->save(); + } + module::set_version("gallery", $version = 50); + } + + if ($version == 50) { + // In v51, we added a lock_timeout variable so that administrators could edit the time out + // from 1 second to a higher variable if their system runs concurrent parallel uploads for + // instance. + module::set_var("gallery", "lock_timeout", 1); + module::set_version("gallery", $version = 51); + } + + if ($version == 51) { + // In v52, we added functions to the legal_file helper that map photo and movie file + // extensions to their mime types (and allow extension of the list by other modules). During + // this process, we correctly mapped m4v files to video/x-m4v, correcting a previous error + // where they were mapped to video/mp4. This corrects the existing items. + db::build() + ->update("items") + ->set("mime_type", "video/x-m4v") + ->where("name", "REGEXP", "\.m4v$") // case insensitive since name column is utf8_general_ci + ->execute(); + module::set_version("gallery", $version = 52); + } + + if ($version == 52) { + // In v53, we added the ability to change the default time used when extracting frames from + // movies. Previously we hard-coded this at 3 seconds, so we use that as the default. + module::set_var("gallery", "movie_extract_frame_time", 3); + module::set_version("gallery", $version = 53); + } + + if ($version == 53) { + // In v54, we changed how we check for name and slug conflicts in Item_Model. Previously, + // we checked the whole filename. As a result, "foo.jpg" and "foo.png" were not considered + // conflicting if their slugs were different (a rare case in practice since server_add and + // uploader would give them both the same slug "foo"). Now, we check the filename without its + // extension. This upgrade stanza fixes any conflicts where they were previously allowed. + + // This might be slow, but if it times out it can just pick up where it left off. + + // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one + // conflict; "bar.jpg", "bar.png", and "bar.flv" are another) + foreach (db::build() + ->select_distinct(array("parent_base_name" => + db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))"))) + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->where("type", "<>", "album") + ->having("C", ">", 1) + ->group_by("parent_base_name") + ->execute() as $conflict) { + list ($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2); + $base_name_escaped = Database::escape_for_like($base_name); + // Loop through the items for each conflict + foreach (db::build() + ->from("items") + ->select("id") + ->where("type", "<>", "album") + ->where("parent_id", "=", $parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->limit(1000000) // required to satisfy SQL syntax (no offset without limit) + ->offset(1) // skips the 0th item + ->execute() as $row) { + set_time_limit(30); + $item = ORM::factory("item", $row->id); + $item->name = $item->name; // this will force Item_Model to check for conflicts on save + $item->save(); + } + } + module::set_version("gallery", $version = 54); + } + + if ($version == 54) { + $db->query("ALTER TABLE {items} ADD KEY `relative_path_cache` (`relative_path_cache`)"); + module::set_version("gallery", $version = 55); + } + + if ($version == 55) { + // In v56, we added the ability to change the default behavior regarding movie uploads. It + // can be set to "always", "never", or "autodetect" to match the previous behavior where they + // are allowed only if FFmpeg is found. + module::set_var("gallery", "movie_allow_uploads", "autodetect"); + module::set_version("gallery", $version = 56); + } + + if ($version == 56) { + // Cleanup possible instances where resize_dirty of albums or movies was set to 0. This is + // unlikely to have occurred, and doesn't currently matter much since albums and movies don't + // have resize images anyway. However, it may be useful to be consistent here going forward. + db::build() + ->update("items") + ->set("resize_dirty", 1) + ->where("type", "<>", "photo") + ->execute(); + module::set_version("gallery", $version = 57); + } + } + + static function uninstall() { + $db = Database::instance(); + $db->query("DROP TABLE IF EXISTS {access_caches}"); + $db->query("DROP TABLE IF EXISTS {access_intents}"); + $db->query("DROP TABLE IF EXISTS {graphics_rules}"); + $db->query("DROP TABLE IF EXISTS {incoming_translations}"); + $db->query("DROP TABLE IF EXISTS {failed_auths}"); + $db->query("DROP TABLE IF EXISTS {items}"); + $db->query("DROP TABLE IF EXISTS {logs}"); + $db->query("DROP TABLE IF EXISTS {modules}"); + $db->query("DROP TABLE IF EXISTS {outgoing_translations}"); + $db->query("DROP TABLE IF EXISTS {permissions}"); + $db->query("DROP TABLE IF EXISTS {sessions}"); + $db->query("DROP TABLE IF EXISTS {tasks}"); + $db->query("DROP TABLE IF EXISTS {themes}"); + $db->query("DROP TABLE IF EXISTS {vars}"); + $db->query("DROP TABLE IF EXISTS {caches}"); + foreach (array("albums", "resizes", "thumbs", "uploads", + "modules", "logs", "database.php") as $entry) { + system("/bin/rm -rf " . VARPATH . $entry); + } + } + + static function _protect_directory($dir) { + $fp = @fopen("$dir/.htaccess", "w+"); + fwrite($fp, "DirectoryIndex .htaccess\nSetHandler Gallery_Security_Do_Not_Remove\n" . + "Options None\n\nRewriteEngine off\n\n" . + "Order allow,deny\nDeny from all\n"); + fclose($fp); + } +} diff --git a/modules/gallery/helpers/gallery_rss.php b/modules/gallery/helpers/gallery_rss.php new file mode 100644 index 0000000..d6b3302 --- /dev/null +++ b/modules/gallery/helpers/gallery_rss.php @@ -0,0 +1,76 @@ + is_album() ? $item : $item->parent(); + + $feeds["gallery/album/{$feed_item->id}"] = + t("%title photos and movies", array("title" => $feed_item->title)); + } + + return $feeds; + } + + static function feed($feed_id, $offset, $limit, $id) { + $feed = new stdClass(); + switch ($feed_id) { + case "latest": + $feed->items = ORM::factory("item") + ->viewable() + ->where("type", "<>", "album") + ->order_by("created", "DESC") + ->find_all($limit, $offset); + + $all_items = ORM::factory("item") + ->viewable() + ->where("type", "<>", "album") + ->order_by("created", "DESC"); + + $feed->max_pages = ceil($all_items->find_all()->count() / $limit); + $feed->title = t("%site_title - Recent updates", array("site_title" => item::root()->title)); + $feed->description = t("Recent updates"); + return $feed; + + case "album": + $item = ORM::factory("item", $id); + access::required("view", $item); + + $feed->items = $item + ->viewable() + ->descendants($limit, $offset, array(array("type", "=", "photo"))); + $feed->max_pages = ceil( + $item->viewable()->descendants_count(array(array("type", "=", "photo"))) / $limit); + if ($item->id == item::root()->id) { + $feed->title = html::purify($item->title); + } else { + $feed->title = t("%site_title - %item_title", + array("site_title" => item::root()->title, + "item_title" => $item->title)); + } + $feed->description = nl2br(html::purify($item->description)); + + return $feed; + } + } +} diff --git a/modules/gallery/helpers/gallery_task.php b/modules/gallery/helpers/gallery_task.php new file mode 100644 index 0000000..618cf8f --- /dev/null +++ b/modules/gallery/helpers/gallery_task.php @@ -0,0 +1,826 @@ +count_records(); + $tasks = array(); + $tasks[] = Task_Definition::factory() + ->callback("gallery_task::rebuild_dirty_images") + ->name(t("Rebuild Images")) + ->description($dirty_count ? + t2("You have one out of date photo", + "You have %count out of date photos", + $dirty_count) + : t("All your photos are up to date")) + ->severity($dirty_count ? log::WARNING : log::SUCCESS); + + $tasks[] = Task_Definition::factory() + ->callback("gallery_task::update_l10n") + ->name(t("Update translations")) + ->description(t("Download new and updated translated strings")) + ->severity(log::SUCCESS); + + $tasks[] = Task_Definition::factory() + ->callback("gallery_task::file_cleanup") + ->name(t("Remove old files")) + ->description(t("Remove expired files from the logs and tmp directory")) + ->severity(log::SUCCESS); + + $tasks[] = Task_Definition::factory() + ->callback("gallery_task::fix") + ->name(t("Fix your Gallery")) + ->description(t("Fix a variety of problems that might cause your Gallery to act strangely. Requires maintenance mode.")) + ->severity(log::SUCCESS); + + return $tasks; + } + + /** + * Task that rebuilds all dirty images. + * @param Task_Model the task + */ + static function rebuild_dirty_images($task) { + $errors = array(); + try { + // Choose the dirty images in a random order so that if we run this task multiple times + // concurrently each task is rebuilding different images simultaneously. + $result = graphics::find_dirty_images_query()->select("id") + ->select(db::expr("RAND() as r")) + ->order_by("r", "ASC") + ->execute(); + $total_count = $task->get("total_count", $result->count()); + $mode = $task->get("mode", "init"); + if ($mode == "init") { + $task->set("total_count", $total_count); + $task->set("mode", "process"); + batch::start(); + } + + $completed = $task->get("completed", 0); + $ignored = $task->get("ignored", array()); + + $i = 0; + + // If there's no work left to do, skip to the end. This can happen if we resume a task long + // after the work got done in some other task. + if (!$result->count()) { + $completed = $total_count; + } + + foreach ($result as $row) { + if (array_key_exists($row->id, $ignored)) { + continue; + } + + $item = ORM::factory("item", $row->id); + if ($item->loaded()) { + try { + graphics::generate($item); + $completed++; + + $errors[] = t("Successfully rebuilt images for '%title'", + array("title" => html::purify($item->title))); + } catch (Exception $e) { + $errors[] = t("Unable to rebuild images for '%title'", + array("title" => html::purify($item->title))); + $errors[] = (string)$e; + $ignored[$item->id] = 1; + } + } + + if (++$i == 2) { + break; + } + } + + $task->status = t2("Updated: 1 image. Total: %total_count.", + "Updated: %count images. Total: %total_count.", + $completed, + array("total_count" => $total_count)); + + if ($completed < $total_count) { + $task->percent_complete = (int)(100 * ($completed + count($ignored)) / $total_count); + } else { + $task->percent_complete = 100; + } + + $task->set("completed", $completed); + $task->set("ignored", $ignored); + if ($task->percent_complete == 100) { + $task->done = true; + $task->state = "success"; + batch::stop(); + site_status::clear("graphics_dirty"); + } + } catch (Exception $e) { + Kohana_Log::add("error",(string)$e); + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + $errors[] = (string)$e; + } + if ($errors) { + $task->log($errors); + } + } + + static function update_l10n($task) { + $errors = array(); + try { + $start = microtime(true); + $data = Cache::instance()->get("update_l10n_cache:{$task->id}"); + if ($data) { + list($dirs, $files, $cache, $num_fetched) = unserialize($data); + } + $i = 0; + + switch ($task->get("mode", "init")) { + case "init": // 0% + $dirs = array("gallery", "modules", "themes", "installer"); + $files = $cache = array(); + $num_fetched = 0; + $task->set("mode", "find_files"); + $task->status = t("Finding files"); + break; + + case "find_files": // 0% - 10% + while (($dir = array_pop($dirs)) && microtime(true) - $start < 0.5) { + if (in_array(basename($dir), array("tests", "lib"))) { + continue; + } + + foreach (glob(DOCROOT . "$dir/*") as $path) { + $relative_path = str_replace(DOCROOT, "", $path); + if (is_dir($path)) { + $dirs[] = $relative_path; + } else { + $files[] = $relative_path; + } + } + } + + $task->status = t2("Finding files: found 1 file", + "Finding files: found %count files", count($files)); + + if (!$dirs) { + $task->set("mode", "scan_files"); + $task->set("total_files", count($files)); + $task->status = t("Scanning files"); + $task->percent_complete = 10; + } + break; + + case "scan_files": // 10% - 70% + while (($file = array_pop($files)) && microtime(true) - $start < 0.5) { + $file = DOCROOT . $file; + switch (pathinfo($file, PATHINFO_EXTENSION)) { + case "php": + l10n_scanner::scan_php_file($file, $cache); + break; + + case "info": + l10n_scanner::scan_info_file($file, $cache); + break; + } + } + + $total_files = $task->get("total_files"); + $task->status = t2("Scanning files: scanned 1 file", + "Scanning files: scanned %count files", $total_files - count($files)); + + $task->percent_complete = 10 + 60 * ($total_files - count($files)) / $total_files; + if (empty($files)) { + $task->set("mode", "fetch_updates"); + $task->status = t("Fetching updates"); + $task->percent_complete = 70; + } + break; + + case "fetch_updates": // 70% - 100% + // Send fetch requests in batches until we're done + $num_remaining = l10n_client::fetch_updates($num_fetched); + if ($num_remaining) { + $total = $num_fetched + $num_remaining; + $task->percent_complete = 70 + 30 * ((float) $num_fetched / $total); + } else { + Gallery_I18n::clear_cache(); + + $task->done = true; + $task->state = "success"; + $task->status = t("Translations installed/updated"); + $task->percent_complete = 100; + } + } + + if (!$task->done) { + Cache::instance()->set("update_l10n_cache:{$task->id}", + serialize(array($dirs, $files, $cache, $num_fetched)), + array("l10n")); + } else { + Cache::instance()->delete("update_l10n_cache:{$task->id}"); + } + } catch (Exception $e) { + Kohana_Log::add("error",(string)$e); + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + $errors[] = (string)$e; + } + if ($errors) { + $task->log($errors); + } + } + + /** + * Task that removes old files from var/logs and var/tmp. + * @param Task_Model the task + */ + static function file_cleanup($task) { + $errors = array(); + try { + $start = microtime(true); + $data = Cache::instance()->get("file_cleanup_cache:{$task->id}"); + $files = $data ? unserialize($data) : array(); + $i = 0; + $current = 0; + $total = 0; + + switch ($task->get("mode", "init")) { + case "init": + $threshold = time() - 1209600; // older than 2 weeks + // Note that this code is roughly duplicated in gallery_event::gallery_shutdown + foreach(array("logs", "tmp") as $dir) { + $dir = VARPATH . $dir; + if ($dh = opendir($dir)) { + while (($file = readdir($dh)) !== false) { + if ($file[0] == ".") { + continue; + } + + // Ignore directories for now, but we should really address them in the long term. + if (is_dir("$dir/$file")) { + continue; + } + + if (filemtime("$dir/$file") <= $threshold) { + $files[] = "$dir/$file"; + } + } + } + } + $task->set("mode", "delete_files"); + $task->set("current", 0); + $task->set("total", count($files)); + Cache::instance()->set("file_cleanup_cache:{$task->id}", serialize($files), + array("file_cleanup")); + if (count($files) == 0) { + break; + } + + case "delete_files": + $current = $task->get("current"); + $total = $task->get("total"); + while ($current < $total && microtime(true) - $start < 1) { + @unlink($files[$current]); + $task->log(t("%file removed", array("file" => $files[$current++]))); + } + $task->percent_complete = $current / $total * 100; + $task->set("current", $current); + } + + $task->status = t2("Removed: 1 file. Total: %total_count.", + "Removed: %count files. Total: %total_count.", + $current, array("total_count" => $total)); + + if ($total == $current) { + $task->done = true; + $task->state = "success"; + $task->percent_complete = 100; + } + } catch (Exception $e) { + Kohana_Log::add("error",(string)$e); + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + $errors[] = (string)$e; + } + if ($errors) { + $task->log($errors); + } + } + + static function fix($task) { + $start = microtime(true); + + $total = $task->get("total"); + if (empty($total)) { + $item_count = db::build()->count_records("items"); + $total = 0; + + // mptt: 2 operations for every item + $total += 2 * $item_count; + + // album audit (permissions and bogus album covers): 1 operation for every album + $total += db::build()->where("type", "=", "album")->count_records("items"); + + // one operation for each dupe slug, dupe name, dupe base name, and missing access cache + foreach (array("find_dupe_slugs", "find_dupe_names", "find_dupe_base_names", + "find_missing_access_caches") as $func) { + foreach (self::$func() as $row) { + $total++; + } + } + + // one operation to rebuild path and url caches; + $total += 1 * $item_count; + + $task->set("total", $total); + $task->set("state", $state = self::FIX_STATE_START_MPTT); + $task->set("ptr", 1); + $task->set("completed", 0); + } + + $completed = $task->get("completed"); + $state = $task->get("state"); + + if (!module::get_var("gallery", "maintenance_mode")) { + module::set_var("gallery", "maintenance_mode", 1); + } + + // This is a state machine that checks each item in the database. It verifies the following + // attributes for an item. + // 1. Left and right MPTT pointers are correct + // 2. The .htaccess permission files for restricted items exist and are well formed. + // 3. The relative_path_cache and relative_url_cache values are set to null. + // 4. there are no album_cover_item_ids pointing to missing items + // + // We'll do a depth-first tree walk over our hierarchy using only the adjacency data because + // we don't trust MPTT here (that might be what we're here to fix!). Avoid avoid using ORM + // calls as much as possible since they're expensive. + // + // NOTE: the MPTT check will only traverse items that have valid parents. It's possible that + // we have some tree corruption where there are items with parent ids to non-existent items. + // We should probably do something about that. + while ($state != self::FIX_STATE_DONE && microtime(true) - $start < 1.5) { + switch ($state) { + case self::FIX_STATE_START_MPTT: + $task->set("ptr", $ptr = 1); + $task->set("stack", item::root()->id . "L1"); + $state = self::FIX_STATE_RUN_MPTT; + break; + + case self::FIX_STATE_RUN_MPTT: + $ptr = $task->get("ptr"); + $stack = explode(" ", $task->get("stack")); + preg_match("/([0-9]+)([A-Z])([0-9]+)/", array_pop($stack), $matches); // e.g. "12345L10" + list ( , $id, $ptr_mode, $level) = $matches; // Skip the 0th entry of matches. + switch ($ptr_mode) { + case "L": + // Albums could be parent nodes. + $stack[] = "{$id}R{$level}"; + db::build() + ->update("items") + ->set("left_ptr", $ptr++) + ->where("id", "=", $id) + ->execute(); + + $level++; + foreach (db::build() + ->select(array("id", "type")) + ->from("items") + ->where("parent_id", "=", $id) + ->order_by("left_ptr", "DESC") // DESC since array_pop effectively reverses them + ->execute() as $child) { + $stack[] = ($child->type == "album") ? "{$child->id}L{$level}" : "{$child->id}B{$level}"; + } + $completed++; + break; + case "B": + // Non-albums must be leaf nodes. + db::build() + ->update("items") + ->set("left_ptr", $ptr++) + ->set("right_ptr", $ptr++) + ->set("level", $level) + ->set("relative_path_cache", null) + ->set("relative_url_cache", null) + ->where("id", "=", $id) + ->execute(); + $completed += 2; // we updated two pointers + break; + case "R": + db::build() + ->update("items") + ->set("right_ptr", $ptr++) + ->set("level", $level) + ->set("relative_path_cache", null) + ->set("relative_url_cache", null) + ->where("id", "=", $id) + ->execute(); + $completed++; + } + $task->set("ptr", $ptr); + $task->set("stack", implode(" ", $stack)); + + if (empty($stack)) { + $state = self::FIX_STATE_START_DUPE_SLUGS; + } + break; + + + case self::FIX_STATE_START_DUPE_SLUGS: + $stack = array(); + foreach (self::find_dupe_slugs() as $row) { + list ($parent_id, $slug) = explode(":", $row->parent_slug, 2); + $stack[] = join(":", array($parent_id, $slug)); + } + if ($stack) { + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_DUPE_SLUGS; + } else { + $state = self::FIX_STATE_START_DUPE_NAMES; + } + break; + + case self::FIX_STATE_RUN_DUPE_SLUGS: + $stack = explode(" ", $task->get("stack")); + list ($parent_id, $slug) = explode(":", array_pop($stack)); + + // We want to leave the first one alone and update all conflicts to be random values. + $fixed = 0; + $conflicts = ORM::factory("item") + ->where("parent_id", "=", $parent_id) + ->where("slug", "=", $slug) + ->find_all(1, 1); + if ($conflicts->count() && $conflict = $conflicts->current()) { + $task->log("Fixing conflicting slug for item id {$conflict->id}"); + db::build() + ->update("items") + ->set("slug", $slug . "-" . (string)rand(1000, 9999)) + ->where("id", "=", $conflict->id) + ->execute(); + + // We fixed one conflict, but there might be more so put this parent back on the stack + // and try again. We won't consider it completed when we don't fix a conflict. This + // guarantees that we won't spend too long fixing one set of conflicts, and that we + // won't stop before all are fixed. + $stack[] = "$parent_id:$slug"; + break; + } + $task->set("stack", implode(" ", $stack)); + $completed++; + + if (empty($stack)) { + $state = self::FIX_STATE_START_DUPE_NAMES; + } + break; + + case self::FIX_STATE_START_DUPE_NAMES: + $stack = array(); + foreach (self::find_dupe_names() as $row) { + list ($parent_id, $name) = explode(":", $row->parent_name, 2); + $stack[] = join(":", array($parent_id, $name)); + } + if ($stack) { + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_DUPE_NAMES; + } else { + $state = self::FIX_STATE_START_DUPE_BASE_NAMES; + } + break; + + case self::FIX_STATE_RUN_DUPE_NAMES: + // NOTE: This does *not* attempt to fix the file system! + $stack = explode(" ", $task->get("stack")); + list ($parent_id, $name) = explode(":", array_pop($stack)); + + $fixed = 0; + // We want to leave the first one alone and update all conflicts to be random values. + $conflicts = ORM::factory("item") + ->where("parent_id", "=", $parent_id) + ->where("name", "=", $name) + ->find_all(1, 1); + if ($conflicts->count() && $conflict = $conflicts->current()) { + $task->log("Fixing conflicting name for item id {$conflict->id}"); + if (!$conflict->is_album() && preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) { + $item_base_name = $matches[1]; + $item_extension = $matches[2]; // includes a leading dot + } else { + $item_base_name = $conflict->name; + $item_extension = ""; + } + db::build() + ->update("items") + ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension) + ->where("id", "=", $conflict->id) + ->execute(); + + // We fixed one conflict, but there might be more so put this parent back on the stack + // and try again. We won't consider it completed when we don't fix a conflict. This + // guarantees that we won't spend too long fixing one set of conflicts, and that we + // won't stop before all are fixed. + $stack[] = "$parent_id:$name"; + break; + } + $task->set("stack", implode(" ", $stack)); + $completed++; + + if (empty($stack)) { + $state = self::FIX_STATE_START_DUPE_BASE_NAMES; + } + break; + + case self::FIX_STATE_START_DUPE_BASE_NAMES: + $stack = array(); + foreach (self::find_dupe_base_names() as $row) { + list ($parent_id, $base_name) = explode(":", $row->parent_base_name, 2); + $stack[] = join(":", array($parent_id, $base_name)); + } + if ($stack) { + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_DUPE_BASE_NAMES; + } else { + $state = self::FIX_STATE_START_ALBUMS; + } + break; + + case self::FIX_STATE_RUN_DUPE_BASE_NAMES: + // NOTE: This *does* attempt to fix the file system! So, it must go *after* run_dupe_names. + $stack = explode(" ", $task->get("stack")); + list ($parent_id, $base_name) = explode(":", array_pop($stack)); + $base_name_escaped = Database::escape_for_like($base_name); + + $fixed = 0; + // We want to leave the first one alone and update all conflicts to be random values. + $conflicts = ORM::factory("item") + ->where("parent_id", "=", $parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->where("type", "<>", "album") + ->find_all(1, 1); + if ($conflicts->count() && $conflict = $conflicts->current()) { + $task->log("Fixing conflicting name for item id {$conflict->id}"); + if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) { + $item_base_name = $matches[1]; // unlike $base_name, this always maintains capitalization + $item_extension = $matches[2]; // includes a leading dot + } else { + $item_base_name = $conflict->name; + $item_extension = ""; + } + // Unlike conflicts found in run_dupe_names, these items are likely to have an intact + // file system. Let's use the item save logic to rebuild the paths and rename the files + // if possible. + try { + $conflict->name = $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension; + $conflict->validate(); + // If we get here, we're safe to proceed with save + $conflict->save(); + } catch (Exception $e) { + // Didn't work. Edit database directly without fixing file system. + db::build() + ->update("items") + ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension) + ->where("id", "=", $conflict->id) + ->execute(); + } + + // We fixed one conflict, but there might be more so put this parent back on the stack + // and try again. We won't consider it completed when we don't fix a conflict. This + // guarantees that we won't spend too long fixing one set of conflicts, and that we + // won't stop before all are fixed. + $stack[] = "$parent_id:$base_name"; + break; + } + $task->set("stack", implode(" ", $stack)); + $completed++; + + if (empty($stack)) { + $state = self::FIX_STATE_START_ALBUMS; + } + break; + + case self::FIX_STATE_START_ALBUMS: + $stack = array(); + foreach (db::build() + ->select("id") + ->from("items") + ->where("type", "=", "album") + ->execute() as $row) { + $stack[] = $row->id; + } + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_ALBUMS; + break; + + case self::FIX_STATE_RUN_ALBUMS: + $stack = explode(" ", $task->get("stack")); + $id = array_pop($stack); + + $item = ORM::factory("item", $id); + if ($item->album_cover_item_id) { + $album_cover_item = ORM::factory("item", $item->album_cover_item_id); + if (!$album_cover_item->loaded()) { + $item->album_cover_item_id = null; + $item->save(); + } + } + + $everybody = identity::everybody(); + $view_col = "view_{$everybody->id}"; + $view_full_col = "view_full_{$everybody->id}"; + $intent = ORM::factory("access_intent")->where("item_id", "=", $id)->find(); + if ($intent->$view_col === access::DENY) { + access::update_htaccess_files($item, $everybody, "view", access::DENY); + } + if ($intent->$view_full_col === access::DENY) { + access::update_htaccess_files($item, $everybody, "view_full", access::DENY); + } + $task->set("stack", implode(" ", $stack)); + $completed++; + + if (empty($stack)) { + $state = self::FIX_STATE_START_REBUILD_ITEM_CACHES; + } + break; + + case self::FIX_STATE_START_REBUILD_ITEM_CACHES: + $stack = array(); + foreach (self::find_empty_item_caches(500) as $row) { + $stack[] = $row->id; + } + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_REBUILD_ITEM_CACHES; + break; + + case self::FIX_STATE_RUN_REBUILD_ITEM_CACHES: + $stack = explode(" ", $task->get("stack")); + if (!empty($stack)) { + $id = array_pop($stack); + $item = ORM::factory("item", $id); + $item->relative_path(); // this rebuilds the cache and saves the item as a side-effect + $task->set("stack", implode(" ", $stack)); + $completed++; + } + + if (empty($stack)) { + // Try refilling the stack + foreach (self::find_empty_item_caches(500) as $row) { + $stack[] = $row->id; + } + $task->set("stack", implode(" ", $stack)); + + if (empty($stack)) { + $state = self::FIX_STATE_START_MISSING_ACCESS_CACHES; + } + } + break; + + case self::FIX_STATE_START_MISSING_ACCESS_CACHES: + $stack = array(); + foreach (self::find_missing_access_caches_limited(500) as $row) { + $stack[] = $row->id; + } + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_MISSING_ACCESS_CACHES; + break; + + case self::FIX_STATE_RUN_MISSING_ACCESS_CACHES: + $stack = array_filter(explode(" ", $task->get("stack"))); // filter removes empty/zero ids + if (!empty($stack)) { + $id = array_pop($stack); + $access_cache = ORM::factory("access_cache"); + $access_cache->item_id = $id; + $access_cache->save(); + $task->set("stack", implode(" ", $stack)); + $completed++; + } + + if (empty($stack)) { + // Try refilling the stack + foreach (self::find_missing_access_caches_limited(500) as $row) { + $stack[] = $row->id; + } + $task->set("stack", implode(" ", $stack)); + + if (empty($stack)) { + // The new cache rows are there, but they're incorrectly populated so we have to fix + // them. If this turns out to be too slow, we'll have to refactor + // access::recalculate_permissions to allow us to do it in slices. + access::recalculate_album_permissions(item::root()); + $state = self::FIX_STATE_DONE; + } + } + break; + } + } + + $task->set("state", $state); + $task->set("completed", $completed); + + if ($state == self::FIX_STATE_DONE) { + $task->done = true; + $task->state = "success"; + $task->percent_complete = 100; + module::set_var("gallery", "maintenance_mode", 0); + } else { + $task->percent_complete = round(100 * $completed / $total); + } + $task->status = t2("One operation complete", "%count / %total operations complete", $completed, + array("total" => $total)); + } + + static function find_dupe_slugs() { + return db::build() + ->select_distinct( + array("parent_slug" => db::expr("CONCAT(`parent_id`, ':', LOWER(`slug`))"))) + ->select("id") + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->having("C", ">", 1) + ->group_by("parent_slug") + ->execute(); + } + + static function find_dupe_names() { + // looking for photos, movies, and albums + return db::build() + ->select_distinct( + array("parent_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(`name`))"))) + ->select("id") + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->having("C", ">", 1) + ->group_by("parent_name") + ->execute(); + } + + static function find_dupe_base_names() { + // looking for photos or movies, not albums + return db::build() + ->select_distinct( + array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))"))) + ->select("id") + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->where("type", "<>", "album") + ->having("C", ">", 1) + ->group_by("parent_base_name") + ->execute(); + } + + static function find_empty_item_caches($limit) { + return db::build() + ->select("items.id") + ->from("items") + ->where("relative_path_cache", "is", null) + ->or_where("relative_url_cache", "is", null) + ->limit($limit) + ->execute(); + } + + static function find_missing_access_caches() { + return self::find_missing_access_caches_limited(1 << 16); + } + + static function find_missing_access_caches_limited($limit) { + return db::build() + ->select("items.id") + ->from("items") + ->join("access_caches", "items.id", "access_caches.item_id", "left") + ->where("access_caches.id", "is", null) + ->limit($limit) + ->execute(); + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/gallery_theme.php b/modules/gallery/helpers/gallery_theme.php new file mode 100644 index 0000000..3c6d71e --- /dev/null +++ b/modules/gallery/helpers/gallery_theme.php @@ -0,0 +1,151 @@ +css("gallery.css"); + if ($session->get("debug")) { + $buf .= $theme->css("debug.css"); + } + + if (module::is_active("rss")) { + if ($item = $theme->item()) { + if ($item->is_album()) { + $buf .= rss::feed_link("gallery/album/{$item->id}"); + } else { + $buf .= rss::feed_link("gallery/album/{$item->parent()->id}"); + } + } else if ($tag = $theme->tag()) { + $buf .= rss::feed_link("tag/tag/{$tag->id}"); + } + } + + if (count(locales::installed())) { + // Needed by the languages block + $buf .= $theme->script("jquery.cookie.js"); + } + + if ($session->get("l10n_mode", false)) { + $buf .= $theme->css("l10n_client.css") + . $theme->script("jquery.cookie.js") + . $theme->script("l10n_client.js"); + } + + $buf .= $theme->css("uploadify/uploadify.css"); + return $buf; + } + + static function admin_head($theme) { + $buf = $theme->css("gallery.css"); + $buf .= $theme->script("gallery.panel.js"); + $session = Session::instance(); + if ($session->get("debug")) { + $buf .= $theme->css("debug.css"); + } + + if ($session->get("l10n_mode", false)) { + $buf .= $theme->css("l10n_client.css"); + $buf .= $theme->script("jquery.cookie.js"); + $buf .= $theme->script("l10n_client.js"); + } + return $buf; + } + + static function page_bottom($theme) { + $session = Session::instance(); + if (gallery::show_profiler()) { + Profiler::enable(); + $profiler = new Profiler(); + $profiler->render(); + } + $content = ""; + if ($session->get("l10n_mode", false)) { + $content .= L10n_Client_Controller::l10n_form(); + } + + if ($session->get_once("after_install")) { + $content .= new View("welcome_message_loader.html"); + } + + if (identity::active_user()->admin && upgrade_checker::should_auto_check()) { + $content .= ''; + } + return $content; + } + + static function admin_page_bottom($theme) { + $session = Session::instance(); + if (gallery::show_profiler()) { + Profiler::enable(); + $profiler = new Profiler(); + $profiler->render(); + } + + // Redirect to the root album when the admin session expires. + $content = ''; + + if (upgrade_checker::should_auto_check()) { + $content .= ''; + } + + if ($session->get("l10n_mode", false)) { + $content .= "\n" . L10n_Client_Controller::l10n_form(); + } + return $content; + } + + static function credits() { + $version_string = SafeString::of_safe_html( + 'Gallery ' . gallery::version_string() . ''); + return "
  • " . + t(module::get_var("gallery", "credits"), + array("url" => "http://galleryproject.org", + "gallery_version" => $version_string)) . + "
  • "; + } + + static function admin_credits() { + return gallery_theme::credits(); + } + + static function body_attributes() { + if (locales::is_rtl()) { + return 'class="rtl"'; + } + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php new file mode 100644 index 0000000..459784c --- /dev/null +++ b/modules/gallery/helpers/graphics.php @@ -0,0 +1,546 @@ + 200, "height" => 200, "master" => Image::AUTO), 100); + * + * Specifies that "gallery" is adding a rule to resize thumbnails down to a max of 200px on + * the longest side. The gallery module adds default rules at a priority of 100. You can set + * higher and lower priorities to perform operations before or after this fires. + * + * @param string $module_name the module that added the rule + * @param string $target the target for this operation ("thumb" or "resize") + * @param string $operation the name of the operation (::method) + * @param array $args arguments to the operation + * @param integer $priority the priority for this rule (lower priorities are run first) + */ + static function add_rule($module_name, $target, $operation, $args, $priority) { + $rule = ORM::factory("graphics_rule"); + $rule->module_name = $module_name; + $rule->target = $target; + $rule->operation = $operation; + $rule->priority = $priority; + $rule->args = serialize($args); + $rule->active = true; + $rule->save(); + + graphics::mark_dirty($target == "thumb", $target == "resize"); + } + + /** + * Remove any matching graphics rules + * @param string $module_name the module that added the rule + * @param string $target the target for this operation ("thumb" or "resize") + * @param string $operation the name of the operation(::method) + */ + static function remove_rule($module_name, $target, $operation) { + db::build() + ->delete("graphics_rules") + ->where("module_name", "=", $module_name) + ->where("target", "=", $target) + ->where("operation", "=", $operation) + ->execute(); + + graphics::mark_dirty($target == "thumb", $target == "resize"); + } + + /** + * Remove all rules for this module + * @param string $module_name + */ + static function remove_rules($module_name) { + $status = db::build() + ->delete("graphics_rules") + ->where("module_name", "=", $module_name) + ->execute(); + if (count($status)) { + graphics::mark_dirty(true, true); + } + } + + /** + * Activate the rules for this module, typically done when the module itself is deactivated. + * Note that this does not mark images as dirty so that if you deactivate and reactivate a + * module it won't cause all of your images to suddenly require a rebuild. + */ + static function activate_rules($module_name) { + db::build() + ->update("graphics_rules") + ->set("active", true) + ->where("module_name", "=", $module_name) + ->execute(); + } + + /** + * Deactivate the rules for this module, typically done when the module itself is deactivated. + * Note that this does not mark images as dirty so that if you deactivate and reactivate a + * module it won't cause all of your images to suddenly require a rebuild. + */ + static function deactivate_rules($module_name) { + db::build() + ->update("graphics_rules") + ->set("active", false) + ->where("module_name", "=", $module_name) + ->execute(); + } + + /** + * Rebuild the thumb and resize for the given item. + * @param Item_Model $item + */ + static function generate($item) { + if ($item->thumb_dirty) { + $ops["thumb"] = $item->thumb_path(); + } + if ($item->resize_dirty && $item->is_photo()) { + $ops["resize"] = $item->resize_path(); + } + + try { + foreach ($ops as $target => $output_file) { + $working_file = $item->file_path(); + // Delete anything that might already be there + @unlink($output_file); + switch ($item->type) { + case "movie": + // Run movie_extract_frame events, which can either: + // - generate an output file, bypassing the ffmpeg-based movie::extract_frame + // - add to the options sent to movie::extract_frame (e.g. change frame extract time, + // add de-interlacing arguments to ffmpeg... see movie helper for more info) + // Note that the args are similar to those of the events in gallery_graphics + $movie_options_wrapper = new stdClass(); + $movie_options_wrapper->movie_options = array(); + module::event("movie_extract_frame", $working_file, $output_file, + $movie_options_wrapper, $item); + // If no output_file generated by events, run movie::extract_frame with movie_options + clearstatcache(); + if (@filesize($output_file) == 0) { + try { + movie::extract_frame($working_file, $output_file, $movie_options_wrapper->movie_options); + // If we're here, we know ffmpeg is installed and the movie is valid. Because the + // user may not always have had ffmpeg installed, the movie's width, height, and + // mime type may need updating. Let's use this opportunity to make sure they're + // correct. It's not optimal to do it at this low level, but it's not trivial to find + // these cases quickly in an upgrade script. + list ($width, $height, $mime_type) = movie::get_file_metadata($working_file); + // Only set them if they need updating to avoid marking them as "changed" + if (($item->width != $width) || ($item->height != $height) || + ($item->mime_type != $mime_type)) { + $item->width = $width; + $item->height = $height; + $item->mime_type = $mime_type; + } + } catch (Exception $e) { + // Didn't work, likely because of MISSING_FFMPEG - use placeholder + graphics::_replace_image_with_placeholder($item, $target); + break; + } + } + $working_file = $output_file; + + case "photo": + // Run the graphics rules (for both movies and photos) + foreach (self::_get_rules($target) as $rule) { + $args = array($working_file, $output_file, unserialize($rule->args), $item); + call_user_func_array($rule->operation, $args); + $working_file = $output_file; + } + break; + + case "album": + if (!$cover = $item->album_cover()) { + // This album has no cover; copy its placeholder image. Because of an old bug, it's + // possible that there's an album cover item id that points to an invalid item. In that + // case, just null out the album cover item id. It's not optimal to do that at this low + // level, but it's not trivial to find these cases quickly in an upgrade script and if we + // don't do this, the album may be permanently marked as "needs rebuilding" + // + // ref: http://sourceforge.net/apps/trac/gallery/ticket/1172 + // http://galleryproject.org/node/96926 + if ($item->album_cover_item_id) { + $item->album_cover_item_id = null; + $item->save(); + } + graphics::_replace_image_with_placeholder($item, $target); + break; + } + if ($cover->thumb_dirty) { + graphics::generate($cover); + } + if (!$cover->thumb_dirty) { + // Make the album cover from the cover item's thumb. Run gallery_graphics::resize with + // null options and it will figure out if this is a direct copy or conversion to jpg. + $working_file = $cover->thumb_path(); + gallery_graphics::resize($working_file, $output_file, null, $item); + } + break; + } + } + + if (!empty($ops["thumb"])) { + if (file_exists($item->thumb_path())) { + $item->thumb_dirty = 0; + } else { + Kohana_Log::add("error", "Failed to rebuild thumb image: $item->title"); + graphics::_replace_image_with_placeholder($item, "thumb"); + } + } + + if (!empty($ops["resize"])) { + if (file_exists($item->resize_path())) { + $item->resize_dirty = 0; + } else { + Kohana_Log::add("error", "Failed to rebuild resize image: $item->title"); + graphics::_replace_image_with_placeholder($item, "resize"); + } + } + graphics::_update_item_dimensions($item); + $item->save(); + } catch (Exception $e) { + // Something went wrong rebuilding the image. Replace with the placeholder images, + // leave it dirty and move on. + Kohana_Log::add("error", "Caught exception rebuilding images: {$item->title}\n" . + $e->getMessage() . "\n" . $e->getTraceAsString()); + if ($item->is_photo()) { + graphics::_replace_image_with_placeholder($item, "resize"); + } + graphics::_replace_image_with_placeholder($item, "thumb"); + try { + graphics::_update_item_dimensions($item); + } catch (Exception $e) { + // Looks like get_file_metadata couldn't identify our placeholders. We should never get + // here, but in the odd case we do, we need to do something. Let's put in hardcoded values. + if ($item->is_photo()) { + list ($item->resize_width, $item->resize_height) = array(200, 200); + } + list ($item->thumb_width, $item->thumb_height) = array(200, 200); + } + $item->save(); + throw $e; + } + } + + private static function _update_item_dimensions($item) { + if ($item->is_photo()) { + list ($item->resize_width, $item->resize_height) = + photo::get_file_metadata($item->resize_path()); + } + list ($item->thumb_width, $item->thumb_height) = + photo::get_file_metadata($item->thumb_path()); + } + + private static function _replace_image_with_placeholder($item, $target) { + if ($item->is_album() && !$item->album_cover_item_id) { + $input_path = MODPATH . "gallery/images/missing_album_cover.jpg"; + } else if ($item->is_movie() || ($item->is_album() && $item->album_cover()->is_movie())) { + $input_path = MODPATH . "gallery/images/missing_movie.jpg"; + } else { + $input_path = MODPATH . "gallery/images/missing_photo.jpg"; + } + + if ($target == "thumb") { + $output_path = $item->thumb_path(); + $size = module::get_var("gallery", "thumb_size", 200); + } else { + $output_path = $item->resize_path(); + $size = module::get_var("gallery", "resize_size", 640); + } + $options = array("width" => $size, "height" => $size, "master" => Image::AUTO); + + try { + // Copy/convert/resize placeholder as needed. + gallery_graphics::resize($input_path, $output_path, $options, null); + } catch (Exception $e) { + // Copy/convert/resize didn't work. Add to the log and copy the jpg version (which could have + // a non-jpg extension). This is less than ideal, but it's better than putting nothing + // there and causing theme views to act strangely because a file is missing. + // @todo we should handle this better. + Kohana_Log::add("error", "Caught exception converting placeholder for missing image: " . + $item->title . "\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); + copy($input_path, $output_path); + } + + if (!file_exists($output_path)) { + // Copy/convert/resize didn't throw an exception, but still didn't work - do the same as above. + // @todo we should handle this better. + Kohana_Log::add("error", "Failed to convert placeholder for missing image: $item->title"); + copy($input_path, $output_path); + } + } + + private static function _get_rules($target) { + if (empty(self::$_rules_cache[$target])) { + $rules = array(); + foreach (ORM::factory("graphics_rule") + ->where("target", "=", $target) + ->where("active", "=", true) + ->order_by("priority", "asc") + ->find_all() as $rule) { + $rules[] = (object)$rule->as_array(); + } + self::$_rules_cache[$target] = $rules; + } + return self::$_rules_cache[$target]; + } + + /** + * Return a query result that locates all items with dirty images. + * @return Database_Result Query result + */ + static function find_dirty_images_query() { + return db::build() + ->from("items") + ->and_open() + ->where("thumb_dirty", "=", 1) + ->and_open() + ->where("type", "<>", "album") + ->or_where("album_cover_item_id", "IS NOT", null) + ->close() + ->or_open() + ->where("resize_dirty", "=", 1) + ->where("type", "=", "photo") + ->close() + ->close(); + } + + /** + * Mark thumbnails and resizes as dirty. They will have to be rebuilt. Optionally, only those of + * a specified type and/or mime type can be marked (e.g. $type="movie" to rebuild movies only). + */ + static function mark_dirty($thumbs, $resizes, $type=null, $mime_type=null) { + if ($thumbs || $resizes) { + $db = db::build() + ->update("items"); + if ($type) { + $db->where("type", "=", $type); + } + if ($mime_type) { + $db->where("mime_type", "=", $mime_type); + } + if ($thumbs) { + $db->set("thumb_dirty", 1); + } + if ($resizes) { + $db->set("resize_dirty", 1); + } + $db->execute(); + } + + $count = graphics::find_dirty_images_query()->count_records(); + if ($count) { + site_status::warning( + t2("One of your photos is out of date. Click here to fix it", + "%count of your photos are out of date. Click here to fix them", + $count, + array("attrs" => html::mark_clean(sprintf( + 'href="%s" class="g-dialog-link"', + url::site("admin/maintenance/start/gallery_task::rebuild_dirty_images?csrf=__CSRF__"))))), + "graphics_dirty"); + } + } + + /** + * Detect which graphics toolkits are available on this system. Return an array of key value + * pairs where the key is one of gd, imagemagick, graphicsmagick and the value is information + * about that toolkit. For GD we return the version string, and for ImageMagick and + * GraphicsMagick we return the path to the directory containing the appropriate binaries. + */ + static function detect_toolkits() { + $toolkits = new stdClass(); + $toolkits->gd = new stdClass(); + $toolkits->imagemagick = new stdClass(); + $toolkits->graphicsmagick = new stdClass(); + + // GD is special, it doesn't use exec() + $gd = function_exists("gd_info") ? gd_info() : array(); + $toolkits->gd->name = "GD"; + if (!isset($gd["GD Version"])) { + $toolkits->gd->installed = false; + $toolkits->gd->error = t("GD is not installed"); + } else { + $toolkits->gd->installed = true; + $toolkits->gd->version = $gd["GD Version"]; + $toolkits->gd->rotate = function_exists("imagerotate"); + $toolkits->gd->sharpen = function_exists("imageconvolution"); + $toolkits->gd->binary = ""; + $toolkits->gd->dir = ""; + + if (!$toolkits->gd->rotate && !$toolkits->gd->sharpen) { + $toolkits->gd->error = + t("You have GD version %version, but it lacks image rotation and sharpening.", + array("version" => $gd["GD Version"])); + } else if (!$toolkits->gd->rotate) { + $toolkits->gd->error = + t("You have GD version %version, but it lacks image rotation.", + array("version" => $gd["GD Version"])); + } else if (!$toolkits->gd->sharpen) { + $toolkits->gd->error = + t("You have GD version %version, but it lacks image sharpening.", + array("version" => $gd["GD Version"])); + } + } + + if (!function_exists("exec")) { + $toolkits->imagemagick->installed = false; + $toolkits->imagemagick->error = t("ImageMagick requires the exec function"); + + $toolkits->graphicsmagick->installed = false; + $toolkits->graphicsmagick->error = t("GraphicsMagick requires the exec function"); + } else { + // ImageMagick & GraphicsMagick + $magick_kits = array( + "imagemagick" => array( + "name" => "ImageMagick", "binary" => "convert", "version_arg" => "-version", + "version_regex" => "/Version: \S+ (\S+)/"), + "graphicsmagick" => array( + "name" => "GraphicsMagick", "binary" => "gm", "version_arg" => "version", + "version_regex" => "/\S+ (\S+)/")); + // Loop through the kits + foreach ($magick_kits as $index => $settings) { + $path = system::find_binary( + $settings["binary"], module::get_var("gallery", "graphics_toolkit_path")); + $toolkits->$index->name = $settings["name"]; + if ($path) { + if (@is_file($path) && + preg_match( + $settings["version_regex"], shell_exec($path . " " . $settings["version_arg"]), $matches)) { + $version = $matches[1]; + + $toolkits->$index->installed = true; + $toolkits->$index->version = $version; + $toolkits->$index->binary = $path; + $toolkits->$index->dir = dirname($path); + $toolkits->$index->rotate = true; + $toolkits->$index->sharpen = true; + } else { + $toolkits->$index->installed = false; + $toolkits->$index->error = + t("%toolkit_name is installed, but PHP's open_basedir restriction prevents Gallery from using it.", + array("toolkit_name" => $settings["name"])); + } + } else { + $toolkits->$index->installed = false; + $toolkits->$index->error = + t("We could not locate %toolkit_name on your system.", + array("toolkit_name" => $settings["name"])); + } + } + } + + return $toolkits; + } + + /** + * This needs to be run once, after the initial install, to choose a graphics toolkit. + */ + static function choose_default_toolkit() { + // Detect a graphics toolkit + $toolkits = graphics::detect_toolkits(); + foreach (array("imagemagick", "graphicsmagick", "gd") as $tk) { + if ($toolkits->$tk->installed) { + module::set_var("gallery", "graphics_toolkit", $tk); + module::set_var("gallery", "graphics_toolkit_path", $toolkits->$tk->dir); + break; + } + } + + if (!module::get_var("gallery", "graphics_toolkit")) { + site_status::warning( + t("Graphics toolkit missing! Please choose a toolkit", + array("url" => html::mark_clean(url::site("admin/graphics")))), + "missing_graphics_toolkit"); + } + } + + /** + * Choose which driver the Kohana Image library uses. + */ + static function init_toolkit() { + if (self::$init) { + return; + } + switch(module::get_var("gallery", "graphics_toolkit")) { + case "gd": + Kohana_Config::instance()->set("image.driver", "GD"); + break; + + case "imagemagick": + Kohana_Config::instance()->set("image.driver", "ImageMagick"); + Kohana_Config::instance()->set( + "image.params.directory", module::get_var("gallery", "graphics_toolkit_path")); + break; + + case "graphicsmagick": + Kohana_Config::instance()->set("image.driver", "GraphicsMagick"); + Kohana_Config::instance()->set( + "image.params.directory", module::get_var("gallery", "graphics_toolkit_path")); + break; + } + + self::$init = 1; + } + + /** + * Verify that a specific graphics function is available with the active toolkit. + * @param string $func (eg rotate, sharpen) + * @return boolean + */ + static function can($func) { + if (module::get_var("gallery", "graphics_toolkit") == "gd") { + switch ($func) { + case "rotate": + return function_exists("imagerotate"); + + case "sharpen": + return function_exists("imageconvolution"); + } + } + + return true; + } + + /** + * Return the max file size that this graphics toolkit can handle. + */ + static function max_filesize() { + if (module::get_var("gallery", "graphics_toolkit") == "gd") { + $memory_limit = trim(ini_get("memory_limit")); + $memory_limit_bytes = num::convert_to_bytes($memory_limit); + + // GD expands images in memory and uses 4 bytes of RAM for every byte + // in the file. + $max_filesize = $memory_limit_bytes / 4; + $max_filesize_human_readable = num::convert_to_human_readable($max_filesize); + return array($max_filesize, $max_filesize_human_readable); + } + + // Some arbitrarily large size + return array(1000000000, "1G"); + } +} diff --git a/modules/gallery/helpers/identity.php b/modules/gallery/helpers/identity.php new file mode 100644 index 0000000..5fc0f2f --- /dev/null +++ b/modules/gallery/helpers/identity.php @@ -0,0 +1,247 @@ + $module) { + if (file_exists(MODPATH . "{$module_name}/config/identity.php")) { + $drivers->$module_name = $module->description; + } + } + self::$available = $drivers; + } + return self::$available; + } + + /** + * Frees the current instance of the identity provider so the next call to instance will reload + * + * @param string configuration + * @return Identity_Core + */ + static function reset() { + IdentityProvider::reset(); + } + + /** + * Make sure that we have a session and group_ids cached in the session. + */ + static function load_user() { + try { + // Call IdentityProvider::instance() now to force the load of the user interface classes. + // We are about to load the active user from the session and which needs the user definition + // class, which can't be reached by Kohana's heiracrchical lookup. + IdentityProvider::instance(); + + $session = Session::instance(); + if (!($user = $session->get("user"))) { + identity::set_active_user($user = identity::guest()); + } + + // The installer cannot set a user into the session, so it just sets an id which we should + // upconvert into a user. + // @todo set the user name into the session instead of 2 and then use it to get the + // user object + if ($user === 2) { + $session->delete("user"); // delete it so that identity code isn't confused by the integer + auth::login(IdentityProvider::instance()->admin_user()); + } + + // Cache the group ids for a day to trade off performance for security updates. + if (!$session->get("group_ids") || $session->get("group_ids_timeout", 0) < time()) { + $ids = array(); + foreach ($user->groups() as $group) { + $ids[] = $group->id; + } + $session->set("group_ids", $ids); + $session->set("group_ids_timeout", time() + 86400); + } + } catch (Exception $e) { + // Log it, so we at least have so notification that we swallowed the exception. + Kohana_Log::add("error", "load_user Exception: " . + $e->getMessage() . "\n" . $e->getTraceAsString()); + try { + Session::instance()->destroy(); + } catch (Exception $e) { + // We don't care if there was a problem destroying the session. + } + url::redirect(item::root()->abs_url()); + } + } + + /** + * Return the array of group ids this user belongs to + * + * @return array + */ + static function group_ids_for_active_user() { + return Session::instance()->get("group_ids", array(1)); + } + + /** + * Return the active user. If there's no active user, return the guest user. + * + * @return User_Definition + */ + static function active_user() { + // @todo (maybe) cache this object so we're not always doing session lookups. + $user = Session::instance()->get("user", null); + if (!isset($user)) { + // Don't do this as a fallback in the Session::get() call because it can trigger unnecessary + // work. + $user = identity::guest(); + } + return $user; + } + + /** + * Change the active user. + * @param User_Definition $user + */ + static function set_active_user($user) { + $session = Session::instance(); + $session->set("user", $user); + $session->delete("group_ids"); + identity::load_user(); + } + + /** + * Determine if if the current driver supports updates. + * + * @return boolean true if the driver supports updates; false if read only + */ + static function is_writable() { + return IdentityProvider::instance()->is_writable(); + } + + /** + * @see IdentityProvider_Driver::guest. + */ + static function guest() { + return IdentityProvider::instance()->guest(); + } + + /** + * @see IdentityProvider_Driver::admin_user. + */ + static function admin_user() { + return IdentityProvider::instance()->admin_user(); + } + + /** + * @see IdentityProvider_Driver::create_user. + */ + static function create_user($name, $full_name, $password, $email) { + return IdentityProvider::instance()->create_user($name, $full_name, $password, $email); + } + + /** + * @see IdentityProvider_Driver::is_correct_password. + */ + static function is_correct_password($user, $password) { + return IdentityProvider::instance()->is_correct_password($user, $password); + } + + /** + * @see IdentityProvider_Driver::lookup_user. + */ + static function lookup_user($id) { + return IdentityProvider::instance()->lookup_user($id); + } + + /** + * @see IdentityProvider_Driver::lookup_user_by_name. + */ + static function lookup_user_by_name($name) { + return IdentityProvider::instance()->lookup_user_by_name($name); + } + + /** + * @see IdentityProvider_Driver::create_group. + */ + static function create_group($name) { + return IdentityProvider::instance()->create_group($name); + } + + /** + * @see IdentityProvider_Driver::everybody. + */ + static function everybody() { + return IdentityProvider::instance()->everybody(); + } + + /** + * @see IdentityProvider_Driver::registered_users. + */ + static function registered_users() { + return IdentityProvider::instance()->registered_users(); + } + + /** + * @see IdentityProvider_Driver::lookup_group. + */ + static function lookup_group($id) { + return IdentityProvider::instance()->lookup_group($id); + } + + /** + * @see IdentityProvider_Driver::lookup_group_by_name. + */ + static function lookup_group_by_name($name) { + return IdentityProvider::instance()->lookup_group_by_name($name); + } + + /** + * @see IdentityProvider_Driver::get_user_list. + */ + static function get_user_list($ids) { + return IdentityProvider::instance()->get_user_list($ids); + } + + /** + * @see IdentityProvider_Driver::groups. + */ + static function groups() { + return IdentityProvider::instance()->groups(); + } + + /** + * @see IdentityProvider_Driver::add_user_to_group. + */ + static function add_user_to_group($user, $group) { + return IdentityProvider::instance()->add_user_to_group($user, $group); + } + + /** + * @see IdentityProvider_Driver::remove_user_to_group. + */ + static function remove_user_from_group($user, $group) { + return IdentityProvider::instance()->remove_user_from_group($user, $group); + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php new file mode 100644 index 0000000..9882a9c --- /dev/null +++ b/modules/gallery/helpers/item.php @@ -0,0 +1,433 @@ +parent(); + if ($parent->album_cover_item_id == $source->id) { + if ($parent->children_count() > 1) { + foreach ($parent->children(2) as $child) { + if ($child->id != $source->id) { + $new_cover_item = $child; + break; + } + } + item::make_album_cover($new_cover_item); + } else { + item::remove_album_cover($parent); + } + } + + $orig_name = $source->name; + $source->parent_id = $target->id; + $source->save(); + if ($orig_name != $source->name) { + switch ($source->type) { + case "album": + message::info( + t("Album %old_name renamed to %new_name to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; + + case "photo": + message::info( + t("Photo %old_name renamed to %new_name to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; + + case "movie": + message::info( + t("Movie %old_name renamed to %new_name to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; + } + } + + // If the target has no cover item, make this it. + if ($target->album_cover_item_id == null) { + item::make_album_cover($source); + } + } + + static function make_album_cover($item) { + $parent = $item->parent(); + access::required("view", $item); + access::required("view", $parent); + access::required("edit", $parent); + + $old_album_cover_id = $parent->album_cover_item_id; + + model_cache::clear(); + $parent->album_cover_item_id = $item->is_album() ? $item->album_cover_item_id : $item->id; + $parent->save(); + graphics::generate($parent); + + // Walk up the parent hierarchy and set album covers if necessary + $grand_parent = $parent->parent(); + if ($grand_parent && access::can("edit", $grand_parent) && + $grand_parent->album_cover_item_id == null) { + item::make_album_cover($parent); + } + + // When albums are album covers themselves, we hotlink directly to the target item. This + // means that when we change an album cover, the grandparent may have a deep link to the old + // album cover. So find any parent albums that had the old item as their album cover and + // switch them over to the new item. + if ($old_album_cover_id) { + foreach ($item->parents(array(array("album_cover_item_id", "=", $old_album_cover_id))) + as $ancestor) { + if (access::can("edit", $ancestor)) { + $ancestor->album_cover_item_id = $parent->album_cover_item_id; + $ancestor->save(); + graphics::generate($ancestor); + } + } + } + } + + static function remove_album_cover($album) { + access::required("view", $album); + access::required("edit", $album); + + model_cache::clear(); + $album->album_cover_item_id = null; + $album->save(); + graphics::generate($album); + } + + /** + * Sanitize a filename into something presentable as an item title + * @param string $filename + * @return string title + */ + static function convert_filename_to_title($filename) { + $title = strtr($filename, "_", " "); + $title = preg_replace("/\..{3,4}$/", "", $title); + $title = preg_replace("/ +/", " ", $title); + return $title; + } + + /** + * Convert a filename into something we can use as a url component. + * @param string $filename + */ + static function convert_filename_to_slug($filename) { + $result = str_replace("&", "-and-", $filename); + $result = str_replace(" ", "-", $result); + + // It's not easy to extend the text helper since it's called by the Input class which is + // referenced in hooks/init_gallery, so it's + if (class_exists("transliterate")) { + $result = transliterate::utf8_to_ascii($result); + } else { + $result = text::transliterate_to_ascii($result); + } + $result = preg_replace("/[^A-Za-z0-9-_]+/", "-", $result); + $result = preg_replace("/-+/", "-", $result); + return trim($result, "-"); + } + + /** + * Display delete confirmation message and form + * @param object $item + * @return string form + */ + static function get_delete_form($item) { + $page_type = Input::instance()->get("page_type"); + $from_id = Input::instance()->get("from_id"); + $form = new Forge( + "quick/delete/$item->id?page_type=$page_type&from_id=$from_id", "", + "post", array("id" => "g-confirm-delete")); + $group = $form->group("confirm_delete")->label(t("Confirm Deletion")); + $group->submit("")->value(t("Delete")); + $form->script("") + ->url(url::abs_file("modules/gallery/js/item_form_delete.js")); + return $form; + } + + /** + * Get the next weight value + */ + static function get_max_weight() { + // Guard against an empty result when we create the first item. It's unfortunate that we + // have to check this every time. + // @todo: figure out a better way to bootstrap the weight. + $result = db::build() + ->select("weight")->from("items") + ->order_by("weight", "desc")->limit(1) + ->execute()->current(); + return ($result ? $result->weight : 0) + 1; + } + + /** + * Add a set of restrictions to any following queries to restrict access only to items + * viewable by the active user. + * @chainable + */ + static function viewable($model) { + $view_restrictions = array(); + if (!identity::active_user()->admin) { + foreach (identity::group_ids_for_active_user() as $id) { + $view_restrictions[] = array("items.view_$id", "=", access::ALLOW); + } + } + + if (count($view_restrictions)) { + $model->and_open()->merge_or_where($view_restrictions)->close(); + } + + return $model; + } + + /** + * Find an item by its path. If there's no match, return an empty Item_Model. + * NOTE: the caller is responsible for performing security checks on the resulting item. + * @param string $path + * @return object Item_Model + */ + static function find_by_path($path) { + $path = trim($path, "/"); + + // The root path name is NULL not "", hence this workaround. + if ($path == "") { + return item::root(); + } + + // Check to see if there's an item in the database with a matching relative_path_cache value. + // Since that field is urlencoded, we must urlencoded the components of the path. + foreach (explode("/", $path) as $part) { + $encoded_array[] = rawurlencode($part); + } + $encoded_path = join("/", $encoded_array); + $item = ORM::factory("item") + ->where("relative_path_cache", "=", $encoded_path) + ->find(); + if ($item->loaded()) { + return $item; + } + + // Since the relative_path_cache field is a cache, it can be unavailable. If we don't find + // anything, fall back to checking the path the hard way. + $paths = explode("/", $path); + foreach (ORM::factory("item") + ->where("name", "=", end($paths)) + ->where("level", "=", count($paths) + 1) + ->find_all() as $item) { + if (urldecode($item->relative_path()) == $path) { + return $item; + } + } + + return new Item_Model(); + } + + + /** + * Locate an item using the URL. We assume that the url is in the form /a/b/c where each + * component matches up with an item slug. If there's no match, return an empty Item_Model + * NOTE: the caller is responsible for performing security checks on the resulting item. + * @param string $url the relative url fragment + * @return Item_Model + */ + static function find_by_relative_url($relative_url) { + // In most cases, we'll have an exact match in the relative_url_cache item field. + // but failing that, walk down the tree until we find it. The fallback code will fix caches + // as it goes, so it'll never be run frequently. + $item = ORM::factory("item")->where("relative_url_cache", "=", $relative_url)->find(); + if (!$item->loaded()) { + $segments = explode("/", $relative_url); + foreach (ORM::factory("item") + ->where("slug", "=", end($segments)) + ->where("level", "=", count($segments) + 1) + ->find_all() as $match) { + if ($match->relative_url() == $relative_url) { + $item = $match; + } + } + } + return $item; + } + + /** + * Return the root Item_Model + * @return Item_Model + */ + static function root() { + return model_cache::get("item", 1); + } + + /** + * Return a query to get a random Item_Model, with optional filters. + * Usage: item::random_query()->execute(); + * + * Note: You can add your own ->where() clauses but if your Gallery is + * small or your where clauses are over-constrained you may wind up with + * no item. You should try running this a few times in a loop if you + * don't get an item back. + */ + static function random_query() { + // Pick a random number and find the item that's got nearest smaller number. + // This approach works best when the random numbers in the system are roughly evenly + // distributed so this is going to be more efficient with larger data sets. + return ORM::factory("item") + ->viewable() + ->where("rand_key", "<", random::percent()) + ->order_by("rand_key", "DESC"); + } + + /** + * Find the position of the given item in its parent album. The resulting + * value is 1-indexed, so the first child in the album is at position 1. + * + * @param Item_Model $item + * @param array $where an array of arrays, each compatible with ORM::where() + */ + static function get_position($item, $where=array()) { + $album = $item->parent(); + + if (!strcasecmp($album->sort_order, "DESC")) { + $comp = ">"; + } else { + $comp = "<"; + } + $query_model = ORM::factory("item"); + + // If the comparison column has NULLs in it, we can't use comparators on it + // and will have to deal with it the hard way. + $count = $query_model->viewable() + ->where("parent_id", "=", $album->id) + ->where($album->sort_column, "IS", null) + ->merge_where($where) + ->count_all(); + + if (empty($count)) { + // There are no NULLs in the sort column, so we can just use it directly. + $sort_column = $album->sort_column; + + $position = $query_model->viewable() + ->where("parent_id", "=", $album->id) + ->where($sort_column, $comp, $item->$sort_column) + ->merge_where($where) + ->count_all(); + + // We stopped short of our target value in the sort (notice that we're + // using a inequality comparator above) because it's possible that we have + // duplicate values in the sort column. An equality check would just + // arbitrarily pick one of those multiple possible equivalent columns, + // which would mean that if you choose a sort order that has duplicates, + // it'd pick any one of them as the child's "position". + // + // Fix this by doing a 2nd query where we iterate over the equivalent + // columns and add them to our position count. + foreach ($query_model->viewable() + ->select("id") + ->where("parent_id", "=", $album->id) + ->where($sort_column, "=", $item->$sort_column) + ->merge_where($where) + ->order_by(array("id" => "ASC")) + ->find_all() as $row) { + $position++; + if ($row->id == $item->id) { + break; + } + } + } else { + // There are NULLs in the sort column, so we can't use MySQL comparators. + // Fall back to iterating over every child row to get to the current one. + // This can be wildly inefficient for really large albums, but it should + // be a rare case that the user is sorting an album with null values in + // the sort column. + // + // Reproduce the children() functionality here using Database directly to + // avoid loading the whole ORM for each row. + $order_by = array($album->sort_column => $album->sort_order); + // Use id as a tie breaker + if ($album->sort_column != "id") { + $order_by["id"] = "ASC"; + } + + $position = 0; + foreach ($query_model->viewable() + ->select("id") + ->where("parent_id", "=", $album->id) + ->merge_where($where) + ->order_by($order_by) + ->find_all() as $row) { + $position++; + if ($row->id == $item->id) { + break; + } + } + } + + return $position; + } + + /** + * Set the display context callback for any future item renders. + */ + static function set_display_context_callback() { + if (!request::user_agent("robot")) { + $args = func_get_args(); + Cache::instance()->set("display_context_" . $sid = Session::instance()->id(), $args, + array("display_context")); + } + } + + /** + * Get rid of the display context callback + */ + static function clear_display_context_callback() { + Cache::instance()->delete("display_context_" . $sid = Session::instance()->id()); + } + + /** + * Call the display context callback for the given item + */ + static function get_display_context($item) { + if (!request::user_agent("robot")) { + $args = Cache::instance()->get("display_context_" . $sid = Session::instance()->id()); + $callback = $args[0]; + $args[0] = $item; + } + + if (empty($callback)) { + $callback = "Albums_Controller::get_display_context"; + $args = array($item); + } + return call_user_func_array($callback, $args); + } + + /** + * Reset all child weights of a given album to a monotonically increasing sequence based on the + * current sort order of the album. + */ + static function resequence_child_weights($album) { + $weight = 0; + foreach ($album->children() as $child) { + $child->weight = ++$weight; + $child->save(); + } + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/item_rest.php b/modules/gallery/helpers/item_rest.php new file mode 100644 index 0000000..efeba2e --- /dev/null +++ b/modules/gallery/helpers/item_rest.php @@ -0,0 +1,210 @@ + + * Only return items where the name contains this substring + * + * random=true + * Return a single random item + * + * type= + * Limit the type to types in this list, eg: "type=photo,movie". + * Also limits the types returned in the member collections (same behaviour as item_rest). + */ + static function get($request) { + $item = rest::resolve($request->url); + access::required("view", $item); + + $p = $request->params; + if (isset($p->random)) { + $orm = item::random_query()->offset(0)->limit(1); + } else { + $orm = ORM::factory("item")->viewable(); + } + + if (empty($p->scope)) { + $p->scope = "direct"; + } + + if (!in_array($p->scope, array("direct", "all"))) { + throw new Rest_Exception("Bad Request", 400); + } + + if ($p->scope == "direct") { + $orm->where("parent_id", "=", $item->id); + } else { + $orm->where("left_ptr", ">", $item->left_ptr); + $orm->where("right_ptr", "<", $item->right_ptr); + } + + if (isset($p->name)) { + $orm->where("name", "LIKE", "%" . Database::escape_for_like($p->name) . "%"); + } + + if (isset($p->type)) { + $orm->where("type", "IN", explode(",", $p->type)); + } + + // Apply the item's sort order, using id as the tie breaker. + // See Item_Model::children() + $order_by = array($item->sort_column => $item->sort_order); + if ($item->sort_column != "id") { + $order_by["id"] = "ASC"; + } + $orm->order_by($order_by); + + $result = array( + "url" => $request->url, + "entity" => $item->as_restful_array(), + "relationships" => rest::relationships("item", $item)); + if ($item->is_album()) { + $result["members"] = array(); + foreach ($orm->find_all() as $child) { + $result["members"][] = rest::url("item", $child); + } + } + + return $result; + } + + static function put($request) { + $item = rest::resolve($request->url); + access::required("edit", $item); + + if ($entity = $request->params->entity) { + // Only change fields from a whitelist. + foreach (array("album_cover", "captured", "description", + "height", "mime_type", "name", "parent", "rand_key", "resize_dirty", + "resize_height", "resize_width", "slug", "sort_column", "sort_order", + "thumb_dirty", "thumb_height", "thumb_width", "title", "view_count", + "width") as $key) { + switch ($key) { + case "album_cover": + if (property_exists($entity, "album_cover")) { + $album_cover_item = rest::resolve($entity->album_cover); + access::required("view", $album_cover_item); + $item->album_cover_item_id = $album_cover_item->id; + } + break; + + case "parent": + if (property_exists($entity, "parent")) { + $parent = rest::resolve($entity->parent); + access::required("edit", $parent); + $item->parent_id = $parent->id; + } + break; + default: + if (property_exists($entity, $key)) { + $item->$key = $entity->$key; + } + } + } + } + + // Replace the data file, if required + if (($item->is_photo() || $item->is_movie()) && isset($request->file)) { + $item->set_data_file($request->file); + } + + $item->save(); + + if (isset($request->params->members) && $item->sort_column == "weight") { + $weight = 0; + foreach ($request->params->members as $url) { + $child = rest::resolve($url); + if ($child->parent_id == $item->id && $child->weight != $weight) { + $child->weight = $weight; + $child->save(); + } + $weight++; + } + } + } + + static function post($request) { + $parent = rest::resolve($request->url); + access::required("add", $parent); + + $entity = $request->params->entity; + $item = ORM::factory("item"); + switch ($entity->type) { + case "album": + $item->type = "album"; + $item->parent_id = $parent->id; + $item->name = $entity->name; + $item->title = isset($entity->title) ? $entity->title : $entity->name; + $item->description = isset($entity->description) ? $entity->description : null; + $item->slug = isset($entity->slug) ? $entity->slug : null; + $item->save(); + break; + + case "photo": + case "movie": + if (empty($request->file)) { + throw new Rest_Exception( + "Bad Request", 400, array("errors" => array("file" => t("Upload failed")))); + } + $item->type = $entity->type; + $item->parent_id = $parent->id; + $item->set_data_file($request->file); + $item->name = $entity->name; + $item->title = isset($entity->title) ? $entity->title : $entity->name; + $item->description = isset($entity->description) ? $entity->description : null; + $item->slug = isset($entity->slug) ? $entity->slug : null; + $item->save(); + break; + + default: + throw new Rest_Exception( + "Bad Request", 400, array("errors" => array("type" => "invalid"))); + } + + return array("url" => rest::url("item", $item)); + } + + static function delete($request) { + $item = rest::resolve($request->url); + access::required("edit", $item); + + $item->delete(); + } + + static function resolve($id) { + $item = ORM::factory("item", $id); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + return $item; + } + + static function url($item) { + return url::abs_site("rest/item/{$item->id}"); + } +} diff --git a/modules/gallery/helpers/items_rest.php b/modules/gallery/helpers/items_rest.php new file mode 100644 index 0000000..7622c53 --- /dev/null +++ b/modules/gallery/helpers/items_rest.php @@ -0,0 +1,96 @@ + + * Limit the type to types in this list, eg: "type=photo,movie". + * Also limits the types returned in the member collections (same behaviour as item_rest). + * Ignored if ancestors_for is set. + */ + static function get($request) { + $items = array(); + $types = array(); + + if (isset($request->params->urls)) { + if (isset($request->params->type)) { + $types = explode(",", $request->params->type); + } + + foreach (json_decode($request->params->urls) as $url) { + $item = rest::resolve($url); + if (!access::can("view", $item)) { + continue; + } + + if (empty($types) || in_array($item->type, $types)) { + $items[] = items_rest::_format_restful_item($item, $types); + } + } + } else if (isset($request->params->ancestors_for)) { + $item = rest::resolve($request->params->ancestors_for); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + $items[] = items_rest::_format_restful_item($item, $types); + while (($item = $item->parent()) != null) { + array_unshift($items, items_rest::_format_restful_item($item, $types)); + }; + } + + return $items; + } + + static function resolve($id) { + $item = ORM::factory("item", $id); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + return $item; + } + + private static function _format_restful_item($item, $types) { + $item_rest = array("url" => rest::url("item", $item), + "entity" => $item->as_restful_array(), + "relationships" => rest::relationships("item", $item)); + if ($item->type == "album") { + $members = array(); + foreach ($item->viewable()->children() as $child) { + if (empty($types) || in_array($child->type, $types)) { + $members[] = rest::url("item", $child); + } + } + $item_rest["members"] = $members; + } + + return $item_rest; + } +} diff --git a/modules/gallery/helpers/json.php b/modules/gallery/helpers/json.php new file mode 100644 index 0000000..b56f269 --- /dev/null +++ b/modules/gallery/helpers/json.php @@ -0,0 +1,31 @@ + $version, + "client_token" => l10n_client::client_token(), + "signature" => $signature, + "uid" => l10n_client::server_uid($api_key))); + } catch (ErrorException $e) { + // Log the error, but then return a "can't make connection" error + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + if (!isset($response_data) && !isset($response_status)) { + return array(false, false); + } + + if (!remote::success($response_status)) { + return array(true, false); + } + return array(true, true); + } + + /** + * Fetches translations for l10n messages. Must be called repeatedly + * until 0 is returned (which is a countdown indicating progress). + * + * @param $num_fetched in/out parameter to specify which batch of + * messages to fetch translations for. + * @return The number of messages for which we didn't fetch + * translations for. + */ + static function fetch_updates(&$num_fetched) { + $request = new stdClass(); + $request->locales = array(); + $request->messages = new stdClass(); + + $locales = locales::installed(); + foreach ($locales as $locale => $locale_data) { + $request->locales[] = $locale; + } + + // See the server side code for how we arrive at this + // number as a good limit for #locales * #messages. + $max_messages = 2000 / count($locales); + $num_messages = 0; + $rows = db::build() + ->select("key", "locale", "revision", "translation") + ->from("incoming_translations") + ->order_by("key") + ->limit(1000000) // ignore, just there to satisfy SQL syntax + ->offset($num_fetched) + ->execute(); + $num_remaining = $rows->count(); + foreach ($rows as $row) { + if (!isset($request->messages->{$row->key})) { + if ($num_messages >= $max_messages) { + break; + } + $request->messages->{$row->key} = 1; + $num_messages++; + } + if (!empty($row->revision) && !empty($row->translation) && + isset($locales[$row->locale])) { + if (!is_object($request->messages->{$row->key})) { + $request->messages->{$row->key} = new stdClass(); + } + $request->messages->{$row->key}->{$row->locale} = (int) $row->revision; + } + $num_fetched++; + $num_remaining--; + } + // @todo Include messages from outgoing_translations? + + if (!$num_messages) { + return $num_remaining; + } + + $request_data = json_encode($request); + $url = self::_server_url("fetch"); + list ($response_data, $response_status) = remote::post($url, array("data" => $request_data)); + if (!remote::success($response_status)) { + throw new Exception("@todo TRANSLATIONS_FETCH_REQUEST_FAILED " . $response_status); + } + if (empty($response_data)) { + return $num_remaining; + } + + $response = json_decode($response_data); + + // Response format (JSON payload): + // [{key:, translation: , rev:, locale:}, + // {key:, ...} + // ] + foreach ($response as $message_data) { + // @todo Better input validation + if (empty($message_data->key) || empty($message_data->translation) || + empty($message_data->locale) || empty($message_data->rev)) { + throw new Exception("@todo TRANSLATIONS_FETCH_REQUEST_FAILED: Invalid response data"); + } + $key = $message_data->key; + $locale = $message_data->locale; + $revision = $message_data->rev; + $translation = json_decode($message_data->translation); + if (!is_string($translation)) { + // Normalize stdclass to array + $translation = (array) $translation; + } + $translation = serialize($translation); + + // @todo Should we normalize the incoming_translations table into messages(id, key, message) + // and incoming_translations(id, translation, locale, revision)? Or just allow + // incoming_translations.message to be NULL? + $locale = $message_data->locale; + $entry = ORM::factory("incoming_translation") + ->where("key", "=", $key) + ->where("locale", "=", $locale) + ->find(); + if (!$entry->loaded()) { + // @todo Load a message key -> message (text) dict into memory outside of this loop + $root_entry = ORM::factory("incoming_translation") + ->where("key", "=", $key) + ->where("locale", "=", "root") + ->find(); + $entry->key = $key; + $entry->message = $root_entry->message; + $entry->locale = $locale; + } + $entry->revision = $revision; + $entry->translation = $translation; + $entry->save(); + } + + return $num_remaining; + } + + static function submit_translations() { + // Request format (HTTP POST): + // client_token = + // uid = + // signature = md5(user_api_key($uid, $client_token) . $data . $client_token)) + // data = // JSON payload + // + // {: {message: + // translations: {: , + // : ...}}, + // : {...} + // } + + // @todo Batch requests (max request size) + // @todo include base_revision in submission / how to handle resubmissions / edit fights? + $request = new stdClass(); + foreach (db::build() + ->select("key", "message", "locale", "base_revision", "translation") + ->from("outgoing_translations") + ->execute() as $row) { + $key = $row->key; + if (!isset($request->{$key})) { + $request->{$key} = new stdClass(); + $request->{$key}->translations = new stdClass(); + $request->{$key}->message = json_encode(unserialize($row->message)); + } + $request->{$key}->translations->{$row->locale} = json_encode(unserialize($row->translation)); + } + + // @todo reduce memory consumption, e.g. free $request + $request_data = json_encode($request); + $url = self::_server_url("submit"); + $signature = self::_sign($request_data); + + list ($response_data, $response_status) = remote::post( + $url, array("data" => $request_data, + "client_token" => l10n_client::client_token(), + "signature" => $signature, + "uid" => l10n_client::server_uid())); + + if (!remote::success($response_status)) { + throw new Exception("@todo TRANSLATIONS_SUBMISSION_FAILED " . $response_status); + } + if (empty($response_data)) { + return; + } + + $response = json_decode($response_data); + // Response format (JSON payload): + // [{key:, locale:, rev:, status:}, + // {key:, ...} + // ] + + // @todo Move messages out of outgoing into incoming, using new rev? + // @todo show which messages have been rejected / are pending? + } + + /** + * Plural forms. + */ + static function plural_forms($locale) { + $parts = explode('_', $locale); + $language = $parts[0]; + + // Data from CLDR 1.6 (http://unicode.org/cldr/data/common/supplemental/plurals.xml). + // Docs: http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html + switch ($language) { + case 'az': + case 'fa': + case 'hu': + case 'ja': + case 'ko': + case 'my': + case 'to': + case 'tr': + case 'vi': + case 'yo': + case 'zh': + case 'bo': + case 'dz': + case 'id': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ms': + case 'th': + return array('other'); + + case 'ar': + return array('zero', 'one', 'two', 'few', 'many', 'other'); + + case 'lv': + return array('zero', 'one', 'other'); + + case 'ga': + case 'se': + case 'sma': + case 'smi': + case 'smj': + case 'smn': + case 'sms': + return array('one', 'two', 'other'); + + case 'ro': + case 'mo': + case 'lt': + case 'cs': + case 'sk': + case 'pl': + return array('one', 'few', 'other'); + + case 'hr': + case 'ru': + case 'sr': + case 'uk': + case 'be': + case 'bs': + case 'sh': + case 'mt': + return array('one', 'few', 'many', 'other'); + + case 'sl': + return array('one', 'two', 'few', 'other'); + + case 'cy': + return array('one', 'two', 'many', 'other'); + + default: // en, de, etc. + return array('one', 'other'); + } + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/l10n_scanner.php b/modules/gallery/helpers/l10n_scanner.php new file mode 100644 index 0000000..5980ebe --- /dev/null +++ b/modules/gallery/helpers/l10n_scanner.php @@ -0,0 +1,178 @@ +select("key") + ->from("incoming_translations") + ->where("locale", "=", "root") + ->execute() as $row) { + $cache[$row->key] = true; + } + } + + $key = Gallery_I18n::get_message_key($message); + if (array_key_exists($key, $cache)) { + return $cache[$key]; + } + + $entry = ORM::factory("incoming_translation")->where("key", "=", $key)->find(); + if (!$entry->loaded()) { + $entry->key = $key; + $entry->message = serialize($message); + $entry->locale = "root"; + $entry->save(); + } + } + + static function scan_php_file($file, &$cache) { + $code = file_get_contents($file); + $raw_tokens = token_get_all($code); + unset($code); + + $tokens = array(); + $func_token_list = array("t" => array(), "t2" => array()); + $token_number = 0; + // Filter out HTML / whitespace, and build a lookup for global function calls. + foreach ($raw_tokens as $token) { + if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) { + if (is_array($token)) { + if ($token[0] == T_STRING && in_array($token[1], array("t", "t2"))) { + $func_token_list[$token[1]][] = $token_number; + } + } + $tokens[] = $token; + $token_number++; + } + } + unset($raw_tokens); + + if (!empty($func_token_list["t"])) { + $errors = l10n_scanner::_parse_t_calls($tokens, $func_token_list["t"], $cache); + foreach ($errors as $line => $error) { + Kohana_Log::add( + "error", "Translation scanner error. " . + "file: " . substr($file, strlen(DOCROOT)) . ", line: $line, context: $error"); + } + } + + if (!empty($func_token_list["t2"])) { + $errors = l10n_scanner::_parse_plural_calls($tokens, $func_token_list["t2"], $cache); + foreach ($errors as $line => $error) { + Kohana_Log::add( + "error", "Translation scanner error. " . + "file: " . substr($file, strlen(DOCROOT)) . ", line: $line, context: $error"); + } + } + } + + static function scan_info_file($file, &$cache) { + $info = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS); + foreach (array('name', 'description') as $property) { + if (isset($info->$property)) { + l10n_scanner::process_message($info->$property, $cache); + } + } + } + + private static function _parse_t_calls(&$tokens, &$call_list, &$cache) { + $errors = array(); + foreach ($call_list as $index) { + $function_name = $tokens[$index++]; + $parens = $tokens[$index++]; + $first_param = $tokens[$index++]; + $next_token = $tokens[$index]; + + if ($parens == "(") { + if (in_array($next_token, array(")", ",")) + && (is_array($first_param) && ($first_param[0] == T_CONSTANT_ENCAPSED_STRING))) { + $message = self::_escape_quoted_string($first_param[1]); + l10n_scanner::process_message($message, $cache); + } else { + if (is_array($first_param) && ($first_param[0] == T_CONSTANT_ENCAPSED_STRING)) { + // Malformed string literals; escalate this + $errors[$first_param[2]] = + var_export(array($function_name, $parens, $first_param, $next_token), 1); + } else { + // t() found, but inside is something which is not a string literal. That's fine. + } + } + } + } + return $errors; + } + + private static function _parse_plural_calls(&$tokens, &$call_list, &$cache) { + $errors = array(); + foreach ($call_list as $index) { + $function_name = $tokens[$index++]; + $parens = $tokens[$index++]; + $first_param = $tokens[$index++]; + $first_separator = $tokens[$index++]; + $second_param = $tokens[$index++]; + $next_token = $tokens[$index]; + + if ($parens == "(") { + if ($first_separator == "," && $next_token == "," + && is_array($first_param) && $first_param[0] == T_CONSTANT_ENCAPSED_STRING + && is_array($second_param) && $second_param[0] == T_CONSTANT_ENCAPSED_STRING) { + $singular = self::_escape_quoted_string($first_param[1]); + $plural = self::_escape_quoted_string($second_param[1]); + l10n_scanner::process_message(array("one" => $singular, "other" => $plural), $cache); + } else { + if (is_array($first_param) && $first_param[0] == T_CONSTANT_ENCAPSED_STRING) { + $errors[$first_param[2]] = var_export( + array($function_name, $parens, $first_param, + $first_separator, $second_param, $next_token), 1); + } else { + // t2() found, but inside is something which is not a string literal. That's fine. + } + } + } + } + return $errors; + } + + /** + * Escape quotes in a strings depending on the surrounding + * quote type used. + * + * @param $str The strings to escape + */ + private static function _escape_quoted_string($str) { + $quo = substr($str, 0, 1); + $str = substr($str, 1, -1); + if ($quo == '"') { + $str = stripcslashes($str); + } else { + $str = strtr($str, array("\\'" => "'", "\\\\" => "\\")); + } + return $str; + } +} diff --git a/modules/gallery/helpers/legal_file.php b/modules/gallery/helpers/legal_file.php new file mode 100644 index 0000000..eb9c25d --- /dev/null +++ b/modules/gallery/helpers/legal_file.php @@ -0,0 +1,310 @@ +types_by_extension = array( + "jpg" => "image/jpeg", "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png"); + module::event("photo_types_by_extension", $types_by_extension_wrapper); + foreach (self::$blacklist as $key) { + unset($types_by_extension_wrapper->types_by_extension[$key]); + } + self::$photo_types_by_extension = $types_by_extension_wrapper->types_by_extension; + } + if ($extension) { + // return matching MIME type + $extension = strtolower($extension); + if (isset(self::$photo_types_by_extension[$extension])) { + return self::$photo_types_by_extension[$extension]; + } else { + return null; + } + } else { + // return complete array + return self::$photo_types_by_extension; + } + } + + /** + * Create a default list of allowed movie MIME types paired with their extensions and then let + * modules modify it. This is an ordered map, mapping extensions to their MIME types. + * Extensions cannot be duplicated, but MIMEs can (e.g. jpeg and jpg both map to image/jpeg). + * + * @param string $extension (opt.) - return MIME of extension; if not given, return complete array + */ + static function get_movie_types_by_extension($extension=null) { + if (empty(self::$movie_types_by_extension)) { + $types_by_extension_wrapper = new stdClass(); + $types_by_extension_wrapper->types_by_extension = array( + "flv" => "video/x-flv", "mp4" => "video/mp4", "m4v" => "video/x-m4v"); + module::event("movie_types_by_extension", $types_by_extension_wrapper); + foreach (self::$blacklist as $key) { + unset($types_by_extension_wrapper->types_by_extension[$key]); + } + self::$movie_types_by_extension = $types_by_extension_wrapper->types_by_extension; + } + if ($extension) { + // return matching MIME type + $extension = strtolower($extension); + if (isset(self::$movie_types_by_extension[$extension])) { + return self::$movie_types_by_extension[$extension]; + } else { + return null; + } + } else { + // return complete array + return self::$movie_types_by_extension; + } + } + + /** + * Create a merged list of all allowed photo and movie MIME types paired with their extensions. + * + * @param string $extension (opt.) - return MIME of extension; if not given, return complete array + */ + static function get_types_by_extension($extension=null) { + $types_by_extension = legal_file::get_photo_types_by_extension(); + if (movie::allow_uploads()) { + $types_by_extension = array_merge($types_by_extension, + legal_file::get_movie_types_by_extension()); + } + if ($extension) { + // return matching MIME type + $extension = strtolower($extension); + if (isset($types_by_extension[$extension])) { + return $types_by_extension[$extension]; + } else { + return null; + } + } else { + // return complete array + return $types_by_extension; + } + } + + /** + * Create a default list of allowed photo extensions and then let modules modify it. + * + * @param string $extension (opt.) - return true if allowed; if not given, return complete array + */ + static function get_photo_extensions($extension=null) { + if (empty(self::$photo_extensions)) { + $extensions_wrapper = new stdClass(); + $extensions_wrapper->extensions = array_keys(legal_file::get_photo_types_by_extension()); + module::event("legal_photo_extensions", $extensions_wrapper); + self::$photo_extensions = array_diff($extensions_wrapper->extensions, self::$blacklist); + } + if ($extension) { + // return true if in array, false if not + return in_array(strtolower($extension), self::$photo_extensions); + } else { + // return complete array + return self::$photo_extensions; + } + } + + /** + * Create a default list of allowed movie extensions and then let modules modify it. + * + * @param string $extension (opt.) - return true if allowed; if not given, return complete array + */ + static function get_movie_extensions($extension=null) { + if (empty(self::$movie_extensions)) { + $extensions_wrapper = new stdClass(); + $extensions_wrapper->extensions = array_keys(legal_file::get_movie_types_by_extension()); + module::event("legal_movie_extensions", $extensions_wrapper); + self::$movie_extensions = array_diff($extensions_wrapper->extensions, self::$blacklist); + } + if ($extension) { + // return true if in array, false if not + return in_array(strtolower($extension), self::$movie_extensions); + } else { + // return complete array + return self::$movie_extensions; + } + } + + /** + * Create a merged list of all allowed photo and movie extensions. + * + * @param string $extension (opt.) - return true if allowed; if not given, return complete array + */ + static function get_extensions($extension=null) { + $extensions = legal_file::get_photo_extensions(); + if (movie::allow_uploads()) { + $extensions = array_merge($extensions, legal_file::get_movie_extensions()); + } + if ($extension) { + // return true if in array, false if not + return in_array(strtolower($extension), $extensions); + } else { + // return complete array + return $extensions; + } + } + + /** + * Create a merged list of all photo and movie filename filters, + * (e.g. "*.gif"), based on allowed extensions. + */ + static function get_filters() { + $filters = array(); + foreach (legal_file::get_extensions() as $extension) { + array_push($filters, "*." . $extension, "*." . strtoupper($extension)); + } + return $filters; + } + + /** + * Create a default list of allowed photo MIME types and then let modules modify it. + * Can be used to add legal alternatives for default MIME types. + * (e.g. flv maps to video/x-flv by default, but video/flv is still legal). + */ + static function get_photo_types() { + if (empty(self::$photo_types)) { + $types_wrapper = new stdClass(); + // Need array_unique since types_by_extension can be many-to-one (e.g. jpeg and jpg). + $types_wrapper->types = array_unique(array_values(legal_file::get_photo_types_by_extension())); + module::event("legal_photo_types", $types_wrapper); + self::$photo_types = $types_wrapper->types; + } + return self::$photo_types; + } + + /** + * Create a default list of allowed movie MIME types and then let modules modify it. + * Can be used to add legal alternatives for default MIME types. + * (e.g. flv maps to video/x-flv by default, but video/flv is still legal). + */ + static function get_movie_types() { + if (empty(self::$movie_types)) { + $types_wrapper = new stdClass(); + // Need array_unique since types_by_extension can be many-to-one (e.g. jpeg and jpg). + $types_wrapper->types = array_unique(array_values(legal_file::get_movie_types_by_extension())); + $types_wrapper->types[] = "video/flv"; + module::event("legal_movie_types", $types_wrapper); + self::$movie_types = $types_wrapper->types; + } + return self::$movie_types; + } + + /** + * Change the extension of a filename. If the original filename has no + * extension, add the new one to the end. + */ + static function change_extension($filename, $new_ext) { + $filename_no_ext = preg_replace("/\.[^\.\/]*?$/", "", $filename); + return "{$filename_no_ext}.{$new_ext}"; + } + + /** + * Reduce the given file to having a single extension. + */ + static function smash_extensions($filename) { + if (!$filename) { + // It's harmless, so return it before it causes issues with pathinfo. + return $filename; + } + $parts = pathinfo($filename); + $result = ""; + if ($parts["dirname"] != ".") { + $result .= $parts["dirname"] . "/"; + } + $parts["filename"] = str_replace(".", "_", $parts["filename"]); + $parts["filename"] = preg_replace("/[_]+/", "_", $parts["filename"]); + $parts["filename"] = trim($parts["filename"], "_"); + $result .= isset($parts["extension"]) ? "{$parts['filename']}.{$parts['extension']}" : $parts["filename"]; + return $result; + } + + /** + * Sanitize a filename for a given type (given as "photo" or "movie") and a target file format + * (given as an extension). This returns a completely legal and valid filename, + * or throws an exception if the type or extension given is invalid or illegal. It tries to + * maintain the filename's original extension even if it's not identical to the given extension + * (e.g. don't change "JPG" or "jpeg" to "jpg"). + * + * Note: it is not okay if the extension given is legal but does not match the type (e.g. if + * extension is "mp4" and type is "photo", it will throw an exception) + * + * @param string $filename (with no directory) + * @param string $extension (can be uppercase or lowercase) + * @param string $type (as "photo" or "movie") + * @return string sanitized filename (or null if bad extension argument) + */ + static function sanitize_filename($filename, $extension, $type) { + // Check if the type is valid - if so, get the mime types of the + // original and target extensions; if not, throw an exception. + $original_extension = pathinfo($filename, PATHINFO_EXTENSION); + switch ($type) { + case "photo": + $mime_type = legal_file::get_photo_types_by_extension($extension); + $original_mime_type = legal_file::get_photo_types_by_extension($original_extension); + break; + case "movie": + $mime_type = legal_file::get_movie_types_by_extension($extension); + $original_mime_type = legal_file::get_movie_types_by_extension($original_extension); + break; + default: + throw new Exception("@todo INVALID_TYPE"); + } + + // Check if the target extension is blank or invalid - if so, throw an exception. + if (!$extension || !$mime_type) { + throw new Exception("@todo ILLEGAL_EXTENSION"); + } + + // Check if the mime types of the original and target extensions match - if not, fix it. + if (!$original_extension || ($mime_type != $original_mime_type)) { + $filename = legal_file::change_extension($filename, $extension); + } + + // It should be a filename without a directory - remove all slashes (and backslashes). + $filename = str_replace("/", "_", $filename); + $filename = str_replace("\\", "_", $filename); + + // Remove extra dots from the filename. This will also remove extraneous underscores. + $filename = legal_file::smash_extensions($filename); + + // It's possible that the filename has no base (e.g. ".jpg") - if so, give it a generic one. + if (empty($filename) || (substr($filename, 0, 1) == ".")) { + $filename = $type . $filename; // e.g. "photo.jpg" or "movie.mp4" + } + + return $filename; + } +} diff --git a/modules/gallery/helpers/locales.php b/modules/gallery/helpers/locales.php new file mode 100644 index 0000000..8ca25c3 --- /dev/null +++ b/modules/gallery/helpers/locales.php @@ -0,0 +1,264 @@ + $name) { + list ($language) = explode("_", $locale . "_"); + // The first one mentioned is the default + if (!isset($d[$language])) { + $d[$language] = $locale; + } + } + self::$language_subtag_to_locale = $d; + } + + static function display_name($locale=null) { + if (empty(self::$locales)) { + self::_init_language_data(); + } + $locale or $locale = Gallery_I18n::instance()->locale(); + + return self::$locales[$locale]; + } + + static function is_rtl($locale=null) { + return Gallery_I18n::instance()->is_rtl($locale); + } + + /** + * Returns the best match comparing the HTTP accept-language header + * with the installed locales. + * @todo replace this with request::accepts_language() when we upgrade to Kohana 2.4 + */ + static function locale_from_http_request() { + $http_accept_language = Input::instance()->server("HTTP_ACCEPT_LANGUAGE"); + if ($http_accept_language) { + // Parse the HTTP header and build a preference list + // Example value: "de,en-us;q=0.7,en-uk,fr-fr;q=0.2" + $locale_preferences = array(); + foreach (explode(",", $http_accept_language) as $code) { + list ($requested_locale, $qvalue) = explode(";", $code . ";"); + $requested_locale = trim($requested_locale); + $qvalue = trim($qvalue); + if (preg_match("/^([a-z]{2,3})(?:[_-]([a-zA-Z]{2}))?/", $requested_locale, $matches)) { + $requested_locale = strtolower($matches[1]); + if (!empty($matches[2])) { + $requested_locale .= "_" . strtoupper($matches[2]); + } + $requested_locale = trim(str_replace("-", "_", $requested_locale)); + if (!strlen($qvalue)) { + // If not specified, default to 1. + $qvalue = 1; + } else { + // qvalue is expected to be something like "q=0.7" + list ($ignored, $qvalue) = explode("=", $qvalue . "=="); + $qvalue = floatval($qvalue); + } + // Group by language to boost inexact same-language matches + list ($language) = explode("_", $requested_locale . "_"); + if (!isset($locale_preferences[$language])) { + $locale_preferences[$language] = array(); + } + $locale_preferences[$language][$requested_locale] = $qvalue; + } + } + + // Compare and score requested locales with installed ones + $scored_locales = array(); + foreach ($locale_preferences as $language => $requested_locales) { + // Inexact match adjustment (same language, different region) + $fallback_adjustment_factor = 0.95; + if (count($requested_locales) > 1) { + // Sort by qvalue, descending + $qvalues = array_values($requested_locales); + rsort($qvalues); + // Ensure inexact match scores worse than 2nd preference in same language. + $fallback_adjustment_factor *= $qvalues[1]; + } + foreach ($requested_locales as $requested_locale => $qvalue) { + list ($matched_locale, $match_score) = + self::_locale_match_score($requested_locale, $qvalue, $fallback_adjustment_factor); + if ($matched_locale && + (!isset($scored_locales[$matched_locale]) || + $match_score > $scored_locales[$matched_locale])) { + $scored_locales[$matched_locale] = $match_score; + } + } + } + + arsort($scored_locales); + + list ($locale) = each($scored_locales); + return $locale; + } + + return null; + } + + private static function _locale_match_score($requested_locale, $qvalue, $adjustment_factor) { + $installed = locales::installed(); + if (isset($installed[$requested_locale])) { + return array($requested_locale, $qvalue); + } + list ($language) = explode("_", $requested_locale . "_"); + if (isset(self::$language_subtag_to_locale[$language]) && + isset($installed[self::$language_subtag_to_locale[$language]])) { + $score = $adjustment_factor * $qvalue; + return array(self::$language_subtag_to_locale[$language], $score); + } + return array(null, 0); + } + + static function set_request_locale() { + // 1. Check the session specific preference (cookie) + $locale = locales::cookie_locale(); + // 2. Check the user's preference + if (!$locale) { + $locale = identity::active_user()->locale; + } + // 3. Check the browser's / OS' preference + if (!$locale) { + $locale = locales::locale_from_http_request(); + } + // If we have any preference, override the site's default locale + if ($locale) { + Gallery_I18n::instance()->locale($locale); + } + } + + static function cookie_locale() { + // Can't use Input framework for client side cookies since + // they're not signed. + $cookie_data = isset($_COOKIE["g_locale"]) ? $_COOKIE["g_locale"] : null; + $locale = null; + if ($cookie_data) { + if (preg_match("/^([a-z]{2,3}(?:_[A-Z]{2})?)$/", trim($cookie_data), $matches)) { + $requested_locale = $matches[1]; + $installed_locales = locales::installed(); + if (isset($installed_locales[$requested_locale])) { + $locale = $requested_locale; + } + } + } + return $locale; + } +} diff --git a/modules/gallery/helpers/log.php b/modules/gallery/helpers/log.php new file mode 100644 index 0000000..cd554b5 --- /dev/null +++ b/modules/gallery/helpers/log.php @@ -0,0 +1,108 @@ +category = $category; + $log->message = $message; + $log->severity = $severity; + $log->html = $html; + $log->url = substr(url::abs_current(true), 0, 255); + $log->referer = request::referrer(null); + $log->timestamp = time(); + $log->user_id = identity::active_user()->id; + $log->save(); + } + + + /** + * Convert a message severity to a CSS class + * @param integer $severity + * @return string + */ + static function severity_class($severity) { + switch($severity) { + case log::SUCCESS: + return "g-success"; + + case log::INFO: + return "g-info"; + + case log::WARNING: + return "g-warning"; + + case log::ERROR: + return "g-error"; + } + } +} diff --git a/modules/gallery/helpers/message.php b/modules/gallery/helpers/message.php new file mode 100644 index 0000000..d109995 --- /dev/null +++ b/modules/gallery/helpers/message.php @@ -0,0 +1,109 @@ +get("messages"); + $status[] = array($msg, $severity); + $session->set("messages", $status); + } + + /** + * Get any pending messages. There are two types of messages, transient and permanent. + * Permanent messages are used to let the admin know that there are pending administrative + * issues that need to be resolved. Transient ones are only displayed once. + * @return html text + */ + static function get() { + $buf = array(); + + $messages = Session::instance()->get_once("messages", array()); + foreach ($messages as $msg) { + $msg[0] = str_replace("__CSRF__", access::csrf_token(), $msg[0]); + $buf[] = "
  • $msg[0]
  • "; + } + if ($buf) { + return "
      " . implode("", $buf) . "
    "; + } + } + + /** + * Convert a message severity to a CSS class + * @param integer $severity + * @return string + */ + static function severity_class($severity) { + switch($severity) { + case message::SUCCESS: + return "g-success"; + + case message::INFO: + return "g-info"; + + case message::WARNING: + return "g-warning"; + + case message::ERROR: + return "g-error"; + } + } +} diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php new file mode 100644 index 0000000..7cff68d --- /dev/null +++ b/modules/gallery/helpers/model_cache.php @@ -0,0 +1,42 @@ +where($field_name, "=", $id)->find(); + if (!$model->loaded()) { + throw new Exception("@todo MISSING_MODEL $model_name:$id"); + } + self::$cache[$model_name][$field_name][$id] = $model; + } + + return self::$cache[$model_name][$field_name][$id]; + } + + static function clear() { + self::$cache = array(); + } + + static function set($model) { + self::$cache[$model->object_name][$model->primary_key][$model->{$model->primary_key}] = $model; + } +} diff --git a/modules/gallery/helpers/module.php b/modules/gallery/helpers/module.php new file mode 100644 index 0000000..1b6c8d1 --- /dev/null +++ b/modules/gallery/helpers/module.php @@ -0,0 +1,594 @@ +loaded()) { + $module->name = $module_name; + $module->active = $module_name == "gallery"; // only gallery is active by default + } + $module->version = $version; + $module->save(); + Kohana_Log::add("debug", "$module_name: version is now $version"); + } + + /** + * Load the corresponding Module_Model + * @param string $module_name + */ + static function get($module_name) { + if (empty(self::$modules[$module_name])) { + return ORM::factory("module")->where("name", "=", $module_name)->find(); + } + return self::$modules[$module_name]; + } + + /** + * Get the information about a module + * @returns ArrayObject containing the module information from the module.info file or false if + * not found + */ + static function info($module_name) { + $module_list = module::available(); + return isset($module_list->$module_name) ? $module_list->$module_name : false; + } + + /** + * Check to see if a module is installed + * @param string $module_name + */ + static function is_installed($module_name) { + return array_key_exists($module_name, self::$modules); + } + + /** + * Check to see if a module is active + * @param string $module_name + */ + static function is_active($module_name) { + return array_key_exists($module_name, self::$modules) && + self::$modules[$module_name]->active; + } + + /** + * Return the list of available modules, including uninstalled modules. + */ + static function available() { + if (empty(self::$available)) { + $modules = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + foreach (glob(MODPATH . "*/module.info") as $file) { + $module_name = basename(dirname($file)); + $modules->$module_name = + new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS); + $m =& $modules->$module_name; + $m->installed = module::is_installed($module_name); + $m->active = module::is_active($module_name); + $m->code_version = $m->version; + $m->version = module::get_version($module_name); + $m->locked = false; + + if ($m->active && $m->version != $m->code_version) { + site_status::warning(t("Some of your modules are out of date. Upgrade now!", array("upgrader_url" => url::abs_site("upgrader"))), "upgrade_now"); + } + } + + // Lock certain modules + $modules->gallery->locked = true; + $identity_module = module::get_var("gallery", "identity_provider", "user"); + $modules->$identity_module->locked = true; + + $modules->uasort(array("module", "module_comparator")); + self::$available = $modules; + } + + return self::$available; + } + + /** + * Natural name sort comparator + */ + static function module_comparator($a, $b) { + return strnatcasecmp($a->name, $b->name); + } + + /** + * Return a list of all the active modules in no particular order. + */ + static function active() { + return self::$active; + } + + /** + * Check that the module can be activated. (i.e. all the prerequistes exist) + * @param string $module_name + * @return array an array of warning or error messages to be displayed + */ + static function can_activate($module_name) { + module::_add_to_path($module_name); + $messages = array(); + + $installer_class = "{$module_name}_installer"; + if (class_exists($installer_class) && method_exists($installer_class, "can_activate")) { + $messages = call_user_func(array($installer_class, "can_activate")); + } + + // Remove it from the active path + module::_remove_from_path($module_name); + return $messages; + } + + /** + * Allow modules to indicate the impact of deactivating the specified module + * @param string $module_name + * @return array an array of warning or error messages to be displayed + */ + static function can_deactivate($module_name) { + $data = (object)array("module" => $module_name, "messages" => array()); + + module::event("pre_deactivate", $data); + + return $data->messages; + } + + /** + * Install a module. This will call _installer::install(), which is responsible for + * creating database tables, setting module variables and calling module::set_version(). + * Note that after installing, the module must be activated before it is available for use. + * @param string $module_name + */ + static function install($module_name) { + module::_add_to_path($module_name); + + $installer_class = "{$module_name}_installer"; + if (class_exists($installer_class) && method_exists($installer_class, "install")) { + call_user_func_array(array($installer_class, "install"), array()); + } + module::set_version($module_name, module::available()->$module_name->code_version); + + // Set the weight of the new module, which controls the order in which the modules are + // loaded. By default, new modules are installed at the end of the priority list. Since the + // id field is monotonically increasing, the easiest way to guarantee that is to set the weight + // the same as the id. We don't know that until we save it for the first time + $module = ORM::factory("module")->where("name", "=", $module_name)->find(); + if ($module->loaded()) { + $module->weight = $module->id; + $module->save(); + } + module::load_modules(); + + // Now the module is installed but inactive, so don't leave it in the active path + module::_remove_from_path($module_name); + + log::success( + "module", t("Installed module %module_name", array("module_name" => $module_name))); + } + + private static function _add_to_path($module_name) { + $config = Kohana_Config::instance(); + $kohana_modules = $config->get("core.modules"); + array_unshift($kohana_modules, MODPATH . $module_name); + $config->set("core.modules", $kohana_modules); + // Rebuild the include path so the module installer can benefit from auto loading + Kohana::include_paths(true); + } + + private static function _remove_from_path($module_name) { + $config = Kohana_Config::instance(); + $kohana_modules = $config->get("core.modules"); + if (($key = array_search(MODPATH . $module_name, $kohana_modules)) !== false) { + unset($kohana_modules[$key]); + $kohana_modules = array_values($kohana_modules); // reindex + } + $config->set("core.modules", $kohana_modules); + Kohana::include_paths(true); + } + + /** + * Upgrade a module. This will call _installer::upgrade(), which is responsible for + * modifying database tables, changing module variables and calling module::set_version(). + * Note that after upgrading, the module must be activated before it is available for use. + * @param string $module_name + */ + static function upgrade($module_name) { + $version_before = module::get_version($module_name); + $installer_class = "{$module_name}_installer"; + $available = module::available(); + if (class_exists($installer_class) && method_exists($installer_class, "upgrade")) { + call_user_func_array(array($installer_class, "upgrade"), array($version_before)); + } else { + if (isset($available->$module_name->code_version)) { + module::set_version($module_name, $available->$module_name->code_version); + } else { + throw new Exception("@todo UNKNOWN_MODULE"); + } + } + module::load_modules(); + + $version_after = module::get_version($module_name); + if ($version_before != $version_after) { + log::success( + "module", t("Upgraded module %module_name from %version_before to %version_after", + array("module_name" => $module_name, + "version_before" => $version_before, + "version_after" => $version_after))); + } + + if ($version_after != $available->$module_name->code_version) { + throw new Exception("@todo MODULE_FAILED_TO_UPGRADE"); + } + } + + /** + * Activate an installed module. This will call _installer::activate() which should take + * any steps to make sure that the module is ready for use. This will also activate any + * existing graphics rules for this module. + * @param string $module_name + */ + static function activate($module_name) { + module::_add_to_path($module_name); + + $installer_class = "{$module_name}_installer"; + if (class_exists($installer_class) && method_exists($installer_class, "activate")) { + call_user_func_array(array($installer_class, "activate"), array()); + } + + $module = module::get($module_name); + if ($module->loaded()) { + $module->active = true; + $module->save(); + } + module::load_modules(); + + graphics::activate_rules($module_name); + + block_manager::activate_blocks($module_name); + + log::success( + "module", t("Activated module %module_name", array("module_name" => $module_name))); + } + + /** + * Deactivate an installed module. This will call _installer::deactivate() which should + * take any cleanup steps to make sure that the module isn't visible in any way. Note that the + * module remains available in Kohana's cascading file system until the end of the request! + * @param string $module_name + */ + static function deactivate($module_name) { + $installer_class = "{$module_name}_installer"; + if (class_exists($installer_class) && method_exists($installer_class, "deactivate")) { + call_user_func_array(array($installer_class, "deactivate"), array()); + } + + $module = module::get($module_name); + if ($module->loaded()) { + $module->active = false; + $module->save(); + } + module::load_modules(); + + graphics::deactivate_rules($module_name); + + block_manager::deactivate_blocks($module_name); + + if (module::info($module_name)) { + log::success( + "module", t("Deactivated module %module_name", array("module_name" => $module_name))); + } else { + log::success( + "module", t("Deactivated missing module %module_name", array("module_name" => $module_name))); + } + } + + /** + * Deactivate modules that are unavailable or missing, yet still active. + * This happens when a user deletes a module without deactivating it. + */ + static function deactivate_missing_modules() { + foreach (self::$modules as $module_name => $module) { + if (module::is_active($module_name) && !module::info($module_name)) { + module::deactivate($module_name); + } + } + } + + /** + * Uninstall a deactivated module. This will call _installer::uninstall() which should + * take whatever steps necessary to make sure that all traces of a module are gone. + * @param string $module_name + */ + static function uninstall($module_name) { + $installer_class = "{$module_name}_installer"; + if (class_exists($installer_class) && method_exists($installer_class, "uninstall")) { + call_user_func(array($installer_class, "uninstall")); + } + + graphics::remove_rules($module_name); + $module = module::get($module_name); + if ($module->loaded()) { + $module->delete(); + } + module::load_modules(); + + // We could delete the module vars here too, but it's nice to leave them around + // in case the module gets reinstalled. + + log::success( + "module", t("Uninstalled module %module_name", array("module_name" => $module_name))); + } + + /** + * Load the active modules. This is called at bootstrap time. + */ + static function load_modules() { + self::$modules = array(); + self::$active = array(); + $kohana_modules = array(); + + // In version 32 we introduced a weight column so we can specify the module order + // If we try to use that blindly, we'll break earlier versions before they can even + // run the upgrader. + $modules = module::get_version("gallery") < 32 ? + ORM::factory("module")->find_all(): + ORM::factory("module")->order_by("weight")->find_all(); + + foreach ($modules as $module) { + self::$modules[$module->name] = $module; + if (!$module->active) { + continue; + } + + if ($module->name == "gallery") { + $gallery = $module; + } else { + self::$active[] = $module; + $kohana_modules[] = MODPATH . $module->name; + } + } + self::$active[] = $gallery; // put gallery last in the module list to match core.modules + $config = Kohana_Config::instance(); + $config->set( + "core.modules", array_merge($kohana_modules, $config->get("core.modules"))); + } + + /** + * Run a specific event on all active modules. + * @param string $name the event name + * @param mixed $data data to pass to each event handler + */ + static function event($name, &$data=null) { + $args = func_get_args(); + array_shift($args); + $function = str_replace(".", "_", $name); + + if (method_exists("gallery_event", $function)) { + switch (count($args)) { + case 0: + gallery_event::$function(); + break; + case 1: + gallery_event::$function($args[0]); + break; + case 2: + gallery_event::$function($args[0], $args[1]); + break; + case 3: + gallery_event::$function($args[0], $args[1], $args[2]); + break; + case 4: // Context menu events have 4 arguments so lets optimize them + gallery_event::$function($args[0], $args[1], $args[2], $args[3]); + break; + default: + call_user_func_array(array("gallery_event", $function), $args); + } + } + + foreach (self::$active as $module) { + if ($module->name == "gallery") { + continue; + } + $class = "{$module->name}_event"; + if (class_exists($class) && method_exists($class, $function)) { + call_user_func_array(array($class, $function), $args); + } + } + + // Give the admin theme a chance to respond, if we're in admin mode. + if (theme::$is_admin) { + $class = theme::$admin_theme_name . "_event"; + if (class_exists($class) && method_exists($class, $function)) { + call_user_func_array(array($class, $function), $args); + } + } + + // Give the site theme a chance to respond as well. It gets a chance even in admin mode, as + // long as the theme has an admin subdir. + $class = theme::$site_theme_name . "_event"; + if (class_exists($class) && method_exists($class, $function)) { + call_user_func_array(array($class, $function), $args); + } + } + + /** + * Get a variable from this module + * @param string $module_name + * @param string $name + * @param string $default_value + * @return the value + */ + static function get_var($module_name, $name, $default_value=null) { + // We cache vars so we can load them all at once for performance. + if (empty(self::$var_cache)) { + self::$var_cache = Cache::instance()->get("var_cache"); + if (empty(self::$var_cache)) { + // Cache doesn't exist, create it now. + foreach (db::build() + ->select("module_name", "name", "value") + ->from("vars") + ->order_by("module_name") + ->order_by("name") + ->execute() as $row) { + // Mute the "Creating default object from empty value" warning below + @self::$var_cache->{$row->module_name}->{$row->name} = $row->value; + } + Cache::instance()->set("var_cache", self::$var_cache, array("vars")); + } + } + + if (isset(self::$var_cache->$module_name->$name)) { + return self::$var_cache->$module_name->$name; + } else { + return $default_value; + } + } + + /** + * Store a variable for this module + * @param string $module_name + * @param string $name + * @param string $value + */ + static function set_var($module_name, $name, $value) { + $var = ORM::factory("var") + ->where("module_name", "=", $module_name) + ->where("name", "=", $name) + ->find(); + if (!$var->loaded()) { + $var->module_name = $module_name; + $var->name = $name; + } + $var->value = $value; + $var->save(); + + Cache::instance()->delete("var_cache"); + self::$var_cache = null; + } + + /** + * Increment the value of a variable for this module + * + * Note: Frequently updating counters is very inefficient because it invalidates the cache value + * which has to be rebuilt every time we make a change. + * + * @todo Get rid of this and find an alternate approach for all callers (currently only Akismet) + * + * @deprecated + * @param string $module_name + * @param string $name + * @param string $increment (optional, default is 1) + */ + static function incr_var($module_name, $name, $increment=1) { + db::build() + ->update("vars") + ->set("value", db::expr("`value` + $increment")) + ->where("module_name", "=", $module_name) + ->where("name", "=", $name) + ->execute(); + + Cache::instance()->delete("var_cache"); + self::$var_cache = null; + } + + /** + * Remove a variable for this module. + * @param string $module_name + * @param string $name + */ + static function clear_var($module_name, $name) { + db::build() + ->delete("vars") + ->where("module_name", "=", $module_name) + ->where("name", "=", $name) + ->execute(); + + Cache::instance()->delete("var_cache"); + self::$var_cache = null; + } + + /** + * Remove all variables for this module. + * @param string $module_name + */ + static function clear_all_vars($module_name) { + db::build() + ->delete("vars") + ->where("module_name", "=", $module_name) + ->execute(); + + Cache::instance()->delete("var_cache"); + self::$var_cache = null; + } + + /** + * Return the version of the installed module. + * @param string $module_name + */ + static function get_version($module_name) { + return module::get($module_name)->version; + } + + /** + * Check if obsolete modules are active and, if so, return a warning message. + * If none are found, return null. + */ + static function get_obsolete_modules_message() { + // This is the obsolete modules list. Any active module that's on the list + // with version number at or below the one given will be considered obsolete. + // It is hard-coded here, and may be updated with future releases of Gallery. + $obsolete_modules = array("videos" => 4, "noffmpeg" => 1, "videodimensions" => 1, + "digibug" => 2); + + // Before we check the active modules, deactivate any that are missing. + module::deactivate_missing_modules(); + + $modules_found = array(); + foreach ($obsolete_modules as $module => $version) { + if (module::is_active($module) && (module::get_version($module) <= $version)) { + $modules_found[] = $module; + } + } + + if ($modules_found) { + // Need this to be on one super-long line or else the localization scanner may not work. + // (ref: http://sourceforge.net/apps/trac/gallery/ticket/1321) + return t("Recent upgrades to Gallery have made the following modules obsolete: %modules. We recommend that you deactivate the module(s). For more information, please see the documentation page.", + array("modules" => implode(", ", $modules_found), + "url_mod" => url::site("admin/modules"), + "url_doc" => "http://codex.galleryproject.org/Gallery3:User_guide:Obsolete_modules")); + } + + return null; + } +} diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php new file mode 100644 index 0000000..2f19088 --- /dev/null +++ b/modules/gallery/helpers/movie.php @@ -0,0 +1,282 @@ +id", "", "post", array("id" => "g-edit-movie-form")); + $form->hidden("from_id")->value($movie->id); + $group = $form->group("edit_item")->label(t("Edit Movie")); + $group->input("title")->label(t("Title"))->value($movie->title) + ->error_messages("required", t("You must provide a title")) + ->error_messages("length", t("Your title is too long")); + $group->textarea("description")->label(t("Description"))->value($movie->description); + $group->input("name")->label(t("Filename"))->value($movie->name) + ->error_messages( + "conflict", t("There is already a movie, photo or album with this name")) + ->error_messages("no_slashes", t("The movie name can't contain a \"/\"")) + ->error_messages("no_trailing_period", t("The movie name can't end in \".\"")) + ->error_messages("illegal_data_file_extension", t("You cannot change the movie file extension")) + ->error_messages("required", t("You must provide a movie file name")) + ->error_messages("length", t("Your movie file name is too long")); + $group->input("slug")->label(t("Internet Address"))->value($movie->slug) + ->error_messages( + "conflict", t("There is already a movie, photo or album with this internet address")) + ->error_messages( + "not_url_safe", + t("The internet address should contain only letters, numbers, hyphens and underscores")) + ->error_messages("required", t("You must provide an internet address")) + ->error_messages("length", t("Your internet address is too long")); + + module::event("item_edit_form", $movie, $form); + + $group = $form->group("buttons")->label(""); + $group->submit("")->value(t("Modify")); + + return $form; + } + + /** + * Extract a frame from a movie file. Valid movie_options are start_time (in seconds), + * input_args (extra ffmpeg input args) and output_args (extra ffmpeg output args). Extra args + * are added at the end of the list, so they can override any prior args. + * + * @param string $input_file + * @param string $output_file + * @param array $movie_options (optional) + */ + static function extract_frame($input_file, $output_file, $movie_options=null) { + $ffmpeg = movie::find_ffmpeg(); + if (empty($ffmpeg)) { + throw new Exception("@todo MISSING_FFMPEG"); + } + + list($width, $height, $mime_type, $extension, $duration) = movie::get_file_metadata($input_file); + + if (isset($movie_options["start_time"]) && is_numeric($movie_options["start_time"])) { + $start_time = max(0, $movie_options["start_time"]); // ensure it's non-negative + } else { + $start_time = module::get_var("gallery", "movie_extract_frame_time", 3); // use default + } + // extract frame at start_time, unless movie is too short + $start_time_arg = ($duration >= $start_time + 0.1) ? + "-ss " . movie::seconds_to_hhmmssdd($start_time) : ""; + + $input_args = isset($movie_options["input_args"]) ? $movie_options["input_args"] : ""; + $output_args = isset($movie_options["output_args"]) ? $movie_options["output_args"] : ""; + + $cmd = escapeshellcmd($ffmpeg) . " $input_args -i " . escapeshellarg($input_file) . + " -an $start_time_arg -an -r 1 -vframes 1" . + " -s {$width}x{$height}" . + " -y -f mjpeg $output_args " . escapeshellarg($output_file) . " 2>&1"; + exec($cmd, $exec_output, $exec_return); + + clearstatcache(); // use $filename parameter when PHP_version is 5.3+ + if (filesize($output_file) == 0 || $exec_return) { + // Maybe the movie needs the "-threads 1" argument added + // (see http://sourceforge.net/apps/trac/gallery/ticket/1924) + $cmd = escapeshellcmd($ffmpeg) . " -threads 1 $input_args -i " . escapeshellarg($input_file) . + " -an $start_time_arg -an -r 1 -vframes 1" . + " -s {$width}x{$height}" . + " -y -f mjpeg $output_args " . escapeshellarg($output_file) . " 2>&1"; + exec($cmd, $exec_output, $exec_return); + + clearstatcache(); + if (filesize($output_file) == 0 || $exec_return) { + throw new Exception("@todo FFMPEG_FAILED"); + } + } + } + + /** + * Return true if movie uploads are allowed, false if not. This is based on the + * "movie_allow_uploads" Gallery variable as well as whether or not ffmpeg is found. + */ + static function allow_uploads() { + if (empty(self::$allow_uploads)) { + // Refresh ffmpeg settings + $ffmpeg = movie::find_ffmpeg(); + switch (module::get_var("gallery", "movie_allow_uploads", "autodetect")) { + case "always": + self::$allow_uploads = true; + break; + case "never": + self::$allow_uploads = false; + break; + default: + self::$allow_uploads = !empty($ffmpeg); + break; + } + } + return self::$allow_uploads; + } + + /** + * Return the path to the ffmpeg binary if one exists and is executable, or null. + */ + static function find_ffmpeg() { + if (!($ffmpeg_path = module::get_var("gallery", "ffmpeg_path")) || + !@is_executable($ffmpeg_path)) { + $ffmpeg_path = system::find_binary( + "ffmpeg", module::get_var("gallery", "graphics_toolkit_path")); + module::set_var("gallery", "ffmpeg_path", $ffmpeg_path); + } + return $ffmpeg_path; + } + + /** + * Return version number and build date of ffmpeg if found, empty string(s) if not. When using + * static builds that aren't official releases, the version numbers are strange, hence why the + * date can be useful. + */ + static function get_ffmpeg_version() { + $ffmpeg = movie::find_ffmpeg(); + if (empty($ffmpeg)) { + return array("", ""); + } + + // Find version using -h argument since -version wasn't available in early versions. + // To keep the preg_match searches quick, we'll trim the (otherwise long) result. + $cmd = escapeshellcmd($ffmpeg) . " -h 2>&1"; + $result = substr(`$cmd`, 0, 1000); + if (preg_match("/ffmpeg version (\S+)/i", $result, $matches_version)) { + // Version number found - see if we can get the build date or copyright year as well. + if (preg_match("/built on (\S+\s\S+\s\S+)/i", $result, $matches_build_date)) { + return array(trim($matches_version[1], ","), trim($matches_build_date[1], ",")); + } else if (preg_match("/copyright \S*\s?2000-(\d{4})/i", $result, $matches_copyright_date)) { + return array(trim($matches_version[1], ","), $matches_copyright_date[1]); + } else { + return array(trim($matches_version[1], ","), ""); + } + } + return array("", ""); + } + + /** + * Return the width, height, mime_type, extension and duration of the given movie file. + * Metadata is first generated using ffmpeg (or set to defaults if it fails), + * then can be modified by other modules using movie_get_file_metadata events. + * + * This function and its use cases are symmetric to those of photo::get_file_metadata. + * + * @param string $file_path + * @return array array($width, $height, $mime_type, $extension, $duration) + * + * Use cases in detail: + * Input is standard movie type (flv/mp4/m4v) + * -> return metadata from ffmpeg + * Input is *not* standard movie type that is supported by ffmpeg (e.g. avi, mts...) + * -> return metadata from ffmpeg + * Input is *not* standard movie type that is *not* supported by ffmpeg but is legal + * -> return zero width, height, and duration; mime type and extension according to legal_file + * Input is illegal, unidentifiable, unreadable, or does not exist + * -> throw exception + * Note: movie_get_file_metadata events can change any of the above cases (except the last one). + */ + static function get_file_metadata($file_path) { + if (!is_readable($file_path)) { + throw new Exception("@todo UNREADABLE_FILE"); + } + + $metadata = new stdClass(); + $ffmpeg = movie::find_ffmpeg(); + if (!empty($ffmpeg)) { + // ffmpeg found - use it to get width, height, and duration. + $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($file_path) . " 2>&1"; + $result = `$cmd`; + if (preg_match("/Stream.*?Video:.*?, (\d+)x(\d+)/", $result, $matches_res)) { + if (preg_match("/Stream.*?Video:.*? \[.*?DAR (\d+):(\d+).*?\]/", $result, $matches_dar) && + $matches_dar[1] >= 1 && $matches_dar[2] >= 1) { + // DAR is defined - determine width based on height and DAR + // (should always be int, but adding round to be sure) + $matches_res[1] = round($matches_res[2] * $matches_dar[1] / $matches_dar[2]); + } + list ($metadata->width, $metadata->height) = array($matches_res[1], $matches_res[2]); + } else { + list ($metadata->width, $metadata->height) = array(0, 0); + } + + if (preg_match("/Duration: (\d+:\d+:\d+\.\d+)/", $result, $matches)) { + $metadata->duration = movie::hhmmssdd_to_seconds($matches[1]); + } else if (preg_match("/duration.*?:.*?(\d+)/", $result, $matches)) { + $metadata->duration = $matches[1]; + } else { + $metadata->duration = 0; + } + } else { + // ffmpeg not found - set width, height, and duration to zero. + $metadata->width = 0; + $metadata->height = 0; + $metadata->duration = 0; + } + + $extension = pathinfo($file_path, PATHINFO_EXTENSION); + if (!$extension || + (!$metadata->mime_type = legal_file::get_movie_types_by_extension($extension))) { + // Extension is empty or illegal. + $metadata->extension = null; + $metadata->mime_type = null; + } else { + // Extension is legal (and mime is already set above). + $metadata->extension = strtolower($extension); + } + + // Run movie_get_file_metadata events which can modify the class. + module::event("movie_get_file_metadata", $file_path, $metadata); + + // If the post-events results are invalid, throw an exception. Note that, unlike photos, having + // zero width and height isn't considered invalid (as is the case when FFmpeg isn't installed). + if (!$metadata->mime_type || !$metadata->extension || + ($metadata->mime_type != legal_file::get_movie_types_by_extension($metadata->extension))) { + throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE"); + } + + return array($metadata->width, $metadata->height, $metadata->mime_type, + $metadata->extension, $metadata->duration); + } + + /** + * Return the time/duration formatted in hh:mm:ss.dd from a number of seconds. + * Useful for inputs to ffmpeg. + * + * Note that this is similar to date("H:i:s", mktime(0,0,$seconds,0,0,0,0)), but unlike this + * approach avoids potential issues with time zone and DST mismatch and/or using deprecated + * features (the last argument of mkdate above, which disables DST, is deprecated as of PHP 5.3). + */ + static function seconds_to_hhmmssdd($seconds) { + return sprintf("%02d:%02d:%05.2f", floor($seconds / 3600), floor(($seconds % 3600) / 60), + floor(100 * $seconds % 6000) / 100); + } + + /** + * Return the number of seconds from a time/duration formatted in hh:mm:ss.dd. + * Useful for outputs from ffmpeg. + */ + static function hhmmssdd_to_seconds($hhmmssdd) { + preg_match("/(\d+):(\d+):(\d+\.\d+)/", $hhmmssdd, $matches); + return 3600 * $matches[1] + 60 * $matches[2] + $matches[3]; + } +} diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php new file mode 100644 index 0000000..004cc7c --- /dev/null +++ b/modules/gallery/helpers/photo.php @@ -0,0 +1,145 @@ +id", "", "post", array("id" => "g-edit-photo-form")); + $form->hidden("from_id")->value($photo->id); + $group = $form->group("edit_item")->label(t("Edit Photo")); + $group->input("title")->label(t("Title"))->value($photo->title) + ->error_messages("required", t("You must provide a title")) + ->error_messages("length", t("Your title is too long")); + $group->textarea("description")->label(t("Description"))->value($photo->description); + $group->input("name")->label(t("Filename"))->value($photo->name) + ->error_messages("conflict", t("There is already a movie, photo or album with this name")) + ->error_messages("no_slashes", t("The photo name can't contain a \"/\"")) + ->error_messages("no_trailing_period", t("The photo name can't end in \".\"")) + ->error_messages("illegal_data_file_extension", t("You cannot change the photo file extension")) + ->error_messages("required", t("You must provide a photo file name")) + ->error_messages("length", t("Your photo file name is too long")); + $group->input("slug")->label(t("Internet Address"))->value($photo->slug) + ->error_messages( + "conflict", t("There is already a movie, photo or album with this internet address")) + ->error_messages( + "not_url_safe", + t("The internet address should contain only letters, numbers, hyphens and underscores")) + ->error_messages("required", t("You must provide an internet address")) + ->error_messages("length", t("Your internet address is too long")); + + module::event("item_edit_form", $photo, $form); + + $group = $form->group("buttons")->label(""); + $group->submit("")->value(t("Modify")); + return $form; + } + + /** + * Return scaled width and height. + * + * @param integer $width + * @param integer $height + * @param integer $max the target size for the largest dimension + * @param string $format the output format using %d placeholders for width and height + */ + static function img_dimensions($width, $height, $max, $format="width=\"%d\" height=\"%d\"") { + if (!$width || !$height) { + return ""; + } + + if ($width > $height) { + $new_width = $max; + $new_height = (int)$max * ($height / $width); + } else { + $new_height = $max; + $new_width = (int)$max * ($width / $height); + } + return sprintf($format, $new_width, $new_height); + } + + /** + * Return the width, height, mime_type and extension of the given image file. + * Metadata is first generated using getimagesize (or the legal_file mapping if it fails), + * then can be modified by other modules using photo_get_file_metadata events. + * + * This function and its use cases are symmetric to those of photo::get_file_metadata. + * + * @param string $file_path + * @return array array($width, $height, $mime_type, $extension) + * + * Use cases in detail: + * Input is standard photo type (jpg/png/gif) + * -> return metadata from getimagesize() + * Input is *not* standard photo type that is supported by getimagesize (e.g. tif, bmp...) + * -> return metadata from getimagesize() + * Input is *not* standard photo type that is *not* supported by getimagesize but is legal + * -> return metadata if found by photo_get_file_metadata events + * Input is illegal, unidentifiable, unreadable, or does not exist + * -> throw exception + * Note: photo_get_file_metadata events can change any of the above cases (except the last one). + */ + static function get_file_metadata($file_path) { + if (!is_readable($file_path)) { + throw new Exception("@todo UNREADABLE_FILE"); + } + + $metadata = new stdClass(); + if ($image_info = getimagesize($file_path)) { + // getimagesize worked - use its results. + $metadata->width = $image_info[0]; + $metadata->height = $image_info[1]; + $metadata->mime_type = $image_info["mime"]; + $metadata->extension = image_type_to_extension($image_info[2], false); + // We prefer jpg instead of jpeg (which is returned by image_type_to_extension). + if ($metadata->extension == "jpeg") { + $metadata->extension = "jpg"; + } + } else { + // getimagesize failed - try to use legal_file mapping instead. + $extension = pathinfo($file_path, PATHINFO_EXTENSION); + if (!$extension || + (!$metadata->mime_type = legal_file::get_photo_types_by_extension($extension))) { + // Extension is empty or illegal. + $metadata->extension = null; + $metadata->mime_type = null; + } else { + // Extension is legal (and mime is already set above). + $metadata->extension = strtolower($extension); + } + $metadata->width = 0; + $metadata->height = 0; + } + + // Run photo_get_file_metadata events which can modify the class. + module::event("photo_get_file_metadata", $file_path, $metadata); + + // If the post-events results are invalid, throw an exception. + if (!$metadata->width || !$metadata->height || !$metadata->mime_type || !$metadata->extension || + ($metadata->mime_type != legal_file::get_photo_types_by_extension($metadata->extension))) { + throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE"); + } + + return array($metadata->width, $metadata->height, $metadata->mime_type, $metadata->extension); + } +} diff --git a/modules/gallery/helpers/random.php b/modules/gallery/helpers/random.php new file mode 100644 index 0000000..d40bfb5 --- /dev/null +++ b/modules/gallery/helpers/random.php @@ -0,0 +1,48 @@ +where("key", "=", $permanent_key) + ->find(); + if (!$message->loaded()) { + $message->key = $permanent_key; + } + $message->severity = $severity; + $message->value = $msg; + $message->save(); + } + + /** + * Remove any permanent message by key. + * @param string $permanent_key + */ + static function clear($permanent_key) { + $message = ORM::factory("message")->where("key", "=", $permanent_key)->find(); + if ($message->loaded()) { + $message->delete(); + } + } + + /** + * Get any pending messages. There are two types of messages, transient and permanent. + * Permanent messages are used to let the admin know that there are pending administrative + * issues that need to be resolved. Transient ones are only displayed once. + * @return html text + */ + static function get() { + if (!identity::active_user()->admin) { + return; + } + $buf = array(); + foreach (ORM::factory("message")->find_all() as $msg) { + $value = str_replace("__CSRF__", access::csrf_token(), $msg->value); + $buf[] = "
  • severity) . "\">$value
  • "; + } + + if ($buf) { + return "
      " . implode("", $buf) . "
    "; + } + } + + /** + * Convert a message severity to a CSS class + * @param integer $severity + * @return string + */ + static function severity_class($severity) { + switch($severity) { + case site_status::SUCCESS: + return "g-success"; + + case site_status::INFO: + return "g-info"; + + case site_status::WARNING: + return "g-warning"; + + case site_status::ERROR: + return "g-error"; + } + } +} diff --git a/modules/gallery/helpers/system.php b/modules/gallery/helpers/system.php new file mode 100644 index 0000000..f0879d6 --- /dev/null +++ b/modules/gallery/helpers/system.php @@ -0,0 +1,113 @@ +name}_task"; + if (class_exists($class_name) && method_exists($class_name, "available_tasks")) { + foreach (call_user_func(array($class_name, "available_tasks")) as $task) { + $tasks[$task->callback] = $task; + } + } + } + + return $tasks; + } + + static function start($task_callback, $context=array()) { + $tasks = task::get_definitions(); + $task = task::create($tasks[$task_callback], array()); + + $task->log(t("Task %task_name started (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id))); + return $task; + } + + static function create($task_def, $context) { + $task = ORM::factory("task"); + $task->callback = $task_def->callback; + $task->name = $task_def->name; + $task->percent_complete = 0; + $task->status = ""; + $task->state = "started"; + $task->owner_id = identity::active_user()->id; + $task->context = serialize($context); + $task->save(); + + return $task; + } + + static function cancel($task_id) { + $task = ORM::factory("task", $task_id); + if (!$task->loaded()) { + throw new Exception("@todo MISSING_TASK"); + } + $task->done = 1; + $task->state = "cancelled"; + $task->log(t("Task %task_name cancelled (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id))); + $task->save(); + + return $task; + } + + static function remove($task_id) { + $task = ORM::factory("task", $task_id); + if ($task->loaded()) { + $task->delete(); + } + } + + static function run($task_id) { + $task = ORM::factory("task", $task_id); + if (!$task->loaded()) { + throw new Exception("@todo MISSING_TASK"); + } + + try { + $task->state = "running"; + call_user_func_array($task->callback, array(&$task)); + if ($task->done) { + $task->log($task->status); + } + $task->save(); + } catch (Exception $e) { + Kohana_Log::add("error", (string)$e); + + // Ugh. I hate to use instanceof, But this beats catching the exception separately since + // we mostly want to treat it the same way as all other exceptions + if ($e instanceof ORM_Validation_Exception) { + Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); + } + + $task->log((string)$e); + $task->state = "error"; + $task->done = true; + $task->status = substr($e->getMessage(), 0, 255); + $task->save(); + } + + return $task; + } +} \ No newline at end of file diff --git a/modules/gallery/helpers/theme.php b/modules/gallery/helpers/theme.php new file mode 100644 index 0000000..072f98a --- /dev/null +++ b/modules/gallery/helpers/theme.php @@ -0,0 +1,113 @@ +server("PATH_INFO"); + if (empty($path)) { + $path = "/" . $input->get("kohana_uri"); + } + + $config = Kohana_Config::instance(); + $modules = $config->get("core.modules"); + + // Normally Router::find_uri() strips off the url suffix for us, but we're working off of the + // PATH_INFO here so we need to strip it off manually + if ($suffix = Kohana::config("core.url_suffix")) { + $path = preg_replace("#" . preg_quote($suffix) . "$#u", "", $path); + } + + self::$is_admin = $path == "/admin" || !strncmp($path, "/admin/", 7); + self::$site_theme_name = module::get_var("gallery", "active_site_theme"); + + // If the site theme doesn't exist, fall back to wind. + if (!file_exists(THEMEPATH . self::$site_theme_name . "/theme.info")) { + site_status::error(t("Theme '%name' is missing. Falling back to the Wind theme.", + array("name" => self::$site_theme_name)), "missing_site_theme"); + module::set_var("gallery", "active_site_theme", self::$site_theme_name = "wind"); + } + + if (self::$is_admin) { + // Load the admin theme + self::$admin_theme_name = module::get_var("gallery", "active_admin_theme"); + + // If the admin theme doesn't exist, fall back to admin_wind. + if (!file_exists(THEMEPATH . self::$admin_theme_name . "/theme.info")) { + site_status::error(t("Admin theme '%name' is missing! Falling back to the Wind theme.", + array("name" => self::$admin_theme_name)), "missing_admin_theme"); + module::set_var("gallery", "active_admin_theme", self::$admin_theme_name = "admin_wind"); + } + + array_unshift($modules, THEMEPATH . self::$admin_theme_name); + + // If the site theme has an admin subdir, load that as a module so that + // themes can provide their own code. + if (file_exists(THEMEPATH . self::$site_theme_name . "/admin")) { + array_unshift($modules, THEMEPATH . self::$site_theme_name . "/admin"); + } + // Admins can override the site theme, temporarily. This lets us preview themes. + if (identity::active_user()->admin && $override = $input->get("theme")) { + if (file_exists(THEMEPATH . $override)) { + self::$admin_theme_name = $override; + array_unshift($modules, THEMEPATH . self::$admin_theme_name); + } else { + Kohana_Log::add("error", "Missing override admin theme: '$override'"); + } + } + } else { + // Admins can override the site theme, temporarily. This lets us preview themes. + if (identity::active_user()->admin && $override = $input->get("theme")) { + if (file_exists(THEMEPATH . $override)) { + self::$site_theme_name = $override; + } else { + Kohana_Log::add("error", "Missing override site theme: '$override'"); + } + } + array_unshift($modules, THEMEPATH . self::$site_theme_name); + } + + $config->set("core.modules", $modules); + } + + static function get_info($theme_name) { + $theme_name = preg_replace("/[^a-zA-Z0-9\._-]/", "", $theme_name); + $file = THEMEPATH . "$theme_name/theme.info"; + $theme_info = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS); + $theme_info->description = t($theme_info->description); + $theme_info->name = t($theme_info->name); + + return $theme_info; + } +} + diff --git a/modules/gallery/helpers/tree_rest.php b/modules/gallery/helpers/tree_rest.php new file mode 100644 index 0000000..5186cf0 --- /dev/null +++ b/modules/gallery/helpers/tree_rest.php @@ -0,0 +1,92 @@ + + * Only traverse this far down into the tree. If there are more albums + * below this depth, provide RESTful urls to other tree resources in + * the members section. Default is infinite. + * + * type= + * Restrict the items displayed to the given type. Default is all types. + * + * fields= + * In the entity section only return these fields for each item. + * Default is all fields. + */ + static function get($request) { + $item = rest::resolve($request->url); + access::required("view", $item); + + $query_params = array(); + $p = $request->params; + $where = array(); + if (isset($p->type)) { + $where[] = array("type", "=", $p->type); + $query_params[] = "type={$p->type}"; + } + + if (isset($p->depth)) { + $lowest_depth = $item->level + $p->depth; + $where[] = array("level", "<=", $lowest_depth); + $query_params[] = "depth={$p->depth}"; + } + + $fields = array(); + if (isset($p->fields)) { + $fields = explode(",", $p->fields); + $query_params[] = "fields={$p->fields}"; + } + + $entity = array(array("url" => rest::url("item", $item), + "entity" => $item->as_restful_array($fields))); + $members = array(); + foreach ($item->viewable()->descendants(null, null, $where) as $child) { + $entity[] = array("url" => rest::url("item", $child), + "entity" => $child->as_restful_array($fields)); + if (isset($lowest_depth) && $child->level == $lowest_depth) { + $members[] = url::merge_querystring(rest::url("tree", $child), $query_params); + } + } + + $result = array( + "url" => $request->url, + "entity" => $entity, + "members" => $members, + "relationships" => rest::relationships("tree", $item)); + return $result; + } + + static function resolve($id) { + $item = ORM::factory("item", $id); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + return $item; + } + + static function url($item) { + return url::abs_site("rest/tree/{$item->id}"); + } +} diff --git a/modules/gallery/helpers/upgrade_checker.php b/modules/gallery/helpers/upgrade_checker.php new file mode 100644 index 0000000..492f72e --- /dev/null +++ b/modules/gallery/helpers/upgrade_checker.php @@ -0,0 +1,105 @@ +get("upgrade_checker_version_info")); + } + + /** + * Return true if auto checking is enabled. + */ + static function auto_check_enabled() { + return (bool)module::get_var("gallery", "upgrade_checker_auto_enabled"); + } + + /** + * Return true if it's time to auto check. + */ + static function should_auto_check() { + if (upgrade_checker::auto_check_enabled() && random::int(1, 100) == 1) { + $version_info = upgrade_checker::version_info(); + return (!$version_info || + (time() - $version_info->timestamp) > upgrade_checker::AUTO_CHECK_INTERVAL); + } + return false; + } + + /** + * Fech version info from the Gallery website. + */ + static function fetch_version_info() { + $result = new stdClass(); + try { + list ($status, $headers, $body) = remote::do_request(upgrade_checker::CHECK_URL); + if ($status == "HTTP/1.1 200 OK") { + $result->status = "success"; + foreach (explode("\n", $body) as $line) { + if ($line) { + list($key, $val) = explode("=", $line, 2); + $result->data[$key] = $val; + } + } + } else { + $result->status = "error"; + } + } catch (Exception $e) { + Kohana_Log::add("error", + sprintf("%s in %s at line %s:\n%s", $e->getMessage(), $e->getFile(), + $e->getLine(), $e->getTraceAsString())); + } + $result->timestamp = time(); + Cache::instance()->set("upgrade_checker_version_info", serialize($result), + array("upgrade"), 86400 * 365); + } + + /** + * Check the latest version info blob to see if it's time for an upgrade. + */ + static function get_upgrade_message() { + $version_info = upgrade_checker::version_info(); + if ($version_info) { + if (gallery::RELEASE_CHANNEL == "release") { + if (version_compare($version_info->data["release_version"], gallery::VERSION, ">")) { + return t("A newer version of Gallery is available! Upgrade now to version %version", + array("version" => $version_info->data["release_version"], + "upgrade-url" => $version_info->data["release_upgrade_url"])); + } + } else { + $branch = gallery::RELEASE_BRANCH; + if (isset($version_info->data["branch_{$branch}_build_number"]) && + version_compare($version_info->data["branch_{$branch}_build_number"], + gallery::build_number(), ">")) { + return t("A newer version of Gallery is available! Upgrade now to version %version (build %build on branch %branch)", + array("version" => $version_info->data["branch_{$branch}_version"], + "upgrade-url" => $version_info->data["branch_{$branch}_upgrade_url"], + "build" => $version_info->data["branch_{$branch}_build_number"], + "branch" => $branch)); + } + } + } + } +} diff --git a/modules/gallery/helpers/user_profile.php b/modules/gallery/helpers/user_profile.php new file mode 100644 index 0000000..222d2f5 --- /dev/null +++ b/modules/gallery/helpers/user_profile.php @@ -0,0 +1,55 @@ +id}", "", "post", + array("id" => "g-user-profile-contact-form")); + $group = $form->group("message") + ->label(t("Compose message to %name", array("name" => $user->display_name()))); + $group->input("reply_to") + ->label(t("From:")) + ->rules("required|length[1, 256]|valid_email") + ->error_messages("required", t("You must enter a valid email address")) + ->error_messages("max_length", t("Your email address is too long")) + ->error_messages("valid_email", t("You must enter a valid email address")); + $group->input("subject") + ->label(t("Subject:")) + ->rules("required|length[1, 256]") + ->error_messages("required", t("Your message must have a subject")) + ->error_messages("max_length", t("Your subject is too long")); + $group->textarea("message") + ->label(t("Message:")) + ->rules("required") + ->error_messages("required", t("You must enter a message")); + module::event("user_profile_contact_form", $form); + module::event("captcha_protect_form", $form); + $group->submit("")->value(t("Send")); + return $form; + } +} diff --git a/modules/gallery/helpers/xml.php b/modules/gallery/helpers/xml.php new file mode 100644 index 0000000..e20beb1 --- /dev/null +++ b/modules/gallery/helpers/xml.php @@ -0,0 +1,35 @@ +\n"; + foreach ($array as $key => $value) { + if (is_array($value)) { + $xml .= xml::to_xml($value, array_slice($element_names, 1)); + } else if (is_object($value)) { + $xml .= xml::to_xml($value->as_array(), array_slice($element_names, 1)); + } else { + $xml .= "<$key>$value\n"; + } + } + $xml .= "\n"; + return $xml; + } +} diff --git a/modules/gallery/hooks/init_gallery.php b/modules/gallery/hooks/init_gallery.php new file mode 100644 index 0000000..fa4f816 --- /dev/null +++ b/modules/gallery/hooks/init_gallery.php @@ -0,0 +1,56 @@ +connect(); +} catch (Kohana_PHP_Exception $e) { + print "Database configuration error. Please check var/database.php"; + exit; +} + +Event::add("system.ready", array("Gallery_I18n", "instance")); +Event::add("system.ready", array("module", "load_modules")); +Event::add("system.ready", array("gallery", "ready")); +Event::add("system.post_routing", array("url", "parse_url")); +Event::add("system.post_routing", array("gallery", "maintenance_mode")); +Event::add("system.post_routing", array("gallery", "private_gallery")); +Event::add("system.shutdown", array("gallery", "shutdown")); + +// @todo once we convert to Kohana 2.4 this doesn't have to be here +set_error_handler(array("gallery_error", "error_handler")); + +// Override the cookie if we have a session id in the URL. +// @todo This should probably be an event callback +$input = Input::instance(); +if ($g3sid = $input->post("g3sid", $input->get("g3sid"))) { + $_COOKIE["g3sid"] = $g3sid; +} + +if ($user_agent = $input->post("user_agent", $input->get("user_agent"))) { + $_SERVER["HTTP_USER_AGENT"] = $user_agent; +} diff --git a/modules/gallery/images/ffmpeg.png b/modules/gallery/images/ffmpeg.png new file mode 100644 index 0000000..6be8b62 Binary files /dev/null and b/modules/gallery/images/ffmpeg.png differ diff --git a/modules/gallery/images/gallery.png b/modules/gallery/images/gallery.png new file mode 100644 index 0000000..ca8e0e9 Binary files /dev/null and b/modules/gallery/images/gallery.png differ diff --git a/modules/gallery/images/gd.png b/modules/gallery/images/gd.png new file mode 100644 index 0000000..b341d71 Binary files /dev/null and b/modules/gallery/images/gd.png differ diff --git a/modules/gallery/images/graphicsmagick.png b/modules/gallery/images/graphicsmagick.png new file mode 100644 index 0000000..3d1d77e Binary files /dev/null and b/modules/gallery/images/graphicsmagick.png differ diff --git a/modules/gallery/images/imagemagick.jpg b/modules/gallery/images/imagemagick.jpg new file mode 100644 index 0000000..d83c450 Binary files /dev/null and b/modules/gallery/images/imagemagick.jpg differ diff --git a/modules/gallery/images/missing_album_cover.jpg b/modules/gallery/images/missing_album_cover.jpg new file mode 100644 index 0000000..bdddeec Binary files /dev/null and b/modules/gallery/images/missing_album_cover.jpg differ diff --git a/modules/gallery/images/missing_movie.jpg b/modules/gallery/images/missing_movie.jpg new file mode 100644 index 0000000..452db22 Binary files /dev/null and b/modules/gallery/images/missing_movie.jpg differ diff --git a/modules/gallery/images/missing_photo.jpg b/modules/gallery/images/missing_photo.jpg new file mode 100644 index 0000000..a9d176d Binary files /dev/null and b/modules/gallery/images/missing_photo.jpg differ diff --git a/modules/gallery/js/albums_form_add.js b/modules/gallery/js/albums_form_add.js new file mode 100644 index 0000000..4e22760 --- /dev/null +++ b/modules/gallery/js/albums_form_add.js @@ -0,0 +1,25 @@ +$("#g-add-album-form input[name=title]").change( + function() { + console.log("changing title"); + $("#g-add-album-form input[name=name]").attr( + "value", $("#g-add-album-form input[name=title]").attr("value") + .replace(/[\s\/]+/g, "-").replace(/\.+$/, "")); + $("#g-add-album-form input[name=slug]").attr( + "value", $("#g-add-album-form input[name=title]").attr("value") + .replace(/[^A-Za-z0-9-_]+/g, "-") + .replace(/^-+/, "") + .replace(/-+$/, "")); + }); +$("#g-add-album-form input[name=title]").keyup( + function() { + console.log("key up"); + $("#g-add-album-form input[name=name]").attr( + "value", $("#g-add-album-form input[name=title]").attr("value") + .replace(/[\s\/]+/g, "-") + .replace(/\.+$/, "")); + $("#g-add-album-form input[name=slug]").attr( + "value", $("#g-add-album-form input[name=title]").attr("value") + .replace(/[^A-Za-z0-9-_]+/g, "-") + .replace(/^-+/, "") + .replace(/-+$/, "")); + }); diff --git a/modules/gallery/js/item_form_delete.js b/modules/gallery/js/item_form_delete.js new file mode 100644 index 0000000..fa3f24a --- /dev/null +++ b/modules/gallery/js/item_form_delete.js @@ -0,0 +1,5 @@ +$("#g-confirm-delete").submit( + function() { + $("#g-confirm-delete input[type=submit]").gallery_show_loading(); + } +); diff --git a/modules/gallery/js/l10n_client.js b/modules/gallery/js/l10n_client.js new file mode 100644 index 0000000..6d919c2 --- /dev/null +++ b/modules/gallery/js/l10n_client.js @@ -0,0 +1,315 @@ +// Fork from Drupal's l10n_client module, originally written by: +// G‡bor Hojtsy http://drupal.org/user/4166 (original author) +// Young Hahn / Development Seed - http://developmentseed.org/ (friendly user interface) + +var Gallery = Gallery || { 'behaviors': {} }; + +Gallery.attachBehaviors = function(context) { + context = context || document; + // Execute all of them. + jQuery.each(Gallery.behaviors, + function() { + this(context); + }); +}; + +$(document).ready(function() { + Gallery.attachBehaviors(this); +}); + +// Store all l10n_client related data + methods in its own object +jQuery.extend(Gallery, { + l10nClient: new (function() { + // Set "selected" string to unselected, i.e. -1 + this.selected = -1; + // Keybindings + this.keys = {'toggle':'ctrl+shift+s', 'clear': 'esc'}; // Keybindings + // Keybinding functions + this.key = function(pressed) { + switch(pressed) { + case 'toggle': + // Grab user-hilighted text & send it into the search filter + userSelection = window.getSelection ? window.getSelection() : document.getSelection ? document.getSelection() : document.selection.createRange().text; + userSelection = String(userSelection); + if(userSelection.length > 0) { + Gallery.l10nClient.filter(userSelection); + Gallery.l10nClient.toggle(1); + $('#l10n-client #g-l10n-search').focus(); + } else { + if($('#l10n-client').is('.hidden')) { + Gallery.l10nClient.toggle(1); + if(!$.browser.safari) { + $('#l10n-client #g-l10n-search').focus(); + } + } else { + Gallery.l10nClient.toggle(0); + } + } + break; + case 'clear': + this.filter(false); + break; + } + }; + + // Toggle the l10nclient + this.toggle = function(state) { + switch(state) { + case 1: + $('#l10n-client-string-select, #l10n-client-string-editor, #l10n-client .labels .label').show(); + $('#l10n-client').height('22em').removeClass('hidden'); + //$('#l10n-client').slideUp(); + $('#g-minimize-l10n').text("_"); + // This CSS clashes with Gallery's CSS, probably due to + // YUI's grid / floats. + // if(!$.browser.msie) { + // $('body').css('border-bottom', '22em solid #fff'); + // } + $.cookie('Gallery_l10n_client', '1', {expires: 7, path: '/'}); + break; + case 0: + $('#l10n-client-string-select, #l10n-client-string-editor, #l10n-client .labels .label').hide(); + $('#l10n-client').height('2em').addClass('hidden'); + // TODO: Localize this message + $('#g-minimize-l10n').text(MSG_TRANSLATE_TEXT); + // if(!$.browser.msie) { + // $('body').css('border-bottom', '0px'); + // } + $.cookie('Gallery_l10n_client', '0', {expires: 7, path: '/'}); + break; + } + }; + + // Get a string from the DOM tree + this.getString = function(index, type) { + if (index < l10n_client_data.length) { + return l10n_client_data[index][type]; + } + return ""; + }; + + // Set a string in the DOM tree + this.setString = function(index, data) { + l10n_client_data[index]['translation'] = data; + }; + + // Display the source message + this.showSourceMessage = function(source, is_plural) { + if (is_plural) { + var pretty_source = $('#source-text-tmp-space').text('[one] - ' + source['one']).html(); + pretty_source += '
    '; + pretty_source += $('#source-text-tmp-space').text('[other] - ' + source['other']).html(); + } else { + var pretty_source = $('#source-text-tmp-space').text(source).html(); + } + $('#l10n-client-string-editor .source-text').html(pretty_source); + }; + this.isPluralMessage = function(message) { + return typeof(message) == 'object'; + }; + + this.updateTranslationForm = function(translation, is_plural) { + $('.translationField').addClass('hidden'); + if (is_plural) { + if (typeof(translation) != 'object') { + translation = {}; + } + var num_plural_forms = plural_forms.length; + for (var i = 0; i < num_plural_forms; i++) { + var form = plural_forms[i]; + if (translation[form] == undefined) { + translation[form] = ''; + } + $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']") + .attr('value', translation[form]); + $('#plural-' + form).removeClass('hidden'); + } + } else { + $('#l10n-edit-translation').attr('value', translation); + $('#l10n-edit-translation').removeClass('hidden'); + } + }; + + // Filter the string list by a search string + this.filter = function(search) { + if(search == false || search == '') { + $('#l10n-client #l10n-search-filter-clear').focus(); + $('#l10n-client-string-select li').show(); + $('#l10n-client #g-l10n-search').val(''); + $('#l10n-client #g-l10n-search').focus(); + } else { + if(search.length > 0) { + $('#l10n-client-string-select li').hide(); + $('#l10n-client-string-select li').each(function() { + if ($(this).val().indexOf(search) != -1) { + $(this).show(); + } + }); + $('#l10n-client #g-l10n-search').val(search); + } + } + }; + + this.copySourceText = function() { + var index = Gallery.l10nClient.selected; + if (index >= 0) { + var source = Gallery.l10nClient.getString(index, 'source'); + var is_plural = Gallery.l10nClient.isPluralMessage(source); + if (is_plural) { + if (typeof(translation) != 'object') { + translation = {}; + } + var num_plural_forms = plural_forms.length; + for (var i = 0; i < num_plural_forms; i++) { + var form = plural_forms[i]; + var text = source['other']; + if (form == 'one') { + text = source['one']; + } + $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']") + .attr('value', text); + } + } else { + $('#l10n-edit-translation').attr('value', source); + } + + } + }; + }) +}); + +// Attaches the localization editor behavior to all required fields. +Gallery.behaviors.l10nClient = function(context) { + + switch($.cookie('Gallery_l10n_client')) { + case '1': + Gallery.l10nClient.toggle(1); + break; + default: + Gallery.l10nClient.toggle(0); + break; + } + + // If the selection changes, copy string values to the source and target fields. + // Add class to indicate selected string in list widget. + $('#l10n-client-string-select li').click(function() { + $('#l10n-client-string-select li').removeClass('active'); + $(this).addClass('active'); + var index = $('#l10n-client-string-select li').index(this); + var source = Gallery.l10nClient.getString(index, 'source'); + var key = Gallery.l10nClient.getString(index, 'key'); + var is_plural = Gallery.l10nClient.isPluralMessage(source); + Gallery.l10nClient.showSourceMessage(source, is_plural); + Gallery.l10nClient.updateTranslationForm(Gallery.l10nClient.getString(index, 'translation'), is_plural); + $("#g-l10n-client-save-form input[name='l10n-message-key']").val(key); + Gallery.l10nClient.selected = index; + }); + + // When l10n_client window is clicked, toggle based on current state. + $('#g-minimize-l10n').click(function() { + if($('#l10n-client').is('.hidden')) { + Gallery.l10nClient.toggle(1); + } else { + Gallery.l10nClient.toggle(0); + } + }); + + // Close the l10n client using an AJAX call and refreshing the page + $('#g-close-l10n').click(function(event) { + $.ajax({ + type: "GET", + url: toggle_l10n_mode_url, + data: "csrf=" + csrf, + success: function() { + window.location.reload(true); + } + }); + event.preventDefault(); + }); + + // Register keybindings using jQuery hotkeys + // TODO: Either remove hotkeys code or add query.hotkeys.js. + if($.hotkeys) { + $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle');}); + $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #g-l10n-search', type:'keyup'}, function(){Gallery.l10nClient.key('clear');}); + } + + // never actually submit the form as the search is done in the browser + $('#g-l10n-search-form').submit(function() { + return false; + }); + + // Custom listener for l10n_client livesearch + $('#l10n-client #g-l10n-search').keyup(function(key) { + Gallery.l10nClient.filter($('#l10n-client #g-l10n-search').val()); + }); + + // Clear search + $('#l10n-client #l10n-search-filter-clear').click(function() { + Gallery.l10nClient.filter(false); + return false; + }); + + // Send AJAX POST data on form submit. + $('#g-l10n-client-save-form').ajaxForm({ + dataType: "json", + success: function(data) { + var source = Gallery.l10nClient.getString(Gallery.l10nClient.selected, 'source'); + var is_plural = Gallery.l10nClient.isPluralMessage(source); + var num_plural_forms = plural_forms.length; + + // Store translation in local js + var translation = {}; + var is_non_empty = false; + if (is_plural) { + for (var i = 0; i < num_plural_forms; i++) { + var form = plural_forms[i]; + translation[form] = $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']").attr('value'); + is_non_empty = is_non_empty || translation[form]; + } + } else { + translation = $('#l10n-edit-translation').attr('value'); + is_non_empty = translation; + } + Gallery.l10nClient.setString(Gallery.l10nClient.selected, translation); + + // Mark message as translated / untranslated. + var source_element = $('#l10n-client-string-select li').eq(Gallery.l10nClient.selected); + if (is_non_empty) { + source_element.removeClass('untranslated').removeClass('active').addClass('translated'); + } else { + source_element.removeClass('active').removeClass('translated').addClass('untranslated'); + } + + // Clear the translation form fields + Gallery.l10nClient.showSourceMessage('', false); + $('#g-l10n-client-save-form #l10n-edit-translation').val(''); + + for (var i = 0; i < num_plural_forms; i++) { + var form = plural_forms[i]; + $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']").val(''); + } + $("#g-l10n-client-save-form input[name='l10n-message-key']").val(''); + }, + error: function(xmlhttp) { + // TODO: Localize this message + alert('An HTTP error @status occured (or empty response).'.replace('@status', xmlhttp.status)); + } + }); + + // TODO: Add copy/clear buttons (without ajax behavior) + /* "/> + "/> + */ + // TODO: Handle plurals in copy button + + // Copy source text to translation field on button click. + $('#g-l10n-client-save-form #l10n-edit-copy').click(function() { + $('#g-l10n-client-save-form #l10n-edit-target').val($('#l10n-client-string-editor .source-text').text()); + }); + + // Clear translation field on button click. + $('#g-l10n-client-save-form #l10n-edit-clear').click(function() { + $('#g-l10n-client-save-form #l10n-edit-target').val(''); + }); +}; diff --git a/modules/gallery/libraries/Admin_View.php b/modules/gallery/libraries/Admin_View.php new file mode 100644 index 0000000..62645d1 --- /dev/null +++ b/modules/gallery/libraries/Admin_View.php @@ -0,0 +1,120 @@ +theme_name = module::get_var("gallery", "active_admin_theme"); + if (identity::active_user()->admin) { + $theme_name = Input::instance()->get("theme"); + if ($theme_name && + file_exists(THEMEPATH . $theme_name) && + strpos(realpath(THEMEPATH . $theme_name), THEMEPATH) == 0) { + $this->theme_name = $theme_name; + } + } + $this->sidebar = ""; + $this->set_global(array("theme" => $this, + "user" => identity::active_user(), + "page_type" => "admin", + "page_subtype" => $name, + "page_title" => null)); + } + + public function admin_menu() { + $menu = Menu::factory("root"); + module::event("admin_menu", $menu, $this); + + $settings_menu = $menu->get("settings_menu"); + uasort($settings_menu->elements, array("Menu", "title_comparator")); + + return $menu->render(); + } + + public function user_menu() { + $menu = Menu::factory("root") + ->css_id("g-login-menu") + ->css_class("g-inline ui-helper-clear-fix"); + module::event("user_menu", $menu, $this); + return $menu->render(); + } + + /** + * Print out any site wide status information. + */ + public function site_status() { + return site_status::get(); + } + + /** + * Print out any messages waiting for this user. + */ + public function messages() { + return message::get(); + } + + /** + * Handle all theme functions that insert module content. + */ + public function __call($function, $args) { + switch ($function) { + case "admin_credits"; + case "admin_footer": + case "admin_header_top": + case "admin_header_bottom": + case "admin_page_bottom": + case "admin_page_top": + case "admin_head": + case "body_attributes": + case "html_attributes": + $blocks = array(); + foreach (module::active() as $module) { + $helper_class = "{$module->name}_theme"; + if (class_exists($helper_class) && method_exists($helper_class, $function)) { + $blocks[] = call_user_func_array( + array($helper_class, $function), + array_merge(array($this), $args)); + } + } + + if (Session::instance()->get("debug")) { + if ($function != "admin_head") { + array_unshift( + $blocks, "
    " . + "
    $function
    "); + $blocks[] = "
    "; + } + } + + return implode("\n", $blocks); + + default: + throw new Exception("@todo UNKNOWN_THEME_FUNCTION: $function"); + } + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/Block.php b/modules/gallery/libraries/Block.php new file mode 100644 index 0000000..818af9c --- /dev/null +++ b/modules/gallery/libraries/Block.php @@ -0,0 +1,30 @@ +__toString(); + } +} diff --git a/modules/gallery/libraries/Breadcrumb.php b/modules/gallery/libraries/Breadcrumb.php new file mode 100644 index 0000000..3805c5d --- /dev/null +++ b/modules/gallery/libraries/Breadcrumb.php @@ -0,0 +1,70 @@ +title = $title; + $this->url = $url; + $this->first = false; + $this->last = false; + } + + /** + * Return an array of Breadcrumb instances build from the parents of a given item. + * The first and last Breadcrumb instances will be marked first/last as appropriate. + * Each breadcrumb will have a ?show= query parameter that refers to the id of the next + * item in line. + * + * @return array Breadcrumb instances + */ + static function array_from_item_parents($item) { + if ($item->id == item::root()->id) { + return array(); + } + + $bc = array_merge($item->parents()->as_array(), array($item)); + for ($i = 0; $i < count($bc) - 1; $i++) { + $bc[$i] = new Breadcrumb($bc[$i]->title, $bc[$i]->url("show={$bc[$i+1]->id}")); + } + $bc[$i] = new Breadcrumb($item->title, $item->url()); + + $bc[0]->set_first(); + end($bc)->set_last(); + return $bc; + } + + public function set_first() { + $this->first = true; + return $this; + } + + public function set_last() { + $this->last = true; + return $this; + } +} diff --git a/modules/gallery/libraries/Form_Script.php b/modules/gallery/libraries/Form_Script.php new file mode 100644 index 0000000..2b5ec3e --- /dev/null +++ b/modules/gallery/libraries/Form_Script.php @@ -0,0 +1,66 @@ + false, + "type" => "script", + "url" => "", + "text" => ""); + + public function __construct($name) { + // Set dummy data so we don"t get errors + $this->attr["action"] = ""; + $this->attr["method"] = "post"; + $this->data["name"] = $name; + } + + public function __get($key) { + return isset($this->data[$key]) ? $this->data[$key] : null; + } + + /** + * Sets url attribute + */ + public function url($url) { + $this->data["url"] = $url; + + return $this; + } + + public function text($script_text) { + $this->data["text"] = $script_text; + + return $this; + } + + public function render($template="forge_template", $custom=false) { + $script = array(); + if (!empty($this->data["url"])) { + $script[] = html::script($this->data["url"]); + } + + if (!empty($this->data["text"])) { + $script[] = "\n"; + } + + return implode("\n", $script); + } + +} \ No newline at end of file diff --git a/modules/gallery/libraries/Form_Uploadify.php b/modules/gallery/libraries/Form_Uploadify.php new file mode 100644 index 0000000..1e58018 --- /dev/null +++ b/modules/gallery/libraries/Form_Uploadify.php @@ -0,0 +1,72 @@ + false, + "type" => "UNKNOWN", + "url" => "", + "text" => ""); + + public function __construct($name) { + parent::__construct($name); + $this->data["script_data"] = array( + "g3sid" => Session::instance()->id(), + "user_agent" => Input::instance()->server("HTTP_USER_AGENT"), + "csrf" => access::csrf_token()); + } + + public function album(Item_Model $album) { + $this->data["album"] = $album; + return $this; + } + + public function script_data($key, $value) { + $this->data["script_data"][$key] = $value; + } + + public function render() { + $v = new View("form_uploadify.html"); + $v->album = $this->data["album"]; + $v->script_data = $this->data["script_data"]; + $v->simultaneous_upload_limit = module::get_var("gallery", "simultaneous_upload_limit"); + $v->movies_allowed = movie::allow_uploads(); + $v->extensions = legal_file::get_filters(); + $v->suhosin_session_encrypt = (bool) ini_get("suhosin.session.encrypt"); + + list ($toolkit_max_filesize_bytes, $toolkit_max_filesize) = graphics::max_filesize(); + + $upload_max_filesize = trim(ini_get("upload_max_filesize")); + $upload_max_filesize_bytes = num::convert_to_bytes($upload_max_filesize); + + if ($upload_max_filesize_bytes < $toolkit_max_filesize_bytes) { + $v->size_limit_bytes = $upload_max_filesize_bytes; + $v->size_limit = $upload_max_filesize; + } else { + $v->size_limit_bytes = $toolkit_max_filesize_bytes; + $v->size_limit = $toolkit_max_filesize; + } + + return $v; + } + + public function validate() { + return true; + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/Form_Uploadify_buttons.php b/modules/gallery/libraries/Form_Uploadify_buttons.php new file mode 100644 index 0000000..2e0327e --- /dev/null +++ b/modules/gallery/libraries/Form_Uploadify_buttons.php @@ -0,0 +1,25 @@ +translate($message, $options); +} + +/** + * Translates a localizable message with plural forms. + * @param $singular String The message to be translated. E.g. "There is one album." + * @param $plural String The plural message to be translated. E.g. + * "There are %count albums." + * @param $count Number The number which is inserted for the %count placeholder and + * which is used to select the proper plural form ($singular or $plural). + * @param $options array (optional) Options array for key value pairs which are used + * for pluralization and interpolation. Special key: "locale" to override the + * currently configured locale. + * @return String The translated message string. + */ +function t2($singular, $plural, $count, $options=array()) { + return Gallery_I18n::instance()->translate(array("one" => $singular, "other" => $plural), + array_merge($options, array("count" => $count))); +} + +class Gallery_I18n_Core { + private static $_instance; + private $_config = array(); + private $_call_log = array(); + private $_cache = array(); + + private function __construct($config) { + $this->_config = $config; + $this->locale($config['default_locale']); + } + + public static function instance($config=null) { + if (self::$_instance == NULL || isset($config)) { + $config = isset($config) ? $config : Kohana::config('locale'); + if (empty($config['default_locale'])) { + $config['default_locale'] = module::get_var('gallery', 'default_locale'); + } + self::$_instance = new Gallery_I18n_Core($config); + } + + return self::$_instance; + } + + public function locale($locale=null) { + if ($locale) { + $this->_config['default_locale'] = $locale; + $php_locale = setlocale(LC_ALL, 0); + list ($php_locale, $unused) = explode('.', $php_locale . '.'); + if ($php_locale != $locale) { + // Attempt to set PHP's locale as well (for number formatting, collation, etc.) + $locale_prefs = array($locale); + // Try appending some character set names; some systems (like FreeBSD) need this. + // Some systems require a format with hyphen (eg. Gentoo) and others without (eg. FreeBSD). + $charsets = array('utf8', 'UTF-8', 'UTF8', 'ISO8859-1', 'ISO-8859-1'); + if (substr($locale, 0, 2) != 'en') { + $charsets = array_merge($charsets, array( + 'EUC', 'Big5', 'euc', 'ISO8859-2', 'ISO8859-5', 'ISO8859-7', + 'ISO8859-9', 'ISO-8859-2', 'ISO-8859-5', 'ISO-8859-7', 'ISO-8859-9')); + } + foreach ($charsets as $charset) { + $locale_prefs[] = $locale . '.' . $charset; + } + $locale_prefs[] = 'en_US'; + $php_locale = setlocale(LC_ALL, $locale_prefs); + } + if (is_string($php_locale) && substr($php_locale, 0, 2) == 'tr') { + // Make PHP 5 work with Turkish (the localization results are mixed though). + // Hack for http://bugs.php.net/18556 + setlocale(LC_CTYPE, 'C'); + } + } + return $this->_config['default_locale']; + } + + public function is_rtl($locale=null) { + $is_rtl = !empty($this->_config["force_rtl"]); + if (empty($is_rtl)) { + $locale or $locale = $this->locale(); + list ($language, $territory) = explode('_', $locale . "_"); + $is_rtl = in_array($language, array("he", "fa", "ar")); + } + return $is_rtl; + } + + /** + * Translates a localizable message. + * + * Security: + * The returned string is safe for use in HTML (it contains a safe subset of HTML and + * interpolation parameters are converted to HTML entities). + * For use in JavaScript, please call ->for_js() on it. + * + * @param $message String|array The message to be translated. E.g. "Hello world" + * or array("one" => "One album", "other" => "%count albums") + * @param $options array (optional) Options array for key value pairs which are used + * for pluralization and interpolation. Special keys are "count" and "locale", + * the latter to override the currently configured locale. + * @return String The translated message string. + */ + public function translate($message, $options=array()) { + $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale']; + $count = isset($options['count']) ? $options['count'] : null; + $values = $options; + unset($values['locale']); + $this->log($message, $options); + + $entry = $this->lookup($locale, $message); + + if (null === $entry) { + // Default to the root locale. + $entry = $message; + $locale = $this->_config['root_locale']; + } + + $entry = $this->pluralize($locale, $entry, $count); + + $entry = $this->interpolate($locale, $entry, $values); + + return SafeString::of_safe_html($entry); + } + + private function lookup($locale, $message) { + if (!isset($this->_cache[$locale])) { + $this->_cache[$locale] = self::load_translations($locale); + } + + $key = self::get_message_key($message); + + if (isset($this->_cache[$locale][$key])) { + return $this->_cache[$locale][$key]; + } else { + return null; + } + } + + private static function load_translations($locale) { + $cache_key = "translation|" . $locale; + $cache = Cache::instance(); + $translations = $cache->get($cache_key); + if (!isset($translations) || !is_array($translations)) { + $translations = array(); + foreach (db::build() + ->select("key", "translation") + ->from("incoming_translations") + ->where("locale", "=", $locale) + ->execute() as $row) { + $translations[$row->key] = unserialize($row->translation); + } + + // Override incoming with outgoing... + foreach (db::build() + ->select("key", "translation") + ->from("outgoing_translations") + ->where("locale", "=", $locale) + ->execute() as $row) { + $translations[$row->key] = unserialize($row->translation); + } + + $cache->set($cache_key, $translations, array("translation"), 0); + } + return $translations; + } + + public function has_translation($message, $options=null) { + $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale']; + + $entry = $this->lookup($locale, $message); + + if (null === $entry) { + return false; + } else if (!is_array($message)) { + return $entry !== ''; + } else { + if (!is_array($entry) || empty($entry)) { + return false; + } + // It would be better to verify that all the locale's plural forms have a non-empty + // translation, but this is fine for now. + foreach ($entry as $value) { + if ($value === '') { + return false; + } + } + return true; + } + } + + static function get_message_key($message) { + $as_string = is_array($message) ? implode('|', $message) : $message; + return md5($as_string); + } + + static function is_plural_message($message) { + return is_array($message); + } + + private function interpolate($locale, $string, $key_values) { + // TODO: Handle locale specific number formatting. + + // Replace x_y before replacing x. + krsort($key_values, SORT_STRING); + + $keys = array(); + $values = array(); + foreach ($key_values as $key => $value) { + $keys[] = "%$key"; + $values[] = new SafeString($value); + } + return str_replace($keys, $values, $string); + } + + private function pluralize($locale, $entry, $count) { + if (!is_array($entry)) { + return $entry; + } + + $plural_key = self::get_plural_key($locale, $count); + if (!isset($entry[$plural_key])) { + // Fallback to the default plural form. + $plural_key = 'other'; + } + + if (isset($entry[$plural_key])) { + return $entry[$plural_key]; + } else { + // Fallback to just any plural form. + list ($plural_key, $string) = each($entry); + return $string; + } + } + + private function log($message, $options) { + $key = self::get_message_key($message); + isset($this->_call_log[$key]) or $this->_call_log[$key] = array($message, $options); + } + + public function call_log() { + return $this->_call_log; + } + + public static function clear_cache($locale=null) { + $cache = Cache::instance(); + if ($locale) { + $cache->delete("translation|" . $locale); + } else { + $cache->delete_tag("translation"); + } + } + + private static function get_plural_key($locale, $count) { + $parts = explode('_', $locale); + $language = $parts[0]; + + // Data from CLDR 1.6 (http://unicode.org/cldr/data/common/supplemental/plurals.xml). + // Docs: http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html + switch ($language) { + case 'az': + case 'fa': + case 'hu': + case 'ja': + case 'ko': + case 'my': + case 'to': + case 'tr': + case 'vi': + case 'yo': + case 'zh': + case 'bo': + case 'dz': + case 'id': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ms': + case 'th': + return 'other'; + + case 'ar': + if ($count == 0) { + return 'zero'; + } else if ($count == 1) { + return 'one'; + } else if ($count == 2) { + return 'two'; + } else if (is_int($count) && ($i = $count % 100) >= 3 && $i <= 10) { + return 'few'; + } else if (is_int($count) && ($i = $count % 100) >= 11 && $i <= 99) { + return 'many'; + } else { + return 'other'; + } + + case 'pt': + case 'am': + case 'bh': + case 'fil': + case 'tl': + case 'guw': + case 'hi': + case 'ln': + case 'mg': + case 'nso': + case 'ti': + case 'wa': + if ($count == 0 || $count == 1) { + return 'one'; + } else { + return 'other'; + } + + case 'fr': + if ($count >= 0 and $count < 2) { + return 'one'; + } else { + return 'other'; + } + + case 'lv': + if ($count == 0) { + return 'zero'; + } else if ($count % 10 == 1 && $count % 100 != 11) { + return 'one'; + } else { + return 'other'; + } + + case 'ga': + case 'se': + case 'sma': + case 'smi': + case 'smj': + case 'smn': + case 'sms': + if ($count == 1) { + return 'one'; + } else if ($count == 2) { + return 'two'; + } else { + return 'other'; + } + + case 'ro': + case 'mo': + if ($count == 1) { + return 'one'; + } else if (is_int($count) && $count == 0 && ($i = $count % 100) >= 1 && $i <= 19) { + return 'few'; + } else { + return 'other'; + } + + case 'lt': + if (is_int($count) && $count % 10 == 1 && $count % 100 != 11) { + return 'one'; + } else if (is_int($count) && ($i = $count % 10) >= 2 && $i <= 9 && ($i = $count % 100) < 11 && $i > 19) { + return 'few'; + } else { + return 'other'; + } + + case 'hr': + case 'ru': + case 'sr': + case 'uk': + case 'be': + case 'bs': + case 'sh': + if (is_int($count) && $count % 10 == 1 && $count % 100 != 11) { + return 'one'; + } else if (is_int($count) && ($i = $count % 10) >= 2 && $i <= 4 && ($i = $count % 100) < 12 && $i > 14) { + return 'few'; + } else if (is_int($count) && ($count % 10 == 0 || (($i = $count % 10) >= 5 && $i <= 9) || (($i = $count % 100) >= 11 && $i <= 14))) { + return 'many'; + } else { + return 'other'; + } + + case 'cs': + case 'sk': + if ($count == 1) { + return 'one'; + } else if (is_int($count) && $count >= 2 && $count <= 4) { + return 'few'; + } else { + return 'other'; + } + + case 'pl': + if ($count == 1) { + return 'one'; + } else if (is_int($count) && ($i = $count % 10) >= 2 && $i <= 4 && + ($i = $count % 100) < 12 && $i > 14 && ($i = $count % 100) < 22 && $i > 24) { + return 'few'; + } else { + return 'other'; + } + + case 'sl': + if ($count % 100 == 1) { + return 'one'; + } else if ($count % 100 == 2) { + return 'two'; + } else if (is_int($count) && ($i = $count % 100) >= 3 && $i <= 4) { + return 'few'; + } else { + return 'other'; + } + + case 'mt': + if ($count == 1) { + return 'one'; + } else if ($count == 0 || is_int($count) && ($i = $count % 100) >= 2 && $i <= 10) { + return 'few'; + } else if (is_int($count) && ($i = $count % 100) >= 11 && $i <= 19) { + return 'many'; + } else { + return 'other'; + } + + case 'mk': + if ($count % 10 == 1) { + return 'one'; + } else { + return 'other'; + } + + case 'cy': + if ($count == 1) { + return 'one'; + } else if ($count == 2) { + return 'two'; + } else if ($count == 8 || $count == 11) { + return 'many'; + } else { + return 'other'; + } + + default: // en, de, etc. + return $count == 1 ? 'one' : 'other'; + } + } +} diff --git a/modules/gallery/libraries/Gallery_View.php b/modules/gallery/libraries/Gallery_View.php new file mode 100644 index 0000000..8f02b53 --- /dev/null +++ b/modules/gallery/libraries/Gallery_View.php @@ -0,0 +1,243 @@ +theme_name}/$path"; + return $absolute_url ? url::abs_file($arg) : url::file($arg); + } + + /** + * Set up the data and render a pager. + * + * See themes/wind/views/pager.html for documentation on the variables generated here. + */ + public function paginator() { + $v = new View("paginator.html"); + $v->page_type = $this->page_type; + $v->page_subtype = $this->page_subtype; + $v->first_page_url = null; + $v->previous_page_url = null; + $v->next_page_url = null; + $v->last_page_url = null; + + if ($this->page_type == "collection") { + $v->page = $this->page; + $v->max_pages = $this->max_pages; + $v->total = $this->children_count; + + if ($this->page != 1) { + $v->first_page_url = url::site(url::merge(array("page" => 1))); + $v->previous_page_url = url::site(url::merge(array("page" => $this->page - 1))); + } + + if ($this->page != $this->max_pages) { + $v->next_page_url = url::site(url::merge(array("page" => $this->page + 1))); + $v->last_page_url = url::site(url::merge(array("page" => $this->max_pages))); + } + + $v->first_visible_position = ($this->page - 1) * $this->page_size + 1; + $v->last_visible_position = min($this->page * $this->page_size, $v->total); + } else if ($this->page_type == "item") { + $v->position = $this->position; + $v->total = $this->sibling_count; + if ($this->previous_item) { + $v->previous_page_url = $this->previous_item->url(); + } + + if ($this->next_item) { + $v->next_page_url = $this->next_item->url(); + } + } + + return $v; + } + + /** + * Begin gather up scripts or css files so that they can be combined into a single request. + * + * @param $types a comma separated list of types to combine, eg "script,css" + */ + public function start_combining($types) { + if (gallery::allow_css_and_js_combining()) { + foreach (explode(",", $types) as $type) { + $this->combine_queue[$type] = array(); + } + } + } + + /** + * If script combining is enabled, add this script to the list of scripts that will be + * combined into a single script element. When combined, the order of scripts is preserved. + * + * @param $file the file name or path of the script to include. If a path is specified then + * it needs to be relative to DOCROOT. Just specifying a file name will result + * in searching Kohana's cascading file system. + * @param $group the group of scripts to combine this with. defaults to "core" + */ + public function script($file, $group="core") { + if (($path = gallery::find_file("js", $file, false))) { + if (isset($this->combine_queue["script"])) { + $this->combine_queue["script"][$group][$path] = 1; + } else { + return html::script($path); + } + } else { + Kohana_Log::add("error", "Can't find script file: $file"); + } + } + + /** + * If css combining is enabled, add this css to the list of css that will be + * combined into a single style element. When combined, the order of style elements + * is preserved. + * + * @param $file the file name or path of the css to include. If a path is specified then + * it needs to be relative to DOCROOT. Just specifying a file name will result + * in searching Kohana's cascading file system. + * @param $group the group of css to combine this with. defaults to "core" + */ + public function css($file, $group="core") { + if (($path = gallery::find_file("css", $file, false))) { + if (isset($this->combine_queue["css"])) { + $this->combine_queue["css"][$group][$path] = 1; + } else { + return html::stylesheet($path); + } + } else { + Kohana_Log::add("error", "Can't find css file: $file"); + } + } + + /** + * Combine a series of files into a single one and cache it in the database. + * @param $type the data type (script or css) + * @param $group the group of scripts or css we want + */ + public function get_combined($type, $group="core") { + $links = array(); + + if (empty($this->combine_queue[$type][$group])) { + return; + } + + // Include the url in the cache key so that if the Gallery moves, we don't use old cached + // entries. + $key = array(url::abs_file("")); + + foreach (array_keys($this->combine_queue[$type][$group]) as $path) { + $stats = stat($path); + // 7 == size, 9 == mtime, see http://php.net/stat + $key[] = "$path $stats[7] $stats[9]"; + } + + $key = md5(join(" ", $key)); + $cache = Cache::instance(); + $contents = $cache->get($key); + + if (empty($contents)) { + $combine_data = new stdClass(); + $combine_data->type = $type; + $combine_data->contents = $this->combine_queue[$type][$group]; + module::event("before_combine", $combine_data); + + $contents = ""; + foreach (array_keys($this->combine_queue[$type][$group]) as $path) { + if ($type == "css") { + $contents .= "/* $path */\n" . $this->process_css($path) . "\n"; + } else { + $contents .= "/* $path */\n" . file_get_contents($path) . "\n"; + } + } + + $combine_data = new stdClass(); + $combine_data->type = $type; + $combine_data->contents = $contents; + module::event("after_combine", $combine_data); + + $cache->set($key, $combine_data->contents, array($type), 30 * 84600); + + $use_gzip = function_exists("gzencode") && + (int) ini_get("zlib.output_compression") === 0; + if ($use_gzip) { + $cache->set("{$key}_gz", gzencode($combine_data->contents, 9, FORCE_GZIP), + array($type, "gzip"), 30 * 84600); + } + + } + + unset($this->combine_queue[$type][$group]); + if (empty($this->combine_queue[$type])) { + unset($this->combine_queue[$type]); + } + + if ($type == "css") { + return html::stylesheet("combined/css/$key", "screen,print,projection", true); + } else { + return html::script("combined/javascript/$key", true); + } + } + + /** + * Convert relative references inside a CSS file to absolute ones so that when it's served from + * a new location as part of a combined bundle the references are still correct. + * @param string the path to the css file + */ + private function process_css($css_file) { + static $PATTERN = "#url\(\s*['|\"]{0,1}(.*?)['|\"]{0,1}\s*\)#"; + $docroot_length = strlen(DOCROOT); + + $css = file_get_contents($css_file); + if (preg_match_all($PATTERN, $css, $matches, PREG_SET_ORDER)) { + $search = $replace = array(); + foreach ($matches as $match) { + $relative = dirname($css_file) . "/$match[1]"; + if (!empty($relative)) { + $search[] = $match[0]; + $replace[] = "url('" . url::abs_file($relative) . "')"; + } else { + Kohana_Log::add("error", "Missing URL reference '{$match[1]}' in CSS file '$css_file'"); + + } + } + $replace = str_replace(DIRECTORY_SEPARATOR, "/", $replace); + $css = str_replace($search, $replace, $css); + } + $imports = preg_match_all("#@import\s*['|\"]{0,1}(.*?)['|\"]{0,1};#", + $css, $matches, PREG_SET_ORDER); + + if ($imports) { + $search = $replace = array(); + foreach ($matches as $match) { + $search[] = $match[0]; + $replace[] = $this->process_css(dirname($css_file) . "/$match[1]"); + } + $css = str_replace($search, $replace, $css); + } + + return $css; + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/IdentityProvider.php b/modules/gallery/libraries/IdentityProvider.php new file mode 100644 index 0000000..525e169 --- /dev/null +++ b/modules/gallery/libraries/IdentityProvider.php @@ -0,0 +1,283 @@ +clear("identity"); + } + + /** + * Return a commen confirmation message + */ + static function confirmation_message() { + return t("Are you sure you want to change your Identity Provider? Continuing will delete all existing users."); + } + + static function change_provider($new_provider) { + if (!identity::active_user()->admin && PHP_SAPI != "cli") { + // Below, the active user is set to the primary admin. + access::forbidden(); + } + + $current_provider = module::get_var("gallery", "identity_provider"); + if (!empty($current_provider)) { + module::uninstall($current_provider); + } + + try { + IdentityProvider::reset(); + $provider = new IdentityProvider($new_provider); + + module::set_var("gallery", "identity_provider", $new_provider); + + if (class_exists("{$new_provider}_installer") && + method_exists("{$new_provider}_installer", "initialize")) { + call_user_func("{$new_provider}_installer::initialize"); + } + + if (!$provider->admin_user()) { + throw new Exception("IdentityProvider $new_provider: Couldn't find the admin user!"); + } + + module::event("identity_provider_changed", $current_provider, $new_provider); + + identity::set_active_user($provider->admin_user()); + Session::instance()->regenerate(); + } catch (Exception $e) { + static $restore_already_running; + + // In case of error, make an attempt to restore the old provider. Since that's calling into + // this function again and can fail, we should be sure not to get into an infinite recursion. + if (!$restore_already_running) { + $restore_already_running = true; + + // Make sure new provider is not in the database + try { + module::uninstall($new_provider); + } catch (Exception $e2) { + Kohana_Log::add("error", "Error uninstalling failed new provider\n" . + $e2->getMessage() . "\n" . $e2->getTraceAsString()); + } + + try { + // Lets reset to the current provider so that the gallery installation is still + // working. + module::set_var("gallery", "identity_provider", null); + IdentityProvider::change_provider($current_provider); + module::activate($current_provider); + } catch (Exception $e2) { + Kohana_Log::add("error", "Error restoring original identity provider\n" . + $e2->getMessage() . "\n" . $e2->getTraceAsString()); + } + + message::error( + t("Error attempting to enable \"%new_provider\" identity provider, reverted to \"%old_provider\" identity provider", + array("new_provider" => $new_provider, "old_provider" => $current_provider))); + + $restore_already_running = false; + } + throw $e; + } + } + + /** + * Loads the configured driver and validates it. + * + * @return void + */ + public function __construct($config=null) { + if (empty($config)) { + $config = module::get_var("gallery", "identity_provider", "user"); + } + + // Test the config group name + if (($this->config = Kohana::config("identity." . $config)) === NULL) { + throw new Exception("@todo NO_USER_LIBRARY_CONFIGURATION_FOR: $config"); + } + + // Set driver name + $driver = "IdentityProvider_" . ucfirst($this->config["driver"]) ."_Driver"; + + // Load the driver + if ( ! Kohana::auto_load($driver)) { + throw new Kohana_Exception("core.driver_not_found", $this->config["driver"], + get_class($this)); + } + + // Initialize the driver + $this->driver = new $driver($this->config["params"]); + + // Validate the driver + if ( !($this->driver instanceof IdentityProvider_Driver)) { + throw new Kohana_Exception("core.driver_implements", $this->config["driver"], + get_class($this), "IdentityProvider_Driver"); + } + + Kohana_Log::add("debug", "Identity Library initialized"); + } + + /** + * Determine if if the current driver supports updates. + * + * @return boolean true if the driver supports updates; false if read only + */ + public function is_writable() { + return !empty($this->config["allow_updates"]); + } + + /** + * @see IdentityProvider_Driver::guest. + */ + public function guest() { + return $this->driver->guest(); + } + + /** + * @see IdentityProvider_Driver::admin_user. + */ + public function admin_user() { + return $this->driver->admin_user(); + } + + /** + * @see IdentityProvider_Driver::create_user. + */ + public function create_user($name, $full_name, $password, $email) { + return $this->driver->create_user($name, $full_name, $password, $email); + } + + /** + * @see IdentityProvider_Driver::is_correct_password. + */ + public function is_correct_password($user, $password) { + return $this->driver->is_correct_password($user, $password); + } + + /** + * @see IdentityProvider_Driver::lookup_user. + */ + public function lookup_user($id) { + return $this->driver->lookup_user($id); + } + + /** + * @see IdentityProvider_Driver::lookup_user_by_name. + */ + public function lookup_user_by_name($name) { + return $this->driver->lookup_user_by_name($name); + } + + /** + * @see IdentityProvider_Driver::create_group. + */ + public function create_group($name) { + return $this->driver->create_group($name); + } + + /** + * @see IdentityProvider_Driver::everybody. + */ + public function everybody() { + return $this->driver->everybody(); + } + + /** + * @see IdentityProvider_Driver::registered_users. + */ + public function registered_users() { + return $this->driver->registered_users(); + } + + /** + * @see IdentityProvider_Driver::lookup_group. + */ + public function lookup_group($id) { + return $this->driver->lookup_group($id); + } + + /** + * @see IdentityProvider_Driver::lookup_group_by_name. + */ + public function lookup_group_by_name($name) { + return $this->driver->lookup_group_by_name($name); + } + + /** + * @see IdentityProvider_Driver::get_user_list. + */ + public function get_user_list($ids) { + return $this->driver->get_user_list($ids); + } + + /** + * @see IdentityProvider_Driver::groups. + */ + public function groups() { + return $this->driver->groups(); + } + + /** + * @see IdentityProvider_Driver::add_user_to_group. + */ + public function add_user_to_group($user, $group) { + return $this->driver->add_user_to_group($user, $group); + } + + /** + * @see IdentityProvider_Driver::remove_user_to_group. + */ + public function remove_user_from_group($user, $group) { + return $this->driver->remove_user_from_group($user, $group); + } +} // End Identity diff --git a/modules/gallery/libraries/InPlaceEdit.php b/modules/gallery/libraries/InPlaceEdit.php new file mode 100644 index 0000000..cd177c2 --- /dev/null +++ b/modules/gallery/libraries/InPlaceEdit.php @@ -0,0 +1,91 @@ +initial_value = $initial_value; + $instance->form = array("input" => $initial_value); + $instance->errors = array("input" => ""); + + return $instance; + } + + public function action($action) { + $this->action = $action; + return $this; + } + + public function rules($rules) { + $this->rules += $rules; + return $this; + } + + public function messages($messages) { + $this->messages += $messages; + return $this; + } + + public function callback($callback) { + $this->callback = $callback; + return $this; + } + + public function validate() { + $post = Validation::factory($_POST); + + if (!empty($this->callback)) { + $post->add_callbacks("input", $this->callback); + } + + foreach ($this->rules as $rule) { + $post->add_rules("input", $rule); + } + + $valid = $post->validate(); + $this->form = array_merge($this->form, $post->as_array()); + $this->errors = array_merge($this->errors, $post->errors()); + return $valid; + } + + public function render() { + $v = new View("in_place_edit.html"); + $v->action = $this->action; + $v->form = $this->form; + $v->errors = $this->errors; + foreach ($v->errors as $key => $error) { + if (!empty($error)) { + $v->errors[$key] = $this->messages[$error]; + } + } + return $v->render(); + } + + public function value() { + return $this->form["input"]; + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/MY_Database.php b/modules/gallery/libraries/MY_Database.php new file mode 100644 index 0000000..33759b6 --- /dev/null +++ b/modules/gallery/libraries/MY_Database.php @@ -0,0 +1,101 @@ +config['benchmark'] = true; + } + } + + /** + * Parse the query string and convert any strings of the form `\([a-zA-Z0-9_]*?)\] + * table prefix . $1 + */ + public function query($sql) { + if (!empty($sql)) { + $sql = $this->add_table_prefixes($sql); + } + return parent::query($sql); + } + + public function add_table_prefixes($sql) { + $prefix = $this->config["table_prefix"]; + if (strpos($sql, "SHOW TABLES") === 0) { + /* + * Don't ignore "show tables", otherwise we could have a infinite + * @todo this may have to be changed if we support more than mysql + */ + return $sql; + } else if (strpos($sql, "CREATE TABLE") === 0) { + // Creating a new table; add it to the table cache. + $open_brace = strpos($sql, "{") + 1; + $close_brace = strpos($sql, "}", $open_brace); + $name = substr($sql, $open_brace, $close_brace - $open_brace); + $this->_table_names["{{$name}}"] = "`{$prefix}$name`"; + } else if (strpos($sql, "RENAME TABLE") === 0) { + // Renaming a table; add it to the table cache. + // You must use the form "TO {new_table_name}" exactly for this to work. + $open_brace = strpos($sql, "TO {") + 4; + $close_brace = strpos($sql, "}", $open_brace); + $name = substr($sql, $open_brace, $close_brace - $open_brace); + $this->_table_names["{{$name}}"] = "`{$prefix}$name`"; + } + + if (!isset($this->_table_names)) { + // This should only run once on the first query + $this->_table_names = array(); + foreach($this->list_tables() as $table_name) { + $this->_table_names["{{$table_name}}"] = "`{$prefix}{$table_name}`"; + } + } + + return strtr($sql, $this->_table_names); + } + + /** + * This is used by the unit test code to switch the active database connection. + */ + static function set_default_instance($db) { + self::$instances["default"] = $db; + } + + /** + * Escape LIKE queries, add wildcards. In MySQL queries using LIKE, _ and % characters are + * treated as wildcards similar to ? and *, respectively. Therefore, we need to escape _, %, + * and \ (the escape character itself). + */ + static function escape_for_like($value) { + // backslash must go first to avoid double-escaping + return addcslashes($value, '\_%'); + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/MY_Forge.php b/modules/gallery/libraries/MY_Forge.php new file mode 100644 index 0000000..635dc2d --- /dev/null +++ b/modules/gallery/libraries/MY_Forge.php @@ -0,0 +1,45 @@ +hidden("csrf")->value(access::csrf_token()); + } + + /** + * Use our own template + */ + public function render($template="form.html", $custom=false) { + return parent::render($template, $custom); + } + + /** + * Validate our CSRF value as a mandatory part of all form validation. + */ + public function validate() { + $status = parent::validate(); + access::verify_csrf(); + return $status; + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/MY_Input.php b/modules/gallery/libraries/MY_Input.php new file mode 100644 index 0000000..2f0a727 --- /dev/null +++ b/modules/gallery/libraries/MY_Input.php @@ -0,0 +1,31 @@ +getCode(), strip_tags($e->getMessage()), + $e->getFile(), $e->getLine(), + $e->getTraceAsString()); + } + } + + /** + * @see Kohana_Exception::dump() + */ + public static function dump($value, $length=128, $max_level=5) { + return self::safe_dump($value, null, $length, $max_level); + } + + /** + * A safer version of dump(), eliding sensitive information in the dumped + * data, such as session ids and passwords / hashes. + */ + public static function safe_dump($value, $key, $length=128, $max_level=5) { + return parent::dump(self::_sanitize_for_dump($value, $key, $max_level), $length, $max_level); + } + + /** + * Elides sensitive data which shouldn't be echoed to the client, + * such as passwords, and other secrets. + */ + /* Visible for testing*/ static function _sanitize_for_dump($value, $key=null, $max_level) { + // Better elide too much than letting something through. + // Note: unanchored match is intended. + if (!$max_level) { + // Too much recursion; give up. We gave it our best shot. + return $value; + } + + $sensitive_info_pattern = + '/(password|pass|email|hash|private_key|session_id|session|g3sid|csrf|secret)/i'; + if (preg_match($sensitive_info_pattern, $key) || + (is_string($value) && preg_match('/[a-f0-9]{20,}/i', $value))) { + return 'removed for display'; + } else if (is_object($value)) { + if ($value instanceof Database) { + // Elide database password, host, name, user, etc. + return get_class($value) . ' object - details omitted for display'; + } else if ($value instanceof User_Model) { + return get_class($value) . ' object for "' . $value->name . '" - details omitted for display'; + } + return self::_sanitize_for_dump((array) $value, $key, $max_level - 1); + } else if (is_array($value)) { + $result = array(); + foreach ($value as $k => $v) { + $actual_key = $k; + $key_for_display = $k; + if ($k[0] === "\x00") { + // Remove the access level from the variable name + $actual_key = substr($k, strrpos($k, "\x00") + 1); + $access = $k[1] === '*' ? 'protected' : 'private'; + $key_for_display = "$access: $actual_key"; + } + if (is_object($v)) { + $key_for_display .= ' (type: ' . get_class($v) . ')'; + } + $result[$key_for_display] = self::_sanitize_for_dump($v, $actual_key, $max_level - 1); + } + } else { + $result = $value; + } + return $result; + } + + public static function debug_path($file) { + return html::clean(parent::debug_path($file)); + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/MY_ORM.php b/modules/gallery/libraries/MY_ORM.php new file mode 100644 index 0000000..6e538d5 --- /dev/null +++ b/modules/gallery/libraries/MY_ORM.php @@ -0,0 +1,52 @@ + $value) { + View::$global_data[$key2] = $value; + } + } else { + View::$global_data[$key] = $value; + } + } + + public function is_set($key=null) { + return parent::is_set($key) ? true : array_key_exists($key, View::$global_data); + } + + /** + * Completely replace View_Core::__get() so that local data trumps global data, trumps members. + * This simulates the Kohana 2.3 behavior. + */ + public function &__get($key) { + if (isset($this->kohana_local_data[$key])) { + return $this->kohana_local_data[$key]; + } else if (isset(View::$global_data[$key])) { + return View::$global_data[$key]; + } else if (isset($this->$key)) { + return $this->$key; + } else { + throw new Kohana_Exception('Undefined view variable: :var', array(':var' => $key)); + } + } + + /** + * Override View_Core::__construct so that we can set the csrf value into all views. + * + * @see View_Core::__construct + */ + public function __construct($name = NULL, $data = NULL, $type = NULL) { + parent::__construct($name, $data, $type); + $this->set_global("csrf", access::csrf_token()); + } + + /** + * Override View_Core::render so that we trap errors stemming from bad PHP includes and show a + * visible stack trace to help developers. + * + * @see View_Core::render + */ + public function render($print=false, $renderer=false, $modifier=false) { + try { + $this->kohana_local_data = array_merge(View::$global_data, $this->kohana_local_data); + return parent::render($print, $renderer, $modifier); + } catch (ORM_Validation_Exception $e) { + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); + return ""; + } catch (Exception $e) { + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + return ""; + } + } +} diff --git a/modules/gallery/libraries/Menu.php b/modules/gallery/libraries/Menu.php new file mode 100644 index 0000000..24a05cd --- /dev/null +++ b/modules/gallery/libraries/Menu.php @@ -0,0 +1,257 @@ +type = $type; + } + + /** + * Set the id + * @chainable + */ + public function id($id) { + $this->id = $id; + return $this; + } + + /** + * Set the label + * @chainable + */ + public function label($label) { + // Guard against developers who forget to internationalize label strings + if (!($label instanceof SafeString)) { + $label = new SafeString($label); + } + + $this->label = $label; + return $this; + } + + /** + * Set the url + * @chainable + */ + public function url($url) { + $this->url = $url; + return $this; + } + + /** + * Set the css id + * @chainable + */ + public function css_id($css_id) { + $this->css_id = $css_id; + return $this; + } + + /** + * Set the css class + * @chainable + */ + public function css_class($css_class) { + $this->css_class = $css_class; + return $this; + } + + /** + * Specifiy a view for this menu item + * @chainable + */ + public function view($view) { + $this->view = $view; + return $this; + } + +} + +/** + * Menu element that provides a link to a new page. + */ +class Menu_Element_Link extends Menu_Element { + public function render() { + $view = new View(isset($this->view) ? $this->view : "menu_link.html"); + $view->menu = $this; + return $view; + } +} + +/** + * Menu element that provides an AJAX link. + */ +class Menu_Element_Ajax_Link extends Menu_Element { + public $ajax_handler; + + /** + * Set the AJAX handler + * @chainable + */ + public function ajax_handler($ajax_handler) { + $this->ajax_handler = $ajax_handler; + return $this; + } + + public function render() { + $view = new View(isset($this->view) ? $this->view : "menu_ajax_link.html"); + $view->menu = $this; + return $view; + } +} + +/** + * Menu element that provides a pop-up dialog + */ +class Menu_Element_Dialog extends Menu_Element { + public function render() { + $view = new View(isset($this->view) ? $this->view : "menu_dialog.html"); + $view->menu = $this; + return $view; + } +} + +/** + * Root menu or submenu + */ +class Menu_Core extends Menu_Element { + public $elements; + public $is_root = false; + + /** + * Return an instance of a Menu_Element + * @chainable + */ + public static function factory($type) { + switch($type) { + case "link": + return new Menu_Element_Link($type); + + case "ajax_link": + return new Menu_Element_Ajax_Link($type); + + case "dialog": + return new Menu_Element_Dialog($type); + + case "root": + $menu = new Menu("root"); + $menu->css_class("g-menu"); + return $menu; + + case "submenu": + return new Menu("submenu"); + + default: + throw Exception("@todo UNKNOWN_MENU_TYPE"); + } + } + + public function __construct($type) { + parent::__construct($type); + $this->elements = array(); + $this->is_root = $type == "root"; + } + + /** + * Add a new element to this menu + */ + public function append($menu_element) { + $this->elements[$menu_element->id] = $menu_element; + return $this; + } + + /** + * Add a new element to this menu, after the specific element + */ + public function add_after($target_id, $new_menu_element) { + $copy = array(); + foreach ($this->elements as $id => $menu_element) { + $copy[$id] = $menu_element; + if ($id == $target_id) { + $copy[$new_menu_element->id] = $new_menu_element; + } + } + $this->elements = $copy; + return $this; + } + + /** + * Add a new element to this menu, before the specific element + */ + public function add_before($target_id, $new_menu_element) { + $copy = array(); + foreach ($this->elements as $id => $menu_element) { + if ($id == $target_id) { + $copy[$new_menu_element->id] = $new_menu_element; + } + $copy[$id] = $menu_element; + } + $this->elements = $copy; + return $this; + } + + /** + * Remove an element from the menu + */ + public function remove($target_id) { + unset($this->elements[$target_id]); + } + + /** + * Retrieve a Menu_Element by id + */ + public function &get($id) { + if (array_key_exists($id, $this->elements)) { + return $this->elements[$id]; + } + + $null = null; + return $null; + } + + public function is_empty() { + foreach ($this->elements as $element) { + if ($element instanceof Menu) { + if (!$element->is_empty()) { + return false; + } + } else { + return false; + } + } + return true; + } + + public function render() { + $view = new View(isset($this->view) ? $this->view : "menu.html"); + $view->menu = $this; + return $view; + } + + static function title_comparator($a, $b) { + return strnatcasecmp((string)$a->label, (string)$b->label); + } +} diff --git a/modules/gallery/libraries/ORM_MPTT.php b/modules/gallery/libraries/ORM_MPTT.php new file mode 100644 index 0000000..0ad8133 --- /dev/null +++ b/modules/gallery/libraries/ORM_MPTT.php @@ -0,0 +1,341 @@ +model_name = inflector::singular($this->table_name); + } + + /** + * Overload ORM::save() to update the MPTT tree when we add new items to the hierarchy. + * + * @chainable + * @return ORM + */ + function save() { + if (!$this->loaded()) { + $this->lock(); + $parent = ORM::factory("item", $this->parent_id); + + try { + // Make a hole in the parent for this new item + db::build() + ->update($this->table_name) + ->set("left_ptr", db::expr("`left_ptr` + 2")) + ->where("left_ptr", ">=", $parent->right_ptr) + ->execute(); + db::build() + ->update($this->table_name) + ->set("right_ptr", db::expr("`right_ptr` + 2")) + ->where("right_ptr", ">=", $parent->right_ptr) + ->execute(); + $parent->right_ptr += 2; + + // Insert this item into the hole + $this->left_ptr = $parent->right_ptr - 2; + $this->right_ptr = $parent->right_ptr - 1; + $this->parent_id = $parent->id; + $this->level = $parent->level + 1; + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + parent::save(); + $this->unlock(); + } else { + parent::save(); + } + + return $this; + } + + /** + * Delete this node and all of its children. + */ + public function delete($ignored_id=null) { + $children = $this->children(); + if ($children) { + foreach ($this->children() as $item) { + // Deleting children affects the MPTT tree, so we have to reload each child before we + // delete it so that we have current left_ptr/right_ptr pointers. This is inefficient. + // @todo load each child once, not twice. + set_time_limit(30); + $item->reload()->delete(); + } + + // Deleting children has affected this item, but we'll reload it below. + } + + $this->lock(); + $this->reload(); // Assume that the prior lock holder may have changed this entry + if (!$this->loaded()) { + // Concurrent deletes may result in this item already being gone. Ignore it. + return; + } + + try { + db::build() + ->update($this->table_name) + ->set("left_ptr", db::expr("`left_ptr` - 2")) + ->where("left_ptr", ">", $this->right_ptr) + ->execute(); + db::build() + ->update($this->table_name) + ->set("right_ptr", db::expr("`right_ptr` - 2")) + ->where("right_ptr", ">", $this->right_ptr) + ->execute(); + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + + $this->unlock(); + parent::delete(); + } + + /** + * Return true if the target is descendant of this item. + * @param ORM $target + * @return boolean + */ + function contains($target) { + return ($this->left_ptr <= $target->left_ptr && $this->right_ptr >= $target->right_ptr); + } + + /** + * Return the parent of this node + * + * @return ORM + */ + function parent() { + if (!$this->parent_id) { + return null; + } + return model_cache::get($this->model_name, $this->parent_id); + } + + /** + * Return all the parents of this node, in order from root to this node's immediate parent. + * + * @return array ORM + */ + function parents($where=null) { + return $this + ->merge_where($where) + ->where("left_ptr", "<=", $this->left_ptr) + ->where("right_ptr", ">=", $this->right_ptr) + ->where("id", "<>", $this->id) + ->order_by("left_ptr", "ASC") + ->find_all(); + } + + /** + * Return all of the children of this node, ordered by id. + * + * @chainable + * @param integer SQL limit + * @param integer SQL offset + * @param array additional where clauses + * @param array order_by + * @return array ORM + */ + function children($limit=null, $offset=null, $where=null, $order_by=array("id" => "ASC")) { + return $this + ->merge_where($where) + ->where("parent_id", "=", $this->id) + ->order_by($order_by) + ->find_all($limit, $offset); + } + + /** + * Return the number of children of this node. + * + * @chainable + * @param array additional where clauses + * @return array ORM + */ + function children_count($where=null) { + return $this + ->merge_where($where) + ->where("parent_id", "=", $this->id) + ->count_all(); + } + + /** + * Return all of the decendents of the specified type, ordered by id. + * + * @param integer SQL limit + * @param integer SQL offset + * @param array additional where clauses + * @param array order_by + * @return object ORM_Iterator + */ + function descendants($limit=null, $offset=null, $where=null, $order_by=array("id" => "ASC")) { + return $this + ->merge_where($where) + ->where("left_ptr", ">", $this->left_ptr) + ->where("right_ptr", "<=", $this->right_ptr) + ->order_by($order_by) + ->find_all($limit, $offset); + } + + /** + * Return the count of all the children of the specified type. + * + * @param array additional where clauses + * @return integer child count + */ + function descendants_count($where=null) { + return $this + ->merge_where($where) + ->where("left_ptr", ">", $this->left_ptr) + ->where("right_ptr", "<=", $this->right_ptr) + ->count_all(); + } + + /** + * Move this item to the specified target. + * + * @chainable + * @param Item_Model $target Target node + * @return ORM_MTPP + */ + protected function move_to($target) { + if ($this->contains($target)) { + throw new Exception("@todo INVALID_TARGET can't move item inside itself"); + } + + $this->lock(); + $this->reload(); // Assume that the prior lock holder may have changed this entry + $target->reload(); + + $number_to_move = (int)(($this->right_ptr - $this->left_ptr) / 2 + 1); + $size_of_hole = $number_to_move * 2; + $original_left_ptr = $this->left_ptr; + $original_right_ptr = $this->right_ptr; + $target_right_ptr = $target->right_ptr; + $level_delta = ($target->level + 1) - $this->level; + + try { + if ($level_delta) { + // Update the levels for the to-be-moved items + db::build() + ->update($this->table_name) + ->set("level", db::expr("`level` + $level_delta")) + ->where("left_ptr", ">=", $original_left_ptr) + ->where("right_ptr", "<=", $original_right_ptr) + ->execute(); + } + + // Make a hole in the target for the move + db::build() + ->update($this->table_name) + ->set("left_ptr", db::expr("`left_ptr` + $size_of_hole")) + ->where("left_ptr", ">=", $target_right_ptr) + ->execute(); + db::build() + ->update($this->table_name) + ->set("right_ptr", db::expr("`right_ptr` + $size_of_hole")) + ->where("right_ptr", ">=", $target_right_ptr) + ->execute(); + + // Change the parent. + db::build() + ->update($this->table_name) + ->set("parent_id", $target->id) + ->where("id", "=", $this->id) + ->execute(); + + // If the source is to the right of the target then we just adjusted its left_ptr and + // right_ptr above. + $left_ptr = $original_left_ptr; + $right_ptr = $original_right_ptr; + if ($original_left_ptr > $target_right_ptr) { + $left_ptr += $size_of_hole; + $right_ptr += $size_of_hole; + } + + $new_offset = $target->right_ptr - $left_ptr; + db::build() + ->update($this->table_name) + ->set("left_ptr", db::expr("`left_ptr` + $new_offset")) + ->set("right_ptr", db::expr("`right_ptr` + $new_offset")) + ->where("left_ptr", ">=", $left_ptr) + ->where("right_ptr", "<=", $right_ptr) + ->execute(); + + // Close the hole in the source's parent after the move + db::build() + ->update($this->table_name) + ->set("left_ptr", db::expr("`left_ptr` - $size_of_hole")) + ->where("left_ptr", ">", $right_ptr) + ->execute(); + db::build() + ->update($this->table_name) + ->set("right_ptr", db::expr("`right_ptr` - $size_of_hole")) + ->where("right_ptr", ">", $right_ptr) + ->execute(); + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + + $this->unlock(); + + // Lets reload to get the changes. + $this->reload(); + $target->reload(); + return $this; + } + + /** + * Lock the tree to prevent concurrent modification. + */ + protected function lock() { + $timeout = module::get_var("gallery", "lock_timeout", 1); + $result = $this->db->query("SELECT GET_LOCK('{$this->table_name}', $timeout) AS l")->current(); + if (empty($result->l)) { + throw new Exception("@todo UNABLE_TO_LOCK_EXCEPTION"); + } + } + + /** + * Unlock the tree. + */ + protected function unlock() { + $this->db->query("SELECT RELEASE_LOCK('{$this->table_name}')"); + } +} diff --git a/modules/gallery/libraries/SafeString.php b/modules/gallery/libraries/SafeString.php new file mode 100644 index 0000000..179cbd4 --- /dev/null +++ b/modules/gallery/libraries/SafeString.php @@ -0,0 +1,162 @@ +_is_safe_html = $string->_is_safe_html; + $string = $string->unescaped(); + } + $this->_raw_string = (string) $string; + } + + /** + * Factory method returning a new SafeString instance for the given string. + */ + static function of($string) { + return new SafeString($string); + } + + /** + * Factory method returning a new SafeString instance after HTML purifying + * the given string. + */ + static function purify($string) { + if ($string instanceof SafeString) { + if ($string->_is_safe_html) { + return $string; + } else { + $string = $string->unescaped(); + } + } + $safe_string = self::of_safe_html(self::_purify_for_html($string)); + return $safe_string; + } + + /** + * Factory method returning a new SafeString instance which won't HTML escape. + */ + static function of_safe_html($string) { + $safe_string = new SafeString($string); + $safe_string->_is_safe_html = true; + return $safe_string; + } + + /** + * Safe for use in HTML. + * @see #for_html() + */ + function __toString() { + if ($this->_is_safe_html) { + return $this->_raw_string; + } else { + return self::_escape_for_html($this->_raw_string); + } + } + + /** + * Safe for use in HTML. + * + * Example:
    +   *   
    + *
    + * @return the string escaped for use in HTML. + */ + function for_html() { + return $this; + } + + /** + * Safe for use as JavaScript string. + * + * Example:
    +   *   
    +   * 
    + * @return the string escaped for use in JavaScript. + */ + function for_js() { + return json_encode((string) $this->_raw_string); + } + + /** + * Safe for use in HTML element attributes. + * + * Assumes that the HTML element attribute is already + * delimited by single or double quotes + * + * Example:
    +   *     ;
    +   *   
    +   * 
    + * @return the string escaped for use in HTML attributes. + */ + function for_html_attr() { + $string = (string) $this->for_html(); + return strtr($string, + array("'"=>"'", + '"'=>'"')); + } + + /** + * Safe for use HTML (purified HTML) + * + * Example:
    +   *   
    purified_html() ?> + *
    + * @return the string escaped for use in HTML. + */ + function purified_html() { + return self::purify($this); + } + + /** + * Returns the raw, unsafe string. Do not use lightly. + */ + function unescaped() { + return $this->_raw_string; + } + + /** + * Escape special HTML chars ("<", ">", "&", etc.) to HTML entities. + */ + private static function _escape_for_html($dirty_html) { + return html::chars($dirty_html); + } + + /** + * Purify the string, removing any potentially malicious or unsafe HTML / JavaScript. + */ + private static function _purify_for_html($dirty_html) { + if (class_exists("purifier") && method_exists("purifier", "purify")) { + return purifier::purify($dirty_html); + } else { + return self::_escape_for_html($dirty_html); + } + } +} diff --git a/modules/gallery/libraries/Sendmail.php b/modules/gallery/libraries/Sendmail.php new file mode 100644 index 0000000..69a7c32 --- /dev/null +++ b/modules/gallery/libraries/Sendmail.php @@ -0,0 +1,98 @@ +headers = array(); + $this->from(module::get_var("gallery", "email_from", "")); + $this->reply_to(module::get_var("gallery", "email_reply_to", "")); + $this->line_length(module::get_var("gallery", "email_line_length", 70)); + $separator = module::get_var("gallery", "email_header_separator", null); + $this->header_separator(empty($separator) ? "\n" : unserialize($separator)); + } + + public function __get($key) { + return null; + } + + public function __call($key, $value) { + switch ($key) { + case "to": + $this->to = is_array($value[0]) ? $value[0] : array($value[0]); + break; + case "header": + if (count($value) != 2) { + Kohana_Log::add("error", wordwrap("Invalid header parameters\n" . Kohana::debug($value))); + throw new Exception("@todo INVALID_HEADER_PARAMETERS"); + } + $this->headers[$value[0]] = $value[1]; + break; + case "from": + $this->headers["From"] = $value[0]; + break; + case "reply_to": + $this->headers["Reply-To"] = $value[0]; + break; + default: + $this->$key = $value[0]; + } + return $this; + } + + public function send() { + if (empty($this->to)) { + Kohana_Log::add("error", wordwrap("Sending mail failed:\nNo to address specified")); + throw new Exception("@todo TO_IS_REQUIRED_FOR_MAIL"); + } + $to = implode(", ", $this->to); + $headers = array(); + foreach ($this->headers as $key => $value) { + $key = ucfirst($key); + $headers[] = "$key: $value"; + } + + // The docs say headers should be separated by \r\n, but occasionaly that doesn't work and you + // need to use a single \n. This can be set in config/sendmail.php + $headers = implode($this->header_separator, $headers); + $message = wordwrap($this->message, $this->line_length, "\n"); + if (!$this->mail($to, $this->subject, $message, $headers)) { + throw new Exception("@todo SEND_MAIL_FAILED"); + } + return $this; + } + + public function mail($to, $subject, $message, $headers) { + return mail($to, $subject, $message, $headers); + } +} diff --git a/modules/gallery/libraries/Task_Definition.php b/modules/gallery/libraries/Task_Definition.php new file mode 100644 index 0000000..f695fe3 --- /dev/null +++ b/modules/gallery/libraries/Task_Definition.php @@ -0,0 +1,50 @@ +callback = $callback; + return $this; + } + + function description($description) { + $this->description = $description; + return $this; + } + + function name($name) { + $this->name = $name; + return $this; + } + + function severity($severity) { + $this->severity = $severity; + return $this; + } +} diff --git a/modules/gallery/libraries/Theme_View.php b/modules/gallery/libraries/Theme_View.php new file mode 100644 index 0000000..9118375 --- /dev/null +++ b/modules/gallery/libraries/Theme_View.php @@ -0,0 +1,271 @@ +theme_name = module::get_var("gallery", "active_site_theme"); + if (identity::active_user()->admin) { + $theme_name = Input::instance()->get("theme"); + if ($theme_name && + file_exists(THEMEPATH . $theme_name) && + strpos(realpath(THEMEPATH . $theme_name), THEMEPATH) == 0) { + $this->theme_name = $theme_name; + } + } + $this->item = null; + $this->tag = null; + $this->set_global(array("theme" => $this, + "theme_info" => theme::get_info($this->theme_name), + "user" => identity::active_user(), + "page_type" => $page_type, + "page_subtype" => $page_subtype, + "page_title" => null)); + + if (module::get_var("gallery", "maintenance_mode", 0)) { + if (identity::active_user()->admin) { + message::warning(t("This site is currently in maintenance mode. Visit the
    maintenance page", array("maintenance_url" => url::site("admin/maintenance")))); + } else + message::warning(t("This site is currently in maintenance mode.")); + } + } + + /** + * Proportion of the current thumb_size's to default + * @param object Item_Model (optional) check the proportions for this item + * @return int + */ + public function thumb_proportion($item=null) { + // If the item is an album with children, grab the first item in that album instead. We're + // interested in the size of the thumbnails in this album, not the thumbnail of the + // album itself. + if ($item && $item->is_album() && $item->children_count()) { + $item = $item->children(1)->current(); + } + + // By default we have a globally fixed thumbnail size In core code, we just return a fixed + // proportion based on the global thumbnail size, but since modules can override that, we + // return the actual proportions when we have them. + if ($item && $item->has_thumb()) { + return max($item->thumb_width, $item->thumb_height) / 200; + } else { + // @TODO change the 200 to a theme supplied value when and if we come up with an + // API to allow the theme to set defaults. + return module::get_var("gallery", "thumb_size", 200) / 200; + } + } + + public function item() { + return $this->item; + } + + public function siblings($limit=null, $offset=null) { + return call_user_func_array( + $this->siblings_callback[0], + array_merge($this->siblings_callback[1], array($limit, $offset))); + } + + public function tag() { + return $this->tag; + } + + public function page_type() { + return $this->page_type; + } + + public function page_subtype() { + return $this->page_subtype; + } + + public function user_menu() { + $menu = Menu::factory("root") + ->css_id("g-login-menu") + ->css_class("g-inline ui-helper-clear-fix"); + module::event("user_menu", $menu, $this); + return $menu->render(); + } + + public function site_menu($item_css_selector) { + $menu = Menu::factory("root"); + module::event("site_menu", $menu, $this, $item_css_selector); + return $menu->render(); + } + + public function album_menu() { + $menu = Menu::factory("root"); + module::event("album_menu", $menu, $this); + return $menu->render(); + } + + public function tag_menu() { + $menu = Menu::factory("root"); + module::event("tag_menu", $menu, $this); + return $menu->render(); + } + + public function photo_menu() { + $menu = Menu::factory("root"); + if (access::can("view_full", $this->item())) { + $menu->append(Menu::factory("link") + ->id("fullsize") + ->label(t("View full size")) + ->url($this->item()->file_url()) + ->css_class("g-fullsize-link")); + } + + module::event("photo_menu", $menu, $this); + return $menu->render(); + } + + public function movie_menu() { + $menu = Menu::factory("root"); + module::event("movie_menu", $menu, $this); + return $menu->render(); + } + + public function context_menu($item, $thumbnail_css_selector) { + $menu = Menu::factory("root") + ->append(Menu::factory("submenu") + ->id("context_menu") + ->label(t("Options"))) + ->css_class("g-context-menu"); + + module::event("context_menu", $menu, $this, $item, $thumbnail_css_selector); + return $menu->render(); + } + + /** + * Print out any site wide status information. + */ + public function site_status() { + return site_status::get(); + } + + /** + * Print out any messages waiting for this user. + */ + public function messages() { + return message::get(); + } + + /** + * Print out the sidebar. + */ + public function sidebar_blocks() { + $sidebar = block_manager::get_html("site_sidebar", $this); + if (empty($sidebar) && identity::active_user()->admin) { + $sidebar = new View("no_sidebar.html"); + } + return $sidebar; + } + + /** + * Handle all theme functions that insert module content. + */ + public function __call($function, $args) { + switch ($function) { + case "album_blocks": + case "album_bottom": + case "album_top": + case "body_attributes": + case "credits"; + case "dynamic_bottom": + case "dynamic_top": + case "footer": + case "head": + case "header_bottom": + case "header_top": + case "html_attributes": + case "page_bottom": + case "page_top": + case "photo_blocks": + case "photo_bottom": + case "photo_top": + case "resize_bottom": + case "resize_top": + case "sidebar_bottom": + case "sidebar_top": + case "thumb_bottom": + case "thumb_info": + case "thumb_top": + $blocks = array(); + if (method_exists("gallery_theme", $function)) { + switch (count($args)) { + case 0: + $blocks[] = gallery_theme::$function($this); + break; + case 1: + $blocks[] = gallery_theme::$function($this, $args[0]); + break; + case 2: + $blocks[] = gallery_theme::$function($this, $args[0], $args[1]); + break; + default: + $blocks[] = call_user_func_array( + array("gallery_theme", $function), + array_merge(array($this), $args)); + } + } + + foreach (module::active() as $module) { + if ($module->name == "gallery") { + continue; + } + $helper_class = "{$module->name}_theme"; + if (class_exists($helper_class) && method_exists($helper_class, $function)) { + $blocks[] = call_user_func_array( + array($helper_class, $function), + array_merge(array($this), $args)); + } + } + + $helper_class = theme::$site_theme_name . "_theme"; + if (class_exists($helper_class) && method_exists($helper_class, $function)) { + $blocks[] = call_user_func_array( + array($helper_class, $function), + array_merge(array($this), $args)); + } + + if (Session::instance()->get("debug")) { + if ($function != "head" && $function != "body_attributes") { + array_unshift( + $blocks, + "
    " . + "
    $function
    "); + $blocks[] = "
    "; + } + } + return implode("\n", $blocks); + + default: + throw new Exception("@todo UNKNOWN_THEME_FUNCTION: $function"); + } + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/drivers/Cache/Database.php b/modules/gallery/libraries/drivers/Cache/Database.php new file mode 100644 index 0000000..8790d0e --- /dev/null +++ b/modules/gallery/libraries/drivers/Cache/Database.php @@ -0,0 +1,166 @@ + value pairs + * @param array cache tags + * @param integer lifetime + * @return bool + */ + public function set($items, $tags=null, $lifetime=null) { + if (!empty($tags)) { + // Escape the tags, adding brackets so the tag can be explicitly matched + $tags = "<" . implode(">,<", $tags) . ">"; + } else { + $tags = null; + } + + // Cache Database driver expects unix timestamp + if ($lifetime !== 0) { + $lifetime += time(); + } + + $db = Database::instance(); + $tags = $db->escape($tags); + foreach ($items as $id => $data) { + $id = $db->escape($id); + $data = $db->escape(serialize($data)); + $db->query("INSERT INTO {caches} (`key`, `tags`, `expiration`, `cache`) + VALUES ('$id', '$tags', $lifetime, '$data') + ON DUPLICATE KEY UPDATE `tags` = VALUES(tags), `expiration` = VALUES(expiration), + `cache` = VALUES(cache)"); + } + + return true; + } + + /** + * Get cache items by tag + * @param array cache tags + * @return array cached data + */ + public function get_tag($tags) { + $db = db::build() + ->select() + ->from("caches"); + foreach ($tags as $tag) { + $db->where("tags", "LIKE", "%" . Database::escape_for_like("<$tag>") . "%"); + } + $db_result = $db->execute(); + + // An array will always be returned + $result = array(); + + // Disable notices for unserializing + $ER = error_reporting(~E_NOTICE); + if ($db_result->count() > 0) { + foreach ($db_result as $row) { + // Add each cache to the array + $result[$row->key] = unserialize($row->cache); + } + } + error_reporting($ER); + + return $result; + } + + /** + * Fetches a cache item. This will delete the item if it is expired or if + * the hash does not match the stored hash. + * + * @param string cache id + * @return mixed|NULL + */ + public function get($keys, $single=false) { + $data = null; + $result = db::build() + ->select() + ->from("caches") + ->where("key", "IN", $keys) + ->execute(); + + if (count($result) > 0) { + $cache = $result->current(); + // Make sure the expiration is valid and that the hash matches + if ($cache->expiration != 0 && $cache->expiration <= time()) { + // Cache is not valid, delete it now + $this->delete(array($cache->id)); + } else { + // Disable notices for unserializing + $ER = error_reporting(~E_NOTICE); + + // Return the valid cache data + $data = unserialize($cache->cache); + + // Turn notices back on + error_reporting($ER); + } + } + + return $data; + } + + /** + * Deletes a cache item by id or tag + * + * @param string cache id or tag, or true for "all items" + * @param bool delete a tag + * @return bool + */ + public function delete($keys, $is_tag=false) { + $db = db::build() + ->delete("caches"); + if ($keys === true) { + // Delete all caches + } else if ($is_tag === true) { + foreach ($keys as $tag) { + $db->where("tags", "LIKE", "%" . Database::escape_for_like("<$tag>") . "%"); + } + } else { + $db->where("key", "IN", $keys); + } + + $status = $db->execute(); + + return count($status) > 0; + } + + /** + * Delete cache items by tag + */ + public function delete_tag($tags) { + return $this->delete($tags, true); + } + + /** + * Empty the cache + */ + public function delete_all() { + Database::instance()->query("TRUNCATE {caches}"); + } +} \ No newline at end of file diff --git a/modules/gallery/libraries/drivers/IdentityProvider.php b/modules/gallery/libraries/drivers/IdentityProvider.php new file mode 100644 index 0000000..5256236 --- /dev/null +++ b/modules/gallery/libraries/drivers/IdentityProvider.php @@ -0,0 +1,134 @@ +loaded()) { + // Set reasonable defaults + $this->created = time(); + $this->rand_key = random::percent(); + $this->thumb_dirty = 1; + $this->resize_dirty = 1; + $this->sort_column = "created"; + $this->sort_order = "ASC"; + $this->owner_id = identity::active_user()->id; + } + } + + /** + * Add a set of restrictions to any following queries to restrict access only to items + * viewable by the active user. + * @chainable + */ + public function viewable() { + return item::viewable($this); + } + + /** + * Is this item an album? + * @return true if it's an album + */ + public function is_album() { + return $this->type == 'album'; + } + + /** + * Is this item a photo? + * @return true if it's a photo + */ + public function is_photo() { + return $this->type == 'photo'; + } + + /** + * Is this item a movie? + * @return true if it's a movie + */ + public function is_movie() { + return $this->type == 'movie'; + } + + public function delete($ignored_id=null) { + if (!$this->loaded()) { + // Concurrent deletes may result in this item already being gone. Ignore it. + return; + } + + if ($this->id == 1) { + $v = new Validation(array("id")); + $v->add_error("id", "cant_delete_root_album"); + ORM_Validation_Exception::handle_validation($this->table_name, $v); + } + + $old = clone $this; + module::event("item_before_delete", $this); + + $parent = $this->parent(); + if ($parent->album_cover_item_id == $this->id) { + item::remove_album_cover($parent); + } + + $path = $this->file_path(); + $resize_path = $this->resize_path(); + $thumb_path = $this->thumb_path(); + + parent::delete(); + if (is_dir($path)) { + // Take some precautions against accidentally deleting way too much + $delete_resize_path = dirname($resize_path); + $delete_thumb_path = dirname($thumb_path); + if ($delete_resize_path == VARPATH . "resizes" || + $delete_thumb_path == VARPATH . "thumbs" || + $path == VARPATH . "albums") { + throw new Exception( + "@todo DELETING_TOO_MUCH ($delete_resize_path, $delete_thumb_path, $path)"); + } + @dir::unlink($path); + @dir::unlink($delete_resize_path); + @dir::unlink($delete_thumb_path); + } else { + @unlink($path); + @unlink($resize_path); + @unlink($thumb_path); + } + + module::event("item_deleted", $old); + } + + /** + * Specify the path to the data file associated with this item. To actually associate it, + * you still have to call save(). + * @chainable + */ + public function set_data_file($data_file) { + $this->data_file = $data_file; + return $this; + } + + /** + * Return the server-relative url to this item, eg: + * album: /gallery3/index.php/Bobs%20Wedding?page=2 + * photo: /gallery3/index.php/Bobs%20Wedding/Eating-Cake + * movie: /gallery3/index.php/Bobs%20Wedding/First-Dance + * + * @param string $query the query string (eg "page=2") + */ + public function url($query=null) { + $url = url::site($this->relative_url()); + if ($query) { + $url .= "?$query"; + } + return $url; + } + + /** + * Return the full url to this item, eg: + * album: http://example.com/gallery3/index.php/Bobs%20Wedding?page=2 + * photo: http://example.com/gallery3/index.php/Bobs%20Wedding/Eating-Cake + * movie: http://example.com/gallery3/index.php/Bobs%20Wedding/First-Dance + * + * @param string $query the query string (eg "page=2") + */ + public function abs_url($query=null) { + $url = url::abs_site($this->relative_url()); + if ($query) { + $url .= "?$query"; + } + return $url; + } + + /** + * Return the full path to this item's file, eg: + * album: /usr/home/www/gallery3/var/albums/Bobs Wedding + * photo: /usr/home/www/gallery3/var/albums/Bobs Wedding/Eating-Cake.jpg + * movie: /usr/home/www/gallery3/var/albums/Bobs Wedding/First-Dance.mp4 + */ + public function file_path() { + return VARPATH . "albums/" . urldecode($this->relative_path()); + } + + /** + * Return the relative url to this item's file, with cache buster, eg: + * album: var/albums/Bobs%20Wedding?m=1234567890 + * photo: var/albums/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890 + * movie: var/albums/Bobs%20Wedding/First-Dance.mp4?m=1234567890 + * If $full_uri==true, return the full url to this item's file, with cache buster, eg: + * album: http://example.com/gallery3/var/albums/Bobs%20Wedding?m=1234567890 + * photo: http://example.com/gallery3/var/albums/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890 + * movie: http://example.com/gallery3/var/albums/Bobs%20Wedding/First-Dance.mp4?m=1234567890 + */ + public function file_url($full_uri=false) { + $relative_path = "var/albums/" . $this->relative_path(); + $cache_buster = $this->_cache_buster($this->file_path()); + return ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)) + . $cache_buster; + } + + /** + * Return the full path to this item's thumb, eg: + * album: /usr/home/www/gallery3/var/thumbs/Bobs Wedding/.album.jpg + * photo: /usr/home/www/gallery3/var/thumbs/Bobs Wedding/Eating-Cake.jpg + * movie: /usr/home/www/gallery3/var/thumbs/Bobs Wedding/First-Dance.jpg + */ + public function thumb_path() { + $base = VARPATH . "thumbs/" . urldecode($this->relative_path()); + if ($this->is_photo()) { + return $base; + } else if ($this->is_album()) { + return $base . "/.album.jpg"; + } else if ($this->is_movie()) { + // Replace the extension with jpg + return legal_file::change_extension($base, "jpg"); + } + } + + /** + * Return true if there is a thumbnail for this item. + */ + public function has_thumb() { + return $this->thumb_width && $this->thumb_height; + } + + /** + * Return the relative url to this item's thumb, with cache buster, eg: + * album: var/thumbs/Bobs%20Wedding/.album.jpg?m=1234567890 + * photo: var/thumbs/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890 + * movie: var/thumbs/Bobs%20Wedding/First-Dance.mp4?m=1234567890 + * If $full_uri==true, return the full url to this item's file, with cache buster, eg: + * album: http://example.com/gallery3/var/thumbs/Bobs%20Wedding/.album.jpg?m=1234567890 + * photo: http://example.com/gallery3/var/thumbs/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890 + * movie: http://example.com/gallery3/var/thumbs/Bobs%20Wedding/First-Dance.mp4?m=1234567890 + */ + public function thumb_url($full_uri=false) { + $cache_buster = $this->_cache_buster($this->thumb_path()); + $relative_path = "var/thumbs/" . $this->relative_path(); + $base = ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)); + if ($this->is_photo()) { + return $base . $cache_buster; + } else if ($this->is_album()) { + return $base . "/.album.jpg" . $cache_buster; + } else if ($this->is_movie()) { + // Replace the extension with jpg + $base = legal_file::change_extension($base, "jpg"); + return $base . $cache_buster; + } + } + + /** + * Return the full path to this item's resize, eg: + * album: /usr/home/www/gallery3/var/resizes/Bobs Wedding/.album.jpg (*) + * photo: /usr/home/www/gallery3/var/resizes/Bobs Wedding/Eating-Cake.jpg + * movie: /usr/home/www/gallery3/var/resizes/Bobs Wedding/First-Dance.mp4 (*) + * (*) Since only photos have resizes, album and movie paths are fictitious. + */ + public function resize_path() { + return VARPATH . "resizes/" . urldecode($this->relative_path()) . + ($this->is_album() ? "/.album.jpg" : ""); + } + + /** + * Return the relative url to this item's resize, with cache buster, eg: + * album: var/resizes/Bobs%20Wedding/.album.jpg?m=1234567890 (*) + * photo: var/resizes/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890 + * movie: var/resizes/Bobs%20Wedding/First-Dance.mp4?m=1234567890 (*) + * If $full_uri==true, return the full url to this item's file, with cache buster, eg: + * album: http://example.com/gallery3/var/resizes/Bobs%20Wedding/.album.jpg?m=1234567890 (*) + * photo: http://example.com/gallery3/var/resizes/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890 + * movie: http://example.com/gallery3/var/resizes/Bobs%20Wedding/First-Dance.mp4?m=1234567890 (*) + * (*) Since only photos have resizes, album and movie urls are fictitious. + */ + public function resize_url($full_uri=false) { + $relative_path = "var/resizes/" . $this->relative_path(); + $cache_buster = $this->_cache_buster($this->resize_path()); + return ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)) . + ($this->is_album() ? "/.album.jpg" : "") . $cache_buster; + } + + /** + * Rebuild the relative_path_cache and relative_url_cache. + */ + private function _build_relative_caches() { + $names = array(); + $slugs = array(); + foreach (db::build() + ->select(array("name", "slug")) + ->from("items") + ->where("left_ptr", "<=", $this->left_ptr) + ->where("right_ptr", ">=", $this->right_ptr) + ->where("id", "<>", 1) + ->order_by("left_ptr", "ASC") + ->execute() as $row) { + // Don't encode the names segment + $names[] = rawurlencode($row->name); + $slugs[] = rawurlencode($row->slug); + } + $this->relative_path_cache = implode($names, "/"); + $this->relative_url_cache = implode($slugs, "/"); + return $this; + } + + /** + * Return the relative path to this item's file. Note that the components of the path are + * urlencoded so if you want to use this as a filesystem path, you need to call urldecode + * on it. + * @return string + */ + public function relative_path() { + if (!$this->loaded()) { + return; + } + + if (!isset($this->relative_path_cache)) { + $this->_build_relative_caches()->save(); + } + return $this->relative_path_cache; + } + + /** + * Return the relative url to this item's file. + * @return string + */ + public function relative_url() { + if (!$this->loaded()) { + return; + } + + if (!isset($this->relative_url_cache)) { + $this->_build_relative_caches()->save(); + } + return $this->relative_url_cache; + } + + /** + * @see ORM::__get() + */ + public function __get($column) { + if ($column == "owner") { + // This relationship depends on an outside module, which may not be present so handle + // failures gracefully. + try { + return identity::lookup_user($this->owner_id); + } catch (Exception $e) { + return null; + } + } else { + return parent::__get($column); + } + } + + /** + * Handle any business logic necessary to create or modify an item. + * @see ORM::save() + * + * @return ORM Item_Model + */ + public function save() { + $significant_changes = $this->changed; + foreach (array("view_count", "relative_url_cache", "relative_path_cache", + "resize_width", "resize_height", "resize_dirty", + "thumb_width", "thumb_height", "thumb_dirty") as $key) { + unset($significant_changes[$key]); + } + + if ((!empty($this->changed) && $significant_changes) || isset($this->data_file)) { + $this->updated = time(); + if (!$this->loaded()) { + // Create a new item. + module::event("item_before_create", $this); + + // Set a weight if it's missing. We don't do this in the constructor because it's not a + // simple assignment. + if (empty($this->weight)) { + $this->weight = item::get_max_weight(); + } + + // Process the data file info. + if (isset($this->data_file)) { + $this->_process_data_file_info(); + } else if (!$this->is_album()) { + // Unless it's an album, new items must have a data file. + $this->data_file_error = true; + } + + // Make an url friendly slug from the name, if necessary + if (empty($this->slug)) { + $this->slug = item::convert_filename_to_slug(pathinfo($this->name, PATHINFO_FILENAME)); + + // If the filename is all invalid characters, then the slug may be empty here. We set a + // generic name ("photo", "movie", or "album") based on its type, then rely on + // check_and_fix_conflicts to ensure it doesn't conflict with another name. + if (empty($this->slug)) { + $this->slug = $this->type; + } + } + + $this->_check_and_fix_conflicts(); + + parent::save(); + + // Build our url caches, then save again. We have to do this after it's already been + // saved once because we use only information from the database to build the paths. If we + // could depend on a save happening later we could defer this 2nd save. + $this->_build_relative_caches(); + parent::save(); + + // Take any actions that we can only do once all our paths are set correctly after saving. + switch ($this->type) { + case "album": + mkdir($this->file_path()); + mkdir(dirname($this->thumb_path())); + mkdir(dirname($this->resize_path())); + break; + + case "photo": + case "movie": + copy($this->data_file, $this->file_path()); + break; + } + + // This will almost definitely trigger another save, so put it at the end so that we're + // tail recursive. Null out the data file variable first, otherwise the next save will + // trigger an item_updated_data_file event. + $this->data_file = null; + module::event("item_created", $this); + } else { + // Update an existing item + module::event("item_before_update", $this); + + // If any significant fields have changed, load up a copy of the original item and + // keep it around. + $original = ORM::factory("item", $this->id); + + // If we have a new data file, process its info. This will get its metadata and + // preserve the extension of the data file. Many helpers, (e.g. ImageMagick), assume + // the MIME type from the extension. So when we adopt the new data file, it's important + // to adopt the new extension. That ensures that the item's extension is always + // appropriate for its data. We don't try to preserve the name of the data file, though, + // because the name is typically a temporary randomly-generated name. + if (isset($this->data_file)) { + $this->_process_data_file_info(); + } else if (!$this->is_album() && array_key_exists("name", $this->changed)) { + // There's no new data file, but the name changed. If it's a photo or movie, + // make sure the new name still agrees with the file type. + $this->name = legal_file::sanitize_filename($this->name, + pathinfo($original->name, PATHINFO_EXTENSION), $this->type); + } + + // If an album's cover has changed (or been removed), delete any existing album cover, + // reset the thumb metadata, and mark the thumb as dirty. + if (array_key_exists("album_cover_item_id", $this->changed) && $this->is_album()) { + @unlink($original->thumb_path()); + $this->thumb_dirty = 1; + $this->thumb_height = 0; + $this->thumb_width = 0; + } + + if (array_intersect($this->changed, array("parent_id", "name", "slug"))) { + $original->_build_relative_caches(); + $this->relative_path_cache = null; + $this->relative_url_cache = null; + } + + $this->_check_and_fix_conflicts(); + + parent::save(); + + // Now update the filesystem and any database caches if there were significant value + // changes. If anything past this point fails, then we'll have an inconsistent database + // so this code should be as robust as we can make it. + + // Update the MPTT pointers, if necessary. We have to do this before we generate any + // cached paths! + if ($original->parent_id != $this->parent_id) { + parent::move_to($this->parent()); + } + + if ($original->parent_id != $this->parent_id || $original->name != $this->name) { + $this->_build_relative_caches(); + // If there is a data file, then we want to preserve both the old data and the new data. + // (Third-party event handlers would like access to both). The old data file will be + // accessible via the $original item, and the new one via $this item. But in that case, + // we don't want to rename the original as below, because the old data would end up being + // clobbered by the new data file. Also, the rename isn't necessary, because the new item + // data is coming from the data file anyway. So we only perform the rename if there isn't + // a data file. Another way to solve this would be to copy the original file rather than + // conditionally rename it, but a copy would cost far more than the rename. + if (!isset($this->data_file)) { + @rename($original->file_path(), $this->file_path()); + } + // Move all of the items associated data files + if ($this->is_album()) { + @rename(dirname($original->resize_path()), dirname($this->resize_path())); + @rename(dirname($original->thumb_path()), dirname($this->thumb_path())); + } else { + @rename($original->resize_path(), $this->resize_path()); + @rename($original->thumb_path(), $this->thumb_path()); + } + + if ($original->parent_id != $this->parent_id) { + // This will result in 2 events since we'll still fire the item_updated event below + module::event("item_moved", $this, $original->parent()); + } + } + + // Changing the name, slug or parent ripples downwards + if ($this->is_album() && + ($original->name != $this->name || + $original->slug != $this->slug || + $original->parent_id != $this->parent_id)) { + db::build() + ->update("items") + ->set("relative_url_cache", null) + ->set("relative_path_cache", null) + ->where("left_ptr", ">", $this->left_ptr) + ->where("right_ptr", "<", $this->right_ptr) + ->execute(); + } + + // Replace the data file, if requested. + if ($this->data_file && ($this->is_photo() || $this->is_movie())) { + copy($this->data_file, $this->file_path()); + $this->thumb_dirty = 1; + $this->resize_dirty = 1; + } + + module::event("item_updated", $original, $this); + + if ($this->data_file) { + // Null out the data file variable here, otherwise this event will trigger another + // save() which will think that we're doing another file move. + $this->data_file = null; + if ($original->file_path() != $this->file_path()) { + @unlink($original->file_path()); + } + module::event("item_updated_data_file", $this); + } + } + } else if (!empty($this->changed)) { + // Insignificant changes only. Don't fire events or do any special checking to try to keep + // this lightweight. + parent::save(); + } + + return $this; + } + + /** + * Check to see if there's another item that occupies the same name or slug that this item + * intends to use, and if so choose a new name/slug while preserving the extension. Since this + * checks the name without its extension, it covers possible collisions with thumbs and resizes + * as well (e.g. between the thumbs of movie "foo.flv" and photo "foo.jpg"). + */ + private function _check_and_fix_conflicts() { + $suffix_num = 1; + $suffix = ""; + if ($this->is_album()) { + while (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("id", $this->id ? "<>" : "IS NOT", $this->id) + ->and_open() + ->where("name", "=", "{$this->name}{$suffix}") + ->or_where("slug", "=", "{$this->slug}{$suffix}") + ->close() + ->count_records()) { + $suffix = "-" . (($suffix_num <= 99) ? sprintf("%02d", $suffix_num++) : random::int()); + } + if ($suffix) { + $this->name = "{$this->name}{$suffix}"; + $this->slug = "{$this->slug}{$suffix}"; + $this->relative_path_cache = null; + $this->relative_url_cache = null; + } + } else { + // Split the filename into its base and extension. This uses a regexp similar to + // legal_file::change_extension (which isn't always the same as pathinfo). + if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $this->name, $matches)) { + $base_name = $matches[1]; + $extension = $matches[2]; // includes a leading dot + } else { + $base_name = $this->name; + $extension = ""; + } + $base_name_escaped = Database::escape_for_like($base_name); + // Note: below query uses LIKE with wildcard % at end, which is still sargable (i.e. quick) + while (db::build() + ->from("items") + ->where("parent_id", "=", $this->parent_id) + ->where("id", $this->id ? "<>" : "IS NOT", $this->id) + ->and_open() + ->where("name", "LIKE", "{$base_name_escaped}{$suffix}.%") + ->or_where("slug", "=", "{$this->slug}{$suffix}") + ->close() + ->count_records()) { + $suffix = "-" . (($suffix_num <= 99) ? sprintf("%02d", $suffix_num++) : random::int()); + } + if ($suffix) { + $this->name = "{$base_name}{$suffix}{$extension}"; + $this->slug = "{$this->slug}{$suffix}"; + $this->relative_path_cache = null; + $this->relative_url_cache = null; + } + } + } + + /** + * Process the data file info. Get its metadata and extension. + * If valid, use it to sanitize the item name and update the + * width, height, and mime type. + */ + private function _process_data_file_info() { + try { + if ($this->is_photo()) { + list ($this->width, $this->height, $this->mime_type, $extension) = + photo::get_file_metadata($this->data_file); + } else if ($this->is_movie()) { + list ($this->width, $this->height, $this->mime_type, $extension) = + movie::get_file_metadata($this->data_file); + } else { + // Albums don't have data files. + $this->data_file = null; + return; + } + + // Sanitize the name based on the idenified extension, but only set $this->name if different + // to ensure it isn't unnecessarily marked as "changed" + $name = legal_file::sanitize_filename($this->name, $extension, $this->type); + if ($this->name != $name) { + $this->name = $name; + } + + // Data file valid - make sure the flag is reset to false. + $this->data_file_error = false; + } catch (Exception $e) { + // Data file invalid - set the flag so it's reported during item validation. + $this->data_file_error = true; + } + } + + /** + * Return the Item_Model representing the cover for this album. + * @return Item_Model or null if there's no cover + */ + public function album_cover() { + if (!$this->is_album()) { + return null; + } + + if (empty($this->album_cover_item_id)) { + return null; + } + + try { + return model_cache::get("item", $this->album_cover_item_id); + } catch (Exception $e) { + // It's possible (unlikely) that the item was deleted, if so keep going. + return null; + } + } + + /** + * Find the position of the given child id in this album. The resulting value is 1-indexed, so + * the first child in the album is at position 1. + * + * This method stands as a backward compatibility for gallery 3.0, and will + * be deprecated in version 3.1. + */ + public function get_position($child, $where=array()) { + return item::get_position($child, $where); + } + + /** + * Return an tag for the thumbnail. + * @param array $extra_attrs Extra attributes to add to the img tag + * @param int (optional) $max Maximum size of the thumbnail (default: null) + * @param boolean (optional) $center_vertically Center vertically (default: false) + * @return string + */ + public function thumb_img($extra_attrs=array(), $max=null, $center_vertically=false) { + list ($height, $width) = $this->scale_dimensions($max); + if ($center_vertically && $max) { + // The constant is divide by 2 to calculate the file and 10 to convert to em + $margin_top = (int)(($max - $height) / 20); + $extra_attrs["style"] = "margin-top: {$margin_top}em"; + $extra_attrs["title"] = $this->title; + } + $attrs = array_merge($extra_attrs, + array( + "src" => $this->thumb_url(), + "alt" => $this->title, + "width" => $width, + "height" => $height) + ); + // html::image forces an absolute url which we don't want + return ""; + } + + /** + * Calculate the largest width/height that fits inside the given maximum, while preserving the + * aspect ratio. Don't upscale. + * @param int $max Maximum size of the largest dimension + * @return array + */ + public function scale_dimensions($max) { + $width = $this->thumb_width; + $height = $this->thumb_height; + + if ($width <= $max && $height <= $max) { + return array($height, $width); + } + + if ($height) { + if (isset($max)) { + if ($width > $height) { + $height = (int)($max * $height / $width); + $width = $max; + } else { + $width = (int)($max * $width / $height); + $height = $max; + } + } + } else { + // Missing thumbnail, can happen on albums with no photos yet. + // @todo we should enforce a placeholder for those albums. + $width = 0; + $height = 0; + } + return array($height, $width); + } + + /** + * Return an tag for the resize. + * @param array $extra_attrs Extra attributes to add to the img tag + * @return string + */ + public function resize_img($extra_attrs) { + $attrs = array_merge($extra_attrs, + array("src" => $this->resize_url(), + "alt" => $this->title, + "width" => $this->resize_width, + "height" => $this->resize_height) + ); + // html::image forces an absolute url which we don't want + return ""; + } + + /** + * Return a view for movies. By default this is a Flowplayer v3 + diff --git a/modules/gallery/views/admin_block_log_entries.html.php b/modules/gallery/views/admin_block_log_entries.html.php new file mode 100644 index 0000000..5a8ed23 --- /dev/null +++ b/modules/gallery/views/admin_block_log_entries.html.php @@ -0,0 +1,15 @@ + +
      + +
    • + user->guest): ?> + user->name) ?> + + user->name) ?> + + timestamp) ?> + message ?> + html ?> +
    • + +
    diff --git a/modules/gallery/views/admin_block_news.html.php b/modules/gallery/views/admin_block_news.html.php new file mode 100644 index 0000000..cb276ae --- /dev/null +++ b/modules/gallery/views/admin_block_news.html.php @@ -0,0 +1,11 @@ + +
      + +
    • + "> +

      + +

      +
    • + +
    diff --git a/modules/gallery/views/admin_block_photo_stream.html.php b/modules/gallery/views/admin_block_photo_stream.html.php new file mode 100644 index 0000000..f9725ee --- /dev/null +++ b/modules/gallery/views/admin_block_photo_stream.html.php @@ -0,0 +1,14 @@ + + +

    + +

    diff --git a/modules/gallery/views/admin_block_platform.html.php b/modules/gallery/views/admin_block_platform.html.php new file mode 100644 index 0000000..9a594fa --- /dev/null +++ b/modules/gallery/views/admin_block_platform.html.php @@ -0,0 +1,24 @@ + +
      +
    • + php_uname("n"))) ?> +
    • +
    • + php_uname("s"), "version" => php_uname("r"))) ?> +
    • +
    • + function_exists("apache_get_version") ? apache_get_version() : t("Unknown"))) ?> +
    • +
    • + phpversion())) ?> +
    • +
    • + Database::instance()->query("SELECT version() as v")->current()->v)) ?> +
    • +
    • + join(" ", sys_getloadavg()))) ?> +
    • +
    • + module::get_var("gallery", "graphics_toolkit"))) ?> +
    • +
    diff --git a/modules/gallery/views/admin_block_stats.html.php b/modules/gallery/views/admin_block_stats.html.php new file mode 100644 index 0000000..c8c5476 --- /dev/null +++ b/modules/gallery/views/admin_block_stats.html.php @@ -0,0 +1,12 @@ + +
      +
    • + gallery::version_string())) ?> +
    • +
    • + $album_count)) ?> +
    • +
    • + $photo_count)) ?> +
    • +
    diff --git a/modules/gallery/views/admin_block_welcome.html.php b/modules/gallery/views/admin_block_welcome.html.php new file mode 100644 index 0000000..d3765d1 --- /dev/null +++ b/modules/gallery/views/admin_block_welcome.html.php @@ -0,0 +1,20 @@ + +

    + +

    +
      +
    • + graphics and language settings.", + array("graphics_url" => html::mark_clean(url::site("admin/graphics")), + "language_url" => html::mark_clean(url::site("admin/languages")))) ?> +
    • +
    • + choose a theme, or customize the way it looks.", + array("theme_url" => html::mark_clean(url::site("admin/themes")), + "theme_options_url" => html::mark_clean(url::site("admin/theme_options")))) ?> +
    • +
    • + install modules to add cool features!", + array("modules_url" => html::mark_clean(url::site("admin/modules")))) ?> +
    • +
    diff --git a/modules/gallery/views/admin_dashboard.html.php b/modules/gallery/views/admin_dashboard.html.php new file mode 100644 index 0000000..cf90ef2 --- /dev/null +++ b/modules/gallery/views/admin_dashboard.html.php @@ -0,0 +1,43 @@ + + +
    + +

    + +

    + +
    +
    + +
    diff --git a/modules/gallery/views/admin_graphics.html.php b/modules/gallery/views/admin_graphics.html.php new file mode 100644 index 0000000..1f45bb1 --- /dev/null +++ b/modules/gallery/views/admin_graphics.html.php @@ -0,0 +1,40 @@ + + + +
    +

    +

    + + We can help!", array("url" => "http://codex.galleryproject.org/Gallery3:Choosing_A_Graphics_Toolkit")) ?> +

    + +
    +

    + + + + $tk->$active, "is_active" => true)) ?> + + +
    +

    + + + $tk->$id, "is_active" => false)) ?> + + +
    +
    +
    + diff --git a/modules/gallery/views/admin_graphics_gd.html.php b/modules/gallery/views/admin_graphics_gd.html.php new file mode 100644 index 0000000..1cc9dc9 --- /dev/null +++ b/modules/gallery/views/admin_graphics_gd.html.php @@ -0,0 +1,30 @@ + +
    installed ? " g-installed-toolkit" : " g-unavailable" ?>"> + " alt="" /> +

    +

    + GD website for more information.", + array("url" => "http://www.boutell.com/gd")) ?> +

    + installed && $tk->rotate): ?> +
    + $tk->version)) ?> +
    +

    + +

    + installed): ?> + error): ?> +

    + error ?> +

    + +

    + +

    + +
    + +
    + +
    diff --git a/modules/gallery/views/admin_graphics_graphicsmagick.html.php b/modules/gallery/views/admin_graphics_graphicsmagick.html.php new file mode 100644 index 0000000..5dae144 --- /dev/null +++ b/modules/gallery/views/admin_graphics_graphicsmagick.html.php @@ -0,0 +1,21 @@ + +
    installed ? " g-installed-toolkit" : " g-unavailable" ?>"> + " alt="" /> +

    +

    + GraphicsMagick website for more information.", + array("url" => "http://www.graphicsmagick.org")) ?> +

    + installed): ?> +
    + $tk->version, "dir" => $tk->dir)) ?> +
    +

    + +

    + +
    + error ?> +
    + +
    diff --git a/modules/gallery/views/admin_graphics_imagemagick.html.php b/modules/gallery/views/admin_graphics_imagemagick.html.php new file mode 100644 index 0000000..9c1a990 --- /dev/null +++ b/modules/gallery/views/admin_graphics_imagemagick.html.php @@ -0,0 +1,21 @@ + +
    installed ? " g-installed-toolkit" : " g-unavailable" ?>"> + " alt="" /> +

    +

    + ImageMagick website for more information.", + array("url" => "http://www.imagemagick.org")) ?> +

    + installed): ?> +
    + $tk->version, "dir" => $tk->dir)) ?> +
    +

    + +

    + error): ?> +
    + error ?> +
    + +
    diff --git a/modules/gallery/views/admin_graphics_none.html.php b/modules/gallery/views/admin_graphics_none.html.php new file mode 100644 index 0000000..e0fc417 --- /dev/null +++ b/modules/gallery/views/admin_graphics_none.html.php @@ -0,0 +1,8 @@ + + +
    +

    +

    + +

    +
    diff --git a/modules/gallery/views/admin_languages.html.php b/modules/gallery/views/admin_languages.html.php new file mode 100644 index 0000000..d6a9c22 --- /dev/null +++ b/modules/gallery/views/admin_languages.html.php @@ -0,0 +1,118 @@ + + + +
    +

    + +
    + +
    +

    +

    + +

    + +
    +
    "> + + + + + + + + + $display_name): ?> + +
    + + + + + + + + "> + + + + + + +
    + +
    + for_html_attr() ?>" /> +
    +
    +
    + +
    +

    +

    + +

    + +
    + for_html_attr() ?>"> + + + +

    + +

    + +
      +
    1. +
    2. locales::display_name())) ?>
    3. +
    4. +
    + + + get("l10n_mode", false)): ?> + + + + + + +

    +

    + +

    + +
    +
    + +
    +
    diff --git a/modules/gallery/views/admin_maintenance.html.php b/modules/gallery/views/admin_maintenance.html.php new file mode 100644 index 0000000..230e935 --- /dev/null +++ b/modules/gallery/views/admin_maintenance.html.php @@ -0,0 +1,212 @@ + +
    +

    +
    +
    +

    + maintenance mode which prevents any non-admin from accessing your Gallery. Some of the tasks below will automatically put your Gallery in maintenance mode for you.") ?> +

    +
      + +
    • + on. Non admins cannot access your Gallery. Turn off maintenance mode", array("enable_maintenance_mode_url" => url::site("admin/maintenance/maintenance_mode/0?csrf=$csrf"))) ?> +
    • + +
    • + Turn on maintenance mode", array("enable_maintenance_mode_url" => url::site("admin/maintenance/maintenance_mode/1?csrf=$csrf"))) ?> +
    • + +
    +
    +
    + +
    +
    +

    +

    + +

    + + + + + + + + severity) ?>"> + + + + + +
    + + + + + +
    + name ?> + + description ?> + + callback?csrf=$csrf") ?>" + class="g-dialog-link g-button ui-icon-left ui-state-default ui-corner-all"> + + +
    +
    + + count()): ?> +
    + " + class="g-button g-right ui-icon-left ui-state-default ui-corner-all"> + +

    + + + + + + + + + + + state == "stalled" ? "g-warning" : "" ?>"> + + + + + + + + +
    + + + + + + + + + + + +
    "> + updated) ?> + + name ?> + + done): ?> + state == "cancelled"): ?> + + + + state == "stalled"): ?> + + + $task->percent_complete)) ?> + + + status ?> + + owner()->name) ?> + + state == "stalled"): ?> + id?csrf=$csrf") ?>"> + + + + get_log()): ?> + id?csrf=$csrf") ?>" class="g-dialog-link g-button ui-state-default ui-corner-all"> + + + + id?csrf=$csrf") ?>" + class="g-button ui-icon-left ui-state-default ui-corner-all"> + + +
    +
    + + + count()): ?> +
    + " + class="g-button g-right ui-icon-left ui-state-default ui-corner-all"> + +

    + + + + + + + + + + + state == "success" ? "g-success" : "g-error" ?>"> + + + + + + + + +
    + + + + + + + + + + + +
    "> + updated) ?> + + name ?> + + state == "success"): ?> + + state == "error"): ?> + + state == "cancelled"): ?> + + + + status ?> + + owner()->name) ?> + + done): ?> + id?csrf=$csrf") ?>" class="g-button ui-state-default ui-corner-all"> + + + get_log()): ?> + id?csrf=$csrf") ?>" class="g-dialog-link g-button ui-state-default ui-corner-all"> + + + + + id?csrf=$csrf") ?>" class="g-dialog-link g-button" ui-state-default ui-corner-all> + + + id?csrf=$csrf") ?>" class="g-button ui-state-default ui-corner-all"> + + + + +
    +
    + +
    +
    diff --git a/modules/gallery/views/admin_maintenance_show_log.html.php b/modules/gallery/views/admin_maintenance_show_log.html.php new file mode 100644 index 0000000..ecf882f --- /dev/null +++ b/modules/gallery/views/admin_maintenance_show_log.html.php @@ -0,0 +1,19 @@ + + +
    +

    name ?>

    +
    +
    get_log()) ?>
    +
    + + +
    diff --git a/modules/gallery/views/admin_maintenance_task.html.php b/modules/gallery/views/admin_maintenance_task.html.php new file mode 100644 index 0000000..013ac01 --- /dev/null +++ b/modules/gallery/views/admin_maintenance_task.html.php @@ -0,0 +1,84 @@ + + +
    +

    name ?>

    +
    +
    + +
    +
    + + +
    +
    diff --git a/modules/gallery/views/admin_modules.html.php b/modules/gallery/views/admin_modules.html.php new file mode 100644 index 0000000..96576ae --- /dev/null +++ b/modules/gallery/views/admin_modules.html.php @@ -0,0 +1,129 @@ + +
    + +

    +

    + adding more modules! Each module provides new cool features.", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Modules")) ?> +

    + + +

    + +

    + + + +
    diff --git a/modules/gallery/views/admin_modules_confirm.html.php b/modules/gallery/views/admin_modules_confirm.html.php new file mode 100644 index 0000000..8c4cb2b --- /dev/null +++ b/modules/gallery/views/admin_modules_confirm.html.php @@ -0,0 +1,22 @@ + +
    +

    + +

    + +
    +
      + "g-error", "warn" => "g-warning") as $type => $css_class): ?> + +
    • + + +
    +
    "> + + + + +
    +
    +
    diff --git a/modules/gallery/views/admin_movies.html.php b/modules/gallery/views/admin_movies.html.php new file mode 100644 index 0000000..abf8fb2 --- /dev/null +++ b/modules/gallery/views/admin_movies.html.php @@ -0,0 +1,44 @@ + +
    +

    +

    + + +

    +

    + + static build of FFmpeg from one of the links here.", array("url" => "http://ffmpeg.org/download.html")) ?> + +

    +

    + +

    +

    + We can help!", + array("url" => "http://codex.galleryproject.org/Gallery3:FAQ#Why_does_it_say_I.27m_missing_ffmpeg.3F")) ?> +

    + +
    +

    +
    + " alt="" /> +

    +
    + FFmpeg website for more information.", array("url" => "http://ffmpeg.org")) ?> +

    +
    + + +

    $ffmpeg_version, "dir" => $ffmpeg_dir)) ?>

    + +

    $ffmpeg_dir)) ?>

    + + +

    + +
    +
    +
    + + +
    diff --git a/modules/gallery/views/admin_sidebar.html.php b/modules/gallery/views/admin_sidebar.html.php new file mode 100644 index 0000000..75499cb --- /dev/null +++ b/modules/gallery/views/admin_sidebar.html.php @@ -0,0 +1,64 @@ + + + +
    +

    +

    + +

    + +
    +
    "> +
    +

    +
    +
      + +
    +
    +
    +
    +

    +
    +
      + +
    +
    +
    +
    +
    +
    diff --git a/modules/gallery/views/admin_sidebar_blocks.html.php b/modules/gallery/views/admin_sidebar_blocks.html.php new file mode 100644 index 0000000..48aa3f0 --- /dev/null +++ b/modules/gallery/views/admin_sidebar_blocks.html.php @@ -0,0 +1,5 @@ + + + $text): ?> +
  • + diff --git a/modules/gallery/views/admin_theme_options.html.php b/modules/gallery/views/admin_theme_options.html.php new file mode 100644 index 0000000..e452913 --- /dev/null +++ b/modules/gallery/views/admin_theme_options.html.php @@ -0,0 +1,7 @@ + +
    +

    +
    + +
    +
    diff --git a/modules/gallery/views/admin_themes.html.php b/modules/gallery/views/admin_themes.html.php new file mode 100644 index 0000000..547f27d --- /dev/null +++ b/modules/gallery/views/admin_themes.html.php @@ -0,0 +1,98 @@ + + + +
    +

    +

    + with a new theme! There are separate themes for the regular site and for the administration interface. Click a theme below to preview and activate it.", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Themes")) ?> +

    + +
    +
    +

    +
    + " + alt="name) ?>" /> +

    name ?>

    +

    + description ?> +

    + info = $themes[$site]; print $v; ?> +
    + +

    +
    + + $info): ?> + site) continue ?> + + + + + + +

    + Download one now!", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Themes")) ?> +

    + +
    +
    + +
    +

    +
    + " + alt="name) ?>" /> +

    name ?>

    +

    + description ?> +

    + info = $themes[$admin]; print $v; ?> +
    + +

    +
    + + $info): ?> + admin) continue ?> + + + + + + +

    + Download one now!", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Themes")) ?> +

    + +
    +
    + +
    +
    diff --git a/modules/gallery/views/admin_themes_buttonset.html.php b/modules/gallery/views/admin_themes_buttonset.html.php new file mode 100644 index 0000000..bf474a2 --- /dev/null +++ b/modules/gallery/views/admin_themes_buttonset.html.php @@ -0,0 +1,48 @@ + + diff --git a/modules/gallery/views/admin_themes_preview.html.php b/modules/gallery/views/admin_themes_preview.html.php new file mode 100644 index 0000000..80a6158 --- /dev/null +++ b/modules/gallery/views/admin_themes_preview.html.php @@ -0,0 +1,8 @@ + +

    $info->name)) ?>

    +

    + "> + %theme_name", array("theme_name" => $info->name)) ?> + +

    + diff --git a/modules/gallery/views/error.html.php b/modules/gallery/views/error.html.php new file mode 100644 index 0000000..5d81b65 --- /dev/null +++ b/modules/gallery/views/error.html.php @@ -0,0 +1,12 @@ + +
    +

    + +

    +

    + +

    +

    + +

    +
    \ No newline at end of file diff --git a/modules/gallery/views/error_404.html.php b/modules/gallery/views/error_404.html.php new file mode 100644 index 0000000..42f62b6 --- /dev/null +++ b/modules/gallery/views/error_404.html.php @@ -0,0 +1,26 @@ + +
    +

    + +

    + +

    + +

    +

    + + +

    + + + +

    + + +

    + +
    diff --git a/modules/gallery/views/error_admin.html.php b/modules/gallery/views/error_admin.html.php new file mode 100644 index 0000000..036e204 --- /dev/null +++ b/modules/gallery/views/error_admin.html.php @@ -0,0 +1,307 @@ + + + + + + + + + <?= t("Something went wrong!") ?> + + + + + +
    + +
    +

    + +

    +

    + +

    +
    +
    +
    +

    + +

    +

    + There's an error message below and you can find more details + in gallery3/var/logs (look for the file with the most recent + date on it). Stuck? Stop by the Gallery 3 + Forums and ask for help. You can also look at our list + of open + tickets to see if the problem you're seeing has been + reported. If you post a request, here's some useful + information to include: + + +

    +
    +

    + + [ ]: + + + + +

    +
    +
      +
    1. +

      + + [ ] + +

      + +
      + $row): ?>"> + +
      +
    2. + + + $step): ?> +
    3. +

      + + + + [ ] + + [ ] + + + {} + + + » + ( + + ) +

      + + + + + + +
    4. + + +
    + + +
    +

    + " onclick="return koggle('')"> +

    + +
    +
    + + diff --git a/modules/gallery/views/error_cli.txt.php b/modules/gallery/views/error_cli.txt.php new file mode 100644 index 0000000..9f476f5 --- /dev/null +++ b/modules/gallery/views/error_cli.txt.php @@ -0,0 +1,3 @@ + + + + + + + + <?= t("Something went wrong!") ?> + + +
    + +
    +

    + +

    +

    + +

    +

    + +

    +
    +
    + + diff --git a/modules/gallery/views/form.html.php b/modules/gallery/views/form.html.php new file mode 100644 index 0000000..abc3216 --- /dev/null +++ b/modules/gallery/views/form.html.php @@ -0,0 +1,77 @@ + +"; +} +if ($title) { + print $title; +} + +if (!function_exists("DrawForm")) { + function DrawForm($inputs, $level=1) { + $error_messages = array(); + $prefix = str_repeat(" ", $level); + $haveGroup = false; + // On the first level, make sure we have a group if not add the
      tag now + if ($level == 1) { + foreach ($inputs as $input) { + $haveGroup |= $input->type == 'group'; + } + if (!$haveGroup) { + print "$prefix
        \n"; + } + } + + foreach ($inputs as $input) { + if ($input->type == 'group') { + print "$prefix
        \n"; + print "$prefix {$input->label}\n"; + print "$prefix
          \n"; + + DrawForm($input->inputs, $level + 2); + print "$prefix
        \n"; + + // Since hidden fields can only have name and value attributes lets just render it now + $hidden_prefix = "$prefix "; + foreach ($input->hidden as $hidden) { + print "$prefix {$hidden->render()}\n"; + } + print "$prefix
        \n"; + } else if ($input->type == 'script') { + print $input->render(); + } else { + if ($input->error_messages()) { + print "$prefix
      • \n"; + } else { + print "$prefix
      • \n"; + } + + if ($input->label()) { + print "$prefix {$input->label()}\n"; + } + print "$prefix {$input->render()}\n"; + if ($input->message()) { + print "$prefix

        {$input->message()}

        \n"; + } + if ($input->error_messages()) { + foreach ($input->error_messages() as $error_message) { + print "$prefix

        \n"; + print "$prefix $error_message\n"; + print "$prefix

        \n"; + } + } + print "$prefix
      • \n"; + } + } + if ($level == 1 && !$haveGroup) { + print "$prefix
      \n"; + } + } +} +DrawForm($inputs); + +print($close); +?> diff --git a/modules/gallery/views/form_uploadify.html.php b/modules/gallery/views/form_uploadify.html.php new file mode 100644 index 0000000..bba6db7 --- /dev/null +++ b/modules/gallery/views/form_uploadify.html.php @@ -0,0 +1,167 @@ + + + + + +
      + admin && !$movies_allowed)): ?> +
      + +

      + suhosin.session.encrypt setting from Suhosin. You must disable this setting to upload photos.", + array("encrypt_url" => "http://www.hardened-php.net/suhosin/configuration.html#suhosin.session.encrypt", + "suhosin_url" => "http://www.hardened-php.net/suhosin/")) ?> +

      + + + admin && !$movies_allowed): ?> +

      + Help!", array("help_url" => url::site("admin/movies"))) ?> +

      + +
      + + +
      +
        + parents() as $i => $parent): ?> + > title) ?> + +
      • title) ?>
      • +
      +
      + +
      + + +
      +
      +
        +
      +
      +
      + + diff --git a/modules/gallery/views/form_uploadify_buttons.html.php b/modules/gallery/views/form_uploadify_buttons.html.php new file mode 100644 index 0000000..e002d82 --- /dev/null +++ b/modules/gallery/views/form_uploadify_buttons.html.php @@ -0,0 +1,11 @@ + +
      + + + + +
      diff --git a/modules/gallery/views/in_place_edit.html.php b/modules/gallery/views/in_place_edit.html.php new file mode 100644 index 0000000..2d6cbe9 --- /dev/null +++ b/modules/gallery/views/in_place_edit.html.php @@ -0,0 +1,21 @@ + + "post", "id" => "g-in-place-edit-form", "class" => "g-short-form")) ?> + +
        + class="g-error"> + + +
      • + "submit ui-state-default"), t("Save")) ?> +
      • +
      • + +
      • +

        +
      • + +
      + + + + diff --git a/modules/gallery/views/kohana/error.php b/modules/gallery/views/kohana/error.php new file mode 100644 index 0000000..0e84f09 --- /dev/null +++ b/modules/gallery/views/kohana/error.php @@ -0,0 +1,46 @@ + +validation->errors(), 1)); +} + +if (php_sapi_name() == "cli") { + include Kohana::find_file("views", "error_cli.txt"); + return; +} + +try { + // Admins get a special error page + $user = identity::active_user(); + if ($user && $user->admin) { + include Kohana::find_file("views", "error_admin.html"); + return; + } +} catch (Exception $ignored) { +} + +// Try to show a themed error page for 404 errors +if ($e instanceof Kohana_404_Exception) { + if (Router::$controller == "file_proxy") { + print "File not found"; + } else { + $view = new Theme_View("page.html", "other", "error"); + $view->page_title = t("Dang... Page not found!"); + $view->content = new View("error_404.html"); + $user = identity::active_user(); + $view->content->is_guest = $user && $user->guest; + if ($view->content->is_guest) { + $view->content->login_form = new View("login_ajax.html"); + $view->content->login_form->form = auth::get_login_form("login/auth_html"); + } + print $view; + } + return; +} + +header("HTTP/1.1 500 Internal Server Error"); +include Kohana::find_file("views", "error_user.html"); diff --git a/modules/gallery/views/kohana_profiler.php b/modules/gallery/views/kohana_profiler.php new file mode 100644 index 0000000..c753434 --- /dev/null +++ b/modules/gallery/views/kohana_profiler.php @@ -0,0 +1,35 @@ + + + +
      + + render(); ?> + +

      s

      +
      diff --git a/modules/gallery/views/l10n_client.html.php b/modules/gallery/views/l10n_client.html.php new file mode 100644 index 0000000..47a45e2 --- /dev/null +++ b/modules/gallery/views/l10n_client.html.php @@ -0,0 +1,82 @@ + + diff --git a/modules/gallery/views/login_ajax.html.php b/modules/gallery/views/login_ajax.html.php new file mode 100644 index 0000000..a40d195 --- /dev/null +++ b/modules/gallery/views/login_ajax.html.php @@ -0,0 +1,52 @@ + + +
      +
        +
      • + +
      • + +
      • + +
      • + +
      +
      diff --git a/modules/gallery/views/login_current_user.html.php b/modules/gallery/views/login_current_user.html.php new file mode 100644 index 0000000..9452557 --- /dev/null +++ b/modules/gallery/views/login_current_user.html.php @@ -0,0 +1,7 @@ + +
    • + label->for_html() ?> + for_html_attr() ?> + html::mark_clean( + "{$name}"))) ?> +
    • diff --git a/modules/gallery/views/menu.html.php b/modules/gallery/views/menu.html.php new file mode 100644 index 0000000..17a249d --- /dev/null +++ b/modules/gallery/views/menu.html.php @@ -0,0 +1,24 @@ + +is_empty()): // Don't show the menu if it has no choices ?> +is_root): ?> +
        css_id ? "id='$menu->css_id'" : "" ?> class="css_class ?>"> + elements as $element): ?> + render() ?> + +
      + + + +
    • + + label->for_html() ?> + +
        + elements as $element): ?> + render() ?> + +
      +
    • + + + diff --git a/modules/gallery/views/menu_ajax_link.html.php b/modules/gallery/views/menu_ajax_link.html.php new file mode 100644 index 0000000..06cd6f9 --- /dev/null +++ b/modules/gallery/views/menu_ajax_link.html.php @@ -0,0 +1,10 @@ + +
    • + css_id ? "id='{$menu->css_id}'" : "" ?> + class="g-ajax-link css_class ?>" + href="url ?>" + title="label->for_html_attr() ?>" + ajax_handler="ajax_handler ?>"> + label->for_html() ?> + +
    • diff --git a/modules/gallery/views/menu_dialog.html.php b/modules/gallery/views/menu_dialog.html.php new file mode 100644 index 0000000..b44080c --- /dev/null +++ b/modules/gallery/views/menu_dialog.html.php @@ -0,0 +1,9 @@ + +
    • + css_id ? "id='{$menu->css_id}'" : "" ?> + class="g-dialog-link css_class ?>" + href="url ?>" + title="label->for_html_attr() ?>"> + label->for_html() ?> + +
    • diff --git a/modules/gallery/views/menu_link.html.php b/modules/gallery/views/menu_link.html.php new file mode 100644 index 0000000..a36d275 --- /dev/null +++ b/modules/gallery/views/menu_link.html.php @@ -0,0 +1,9 @@ + +
    • + css_id ? "id='{$menu->css_id}'" : "" ?> + class="g-menu-link css_class ?>" + href="url ?>" + title="label->for_html_attr() ?>"> + label->for_html() ?> + +
    • diff --git a/modules/gallery/views/movieplayer.html.php b/modules/gallery/views/movieplayer.html.php new file mode 100644 index 0000000..3cf5d17 --- /dev/null +++ b/modules/gallery/views/movieplayer.html.php @@ -0,0 +1,50 @@ + + + diff --git a/modules/gallery/views/permissions_browse.html.php b/modules/gallery/views/permissions_browse.html.php new file mode 100644 index 0000000..0b27336 --- /dev/null +++ b/modules/gallery/views/permissions_browse.html.php @@ -0,0 +1,62 @@ + + +
      + +
        +
      • + mod_rewrite and set AllowOverride FileInfo Options to fix this.", + array("mod_rewrite_attrs" => html::mark_clean('href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html" target="_blank"'), + "apache_attrs" => html::mark_clean('href="http://httpd.apache.org/docs/2.0/mod/core.html#allowoverride" target="_blank"'))) ?> +
      • +
      + + +

      + + + +
      + +
      +
      diff --git a/modules/gallery/views/permissions_form.html.php b/modules/gallery/views/permissions_form.html.php new file mode 100644 index 0000000..f171411 --- /dev/null +++ b/modules/gallery/views/permissions_form.html.php @@ -0,0 +1,92 @@ + +
      + + + + + + + + + + + + + + name, $item) ?> + name, $item) ?> + name, $item) ?> + + + + + + + + + + + + + + + + + + + + + +
      name) ?>
      display_name) ?> + + " + title="for_html_attr() ?>" + alt="for_html_attr() ?>" /> + + " alt="for_html_attr() ?>" /> + + + + " alt="for_html_attr() ?>" /> + + + " alt="for_html_attr() ?>" /> + + + + " alt="for_html_attr() ?>" /> + + + " alt="for_html_attr() ?>" /> + + + + " alt="for_html_attr() ?>" /> + + id == 1): ?> + " alt="for_html_attr() ?>" title="for_html_attr() ?>"/> + + + " alt="for_html_attr() ?>" /> + + + + id == 1): ?> + " title="for_html_attr() ?>" alt="for_html_attr() ?>" /> + + + " alt="for_html_attr() ?>" /> + + + + " alt="for_html_attr() ?>" /> + +
      +
      diff --git a/modules/gallery/views/quick_delete_confirm.html.php b/modules/gallery/views/quick_delete_confirm.html.php new file mode 100644 index 0000000..176ffb9 --- /dev/null +++ b/modules/gallery/views/quick_delete_confirm.html.php @@ -0,0 +1,12 @@ + +
      +

      + is_album()): ?> + %title? All photos and movies in the album will also be deleted.", + array("title" => html::purify($item->title))) ?> + + %title?", array("title" => html::purify($item->title))) ?> + +

      + +
      diff --git a/modules/gallery/views/reauthenticate.html.php b/modules/gallery/views/reauthenticate.html.php new file mode 100644 index 0000000..9a6696f --- /dev/null +++ b/modules/gallery/views/reauthenticate.html.php @@ -0,0 +1,15 @@ + +
      +

      + +

      +

      + $user_name)) ?> +

      + + +
      \ No newline at end of file diff --git a/modules/gallery/views/upgrade_checker_block.html.php b/modules/gallery/views/upgrade_checker_block.html.php new file mode 100644 index 0000000..c984d99 --- /dev/null +++ b/modules/gallery/views/upgrade_checker_block.html.php @@ -0,0 +1,55 @@ + +

      + +

      + +

      + + %code_name.", array("version" => gallery::VERSION, "code_name" => gallery::CODE_NAME)) ?> + + gallery::VERSION, "branch" => gallery::RELEASE_BRANCH, "build_number" => $build_number)) ?> + + gallery::VERSION, "branch" => gallery::RELEASE_BRANCH, "build_number" => $build_number)) ?> + +

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

      + "> + + + + "> + + + + "> + + + +

      + +

      + + + + + + + + + gallery::date_time($version_info->timestamp))) ?> + +

      + diff --git a/modules/gallery/views/upgrader.html.php b/modules/gallery/views/upgrader.html.php new file mode 100644 index 0000000..4c611f7 --- /dev/null +++ b/modules/gallery/views/upgrader.html.php @@ -0,0 +1,163 @@ + + + + + <?= t("Gallery 3 upgrader") ?> + " + media="screen,print,projection" /> + + + > +
      + " /> +
      + + + +
      +

      "> + +

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

      + + +

      +
      + + + + "> + + + + + + $module): ?> + active): ?> + " > + + + + + + + + +
      + name) ?> + + version ?> + + code_version ?> +
      + + +

      "> + +

      +
        "> + + active): ?> +
      • + name) ?> +
      • + + +
      + + +

      +

      + %name in your %tmp_dir_path directory.", + array("name" => "$upgrade_token", + "tmp_dir_path" => "gallery3/var/tmp")) ?> +

      + "> + +
      + +
      + + diff --git a/modules/gallery/views/user_languages_block.html.php b/modules/gallery/views/user_languages_block.html.php new file mode 100644 index 0000000..3776ca1 --- /dev/null +++ b/modules/gallery/views/user_languages_block.html.php @@ -0,0 +1,19 @@ + + + + diff --git a/modules/gallery/views/user_profile.html.php b/modules/gallery/views/user_profile.html.php new file mode 100644 index 0000000..257bd7c --- /dev/null +++ b/modules/gallery/views/user_profile.html.php @@ -0,0 +1,47 @@ + + +
      + +

      + " + alt="display_name()) ?>" + class="g-avatar g-left" width="40" height="40" /> + $user->display_name())) ?> +

      + +
      +

      title) ?>

      +
      + view ?> +
      +
      + +
      diff --git a/modules/gallery/views/user_profile_info.html.php b/modules/gallery/views/user_profile_info.html.php new file mode 100644 index 0000000..e559abd --- /dev/null +++ b/modules/gallery/views/user_profile_info.html.php @@ -0,0 +1,9 @@ + + + $value): ?> + + + + + +
      diff --git a/modules/gallery/views/welcome_message.html.php b/modules/gallery/views/welcome_message.html.php new file mode 100644 index 0000000..bb6b4a8 --- /dev/null +++ b/modules/gallery/views/welcome_message.html.php @@ -0,0 +1,36 @@ + +
      +

      + +

      + +

      +

      + +

      +

      + +

      + %user_name account. You should change your password to something that you'll remember.", array("user_name" => $user->name)) ?> +

      + +

      + id}") ?>" + title="for_html_attr() ?>" + id="g-after-install-change-password-link" + class="g-button ui-state-default ui-corner-all"> + + + +

      + +

      + Gallery website has news and information about the Gallery project and community.", array("url" => "http://galleryproject.org")) ?> +

      + +

      + documentation site or you can ask for help in the forums!", array("codex_url" => "http://codex.galleryproject.org/Main_Page", "forum_url" => "http://galleryproject.org/forum")) ?> +

      +
      diff --git a/modules/gallery/views/welcome_message_loader.html.php b/modules/gallery/views/welcome_message_loader.html.php new file mode 100644 index 0000000..d1ff2f3 --- /dev/null +++ b/modules/gallery/views/welcome_message_loader.html.php @@ -0,0 +1,7 @@ + +for_html_attr() ?>" + href=""/> + diff --git a/modules/greydragon/changelog.txt b/modules/greydragon/changelog.txt new file mode 100644 index 0000000..ae8baf9 --- /dev/null +++ b/modules/greydragon/changelog.txt @@ -0,0 +1,11 @@ +GreyDragon Shared Module Changelog + +version 1.3: +- Added check to detect situation when CURL is not installed to disable Auto Update feature +- Improved CURL logic to properly handle redirects + +version 1.2: +- Fixed issue with some installations not taking relative path for CSS + +version 1.1: +- Initial release \ No newline at end of file diff --git a/modules/greydragon/css/gd_common.css b/modules/greydragon/css/gd_common.css new file mode 100644 index 0000000..32b780b --- /dev/null +++ b/modules/greydragon/css/gd_common.css @@ -0,0 +1,59 @@ +/** + * Gallery 3 Grey Dragon Common Module + * Copyright (C) 2012 Serguei Dosyukov + * + * CSS rules for admin section + */ + +body { min-width: 1200px; } + +#g-content { font-size: 1em; margin-bottom: 0; width: auto; padding-left: 1em; padding-right: 1em; } +#g-content ul { margin-bottom: 0; } + +#g-content h3 { color: #d54e21; border-bottom: #a2bdbf 1px solid; margin-top: 0.3em; margin-bottom: 0.3em; } +#g-content p { color: #333; } +#g-content table { margin-bottom: 0; } + +#g-content input { display: inline; float: left; margin-right: 0.8em; color: #555555; border: 1px solid #ccc; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } +#g-content textarea { height: 6em; color: #555555; border: 1px solid #ccc; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } +#g-content select { display: inline; float: left; margin-right: 0.8em; width: 50.6%; color: #555555; border: 1px solid #ccc; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } + +#g-content input[type='checkbox'] { border: none; } +#g-content input[type='text'] { width: 50%; } + +#g-content input.submit { display: inline-block; min-width: 100px; padding: 4px 10px 4px; font-size: 13px; line-height: 18px; color:#333333; text-align: center; text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); background-color: #fafafa; background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-repeat: no-repeat; filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); border: 1px solid #ccc; border-bottom-color: #bbb; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; *margin-left: .3em; } +#g-content input.submit:first-child { *margin-left: 0; } +#g-content input.submit:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; } + +#g-content input.g-error { padding-left: 30px; border: none; } +#g-content input.g-success { background-color: transparent; } +#g-content input.g-warning { background-color: transparent; border: none; } + +#g-content p.g-error { padding-left: 30px; border: none; margin-bottom: 0; background-image: none; } + +#gd-admin-header { padding: 7px 0; margin: 4px 0 0 0; background-color: #fbfbfb; background-image: -moz-linear-gradient(top, #ffffff, #f5f5f5); background-image:- ms-linear-gradient(top, #ffffff, #f5f5f5); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5)); background-image: -webkit-linear-gradient(top, #ffffff, #f5f5f5); background-image: -o-linear-gradient(top, #ffffff, #f5f5f5); background-image: linear-gradient(top, #ffffff, #f5f5f5); background-repeat: repeat-x; filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0); border: 1px solid #ddd; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 0 #ffffff; -moz-box-shadow: inset 0 1px 0 #ffffff; box-shadow: inset 0 1px 0 #ffffff; display: inline-block; width: 100%; } +#gd-admin-header .divider{padding:0 5px;color:#999999;} +#gd-admin-header .active a{color:#333333;} + +#gd-admin-version, +#gd-admin-version-2 { margin-top: 4px; padding: 7px 14px; background-color: rgb(217, 237, 247); border: 1px solid #bce8f1; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 0 #ffffff; -moz-box-shadow: inset 0 1px 0 #ffffff; box-shadow: inset 0 1px 0 #ffffff; } + +#gd-admin-title { float: left; padding-left: 10px; color: #333v42; font-weight: bold; font-size: 1.4em; text-shadow: #deeefa 0 1px 0; display: inline-block; } +#gd-admin-hlinks { float: right; padding-right: 10px; } +#gd-admin-hlinks li { list-style-type: none; float: left; color: #618299; display: inline; text-shadow: 0 1px 0 #ffffff; } +#gd-admin-hlinks a { line-height: 1.6em; } +#gd-admin-hlinks a[disabled="disabled"], #gd-admin-hlinks a[disabled="disabled"]:hover { text-decoration: none; cursor: default; } + +#g-autoupdate-config { display: none; border: 1px solid #ddd; border-top: none; width: 45%; height: 2.5em; margin-left: 54%; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 0 #ffffff; -moz-box-shadow: inset 0 1px 0 #ffffff; box-shadow: inset 0 1px 0 #ffffff; } +#g-autoupdate-config.visible { min-height: 30px; display: inline-block; } +#g-autoupdate-config ul { float: right; margin: 6px 10px;} +#g-autoupdate-config li { float: left; display: inline; line-height: 1.5em; } + +#g-admin-container { margin-top: 14px; font-size: 0.9em; line-height: 1.6em; } +#g-admin-container .column1 { float: left; width: 53%; min-width: 610px; } +#g-admin-container .column2 { float: right; width: 46%; min-width: 529px; } + +#g-admin-container fieldset { position: relative; border-top-left-radius: 0.4em; border-top-right-radius: 0.4em; overflow: hidden; } +#g-admin-container legend { position: absolute; left: 0; width: 100%; padding: 0.4em 0.8em; background: url(../images/blue-grad.png) #d5e6f2 repeat-x left top; border-bottom: #dfdfdf 1px solid; } +#g-admin-container fieldset ul { margin-top: 34px; } + diff --git a/modules/greydragon/images/blue-grad.png b/modules/greydragon/images/blue-grad.png new file mode 100644 index 0000000..868a657 Binary files /dev/null and b/modules/greydragon/images/blue-grad.png differ diff --git a/modules/greydragon/module.info b/modules/greydragon/module.info new file mode 100644 index 0000000..4a65e8c --- /dev/null +++ b/modules/greydragon/module.info @@ -0,0 +1,9 @@ +name = "GreyDragon Shared" +description = "Shared content for modules from GreyDragon. Need to be activated.
      Version 1.3 | By Serguei Dosyukov" +version = 13 +author_name = "Serguei Dosyukov" +author_url = "http://blog.dragonsoft.us/gallery-3/" +info_url = "" +discuss_url = "" + + diff --git a/modules/greydragon/views/gd_admin_include.html.php b/modules/greydragon/views/gd_admin_include.html.php new file mode 100644 index 0000000..73962cb --- /dev/null +++ b/modules/greydragon/views/gd_admin_include.html.php @@ -0,0 +1,180 @@ + + + + + +id == $downloadid): + $newversion = $json->version; + if ($json->version > $version): + return $json->version; + else: + return 0; + endif; + else: + return -1; + endif; + } catch (Exception $e) { + return -1; + } +} + +if ($is_module): + $admin_info = new ArrayObject(parse_ini_file(MODPATH . $name . "/module.info"), ArrayObject::ARRAY_AS_PROPS); + $version = number_format($admin_info->version / 10, 1, '.', ''); + $lastupdate = module::get_var($name, "last_update", time()); + $checkInDays = module::get_var($name, "auto_delay", 30); +else: + $admin_info = new ArrayObject(parse_ini_file(THEMEPATH . $name . "/theme.info"), ArrayObject::ARRAY_AS_PROPS); + $version = $admin_info->version; + $lastupdate = module::get_var("th_" . $name, "last_update", time()); + $checkInDays = module::get_var("th_" . $name, "auto_delay", 30); +endif; + +if (isCurlInstalled() && ($checkInDays > 0) && ((time() - $lastupdate) > ($checkInDays * 24 * 60 * 60))): // Check version every N days + $admin_info2 = new ArrayObject(parse_ini_file(MODPATH . "greydragon/module.info"), ArrayObject::ARRAY_AS_PROPS); + $version2 = number_format($admin_info2->version / 10, 1, '.', ''); + + $versionCheck = checkVersionInfo($downloadid, $version); + $versionCheck2 = checkVersionInfo(15, $version2); + + if (($versionCheck == 0) && ($versionCheck2 == 0)): + if ($is_module): + module::set_var($name, "last_update", time()); + else: + module::set_var("th_" . $name, "last_update", time()); + endif; + endif; +else: + $versionCheck = 0; + $versionCheck2 = 0; +endif; +?> + +
      +
      name) ?> -
      + +
      +
      +
      •   
      • +
      • +
      • + +
      •   |   
      • + +
      +
      + + +
      :
      + + +
      + + + +
      ' . t("here") . ' ' . t("for more info.") ?>
      + +
      + +
      + +
      +
      + +
      + + + +
      diff --git a/modules/html5_uploader/controllers/uploader.php b/modules/html5_uploader/controllers/uploader.php new file mode 100644 index 0000000..8f839a0 --- /dev/null +++ b/modules/html5_uploader/controllers/uploader.php @@ -0,0 +1,126 @@ +is_album()) { + $album = $album->parent(); + } + + print $this->_get_add_form($album); + } + + public function add($id) { + $album = ORM::factory("item", $id); + access::required("view", $album); + access::required("add", $album); + access::verify_csrf(); + + $form = $this->_get_add_form($album); + if ($form->validate()) { + batch::start(); + + $count = 0; + $added_a_movie = false; + $added_a_photo = false; + + $files_list=$_FILES['files']; + foreach (array_keys($files_list['name']) as $index) { + try { + $temp_filename = $files_list['tmp_name'][$index]; + $item = ORM::factory("item"); + $item->name = basename($files_list['name'][$index]); + $item->title = item::convert_filename_to_title($item->name); + $item->parent_id = $album->id; + $item->set_data_file($temp_filename); + + $path_info = @pathinfo($item->name); + if (array_key_exists("extension", $path_info) && + in_array(strtolower($path_info["extension"]), array("flv", "mp4", "m4v"))) { + $item->type = "movie"; + $item->save(); + $added_a_movie = true; + log::success("content", t("Added a movie"), + html::anchor("movies/$item->id", t("view movie"))); + } else { + $item->type = "photo"; + $item->save(); + $added_a_photo = true; + log::success("content", t("Added a photo"), + html::anchor("photos/$item->id", t("view photo"))); + } + $count++; + module::event("add_photos_form_completed", $item, $form); + } catch (Exception $e) { + // Lame error handling for now. Just record the exception and move on + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + + // Ugh. I hate to use instanceof, But this beats catching the exception separately since + // we mostly want to treat it the same way as all other exceptions + if ($e instanceof ORM_Validation_Exception) { + Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); + } + } + + if (file_exists($temp_filename)) { + unlink($temp_filename); + } + } + batch::stop(); + if ($count) { + if ($added_a_photo && $added_a_movie) { + message::success(t("Added %count photos and movies", array("count" => $count))); + } else if ($added_a_photo) { + message::success(t2("Added one photo", "Added %count photos", $count)); + } else { + message::success(t2("Added one movie", "Added %count movies", $count)); + } + } + json::reply(array("result" => "success")); + } else { + json::reply(array("result" => "error", "html" => (string) $form)); + } + + // Override the application/json mime type. The dialog based HTML uploader uses an iframe to + // buffer the reply, and on some browsers (Firefox 3.6) it does not know what to do with the + // JSON that it gets back so it puts up a dialog asking the user what to do with it. So force + // the encoding type back to HTML for the iframe. + // See: http://jquery.malsup.com/form/#file-upload + header("Content-Type: text/html; charset=" . Kohana::CHARSET); + } + + private function _get_add_form($album) { + $form = new Forge("uploader/add/{$album->id}", "", "post", array("id" => "g-add-photos-form")); + $group = $form->group("add_photos") + ->label(t("Add photos to %album_title", array("album_title" => html::purify($album->title)))); + $group->input("files[]")->type("file")->multiple(); + + $form->input("FOO")->type("hidden")->label(sprintf("Es können mehrere Bilder auf einmal hochgeladen werden. Dies dauert allerdings etwas - ohne extra Ladebalken. Also Geduld bewahren. Max. upload size of all pictures: %.0f MB.", ini_get("upload_max_filesize"))); + + module::event("add_photos_form", $album, $form); + + $group = $form->group("buttons")->label(""); + $group->submit("")->value(t("Upload")); + + return $form; + } +} diff --git a/modules/html5_uploader/module.info b/modules/html5_uploader/module.info new file mode 100644 index 0000000..542de3e --- /dev/null +++ b/modules/html5_uploader/module.info @@ -0,0 +1,7 @@ +name = "HTML5 Uploader" +description = "Simple HTML uploader that replaces the Flash based uploader" +version = 1 +author_name = "" +author_url = "" +info_url = "http://codex.gallery2.org/Gallery3:Modules:html_uploader" +discuss_url = "http://gallery.menalto.com/forum_module_html_uploader" diff --git a/modules/image_block/controllers/image_block.php b/modules/image_block/controllers/image_block.php new file mode 100644 index 0000000..3198970 --- /dev/null +++ b/modules/image_block/controllers/image_block.php @@ -0,0 +1,27 @@ +abs_url()); + } +} diff --git a/modules/image_block/helpers/image_block_block.php b/modules/image_block/helpers/image_block_block.php new file mode 100644 index 0000000..37e5b4c --- /dev/null +++ b/modules/image_block/helpers/image_block_block.php @@ -0,0 +1,56 @@ + t("Random image")); + } + + static function get($block_id, $theme) { + $block = ""; + switch ($block_id) { + case "random_image": + // The random_query approach is flawed and doesn't always return a + // result when there actually is one. Retry a *few* times. + // @todo Consider another fallback if further optimizations are necessary. + $image_count = module::get_var("image_block", "image_count"); + $items = array(); + for ($i = 0; $i < $image_count; $i++) { + $attempts = 0; + $item = null; + do { + $item = item::random_query()->where("type", "!=", "album")->find_all(1)->current(); + } while (!$item && $attempts++ < 3); + if ($item) { + $items[] = $item; + } + } + if ($items) { + $block = new Block(); + $block->css_id = "g-image-block"; + $block->title = t2("Random image", "Random images", $image_count); + $block->content = new View("image_block_block.html"); + $block->content->items = $items; + } + break; + } + + return $block; + } +} diff --git a/modules/image_block/helpers/image_block_installer.php b/modules/image_block/helpers/image_block_installer.php new file mode 100644 index 0000000..b177b97 --- /dev/null +++ b/modules/image_block/helpers/image_block_installer.php @@ -0,0 +1,43 @@ + + + + diff --git a/modules/info/helpers/info_block.php b/modules/info/helpers/info_block.php new file mode 100644 index 0000000..e4b5adf --- /dev/null +++ b/modules/info/helpers/info_block.php @@ -0,0 +1,93 @@ + t("Metadata")); + } + + static function get($block_id, $theme) { + $block = ""; + switch ($block_id) { + case "metadata": + if ($theme->item()) { + $block = new Block(); + $block->css_id = "g-metadata"; + $block->title = $theme->item()->is_album() ? t("Album info") : + ($theme->item()->is_movie() ? t("Movie info") : t("Photo info")); + $block->content = new View("info_block.html"); + if ($theme->item->title && module::get_var("info", "show_title")) { + $info["title"] = array( + "label" => t("Title:"), + "value" => html::purify($theme->item->title) + ); + } + if ($theme->item->description && module::get_var("info", "show_description")) { + $info["description"] = array( + "label" => t("Description:"), + "value" => nl2br(html::purify($theme->item->description)) + ); + } + if (!$theme->item->is_album() && module::get_var("info", "show_name")) { + $info["file_name"] = array( + "label" => t("File name:"), + "value" => html::clean($theme->item->name) + ); + } + if ($theme->item->captured && module::get_var("info", "show_captured")) { + $info["captured"] = array( + "label" => t("Captured:"), + "value" => gallery::date_time($theme->item->captured) + ); + } + if ($theme->item->owner && module::get_var("info", "show_owner")) { + $display_name = $theme->item->owner->display_name(); + if ($theme->item->owner->url) { + $info["owner"] = array( + "label" => t("Owner:"), + "value" => html::anchor( + html::clean($theme->item->owner->url), + html::clean($display_name)) + ); + } else { + $info["owner"] = array( + "label" => t("Owner:"), + "value" => html::clean($display_name) + ); + } + } + if (($theme->item->width && $theme->item->height) && + module::get_var("info", "show_dimensions")) { + $info["size"] = array( + "label" => t("Dimensions:"), + "value" => t( + "%width x %height px", + array("width" => $theme->item->width, "height" => $theme->item->height)) + ); + } + + $block->content->metadata = $info; + + module::event("info_block_get_metadata", $block, $theme->item); + } + break; + } + return $block; + } +} \ No newline at end of file diff --git a/modules/info/helpers/info_installer.php b/modules/info/helpers/info_installer.php new file mode 100644 index 0000000..2d06a0e --- /dev/null +++ b/modules/info/helpers/info_installer.php @@ -0,0 +1,45 @@ +view_count) { + $results .= "
    • "; + $results .= t("Views: %view_count", array("view_count" => $item->view_count)); + $results .= "
    • "; + } + if ($item->owner) { + $results .= "
    • "; + if ($item->owner->url) { + $results .= t("By: %owner_name", + array("owner_name" => $item->owner->display_name(), + "owner_url" => $item->owner->url)); + } else { + $results .= t("By: %owner_name", array("owner_name" => $item->owner->display_name())); + } + $results .= "
    • "; + } + return $results; + } +} \ No newline at end of file diff --git a/modules/info/module.info b/modules/info/module.info new file mode 100644 index 0000000..33b1622 --- /dev/null +++ b/modules/info/module.info @@ -0,0 +1,7 @@ +name = "Info" +description = "Display extra information about photos and albums" +version = 3 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:info" +discuss_url = "http://galleryproject.org/forum_module_info" diff --git a/modules/info/views/info_block.html.php b/modules/info/views/info_block.html.php new file mode 100644 index 0000000..b296fa1 --- /dev/null +++ b/modules/info/views/info_block.html.php @@ -0,0 +1,8 @@ + + diff --git a/modules/kbd_nav/changelog.txt b/modules/kbd_nav/changelog.txt new file mode 100644 index 0000000..148e1e5 --- /dev/null +++ b/modules/kbd_nav/changelog.txt @@ -0,0 +1,47 @@ +Kbd Navigation Changelog + +version 2.1: +- Fixed issue with jQuery extension not always initialized +- New Shortcodes: + Shift + C - Go to Calendar Page - Album/Photo page + Shift + E - Open Edit Dialog - Album/Photo page + Shift + F - Open Full Size - Photo page + Shift + I - Open Exif Info dialog - Photo page + Shift + S - Go to Search Box + Shift + V - Thumb/Photo Click to open Slideshow, where supported + +version 2.0: +- Module info adjusted to match new format in G3 3.0.2+ +- Added support for ColorBox + +version 1.9: +- Code adjusments to comply with new G3 requirements + +version 1.8: +- Added logic to prevent navigation when Fancybox is opened. + +version 1.7: +- Added logic further protecting user input activities (ex: comments) + +version 1.6: +- Added logic to prevent navigation when dialogs are opened. +- Changed detection of Shadowbox preview overlay + +version 1.5: +- Fix for RTL detection +- Added support for Wind theme + +version 1.4: +- Added RTL detection + +version 1.3: +- Internal revision + +version 1.2: +- Added support for GreyDragon Photo Slideshow navigation - in Photo SB slideshow mode, key navigation is superseded by slideshow navigation. + +version 1.1: +- Internal revision + +version 1.0: +- Initial release \ No newline at end of file diff --git a/modules/kbd_nav/helpers/kbd_nav_theme.php b/modules/kbd_nav/helpers/kbd_nav_theme.php new file mode 100644 index 0000000..c539570 --- /dev/null +++ b/modules/kbd_nav/helpers/kbd_nav_theme.php @@ -0,0 +1,7 @@ +script("kbd_nav.js"); + } +} \ No newline at end of file diff --git a/modules/kbd_nav/js/kbd_nav.js b/modules/kbd_nav/js/kbd_nav.js new file mode 100644 index 0000000..8a22d98 --- /dev/null +++ b/modules/kbd_nav/js/kbd_nav.js @@ -0,0 +1,120 @@ +/** +* +* Copyright (c) 2010-2012 Serguei Dosyukov, http://blog.dragonsoft.us +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +(function($) { + $.fn.KbdNavigation = function(options, callback) { + this.options = options || {}; + var opt = this.options; + this.callback = callback || null; + var clbk = this.callback; + + $(this).bind("keydown", function(event) { + if (event.target.type) { return true; } + if (($('div#sb-overlay').is(':visible')) || ($('div#fancybox-overlay').is(':visible')) || ($('div.ui-widget-overlay').is(':visible')) || ($('div#cboxOverlay').is(':visible'))) { return true; } + + var direction = "ltr"; + if (document.body) { + if (window.getComputedStyle) { + direction = window.getComputedStyle(document.body, null).direction; + } else if (document.body.currentStyle) { + direction = document.body.currentStyle.direction; + } + } + + var lnk = ""; + var lnk_first, lnk_prev, lnk_parent, lnk_next, lnk_last; + + if(opt.first) { lnk_first = opt.first; } else { lnk_first = $("#g-navi-first").attr("href"); } + if(opt.prev) { lnk_prev = opt.prev; } else { lnk_prev = $("#g-navi-prev").attr("href"); } + if(opt.parent) { lnk_parent = opt.parent; } else { lnk_parent = $("#g-navi-parent").attr("href"); } + if(opt.next) { lnk_next = opt.next; } else { lnk_next = $("#g-navi-next").attr("href"); } + if(opt.last) { lnk_last = opt.last; } else { lnk_last = $("#g-navi-last").attr("href"); } + + // Support for standard Wind Theme tags + if(!lnk_first) { lnk_first = $(".g-paginator .ui-icon-seek-first").parent().attr("href"); } + if(!lnk_prev) { lnk_prev = $(".g-paginator .ui-icon-seek-prev").parent().attr("href"); } + if(!lnk_next) { lnk_next = $(".g-paginator .ui-icon-seek-next").parent().attr("href"); } + if(!lnk_last) { lnk_last = $(".g-paginator .ui-icon-seek-end").parent().attr("href"); } + + var keyCode = event.keyCode; + + if (direction == "rtl") { + switch(keyCode) { + case 0x25: // Left + keyCode = 0x27; + break; + case 0x27: // Right + keyCode = 0x25; + break; + } + } + + switch(keyCode) { + case 0x25: // Ctr+Left/Left + if(event.ctrlKey) { lnk = lnk_first; } else { lnk = lnk_prev; } + break; + case 0x26: // Ctrl+Up + if(event.ctrlKey) { lnk = lnk_parent; } + break; + case 0x27: // Ctrl+Right/Right + if(event.ctrlKey) { lnk = lnk_last; } else { lnk = lnk_next; } + break; + case 0x43: // Shift + C - Go to Calendar Page - Album/Photo page + if(event.shiftKey) { lnk = $('#g-view-menu a#g-calendarview-link').attr("href"); } + break; + case 0x45: // Shift + E - Open Edit Dialog - Album/Photo page + if(event.shiftKey) { $('#g-site-menu li a.ui-icon-pencil').click(); return false; } + break; + case 0x46: // Shift + F - Open Full Size - Photo page + if(event.shiftKey) { $('#g-view-menu a.g-fullsize-link').click(); return false; } + break; + case 0x49: // Shift + I - Open Exif Info dialog - Photo page + if(event.shiftKey) { $('#g-view-menu a#g-exifdata-link').click(); return false; } + break; + case 0x53: // Shift + S - Go to Search Box + if(event.shiftKey) { $('#g-quick-search-form input[name="q"]').focus(); return false; } + break; + case 0x56: // Shift + V - Thumb/Photo Click to open Slideshow, where supported + if(event.shiftKey) { + $('ul#g-album-grid a.g-sb-preview:visible:first').click(); + $('#g-photo a.g-sb-preview:not(.g-hide)').click(); + return false; + } + break; + } + + if(lnk) { + if(typeof clbk == 'function') { + clbk(); + return false; + } else { + window.location = lnk; + return true; + } + } + return true; + }); + + return this; + } +})(jQuery); + +$(document).ready( function() { + $(document).KbdNavigation({}); + if ($('#sb-content').is(':visible')) { return true; } +}); \ No newline at end of file diff --git a/modules/kbd_nav/module.info b/modules/kbd_nav/module.info new file mode 100644 index 0000000..dd18692 --- /dev/null +++ b/modules/kbd_nav/module.info @@ -0,0 +1,8 @@ +name = "Kbd Navigation" +description = "Adds keyboard navigation to the gallery.
      Version 2.1 | By Serguei Dosyukov | Visit plugin Site | Support" +version = 21 +author_name = "Serguei Dosyukov" +author_url = "http://blog.dragonsoft.us/gallery-3/" +info_url = "http://codex.gallery2.org/Gallery3:Modules:kbd_nav"; +discuss_url = "http://gallery.menalto.com/node/95438"; + diff --git a/modules/kohana23_compat/config/pagination.php b/modules/kohana23_compat/config/pagination.php new file mode 100644 index 0000000..174bc23 --- /dev/null +++ b/modules/kohana23_compat/config/pagination.php @@ -0,0 +1,27 @@ + "pagination", + "style" => "classic", + "uri_segment" => 3, + "query_string" => "", + "items_per_page" => 20, + "auto_hide" => FALSE +); diff --git a/modules/kohana23_compat/libraries/MY_Database_Builder.php b/modules/kohana23_compat/libraries/MY_Database_Builder.php new file mode 100644 index 0000000..4a09b20 --- /dev/null +++ b/modules/kohana23_compat/libraries/MY_Database_Builder.php @@ -0,0 +1,50 @@ +where($tuple[0], $tuple[1], $tuple[2]); + } + } + return $this; + } + + /** + * Merge in a series of where clause tuples and call or_where() on each one. + * @chainable + */ + public function merge_or_where($tuples) { + if ($tuples) { + foreach ($tuples as $tuple) { + $this->or_where($tuple[0], $tuple[1], $tuple[2]); + } + } + return $this; + } + + public function compile() { + return parent::compile(); + } +} \ No newline at end of file diff --git a/modules/kohana23_compat/libraries/Pagination.php b/modules/kohana23_compat/libraries/Pagination.php new file mode 100644 index 0000000..bd74a34 --- /dev/null +++ b/modules/kohana23_compat/libraries/Pagination.php @@ -0,0 +1,252 @@ +initialize($config); + } + + /** + * Sets config values. + * + * @throws Kohana_Exception + * @param array configuration settings + * @return void + */ + public function initialize($config = array()) + { + // Load config group + if (isset($config['group'])) + { + // Load and validate config group + if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group']))) + throw new Kohana_Exception('pagination.undefined_group: ' . $config['group']); + + // All pagination config groups inherit default config group + if ($config['group'] !== 'default') + { + // Load and validate default config group + if ( ! is_array($default_config = Kohana::config('pagination.default'))) + throw new Kohana_Exception('pagination.undefined_group: default'); + + // Merge config group with default config group + $group_config += $default_config; + } + + // Merge custom config items with config group + $config += $group_config; + } + + // Assign config values to the object + foreach ($config as $key => $value) + { + if (property_exists($this, $key)) + { + $this->$key = $value; + } + } + + // Clean view directory + $this->directory = trim($this->directory, '/').'/'; + + // Build generic URL with page in query string + if ($this->query_string !== '') + { + // Extract current page + $this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1; + + // Insert {page} placeholder + $_GET[$this->query_string] = '{page}'; + + // Create full URL + $base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url; + $this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET)); + + // Reset page number + $_GET[$this->query_string] = $this->current_page; + } + + // Build generic URL with page as URI segment + else + { + // Use current URI if no base_url set + $this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/')); + + // Convert uri 'label' to corresponding integer if needed + if (is_string($this->uri_segment)) + { + if (($key = array_search($this->uri_segment, $this->url)) === FALSE) + { + // If uri 'label' is not found, auto add it to base_url + $this->url[] = $this->uri_segment; + $this->uri_segment = count($this->url) + 1; + } + else + { + $this->uri_segment = $key + 2; + } + } + + // Insert {page} placeholder + $this->url[$this->uri_segment - 1] = '{page}'; + + // Create full URL + $this->url = url::site(implode('/', $this->url)).Router::$query_string; + + // Extract current page + $this->current_page = URI::instance()->segment($this->uri_segment); + } + + // Core pagination values + $this->total_items = (int) max(0, $this->total_items); + $this->items_per_page = (int) max(1, $this->items_per_page); + $this->total_pages = (int) ceil($this->total_items / $this->items_per_page); + $this->current_page = (int) min(max(1, $this->current_page), max(1, $this->total_pages)); + $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items); + $this->current_last_item = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items); + + // If there is no first/last/previous/next page, relative to the + // current page, value is set to FALSE. Valid page number otherwise. + $this->first_page = ($this->current_page === 1) ? FALSE : 1; + $this->last_page = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages; + $this->previous_page = ($this->current_page > 1) ? $this->current_page - 1 : FALSE; + $this->next_page = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE; + + // SQL values + $this->sql_offset = (int) ($this->current_page - 1) * $this->items_per_page; + $this->sql_limit = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset); + } + + /** + * Generates the HTML for the chosen pagination style. + * + * @param string pagination style + * @return string pagination html + */ + public function render($style = NULL) + { + // Hide single page pagination + if ($this->auto_hide === TRUE AND $this->total_pages <= 1) + return ''; + + if ($style === NULL) + { + // Use default style + $style = $this->style; + } + + // Return rendered pagination view + return View::factory($this->directory.$style, get_object_vars($this))->render(); + } + + /** + * Magically converts Pagination object to string. + * + * @return string pagination html + */ + public function __toString() + { + return $this->render(); + } + + /** + * Magically gets a pagination variable. + * + * @param string variable key + * @return mixed variable value if the key is found + * @return void if the key is not found + */ + public function __get($key) + { + if (isset($this->$key)) + return $this->$key; + } + + /** + * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages(). + * Note that $pagination->total_pages is the recommended way to access properties. + * + * @param string function name + * @return string + */ + public function __call($func, $args = NULL) + { + return $this->__get($func); + } + +} // End Pagination Class \ No newline at end of file diff --git a/modules/localprint/controllers/admin_localprint.php b/modules/localprint/controllers/admin_localprint.php new file mode 100644 index 0000000..ba792fe --- /dev/null +++ b/modules/localprint/controllers/admin_localprint.php @@ -0,0 +1,26 @@ +content = new View("admin_localprint.html"); + print $v; + } +} \ No newline at end of file diff --git a/modules/localprint/css/localprint_menu.css b/modules/localprint/css/localprint_menu.css new file mode 100644 index 0000000..1048d09 --- /dev/null +++ b/modules/localprint/css/localprint_menu.css @@ -0,0 +1,16 @@ +#g-view-menu #g-localprint-link { + background-image: url('../images/localprint_logo.png'); +} +@media screen { + .printimage { + display: none; + } +} +@media print { + body * { + display: none; + } + .printimage { + display: block; + } +} \ No newline at end of file diff --git a/modules/localprint/helpers/localprint_event.php b/modules/localprint/helpers/localprint_event.php new file mode 100644 index 0000000..5fd9bf2 --- /dev/null +++ b/modules/localprint/helpers/localprint_event.php @@ -0,0 +1,39 @@ +get("settings_menu") + ->append(Menu::factory("link") + ->id("localprint_menu") + ->label(t("Local print")) + ->url(url::site("admin/localprint"))); + } + + static function photo_menu($menu, $theme) { + if (access::can("view_full", $theme->item)) { + $item = $theme->item(); + $menu->append(Menu::factory("link") + ->id("localprint") + ->label(t("Print with local printer")) + ->url("#") + ->css_id("g-localprint-link")); + } + } +} diff --git a/modules/localprint/helpers/localprint_installer.php b/modules/localprint/helpers/localprint_installer.php new file mode 100644 index 0000000..7d45603 --- /dev/null +++ b/modules/localprint/helpers/localprint_installer.php @@ -0,0 +1,35 @@ +css("localprint_menu.css"); + } + static function photo_bottom($theme) { + return new View("localprint_code.html"); + } +} diff --git a/modules/localprint/images/localprint_logo.png b/modules/localprint/images/localprint_logo.png new file mode 100644 index 0000000..3022b09 Binary files /dev/null and b/modules/localprint/images/localprint_logo.png differ diff --git a/modules/localprint/js/localprint.js b/modules/localprint/js/localprint.js new file mode 100644 index 0000000..6f4ab22 --- /dev/null +++ b/modules/localprint/js/localprint.js @@ -0,0 +1,8 @@ +$(document).ready(function(){ + alert('One'); + $("#g-localprint-link").click(function() { + alert('The two of them'); + $("body").append(""); + window.print(); + }); +}); \ No newline at end of file diff --git a/modules/localprint/module.info b/modules/localprint/module.info new file mode 100644 index 0000000..558e3ba --- /dev/null +++ b/modules/localprint/module.info @@ -0,0 +1,3 @@ +name = "Local print" +description = "Adds a print button for users to print to a local printer." +version = 1 diff --git a/modules/localprint/views/admin_localprint.html.php b/modules/localprint/views/admin_localprint.html.php new file mode 100644 index 0000000..81bff35 --- /dev/null +++ b/modules/localprint/views/admin_localprint.html.php @@ -0,0 +1,4 @@ + +
      + some stuff for the future +
      diff --git a/modules/localprint/views/localprint_code.html.php b/modules/localprint/views/localprint_code.html.php new file mode 100644 index 0000000..5e2541d --- /dev/null +++ b/modules/localprint/views/localprint_code.html.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/modules/notification/controllers/notification.php b/modules/notification/controllers/notification.php new file mode 100644 index 0000000..67f8ed3 --- /dev/null +++ b/modules/notification/controllers/notification.php @@ -0,0 +1,36 @@ +title))); + } else { + notification::add_watch($item); + message::success(sprintf(t("You are now watching %s"), html::purify($item->title))); + } + url::redirect($item->abs_url()); + } +} diff --git a/modules/notification/helpers/notification.php b/modules/notification/helpers/notification.php new file mode 100644 index 0000000..96493a2 --- /dev/null +++ b/modules/notification/helpers/notification.php @@ -0,0 +1,218 @@ +where("item_id", "=", $item_id) + ->where("user_id", "=", $user->id) + ->find(); + } + + static function is_watching($item, $user=null) { + if (empty($user)) { + $user = identity::active_user(); + } + + return ORM::factory("subscription") + ->where("item_id", "=", $item->id) + ->where("user_id", "=", $user->id) + ->find() + ->loaded(); + } + + static function add_watch($item, $user=null) { + if ($item->is_album()) { + if (empty($user)) { + $user = identity::active_user(); + } + $subscription = ORM::factory("subscription"); + $subscription->item_id = $item->id; + $subscription->user_id = $user->id; + $subscription->save(); + } + } + + static function remove_watch($item, $user=null) { + if ($item->is_album()) { + if (empty($user)) { + $user = identity::active_user(); + } + + $subscription = ORM::factory("subscription") + ->where("item_id", "=", $item->id) + ->where("user_id", "=", $user->id) + ->find()->delete(); + } + } + + static function get_subscribers($item) { + $subscriber_ids = array(); + foreach (ORM::factory("subscription") + ->select("user_id") + ->join("items", "subscriptions.item_id", "items.id") + ->where("items.left_ptr", "<=", $item->left_ptr) + ->where("items.right_ptr", ">", $item->right_ptr) + ->find_all() + ->as_array() as $subscriber) { + $subscriber_ids[] = $subscriber->user_id; + } + + if (empty($subscriber_ids)) { + return array(); + } + $users = identity::get_user_list($subscriber_ids); + + $subscribers = array(); + foreach ($users as $user) { + if (access::user_can($user, "view", $item) && !empty($user->email)) { + $subscribers[$user->email] = $user->locale; + } + } + return $subscribers; + } + + static function send_item_updated($original, $item) { + foreach (self::get_subscribers($item) as $email => $locale) { + $v = new View("item_updated.html"); + $v->original = $original; + $v->item = $item; + $v->subject = $item->is_album() ? + t("Album \"%title\" updated", array("title" => $original->title, "locale" => $locale)) : + ($item->is_photo() ? + t("Photo \"%title\" updated", array("title" => $original->title, "locale" => $locale)) + : t("Movie \"%title\" updated", array("title" => $original->title, "locale" => $locale))); + self::_notify($email, $locale, $item, $v->render(), $v->subject); + } + } + + static function send_item_add($item) { + $parent = $item->parent(); + foreach (self::get_subscribers($item) as $email => $locale) { + $v = new View("item_added.html"); + $v->item = $item; + $v->subject = $item->is_album() ? + t("Album \"%title\" added to \"%parent_title\"", + array("title" => $item->title, "parent_title" => $parent->title, "locale" => $locale)) : + ($item->is_photo() ? + t("Photo \"%title\" added to \"%parent_title\"", + array("title" => $item->title, "parent_title" => $parent->title, "locale" => $locale)) : + t("Movie \"%title\" added to \"%parent_title\"", + array("title" => $item->title, "parent_title" => $parent->title, "locale" => $locale))); + self::_notify($email, $locale, $item, $v->render(), $v->subject); + } + } + + static function send_item_deleted($item) { + $parent = $item->parent(); + foreach (self::get_subscribers($item) as $email => $locale) { + $v = new View("item_deleted.html"); + $v->item = $item; + $v->subject = $item->is_album() ? + t("Album \"%title\" removed from \"%parent_title\"", + array("title" => $item->title, "parent_title" => $parent->title, "locale" => $locale)) : + ($item->is_photo() ? + t("Photo \"%title\" removed from \"%parent_title\"", + array("title" => $item->title, "parent_title" => $parent->title, "locale" => $locale)) + : t("Movie \"%title\" removed from \"%parent_title\"", + array("title" => $item->title, "parent_title" => $parent->title, + "locale" => $locale))); + self::_notify($email, $locale, $item, $v->render(), $v->subject); + } + } + + static function send_comment_published($comment) { + $item = $comment->item(); + foreach (self::get_subscribers($item) as $email => $locale) { + $v = new View("comment_published.html"); + $v->comment = $comment; + $v->subject = $item->is_album() ? + t("A new comment was published for album \"%title\"", + array("title" => $item->title, "locale" => $locale)) : + ($item->is_photo() ? + t("A new comment was published for photo \"%title\"", + array("title" => $item->title, "locale" => $locale)) + : t("A new comment was published for movie \"%title\"", + array("title" => $item->title, "locale" => $locale))); + self::_notify($email, $locale, $item, $v->render(), $v->subject); + } + } + + static function send_pending_notifications() { + foreach (db::build() + ->select(db::expr("DISTINCT `email`")) + ->from("pending_notifications") + ->execute() as $row) { + $email = $row->email; + $result = ORM::factory("pending_notification") + ->where("email", "=", $email) + ->find_all(); + if ($result->count() == 1) { + $pending = $result->current(); + Sendmail::factory() + ->to($email) + ->subject($pending->subject) + ->header("Mime-Version", "1.0") + ->header("Content-Type", "text/html; charset=UTF-8") + ->message($pending->text) + ->send(); + $pending->delete(); + } else { + $text = ""; + $locale = null; + foreach ($result as $pending) { + $text .= $pending->text; + $locale = $pending->locale; + $pending->delete(); + } + Sendmail::factory() + ->to($email) + ->subject(t("New activity for %site_name", + array("site_name" => item::root()->title, "locale" => $locale))) + ->header("Mime-Version", "1.0") + ->header("Content-Type", "text/html; charset=UTF-8") + ->message($text) + ->send(); + } + } + } + + private static function _notify($email, $locale, $item, $text, $subject) { + if (!batch::in_progress()) { + Sendmail::factory() + ->to($email) + ->subject($subject) + ->header("Mime-Version", "1.0") + ->header("Content-Type", "text/html; charset=UTF-8") + ->message($text) + ->send(); + } else { + $pending = ORM::factory("pending_notification"); + $pending->subject = $subject; + $pending->text = $text; + $pending->email = $email; + $pending->locale = $locale; + $pending->save(); + } + } +} diff --git a/modules/notification/helpers/notification_event.php b/modules/notification/helpers/notification_event.php new file mode 100644 index 0000000..264ec55 --- /dev/null +++ b/modules/notification/helpers/notification_event.php @@ -0,0 +1,149 @@ +getMessage() . "\n" . $e->getTraceAsString()); + } + } + + static function item_deleted($item) { + try { + notification::send_item_deleted($item); + + if (notification::is_watching($item)) { + notification::remove_watch($item); + } + } catch (Exception $e) { + Kohana_Log::add("error", "@todo notification_event::item_deleted() failed"); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } + + static function user_deleted($user) { + db::build() + ->delete("subscriptions") + ->where("user_id", "=", $user->id) + ->execute(); + } + + static function identity_provider_changed($old_provider, $new_provider) { + db::build() + ->delete("subscriptions") + ->execute(); + } + + static function comment_created($comment) { + try { + if ($comment->state == "published") { + notification::send_comment_published($comment); + } + } catch (Exception $e) { + Kohana_Log::add("error", "@todo notification_event::comment_created() failed"); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } + + static function comment_updated($original, $new) { + try { + if ($new->state == "published" && $original->state != "published") { + notification::send_comment_published($new); + } + } catch (Exception $e) { + Kohana_Log::add("error", "@todo notification_event::comment_updated() failed"); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } + + static function user_before_delete($user) { + try { + db::build() + ->delete("subscriptions") + ->where("user_id", "=", $user->id) + ->execute(); + } catch (Exception $e) { + Kohana_Log::add("error", "@todo notification_event::user_before_delete() failed"); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } + + static function batch_complete() { + try { + notification::send_pending_notifications(); + } catch (Exception $e) { + Kohana_Log::add("error", "@todo notification_event::batch_complete() failed"); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } + + static function site_menu($menu, $theme) { + if (!identity::active_user()->guest) { + $item = $theme->item(); + + if ($item && $item->is_album() && access::can("view", $item)) { + $watching = notification::is_watching($item); + + $label = $watching ? t("Remove notifications") : t("Enable notifications"); + + $menu->get("options_menu") + ->append(Menu::factory("link") + ->id("watch") + ->label($label) + ->css_id("g-notify-link") + ->url(url::site("notification/watch/$item->id?csrf=" . access::csrf_token()))); + } + } + } + + static function show_user_profile($data) { + // Guests don't see comment listings + if (identity::active_user()->guest) { + return; + } + + // Only logged in users can see their comment listings + if (identity::active_user()->id != $data->user->id) { + return; + } + + $view = new View("user_profile_notification.html"); + $view->subscriptions = array(); + foreach(ORM::factory("subscription") + ->where("user_id", "=", $data->user->id) + ->find_all() as $subscription) { + $item = ORM::factory("item") + ->where("id", "=", $subscription->item_id) + ->find(); + if ($item->loaded()) { + $view->subscriptions[] = (object)array("id" => $subscription->id, "title" => $item->title, + "url" => $item->url()); + } + } + if (count($view->subscriptions) > 0) { + $data->content[] = (object)array("title" => t("Watching"), "view" => $view); + } + } +} \ No newline at end of file diff --git a/modules/notification/helpers/notification_installer.php b/modules/notification/helpers/notification_installer.php new file mode 100644 index 0000000..f6b05c1 --- /dev/null +++ b/modules/notification/helpers/notification_installer.php @@ -0,0 +1,54 @@ +query("CREATE TABLE IF NOT EXISTS {subscriptions} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9) NOT NULL, + `user_id` int(9) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY (`item_id`, `user_id`), + UNIQUE KEY (`user_id`, `item_id`)) + DEFAULT CHARSET=utf8;"); + $db->query("CREATE TABLE IF NOT EXISTS {pending_notifications} ( + `id` int(9) NOT NULL auto_increment, + `locale` char(10) default NULL, + `email` varchar(128) NOT NULL, + `subject` varchar(255) NOT NULL, + `text` text, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("ALTER TABLE {pending_notifications} ADD COLUMN `locale` char(10) default NULL"); + module::set_version("notification", $version = 2); + } + } + + static function uninstall() { + $db = Database::instance(); + $db->query("DROP TABLE IF EXISTS {subscriptions};"); + $db->query("DROP TABLE IF EXISTS {pending_notifications};"); + } +} diff --git a/modules/notification/models/pending_notification.php b/modules/notification/models/pending_notification.php new file mode 100644 index 0000000..4033aed --- /dev/null +++ b/modules/notification/models/pending_notification.php @@ -0,0 +1,21 @@ + + + + <?= html::clean($subject) ?> + + +

      + + + + + + + + + + + + + + + + + + + + + +
      text)) ?>
      author_name()) ?>
      author_email()) ?>
      author_url()) ?>
      + + item()->abs_url() ?>#comments + +
      + + diff --git a/modules/notification/views/item_added.html.php b/modules/notification/views/item_added.html.php new file mode 100644 index 0000000..1ea3720 --- /dev/null +++ b/modules/notification/views/item_added.html.php @@ -0,0 +1,29 @@ + + + + <?= html::clean($subject) ?> + + +

      + + + + + + + + + + description): ?> + + + + + +
      title) ?>
      + + abs_url() ?> + +
      description)) ?>
      + + diff --git a/modules/notification/views/item_deleted.html.php b/modules/notification/views/item_deleted.html.php new file mode 100644 index 0000000..a95cdd8 --- /dev/null +++ b/modules/notification/views/item_deleted.html.php @@ -0,0 +1,25 @@ + + + + <?= html::clean($subject) ?> + + +

      + + + + + + + + +
      + html::purify($item->parent()->title))) ?> +
      + + parent()->abs_url() ?> + +
      + + diff --git a/modules/notification/views/item_updated.html.php b/modules/notification/views/item_updated.html.php new file mode 100644 index 0000000..7020fd5 --- /dev/null +++ b/modules/notification/views/item_updated.html.php @@ -0,0 +1,35 @@ + + + + <?= html::clean($subject) ?> + + +

      + + + title != $item->title): ?> + + + + + + + + + + + + description != $item->description): ?> + + + + + description)): ?> + + + + + +
      title) ?>title) ?>
      abs_url() ?>
      description) ?>
      description) ?>
      + + diff --git a/modules/notification/views/user_profile_notification.html.php b/modules/notification/views/user_profile_notification.html.php new file mode 100644 index 0000000..8864f0c --- /dev/null +++ b/modules/notification/views/user_profile_notification.html.php @@ -0,0 +1,12 @@ + +
      + +
      diff --git a/modules/organize/controllers/organize.php b/modules/organize/controllers/organize.php new file mode 100644 index 0000000..ba73ae7 --- /dev/null +++ b/modules/organize/controllers/organize.php @@ -0,0 +1,228 @@ +album = $album; + print $v; + } + + function dialog($album_id) { + $album = ORM::factory("item", $album_id); + access::required("view", $album); + access::required("edit", $album); + + $v = new View("organize_dialog.html"); + $v->album = $album; + print $v; + } + + function tree($selected_album_id) { + $root = ORM::factory("item", Input::instance()->post("root_id", 1)); + $selected_album = ORM::factory("item", $selected_album_id); + access::required("view", $root); + access::required("view", $selected_album); + + $tree = $this->_get_tree($root, $selected_album); + json::reply($tree); + } + + function album_info($album_id) { + $album = ORM::factory("item", $album_id); + access::required("view", $album); + + $data = array( + "sort_column" => $album->sort_column, + "sort_order" => $album->sort_order, + "editable" => access::can("edit", $album), + "title" => (string)html::clean($album->title), + "children" => array()); + + foreach ($album->viewable()->children() as $child) { + $dims = $child->scale_dimensions(120); + $data["children"][] = array( + "id" => $child->id, + "thumb_url" => $child->has_thumb() ? $child->thumb_url() : null, + "width" => $dims[1], + "height" => $dims[0], + "type" => $child->type, + "title" => (string)html::clean($child->title)); + } + json::reply($data); + } + + function reparent() { + access::verify_csrf(); + + $input = Input::instance(); + $new_parent = ORM::factory("item", $input->post("target_id")); + access::required("edit", $new_parent); + + foreach (explode(",", $input->post("source_ids")) as $source_id) { + $source = ORM::factory("item", $source_id); + if (!$source->loaded()) { + continue; + } + access::required("edit", $source->parent()); + + if ($source->contains($new_parent) || $source->id == $new_parent->id) { + // Can't move an item into its own hierarchy. Silently skip this, + // since the UI shouldn't even allow this operation. + continue; + } + + $source->parent_id = $new_parent->id; + $source->save(); + } + json::reply(null); + } + + function set_sort($album_id) { + access::verify_csrf(); + $album = ORM::factory("item", $album_id); + access::required("view", $album); + access::required("edit", $album); + + foreach (array("sort_column", "sort_order") as $key) { + if ($val = Input::instance()->post($key)) { + $album->$key = $val; + } + } + $album->save(); + + json::reply(null); + } + + function rearrange() { + access::verify_csrf(); + + $input = Input::instance(); + $target = ORM::factory("item", $input->post("target_id")); + if (!$target->loaded()) { + json::reply(null); + return; + } + + $album = $target->parent(); + access::required("edit", $album); + + if ($album->sort_column != "weight") { + // Force all the weights into the current order before changing the order to manual + // @todo: consider making this a trigger in the Item_Model. + item::resequence_child_weights($album); + $album->sort_column = "weight"; + $album->sort_order = "ASC"; + $album->save(); + } + + $source_ids = explode(",", $input->post("source_ids")); + $base_weight = $target->weight; + if ($input->post("relative") == "after") { + $base_weight++; + } + + if ($source_ids) { + // Make a hole the right size + db::build() + ->update("items") + ->set("weight", db::expr("`weight` + " . count($source_ids))) + ->where("parent_id", "=", $album->id) + ->where("weight", ">=", $base_weight) + ->execute(); + + // Move all the source items to the right spots. + for ($i = 0; $i < count($source_ids); $i++) { + $source = ORM::factory("item", $source_ids[$i]); + if ($source->parent_id == $album->id) { + $source->weight = $base_weight + $i; + $source->save(); + } + } + } + json::reply(null); + } + + function delete() { + access::verify_csrf(); + + $input = Input::instance(); + + foreach (explode(",", $input->post("item_ids")) as $item_id) { + $item = ORM::factory("item", $item_id); + if (access::can("edit", $item)) { + $item->delete(); + } + } + + json::reply(null); + } + + function tag() { + access::verify_csrf(); + $input = Input::instance(); + + foreach (explode(",", $input->post("item_ids")) as $item_id) { + $item = ORM::factory("item", $item_id); + if (access::can("edit", $item)) { + // Assuming the user can view/edit the current item, loop + // through each tag that was submitted and apply it to + // the current item. + foreach (explode(",", $input->post("tag_names")) as $tag_name) { + $tag_name = trim($tag_name); + if ($tag_name) { + tag::add($item, $tag_name); + } + } + } + } + + json::reply(null); + } + + private function _get_tree($item, $selected) { + $tree = array(); + $children = $item->viewable() + ->children(null, null, array(array("type", "=", "album"))) + ->as_array(); + foreach ($children as $child) { + $node = array( + "allowDrag" => false, + "allowDrop" => access::can("edit", $child), + "editable" => false, + "expandable" => false, + "id" => $child->id, + "leaf" => $child->children_count(array(array("type", "=", "album"))) == 0, + "text" => (string)html::clean($child->title), + "nodeType" => "async"); + + // If the child is in the selected path, open it now. Else, mark it async. + if ($child->contains($selected)) { + $node["children"] = $this->_get_tree($child, $selected); + $node["expanded"] = true; + } + $tree[] = $node; + } + return $tree; + } +} diff --git a/modules/organize/css/organize_dialog.css b/modules/organize/css/organize_dialog.css new file mode 100644 index 0000000..2b39cdf --- /dev/null +++ b/modules/organize/css/organize_dialog.css @@ -0,0 +1,17 @@ +#g-organize-frame { + border: 0px; + width: 100%; + height: 100%; +} + +#g-organize-app-loading { + display: block; + position: absolute; + top: 50%; + left: 50%; + width: 16px; + height: 16px; + background-image: url(../vendor/ext/images/default/tree/loading.gif); + background-position: center center; + background-repeat: no-repeat; +} diff --git a/modules/organize/css/organize_frame.css b/modules/organize/css/organize_frame.css new file mode 100644 index 0000000..12bc609 --- /dev/null +++ b/modules/organize/css/organize_frame.css @@ -0,0 +1,118 @@ +.g-organize { + font-family: 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; +} + +.g-organize div.thumb { + padding: 8px; + width: 128px; + height: 128px; + vertical-align: middle; + float: left; +} + +.g-organize div.selected { + background: #C9D8EB; +} + +.g-organize div.thumb { + border: 4px solid white; + text-align: center; +} + +.g-organize div.thumb-missing span { + display: block; + background: #eee; + width: 120px; + height: 120px; + padding-top: 8px; + text-align: center; + font: 14px arial, tahoma; + border: 1px solid #ddd; + font-style: italic; +} + +.g-organize div.thumb:hover { + border: 2px solid #eee; + margin: 2px; + cursor: pointer; +} + +.g-organize div.thumb div.icon { + position: relative; + padding: 0px; + margin: 0px; + visibility: hidden; + width: 16px; + height: 16px; + top: -16px; + margin-bottom: -16px; + padding-bottom: -16px; +} + +.g-organize div.thumb-album div.icon { + visibility: visible; +} + +.g-organize div.drag-ghost { + width: 300px; + height: 180px; +} + +.g-organize div.drag-ghost div { + width: 72px; + height: 72px; + vertical-align: baseline; + float: left; +} + +.g-organize div.drop-target { + background: #eee; +} + +.g-organize div.active-left { + border-left: 4px solid #C9D8EB; +} + +.g-organize div.active-right { + border-right: 4px solid #C9D8EB; +} + +.g-organize label.sort { + font: 12px arial, tahoma; + vertical-align: middle; + font-weight: bold; + height: 22px; + text-align: center; + padding: 4px; +} + +.loading div { + font-size: 1.1em; + padding-left: 24px; + background-color: white; + background-image: url(../vendor/ext/images/default/tree/loading.gif); + background-position: 4px 8px; + background-repeat: no-repeat; +} + +button.delete { + background-image: url(../vendor/ext/images/fam/delete.gif); + background-position: 10px 8px; + background-repeat: no-repeat; +} + +/* IE specific overrides */ +body.ext-ie div.thumb { + width: 150px; + height: 150px; +} + +/* ExtJS overrides */ +.x-tree-node-el { + font-size: 12px; + line-height: 20px; +} + +.x-tree-node-leaf .x-tree-node-icon { + background-image:url(../vendor/ext/images/default/tree/folder.gif); +} diff --git a/modules/organize/helpers/organize_event.php b/modules/organize/helpers/organize_event.php new file mode 100644 index 0000000..2ca9e9b --- /dev/null +++ b/modules/organize/helpers/organize_event.php @@ -0,0 +1,54 @@ +item(); + + if ($item && $item->is_album() && access::can("edit", $item)) { + $menu->get("options_menu") + ->append(Menu::factory("dialog") + ->id("organize") + ->label(t("Organize album")) + ->css_id("g-organize-link") + ->url(url::site("organize/dialog/{$item->id}"))); + } + } + + static function context_menu($menu, $theme, $item) { + if (access::can("edit", $item)) { + if ($item->is_album()) { + $menu->get("options_menu") + ->append(Menu::factory("dialog") + ->id("organize") + ->label(t("Organize album")) + ->css_class("ui-icon-folder-open g-organize-link") + ->url(url::site("organize/dialog/{$item->id}"))); + } else { + $parent = $item->parent(); + $menu->get("options_menu") + ->append(Menu::factory("dialog") + ->id("move") + ->label(t("Move to another album")) + ->css_class("ui-icon-folder-open g-organize-link") + ->url(url::site("organize/dialog/{$parent->id}?selected_id={$item->id}"))); + } + } + } +} diff --git a/modules/organize/helpers/organize_installer.php b/modules/organize/helpers/organize_installer.php new file mode 100644 index 0000000..a2a0744 --- /dev/null +++ b/modules/organize/helpers/organize_installer.php @@ -0,0 +1,28 @@ + +
      +
      +

      YOUR TITLE HERE (optional)

      +
      YOUR CONTENT HERE
      +
      +
      + + */ + +.x-box-tl { + background: transparent no-repeat 0 0; + zoom:1; +} + +.x-box-tc { + height: 8px; + background: transparent repeat-x 0 0; + overflow: hidden; +} + +.x-box-tr { + background: transparent no-repeat right -8px; +} + +.x-box-ml { + background: transparent repeat-y 0; + padding-left: 4px; + overflow: hidden; + zoom:1; +} + +.x-box-mc { + background: repeat-x 0 -16px; + padding: 4px 10px; +} + +.x-box-mc h3 { + margin: 0 0 4px 0; + zoom:1; +} + +.x-box-mr { + background: transparent repeat-y right; + padding-right: 4px; + overflow: hidden; +} + +.x-box-bl { + background: transparent no-repeat 0 -16px; + zoom:1; +} + +.x-box-bc { + background: transparent repeat-x 0 -8px; + height: 8px; + overflow: hidden; +} + +.x-box-br { + background: transparent no-repeat right -24px; +} + +.x-box-tl, .x-box-bl { + padding-left: 8px; + overflow: hidden; +} + +.x-box-tr, .x-box-br { + padding-right: 8px; + overflow: hidden; +}.x-combo-list { + border:1px solid; + zoom:1; + overflow:hidden; +} + +.x-combo-list-inner { + overflow:auto; + position:relative; /* for calculating scroll offsets */ + zoom:1; + overflow-x:hidden; +} + +.x-combo-list-hd { + border-bottom:1px solid; + padding:3px; +} + +.x-resizable-pinned .x-combo-list-inner { + border-bottom:1px solid; +} + +.x-combo-list-item { + padding:2px; + border:1px solid; + white-space: nowrap; + overflow:hidden; + text-overflow: ellipsis; +} + +.x-combo-list .x-combo-selected{ + border:1px dotted !important; + cursor:pointer; +} + +.x-combo-list .x-toolbar { + border-top:1px solid; + border-bottom:0 none; +}.x-panel { + border-style: solid; + border-width:0; +} + +.x-panel-header { + overflow:hidden; + zoom:1; + padding:5px 3px 4px 5px; + border:1px solid; + line-height: 15px; + background: transparent repeat-x 0 -1px; +} + +.x-panel-body { + border:1px solid; + border-top:0 none; + overflow:hidden; + position: relative; /* added for item scroll positioning */ +} + +.x-panel-bbar .x-toolbar, .x-panel-tbar .x-toolbar { + border:1px solid; + border-top:0 none; + overflow:hidden; + padding:2px; +} + +.x-panel-tbar-noheader .x-toolbar, .x-panel-mc .x-panel-tbar .x-toolbar { + border-top:1px solid; + border-bottom: 0 none; +} + +.x-panel-body-noheader, .x-panel-mc .x-panel-body { + border-top:1px solid; +} + +.x-panel-header { + overflow:hidden; + zoom:1; +} + +.x-panel-tl .x-panel-header { + padding:5px 0 4px 0; + border:0 none; + background:transparent no-repeat; +} + +.x-panel-tl .x-panel-icon, .x-window-tl .x-panel-icon { + padding-left:20px !important; + background-repeat:no-repeat; + background-position:0 4px; + zoom:1; +} + +.x-panel-inline-icon { + width:16px; + height:16px; + background-repeat:no-repeat; + background-position:0 0; + vertical-align:middle; + margin-right:4px; + margin-top:-1px; + margin-bottom:-1px; +} + +.x-panel-tc { + background: transparent repeat-x 0 0; + overflow:hidden; +} + +/* fix ie7 strict mode bug */ +.ext-strict .ext-ie7 .x-panel-tc { + overflow: visible; +} + +.x-panel-tl { + background: transparent no-repeat 0 0; + padding-left:6px; + zoom:1; + border-bottom:1px solid; +} + +.x-panel-tr { + background: transparent no-repeat right 0; + zoom:1; + padding-right:6px; +} + +.x-panel-bc { + background: transparent repeat-x 0 bottom; + zoom:1; +} + +.x-panel-bc .x-panel-footer { + zoom:1; +} + +.x-panel-bl { + background: transparent no-repeat 0 bottom; + padding-left:6px; + zoom:1; +} + +.x-panel-br { + background: transparent no-repeat right bottom; + padding-right:6px; + zoom:1; +} + +.x-panel-mc { + border:0 none; + padding:0; + margin:0; + padding-top:6px; +} + +.x-panel-mc .x-panel-body { + background-color:transparent; + border: 0 none; +} + +.x-panel-ml { + background: repeat-y 0 0; + padding-left:6px; + zoom:1; +} + +.x-panel-mr { + background: transparent repeat-y right 0; + padding-right:6px; + zoom:1; +} + +.x-panel-bc .x-panel-footer { + padding-bottom:6px; +} + +.x-panel-nofooter .x-panel-bc, .x-panel-nofooter .x-window-bc { + height:6px; + font-size:0; + line-height:0; +} + +.x-panel-bwrap { + overflow:hidden; + zoom:1; + left:0; + top:0; +} +.x-panel-body { + overflow:hidden; + zoom:1; +} + +.x-panel-collapsed .x-resizable-handle{ + display:none; +} + +.ext-gecko .x-panel-animated div { + overflow:hidden !important; +} + +/* Plain */ +.x-plain-body { + overflow:hidden; +} + +.x-plain-bbar .x-toolbar { + overflow:hidden; + padding:2px; +} + +.x-plain-tbar .x-toolbar { + overflow:hidden; + padding:2px; +} + +.x-plain-bwrap { + overflow:hidden; + zoom:1; +} + +.x-plain { + overflow:hidden; +} + +/* Tools */ +.x-tool { + overflow:hidden; + width:15px; + height:15px; + float:right; + cursor:pointer; + background:transparent no-repeat; + margin-left:2px; +} + +/* expand / collapse tools */ +.x-tool-toggle { + background-position:0 -60px; +} + +.x-tool-toggle-over { + background-position:-15px -60px; +} + +.x-panel-collapsed .x-tool-toggle { + background-position:0 -75px; +} + +.x-panel-collapsed .x-tool-toggle-over { + background-position:-15px -75px; +} + + +.x-tool-close { + background-position:0 -0; +} + +.x-tool-close-over { + background-position:-15px 0; +} + +.x-tool-minimize { + background-position:0 -15px; +} + +.x-tool-minimize-over { + background-position:-15px -15px; +} + +.x-tool-maximize { + background-position:0 -30px; +} + +.x-tool-maximize-over { + background-position:-15px -30px; +} + +.x-tool-restore { + background-position:0 -45px; +} + +.x-tool-restore-over { + background-position:-15px -45px; +} + +.x-tool-gear { + background-position:0 -90px; +} + +.x-tool-gear-over { + background-position:-15px -90px; +} + +.x-tool-prev { + background-position:0 -105px; +} + +.x-tool-prev-over { + background-position:-15px -105px; +} + +.x-tool-next { + background-position:0 -120px; +} + +.x-tool-next-over { + background-position:-15px -120px; +} + +.x-tool-pin { + background-position:0 -135px; +} + +.x-tool-pin-over { + background-position:-15px -135px; +} + +.x-tool-unpin { + background-position:0 -150px; +} + +.x-tool-unpin-over { + background-position:-15px -150px; +} + +.x-tool-right { + background-position:0 -165px; +} + +.x-tool-right-over { + background-position:-15px -165px; +} + +.x-tool-left { + background-position:0 -180px; +} + +.x-tool-left-over { + background-position:-15px -180px; +} + +.x-tool-down { + background-position:0 -195px; +} + +.x-tool-down-over { + background-position:-15px -195px; +} + +.x-tool-up { + background-position:0 -210px; +} + +.x-tool-up-over { + background-position:-15px -210px; +} + +.x-tool-refresh { + background-position:0 -225px; +} + +.x-tool-refresh-over { + background-position:-15px -225px; +} + +.x-tool-plus { + background-position:0 -240px; +} + +.x-tool-plus-over { + background-position:-15px -240px; +} + +.x-tool-minus { + background-position:0 -255px; +} + +.x-tool-minus-over { + background-position:-15px -255px; +} + +.x-tool-search { + background-position:0 -270px; +} + +.x-tool-search-over { + background-position:-15px -270px; +} + +.x-tool-save { + background-position:0 -285px; +} + +.x-tool-save-over { + background-position:-15px -285px; +} + +.x-tool-help { + background-position:0 -300px; +} + +.x-tool-help-over { + background-position:-15px -300px; +} + +.x-tool-print { + background-position:0 -315px; +} + +.x-tool-print-over { + background-position:-15px -315px; +} + +.x-tool-expand { + background-position:0 -330px; +} + +.x-tool-expand-over { + background-position:-15px -330px; +} + +.x-tool-collapse { + background-position:0 -345px; +} + +.x-tool-collapse-over { + background-position:-15px -345px; +} + +.x-tool-resize { + background-position:0 -360px; +} + +.x-tool-resize-over { + background-position:-15px -360px; +} + +.x-tool-move { + background-position:0 -375px; +} + +.x-tool-move-over { + background-position:-15px -375px; +} + +/* Ghosting */ +.x-panel-ghost { + z-index:12000; + overflow:hidden; + position:absolute; + left:0;top:0; + opacity:.65; + -moz-opacity:.65; + filter:alpha(opacity=65); +} + +.x-panel-ghost ul { + margin:0; + padding:0; + overflow:hidden; + font-size:0; + line-height:0; + border:1px solid; + border-top:0 none; + display:block; +} + +.x-panel-ghost * { + cursor:move !important; +} + +.x-panel-dd-spacer { + border:2px dashed; +} + +/* Buttons */ +.x-panel-btns { + padding:5px; + overflow:hidden; +} + +.x-panel-btns td.x-toolbar-cell{ + padding:3px; +} + +.x-panel-btns .x-btn-focus .x-btn-left{ + background-position:0 -147px; +} + +.x-panel-btns .x-btn-focus .x-btn-right{ + background-position:0 -168px; +} + +.x-panel-btns .x-btn-focus .x-btn-center{ + background-position:0 -189px; +} + +.x-panel-btns .x-btn-over .x-btn-left{ + background-position:0 -63px; +} + +.x-panel-btns .x-btn-over .x-btn-right{ + background-position:0 -84px; +} + +.x-panel-btns .x-btn-over .x-btn-center{ + background-position:0 -105px; +} + +.x-panel-btns .x-btn-click .x-btn-center{ + background-position:0 -126px; +} + +.x-panel-btns .x-btn-click .x-btn-right{ + background-position:0 -84px; +} + +.x-panel-btns .x-btn-click .x-btn-left{ + background-position:0 -63px; +} + +.x-panel-fbar td,.x-panel-fbar span,.x-panel-fbar input,.x-panel-fbar div,.x-panel-fbar select,.x-panel-fbar label{ + white-space: nowrap; +} +/** + * W3C Suggested Default style sheet for HTML 4 + * http://www.w3.org/TR/CSS21/sample.html + * + * Resets for Ext.Panel @cfg normal: true + */ +.x-panel-reset .x-panel-body html, +.x-panel-reset .x-panel-body address, +.x-panel-reset .x-panel-body blockquote, +.x-panel-reset .x-panel-body body, +.x-panel-reset .x-panel-body dd, +.x-panel-reset .x-panel-body div, +.x-panel-reset .x-panel-body dl, +.x-panel-reset .x-panel-body dt, +.x-panel-reset .x-panel-body fieldset, +.x-panel-reset .x-panel-body form, +.x-panel-reset .x-panel-body frame, frameset, +.x-panel-reset .x-panel-body h1, +.x-panel-reset .x-panel-body h2, +.x-panel-reset .x-panel-body h3, +.x-panel-reset .x-panel-body h4, +.x-panel-reset .x-panel-body h5, +.x-panel-reset .x-panel-body h6, +.x-panel-reset .x-panel-body noframes, +.x-panel-reset .x-panel-body ol, +.x-panel-reset .x-panel-body p, +.x-panel-reset .x-panel-body ul, +.x-panel-reset .x-panel-body center, +.x-panel-reset .x-panel-body dir, +.x-panel-reset .x-panel-body hr, +.x-panel-reset .x-panel-body menu, +.x-panel-reset .x-panel-body pre { display: block } +.x-panel-reset .x-panel-body li { display: list-item } +.x-panel-reset .x-panel-body head { display: none } +.x-panel-reset .x-panel-body table { display: table } +.x-panel-reset .x-panel-body tr { display: table-row } +.x-panel-reset .x-panel-body thead { display: table-header-group } +.x-panel-reset .x-panel-body tbody { display: table-row-group } +.x-panel-reset .x-panel-body tfoot { display: table-footer-group } +.x-panel-reset .x-panel-body col { display: table-column } +.x-panel-reset .x-panel-body colgroup { display: table-column-group } +.x-panel-reset .x-panel-body td, +.x-panel-reset .x-panel-body th { display: table-cell } +.x-panel-reset .x-panel-body caption { display: table-caption } +.x-panel-reset .x-panel-body th { font-weight: bolder; text-align: center } +.x-panel-reset .x-panel-body caption { text-align: center } +.x-panel-reset .x-panel-body body { margin: 8px } +.x-panel-reset .x-panel-body h1 { font-size: 2em; margin: .67em 0 } +.x-panel-reset .x-panel-body h2 { font-size: 1.5em; margin: .75em 0 } +.x-panel-reset .x-panel-body h3 { font-size: 1.17em; margin: .83em 0 } +.x-panel-reset .x-panel-body h4, +.x-panel-reset .x-panel-body p, +.x-panel-reset .x-panel-body blockquote, +.x-panel-reset .x-panel-body ul, +.x-panel-reset .x-panel-body fieldset, +.x-panel-reset .x-panel-body form, +.x-panel-reset .x-panel-body ol, +.x-panel-reset .x-panel-body dl, +.x-panel-reset .x-panel-body dir, +.x-panel-reset .x-panel-body menu { margin: 1.12em 0 } +.x-panel-reset .x-panel-body h5 { font-size: .83em; margin: 1.5em 0 } +.x-panel-reset .x-panel-body h6 { font-size: .75em; margin: 1.67em 0 } +.x-panel-reset .x-panel-body h1, +.x-panel-reset .x-panel-body h2, +.x-panel-reset .x-panel-body h3, +.x-panel-reset .x-panel-body h4, +.x-panel-reset .x-panel-body h5, +.x-panel-reset .x-panel-body h6, +.x-panel-reset .x-panel-body b, +.x-panel-reset .x-panel-body strong { font-weight: bolder } +.x-panel-reset .x-panel-body blockquote { margin-left: 40px; margin-right: 40px } +.x-panel-reset .x-panel-body i, +.x-panel-reset .x-panel-body cite, +.x-panel-reset .x-panel-body em, +.x-panel-reset .x-panel-body var, +.x-panel-reset .x-panel-body address { font-style: italic } +.x-panel-reset .x-panel-body pre, +.x-panel-reset .x-panel-body tt, +.x-panel-reset .x-panel-body code, +.x-panel-reset .x-panel-body kbd, +.x-panel-reset .x-panel-body samp { font-family: monospace } +.x-panel-reset .x-panel-body pre { white-space: pre } +.x-panel-reset .x-panel-body button, +.x-panel-reset .x-panel-body textarea, +.x-panel-reset .x-panel-body input, +.x-panel-reset .x-panel-body select { display: inline-block } +.x-panel-reset .x-panel-body big { font-size: 1.17em } +.x-panel-reset .x-panel-body small, +.x-panel-reset .x-panel-body sub, +.x-panel-reset .x-panel-body sup { font-size: .83em } +.x-panel-reset .x-panel-body sub { vertical-align: sub } +.x-panel-reset .x-panel-body sup { vertical-align: super } +.x-panel-reset .x-panel-body table { border-spacing: 2px; } +.x-panel-reset .x-panel-body thead, +.x-panel-reset .x-panel-body tbody, +.x-panel-reset .x-panel-body tfoot { vertical-align: middle } +.x-panel-reset .x-panel-body td, +.x-panel-reset .x-panel-body th { vertical-align: inherit } +.x-panel-reset .x-panel-body s, +.x-panel-reset .x-panel-body strike, +.x-panel-reset .x-panel-body del { text-decoration: line-through } +.x-panel-reset .x-panel-body hr { border: 1px inset } +.x-panel-reset .x-panel-body ol, +.x-panel-reset .x-panel-body ul, +.x-panel-reset .x-panel-body dir, +.x-panel-reset .x-panel-body menu, +.x-panel-reset .x-panel-body dd { margin-left: 40px } +.x-panel-reset .x-panel-body ul, .x-panel-reset .x-panel-body menu, .x-panel-reset .x-panel-body dir { list-style-type: disc;} +.x-panel-reset .x-panel-body ol { list-style-type: decimal } +.x-panel-reset .x-panel-body ol ul, +.x-panel-reset .x-panel-body ul ol, +.x-panel-reset .x-panel-body ul ul, +.x-panel-reset .x-panel-body ol ol { margin-top: 0; margin-bottom: 0 } +.x-panel-reset .x-panel-body u, +.x-panel-reset .x-panel-body ins { text-decoration: underline } +.x-panel-reset .x-panel-body br:before { content: "\A" } +.x-panel-reset .x-panel-body :before, .x-panel-reset .x-panel-body :after { white-space: pre-line } +.x-panel-reset .x-panel-body center { text-align: center } +.x-panel-reset .x-panel-body :link, .x-panel-reset .x-panel-body :visited { text-decoration: underline } +.x-panel-reset .x-panel-body :focus { outline: invert dotted thin } + +/* Begin bidirectionality settings (do not change) */ +.x-panel-reset .x-panel-body BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override } +.x-panel-reset .x-panel-body BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override } +.x-window { + zoom:1; +} + +.x-window .x-window-handle { + opacity:0; + -moz-opacity:0; + filter:alpha(opacity=0); +} + +.x-window-proxy { + border:1px solid; + z-index:12000; + overflow:hidden; + position:absolute; + left:0;top:0; + display:none; + opacity:.5; + -moz-opacity:.5; + filter:alpha(opacity=50); +} + +.x-window-header { + overflow:hidden; + zoom:1; +} + +.x-window-bwrap { + z-index:1; + position:relative; + zoom:1; + left:0;top:0; +} + +.x-window-tl .x-window-header { + padding:5px 0 4px 0; +} + +.x-window-header-text { + cursor:pointer; +} + +.x-window-tc { + background: transparent repeat-x 0 0; + overflow:hidden; + zoom:1; +} + +.x-window-tl { + background: transparent no-repeat 0 0; + padding-left:6px; + zoom:1; + z-index:1; + position:relative; +} + +.x-window-tr { + background: transparent no-repeat right 0; + padding-right:6px; +} + +.x-window-bc { + background: transparent repeat-x 0 bottom; + zoom:1; +} + +.x-window-bc .x-window-footer { + padding-bottom:6px; + zoom:1; + font-size:0; + line-height:0; +} + +.x-window-bl { + background: transparent no-repeat 0 bottom; + padding-left:6px; + zoom:1; +} + +.x-window-br { + background: transparent no-repeat right bottom; + padding-right:6px; + zoom:1; +} + +.x-window-mc { + border:1px solid; + padding:0; + margin:0; +} + +.x-window-ml { + background: transparent repeat-y 0 0; + padding-left:6px; + zoom:1; +} + +.x-window-mr { + background: transparent repeat-y right 0; + padding-right:6px; + zoom:1; +} + +.x-window-body { + overflow:hidden; +} + +.x-window-bwrap { + overflow:hidden; +} + +.x-window-maximized .x-window-bl, .x-window-maximized .x-window-br, + .x-window-maximized .x-window-ml, .x-window-maximized .x-window-mr, + .x-window-maximized .x-window-tl, .x-window-maximized .x-window-tr { + padding:0; +} + +.x-window-maximized .x-window-footer { + padding-bottom:0; +} + +.x-window-maximized .x-window-tc { + padding-left:3px; + padding-right:3px; +} + +.x-window-maximized .x-window-mc { + border-left:0 none; + border-right:0 none; +} + +.x-window-tbar .x-toolbar, .x-window-bbar .x-toolbar { + border-left:0 none; + border-right: 0 none; +} + +.x-window-bbar .x-toolbar { + border-top:1px solid; + border-bottom:0 none; +} + +.x-window-draggable, .x-window-draggable .x-window-header-text { + cursor:move; +} + +.x-window-maximized .x-window-draggable, .x-window-maximized .x-window-draggable .x-window-header-text { + cursor:default; +} + +.x-window-body { + background-color:transparent; +} + +.x-panel-ghost .x-window-tl { + border-bottom:1px solid; +} + +.x-panel-collapsed .x-window-tl { + border-bottom:1px solid; +} + +.x-window-maximized-ct { + overflow:hidden; +} + +.x-window-maximized .x-window-handle { + display:none; +} + +.x-window-sizing-ghost ul { + border:0 none !important; +} + +.x-dlg-focus{ + -moz-outline:0 none; + outline:0 none; + width:0; + height:0; + overflow:hidden; + position:absolute; + top:0; + left:0; +} + +.ext-webkit .x-dlg-focus{ + width: 1px; + height: 1px; +} + +.x-dlg-mask{ + z-index:10000; + display:none; + position:absolute; + top:0; + left:0; + -moz-opacity: 0.5; + opacity:.50; + filter: alpha(opacity=50); +} + +body.ext-ie6.x-body-masked select { + visibility:hidden; +} + +body.ext-ie6.x-body-masked .x-window select { + visibility:visible; +} + +.x-window-plain .x-window-mc { + border: 1px solid; +} + +.x-window-plain .x-window-body { + border: 1px solid; + background:transparent !important; +}.x-html-editor-wrap { + border:1px solid; +} + +.x-html-editor-tb .x-btn-text { + background:transparent no-repeat; +} + +.x-html-editor-tb .x-edit-bold, .x-menu-item img.x-edit-bold { + background-position:0 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-italic, .x-menu-item img.x-edit-italic { + background-position:-16px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-underline, .x-menu-item img.x-edit-underline { + background-position:-32px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-forecolor, .x-menu-item img.x-edit-forecolor { + background-position:-160px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-backcolor, .x-menu-item img.x-edit-backcolor { + background-position:-176px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-justifyleft, .x-menu-item img.x-edit-justifyleft { + background-position:-112px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-justifycenter, .x-menu-item img.x-edit-justifycenter { + background-position:-128px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-justifyright, .x-menu-item img.x-edit-justifyright { + background-position:-144px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-insertorderedlist, .x-menu-item img.x-edit-insertorderedlist { + background-position:-80px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-insertunorderedlist, .x-menu-item img.x-edit-insertunorderedlist { + background-position:-96px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-increasefontsize, .x-menu-item img.x-edit-increasefontsize { + background-position:-48px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-decreasefontsize, .x-menu-item img.x-edit-decreasefontsize { + background-position:-64px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-sourceedit, .x-menu-item img.x-edit-sourceedit { + background-position:-192px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tb .x-edit-createlink, .x-menu-item img.x-edit-createlink { + background-position:-208px 0; + background-image:url(../images/default/editor/tb-sprite.gif); +} + +.x-html-editor-tip .x-tip-bd .x-tip-bd-inner { + padding:5px; + padding-bottom:1px; +} + +.x-html-editor-tb .x-toolbar { + position:static !important; +}.x-panel-noborder .x-panel-body-noborder { + border-width:0; +} + +.x-panel-noborder .x-panel-header-noborder { + border-width:0 0 1px; + border-style:solid; +} + +.x-panel-noborder .x-panel-tbar-noborder .x-toolbar { + border-width:0 0 1px; + border-style:solid; +} + +.x-panel-noborder .x-panel-bbar-noborder .x-toolbar { + border-width:1px 0 0 0; + border-style:solid; +} + +.x-window-noborder .x-window-mc { + border-width:0; +} + +.x-window-plain .x-window-body-noborder { + border-width:0; +} + +.x-tab-panel-noborder .x-tab-panel-body-noborder { + border-width:0; +} + +.x-tab-panel-noborder .x-tab-panel-header-noborder { + border-width: 0 0 1px 0; +} + +.x-tab-panel-noborder .x-tab-panel-footer-noborder { + border-width: 1px 0 0 0; +} + +.x-tab-panel-bbar-noborder .x-toolbar { + border-width: 1px 0 0 0; + border-style:solid; +} + +.x-tab-panel-tbar-noborder .x-toolbar { + border-width:0 0 1px; + border-style:solid; +}.x-border-layout-ct { + position: relative; +} + +.x-border-panel { + position:absolute; + left:0; + top:0; +} + +.x-tool-collapse-south { + background-position:0 -195px; +} + +.x-tool-collapse-south-over { + background-position:-15px -195px; +} + +.x-tool-collapse-north { + background-position:0 -210px; +} + +.x-tool-collapse-north-over { + background-position:-15px -210px; +} + +.x-tool-collapse-west { + background-position:0 -180px; +} + +.x-tool-collapse-west-over { + background-position:-15px -180px; +} + +.x-tool-collapse-east { + background-position:0 -165px; +} + +.x-tool-collapse-east-over { + background-position:-15px -165px; +} + +.x-tool-expand-south { + background-position:0 -210px; +} + +.x-tool-expand-south-over { + background-position:-15px -210px; +} + +.x-tool-expand-north { + background-position:0 -195px; +} +.x-tool-expand-north-over { + background-position:-15px -195px; +} + +.x-tool-expand-west { + background-position:0 -165px; +} + +.x-tool-expand-west-over { + background-position:-15px -165px; +} + +.x-tool-expand-east { + background-position:0 -180px; +} + +.x-tool-expand-east-over { + background-position:-15px -180px; +} + +.x-tool-expand-north, .x-tool-expand-south { + float:right; + margin:3px; +} + +.x-tool-expand-east, .x-tool-expand-west { + float:none; + margin:3px 2px; +} + +.x-accordion-hd .x-tool-toggle { + background-position:0 -255px; +} + +.x-accordion-hd .x-tool-toggle-over { + background-position:-15px -255px; +} + +.x-panel-collapsed .x-accordion-hd .x-tool-toggle { + background-position:0 -240px; +} + +.x-panel-collapsed .x-accordion-hd .x-tool-toggle-over { + background-position:-15px -240px; +} + +.x-accordion-hd { + padding-top:4px; + padding-bottom:3px; + border-top:0 none; + background: transparent repeat-x 0 -9px; +} + +.x-layout-collapsed{ + position:absolute; + left:-10000px; + top:-10000px; + visibility:hidden; + width:20px; + height:20px; + overflow:hidden; + border:1px solid; + z-index:20; +} + +.ext-border-box .x-layout-collapsed{ + width:22px; + height:22px; +} + +.x-layout-collapsed-over{ + cursor:pointer; +} + +.x-layout-collapsed-west .x-layout-collapsed-tools, .x-layout-collapsed-east .x-layout-collapsed-tools{ + position:absolute; + top:0; + left:0; + width:20px; + height:20px; +} + + +.x-layout-split{ + position:absolute; + height:5px; + width:5px; + line-height:1px; + font-size:1px; + z-index:3; + background-color:transparent; +} + +/* IE6 strict won't drag w/out a color */ +.ext-strict .ext-ie6 .x-layout-split{ + background-color: #fff !important; + filter: alpha(opacity=1); +} + +.x-layout-split-h{ + background-image:url(../images/default/s.gif); + background-position: left; +} + +.x-layout-split-v{ + background-image:url(../images/default/s.gif); + background-position: top; +} + +.x-column-layout-ct { + overflow:hidden; + zoom:1; +} + +.x-column { + float:left; + padding:0; + margin:0; + overflow:hidden; + zoom:1; +} + +.x-column-inner { + overflow:hidden; + zoom:1; +} + +/* mini mode */ +.x-layout-mini { + position:absolute; + top:0; + left:0; + display:block; + width:5px; + height:35px; + cursor:pointer; + opacity:.5; + -moz-opacity:.5; + filter:alpha(opacity=50); +} + +.x-layout-mini-over, .x-layout-collapsed-over .x-layout-mini{ + opacity:1; + -moz-opacity:1; + filter:none; +} + +.x-layout-split-west .x-layout-mini { + top:48%; +} + +.x-layout-split-east .x-layout-mini { + top:48%; +} + +.x-layout-split-north .x-layout-mini { + left:48%; + height:5px; + width:35px; +} + +.x-layout-split-south .x-layout-mini { + left:48%; + height:5px; + width:35px; +} + +.x-layout-cmini-west .x-layout-mini { + top:48%; +} + +.x-layout-cmini-east .x-layout-mini { + top:48%; +} + +.x-layout-cmini-north .x-layout-mini { + left:48%; + height:5px; + width:35px; +} + +.x-layout-cmini-south .x-layout-mini { + left:48%; + height:5px; + width:35px; +} + +.x-layout-cmini-west, .x-layout-cmini-east { + border:0 none; + width:5px !important; + padding:0; + background-color:transparent; +} + +.x-layout-cmini-north, .x-layout-cmini-south { + border:0 none; + height:5px !important; + padding:0; + background-color:transparent; +} + +.x-viewport, .x-viewport body { + margin: 0; + padding: 0; + border: 0 none; + overflow: hidden; + height: 100%; +} + +.x-abs-layout-item { + position:absolute; + left:0; + top:0; +} + +.ext-ie input.x-abs-layout-item, .ext-ie textarea.x-abs-layout-item { + margin:0; +} + +.x-box-layout-ct { + overflow:hidden; + zoom:1; +} + +.x-box-inner { + overflow:hidden; + zoom:1; + position:relative; + left:0; + top:0; +} + +.x-box-item { + position:absolute; + left:0; + top:0; +}.x-progress-wrap { + border:1px solid; + overflow:hidden; +} + +.x-progress-inner { + height:18px; + background:repeat-x; + position:relative; +} + +.x-progress-bar { + height:18px; + float:left; + width:0; + background: repeat-x left center; + border-top:1px solid; + border-bottom:1px solid; + border-right:1px solid; +} + +.x-progress-text { + padding:1px 5px; + overflow:hidden; + position:absolute; + left:0; + text-align:center; +} + +.x-progress-text-back { + line-height:16px; +} + +.ext-ie .x-progress-text-back { + line-height:15px; +} + +.ext-strict .ext-ie7 .x-progress-text-back{ + width: 100%; +} +.x-list-header{ + background: repeat-x 0 bottom; + cursor:default; + zoom:1; + height:22px; +} + +.x-list-header-inner div { + display:block; + float:left; + overflow:hidden; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; +} + +.x-list-header-inner div em { + display:block; + border-left:1px solid; + padding:4px 4px; + overflow:hidden; + -moz-user-select: none; + -khtml-user-select: none; + line-height:14px; +} + +.x-list-body { + overflow:auto; + overflow-x:hidden; + overflow-y:auto; + zoom:1; + float: left; + width: 100%; +} + +.x-list-body dl { + zoom:1; +} + +.x-list-body dt { + display:block; + float:left; + overflow:hidden; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; + cursor:pointer; + zoom:1; +} + +.x-list-body dt em { + display:block; + padding:3px 4px; + overflow:hidden; + -moz-user-select: none; + -khtml-user-select: none; +} + +.x-list-resizer { + border-left:1px solid; + border-right:1px solid; + position:absolute; + left:0; + top:0; +} + +.x-list-header-inner em.sort-asc { + background: transparent no-repeat center 0; + border-style:solid; + border-width: 0 1px 1px; + padding-bottom:3px; +} + +.x-list-header-inner em.sort-desc { + background: transparent no-repeat center -23px; + border-style:solid; + border-width: 0 1px 1px; + padding-bottom:3px; +} + +/* Shared styles */ +.x-slider { + zoom:1; +} + +.x-slider-inner { + position:relative; + left:0; + top:0; + overflow:visible; + zoom:1; +} + +.x-slider-focus { + position:absolute; + left:0; + top:0; + width:1px; + height:1px; + line-height:1px; + font-size:1px; + -moz-outline:0 none; + outline:0 none; + -moz-user-select: none; + -khtml-user-select:none; + -webkit-user-select:ignore; + display:block; + overflow:hidden; +} + +/* Horizontal styles */ +.x-slider-horz { + padding-left:7px; + background:transparent no-repeat 0 -22px; +} + +.x-slider-horz .x-slider-end { + padding-right:7px; + zoom:1; + background:transparent no-repeat right -44px; +} + +.x-slider-horz .x-slider-inner { + background:transparent repeat-x 0 0; + height:22px; +} + +.x-slider-horz .x-slider-thumb { + width:14px; + height:15px; + position:absolute; + left:0; + top:3px; + background:transparent no-repeat 0 0; +} + +.x-slider-horz .x-slider-thumb-over { + background-position: -14px -15px; +} + +.x-slider-horz .x-slider-thumb-drag { + background-position: -28px -30px; +} + +/* Vertical styles */ +.x-slider-vert { + padding-top:7px; + background:transparent no-repeat -44px 0; + width:22px; +} + +.x-slider-vert .x-slider-end { + padding-bottom:7px; + zoom:1; + background:transparent no-repeat -22px bottom; +} + +.x-slider-vert .x-slider-inner { + background:transparent repeat-y 0 0; +} + +.x-slider-vert .x-slider-thumb { + width:15px; + height:14px; + position:absolute; + left:3px; + bottom:0; + background:transparent no-repeat 0 0; +} + +.x-slider-vert .x-slider-thumb-over { + background-position: -15px -14px; +} + +.x-slider-vert .x-slider-thumb-drag { + background-position: -30px -28px; +}.x-window-dlg .x-window-body { + border:0 none !important; + padding:5px 10px; + overflow:hidden !important; +} + +.x-window-dlg .x-window-mc { + border:0 none !important; +} + +.x-window-dlg .ext-mb-input { + margin-top:4px; + width:95%; +} + +.x-window-dlg .ext-mb-textarea { + margin-top:4px; +} + +.x-window-dlg .x-progress-wrap { + margin-top:4px; +} + +.ext-ie .x-window-dlg .x-progress-wrap { + margin-top:6px; +} + +.x-window-dlg .x-msg-box-wait { + background:transparent no-repeat left; + display:block; + width:300px; + padding-left:18px; + line-height:18px; +} + +.x-window-dlg .ext-mb-icon { + float:left; + width:47px; + height:32px; +} + +.x-window-dlg .x-dlg-icon .ext-mb-content{ + zoom: 1; + margin-left: 47px; +} + +.x-window-dlg .ext-mb-info, .x-window-dlg .ext-mb-warning, .x-window-dlg .ext-mb-question, .x-window-dlg .ext-mb-error { + background:transparent no-repeat top left; +} + +.ext-gecko2 .ext-mb-fix-cursor { + overflow:auto; +}.ext-el-mask { + background-color: #ccc; +} + +.ext-el-mask-msg { + border-color:#6593cf; + background-color:#c3daf9; + background-image:url(../images/default/box/tb-blue.gif); +} +.ext-el-mask-msg div { + background-color: #eee; + border-color:#a3bad9; + color:#222; + font:normal 11px tahoma, arial, helvetica, sans-serif; +} + +.x-mask-loading div { + background-color:#fbfbfb; + background-image:url(../images/default/grid/loading.gif); +} + +.x-item-disabled { + color: gray; +} + +.x-item-disabled * { + color: gray !important; +} + +.x-splitbar-proxy { + background-color: #aaa; +} + +.x-color-palette a { + border-color:#fff; +} + +.x-color-palette a:hover, .x-color-palette a.x-color-palette-sel { + border-color:#8bb8f3; + background-color: #deecfd; +} + +/* +.x-color-palette em:hover, .x-color-palette span:hover{ + background-color: #deecfd; +} +*/ + +.x-color-palette em { + border-color:#aca899; +} + +.x-ie-shadow { + background-color:#777; +} + +.x-shadow .xsmc { + background-image: url(../images/default/shadow-c.png); +} + +.x-shadow .xsml, .x-shadow .xsmr { + background-image: url(../images/default/shadow-lr.png); +} + +.x-shadow .xstl, .x-shadow .xstc, .x-shadow .xstr, .x-shadow .xsbl, .x-shadow .xsbc, .x-shadow .xsbr{ + background-image: url(../images/default/shadow.png); +} + +.loading-indicator { + font-size: 11px; + background-image: url(../images/default/grid/loading.gif); +} + +.x-spotlight { + background-color: #ccc; +} +.x-tab-panel-header, .x-tab-panel-footer { + background-color: #deecfd; + border-color:#8db2e3; + overflow:hidden; + zoom:1; +} + +.x-tab-panel-header, .x-tab-panel-footer { + border-color:#8db2e3; +} + +ul.x-tab-strip-top{ + background-color:#cedff5; + background-image: url(../images/default/tabs/tab-strip-bg.gif); + border-bottom-color:#8db2e3; +} + +ul.x-tab-strip-bottom{ + background-color:#cedff5; + background-image: url(../images/default/tabs/tab-strip-btm-bg.gif); + border-top-color:#8db2e3; +} + +.x-tab-panel-header-plain .x-tab-strip-spacer, +.x-tab-panel-footer-plain .x-tab-strip-spacer { + border-color:#8db2e3; + background-color: #deecfd; +} + +.x-tab-strip span.x-tab-strip-text { + font:normal 11px tahoma,arial,helvetica; + color:#416aa3; +} + +.x-tab-strip-over span.x-tab-strip-text { + color:#15428b; +} + +.x-tab-strip-active span.x-tab-strip-text { + color:#15428b; + font-weight:bold; +} + +.x-tab-strip-disabled .x-tabs-text { + color:#aaaaaa; +} + +.x-tab-strip-top .x-tab-right, .x-tab-strip-top .x-tab-left, .x-tab-strip-top .x-tab-strip-inner{ + background-image: url(../images/default/tabs/tabs-sprite.gif); +} + +.x-tab-strip-bottom .x-tab-right { + background-image: url(../images/default/tabs/tab-btm-inactive-right-bg.gif); +} + +.x-tab-strip-bottom .x-tab-left { + background-image: url(../images/default/tabs/tab-btm-inactive-left-bg.gif); +} + +.x-tab-strip-bottom .x-tab-strip-over .x-tab-right { + background-image: url(../images/default/tabs/tab-btm-over-right-bg.gif); +} + +.x-tab-strip-bottom .x-tab-strip-over .x-tab-left { + background-image: url(../images/default/tabs/tab-btm-over-left-bg.gif); +} + +.x-tab-strip-bottom .x-tab-strip-active .x-tab-right { + background-image: url(../images/default/tabs/tab-btm-right-bg.gif); +} + +.x-tab-strip-bottom .x-tab-strip-active .x-tab-left { + background-image: url(../images/default/tabs/tab-btm-left-bg.gif); +} + +.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close { + background-image:url(../images/default/tabs/tab-close.gif); +} + +.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close:hover{ + background-image:url(../images/default/tabs/tab-close.gif); +} + +.x-tab-panel-body { + border-color:#8db2e3; + background-color:#fff; +} + +.x-tab-panel-body-top { + border-top: 0 none; +} + +.x-tab-panel-body-bottom { + border-bottom: 0 none; +} + +.x-tab-scroller-left { + background-image:url(../images/default/tabs/scroll-left.gif); + border-bottom-color:#8db2e3; +} + +.x-tab-scroller-left-over { + background-position: 0 0; +} + +.x-tab-scroller-left-disabled { + background-position: -18px 0; + opacity:.5; + -moz-opacity:.5; + filter:alpha(opacity=50); + cursor:default; +} + +.x-tab-scroller-right { + background-image:url(../images/default/tabs/scroll-right.gif); + border-bottom-color:#8db2e3; +} + +.x-tab-panel-bbar .x-toolbar, .x-tab-panel-tbar .x-toolbar { + border-color:#99bbe8; +}.x-form-field { + font:normal 12px tahoma, arial, helvetica, sans-serif; +} + +.x-form-text, textarea.x-form-field { + background-color:#fff; + background-image:url(../images/default/form/text-bg.gif); + border-color:#b5b8c8; +} + +.x-form-select-one { + background-color:#fff; + border-color:#b5b8c8; +} + +.x-form-check-group-label { + border-bottom: 1px solid #99bbe8; + color: #15428b; +} + +.x-editor .x-form-check-wrap { + background-color:#fff; +} + +.x-form-field-wrap .x-form-trigger { + background-image:url(../images/default/form/trigger.gif); + border-bottom-color:#b5b8c8; +} + +.x-form-field-wrap .x-form-date-trigger { + background-image: url(../images/default/form/date-trigger.gif); +} + +.x-form-field-wrap .x-form-clear-trigger { + background-image: url(../images/default/form/clear-trigger.gif); +} + +.x-form-field-wrap .x-form-search-trigger { + background-image: url(../images/default/form/search-trigger.gif); +} + +.x-trigger-wrap-focus .x-form-trigger { + border-bottom-color:#7eadd9; +} + +.x-item-disabled .x-form-trigger-over { + border-bottom-color:#b5b8c8; +} + +.x-item-disabled .x-form-trigger-click { + border-bottom-color:#b5b8c8; +} + +.x-form-focus, textarea.x-form-focus { + border-color:#7eadd9; +} + +.x-form-invalid, textarea.x-form-invalid { + background-color:#fff; + background-image:url(../images/default/grid/invalid_line.gif); + border-color:#c30; +} + +.x-form-invalid.x-form-composite { + border: none; + background-image: none; +} + +.x-form-invalid.x-form-composite .x-form-invalid { + background-color:#fff; + background-image:url(../images/default/grid/invalid_line.gif); + border-color:#c30; +} + +.x-form-inner-invalid, textarea.x-form-inner-invalid { + background-color:#fff; + background-image:url(../images/default/grid/invalid_line.gif); +} + +.x-form-grow-sizer { + font:normal 12px tahoma, arial, helvetica, sans-serif; +} + +.x-form-item { + font:normal 12px tahoma, arial, helvetica, sans-serif; +} + +.x-form-invalid-msg { + color:#c0272b; + font:normal 11px tahoma, arial, helvetica, sans-serif; + background-image:url(../images/default/shared/warning.gif); +} + +.x-form-empty-field { + color:gray; +} + +.x-small-editor .x-form-field { + font:normal 11px arial, tahoma, helvetica, sans-serif; +} + +.ext-webkit .x-small-editor .x-form-field { + font:normal 11px arial, tahoma, helvetica, sans-serif; +} + +.x-form-invalid-icon { + background-image:url(../images/default/form/exclamation.gif); +} + +.x-fieldset { + border-color:#b5b8c8; +} + +.x-fieldset legend { + font:bold 11px tahoma, arial, helvetica, sans-serif; + color:#15428b; +} +.x-btn{ + font:normal 11px tahoma, verdana, helvetica; +} + +.x-btn button{ + font:normal 11px arial,tahoma,verdana,helvetica; + color:#333; +} + +.x-btn em { + font-style:normal; + font-weight:normal; +} + +.x-btn-tl, .x-btn-tr, .x-btn-tc, .x-btn-ml, .x-btn-mr, .x-btn-mc, .x-btn-bl, .x-btn-br, .x-btn-bc{ + background-image:url(../images/default/button/btn.gif); +} + +.x-btn-click .x-btn-text, .x-btn-menu-active .x-btn-text, .x-btn-pressed .x-btn-text{ + color:#000; +} + +.x-btn-disabled *{ + color:gray !important; +} + +.x-btn-mc em.x-btn-arrow { + background-image:url(../images/default/button/arrow.gif); +} + +.x-btn-mc em.x-btn-split { + background-image:url(../images/default/button/s-arrow.gif); +} + +.x-btn-over .x-btn-mc em.x-btn-split, .x-btn-click .x-btn-mc em.x-btn-split, .x-btn-menu-active .x-btn-mc em.x-btn-split, .x-btn-pressed .x-btn-mc em.x-btn-split { + background-image:url(../images/default/button/s-arrow-o.gif); +} + +.x-btn-mc em.x-btn-arrow-bottom { + background-image:url(../images/default/button/s-arrow-b-noline.gif); +} + +.x-btn-mc em.x-btn-split-bottom { + background-image:url(../images/default/button/s-arrow-b.gif); +} + +.x-btn-over .x-btn-mc em.x-btn-split-bottom, .x-btn-click .x-btn-mc em.x-btn-split-bottom, .x-btn-menu-active .x-btn-mc em.x-btn-split-bottom, .x-btn-pressed .x-btn-mc em.x-btn-split-bottom { + background-image:url(../images/default/button/s-arrow-bo.gif); +} + +.x-btn-group-header { + color: #3e6aaa; +} + +.x-btn-group-tc { + background-image: url(../images/default/button/group-tb.gif); +} + +.x-btn-group-tl { + background-image: url(../images/default/button/group-cs.gif); +} + +.x-btn-group-tr { + background-image: url(../images/default/button/group-cs.gif); +} + +.x-btn-group-bc { + background-image: url(../images/default/button/group-tb.gif); +} + +.x-btn-group-bl { + background-image: url(../images/default/button/group-cs.gif); +} + +.x-btn-group-br { + background-image: url(../images/default/button/group-cs.gif); +} + +.x-btn-group-ml { + background-image: url(../images/default/button/group-lr.gif); +} +.x-btn-group-mr { + background-image: url(../images/default/button/group-lr.gif); +} + +.x-btn-group-notitle .x-btn-group-tc { + background-image: url(../images/default/button/group-tb.gif); +}.x-toolbar{ + border-color:#a9bfd3; + background-color:#d0def0; + background-image:url(../images/default/toolbar/bg.gif); +} + +.x-toolbar td,.x-toolbar span,.x-toolbar input,.x-toolbar div,.x-toolbar select,.x-toolbar label{ + font:normal 11px arial,tahoma, helvetica, sans-serif; +} + +.x-toolbar .x-item-disabled { + color:gray; +} + +.x-toolbar .x-item-disabled * { + color:gray; +} + +.x-toolbar .x-btn-mc em.x-btn-split { + background-image:url(../images/default/button/s-arrow-noline.gif); +} + +.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split, .x-toolbar .x-btn-click .x-btn-mc em.x-btn-split, +.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split, .x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split +{ + background-image:url(../images/default/button/s-arrow-o.gif); +} + +.x-toolbar .x-btn-mc em.x-btn-split-bottom { + background-image:url(../images/default/button/s-arrow-b-noline.gif); +} + +.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split-bottom, .x-toolbar .x-btn-click .x-btn-mc em.x-btn-split-bottom, +.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split-bottom, .x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split-bottom +{ + background-image:url(../images/default/button/s-arrow-bo.gif); +} + +.x-toolbar .xtb-sep { + background-image: url(../images/default/grid/grid-blue-split.gif); +} + +.x-tbar-page-first{ + background-image: url(../images/default/grid/page-first.gif) !important; +} + +.x-tbar-loading{ + background-image: url(../images/default/grid/refresh.gif) !important; +} + +.x-tbar-page-last{ + background-image: url(../images/default/grid/page-last.gif) !important; +} + +.x-tbar-page-next{ + background-image: url(../images/default/grid/page-next.gif) !important; +} + +.x-tbar-page-prev{ + background-image: url(../images/default/grid/page-prev.gif) !important; +} + +.x-item-disabled .x-tbar-loading{ + background-image: url(../images/default/grid/refresh-disabled.gif) !important; +} + +.x-item-disabled .x-tbar-page-first{ + background-image: url(../images/default/grid/page-first-disabled.gif) !important; +} + +.x-item-disabled .x-tbar-page-last{ + background-image: url(../images/default/grid/page-last-disabled.gif) !important; +} + +.x-item-disabled .x-tbar-page-next{ + background-image: url(../images/default/grid/page-next-disabled.gif) !important; +} + +.x-item-disabled .x-tbar-page-prev{ + background-image: url(../images/default/grid/page-prev-disabled.gif) !important; +} + +.x-paging-info { + color:#444; +} + +.x-toolbar-more-icon { + background-image: url(../images/default/toolbar/more.gif) !important; +}.x-resizable-handle { + background-color:#fff; +} + +.x-resizable-over .x-resizable-handle-east, .x-resizable-pinned .x-resizable-handle-east, +.x-resizable-over .x-resizable-handle-west, .x-resizable-pinned .x-resizable-handle-west +{ + background-image:url(../images/default/sizer/e-handle.gif); +} + +.x-resizable-over .x-resizable-handle-south, .x-resizable-pinned .x-resizable-handle-south, +.x-resizable-over .x-resizable-handle-north, .x-resizable-pinned .x-resizable-handle-north +{ + background-image:url(../images/default/sizer/s-handle.gif); +} + +.x-resizable-over .x-resizable-handle-north, .x-resizable-pinned .x-resizable-handle-north{ + background-image:url(../images/default/sizer/s-handle.gif); +} +.x-resizable-over .x-resizable-handle-southeast, .x-resizable-pinned .x-resizable-handle-southeast{ + background-image:url(../images/default/sizer/se-handle.gif); +} +.x-resizable-over .x-resizable-handle-northwest, .x-resizable-pinned .x-resizable-handle-northwest{ + background-image:url(../images/default/sizer/nw-handle.gif); +} +.x-resizable-over .x-resizable-handle-northeast, .x-resizable-pinned .x-resizable-handle-northeast{ + background-image:url(../images/default/sizer/ne-handle.gif); +} +.x-resizable-over .x-resizable-handle-southwest, .x-resizable-pinned .x-resizable-handle-southwest{ + background-image:url(../images/default/sizer/sw-handle.gif); +} +.x-resizable-proxy{ + border-color:#3b5a82; +} +.x-resizable-overlay{ + background-color:#fff; +} +.x-grid3 { + background-color:#fff; +} + +.x-grid-panel .x-panel-mc .x-panel-body { + border-color:#99bbe8; +} + +.x-grid3-row td, .x-grid3-summary-row td{ + font:normal 11px/13px arial, tahoma, helvetica, sans-serif; +} + +.x-grid3-hd-row td { + font:normal 11px/15px arial, tahoma, helvetica, sans-serif; +} + + +.x-grid3-hd-row td { + border-left-color:#eee; + border-right-color:#d0d0d0; +} + +.x-grid-row-loading { + background-color: #fff; + background-image:url(../images/default/shared/loading-balls.gif); +} + +.x-grid3-row { + border-color:#ededed; + border-top-color:#fff; +} + +.x-grid3-row-alt{ + background-color:#fafafa; +} + +.x-grid3-row-over { + border-color:#ddd; + background-color:#efefef; + background-image:url(../images/default/grid/row-over.gif); +} + +.x-grid3-resize-proxy { + background-color:#777; +} + +.x-grid3-resize-marker { + background-color:#777; +} + +.x-grid3-header{ + background-color:#f9f9f9; + background-image:url(../images/default/grid/grid3-hrow.gif); +} + +.x-grid3-header-pop { + border-left-color:#d0d0d0; +} + +.x-grid3-header-pop-inner { + border-left-color:#eee; + background-image:url(../images/default/grid/hd-pop.gif); +} + +td.x-grid3-hd-over, td.sort-desc, td.sort-asc, td.x-grid3-hd-menu-open { + border-left-color:#aaccf6; + border-right-color:#aaccf6; +} + +td.x-grid3-hd-over .x-grid3-hd-inner, td.sort-desc .x-grid3-hd-inner, td.sort-asc .x-grid3-hd-inner, td.x-grid3-hd-menu-open .x-grid3-hd-inner { + background-color:#ebf3fd; + background-image:url(../images/default/grid/grid3-hrow-over.gif); + +} + +.sort-asc .x-grid3-sort-icon { + background-image: url(../images/default/grid/sort_asc.gif); +} + +.sort-desc .x-grid3-sort-icon { + background-image: url(../images/default/grid/sort_desc.gif); +} + +.x-grid3-cell-text, .x-grid3-hd-text { + color:#000; +} + +.x-grid3-split { + background-image: url(../images/default/grid/grid-split.gif); +} + +.x-grid3-hd-text { + color:#15428b; +} + +.x-dd-drag-proxy .x-grid3-hd-inner{ + background-color:#ebf3fd; + background-image:url(../images/default/grid/grid3-hrow-over.gif); + border-color:#aaccf6; +} + +.col-move-top{ + background-image:url(../images/default/grid/col-move-top.gif); +} + +.col-move-bottom{ + background-image:url(../images/default/grid/col-move-bottom.gif); +} + +td.grid-hd-group-cell { + background: url(../images/default/grid/grid3-hrow.gif) repeat-x bottom; +} + +.x-grid3-row-selected { + background-color: #dfe8f6 !important; + background-image: none; + border-color:#a3bae9; +} + +.x-grid3-cell-selected{ + background-color: #b8cfee !important; + color:#000; +} + +.x-grid3-cell-selected span{ + color:#000 !important; +} + +.x-grid3-cell-selected .x-grid3-cell-text{ + color:#000; +} + +.x-grid3-locked td.x-grid3-row-marker, .x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker{ + background-color:#ebeadb !important; + background-image:url(../images/default/grid/grid-hrow.gif) !important; + color:#000; + border-top-color:#fff; + border-right-color:#6fa0df !important; +} + +.x-grid3-locked td.x-grid3-row-marker div, .x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker div{ + color:#15428b !important; +} + +.x-grid3-dirty-cell { + background-image:url(../images/default/grid/dirty.gif); +} + +.x-grid3-topbar, .x-grid3-bottombar{ + font:normal 11px arial, tahoma, helvetica, sans-serif; +} + +.x-grid3-bottombar .x-toolbar{ + border-top-color:#a9bfd3; +} + +.x-props-grid .x-grid3-td-name .x-grid3-cell-inner{ + background-image:url(../images/default/grid/grid3-special-col-bg.gif) !important; + color:#000 !important; +} + +.x-props-grid .x-grid3-body .x-grid3-td-name{ + background-color:#fff !important; + border-right-color:#eee; +} + +.xg-hmenu-sort-asc .x-menu-item-icon{ + background-image: url(../images/default/grid/hmenu-asc.gif); +} + +.xg-hmenu-sort-desc .x-menu-item-icon{ + background-image: url(../images/default/grid/hmenu-desc.gif); +} + +.xg-hmenu-lock .x-menu-item-icon{ + background-image: url(../images/default/grid/hmenu-lock.gif); +} + +.xg-hmenu-unlock .x-menu-item-icon{ + background-image: url(../images/default/grid/hmenu-unlock.gif); +} + +.x-grid3-hd-btn { + background-color:#c3daf9; + background-image:url(../images/default/grid/grid3-hd-btn.gif); +} + +.x-grid3-body .x-grid3-td-expander { + background-image:url(../images/default/grid/grid3-special-col-bg.gif); +} + +.x-grid3-row-expander { + background-image:url(../images/default/grid/row-expand-sprite.gif); +} + +.x-grid3-body .x-grid3-td-checker { + background-image: url(../images/default/grid/grid3-special-col-bg.gif); +} + +.x-grid3-row-checker, .x-grid3-hd-checker { + background-image:url(../images/default/grid/row-check-sprite.gif); +} + +.x-grid3-body .x-grid3-td-numberer { + background-image:url(../images/default/grid/grid3-special-col-bg.gif); +} + +.x-grid3-body .x-grid3-td-numberer .x-grid3-cell-inner { + color:#444; +} + +.x-grid3-body .x-grid3-td-row-icon { + background-image:url(../images/default/grid/grid3-special-col-bg.gif); +} + +.x-grid3-body .x-grid3-row-selected .x-grid3-td-numberer, +.x-grid3-body .x-grid3-row-selected .x-grid3-td-checker, +.x-grid3-body .x-grid3-row-selected .x-grid3-td-expander { + background-image:url(../images/default/grid/grid3-special-col-sel-bg.gif); +} + +.x-grid3-check-col { + background-image:url(../images/default/menu/unchecked.gif); +} + +.x-grid3-check-col-on { + background-image:url(../images/default/menu/checked.gif); +} + +.x-grid-group, .x-grid-group-body, .x-grid-group-hd { + zoom:1; +} + +.x-grid-group-hd { + border-bottom-color:#99bbe8; +} + +.x-grid-group-hd div.x-grid-group-title { + background-image:url(../images/default/grid/group-collapse.gif); + color:#3764a0; + font:bold 11px tahoma, arial, helvetica, sans-serif; +} + +.x-grid-group-collapsed .x-grid-group-hd div.x-grid-group-title { + background-image:url(../images/default/grid/group-expand.gif); +} + +.x-group-by-icon { + background-image:url(../images/default/grid/group-by.gif); +} + +.x-cols-icon { + background-image:url(../images/default/grid/columns.gif); +} + +.x-show-groups-icon { + background-image:url(../images/default/grid/group-by.gif); +} + +.x-grid-empty { + color:gray; + font:normal 11px tahoma, arial, helvetica, sans-serif; +} + +.x-grid-with-col-lines .x-grid3-row td.x-grid3-cell { + border-right-color:#ededed; +} + +.x-grid-with-col-lines .x-grid3-row-selected { + border-top-color:#a3bae9; +}.x-pivotgrid .x-grid3-header-offset table td { + background: url(../images/default/grid/grid3-hrow.gif) repeat-x 50% 100%; + border-left: 1px solid; + border-right: 1px solid; + border-left-color: #EEE; + border-right-color: #D0D0D0; +} + +.x-pivotgrid .x-grid3-row-headers { + background-color: #f9f9f9; +} + +.x-pivotgrid .x-grid3-row-headers table td { + background: #EEE url(../images/default/grid/grid3-rowheader.gif) repeat-x left top; + border-left: 1px solid; + border-right: 1px solid; + border-left-color: #EEE; + border-right-color: #D0D0D0; + border-bottom: 1px solid; + border-bottom-color: #D0D0D0; + height: 18px; +} +.x-dd-drag-ghost{ + color:#000; + font: normal 11px arial, helvetica, sans-serif; + border-color: #ddd #bbb #bbb #ddd; + background-color:#fff; +} + +.x-dd-drop-nodrop .x-dd-drop-icon{ + background-image: url(../images/default/dd/drop-no.gif); +} + +.x-dd-drop-ok .x-dd-drop-icon{ + background-image: url(../images/default/dd/drop-yes.gif); +} + +.x-dd-drop-ok-add .x-dd-drop-icon{ + background-image: url(../images/default/dd/drop-add.gif); +} + +.x-view-selector { + background-color:#c3daf9; + border-color:#3399bb; +}.x-tree-node-expanded .x-tree-node-icon{ + background-image:url(../images/default/tree/folder-open.gif); +} + +.x-tree-node-leaf .x-tree-node-icon{ + background-image:url(../images/default/tree/leaf.gif); +} + +.x-tree-node-collapsed .x-tree-node-icon{ + background-image:url(../images/default/tree/folder.gif); +} + +.x-tree-node-loading .x-tree-node-icon{ + background-image:url(../images/default/tree/loading.gif) !important; +} + +.x-tree-node .x-tree-node-inline-icon { + background-image: none; +} + +.x-tree-node-loading a span{ + font-style: italic; + color:#444444; +} + +.x-tree-lines .x-tree-elbow{ + background-image:url(../images/default/tree/elbow.gif); +} + +.x-tree-lines .x-tree-elbow-plus{ + background-image:url(../images/default/tree/elbow-plus.gif); +} + +.x-tree-lines .x-tree-elbow-minus{ + background-image:url(../images/default/tree/elbow-minus.gif); +} + +.x-tree-lines .x-tree-elbow-end{ + background-image:url(../images/default/tree/elbow-end.gif); +} + +.x-tree-lines .x-tree-elbow-end-plus{ + background-image:url(../images/default/tree/elbow-end-plus.gif); +} + +.x-tree-lines .x-tree-elbow-end-minus{ + background-image:url(../images/default/tree/elbow-end-minus.gif); +} + +.x-tree-lines .x-tree-elbow-line{ + background-image:url(../images/default/tree/elbow-line.gif); +} + +.x-tree-no-lines .x-tree-elbow-plus{ + background-image:url(../images/default/tree/elbow-plus-nl.gif); +} + +.x-tree-no-lines .x-tree-elbow-minus{ + background-image:url(../images/default/tree/elbow-minus-nl.gif); +} + +.x-tree-no-lines .x-tree-elbow-end-plus{ + background-image:url(../images/default/tree/elbow-end-plus-nl.gif); +} + +.x-tree-no-lines .x-tree-elbow-end-minus{ + background-image:url(../images/default/tree/elbow-end-minus-nl.gif); +} + +.x-tree-arrows .x-tree-elbow-plus{ + background-image:url(../images/default/tree/arrows.gif); +} + +.x-tree-arrows .x-tree-elbow-minus{ + background-image:url(../images/default/tree/arrows.gif); +} + +.x-tree-arrows .x-tree-elbow-end-plus{ + background-image:url(../images/default/tree/arrows.gif); +} + +.x-tree-arrows .x-tree-elbow-end-minus{ + background-image:url(../images/default/tree/arrows.gif); +} + +.x-tree-node{ + color:#000; + font: normal 11px arial, tahoma, helvetica, sans-serif; +} + +.x-tree-node a, .x-dd-drag-ghost a{ + color:#000; +} + +.x-tree-node a span, .x-dd-drag-ghost a span{ + color:#000; +} + +.x-tree-node .x-tree-node-disabled a span{ + color:gray !important; +} + +.x-tree-node div.x-tree-drag-insert-below{ + border-bottom-color:#36c; +} + +.x-tree-node div.x-tree-drag-insert-above{ + border-top-color:#36c; +} + +.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below a{ + border-bottom-color:#36c; +} + +.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above a{ + border-top-color:#36c; +} + +.x-tree-node .x-tree-drag-append a span{ + background-color:#ddd; + border-color:gray; +} + +.x-tree-node .x-tree-node-over { + background-color: #eee; +} + +.x-tree-node .x-tree-selected { + background-color: #d9e8fb; +} + +.x-tree-drop-ok-append .x-dd-drop-icon{ + background-image: url(../images/default/tree/drop-add.gif); +} + +.x-tree-drop-ok-above .x-dd-drop-icon{ + background-image: url(../images/default/tree/drop-over.gif); +} + +.x-tree-drop-ok-below .x-dd-drop-icon{ + background-image: url(../images/default/tree/drop-under.gif); +} + +.x-tree-drop-ok-between .x-dd-drop-icon{ + background-image: url(../images/default/tree/drop-between.gif); +}.x-date-picker { + border-color: #1b376c; + background-color:#fff; +} + +.x-date-middle,.x-date-left,.x-date-right { + background-image: url(../images/default/shared/hd-sprite.gif); + color:#fff; + font:bold 11px "sans serif", tahoma, verdana, helvetica; +} + +.x-date-middle .x-btn .x-btn-text { + color:#fff; +} + +.x-date-middle .x-btn-mc em.x-btn-arrow { + background-image:url(../images/default/toolbar/btn-arrow-light.gif); +} + +.x-date-right a { + background-image: url(../images/default/shared/right-btn.gif); +} + +.x-date-left a{ + background-image: url(../images/default/shared/left-btn.gif); +} + +.x-date-inner th { + background-color:#dfecfb; + background-image:url(../images/default/shared/glass-bg.gif); + border-bottom-color:#a3bad9; + font:normal 10px arial, helvetica,tahoma,sans-serif; + color:#233d6d; +} + +.x-date-inner td { + border-color:#fff; +} + +.x-date-inner a { + font:normal 11px arial, helvetica,tahoma,sans-serif; + color:#000; +} + +.x-date-inner .x-date-active{ + color:#000; +} + +.x-date-inner .x-date-selected a{ + background-color:#dfecfb; + background-image:url(../images/default/shared/glass-bg.gif); + border-color:#8db2e3; +} + +.x-date-inner .x-date-today a{ + border-color:darkred; +} + +.x-date-inner .x-date-selected span{ + font-weight:bold; +} + +.x-date-inner .x-date-prevday a,.x-date-inner .x-date-nextday a { + color:#aaa; +} + +.x-date-bottom { + border-top-color:#a3bad9; + background-color:#dfecfb; + background-image:url(../images/default/shared/glass-bg.gif); +} + +.x-date-inner a:hover, .x-date-inner .x-date-disabled a:hover{ + color:#000; + background-color:#ddecfe; +} + +.x-date-inner .x-date-disabled a { + background-color:#eee; + color:#bbb; +} + +.x-date-mmenu{ + background-color:#eee !important; +} + +.x-date-mmenu .x-menu-item { + font-size:10px; + color:#000; +} + +.x-date-mp { + background-color:#fff; +} + +.x-date-mp td { + font:normal 11px arial, helvetica,tahoma,sans-serif; +} + +.x-date-mp-btns button { + background-color:#083772; + color:#fff; + border-color: #3366cc #000055 #000055 #3366cc; + font:normal 11px arial, helvetica,tahoma,sans-serif; +} + +.x-date-mp-btns { + background-color: #dfecfb; + background-image: url(../images/default/shared/glass-bg.gif); +} + +.x-date-mp-btns td { + border-top-color: #c5d2df; +} + +td.x-date-mp-month a,td.x-date-mp-year a { + color:#15428b; +} + +td.x-date-mp-month a:hover,td.x-date-mp-year a:hover { + color:#15428b; + background-color: #ddecfe; +} + +td.x-date-mp-sel a { + background-color: #dfecfb; + background-image: url(../images/default/shared/glass-bg.gif); + border-color:#8db2e3; +} + +.x-date-mp-ybtn a { + background-image:url(../images/default/panel/tool-sprites.gif); +} + +td.x-date-mp-sep { + border-right-color:#c5d2df; +}.x-tip .x-tip-close{ + background-image: url(../images/default/qtip/close.gif); +} + +.x-tip .x-tip-tc, .x-tip .x-tip-tl, .x-tip .x-tip-tr, .x-tip .x-tip-bc, .x-tip .x-tip-bl, .x-tip .x-tip-br, .x-tip .x-tip-ml, .x-tip .x-tip-mr { + background-image: url(../images/default/qtip/tip-sprite.gif); +} + +.x-tip .x-tip-mc { + font: normal 11px tahoma,arial,helvetica,sans-serif; +} +.x-tip .x-tip-ml { + background-color: #fff; +} + +.x-tip .x-tip-header-text { + font: bold 11px tahoma,arial,helvetica,sans-serif; + color:#444; +} + +.x-tip .x-tip-body { + font: normal 11px tahoma,arial,helvetica,sans-serif; + color:#444; +} + +.x-form-invalid-tip .x-tip-tc, .x-form-invalid-tip .x-tip-tl, .x-form-invalid-tip .x-tip-tr, .x-form-invalid-tip .x-tip-bc, +.x-form-invalid-tip .x-tip-bl, .x-form-invalid-tip .x-tip-br, .x-form-invalid-tip .x-tip-ml, .x-form-invalid-tip .x-tip-mr +{ + background-image: url(../images/default/form/error-tip-corners.gif); +} + +.x-form-invalid-tip .x-tip-body { + background-image:url(../images/default/form/exclamation.gif); +} + +.x-tip-anchor { + background-image:url(../images/default/qtip/tip-anchor-sprite.gif); +}.x-menu { + background-color:#f0f0f0; + background-image:url(../images/default/menu/menu.gif); +} + +.x-menu-floating{ + border-color:#718bb7; +} + +.x-menu-nosep { + background-image:none; +} + +.x-menu-list-item{ + font:normal 11px arial,tahoma,sans-serif; +} + +.x-menu-item-arrow{ + background-image:url(../images/default/menu/menu-parent.gif); +} + +.x-menu-sep { + background-color:#e0e0e0; + border-bottom-color:#fff; +} + +a.x-menu-item { + color:#222; +} + +.x-menu-item-active { + background-image: url(../images/default/menu/item-over.gif); + background-color: #dbecf4; + border-color:#aaccf6; +} + +.x-menu-item-active a.x-menu-item { + border-color:#aaccf6; +} + +.x-menu-check-item .x-menu-item-icon{ + background-image:url(../images/default/menu/unchecked.gif); +} + +.x-menu-item-checked .x-menu-item-icon{ + background-image:url(../images/default/menu/checked.gif); +} + +.x-menu-item-checked .x-menu-group-item .x-menu-item-icon{ + background-image:url(../images/default/menu/group-checked.gif); +} + +.x-menu-group-item .x-menu-item-icon{ + background-image:none; +} + +.x-menu-plain { + background-color:#f0f0f0 !important; + background-image: none; +} + +.x-date-menu, .x-color-menu{ + background-color: #fff !important; +} + +.x-menu .x-date-picker{ + border-color:#a3bad9; +} + +.x-cycle-menu .x-menu-item-checked { + border-color:#a3bae9 !important; + background-color:#def8f6; +} + +.x-menu-scroller-top { + background-image:url(../images/default/layout/mini-top.gif); +} + +.x-menu-scroller-bottom { + background-image:url(../images/default/layout/mini-bottom.gif); +} +.x-box-tl { + background-image: url(../images/default/box/corners.gif); +} + +.x-box-tc { + background-image: url(../images/default/box/tb.gif); +} + +.x-box-tr { + background-image: url(../images/default/box/corners.gif); +} + +.x-box-ml { + background-image: url(../images/default/box/l.gif); +} + +.x-box-mc { + background-color: #eee; + background-image: url(../images/default/box/tb.gif); + font-family: "Myriad Pro","Myriad Web","Tahoma","Helvetica","Arial",sans-serif; + color: #393939; + font-size: 12px; +} + +.x-box-mc h3 { + font-size: 14px; + font-weight: bold; +} + +.x-box-mr { + background-image: url(../images/default/box/r.gif); +} + +.x-box-bl { + background-image: url(../images/default/box/corners.gif); +} + +.x-box-bc { + background-image: url(../images/default/box/tb.gif); +} + +.x-box-br { + background-image: url(../images/default/box/corners.gif); +} + +.x-box-blue .x-box-bl, .x-box-blue .x-box-br, .x-box-blue .x-box-tl, .x-box-blue .x-box-tr { + background-image: url(../images/default/box/corners-blue.gif); +} + +.x-box-blue .x-box-bc, .x-box-blue .x-box-mc, .x-box-blue .x-box-tc { + background-image: url(../images/default/box/tb-blue.gif); +} + +.x-box-blue .x-box-mc { + background-color: #c3daf9; +} + +.x-box-blue .x-box-mc h3 { + color: #17385b; +} + +.x-box-blue .x-box-ml { + background-image: url(../images/default/box/l-blue.gif); +} + +.x-box-blue .x-box-mr { + background-image: url(../images/default/box/r-blue.gif); +}.x-combo-list { + border-color:#98c0f4; + background-color:#ddecfe; + font:normal 12px tahoma, arial, helvetica, sans-serif; +} + +.x-combo-list-inner { + background-color:#fff; +} + +.x-combo-list-hd { + font:bold 11px tahoma, arial, helvetica, sans-serif; + color:#15428b; + background-image: url(../images/default/layout/panel-title-light-bg.gif); + border-bottom-color:#98c0f4; +} + +.x-resizable-pinned .x-combo-list-inner { + border-bottom-color:#98c0f4; +} + +.x-combo-list-item { + border-color:#fff; +} + +.x-combo-list .x-combo-selected{ + border-color:#a3bae9 !important; + background-color:#dfe8f6; +} + +.x-combo-list .x-toolbar { + border-top-color:#98c0f4; +} + +.x-combo-list-small { + font:normal 11px tahoma, arial, helvetica, sans-serif; +}.x-panel { + border-color: #99bbe8; +} + +.x-panel-header { + color:#15428b; + font-weight:bold; + font-size: 11px; + font-family: tahoma,arial,verdana,sans-serif; + border-color:#99bbe8; + background-image: url(../images/default/panel/white-top-bottom.gif); +} + +.x-panel-body { + border-color:#99bbe8; + background-color:#fff; +} + +.x-panel-bbar .x-toolbar, .x-panel-tbar .x-toolbar { + border-color:#99bbe8; +} + +.x-panel-tbar-noheader .x-toolbar, .x-panel-mc .x-panel-tbar .x-toolbar { + border-top-color:#99bbe8; +} + +.x-panel-body-noheader, .x-panel-mc .x-panel-body { + border-top-color:#99bbe8; +} + +.x-panel-tl .x-panel-header { + color:#15428b; + font:bold 11px tahoma,arial,verdana,sans-serif; +} + +.x-panel-tc { + background-image: url(../images/default/panel/top-bottom.gif); +} + +.x-panel-tl, .x-panel-tr, .x-panel-bl, .x-panel-br{ + background-image: url(../images/default/panel/corners-sprite.gif); + border-bottom-color:#99bbe8; +} + +.x-panel-bc { + background-image: url(../images/default/panel/top-bottom.gif); +} + +.x-panel-mc { + font: normal 11px tahoma,arial,helvetica,sans-serif; + background-color:#dfe8f6; +} + +.x-panel-ml { + background-color: #fff; + background-image:url(../images/default/panel/left-right.gif); +} + +.x-panel-mr { + background-image: url(../images/default/panel/left-right.gif); +} + +.x-tool { + background-image:url(../images/default/panel/tool-sprites.gif); +} + +.x-panel-ghost { + background-color:#cbddf3; +} + +.x-panel-ghost ul { + border-color:#99bbe8; +} + +.x-panel-dd-spacer { + border-color:#99bbe8; +} + +.x-panel-fbar td,.x-panel-fbar span,.x-panel-fbar input,.x-panel-fbar div,.x-panel-fbar select,.x-panel-fbar label{ + font:normal 11px arial,tahoma, helvetica, sans-serif; +} +.x-window-proxy { + background-color:#c7dffc; + border-color:#99bbe8; +} + +.x-window-tl .x-window-header { + color:#15428b; + font:bold 11px tahoma,arial,verdana,sans-serif; +} + +.x-window-tc { + background-image: url(../images/default/window/top-bottom.png); +} + +.x-window-tl { + background-image: url(../images/default/window/left-corners.png); +} + +.x-window-tr { + background-image: url(../images/default/window/right-corners.png); +} + +.x-window-bc { + background-image: url(../images/default/window/top-bottom.png); +} + +.x-window-bl { + background-image: url(../images/default/window/left-corners.png); +} + +.x-window-br { + background-image: url(../images/default/window/right-corners.png); +} + +.x-window-mc { + border-color:#99bbe8; + font: normal 11px tahoma,arial,helvetica,sans-serif; + background-color:#dfe8f6; +} + +.x-window-ml { + background-image: url(../images/default/window/left-right.png); +} + +.x-window-mr { + background-image: url(../images/default/window/left-right.png); +} + +.x-window-maximized .x-window-tc { + background-color:#fff; +} + +.x-window-bbar .x-toolbar { + border-top-color:#99bbe8; +} + +.x-panel-ghost .x-window-tl { + border-bottom-color:#99bbe8; +} + +.x-panel-collapsed .x-window-tl { + border-bottom-color:#84a0c4; +} + +.x-dlg-mask{ + background-color:#ccc; +} + +.x-window-plain .x-window-mc { + background-color: #ccd9e8; + border-color: #a3bae9 #dfe8f6 #dfe8f6 #a3bae9; +} + +.x-window-plain .x-window-body { + border-color: #dfe8f6 #a3bae9 #a3bae9 #dfe8f6; +} + +body.x-body-masked .x-window-plain .x-window-mc { + background-color: #ccd9e8; +}.x-html-editor-wrap { + border-color:#a9bfd3; + background-color:#fff; +} +.x-html-editor-tb .x-btn-text { + background-image:url(../images/default/editor/tb-sprite.gif); +}.x-panel-noborder .x-panel-header-noborder { + border-bottom-color:#99bbe8; +} + +.x-panel-noborder .x-panel-tbar-noborder .x-toolbar { + border-bottom-color:#99bbe8; +} + +.x-panel-noborder .x-panel-bbar-noborder .x-toolbar { + border-top-color:#99bbe8; +} + +.x-tab-panel-bbar-noborder .x-toolbar { + border-top-color:#99bbe8; +} + +.x-tab-panel-tbar-noborder .x-toolbar { + border-bottom-color:#99bbe8; +}.x-border-layout-ct { + background-color:#dfe8f6; +} + +.x-accordion-hd { + color:#222; + font-weight:normal; + background-image: url(../images/default/panel/light-hd.gif); +} + +.x-layout-collapsed{ + background-color:#d2e0f2; + border-color:#98c0f4; +} + +.x-layout-collapsed-over{ + background-color:#d9e8fb; +} + +.x-layout-split-west .x-layout-mini { + background-image:url(../images/default/layout/mini-left.gif); +} +.x-layout-split-east .x-layout-mini { + background-image:url(../images/default/layout/mini-right.gif); +} +.x-layout-split-north .x-layout-mini { + background-image:url(../images/default/layout/mini-top.gif); +} +.x-layout-split-south .x-layout-mini { + background-image:url(../images/default/layout/mini-bottom.gif); +} + +.x-layout-cmini-west .x-layout-mini { + background-image:url(../images/default/layout/mini-right.gif); +} + +.x-layout-cmini-east .x-layout-mini { + background-image:url(../images/default/layout/mini-left.gif); +} + +.x-layout-cmini-north .x-layout-mini { + background-image:url(../images/default/layout/mini-bottom.gif); +} + +.x-layout-cmini-south .x-layout-mini { + background-image:url(../images/default/layout/mini-top.gif); +}.x-progress-wrap { + border-color:#6593cf; +} + +.x-progress-inner { + background-color:#e0e8f3; + background-image:url(../images/default/qtip/bg.gif); +} + +.x-progress-bar { + background-color:#9cbfee; + background-image:url(../images/default/progress/progress-bg.gif); + border-top-color:#d1e4fd; + border-bottom-color:#7fa9e4; + border-right-color:#7fa9e4; +} + +.x-progress-text { + font-size:11px; + font-weight:bold; + color:#fff; +} + +.x-progress-text-back { + color:#396095; +}.x-list-header{ + background-color:#f9f9f9; + background-image:url(../images/default/grid/grid3-hrow.gif); +} + +.x-list-header-inner div em { + border-left-color:#ddd; + font:normal 11px arial, tahoma, helvetica, sans-serif; +} + +.x-list-body dt em { + font:normal 11px arial, tahoma, helvetica, sans-serif; +} + +.x-list-over { + background-color:#eee; +} + +.x-list-selected { + background-color:#dfe8f6; +} + +.x-list-resizer { + border-left-color:#555; + border-right-color:#555; +} + +.x-list-header-inner em.sort-asc, .x-list-header-inner em.sort-desc { + background-image:url(../images/default/grid/sort-hd.gif); + border-color: #99bbe8; +}.x-slider-horz, .x-slider-horz .x-slider-end, .x-slider-horz .x-slider-inner { + background-image:url(../images/default/slider/slider-bg.png); +} + +.x-slider-horz .x-slider-thumb { + background-image:url(../images/default/slider/slider-thumb.png); +} + +.x-slider-vert, .x-slider-vert .x-slider-end, .x-slider-vert .x-slider-inner { + background-image:url(../images/default/slider/slider-v-bg.png); +} + +.x-slider-vert .x-slider-thumb { + background-image:url(../images/default/slider/slider-v-thumb.png); +}.x-window-dlg .ext-mb-text, +.x-window-dlg .x-window-header-text { + font-size:12px; +} + +.x-window-dlg .ext-mb-textarea { + font:normal 12px tahoma,arial,helvetica,sans-serif; +} + +.x-window-dlg .x-msg-box-wait { + background-image:url(../images/default/grid/loading.gif); +} + +.x-window-dlg .ext-mb-info { + background-image:url(../images/default/window/icon-info.gif); +} + +.x-window-dlg .ext-mb-warning { + background-image:url(../images/default/window/icon-warning.gif); +} + +.x-window-dlg .ext-mb-question { + background-image:url(../images/default/window/icon-question.gif); +} + +.x-window-dlg .ext-mb-error { + background-image:url(../images/default/window/icon-error.gif); +} \ No newline at end of file diff --git a/modules/organize/vendor/ext/css/ux-all.css b/modules/organize/vendor/ext/css/ux-all.css new file mode 100644 index 0000000..42943be --- /dev/null +++ b/modules/organize/vendor/ext/css/ux-all.css @@ -0,0 +1,772 @@ +/*! + * Ext JS Library 3.3.1 + * Copyright(c) 2006-2010 Sencha Inc. + * licensing@sencha.com + * http://www.sencha.com/license + */ +.ux-layout-center-item { + margin:0 auto; + text-align:left; +} +.ux-layout-center .x-panel-body, +body.ux-layout-center { + text-align:center; +} +td.ux-grid-hd-group-cell { + background: url(../../../resources/images/default/grid/grid3-hrow.gif) repeat-x bottom; +}.x-column-tree .x-panel-header { + padding: 3px 0px 0px 0px; + border-bottom-width: 0px; +} + +.x-column-tree .x-panel-header .x-panel-header-text { + margin-left: 3px +} + +.x-column-tree .x-tree-node { + zoom:1; +} +.x-column-tree .x-tree-node-el { + /*border-bottom:1px solid #eee; borders? */ + zoom:1; +} +.x-column-tree .x-tree-selected { + background: #d9e8fb; +} +.x-column-tree .x-tree-node a { + line-height:18px; + vertical-align:middle; +} +.x-column-tree .x-tree-node a span{ + +} +.x-column-tree .x-tree-node .x-tree-selected a span{ + background:transparent; + color:#000; +} +.x-tree-col { + float:left; + overflow:hidden; + padding:0 1px; + zoom:1; +} + +.x-tree-col-text, .x-tree-hd-text { + color:#000; + overflow:hidden; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + padding:3px 3px 3px 5px; + white-space: nowrap; + font:normal 11px arial, tahoma, helvetica, sans-serif; +} + +.x-tree-headers { + margin-top: 3px; + background: #f9f9f9 url(../../../resources/images/default/grid/grid3-hrow.gif) repeat-x 0 bottom; + cursor:default; + zoom:1; +} + +.x-tree-hd { + float:left; + overflow:hidden; + border-left:1px solid #eee; + border-right:1px solid #d0d0d0; +} +/* + * FileUploadField component styles + */ +.x-form-file-wrap { + position: relative; + height: 22px; +} +.x-form-file-wrap .x-form-file { + position: absolute; + right: 0; + -moz-opacity: 0; + filter:alpha(opacity: 0); + opacity: 0; + z-index: 2; + height: 22px; +} +.x-form-file-wrap .x-form-file-btn { + position: absolute; + right: 0; + z-index: 1; +} +.x-form-file-wrap .x-form-file-text { + position: absolute; + left: 0; + z-index: 3; + color: #777; +}/** + * GridFilters Styles + **/ +/* +.x-grid3-hd-row .ux-filtered-column { + border-left: 1px solid #C7E3B4; + border-right: 1px solid #C7E3B4; +} + +.x-grid3-hd-row .ux-filtered-column .x-grid3-hd-inner { + background-image: url(../images/header_bg.gif); +} + +.ux-filtered-column .x-grid3-hd-btn { + background-image: url(../images/hd-btn.gif); +} +*/ +.x-grid3-hd-row td.ux-filtered-column { + font-style: italic; + font-weight: bold; +} + +.ux-filtered-column.sort-asc .x-grid3-sort-icon { + background-image: url(../images/sort_filtered_asc.gif) !important; +} + +.ux-filtered-column.sort-desc .x-grid3-sort-icon { + background-image: url(../images/sort_filtered_desc.gif) !important; +} + +.ux-gridfilter-text-icon { + background-image: url(../images/find.png) !important; +} + +/* Temporary Patch for Bug ??? */ +.x-menu-list-item-indent .x-menu-item-icon { + position: relative; + top: 3px; + left: 3px; + margin-right: 10px; +} +li.x-menu-list-item-indent { + padding-left:0px; +} +li.x-menu-list-item div { + display: block; +} + +/** + * RangeMenu Styles + **/ +.ux-rangemenu-gt { + background-image: url(../images/greater_than.png) !important; +} + +.ux-rangemenu-lt { + background-image: url(../images/less_than.png) !important; +} + +.ux-rangemenu-eq { + background-image: url(../images/equals.png) !important; +} +.x-grid3-summary-row { + border-left:1px solid #fff; + border-right:1px solid #fff; + color:#333; + background: #f1f2f4; +} +.x-grid3-summary-row .x-grid3-cell-inner { + font-weight:bold; + padding-bottom:4px; +} +.x-grid3-cell-first .x-grid3-cell-inner { + padding-left:16px; +} +.x-grid-hide-summary .x-grid3-summary-row { + display:none; +} +.x-grid3-summary-msg { + padding:4px 16px; + font-weight:bold; +}.x-grouptabs-panel { + background-color: #4E78B1; + border: solid 15px #4E78B1; +} +.x-tab-panel-left .x-grouptabs-panel-header, +.x-tab-panel-right .x-grouptabs-panel-header { + float: left; + border: 0; + background: transparent; +} +.x-tab-panel-right .x-grouptabs-panel-header { + float:right; +} +.x-tab-panel-left .x-grouptabs-bwrap { + float: right; + position: relative; +} +.x-tab-panel-right .x-grouptabs-bwrap { + float: left; + position: relative; +} +.x-tab-panel-left ul.x-grouptabs-strip, +.x-tab-panel-right ul.x-grouptabs-strip { + width: auto; + display: block; +} +.x-tab-panel-left ul.x-grouptabs-strip li, +.x-tab-panel-right ul.x-grouptabs-strip li { + padding: 6px 0 2px 6px; + float: none; + margin: 0; + position: relative; + clear: both; +} +.x-tab-panel-left .x-tab-panel-header ul.x-grouptabs-strip a.x-grouptabs-text, +.x-tab-panel-right .x-tab-panel-header ul.x-grouptabs-strip a.x-grouptabs-text{ + font-size: 13px; + line-height: 18px; + cursor: pointer; +} + +.x-tab-panel-left .x-tab-panel-header ul.x-grouptabs-strip a.x-grouptabs-text{ + padding-left: 18px; +} +.x-tab-panel-right .x-tab-panel-header ul.x-grouptabs-strip a.x-grouptabs-text{ + padding-right: 18px; +} + +.x-tab-panel-left .x-tab-panel-header ul.x-grouptabs-sub a.x-grouptabs-text, +.x-tab-panel-right .x-tab-panel-header ul.x-grouptabs-sub a.x-grouptabs-text{ + font-size: 12px; + padding: 0; +} + +.x-tab-panel-left .x-tab-panel-header ul.x-grouptabs-sub a.x-grouptabs-text{ + margin-left: 4px; +} +.x-tab-panel-right .x-tab-panel-header ul.x-grouptabs-sub a.x-grouptabs-text{ + margin-right: 4px; +} + +.x-grouptabs-panel .x-grouptabs-strip a.x-grouptabs-text{ + overflow: hidden; + white-space: nowrap; + display: block; + color: #DFE8F6; + font-family: tahoma, arial, sans-serif; + font-weight: bold; + text-decoration: none; +} +.x-tab-panel-right .x-grouptabs-strip a.x-grouptabs-text { + text-align: right; +} + +.x-grouptabs-panel .x-grouptabs-strip-active a.x-grouptabs-text { + color: #395B8E; +} + +.x-grouptabs-panel ul.x-grouptabs-sub a.x-grouptabs-text { + font-weight: normal; +} +.x-tab-joint { + position: absolute; + width: 3px; + top: 1px; + background: #fff; + z-index: 8999; +} + +.x-grouptabs-panel .x-grouptabs-panel-body { + border: 1px solid #999; +} + +.x-grouptabs-panel ul.x-grouptabs-strip li { + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + border-left: 1px solid transparent; +} + +.x-grouptabs-panel ul.x-grouptabs-strip li.x-grouptabs-strip-active { + border: 0; + background: #fff; + border-top: 1px solid #999; + border-bottom: 1px solid #999; +} + +.x-tab-panel-left ul.x-grouptabs-strip li.x-grouptabs-strip-active { + border-left: 1px solid #999; +} +.x-tab-panel-right ul.x-grouptabs-strip li.x-grouptabs-strip-active { + border-right: 1px solid #999; +} + +.x-grouptabs-panel li.x-grouptabs-strip-active ul.x-grouptabs-sub li.x-grouptabs-strip-active{ + background-color: #EDEEF0; +} + +.x-grouptabs-panel li.x-grouptabs-strip-active ul.x-grouptabs-sub { + background-color: transparent; +} + +.x-grouptabs-panel li.x-grouptabs-strip-active ul.x-grouptabs-sub li { + border-color: transparent; +} + +/* Tab corners */ +.x-grouptabs-panel .x-grouptabs-corner { + background-image: url('../images/x-grouptabs-corners.gif'); + display: none; + width: 11px; + height: 11px; + position: absolute; + font-size: 1px; + line-height: 6px; + overflow: hidden; + zoom:1; +} +.x-grouptabs-panel .x-grouptabs-strip-active .x-grouptabs-corner { + display: block; +} +.x-grouptabs-panel .x-grouptabs-main.x-grouptabs-strip-active ul.x-grouptabs-sub .x-grouptabs-corner { + display: none; +} + +.x-grouptabs-panel .x-grouptabs-corner-top-left { + background-position: top left; + left: 0; top: 0; +} +.x-grouptabs-panel .x-grouptabs-corner-bottom-left { + background-position: bottom left; + left: 0; bottom: 0; +} +.x-grouptabs-panel .x-grouptabs-corner-top-right { + background-position: top right; + right: 0; top: 0; +} +.x-grouptabs-panel .x-grouptabs-corner-bottom-right { + background-position: bottom right; + right: 0; bottom: 0; +} +.x-grouptabs-panel li.x-grouptabs-strip-active .x-grouptabs-corner-bottom-left{ + bottom: -4px; left: -4px; +} +.x-grouptabs-panel li.x-grouptabs-strip-active .x-grouptabs-corner-bottom-right{ + bottom: -4px; right: -4px; +} +.x-grouptabs-panel li.x-grouptabs-strip-active .x-grouptabs-corner-top-left{ + top: -4px; left: -4px; +} +.x-grouptabs-panel li.x-grouptabs-strip-active .x-grouptabs-corner-top-right{ + top: -4px; right: -4px; +} + +.x-grouptabs-panel ul.x-grouptabs-sub li.x-tab-with-icon a.x-grouptabs-text { + background-repeat: no-repeat; + padding-left: 20px; +} + +/* General tab styling */ +.x-grouptabs-panel .x-grouptabs-expand { + background: transparent url('../images/elbow-plus-nl.gif') no-repeat; + width: 16px; + height: 16px; + position: absolute; + left: 7px; + top: 6px; +} + +.ext-ie6 .x-grouptabs-panel .x-grouptabs-expand, +.ext-border-box .x-grouptabs-panel .x-grouptabs-expand { + left: 0; +} + +.x-grouptabs-expanded .x-grouptabs-expand { + background-image: url('../images/elbow-minus-nl.gif'); +} + +/* GroupTabs sub group styling */ +.x-grouptabs-sub { + display: none; + margin-top: 4px; +} + +.x-grouptabs-expanded .x-grouptabs-sub { + display: block; +} + +.x-grouptabs-panel ul.x-grouptabs-sub li { + height: 18px; + margin: 0 0 2px; + padding: 0; +} + +.x-grouptabs-panel ul.x-grouptabs-sub .x-grouptabs-main-item { + display: none; +} + +.x-tab-with-icon{ + border-style:none !important; +} +.x-grid3-locked, .x-grid3-unlocked { + overflow: hidden; + position: absolute; +} + +.x-grid3-locked { + border-right: 1px solid #99BBE8; +} + +.x-grid3-locked .x-grid3-scroller { + overflow: hidden; +} + +.x-grid3-locked .x-grid3-row { + border-right: 0; +} + +.x-grid3-scroll-spacer { + height: 19px; +} + +.x-grid3-unlocked .x-grid3-header-offset { + padding-left: 0; +} + +.x-grid3-unlocked .x-grid3-row { + border-left: 0; +} +.ux-mselect{ + overflow:auto; + background:white; + position:relative; /* for calculating scroll offsets */ + zoom:1; + overflow:auto; +} +.ux-mselect-item{ + font:normal 12px tahoma, arial, helvetica, sans-serif; + padding:2px; + border:1px solid #fff; + white-space: nowrap; + cursor:pointer; +} +.ux-mselect-selected{ + border:1px dotted #a3bae9 !important; + background:#DFE8F6; + cursor:pointer; +} + +.x-view-drag-insert-above { + border-top:1px dotted #3366cc; +} +.x-view-drag-insert-below { + border-bottom:1px dotted #3366cc; +} +.x-panel-resize { + height:5px; + background:transparent url(../images/panel-handle.gif) no-repeat center bottom; + position:relative; + left:0; + top:2px; + cursor:n-resize; + cursor:row-resize; + /* for IE */ + font-size:1px; + line-height:1px; + overflow:hidden; +}.x-portal .x-panel-dd-spacer { + margin-bottom:10px; +} + +.x-portlet { + margin-bottom:10px; +} + +/* Clean up the look of the portlets */ +.x-portlet .x-panel-ml { + padding-left:2px; +} +.x-portlet .x-panel-mr { + padding-right:2px; +} +.x-portlet .x-panel-bl { + padding-left:2px; +} + +.x-portlet .x-panel-br { + padding-right:2px; +} +.x-portlet .x-panel-body { + background:white; +} +.x-portlet .x-panel-mc { + padding-top:2px; +} +.x-portlet .x-panel-bc .x-panel-footer { + padding-bottom:2px; +} +.x-portlet .x-panel-nofooter .x-panel-bc { + height:2px; +}.ext-ie .x-row-editor .x-form-text { + margin:0 !important; +} +.x-row-editor-header { + height:2px; + overflow:hidden; + background: transparent url(../images/row-editor-bg.gif) repeat-x 0 0; +} +.x-row-editor-footer { + height:2px; + overflow:hidden; + background: transparent url(../images/row-editor-bg.gif) repeat-x 0 -2px; +} +.ext-ie .x-row-editor-footer { + margin-top:-1px; +} + +.x-row-editor-body { + overflow:hidden; + zoom:1; + background: #ebf2fb; + padding-top:2px; +} +.x-row-editor .x-btns { + position:absolute; + top:28px; + left:20px; + padding-left:5px; + background: transparent url(../images/row-editor-btns.gif) no-repeat 0 0; +} +.x-row-editor .x-btns .x-plain-bwrap { + padding-right:5px; + background: transparent url(../images/row-editor-btns.gif) no-repeat right -31px; +} +.x-row-editor .x-btns .x-plain-body { + background: transparent url(../images/row-editor-btns.gif) repeat-x 0 -62px; + height:31px; +} +.x-row-editor .x-btns .x-table-layout-cell { + padding:3px; +} + +/* Fixes for IE6/7 trigger fields */ +.ext-ie6 .x-row-editor .x-form-field-wrap .x-form-trigger, .ext-ie7 .x-row-editor .x-form-field-wrap .x-form-trigger { + top: 1px; +} + +.ext-ie6 .x-row-editor .x-form-field-trigger-wrap, .ext-ie7 .x-row-editor .x-form-field-trigger-wrap { + margin-top: -1px; +} + +.errorTip .x-tip-body ul{ + list-style-type:disc; + margin-left:15px; +} +.x-form-spinner-proxy{ + /*background-color:#ff00cc;*/ +} +.x-form-field-wrap .x-form-spinner-trigger { + background:transparent url('../images/spinner.gif') no-repeat 0 0; +} + +.x-form-field-wrap .x-form-spinner-overup{ + background-position:-17px 0; +} +.x-form-field-wrap .x-form-spinner-clickup{ + background-position:-34px 0; +} +.x-form-field-wrap .x-form-spinner-overdown{ + background-position:-51px 0; +} +.x-form-field-wrap .x-form-spinner-clickdown{ + background-position:-68px 0; +} + + +.x-trigger-wrap-focus .x-form-spinner-trigger{ + background-position:-85px 0; +} +.x-trigger-wrap-focus .x-form-spinner-overup{ + background-position:-102px 0; +} +.x-trigger-wrap-focus .x-form-spinner-clickup{ + background-position:-119px 0; +} +.x-trigger-wrap-focus .x-form-spinner-overdown{ + background-position:-136px 0; +} +.x-trigger-wrap-focus .x-form-spinner-clickdown{ + background-position:-153px 0; +} +.x-trigger-wrap-focus .x-form-trigger{ + border-bottom: 1px solid #7eadd9; +} + +.x-form-field-wrap .x-form-spinner-splitter { + line-height:1px; + font-size:1px; + background:transparent url('../images/spinner-split.gif') no-repeat 0 0; + position:absolute; + cursor: n-resize; +} +.x-trigger-wrap-focus .x-form-spinner-splitter{ + background-position:-14px 0; +} +/* StatusBar - structure */ +.x-statusbar .x-status-text { + cursor: default; +/* + height: 21px; + line-height: 21px; + padding: 0 4px; +*/ +} +.x-statusbar .x-status-busy { + padding-left: 25px !important; + background: transparent no-repeat 3px 2px; +} + +.x-toolbar div.xtb-text + +.x-statusbar .x-status-text-panel { + border-top: 1px solid; + border-right: 1px solid; + border-bottom: 1px solid; + border-left: 1px solid; + padding: 2px 8px 2px 5px; +} + +/* StatusBar word processor example styles */ + +#word-status .x-status-text-panel .spacer { + width: 60px; + font-size:0; + line-height:0; +} +#word-status .x-status-busy { + padding-left: 25px !important; + background: transparent no-repeat 3px 2px; +} +#word-status .x-status-saved { + padding-left: 25px !important; + background: transparent no-repeat 3px 2px; +} + +/* StatusBar form validation example styles */ + +.x-statusbar .x-status-error { + cursor: pointer; + padding-left: 25px !important; + background: transparent no-repeat 3px 2px; +} +.x-statusbar .x-status-valid { + padding-left: 25px !important; + background: transparent no-repeat 3px 2px; +} +.x-status-error-list { + font: 11px tahoma,arial,verdana,sans-serif; + position: absolute; + z-index: 9999; + border-top: 1px solid; + border-right: 1px solid; + border-bottom: 1px solid; + border-left: 1px solid; + padding: 5px 10px; +} +.x-status-error-list li { + cursor: pointer; + list-style: disc; + margin-left: 10px; +} +.x-status-error-list li a { + text-decoration: none; +} +.x-status-error-list li a:hover { + text-decoration: underline; +} + + +/* *********************************************************** */ +/* *********************************************************** */ +/* *********************************************************** */ + + +/* StatusBar - visual */ + +.x-statusbar .x-status-busy { + background-image: url(../images/loading.gif); +} +.x-statusbar .x-status-text-panel { + border-color: #99bbe8 #fff #fff #99bbe8; +} + +/* StatusBar word processor example styles */ + +#word-status .x-status-text { + color: #777; +} +#word-status .x-status-busy { + background-image: url(../images/saving.gif); +} +#word-status .x-status-saved { + background-image: url(../images/saved.png); +} + +/* StatusBar form validation example styles */ + +.x-statusbar .x-status-error { + color: #C33; + background-image: url(../images/exclamation.gif); +} +.x-statusbar .x-status-valid { + background-image: url(../images/accept.png); +} +.x-status-error-list { + border-color: #C33; +} +.x-status-error-list li a { + color: #15428B; +}.x-treegrid-root-table { + border-right: 1px solid; +} + +.x-treegrid-root-node { + overflow: auto; +} + +.x-treegrid-hd-hidden { + visibility: hidden; + border: 0; + width: 0; +} + +.x-treegrid-col { + border-bottom: 1px solid; + height: 20px; + overflow: hidden; + vertical-align: top; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; +} + +.x-treegrid-text { + padding-left: 4px; + -moz-user-select: none; + -khtml-user-select: none; +} + +.x-treegrid-resizer { + border-left:1px solid; + border-right:1px solid; + position:absolute; + left:0; + top:0; +} + +.x-treegrid-header-inner { + overflow: hidden; +} + +.x-treegrid-root-table, +.x-treegrid-col { + border-color: #ededed; +} + +.x-treegrid-resizer { + border-left-color:#555; + border-right-color:#555; +} \ No newline at end of file diff --git a/modules/organize/vendor/ext/images/default/box/tb-blue.gif b/modules/organize/vendor/ext/images/default/box/tb-blue.gif new file mode 100644 index 0000000..562fecc Binary files /dev/null and b/modules/organize/vendor/ext/images/default/box/tb-blue.gif differ diff --git a/modules/organize/vendor/ext/images/default/button/btn.gif b/modules/organize/vendor/ext/images/default/button/btn.gif new file mode 100644 index 0000000..06b404d Binary files /dev/null and b/modules/organize/vendor/ext/images/default/button/btn.gif differ diff --git a/modules/organize/vendor/ext/images/default/dd/drop-no.gif b/modules/organize/vendor/ext/images/default/dd/drop-no.gif new file mode 100644 index 0000000..08d0833 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/dd/drop-no.gif differ diff --git a/modules/organize/vendor/ext/images/default/dd/drop-yes.gif b/modules/organize/vendor/ext/images/default/dd/drop-yes.gif new file mode 100644 index 0000000..8aacb30 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/dd/drop-yes.gif differ diff --git a/modules/organize/vendor/ext/images/default/form/text-bg.gif b/modules/organize/vendor/ext/images/default/form/text-bg.gif new file mode 100644 index 0000000..4179607 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/form/text-bg.gif differ diff --git a/modules/organize/vendor/ext/images/default/form/trigger.gif b/modules/organize/vendor/ext/images/default/form/trigger.gif new file mode 100644 index 0000000..f6cba37 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/form/trigger.gif differ diff --git a/modules/organize/vendor/ext/images/default/grid/invalid_line.gif b/modules/organize/vendor/ext/images/default/grid/invalid_line.gif new file mode 100644 index 0000000..fb7e0f3 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/grid/invalid_line.gif differ diff --git a/modules/organize/vendor/ext/images/default/grid/loading.gif b/modules/organize/vendor/ext/images/default/grid/loading.gif new file mode 100644 index 0000000..e846e1d Binary files /dev/null and b/modules/organize/vendor/ext/images/default/grid/loading.gif differ diff --git a/modules/organize/vendor/ext/images/default/panel/tool-sprites.gif b/modules/organize/vendor/ext/images/default/panel/tool-sprites.gif new file mode 100644 index 0000000..9a3c5b9 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/panel/tool-sprites.gif differ diff --git a/modules/organize/vendor/ext/images/default/panel/white-top-bottom.gif b/modules/organize/vendor/ext/images/default/panel/white-top-bottom.gif new file mode 100644 index 0000000..fe7dd1c Binary files /dev/null and b/modules/organize/vendor/ext/images/default/panel/white-top-bottom.gif differ diff --git a/modules/organize/vendor/ext/images/default/progress/progress-bg.gif b/modules/organize/vendor/ext/images/default/progress/progress-bg.gif new file mode 100644 index 0000000..1c1abeb Binary files /dev/null and b/modules/organize/vendor/ext/images/default/progress/progress-bg.gif differ diff --git a/modules/organize/vendor/ext/images/default/qtip/bg.gif b/modules/organize/vendor/ext/images/default/qtip/bg.gif new file mode 100644 index 0000000..43488af Binary files /dev/null and b/modules/organize/vendor/ext/images/default/qtip/bg.gif differ diff --git a/modules/organize/vendor/ext/images/default/s.gif b/modules/organize/vendor/ext/images/default/s.gif new file mode 100644 index 0000000..1d11fa9 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/s.gif differ diff --git a/modules/organize/vendor/ext/images/default/shadow-c.png b/modules/organize/vendor/ext/images/default/shadow-c.png new file mode 100644 index 0000000..d435f80 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/shadow-c.png differ diff --git a/modules/organize/vendor/ext/images/default/shadow-lr.png b/modules/organize/vendor/ext/images/default/shadow-lr.png new file mode 100644 index 0000000..bb88b6f Binary files /dev/null and b/modules/organize/vendor/ext/images/default/shadow-lr.png differ diff --git a/modules/organize/vendor/ext/images/default/shadow.png b/modules/organize/vendor/ext/images/default/shadow.png new file mode 100644 index 0000000..75c0eba Binary files /dev/null and b/modules/organize/vendor/ext/images/default/shadow.png differ diff --git a/modules/organize/vendor/ext/images/default/toolbar/bg.gif b/modules/organize/vendor/ext/images/default/toolbar/bg.gif new file mode 100644 index 0000000..0b085bf Binary files /dev/null and b/modules/organize/vendor/ext/images/default/toolbar/bg.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/arrows.gif b/modules/organize/vendor/ext/images/default/tree/arrows.gif new file mode 100644 index 0000000..2683463 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/arrows.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/drop-add.gif b/modules/organize/vendor/ext/images/default/tree/drop-add.gif new file mode 100644 index 0000000..b22cd14 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/drop-add.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/drop-between.gif b/modules/organize/vendor/ext/images/default/tree/drop-between.gif new file mode 100644 index 0000000..5c6c09d Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/drop-between.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/drop-over.gif b/modules/organize/vendor/ext/images/default/tree/drop-over.gif new file mode 100644 index 0000000..30d1ca7 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/drop-over.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/folder-open.gif b/modules/organize/vendor/ext/images/default/tree/folder-open.gif new file mode 100644 index 0000000..56ba737 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/folder-open.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/folder.gif b/modules/organize/vendor/ext/images/default/tree/folder.gif new file mode 100644 index 0000000..20412f7 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/folder.gif differ diff --git a/modules/organize/vendor/ext/images/default/tree/loading.gif b/modules/organize/vendor/ext/images/default/tree/loading.gif new file mode 100644 index 0000000..e846e1d Binary files /dev/null and b/modules/organize/vendor/ext/images/default/tree/loading.gif differ diff --git a/modules/organize/vendor/ext/images/default/window/left-corners.png b/modules/organize/vendor/ext/images/default/window/left-corners.png new file mode 100644 index 0000000..1a51833 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/window/left-corners.png differ diff --git a/modules/organize/vendor/ext/images/default/window/left-right.png b/modules/organize/vendor/ext/images/default/window/left-right.png new file mode 100644 index 0000000..7586ff3 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/window/left-right.png differ diff --git a/modules/organize/vendor/ext/images/default/window/right-corners.png b/modules/organize/vendor/ext/images/default/window/right-corners.png new file mode 100644 index 0000000..e69a3ff Binary files /dev/null and b/modules/organize/vendor/ext/images/default/window/right-corners.png differ diff --git a/modules/organize/vendor/ext/images/default/window/top-bottom.png b/modules/organize/vendor/ext/images/default/window/top-bottom.png new file mode 100644 index 0000000..33779e7 Binary files /dev/null and b/modules/organize/vendor/ext/images/default/window/top-bottom.png differ diff --git a/modules/organize/vendor/ext/images/fam/delete.gif b/modules/organize/vendor/ext/images/fam/delete.gif new file mode 100644 index 0000000..5e2a3b1 Binary files /dev/null and b/modules/organize/vendor/ext/images/fam/delete.gif differ diff --git a/modules/organize/vendor/ext/js/ext-organize-bundle.js b/modules/organize/vendor/ext/js/ext-organize-bundle.js new file mode 100644 index 0000000..bf483bd --- /dev/null +++ b/modules/organize/vendor/ext/js/ext-organize-bundle.js @@ -0,0 +1,2202 @@ + +window.undefined=window.undefined;Ext={version:'3.3.1',versionDetail:{major:3,minor:3,patch:1}};Ext.apply=function(o,c,defaults){if(defaults){Ext.apply(o,defaults);} +if(o&&c&&typeof c=='object'){for(var p in c){o[p]=c[p];}} +return o;};(function(){var idSeed=0,toString=Object.prototype.toString,ua=navigator.userAgent.toLowerCase(),check=function(r){return r.test(ua);},DOC=document,docMode=DOC.documentMode,isStrict=DOC.compatMode=="CSS1Compat",isOpera=check(/opera/),isChrome=check(/\bchrome\b/),isWebKit=check(/webkit/),isSafari=!isChrome&&check(/safari/),isSafari2=isSafari&&check(/applewebkit\/4/),isSafari3=isSafari&&check(/version\/3/),isSafari4=isSafari&&check(/version\/4/),isIE=!isOpera&&check(/msie/),isIE7=isIE&&(check(/msie 7/)||docMode==7),isIE8=isIE&&(check(/msie 8/)&&docMode!=7),isIE6=isIE&&!isIE7&&!isIE8,isGecko=!isWebKit&&check(/gecko/),isGecko2=isGecko&&check(/rv:1\.8/),isGecko3=isGecko&&check(/rv:1\.9/),isBorderBox=isIE&&!isStrict,isWindows=check(/windows|win32/),isMac=check(/macintosh|mac os x/),isAir=check(/adobeair/),isLinux=check(/linux/),isSecure=/^https/i.test(window.location.protocol);if(isIE6){try{DOC.execCommand("BackgroundImageCache",false,true);}catch(e){}} +Ext.apply(Ext,{SSL_SECURE_URL:isSecure&&isIE?'javascript:""':'about:blank',isStrict:isStrict,isSecure:isSecure,isReady:false,enableForcedBoxModel:false,enableGarbageCollector:true,enableListenerCollection:false,enableNestedListenerRemoval:false,USE_NATIVE_JSON:false,applyIf:function(o,c){if(o){for(var p in c){if(!Ext.isDefined(o[p])){o[p]=c[p];}}} +return o;},id:function(el,prefix){el=Ext.getDom(el,true)||{};if(!el.id){el.id=(prefix||"ext-gen")+(++idSeed);} +return el.id;},extend:function(){var io=function(o){for(var m in o){this[m]=o[m];}};var oc=Object.prototype.constructor;return function(sb,sp,overrides){if(typeof sp=='object'){overrides=sp;sp=sb;sb=overrides.constructor!=oc?overrides.constructor:function(){sp.apply(this,arguments);};} +var F=function(){},sbp,spp=sp.prototype;F.prototype=spp;sbp=sb.prototype=new F();sbp.constructor=sb;sb.superclass=spp;if(spp.constructor==oc){spp.constructor=sp;} +sb.override=function(o){Ext.override(sb,o);};sbp.superclass=sbp.supr=(function(){return spp;});sbp.override=io;Ext.override(sb,overrides);sb.extend=function(o){return Ext.extend(sb,o);};return sb;};}(),override:function(origclass,overrides){if(overrides){var p=origclass.prototype;Ext.apply(p,overrides);if(Ext.isIE&&overrides.hasOwnProperty('toString')){p.toString=overrides.toString;}}},namespace:function(){var o,d;Ext.each(arguments,function(v){d=v.split(".");o=window[d[0]]=window[d[0]]||{};Ext.each(d.slice(1),function(v2){o=o[v2]=o[v2]||{};});});return o;},urlEncode:function(o,pre){var empty,buf=[],e=encodeURIComponent;Ext.iterate(o,function(key,item){empty=Ext.isEmpty(item);Ext.each(empty?key:item,function(val){buf.push('&',e(key),'=',(!Ext.isEmpty(val)&&(val!=key||!empty))?(Ext.isDate(val)?Ext.encode(val).replace(/"/g,''):e(val)):'');});});if(!pre){buf.shift();pre='';} +return pre+buf.join('');},urlDecode:function(string,overwrite){if(Ext.isEmpty(string)){return{};} +var obj={},pairs=string.split('&'),d=decodeURIComponent,name,value;Ext.each(pairs,function(pair){pair=pair.split('=');name=d(pair[0]);value=d(pair[1]);obj[name]=overwrite||!obj[name]?value:[].concat(obj[name]).concat(value);});return obj;},urlAppend:function(url,s){if(!Ext.isEmpty(s)){return url+(url.indexOf('?')===-1?'?':'&')+s;} +return url;},toArray:function(){return isIE?function(a,i,j,res){res=[];for(var x=0,len=a.length;x0){return setTimeout(fn,millis);} +fn();return 0;}});Ext.applyIf(String,{format:function(format){var args=Ext.toArray(arguments,1);return format.replace(/\{(\d+)\}/g,function(m,i){return args[i];});}});Ext.applyIf(Array.prototype,{indexOf:function(o,from){var len=this.length;from=from||0;from+=(from<0)?len:0;for(;from0){for(var i=0;i0);if(!locked){locked=true;for(i=0;i=0){s=v.substr(0,t).toLowerCase();if(v.charAt(t+1)==' '){++t;} +headerObj[s]=v.substr(t+1);}});}catch(e){} +return{tId:o.tId,status:isBrokenStatus?204:conn.status,statusText:isBrokenStatus?'No Content':conn.statusText,getResponseHeader:function(header){return headerObj[header.toLowerCase()];},getAllResponseHeaders:function(){return headerStr;},responseText:conn.responseText,responseXML:conn.responseXML,argument:callbackArg};} +function releaseObject(o){if(o.tId){pub.conn[o.tId]=null;} +o.conn=null;o=null;} +function handleTransactionResponse(o,callback,isAbort,isTimeout){if(!callback){releaseObject(o);return;} +var httpStatus,responseObject;try{if(o.conn.status!==undefined&&o.conn.status!=0){httpStatus=o.conn.status;} +else{httpStatus=13030;}} +catch(e){httpStatus=13030;} +if((httpStatus>=200&&httpStatus<300)||(Ext.isIE&&httpStatus==1223)){responseObject=createResponseObject(o,callback.argument);if(callback.success){if(!callback.scope){callback.success(responseObject);} +else{callback.success.apply(callback.scope,[responseObject]);}}} +else{switch(httpStatus){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:responseObject=createExceptionObject(o.tId,callback.argument,(isAbort?isAbort:false),isTimeout);if(callback.failure){if(!callback.scope){callback.failure(responseObject);} +else{callback.failure.apply(callback.scope,[responseObject]);}} +break;default:responseObject=createResponseObject(o,callback.argument);if(callback.failure){if(!callback.scope){callback.failure(responseObject);} +else{callback.failure.apply(callback.scope,[responseObject]);}}}} +releaseObject(o);responseObject=null;} +function checkResponse(o,callback,conn,tId,poll,cbTimeout){if(conn&&conn.readyState==4){clearInterval(poll[tId]);poll[tId]=null;if(cbTimeout){clearTimeout(pub.timeout[tId]);pub.timeout[tId]=null;} +handleTransactionResponse(o,callback);}} +function checkTimeout(o,callback){pub.abort(o,callback,true);} +function handleReadyState(o,callback){callback=callback||{};var conn=o.conn,tId=o.tId,poll=pub.poll,cbTimeout=callback.timeout||null;if(cbTimeout){pub.conn[tId]=conn;pub.timeout[tId]=setTimeout(checkTimeout.createCallback(o,callback),cbTimeout);} +poll[tId]=setInterval(checkResponse.createCallback(o,callback,conn,tId,poll,cbTimeout),pub.pollInterval);} +function asyncRequest(method,uri,callback,postData){var o=getConnectionObject()||null;if(o){o.conn.open(method,uri,true);if(pub.useDefaultXhrHeader){initHeader('X-Requested-With',pub.defaultXhrHeader);} +if(postData&&pub.useDefaultHeader&&(!pub.headers||!pub.headers[CONTENTTYPE])){initHeader(CONTENTTYPE,pub.defaultPostHeader);} +if(pub.defaultHeaders||pub.headers){setHeader(o);} +handleReadyState(o,callback);o.conn.send(postData||null);} +return o;} +function getConnectionObject(){var o;try{if(o=createXhrObject(pub.transactionId)){pub.transactionId++;}}catch(e){}finally{return o;}} +function createXhrObject(transactionId){var http;try{http=new XMLHttpRequest();}catch(e){for(var i=0;i0&&isFinite(tweak)){if(tween.curFrame+tweak>=frames){tweak=frames-(frame+1);} +tween.curFrame+=tweak;}};};EXTLIB.Bezier=new function(){this.getPosition=function(points,t){var n=points.length,tmp=[],c=1-t,i,j;for(i=0;i0&&!Ext.isArray(control[0])){control=[control];}else{} +Ext.fly(el,'_anim').position();DOM.setXY(el,isset(from)?from:DOM.getXY(el));start=me.getAttr('points');if(isset(to)){end=translateValues.call(me,to,start);for(i=0,len=control.length;i0){ra=ra.concat(control);} +ra[ra.length]=end;}else{superclass.setRunAttr.call(this,attr);}}});var translateValues=function(val,start){var pageXY=EXTLIB.Dom.getXY(this.el);return[val[0]-pageXY[0]+start[0],val[1]-pageXY[1]+start[1]];};})();})();(function(){var abs=Math.abs,pi=Math.PI,asin=Math.asin,pow=Math.pow,sin=Math.sin,EXTLIB=Ext.lib;Ext.apply(EXTLIB.Easing,{easeBoth:function(t,b,c,d){return((t/=d/2)<1)?c/2*t*t+b:-c/2*((--t)*(t-2)-1)+b;},easeInStrong:function(t,b,c,d){return c*(t/=d)*t*t*t+b;},easeOutStrong:function(t,b,c,d){return-c*((t=t/d-1)*t*t*t-1)+b;},easeBothStrong:function(t,b,c,d){return((t/=d/2)<1)?c/2*t*t*t*t+b:-c/2*((t-=2)*t*t*t-2)+b;},elasticIn:function(t,b,c,d,a,p){if(t==0||(t/=d)==1){return t==0?b:b+c;} +p=p||(d*.3);var s;if(a>=abs(c)){s=p/(2*pi)*asin(c/a);}else{a=c;s=p/4;} +return-(a*pow(2,10*(t-=1))*sin((t*d-s)*(2*pi)/p))+b;},elasticOut:function(t,b,c,d,a,p){if(t==0||(t/=d)==1){return t==0?b:b+c;} +p=p||(d*.3);var s;if(a>=abs(c)){s=p/(2*pi)*asin(c/a);}else{a=c;s=p/4;} +return a*pow(2,-10*t)*sin((t*d-s)*(2*pi)/p)+c+b;},elasticBoth:function(t,b,c,d,a,p){if(t==0||(t/=d/2)==2){return t==0?b:b+c;} +p=p||(d*(.3*1.5));var s;if(a>=abs(c)){s=p/(2*pi)*asin(c/a);}else{a=c;s=p/4;} +return t<1?-.5*(a*pow(2,10*(t-=1))*sin((t*d-s)*(2*pi)/p))+b:a*pow(2,-10*(t-=1))*sin((t*d-s)*(2*pi)/p)*.5+c+b;},backIn:function(t,b,c,d,s){s=s||1.70158;return c*(t/=d)*t*((s+1)*t-s)+b;},backOut:function(t,b,c,d,s){if(!s){s=1.70158;} +return c*((t=t/d-1)*t*((s+1)*t+s)+1)+b;},backBoth:function(t,b,c,d,s){s=s||1.70158;return((t/=d/2)<1)?c/2*(t*t*(((s*=(1.525))+1)*t-s))+b:c/2*((t-=2)*t*(((s*=(1.525))+1)*t+s)+2)+b;},bounceIn:function(t,b,c,d){return c-EXTLIB.Easing.bounceOut(d-t,0,c,d)+b;},bounceOut:function(t,b,c,d){if((t/=d)<(1/2.75)){return c*(7.5625*t*t)+b;}else if(t<(2/2.75)){return c*(7.5625*(t-=(1.5/2.75))*t+.75)+b;}else if(t<(2.5/2.75)){return c*(7.5625*(t-=(2.25/2.75))*t+.9375)+b;} +return c*(7.5625*(t-=(2.625/2.75))*t+.984375)+b;},bounceBoth:function(t,b,c,d){return(t0;},suspendEvents:function(queueSuspended){this.eventsSuspended=TRUE;if(queueSuspended&&!this.eventQueue){this.eventQueue=[];}},resumeEvents:function(){var me=this,queued=me.eventQueue||[];me.eventsSuspended=FALSE;delete me.eventQueue;EACH(queued,function(e){me.fireEvent.apply(me,e);});}};var OBSERVABLE=EXTUTIL.Observable.prototype;OBSERVABLE.on=OBSERVABLE.addListener;OBSERVABLE.un=OBSERVABLE.removeListener;EXTUTIL.Observable.releaseCapture=function(o){o.fireEvent=OBSERVABLE.fireEvent;};function createTargeted(h,o,scope){return function(){if(o.target==arguments[0]){h.apply(scope,Array.prototype.slice.call(arguments,0));}};};function createBuffered(h,o,l,scope){l.task=new EXTUTIL.DelayedTask();return function(){l.task.delay(o.buffer,h,scope,Array.prototype.slice.call(arguments,0));};};function createSingle(h,e,fn,scope){return function(){e.removeListener(fn,scope);return h.apply(scope,arguments);};};function createDelayed(h,o,l,scope){return function(){var task=new EXTUTIL.DelayedTask(),args=Array.prototype.slice.call(arguments,0);if(!l.tasks){l.tasks=[];} +l.tasks.push(task);task.delay(o.delay||10,function(){l.tasks.remove(task);h.apply(scope,args);},scope);};};EXTUTIL.Event=function(obj,name){this.name=name;this.obj=obj;this.listeners=[];};EXTUTIL.Event.prototype={addListener:function(fn,scope,options){var me=this,l;scope=scope||me.obj;if(!me.isListening(fn,scope)){l=me.createListener(fn,scope,options);if(me.firing){me.listeners=me.listeners.slice(0);} +me.listeners.push(l);}},createListener:function(fn,scope,o){o=o||{};scope=scope||this.obj;var l={fn:fn,scope:scope,options:o},h=fn;if(o.target){h=createTargeted(h,o,scope);} +if(o.delay){h=createDelayed(h,o,l,scope);} +if(o.single){h=createSingle(h,this,fn,scope);} +if(o.buffer){h=createBuffered(h,o,l,scope);} +l.fireFn=h;return l;},findListener:function(fn,scope){var list=this.listeners,i=list.length,l;scope=scope||this.obj;while(i--){l=list[i];if(l){if(l.fn==fn&&l.scope==scope){return i;}}} +return-1;},isListening:function(fn,scope){return this.findListener(fn,scope)!=-1;},removeListener:function(fn,scope){var index,l,k,me=this,ret=FALSE;if((index=me.findListener(fn,scope))!=-1){if(me.firing){me.listeners=me.listeners.slice(0);} +l=me.listeners[index];if(l.task){l.task.cancel();delete l.task;} +k=l.tasks&&l.tasks.length;if(k){while(k--){l.tasks[k].cancel();} +delete l.tasks;} +me.listeners.splice(index,1);ret=TRUE;} +return ret;},clearListeners:function(){var me=this,l=me.listeners,i=l.length;while(i--){me.removeListener(l[i].fn,l[i].scope);}},fire:function(){var me=this,listeners=me.listeners,len=listeners.length,i=0,l;if(len>0){me.firing=TRUE;var args=Array.prototype.slice.call(arguments,0);for(;i',te='',tbs=ts+'',tbe=''+te,trs=tbs+'',tre=''+tbe;function doInsert(el,o,returnElement,pos,sibling,append){var newNode=pub.insertHtml(pos,Ext.getDom(el),createHtml(o));return returnElement?Ext.get(newNode,true):newNode;} +function createHtml(o){var b='',attr,val,key,cn;if(typeof o=="string"){b=o;}else if(Ext.isArray(o)){for(var i=0;i';}} +return b;} +function ieTable(depth,s,h,e){tempTableEl.innerHTML=[s,h,e].join('');var i=-1,el=tempTableEl,ns;while(++i "'+where+'"';},insertBefore:function(el,o,returnElement){return doInsert(el,o,returnElement,beforebegin);},insertAfter:function(el,o,returnElement){return doInsert(el,o,returnElement,afterend,'nextSibling');},insertFirst:function(el,o,returnElement){return doInsert(el,o,returnElement,afterbegin,'firstChild');},append:function(el,o,returnElement){return doInsert(el,o,returnElement,beforeend,'',true);},overwrite:function(el,o,returnElement){el=Ext.getDom(el);el.innerHTML=createHtml(o);return returnElement?Ext.get(el.firstChild):el.firstChild;},createHtml:createHtml};return pub;}();Ext.Template=function(html){var me=this,a=arguments,buf=[],v;if(Ext.isArray(html)){html=html.join("");}else if(a.length>1){for(var i=0,len=a.length;i+~]\s?|\s|$)/,tagTokenRe=/^(#)?([\w-\*]+)/,nthRe=/(\d*)n\+?(\d*)/,nthRe2=/\D/,isIE=window.ActiveXObject?true:false,key=30803;eval("var batch = 30803;");function child(parent,index){var i=0,n=parent.firstChild;while(n){if(n.nodeType==1){if(++i==index){return n;}} +n=n.nextSibling;} +return null;} +function next(n){while((n=n.nextSibling)&&n.nodeType!=1);return n;} +function prev(n){while((n=n.previousSibling)&&n.nodeType!=1);return n;} +function children(parent){var n=parent.firstChild,nodeIndex=-1,nextNode;while(n){nextNode=n.nextSibling;if(n.nodeType==3&&!nonSpace.test(n.nodeValue)){parent.removeChild(n);}else{n.nodeIndex=++nodeIndex;} +n=nextNode;} +return this;} +function byClassName(nodeSet,cls){if(!cls){return nodeSet;} +var result=[],ri=-1;for(var i=0,ci;ci=nodeSet[i];i++){if((' '+ci.className+' ').indexOf(cls)!=-1){result[++ri]=ci;}} +return result;};function attrValue(n,attr){if(!n.tagName&&typeof n.length!="undefined"){n=n[0];} +if(!n){return null;} +if(attr=="for"){return n.htmlFor;} +if(attr=="class"||attr=="className"){return n.className;} +return n.getAttribute(attr)||n[attr];};function getNodes(ns,mode,tagName){var result=[],ri=-1,cs;if(!ns){return result;} +tagName=tagName||"*";if(typeof ns.getElementsByTagName!="undefined"){ns=[ns];} +if(!mode){for(var i=0,ni;ni=ns[i];i++){cs=ni.getElementsByTagName(tagName);for(var j=0,ci;ci=cs[j];j++){result[++ri]=ci;}}}else if(mode=="/"||mode==">"){var utag=tagName.toUpperCase();for(var i=0,ni,cn;ni=ns[i];i++){cn=ni.childNodes;for(var j=0,cj;cj=cn[j];j++){if(cj.nodeName==utag||cj.nodeName==tagName||tagName=='*'){result[++ri]=cj;}}}}else if(mode=="+"){var utag=tagName.toUpperCase();for(var i=0,n;n=ns[i];i++){while((n=n.nextSibling)&&n.nodeType!=1);if(n&&(n.nodeName==utag||n.nodeName==tagName||tagName=='*')){result[++ri]=n;}}}else if(mode=="~"){var utag=tagName.toUpperCase();for(var i=0,n;n=ns[i];i++){while((n=n.nextSibling)){if(n.nodeName==utag||n.nodeName==tagName||tagName=='*'){result[++ri]=n;}}}} +return result;} +function concat(a,b){if(b.slice){return a.concat(b);} +for(var i=0,l=b.length;i1){return nodup(results);} +return results;},isXml:function(el){var docEl=(el?el.ownerDocument||el:0).documentElement;return docEl?docEl.nodeName!=="HTML":false;},select:document.querySelectorAll?function(path,root,type){root=root||document;if(!Ext.DomQuery.isXml(root)){try{var cs=root.querySelectorAll(path);return Ext.toArray(cs);} +catch(ex){}} +return Ext.DomQuery.jsSelect.call(this,path,root,type);}:function(path,root,type){return Ext.DomQuery.jsSelect.call(this,path,root,type);},selectNode:function(path,root){return Ext.DomQuery.select(path,root)[0];},selectValue:function(path,root,defaultValue){path=path.replace(trimRe,"");if(!valueCache[path]){valueCache[path]=Ext.DomQuery.compile(path,"select");} +var n=valueCache[path](root),v;n=n[0]?n[0]:n;if(typeof n.normalize=='function')n.normalize();v=(n&&n.firstChild?n.firstChild.nodeValue:null);return((v===null||v===undefined||v==='')?defaultValue:v);},selectNumber:function(path,root,defaultValue){var v=Ext.DomQuery.selectValue(path,root,defaultValue||0);return parseFloat(v);},is:function(el,ss){if(typeof el=="string"){el=document.getElementById(el);} +var isArray=Ext.isArray(el),result=Ext.DomQuery.filter(isArray?el:[el],ss);return isArray?(result.length==el.length):(result.length>0);},filter:function(els,ss,nonMatches){ss=ss.replace(trimRe,"");if(!simpleCache[ss]){simpleCache[ss]=Ext.DomQuery.compile(ss,"simple");} +var result=simpleCache[ss](els);return nonMatches?quickDiff(result,els):result;},matchers:[{re:/^\.([\w-]+)/,select:'n = byClassName(n, " {1} ");'},{re:/^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,select:'n = byPseudo(n, "{1}", "{2}");'},{re:/^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,select:'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'},{re:/^#([\w-]+)/,select:'n = byId(n, "{1}");'},{re:/^@([\w-]+)/,select:'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'}],operators:{"=":function(a,v){return a==v;},"!=":function(a,v){return a!=v;},"^=":function(a,v){return a&&a.substr(0,v.length)==v;},"$=":function(a,v){return a&&a.substr(a.length-v.length)==v;},"*=":function(a,v){return a&&a.indexOf(v)!==-1;},"%=":function(a,v){return(a%v)==0;},"|=":function(a,v){return a&&(a==v||a.substr(0,v.length+1)==v+'-');},"~=":function(a,v){return a&&(' '+a+' ').indexOf(' '+v+' ')!=-1;}},pseudos:{"first-child":function(c){var r=[],ri=-1,n;for(var i=0,ci;ci=n=c[i];i++){while((n=n.previousSibling)&&n.nodeType!=1);if(!n){r[++ri]=ci;}} +return r;},"last-child":function(c){var r=[],ri=-1,n;for(var i=0,ci;ci=n=c[i];i++){while((n=n.nextSibling)&&n.nodeType!=1);if(!n){r[++ri]=ci;}} +return r;},"nth-child":function(c,a){var r=[],ri=-1,m=nthRe.exec(a=="even"&&"2n"||a=="odd"&&"2n+1"||!nthRe2.test(a)&&"n+"+a||a),f=(m[1]||1)-0,l=m[2]-0;for(var i=0,n;n=c[i];i++){var pn=n.parentNode;if(batch!=pn._batch){var j=0;for(var cn=pn.firstChild;cn;cn=cn.nextSibling){if(cn.nodeType==1){cn.nodeIndex=++j;}} +pn._batch=batch;} +if(f==1){if(l==0||n.nodeIndex==l){r[++ri]=n;}}else if((n.nodeIndex+l)%f==0){r[++ri]=n;}} +return r;},"only-child":function(c){var r=[],ri=-1;;for(var i=0,ci;ci=c[i];i++){if(!prev(ci)&&!next(ci)){r[++ri]=ci;}} +return r;},"empty":function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var cns=ci.childNodes,j=0,cn,empty=true;while(cn=cns[j]){++j;if(cn.nodeType==1||cn.nodeType==3){empty=false;break;}} +if(empty){r[++ri]=ci;}} +return r;},"contains":function(c,v){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if((ci.textContent||ci.innerText||'').indexOf(v)!=-1){r[++ri]=ci;}} +return r;},"nodeValue":function(c,v){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(ci.firstChild&&ci.firstChild.nodeValue==v){r[++ri]=ci;}} +return r;},"checked":function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(ci.checked==true){r[++ri]=ci;}} +return r;},"not":function(c,ss){return Ext.DomQuery.filter(c,ss,true);},"any":function(c,selectors){var ss=selectors.split('|'),r=[],ri=-1,s;for(var i=0,ci;ci=c[i];i++){for(var j=0;s=ss[j];j++){if(Ext.DomQuery.is(ci,s)){r[++ri]=ci;break;}}} +return r;},"odd":function(c){return this["nth-child"](c,"odd");},"even":function(c){return this["nth-child"](c,"even");},"nth":function(c,a){return c[a-1]||[];},"first":function(c){return c[0]||[];},"last":function(c){return c[c.length-1]||[];},"has":function(c,ss){var s=Ext.DomQuery.select,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(s(ss,ci).length>0){r[++ri]=ci;}} +return r;},"next":function(c,ss){var is=Ext.DomQuery.is,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var n=next(ci);if(n&&is(n,ss)){r[++ri]=ci;}} +return r;},"prev":function(c,ss){var is=Ext.DomQuery.is,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var n=prev(ci);if(n&&is(n,ss)){r[++ri]=ci;}} +return r;}}};}();Ext.query=Ext.DomQuery.select;Ext.util.DelayedTask=function(fn,scope,args){var me=this,id,call=function(){clearInterval(id);id=null;fn.apply(scope,args||[]);};me.delay=function(delay,newFn,newScope,newArgs){me.cancel();fn=newFn||fn;scope=newScope||scope;args=newArgs||args;id=setInterval(call,delay);};me.cancel=function(){if(id){clearInterval(id);id=null;}};};(function(){var DOC=document;Ext.Element=function(element,forceNew){var dom=typeof element=="string"?DOC.getElementById(element):element,id;if(!dom)return null;id=dom.id;if(!forceNew&&id&&Ext.elCache[id]){return Ext.elCache[id].el;} +this.dom=dom;this.id=id||Ext.id(dom);};var DH=Ext.DomHelper,El=Ext.Element,EC=Ext.elCache;El.prototype={set:function(o,useSet){var el=this.dom,attr,val,useSet=(useSet!==false)&&!!el.setAttribute;for(attr in o){if(o.hasOwnProperty(attr)){val=o[attr];if(attr=='style'){DH.applyStyles(el,val);}else if(attr=='cls'){el.className=val;}else if(useSet){el.setAttribute(attr,val);}else{el[attr]=val;}}} +return this;},defaultUnit:"px",is:function(simpleSelector){return Ext.DomQuery.is(this.dom,simpleSelector);},focus:function(defer,dom){var me=this,dom=dom||me.dom;try{if(Number(defer)){me.focus.defer(defer,null,[null,dom]);}else{dom.focus();}}catch(e){} +return me;},blur:function(){try{this.dom.blur();}catch(e){} +return this;},getValue:function(asNumber){var val=this.dom.value;return asNumber?parseInt(val,10):val;},addListener:function(eventName,fn,scope,options){Ext.EventManager.on(this.dom,eventName,fn,scope||this,options);return this;},removeListener:function(eventName,fn,scope){Ext.EventManager.removeListener(this.dom,eventName,fn,scope||this);return this;},removeAllListeners:function(){Ext.EventManager.removeAll(this.dom);return this;},purgeAllListeners:function(){Ext.EventManager.purgeElement(this,true);return this;},addUnits:function(size){if(size===""||size=="auto"||size===undefined){size=size||'';}else if(!isNaN(size)||!unitPattern.test(size)){size=size+(this.defaultUnit||'px');} +return size;},load:function(url,params,cb){Ext.Ajax.request(Ext.apply({params:params,url:url.url||url,callback:cb,el:this.dom,indicatorText:url.indicatorText||''},Ext.isObject(url)?url:{}));return this;},isBorderBox:function(){return Ext.isBorderBox||Ext.isForcedBorderBox||noBoxAdjust[(this.dom.tagName||"").toLowerCase()];},remove:function(){var me=this,dom=me.dom;if(dom){delete me.dom;Ext.removeNode(dom);}},hover:function(overFn,outFn,scope,options){var me=this;me.on('mouseenter',overFn,scope||me.dom,options);me.on('mouseleave',outFn,scope||me.dom,options);return me;},contains:function(el){return!el?false:Ext.lib.Dom.isAncestor(this.dom,el.dom?el.dom:el);},getAttributeNS:function(ns,name){return this.getAttribute(name,ns);},getAttribute:Ext.isIE?function(name,ns){var d=this.dom,type=typeof d[ns+":"+name];if(['undefined','unknown'].indexOf(type)==-1){return d[ns+":"+name];} +return d[name];}:function(name,ns){var d=this.dom;return d.getAttributeNS(ns,name)||d.getAttribute(ns+":"+name)||d.getAttribute(name)||d[name];},update:function(html){if(this.dom){this.dom.innerHTML=html;} +return this;}};var ep=El.prototype;El.addMethods=function(o){Ext.apply(ep,o);};ep.on=ep.addListener;ep.un=ep.removeListener;ep.autoBoxAdjust=true;var unitPattern=/\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,docEl;El.get=function(el){var ex,elm,id;if(!el){return null;} +if(typeof el=="string"){if(!(elm=DOC.getElementById(el))){return null;} +if(EC[el]&&EC[el].el){ex=EC[el].el;ex.dom=elm;}else{ex=El.addToCache(new El(elm));} +return ex;}else if(el.tagName){if(!(id=el.id)){id=Ext.id(el);} +if(EC[id]&&EC[id].el){ex=EC[id].el;ex.dom=el;}else{ex=El.addToCache(new El(el));} +return ex;}else if(el instanceof El){if(el!=docEl){if(Ext.isIE&&(el.id==undefined||el.id=='')){el.dom=el.dom;}else{el.dom=DOC.getElementById(el.id)||el.dom;}} +return el;}else if(el.isComposite){return el;}else if(Ext.isArray(el)){return El.select(el);}else if(el==DOC){if(!docEl){var f=function(){};f.prototype=El.prototype;docEl=new f();docEl.dom=DOC;} +return docEl;} +return null;};El.addToCache=function(el,id){id=id||el.id;EC[id]={el:el,data:{},events:{}};return el;};El.data=function(el,key,value){el=El.get(el);if(!el){return null;} +var c=EC[el.id].data;if(arguments.length==2){return c[key];}else{return(c[key]=value);}};function garbageCollect(){if(!Ext.enableGarbageCollector){clearInterval(El.collectorThreadId);}else{var eid,el,d,o;for(eid in EC){o=EC[eid];if(o.skipGC){continue;} +el=o.el;d=el.dom;if(!d||!d.parentNode||(!d.offsetParent&&!DOC.getElementById(eid))){if(Ext.enableListenerCollection){Ext.EventManager.removeAll(d);} +delete EC[eid];}} +if(Ext.isIE){var t={};for(eid in EC){t[eid]=EC[eid];} +EC=Ext.elCache=t;}}} +El.collectorThreadId=setInterval(garbageCollect,30000);var flyFn=function(){};flyFn.prototype=El.prototype;El.Flyweight=function(dom){this.dom=dom;};El.Flyweight.prototype=new flyFn();El.Flyweight.prototype.isFlyweight=true;El._flyweights={};El.fly=function(el,named){var ret=null;named=named||'_global';if(el=Ext.getDom(el)){(El._flyweights[named]=El._flyweights[named]||new El.Flyweight()).dom=el;ret=El._flyweights[named];} +return ret;};Ext.get=El.get;Ext.fly=El.fly;var noBoxAdjust=Ext.isStrict?{select:1}:{input:1,select:1,textarea:1};if(Ext.isIE||Ext.isGecko){noBoxAdjust['button']=1;}})();Ext.Element.addMethods(function(){var PARENTNODE='parentNode',NEXTSIBLING='nextSibling',PREVIOUSSIBLING='previousSibling',DQ=Ext.DomQuery,GET=Ext.get;return{findParent:function(simpleSelector,maxDepth,returnEl){var p=this.dom,b=document.body,depth=0,stopEl;if(Ext.isGecko&&Object.prototype.toString.call(p)=='[object XULElement]'){return null;} +maxDepth=maxDepth||50;if(isNaN(maxDepth)){stopEl=Ext.getDom(maxDepth);maxDepth=Number.MAX_VALUE;} +while(p&&p.nodeType==1&&depth "+selector,this.dom);return returnDom?n:GET(n);},parent:function(selector,returnDom){return this.matchNode(PARENTNODE,PARENTNODE,selector,returnDom);},next:function(selector,returnDom){return this.matchNode(NEXTSIBLING,NEXTSIBLING,selector,returnDom);},prev:function(selector,returnDom){return this.matchNode(PREVIOUSSIBLING,PREVIOUSSIBLING,selector,returnDom);},first:function(selector,returnDom){return this.matchNode(NEXTSIBLING,'firstChild',selector,returnDom);},last:function(selector,returnDom){return this.matchNode(PREVIOUSSIBLING,'lastChild',selector,returnDom);},matchNode:function(dir,start,selector,returnDom){var n=this.dom[start];while(n){if(n.nodeType==1&&(!selector||DQ.is(n,selector))){return!returnDom?GET(n):n;} +n=n[dir];} +return null;}};}());Ext.Element.addMethods(function(){var GETDOM=Ext.getDom,GET=Ext.get,DH=Ext.DomHelper;return{appendChild:function(el){return GET(el).appendTo(this);},appendTo:function(el){GETDOM(el).appendChild(this.dom);return this;},insertBefore:function(el){(el=GETDOM(el)).parentNode.insertBefore(this.dom,el);return this;},insertAfter:function(el){(el=GETDOM(el)).parentNode.insertBefore(this.dom,el.nextSibling);return this;},insertFirst:function(el,returnDom){el=el||{};if(el.nodeType||el.dom||typeof el=='string'){el=GETDOM(el);this.dom.insertBefore(el,this.dom.firstChild);return!returnDom?GET(el):el;}else{return this.createChild(el,this.dom.firstChild,returnDom);}},replace:function(el){el=GET(el);this.insertBefore(el);el.remove();return this;},replaceWith:function(el){var me=this;if(el.nodeType||el.dom||typeof el=='string'){el=GETDOM(el);me.dom.parentNode.insertBefore(el,me.dom);}else{el=DH.insertBefore(me.dom,el);} +delete Ext.elCache[me.id];Ext.removeNode(me.dom);me.id=Ext.id(me.dom=el);Ext.Element.addToCache(me.isFlyweight?new Ext.Element(me.dom):me);return me;},createChild:function(config,insertBefore,returnDom){config=config||{tag:'div'};return insertBefore?DH.insertBefore(insertBefore,config,returnDom!==true):DH[!this.dom.firstChild?'overwrite':'append'](this.dom,config,returnDom!==true);},wrap:function(config,returnDom){var newEl=DH.insertBefore(this.dom,config||{tag:"div"},!returnDom);newEl.dom?newEl.dom.appendChild(this.dom):newEl.appendChild(this.dom);return newEl;},insertHtml:function(where,html,returnEl){var el=DH.insertHtml(where,this.dom,html);return returnEl?Ext.get(el):el;}};}());Ext.Element.addMethods(function(){var supports=Ext.supports,propCache={},camelRe=/(-[a-z])/gi,view=document.defaultView,opacityRe=/alpha\(opacity=(.*)\)/i,trimRe=/^\s+|\s+$/g,EL=Ext.Element,spacesRe=/\s+/,wordsRe=/\w/g,PADDING="padding",MARGIN="margin",BORDER="border",LEFT="-left",RIGHT="-right",TOP="-top",BOTTOM="-bottom",WIDTH="-width",MATH=Math,HIDDEN='hidden',ISCLIPPED='isClipped',OVERFLOW='overflow',OVERFLOWX='overflow-x',OVERFLOWY='overflow-y',ORIGINALCLIP='originalClip',borders={l:BORDER+LEFT+WIDTH,r:BORDER+RIGHT+WIDTH,t:BORDER+TOP+WIDTH,b:BORDER+BOTTOM+WIDTH},paddings={l:PADDING+LEFT,r:PADDING+RIGHT,t:PADDING+TOP,b:PADDING+BOTTOM},margins={l:MARGIN+LEFT,r:MARGIN+RIGHT,t:MARGIN+TOP,b:MARGIN+BOTTOM},data=Ext.Element.data;function camelFn(m,a){return a.charAt(1).toUpperCase();} +function chkCache(prop){return propCache[prop]||(propCache[prop]=prop=='float'?(supports.cssFloat?'cssFloat':'styleFloat'):prop.replace(camelRe,camelFn));} +return{adjustWidth:function(width){var me=this;var isNum=(typeof width=="number");if(isNum&&me.autoBoxAdjust&&!me.isBorderBox()){width-=(me.getBorderWidth("lr")+me.getPadding("lr"));} +return(isNum&&width<0)?0:width;},adjustHeight:function(height){var me=this;var isNum=(typeof height=="number");if(isNum&&me.autoBoxAdjust&&!me.isBorderBox()){height-=(me.getBorderWidth("tb")+me.getPadding("tb"));} +return(isNum&&height<0)?0:height;},addClass:function(className){var me=this,i,len,v,cls=[];if(!Ext.isArray(className)){if(typeof className=='string'&&!this.hasClass(className)){me.dom.className+=" "+className;}} +else{for(i=0,len=className.length;i5?color.toLowerCase():defaultValue);},setStyle:function(prop,value){var tmp,style;if(typeof prop!='object'){tmp={};tmp[prop]=value;prop=tmp;} +for(style in prop){value=prop[style];style=='opacity'?this.setOpacity(value):this.dom.style[chkCache(style)]=value;} +return this;},setOpacity:function(opacity,animate){var me=this,s=me.dom.style;if(!animate||!me.anim){if(Ext.isIE){var opac=opacity<1?'alpha(opacity='+opacity*100+')':'',val=s.filter.replace(opacityRe,'').replace(trimRe,'');s.zoom=1;s.filter=val+(val.length>0?' ':'')+opac;}else{s.opacity=opacity;}}else{me.anim({opacity:{to:opacity}},me.preanim(arguments,1),null,.35,'easeIn');} +return me;},clearOpacity:function(){var style=this.dom.style;if(Ext.isIE){if(!Ext.isEmpty(style.filter)){style.filter=style.filter.replace(opacityRe,'').replace(trimRe,'');}}else{style.opacity=style['-moz-opacity']=style['-khtml-opacity']='';} +return this;},getHeight:function(contentHeight){var me=this,dom=me.dom,hidden=Ext.isIE&&me.isStyle('display','none'),h=MATH.max(dom.offsetHeight,hidden?0:dom.clientHeight)||0;h=!contentHeight?h:h-me.getBorderWidth("tb")-me.getPadding("tb");return h<0?0:h;},getWidth:function(contentWidth){var me=this,dom=me.dom,hidden=Ext.isIE&&me.isStyle('display','none'),w=MATH.max(dom.offsetWidth,hidden?0:dom.clientWidth)||0;w=!contentWidth?w:w-me.getBorderWidth("lr")-me.getPadding("lr");return w<0?0:w;},setWidth:function(width,animate){var me=this;width=me.adjustWidth(width);!animate||!me.anim?me.dom.style.width=me.addUnits(width):me.anim({width:{to:width}},me.preanim(arguments,1));return me;},setHeight:function(height,animate){var me=this;height=me.adjustHeight(height);!animate||!me.anim?me.dom.style.height=me.addUnits(height):me.anim({height:{to:height}},me.preanim(arguments,1));return me;},getBorderWidth:function(side){return this.addStyles(side,borders);},getPadding:function(side){return this.addStyles(side,paddings);},clip:function(){var me=this,dom=me.dom;if(!data(dom,ISCLIPPED)){data(dom,ISCLIPPED,true);data(dom,ORIGINALCLIP,{o:me.getStyle(OVERFLOW),x:me.getStyle(OVERFLOWX),y:me.getStyle(OVERFLOWY)});me.setStyle(OVERFLOW,HIDDEN);me.setStyle(OVERFLOWX,HIDDEN);me.setStyle(OVERFLOWY,HIDDEN);} +return me;},unclip:function(){var me=this,dom=me.dom;if(data(dom,ISCLIPPED)){data(dom,ISCLIPPED,false);var o=data(dom,ORIGINALCLIP);if(o.o){me.setStyle(OVERFLOW,o.o);} +if(o.x){me.setStyle(OVERFLOWX,o.x);} +if(o.y){me.setStyle(OVERFLOWY,o.y);}} +return me;},addStyles:function(sides,styles){var ttlSize=0,sidesArr=sides.match(wordsRe),side,size,i,len=sidesArr.length;for(i=0;idom.clientHeight||dom.scrollWidth>dom.clientWidth;},scrollTo:function(side,value){this.dom["scroll"+(/top/i.test(side)?"Top":"Left")]=value;return this;},getScroll:function(){var d=this.dom,doc=document,body=doc.body,docElement=doc.documentElement,l,t,ret;if(d==doc||d==body){if(Ext.isIE&&Ext.isStrict){l=docElement.scrollLeft;t=docElement.scrollTop;}else{l=window.pageXOffset;t=window.pageYOffset;} +ret={left:l||(body?body.scrollLeft:0),top:t||(body?body.scrollTop:0)};}else{ret={left:d.scrollLeft,top:d.scrollTop};} +return ret;}});Ext.Element.VISIBILITY=1;Ext.Element.DISPLAY=2;Ext.Element.OFFSETS=3;Ext.Element.ASCLASS=4;Ext.Element.visibilityCls='x-hide-nosize';Ext.Element.addMethods(function(){var El=Ext.Element,OPACITY="opacity",VISIBILITY="visibility",DISPLAY="display",HIDDEN="hidden",OFFSETS="offsets",ASCLASS="asclass",NONE="none",NOSIZE='nosize',ORIGINALDISPLAY='originalDisplay',VISMODE='visibilityMode',ISVISIBLE='isVisible',data=El.data,getDisplay=function(dom){var d=data(dom,ORIGINALDISPLAY);if(d===undefined){data(dom,ORIGINALDISPLAY,d='');} +return d;},getVisMode=function(dom){var m=data(dom,VISMODE);if(m===undefined){data(dom,VISMODE,m=1);} +return m;};return{originalDisplay:"",visibilityMode:1,setVisibilityMode:function(visMode){data(this.dom,VISMODE,visMode);return this;},animate:function(args,duration,onComplete,easing,animType){this.anim(args,{duration:duration,callback:onComplete,easing:easing},animType);return this;},anim:function(args,opt,animType,defaultDur,defaultEase,cb){animType=animType||'run';opt=opt||{};var me=this,anim=Ext.lib.Anim[animType](me.dom,args,(opt.duration||defaultDur)||.35,(opt.easing||defaultEase)||'easeOut',function(){if(cb)cb.call(me);if(opt.callback)opt.callback.call(opt.scope||me,me,opt);},me);opt.anim=anim;return anim;},preanim:function(a,i){return!a[i]?false:(typeof a[i]=='object'?a[i]:{duration:a[i+1],callback:a[i+2],easing:a[i+3]});},isVisible:function(){var me=this,dom=me.dom,visible=data(dom,ISVISIBLE);if(typeof visible=='boolean'){return visible;} +visible=!me.isStyle(VISIBILITY,HIDDEN)&&!me.isStyle(DISPLAY,NONE)&&!((getVisMode(dom)==El.ASCLASS)&&me.hasClass(me.visibilityCls||El.visibilityCls));data(dom,ISVISIBLE,visible);return visible;},setVisible:function(visible,animate){var me=this,isDisplay,isVisibility,isOffsets,isNosize,dom=me.dom,visMode=getVisMode(dom);if(typeof animate=='string'){switch(animate){case DISPLAY:visMode=El.DISPLAY;break;case VISIBILITY:visMode=El.VISIBILITY;break;case OFFSETS:visMode=El.OFFSETS;break;case NOSIZE:case ASCLASS:visMode=El.ASCLASS;break;} +me.setVisibilityMode(visMode);animate=false;} +if(!animate||!me.anim){if(visMode==El.ASCLASS){me[visible?'removeClass':'addClass'](me.visibilityCls||El.visibilityCls);}else if(visMode==El.DISPLAY){return me.setDisplayed(visible);}else if(visMode==El.OFFSETS){if(!visible){me.hideModeStyles={position:me.getStyle('position'),top:me.getStyle('top'),left:me.getStyle('left')};me.applyStyles({position:'absolute',top:'-10000px',left:'-10000px'});}else{me.applyStyles(me.hideModeStyles||{position:'',top:'',left:''});delete me.hideModeStyles;}}else{me.fixDisplay();dom.style.visibility=visible?"visible":HIDDEN;}}else{if(visible){me.setOpacity(.01);me.setVisible(true);} +me.anim({opacity:{to:(visible?1:0)}},me.preanim(arguments,1),null,.35,'easeIn',function(){visible||me.setVisible(false).setOpacity(1);});} +data(dom,ISVISIBLE,visible);return me;},hasMetrics:function(){var dom=this.dom;return this.isVisible()||(getVisMode(dom)==El.VISIBILITY);},toggle:function(animate){var me=this;me.setVisible(!me.isVisible(),me.preanim(arguments,0));return me;},setDisplayed:function(value){if(typeof value=="boolean"){value=value?getDisplay(this.dom):NONE;} +this.setStyle(DISPLAY,value);return this;},fixDisplay:function(){var me=this;if(me.isStyle(DISPLAY,NONE)){me.setStyle(VISIBILITY,HIDDEN);me.setStyle(DISPLAY,getDisplay(this.dom));if(me.isStyle(DISPLAY,NONE)){me.setStyle(DISPLAY,"block");}}},hide:function(animate){if(typeof animate=='string'){this.setVisible(false,animate);return this;} +this.setVisible(false,this.preanim(arguments,0));return this;},show:function(animate){if(typeof animate=='string'){this.setVisible(true,animate);return this;} +this.setVisible(true,this.preanim(arguments,0));return this;}};}());(function(){var NULL=null,UNDEFINED=undefined,TRUE=true,FALSE=false,SETX="setX",SETY="setY",SETXY="setXY",LEFT="left",BOTTOM="bottom",TOP="top",RIGHT="right",HEIGHT="height",WIDTH="width",POINTS="points",HIDDEN="hidden",ABSOLUTE="absolute",VISIBLE="visible",MOTION="motion",POSITION="position",EASEOUT="easeOut",flyEl=new Ext.Element.Flyweight(),queues={},getObject=function(o){return o||{};},fly=function(dom){flyEl.dom=dom;flyEl.id=Ext.id(dom);return flyEl;},getQueue=function(id){if(!queues[id]){queues[id]=[];} +return queues[id];},setQueue=function(id,value){queues[id]=value;};Ext.enableFx=TRUE;Ext.Fx={switchStatements:function(key,fn,argHash){return fn.apply(this,argHash[key]);},slideIn:function(anchor,o){o=getObject(o);var me=this,dom=me.dom,st=dom.style,xy,r,b,wrap,after,st,args,pt,bw,bh;anchor=anchor||"t";me.queueFx(o,function(){xy=fly(dom).getXY();fly(dom).fixDisplay();r=fly(dom).getFxRestore();b={x:xy[0],y:xy[1],0:xy[0],1:xy[1],width:dom.offsetWidth,height:dom.offsetHeight};b.right=b.x+b.width;b.bottom=b.y+b.height;fly(dom).setWidth(b.width).setHeight(b.height);wrap=fly(dom).fxWrap(r.pos,o,HIDDEN);st.visibility=VISIBLE;st.position=ABSOLUTE;function after(){fly(dom).fxUnwrap(wrap,r.pos,o);st.width=r.width;st.height=r.height;fly(dom).afterFx(o);} +pt={to:[b.x,b.y]};bw={to:b.width};bh={to:b.height};function argCalc(wrap,style,ww,wh,sXY,sXYval,s1,s2,w,h,p){var ret={};fly(wrap).setWidth(ww).setHeight(wh);if(fly(wrap)[sXY]){fly(wrap)[sXY](sXYval);} +style[s1]=style[s2]="0";if(w){ret.width=w;} +if(h){ret.height=h;} +if(p){ret.points=p;} +return ret;};args=fly(dom).switchStatements(anchor.toLowerCase(),argCalc,{t:[wrap,st,b.width,0,NULL,NULL,LEFT,BOTTOM,NULL,bh,NULL],l:[wrap,st,0,b.height,NULL,NULL,RIGHT,TOP,bw,NULL,NULL],r:[wrap,st,b.width,b.height,SETX,b.right,LEFT,TOP,NULL,NULL,pt],b:[wrap,st,b.width,b.height,SETY,b.bottom,LEFT,TOP,NULL,bh,pt],tl:[wrap,st,0,0,NULL,NULL,RIGHT,BOTTOM,bw,bh,pt],bl:[wrap,st,0,0,SETY,b.y+b.height,RIGHT,TOP,bw,bh,pt],br:[wrap,st,0,0,SETXY,[b.right,b.bottom],LEFT,TOP,bw,bh,pt],tr:[wrap,st,0,0,SETX,b.x+b.width,LEFT,BOTTOM,bw,bh,pt]});st.visibility=VISIBLE;fly(wrap).show();arguments.callee.anim=fly(wrap).fxanim(args,o,MOTION,.5,EASEOUT,after);});return me;},slideOut:function(anchor,o){o=getObject(o);var me=this,dom=me.dom,st=dom.style,xy=me.getXY(),wrap,r,b,a,zero={to:0};anchor=anchor||"t";me.queueFx(o,function(){r=fly(dom).getFxRestore();b={x:xy[0],y:xy[1],0:xy[0],1:xy[1],width:dom.offsetWidth,height:dom.offsetHeight};b.right=b.x+b.width;b.bottom=b.y+b.height;fly(dom).setWidth(b.width).setHeight(b.height);wrap=fly(dom).fxWrap(r.pos,o,VISIBLE);st.visibility=VISIBLE;st.position=ABSOLUTE;fly(wrap).setWidth(b.width).setHeight(b.height);function after(){o.useDisplay?fly(dom).setDisplayed(FALSE):fly(dom).hide();fly(dom).fxUnwrap(wrap,r.pos,o);st.width=r.width;st.height=r.height;fly(dom).afterFx(o);} +function argCalc(style,s1,s2,p1,v1,p2,v2,p3,v3){var ret={};style[s1]=style[s2]="0";ret[p1]=v1;if(p2){ret[p2]=v2;} +if(p3){ret[p3]=v3;} +return ret;};a=fly(dom).switchStatements(anchor.toLowerCase(),argCalc,{t:[st,LEFT,BOTTOM,HEIGHT,zero],l:[st,RIGHT,TOP,WIDTH,zero],r:[st,LEFT,TOP,WIDTH,zero,POINTS,{to:[b.right,b.y]}],b:[st,LEFT,TOP,HEIGHT,zero,POINTS,{to:[b.x,b.bottom]}],tl:[st,RIGHT,BOTTOM,WIDTH,zero,HEIGHT,zero],bl:[st,RIGHT,TOP,WIDTH,zero,HEIGHT,zero,POINTS,{to:[b.x,b.bottom]}],br:[st,LEFT,TOP,WIDTH,zero,HEIGHT,zero,POINTS,{to:[b.x+b.width,b.bottom]}],tr:[st,LEFT,BOTTOM,WIDTH,zero,HEIGHT,zero,POINTS,{to:[b.right,b.y]}]});arguments.callee.anim=fly(wrap).fxanim(a,o,MOTION,.5,EASEOUT,after);});return me;},puff:function(o){o=getObject(o);var me=this,dom=me.dom,st=dom.style,width,height,r;me.queueFx(o,function(){width=fly(dom).getWidth();height=fly(dom).getHeight();fly(dom).clearOpacity();fly(dom).show();r=fly(dom).getFxRestore();function after(){o.useDisplay?fly(dom).setDisplayed(FALSE):fly(dom).hide();fly(dom).clearOpacity();fly(dom).setPositioning(r.pos);st.width=r.width;st.height=r.height;st.fontSize='';fly(dom).afterFx(o);} +arguments.callee.anim=fly(dom).fxanim({width:{to:fly(dom).adjustWidth(width*2)},height:{to:fly(dom).adjustHeight(height*2)},points:{by:[-width*.5,-height*.5]},opacity:{to:0},fontSize:{to:200,unit:"%"}},o,MOTION,.5,EASEOUT,after);});return me;},switchOff:function(o){o=getObject(o);var me=this,dom=me.dom,st=dom.style,r;me.queueFx(o,function(){fly(dom).clearOpacity();fly(dom).clip();r=fly(dom).getFxRestore();function after(){o.useDisplay?fly(dom).setDisplayed(FALSE):fly(dom).hide();fly(dom).clearOpacity();fly(dom).setPositioning(r.pos);st.width=r.width;st.height=r.height;fly(dom).afterFx(o);};fly(dom).fxanim({opacity:{to:0.3}},NULL,NULL,.1,NULL,function(){fly(dom).clearOpacity();(function(){fly(dom).fxanim({height:{to:1},points:{by:[0,fly(dom).getHeight()*.5]}},o,MOTION,0.3,'easeIn',after);}).defer(100);});});return me;},highlight:function(color,o){o=getObject(o);var me=this,dom=me.dom,attr=o.attr||"backgroundColor",a={},restore;me.queueFx(o,function(){fly(dom).clearOpacity();fly(dom).show();function after(){dom.style[attr]=restore;fly(dom).afterFx(o);} +restore=dom.style[attr];a[attr]={from:color||"ffff9c",to:o.endColor||fly(dom).getColor(attr)||"ffffff"};arguments.callee.anim=fly(dom).fxanim(a,o,'color',1,'easeIn',after);});return me;},frame:function(color,count,o){o=getObject(o);var me=this,dom=me.dom,proxy,active;me.queueFx(o,function(){color=color||'#C3DAF9';if(color.length==6){color='#'+color;} +count=count||1;fly(dom).show();var xy=fly(dom).getXY(),b={x:xy[0],y:xy[1],0:xy[0],1:xy[1],width:dom.offsetWidth,height:dom.offsetHeight},queue=function(){proxy=fly(document.body||document.documentElement).createChild({style:{position:ABSOLUTE,'z-index':35000,border:'0px solid '+color}});return proxy.queueFx({},animFn);};arguments.callee.anim={isAnimated:true,stop:function(){count=0;proxy.stopFx();}};function animFn(){var scale=Ext.isBorderBox?2:1;active=proxy.anim({top:{from:b.y,to:b.y-20},left:{from:b.x,to:b.x-20},borderWidth:{from:0,to:10},opacity:{from:1,to:0},height:{from:b.height,to:b.height+20*scale},width:{from:b.width,to:b.width+20*scale}},{duration:o.duration||1,callback:function(){proxy.remove();--count>0?queue():fly(dom).afterFx(o);}});arguments.callee.anim={isAnimated:true,stop:function(){active.stop();}};};queue();});return me;},pause:function(seconds){var dom=this.dom,t;this.queueFx({},function(){t=setTimeout(function(){fly(dom).afterFx({});},seconds*1000);arguments.callee.anim={isAnimated:true,stop:function(){clearTimeout(t);fly(dom).afterFx({});}};});return this;},fadeIn:function(o){o=getObject(o);var me=this,dom=me.dom,to=o.endOpacity||1;me.queueFx(o,function(){fly(dom).setOpacity(0);fly(dom).fixDisplay();dom.style.visibility=VISIBLE;arguments.callee.anim=fly(dom).fxanim({opacity:{to:to}},o,NULL,.5,EASEOUT,function(){if(to==1){fly(dom).clearOpacity();} +fly(dom).afterFx(o);});});return me;},fadeOut:function(o){o=getObject(o);var me=this,dom=me.dom,style=dom.style,to=o.endOpacity||0;me.queueFx(o,function(){arguments.callee.anim=fly(dom).fxanim({opacity:{to:to}},o,NULL,.5,EASEOUT,function(){if(to==0){Ext.Element.data(dom,'visibilityMode')==Ext.Element.DISPLAY||o.useDisplay?style.display="none":style.visibility=HIDDEN;fly(dom).clearOpacity();} +fly(dom).afterFx(o);});});return me;},scale:function(w,h,o){this.shift(Ext.apply({},o,{width:w,height:h}));return this;},shift:function(o){o=getObject(o);var dom=this.dom,a={};this.queueFx(o,function(){for(var prop in o){if(o[prop]!=UNDEFINED){a[prop]={to:o[prop]};}} +a.width?a.width.to=fly(dom).adjustWidth(o.width):a;a.height?a.height.to=fly(dom).adjustWidth(o.height):a;if(a.x||a.y||a.xy){a.points=a.xy||{to:[a.x?a.x.to:fly(dom).getX(),a.y?a.y.to:fly(dom).getY()]};} +arguments.callee.anim=fly(dom).fxanim(a,o,MOTION,.35,EASEOUT,function(){fly(dom).afterFx(o);});});return this;},ghost:function(anchor,o){o=getObject(o);var me=this,dom=me.dom,st=dom.style,a={opacity:{to:0},points:{}},pt=a.points,r,w,h;anchor=anchor||"b";me.queueFx(o,function(){r=fly(dom).getFxRestore();w=fly(dom).getWidth();h=fly(dom).getHeight();function after(){o.useDisplay?fly(dom).setDisplayed(FALSE):fly(dom).hide();fly(dom).clearOpacity();fly(dom).setPositioning(r.pos);st.width=r.width;st.height=r.height;fly(dom).afterFx(o);} +pt.by=fly(dom).switchStatements(anchor.toLowerCase(),function(v1,v2){return[v1,v2];},{t:[0,-h],l:[-w,0],r:[w,0],b:[0,h],tl:[-w,-h],bl:[-w,h],br:[w,h],tr:[w,-h]});arguments.callee.anim=fly(dom).fxanim(a,o,MOTION,.5,EASEOUT,after);});return me;},syncFx:function(){var me=this;me.fxDefaults=Ext.apply(me.fxDefaults||{},{block:FALSE,concurrent:TRUE,stopFx:FALSE});return me;},sequenceFx:function(){var me=this;me.fxDefaults=Ext.apply(me.fxDefaults||{},{block:FALSE,concurrent:FALSE,stopFx:FALSE});return me;},nextFx:function(){var ef=getQueue(this.dom.id)[0];if(ef){ef.call(this);}},hasActiveFx:function(){return getQueue(this.dom.id)[0];},stopFx:function(finish){var me=this,id=me.dom.id;if(me.hasActiveFx()){var cur=getQueue(id)[0];if(cur&&cur.anim){if(cur.anim.isAnimated){setQueue(id,[cur]);cur.anim.stop(finish!==undefined?finish:TRUE);}else{setQueue(id,[]);}}} +return me;},beforeFx:function(o){if(this.hasActiveFx()&&!o.concurrent){if(o.stopFx){this.stopFx();return TRUE;} +return FALSE;} +return TRUE;},hasFxBlock:function(){var q=getQueue(this.dom.id);return q&&q[0]&&q[0].block;},queueFx:function(o,fn){var me=fly(this.dom);if(!me.hasFxBlock()){Ext.applyIf(o,me.fxDefaults);if(!o.concurrent){var run=me.beforeFx(o);fn.block=o.block;getQueue(me.dom.id).push(fn);if(run){me.nextFx();}}else{fn.call(me);}} +return me;},fxWrap:function(pos,o,vis){var dom=this.dom,wrap,wrapXY;if(!o.wrap||!(wrap=Ext.getDom(o.wrap))){if(o.fixPosition){wrapXY=fly(dom).getXY();} +var div=document.createElement("div");div.style.visibility=vis;wrap=dom.parentNode.insertBefore(div,dom);fly(wrap).setPositioning(pos);if(fly(wrap).isStyle(POSITION,"static")){fly(wrap).position("relative");} +fly(dom).clearPositioning('auto');fly(wrap).clip();wrap.appendChild(dom);if(wrapXY){fly(wrap).setXY(wrapXY);}} +return wrap;},fxUnwrap:function(wrap,pos,o){var dom=this.dom;fly(dom).clearPositioning();fly(dom).setPositioning(pos);if(!o.wrap){var pn=fly(wrap).dom.parentNode;pn.insertBefore(dom,wrap);fly(wrap).remove();}},getFxRestore:function(){var st=this.dom.style;return{pos:this.getPositioning(),width:st.width,height:st.height};},afterFx:function(o){var dom=this.dom,id=dom.id;if(o.afterStyle){fly(dom).setStyle(o.afterStyle);} +if(o.afterCls){fly(dom).addClass(o.afterCls);} +if(o.remove==TRUE){fly(dom).remove();} +if(o.callback){o.callback.call(o.scope,fly(dom));} +if(!o.concurrent){getQueue(id).shift();fly(dom).nextFx();}},fxanim:function(args,opt,animType,defaultDur,defaultEase,cb){animType=animType||'run';opt=opt||{};var anim=Ext.lib.Anim[animType](this.dom,args,(opt.duration||defaultDur)||.35,(opt.easing||defaultEase)||EASEOUT,cb,this);opt.anim=anim;return anim;}};Ext.Fx.resize=Ext.Fx.scale;Ext.Element.addMethods(Ext.Fx);})();Ext.CompositeElementLite=function(els,root){this.elements=[];this.add(els,root);this.el=new Ext.Element.Flyweight();};Ext.CompositeElementLite.prototype={isComposite:true,getElement:function(el){var e=this.el;e.dom=el;e.id=el.id;return e;},transformElement:function(el){return Ext.getDom(el);},getCount:function(){return this.elements.length;},add:function(els,root){var me=this,elements=me.elements;if(!els){return this;} +if(typeof els=="string"){els=Ext.Element.selectorFunction(els,root);}else if(els.isComposite){els=els.elements;}else if(!Ext.isIterable(els)){els=[els];} +for(var i=0,len=els.length;i-1){replacement=Ext.getDom(replacement);if(domReplace){d=this.elements[index];d.parentNode.insertBefore(replacement,d);Ext.removeNode(d);} +this.elements.splice(index,1,replacement);} +return this;},clear:function(){this.elements=[];}};Ext.CompositeElementLite.prototype.on=Ext.CompositeElementLite.prototype.addListener;Ext.CompositeElementLite.importElementMethods=function(){var fnName,ElProto=Ext.Element.prototype,CelProto=Ext.CompositeElementLite.prototype;for(fnName in ElProto){if(typeof ElProto[fnName]=='function'){(function(fnName){CelProto[fnName]=CelProto[fnName]||function(){return this.invoke(fnName,arguments);};}).call(CelProto,fnName);}}};Ext.CompositeElementLite.importElementMethods();if(Ext.DomQuery){Ext.Element.selectorFunction=Ext.DomQuery.select;} +Ext.Element.select=function(selector,root){var els;if(typeof selector=="string"){els=Ext.Element.selectorFunction(selector,root);}else if(selector.length!==undefined){els=selector;}else{throw"Invalid selector";} +return new Ext.CompositeElementLite(els);};Ext.select=Ext.Element.select;(function(){var BEFOREREQUEST="beforerequest",REQUESTCOMPLETE="requestcomplete",REQUESTEXCEPTION="requestexception",UNDEFINED=undefined,LOAD='load',POST='POST',GET='GET',WINDOW=window;Ext.data.Connection=function(config){Ext.apply(this,config);this.addEvents(BEFOREREQUEST,REQUESTCOMPLETE,REQUESTEXCEPTION);Ext.data.Connection.superclass.constructor.call(this);};Ext.extend(Ext.data.Connection,Ext.util.Observable,{timeout:30000,autoAbort:false,disableCaching:true,disableCachingParam:'_dc',request:function(o){var me=this;if(me.fireEvent(BEFOREREQUEST,me,o)){if(o.el){if(!Ext.isEmpty(o.indicatorText)){me.indicatorText='
      '+o.indicatorText+"
      ";} +if(me.indicatorText){Ext.getDom(o.el).innerHTML=me.indicatorText;} +o.success=(Ext.isFunction(o.success)?o.success:function(){}).createInterceptor(function(response){Ext.getDom(o.el).innerHTML=response.responseText;});} +var p=o.params,url=o.url||me.url,method,cb={success:me.handleResponse,failure:me.handleFailure,scope:me,argument:{options:o},timeout:Ext.num(o.timeout,me.timeout)},form,serForm;if(Ext.isFunction(p)){p=p.call(o.scope||WINDOW,o);} +p=Ext.urlEncode(me.extraParams,Ext.isObject(p)?Ext.urlEncode(p):p);if(Ext.isFunction(url)){url=url.call(o.scope||WINDOW,o);} +if((form=Ext.getDom(o.form))){url=url||form.action;if(o.isUpload||(/multipart\/form-data/i.test(form.getAttribute("enctype")))){return me.doFormUpload.call(me,o,p,url);} +serForm=Ext.lib.Ajax.serializeForm(form);p=p?(p+'&'+serForm):serForm;} +method=o.method||me.method||((p||o.xmlData||o.jsonData)?POST:GET);if(method===GET&&(me.disableCaching&&o.disableCaching!==false)||o.disableCaching===true){var dcp=o.disableCachingParam||me.disableCachingParam;url=Ext.urlAppend(url,dcp+'='+(new Date().getTime()));} +o.headers=Ext.apply(o.headers||{},me.defaultHeaders||{});if(o.autoAbort===true||me.autoAbort){me.abort();} +if((method==GET||o.xmlData||o.jsonData)&&p){url=Ext.urlAppend(url,p);p='';} +return(me.transId=Ext.lib.Ajax.request(method,url,cb,p,o));}else{return o.callback?o.callback.apply(o.scope,[o,UNDEFINED,UNDEFINED]):null;}},isLoading:function(transId){return transId?Ext.lib.Ajax.isCallInProgress(transId):!!this.transId;},abort:function(transId){if(transId||this.isLoading()){Ext.lib.Ajax.abort(transId||this.transId);}},handleResponse:function(response){this.transId=false;var options=response.argument.options;response.argument=options?options.argument:null;this.fireEvent(REQUESTCOMPLETE,this,response,options);if(options.success){options.success.call(options.scope,response,options);} +if(options.callback){options.callback.call(options.scope,options,true,response);}},handleFailure:function(response,e){this.transId=false;var options=response.argument.options;response.argument=options?options.argument:null;this.fireEvent(REQUESTEXCEPTION,this,response,options,e);if(options.failure){options.failure.call(options.scope,response,options);} +if(options.callback){options.callback.call(options.scope,options,false,response);}},doFormUpload:function(o,ps,url){var id=Ext.id(),doc=document,frame=doc.createElement('iframe'),form=Ext.getDom(o.form),hiddens=[],hd,encoding='multipart/form-data',buf={target:form.target,method:form.method,encoding:form.encoding,enctype:form.enctype,action:form.action};Ext.fly(frame).set({id:id,name:id,cls:'x-hidden',src:Ext.SSL_SECURE_URL});doc.body.appendChild(frame);if(Ext.isIE){document.frames[id].name=id;} +Ext.fly(form).set({target:id,method:POST,enctype:encoding,encoding:encoding,action:url||buf.action});Ext.iterate(Ext.urlDecode(ps,false),function(k,v){hd=doc.createElement('input');Ext.fly(hd).set({type:'hidden',value:v,name:k});form.appendChild(hd);hiddens.push(hd);});function cb(){var me=this,r={responseText:'',responseXML:null,argument:o.argument},doc,firstChild;try{doc=frame.contentWindow.document||frame.contentDocument||WINDOW.frames[id].document;if(doc){if(doc.body){if(/textarea/i.test((firstChild=doc.body.firstChild||{}).tagName)){r.responseText=firstChild.value;}else{r.responseText=doc.body.innerHTML;}} +r.responseXML=doc.XMLDocument||doc;}} +catch(e){} +Ext.EventManager.removeListener(frame,LOAD,cb,me);me.fireEvent(REQUESTCOMPLETE,me,r,o);function runCallback(fn,scope,args){if(Ext.isFunction(fn)){fn.apply(scope,args);}} +runCallback(o.success,o.scope,[r,o]);runCallback(o.callback,o.scope,[o,true,r]);if(!me.debugUploads){setTimeout(function(){Ext.removeNode(frame);},100);}} +Ext.EventManager.on(frame,LOAD,cb,this);form.submit();Ext.fly(form).set(buf);Ext.each(hiddens,function(h){Ext.removeNode(h);});}});})();Ext.Ajax=new Ext.data.Connection({autoAbort:false,serializeForm:function(form){return Ext.lib.Ajax.serializeForm(form);}});Ext.util.JSON=new(function(){var useHasOwn=!!{}.hasOwnProperty,isNative=function(){var useNative=null;return function(){if(useNative===null){useNative=Ext.USE_NATIVE_JSON&&window.JSON&&JSON.toString()=='[object JSON]';} +return useNative;};}(),pad=function(n){return n<10?"0"+n:n;},doDecode=function(json){return eval("("+json+")");},doEncode=function(o){if(!Ext.isDefined(o)||o===null){return"null";}else if(Ext.isArray(o)){return encodeArray(o);}else if(Ext.isDate(o)){return Ext.util.JSON.encodeDate(o);}else if(Ext.isString(o)){return encodeString(o);}else if(typeof o=="number"){return isFinite(o)?String(o):"null";}else if(Ext.isBoolean(o)){return String(o);}else{var a=["{"],b,i,v;for(i in o){if(!o.getElementsByTagName){if(!useHasOwn||o.hasOwnProperty(i)){v=o[i];switch(typeof v){case"undefined":case"function":case"unknown":break;default:if(b){a.push(',');} +a.push(doEncode(i),":",v===null?"null":doEncode(v));b=true;}}}} +a.push("}");return a.join("");}},m={"\b":'\\b',"\t":'\\t',"\n":'\\n',"\f":'\\f',"\r":'\\r','"':'\\"',"\\":'\\\\'},encodeString=function(s){if(/["\\\x00-\x1f]/.test(s)){return'"'+s.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];if(c){return c;} +c=b.charCodeAt();return"\\u00"+ +Math.floor(c/16).toString(16)+ +(c%16).toString(16);})+'"';} +return'"'+s+'"';},encodeArray=function(o){var a=["["],b,i,l=o.length,v;for(i=0;i
      ';doc.body.appendChild(div);last=div.lastChild;if((view=doc.defaultView)){if(view.getComputedStyle(div.firstChild.firstChild,null).marginRight!='0px'){supports.correctRightMargin=false;} +if(view.getComputedStyle(last,null).backgroundColor!='transparent'){supports.correctTransparentColor=false;}} +supports.cssFloat=!!last.style.cssFloat;doc.body.removeChild(div);};if(Ext.isReady){supportTests();}else{Ext.onReady(supportTests);}})();Ext.EventObject=function(){var E=Ext.lib.Event,clickRe=/(dbl)?click/,safariKeys={3:13,63234:37,63235:39,63232:38,63233:40,63276:33,63277:34,63272:46,63273:36,63275:35},btnMap=Ext.isIE?{1:0,4:1,2:2}:{0:0,1:1,2:2};Ext.EventObjectImpl=function(e){if(e){this.setEvent(e.browserEvent||e);}};Ext.EventObjectImpl.prototype={setEvent:function(e){var me=this;if(e==me||(e&&e.browserEvent)){return e;} +me.browserEvent=e;if(e){me.button=e.button?btnMap[e.button]:(e.which?e.which-1:-1);if(clickRe.test(e.type)&&me.button==-1){me.button=0;} +me.type=e.type;me.shiftKey=e.shiftKey;me.ctrlKey=e.ctrlKey||e.metaKey||false;me.altKey=e.altKey;me.keyCode=e.keyCode;me.charCode=e.charCode;me.target=E.getTarget(e);me.xy=E.getXY(e);}else{me.button=-1;me.shiftKey=false;me.ctrlKey=false;me.altKey=false;me.keyCode=0;me.charCode=0;me.target=null;me.xy=[0,0];} +return me;},stopEvent:function(){var me=this;if(me.browserEvent){if(me.browserEvent.type=='mousedown'){Ext.EventManager.stoppedMouseDownEvent.fire(me);} +E.stopEvent(me.browserEvent);}},preventDefault:function(){if(this.browserEvent){E.preventDefault(this.browserEvent);}},stopPropagation:function(){var me=this;if(me.browserEvent){if(me.browserEvent.type=='mousedown'){Ext.EventManager.stoppedMouseDownEvent.fire(me);} +E.stopPropagation(me.browserEvent);}},getCharCode:function(){return this.charCode||this.keyCode;},getKey:function(){return this.normalizeKey(this.keyCode||this.charCode);},normalizeKey:function(k){return Ext.isSafari?(safariKeys[k]||k):k;},getPageX:function(){return this.xy[0];},getPageY:function(){return this.xy[1];},getXY:function(){return this.xy;},getTarget:function(selector,maxDepth,returnEl){return selector?Ext.fly(this.target).findParent(selector,maxDepth,returnEl):(returnEl?Ext.get(this.target):this.target);},getRelatedTarget:function(){return this.browserEvent?E.getRelatedTarget(this.browserEvent):null;},getWheelDelta:function(){var e=this.browserEvent;var delta=0;if(e.wheelDelta){delta=e.wheelDelta/120;}else if(e.detail){delta=-e.detail/3;} +return delta;},within:function(el,related,allowEl){if(el){var t=this[related?"getRelatedTarget":"getTarget"]();return t&&((allowEl?(t==Ext.getDom(el)):false)||Ext.fly(el).contains(t));} +return false;}};return new Ext.EventObjectImpl();}();Ext.Loader=Ext.apply({},{load:function(fileList,callback,scope,preserveOrder){var scope=scope||this,head=document.getElementsByTagName("head")[0],fragment=document.createDocumentFragment(),numFiles=fileList.length,loadedFiles=0,me=this;var loadFileIndex=function(index){head.appendChild(me.buildScriptTag(fileList[index],onFileLoaded));};var onFileLoaded=function(){loadedFiles++;if(numFiles==loadedFiles&&typeof callback=='function'){callback.call(scope);}else{if(preserveOrder===true){loadFileIndex(loadedFiles);}}};if(preserveOrder===true){loadFileIndex.call(this,0);}else{Ext.each(fileList,function(file,index){fragment.appendChild(this.buildScriptTag(file,onFileLoaded));},this);head.appendChild(fragment);}},buildScriptTag:function(filename,callback){var script=document.createElement('script');script.type="text/javascript";script.src=filename;if(script.readyState){script.onreadystatechange=function(){if(script.readyState=="loaded"||script.readyState=="complete"){script.onreadystatechange=null;callback();}};}else{script.onload=callback;} +return script;}});Ext.ns("Ext.grid","Ext.list","Ext.dd","Ext.tree","Ext.form","Ext.menu","Ext.state","Ext.layout","Ext.app","Ext.ux","Ext.chart","Ext.direct");Ext.apply(Ext,function(){var E=Ext,idSeed=0,scrollWidth=null;return{emptyFn:function(){},BLANK_IMAGE_URL:Ext.isIE6||Ext.isIE7||Ext.isAir?'http:/'+'/www.extjs.com/s.gif':'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',extendX:function(supr,fn){return Ext.extend(supr,fn(supr.prototype));},getDoc:function(){return Ext.get(document);},num:function(v,defaultValue){v=Number(Ext.isEmpty(v)||Ext.isArray(v)||typeof v=='boolean'||(typeof v=='string'&&v.trim().length==0)?NaN:v);return isNaN(v)?defaultValue:v;},value:function(v,defaultValue,allowBlank){return Ext.isEmpty(v,allowBlank)?defaultValue:v;},escapeRe:function(s){return s.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},sequence:function(o,name,fn,scope){o[name]=o[name].createSequence(fn,scope);},addBehaviors:function(o){if(!Ext.isReady){Ext.onReady(function(){Ext.addBehaviors(o);});}else{var cache={},parts,b,s;for(b in o){if((parts=b.split('@'))[1]){s=parts[0];if(!cache[s]){cache[s]=Ext.select(s);} +cache[s].on(parts[1],o[b]);}} +cache=null;}},getScrollBarWidth:function(force){if(!Ext.isReady){return 0;} +if(force===true||scrollWidth===null){var div=Ext.getBody().createChild('
      '),child=div.child('div',true);var w1=child.offsetWidth;div.setStyle('overflow',(Ext.isWebKit||Ext.isGecko)?'auto':'scroll');var w2=child.offsetWidth;div.remove();scrollWidth=w1-w2+2;} +return scrollWidth;},combine:function(){var as=arguments,l=as.length,r=[];for(var i=0;ib?1:-1;};Ext.each(arr,function(v){ret=comp(ret,v)==1?ret:v;});return ret;},mean:function(arr){return arr.length>0?Ext.sum(arr)/arr.length:undefined;},sum:function(arr){var ret=0;Ext.each(arr,function(v){ret+=v;});return ret;},partition:function(arr,truth){var ret=[[],[]];Ext.each(arr,function(v,i,a){ret[(truth&&truth(v,i,a))||(!truth&&v)?0:1].push(v);});return ret;},invoke:function(arr,methodName){var ret=[],args=Array.prototype.slice.call(arguments,2);Ext.each(arr,function(v,i){if(v&&typeof v[methodName]=='function'){ret.push(v[methodName].apply(v,args));}else{ret.push(undefined);}});return ret;},pluck:function(arr,prop){var ret=[];Ext.each(arr,function(v){ret.push(v[prop]);});return ret;},zip:function(){var parts=Ext.partition(arguments,function(val){return typeof val!='function';}),arrs=parts[0],fn=parts[1][0],len=Ext.max(Ext.pluck(arrs,"length")),ret=[];for(var i=0;i=me.left&®ion.right<=me.right&®ion.top>=me.top&®ion.bottom<=me.bottom);},getArea:function(){var me=this;return((me.bottom-me.top)*(me.right-me.left));},intersect:function(region){var me=this,t=Math.max(me.top,region.top),r=Math.min(me.right,region.right),b=Math.min(me.bottom,region.bottom),l=Math.max(me.left,region.left);if(b>=t&&r>=l){return new Ext.lib.Region(t,r,b,l);}},union:function(region){var me=this,t=Math.min(me.top,region.top),r=Math.max(me.right,region.right),b=Math.max(me.bottom,region.bottom),l=Math.min(me.left,region.left);return new Ext.lib.Region(t,r,b,l);},constrainTo:function(r){var me=this;me.top=me.top.constrain(r.top,r.bottom);me.bottom=me.bottom.constrain(r.top,r.bottom);me.left=me.left.constrain(r.left,r.right);me.right=me.right.constrain(r.left,r.right);return me;},adjust:function(t,l,b,r){var me=this;me.top+=t;me.left+=l;me.right+=r;me.bottom+=b;return me;}};Ext.lib.Region.getRegion=function(el){var p=Ext.lib.Dom.getXY(el),t=p[1],r=p[0]+el.offsetWidth,b=p[1]+el.offsetHeight,l=p[0];return new Ext.lib.Region(t,r,b,l);};Ext.lib.Point=function(x,y){if(Ext.isArray(x)){y=x[1];x=x[0];} +var me=this;me.x=me.right=me.left=me[0]=x;me.y=me.top=me.bottom=me[1]=y;};Ext.lib.Point.prototype=new Ext.lib.Region();Ext.apply(Ext.DomHelper,function(){var pub,afterbegin='afterbegin',afterend='afterend',beforebegin='beforebegin',beforeend='beforeend',confRe=/tag|children|cn|html$/i;function doInsert(el,o,returnElement,pos,sibling,append){el=Ext.getDom(el);var newNode;if(pub.useDom){newNode=createDom(o,null);if(append){el.appendChild(newNode);}else{(sibling=='firstChild'?el:el.parentNode).insertBefore(newNode,el[sibling]||el);}}else{newNode=Ext.DomHelper.insertHtml(pos,el,Ext.DomHelper.createHtml(o));} +return returnElement?Ext.get(newNode,true):newNode;} +function createDom(o,parentNode){var el,doc=document,useSet,attr,val,cn;if(Ext.isArray(o)){el=doc.createDocumentFragment();for(var i=0,l=o.length;i0){return setTimeout(fn,millis);} +fn();return 0;},createSequence:function(origFn,newFn,scope){if(!Ext.isFunction(newFn)){return origFn;} +else{return function(){var retval=origFn.apply(this||window,arguments);newFn.apply(scope||this||window,arguments);return retval;};}}};Ext.defer=Ext.util.Functions.defer;Ext.createInterceptor=Ext.util.Functions.createInterceptor;Ext.createSequence=Ext.util.Functions.createSequence;Ext.createDelegate=Ext.util.Functions.createDelegate;Ext.apply(Ext.util.Observable.prototype,function(){function getMethodEvent(method){var e=(this.methodEvents=this.methodEvents||{})[method],returnValue,v,cancel,obj=this;if(!e){this.methodEvents[method]=e={};e.originalFn=this[method];e.methodName=method;e.before=[];e.after=[];var makeCall=function(fn,scope,args){if((v=fn.apply(scope||obj,args))!==undefined){if(typeof v=='object'){if(v.returnValue!==undefined){returnValue=v.returnValue;}else{returnValue=v;} +cancel=!!v.cancel;} +else +if(v===false){cancel=true;} +else{returnValue=v;}}};this[method]=function(){var args=Array.prototype.slice.call(arguments,0),b;returnValue=v=undefined;cancel=false;for(var i=0,len=e.before.length;i=525:!((Ext.isGecko&&!Ext.isWindows)||Ext.isOpera);return{doResizeEvent:function(){var h=D.getViewHeight(),w=D.getViewWidth();if(curHeight!=h||curWidth!=w){resizeEvent.fire(curWidth=w,curHeight=h);}},onWindowResize:function(fn,scope,options){if(!resizeEvent){resizeEvent=new Ext.util.Event();resizeTask=new Ext.util.DelayedTask(this.doResizeEvent);Ext.EventManager.on(window,"resize",this.fireWindowResize,this);} +resizeEvent.addListener(fn,scope,options);},fireWindowResize:function(){if(resizeEvent){resizeTask.delay(100);}},onTextResize:function(fn,scope,options){if(!textEvent){textEvent=new Ext.util.Event();var textEl=new Ext.Element(document.createElement('div'));textEl.dom.className='x-text-resize';textEl.dom.innerHTML='X';textEl.appendTo(document.body);textSize=textEl.dom.offsetHeight;setInterval(function(){if(textEl.dom.offsetHeight!=textSize){textEvent.fire(textSize,textSize=textEl.dom.offsetHeight);}},this.textResizeInterval);} +textEvent.addListener(fn,scope,options);},removeResizeListener:function(fn,scope){if(resizeEvent){resizeEvent.removeListener(fn,scope);}},fireResize:function(){if(resizeEvent){resizeEvent.fire(D.getViewWidth(),D.getViewHeight());}},textResizeInterval:50,ieDeferSrc:false,getKeyEvent:function(){return useKeydown?'keydown':'keypress';},useKeydown:useKeydown};}());Ext.EventManager.on=Ext.EventManager.addListener;Ext.apply(Ext.EventObjectImpl.prototype,{BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,RETURN:13,SHIFT:16,CTRL:17,CONTROL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGEUP:33,PAGE_DOWN:34,PAGEDOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,isNavKeyPress:function(){var me=this,k=this.normalizeKey(me.keyCode);return(k>=33&&k<=40)||k==me.RETURN||k==me.TAB||k==me.ESC;},isSpecialKey:function(){var k=this.normalizeKey(this.keyCode);return(this.type=='keypress'&&this.ctrlKey)||this.isNavKeyPress()||(k==this.BACKSPACE)||(k>=16&&k<=20)||(k>=44&&k<=46);},getPoint:function(){return new Ext.lib.Point(this.xy[0],this.xy[1]);},hasModifier:function(){return((this.ctrlKey||this.altKey)||this.shiftKey);}});Ext.Element.addMethods({swallowEvent:function(eventName,preventDefault){var me=this;function fn(e){e.stopPropagation();if(preventDefault){e.preventDefault();}} +if(Ext.isArray(eventName)){Ext.each(eventName,function(e){me.on(e,fn);});return me;} +me.on(eventName,fn);return me;},relayEvent:function(eventName,observable){this.on(eventName,function(e){observable.fireEvent(eventName,e);});},clean:function(forceReclean){var me=this,dom=me.dom,n=dom.firstChild,ni=-1;if(Ext.Element.data(dom,'isCleaned')&&forceReclean!==true){return me;} +while(n){var nx=n.nextSibling;if(n.nodeType==3&&!(/\S/.test(n.nodeValue))){dom.removeChild(n);}else{n.nodeIndex=++ni;} +n=nx;} +Ext.Element.data(dom,'isCleaned',true);return me;},load:function(){var updateManager=this.getUpdater();updateManager.update.apply(updateManager,arguments);return this;},getUpdater:function(){return this.updateManager||(this.updateManager=new Ext.Updater(this));},update:function(html,loadScripts,callback){if(!this.dom){return this;} +html=html||"";if(loadScripts!==true){this.dom.innerHTML=html;if(typeof callback=='function'){callback();} +return this;} +var id=Ext.id(),dom=this.dom;html+='';Ext.lib.Event.onAvailable(id,function(){var DOC=document,hd=DOC.getElementsByTagName("head")[0],re=/(?:]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,srcRe=/\ssrc=([\'\"])(.*?)\1/i,typeRe=/\stype=([\'\"])(.*?)\1/i,match,attrs,srcMatch,typeMatch,el,s;while((match=re.exec(html))){attrs=match[1];srcMatch=attrs?attrs.match(srcRe):false;if(srcMatch&&srcMatch[2]){s=DOC.createElement("script");s.src=srcMatch[2];typeMatch=attrs.match(typeRe);if(typeMatch&&typeMatch[2]){s.type=typeMatch[2];} +hd.appendChild(s);}else if(match[2]&&match[2].length>0){if(window.execScript){window.execScript(match[2]);}else{window.eval(match[2]);}}} +el=DOC.getElementById(id);if(el){Ext.removeNode(el);} +if(typeof callback=='function'){callback();}});dom.innerHTML=html.replace(/(?:)((\n|\r|.)*?)(?:<\/script>)/ig,"");return this;},removeAllListeners:function(){this.removeAnchor();Ext.EventManager.removeAll(this.dom);return this;},createProxy:function(config,renderTo,matchBox){config=(typeof config=='object')?config:{tag:"div",cls:config};var me=this,proxy=renderTo?Ext.DomHelper.append(renderTo,config,true):Ext.DomHelper.insertBefore(me.dom,config,true);if(matchBox&&me.setBox&&me.getBox){proxy.setBox(me.getBox());} +return proxy;}});Ext.Element.prototype.getUpdateManager=Ext.Element.prototype.getUpdater;Ext.Element.addMethods({getAnchorXY:function(anchor,local,s){anchor=(anchor||"tl").toLowerCase();s=s||{};var me=this,vp=me.dom==document.body||me.dom==document,w=s.width||vp?Ext.lib.Dom.getViewWidth():me.getWidth(),h=s.height||vp?Ext.lib.Dom.getViewHeight():me.getHeight(),xy,r=Math.round,o=me.getXY(),scroll=me.getScroll(),extraX=vp?scroll.left:!local?o[0]:0,extraY=vp?scroll.top:!local?o[1]:0,hash={c:[r(w*0.5),r(h*0.5)],t:[r(w*0.5),0],l:[0,r(h*0.5)],r:[w,r(h*0.5)],b:[r(w*0.5),h],tl:[0,0],bl:[0,h],br:[w,h],tr:[w,0]};xy=hash[anchor];return[xy[0]+extraX,xy[1]+extraY];},anchorTo:function(el,alignment,offsets,animate,monitorScroll,callback){var me=this,dom=me.dom,scroll=!Ext.isEmpty(monitorScroll),action=function(){Ext.fly(dom).alignTo(el,alignment,offsets,animate);Ext.callback(callback,Ext.fly(dom));},anchor=this.getAnchor();this.removeAnchor();Ext.apply(anchor,{fn:action,scroll:scroll});Ext.EventManager.onWindowResize(action,null);if(scroll){Ext.EventManager.on(window,'scroll',action,null,{buffer:!isNaN(monitorScroll)?monitorScroll:50});} +action.call(me);return me;},removeAnchor:function(){var me=this,anchor=this.getAnchor();if(anchor&&anchor.fn){Ext.EventManager.removeResizeListener(anchor.fn);if(anchor.scroll){Ext.EventManager.un(window,'scroll',anchor.fn);} +delete anchor.fn;} +return me;},getAnchor:function(){var data=Ext.Element.data,dom=this.dom;if(!dom){return;} +var anchor=data(dom,'_anchor');if(!anchor){anchor=data(dom,'_anchor',{});} +return anchor;},getAlignToXY:function(el,p,o){el=Ext.get(el);if(!el||!el.dom){throw"Element.alignToXY with an element that doesn't exist";} +o=o||[0,0];p=(!p||p=="?"?"tl-bl?":(!(/-/).test(p)&&p!==""?"tl-"+p:p||"tl-bl")).toLowerCase();var me=this,d=me.dom,a1,a2,x,y,w,h,r,dw=Ext.lib.Dom.getViewWidth()-10,dh=Ext.lib.Dom.getViewHeight()-10,p1y,p1x,p2y,p2x,swapY,swapX,doc=document,docElement=doc.documentElement,docBody=doc.body,scrollX=(docElement.scrollLeft||docBody.scrollLeft||0)+5,scrollY=(docElement.scrollTop||docBody.scrollTop||0)+5,c=false,p1="",p2="",m=p.match(/^([a-z]+)-([a-z]+)(\?)?$/);if(!m){throw"Element.alignTo with an invalid alignment "+p;} +p1=m[1];p2=m[2];c=!!m[3];a1=me.getAnchorXY(p1,true);a2=el.getAnchorXY(p2,false);x=a2[0]-a1[0]+o[0];y=a2[1]-a1[1]+o[1];if(c){w=me.getWidth();h=me.getHeight();r=el.getRegion();p1y=p1.charAt(0);p1x=p1.charAt(p1.length-1);p2y=p2.charAt(0);p2x=p2.charAt(p2.length-1);swapY=((p1y=="t"&&p2y=="b")||(p1y=="b"&&p2y=="t"));swapX=((p1x=="r"&&p2x=="l")||(p1x=="l"&&p2x=="r"));if(x+w>dw+scrollX){x=swapX?r.left-w:dw+scrollX-w;} +if(xdh+scrollY){y=swapY?r.top-h:dh+scrollY-h;} +if(yvr){x=vr-w;moved=true;} +if((y+h)>vb){y=vb-h;moved=true;} +if(x"+String.format(Ext.Element.boxMarkup,cls)+"
      "));Ext.DomQuery.selectNode('.'+cls+'-mc',el.dom).appendChild(this.dom);return el;},setSize:function(width,height,animate){var me=this;if(typeof width=='object'){height=width.height;width=width.width;} +width=me.adjustWidth(width);height=me.adjustHeight(height);if(!animate||!me.anim){me.dom.style.width=me.addUnits(width);me.dom.style.height=me.addUnits(height);}else{me.anim({width:{to:width},height:{to:height}},me.preanim(arguments,2));} +return me;},getComputedHeight:function(){var me=this,h=Math.max(me.dom.offsetHeight,me.dom.clientHeight);if(!h){h=parseFloat(me.getStyle('height'))||0;if(!me.isBorderBox()){h+=me.getFrameWidth('tb');}} +return h;},getComputedWidth:function(){var w=Math.max(this.dom.offsetWidth,this.dom.clientWidth);if(!w){w=parseFloat(this.getStyle('width'))||0;if(!this.isBorderBox()){w+=this.getFrameWidth('lr');}} +return w;},getFrameWidth:function(sides,onlyContentBox){return onlyContentBox&&this.isBorderBox()?0:(this.getPadding(sides)+this.getBorderWidth(sides));},addClassOnOver:function(className){this.hover(function(){Ext.fly(this,INTERNAL).addClass(className);},function(){Ext.fly(this,INTERNAL).removeClass(className);});return this;},addClassOnFocus:function(className){this.on("focus",function(){Ext.fly(this,INTERNAL).addClass(className);},this.dom);this.on("blur",function(){Ext.fly(this,INTERNAL).removeClass(className);},this.dom);return this;},addClassOnClick:function(className){var dom=this.dom;this.on("mousedown",function(){Ext.fly(dom,INTERNAL).addClass(className);var d=Ext.getDoc(),fn=function(){Ext.fly(dom,INTERNAL).removeClass(className);d.removeListener("mouseup",fn);};d.on("mouseup",fn);});return this;},getViewSize:function(){var doc=document,d=this.dom,isDoc=(d==doc||d==doc.body);if(isDoc){var extdom=Ext.lib.Dom;return{width:extdom.getViewWidth(),height:extdom.getViewHeight()};}else{return{width:d.clientWidth,height:d.clientHeight};}},getStyleSize:function(){var me=this,w,h,doc=document,d=this.dom,isDoc=(d==doc||d==doc.body),s=d.style;if(isDoc){var extdom=Ext.lib.Dom;return{width:extdom.getViewWidth(),height:extdom.getViewHeight()};} +if(s.width&&s.width!='auto'){w=parseFloat(s.width);if(me.isBorderBox()){w-=me.getFrameWidth('lr');}} +if(s.height&&s.height!='auto'){h=parseFloat(s.height);if(me.isBorderBox()){h-=me.getFrameWidth('tb');}} +return{width:w||me.getWidth(true),height:h||me.getHeight(true)};},getSize:function(contentSize){return{width:this.getWidth(contentSize),height:this.getHeight(contentSize)};},repaint:function(){var dom=this.dom;this.addClass("x-repaint");setTimeout(function(){Ext.fly(dom).removeClass("x-repaint");},1);return this;},unselectable:function(){this.dom.unselectable="on";return this.swallowEvent("selectstart",true).applyStyles("-moz-user-select:none;-khtml-user-select:none;").addClass("x-unselectable");},getMargins:function(side){var me=this,key,hash={t:"top",l:"left",r:"right",b:"bottom"},o={};if(!side){for(key in me.margins){o[hash[key]]=parseFloat(me.getStyle(me.margins[key]))||0;} +return o;}else{return me.addStyles.call(me,side,me.margins);}}};}());Ext.Element.addMethods({setBox:function(box,adjust,animate){var me=this,w=box.width,h=box.height;if((adjust&&!me.autoBoxAdjust)&&!me.isBorderBox()){w-=(me.getBorderWidth("lr")+me.getPadding("lr"));h-=(me.getBorderWidth("tb")+me.getPadding("tb"));} +me.setBounds(box.x,box.y,w,h,me.animTest.call(me,arguments,animate,2));return me;},getBox:function(contentBox,local){var me=this,xy,left,top,getBorderWidth=me.getBorderWidth,getPadding=me.getPadding,l,r,t,b;if(!local){xy=me.getXY();}else{left=parseInt(me.getStyle("left"),10)||0;top=parseInt(me.getStyle("top"),10)||0;xy=[left,top];} +var el=me.dom,w=el.offsetWidth,h=el.offsetHeight,bx;if(!contentBox){bx={x:xy[0],y:xy[1],0:xy[0],1:xy[1],width:w,height:h};}else{l=getBorderWidth.call(me,"l")+getPadding.call(me,"l");r=getBorderWidth.call(me,"r")+getPadding.call(me,"r");t=getBorderWidth.call(me,"t")+getPadding.call(me,"t");b=getBorderWidth.call(me,"b")+getPadding.call(me,"b");bx={x:xy[0]+l,y:xy[1]+t,0:xy[0]+l,1:xy[1]+t,width:w-(l+r),height:h-(t+b)};} +bx.right=bx.x+bx.width;bx.bottom=bx.y+bx.height;return bx;},move:function(direction,distance,animate){var me=this,xy=me.getXY(),x=xy[0],y=xy[1],left=[x-distance,y],right=[x+distance,y],top=[x,y-distance],bottom=[x,y+distance],hash={l:left,left:left,r:right,right:right,t:top,top:top,up:top,b:bottom,bottom:bottom,down:bottom};direction=direction.toLowerCase();me.moveTo(hash[direction][0],hash[direction][1],me.animTest.call(me,arguments,animate,2));},setLeftTop:function(left,top){var me=this,style=me.dom.style;style.left=me.addUnits(left);style.top=me.addUnits(top);return me;},getRegion:function(){return Ext.lib.Dom.getRegion(this.dom);},setBounds:function(x,y,width,height,animate){var me=this;if(!animate||!me.anim){me.setSize(width,height);me.setLocation(x,y);}else{me.anim({points:{to:[x,y]},width:{to:me.adjustWidth(width)},height:{to:me.adjustHeight(height)}},me.preanim(arguments,4),'motion');} +return me;},setRegion:function(region,animate){return this.setBounds(region.left,region.top,region.right-region.left,region.bottom-region.top,this.animTest.call(this,arguments,animate,1));}});Ext.Element.addMethods({scrollTo:function(side,value,animate){var top=/top/i.test(side),me=this,dom=me.dom,prop;if(!animate||!me.anim){prop='scroll'+(top?'Top':'Left');dom[prop]=value;} +else{prop='scroll'+(top?'Left':'Top');me.anim({scroll:{to:top?[dom[prop],value]:[value,dom[prop]]}},me.preanim(arguments,2),'scroll');} +return me;},scrollIntoView:function(container,hscroll){var c=Ext.getDom(container)||Ext.getBody().dom,el=this.dom,o=this.getOffsetsTo(c),l=o[0]+c.scrollLeft,t=o[1]+c.scrollTop,b=t+el.offsetHeight,r=l+el.offsetWidth,ch=c.clientHeight,ct=parseInt(c.scrollTop,10),cl=parseInt(c.scrollLeft,10),cb=ct+ch,cr=cl+c.clientWidth;if(el.offsetHeight>ch||tcb){c.scrollTop=b-ch;} +c.scrollTop=c.scrollTop;if(hscroll!==false){if(el.offsetWidth>c.clientWidth||lcr){c.scrollLeft=r-c.clientWidth;} +c.scrollLeft=c.scrollLeft;} +return this;},scrollChildIntoView:function(child,hscroll){Ext.fly(child,'_scrollChildIntoView').scrollIntoView(this,hscroll);},scroll:function(direction,distance,animate){if(!this.isScrollable()){return false;} +var el=this.dom,l=el.scrollLeft,t=el.scrollTop,w=el.scrollWidth,h=el.scrollHeight,cw=el.clientWidth,ch=el.clientHeight,scrolled=false,v,hash={l:Math.min(l+distance,w-cw),r:v=Math.max(l-distance,0),t:Math.max(t-distance,0),b:Math.min(t+distance,h-ch)};hash.d=hash.b;hash.u=hash.t;direction=direction.substr(0,1);if((v=hash[direction])>-1){scrolled=true;this.scrollTo(direction=='l'||direction=='r'?'left':'top',v,this.preanim(arguments,2));} +return scrolled;}});Ext.Element.addMethods(function(){var VISIBILITY="visibility",DISPLAY="display",HIDDEN="hidden",NONE="none",XMASKED="x-masked",XMASKEDRELATIVE="x-masked-relative",data=Ext.Element.data;return{isVisible:function(deep){var vis=!this.isStyle(VISIBILITY,HIDDEN)&&!this.isStyle(DISPLAY,NONE),p=this.dom.parentNode;if(deep!==true||!vis){return vis;} +while(p&&!(/^body/i.test(p.tagName))){if(!Ext.fly(p,'_isVisible').isVisible()){return false;} +p=p.parentNode;} +return true;},isDisplayed:function(){return!this.isStyle(DISPLAY,NONE);},enableDisplayMode:function(display){this.setVisibilityMode(Ext.Element.DISPLAY);if(!Ext.isEmpty(display)){data(this.dom,'originalDisplay',display);} +return this;},mask:function(msg,msgCls){var me=this,dom=me.dom,dh=Ext.DomHelper,EXTELMASKMSG="ext-el-mask-msg",el,mask;if(!(/^body/i.test(dom.tagName)&&me.getStyle('position')=='static')){me.addClass(XMASKEDRELATIVE);} +if(el=data(dom,'maskMsg')){el.remove();} +if(el=data(dom,'mask')){el.remove();} +mask=dh.append(dom,{cls:"ext-el-mask"},true);data(dom,'mask',mask);me.addClass(XMASKED);mask.setDisplayed(true);if(typeof msg=='string'){var mm=dh.append(dom,{cls:EXTELMASKMSG,cn:{tag:'div'}},true);data(dom,'maskMsg',mm);mm.dom.className=msgCls?EXTELMASKMSG+" "+msgCls:EXTELMASKMSG;mm.dom.firstChild.innerHTML=msg;mm.setDisplayed(true);mm.center(me);} +if(Ext.isIE&&!(Ext.isIE7&&Ext.isStrict)&&me.getStyle('height')=='auto'){mask.setSize(undefined,me.getHeight());} +return mask;},unmask:function(){var me=this,dom=me.dom,mask=data(dom,'mask'),maskMsg=data(dom,'maskMsg');if(mask){if(maskMsg){maskMsg.remove();data(dom,'maskMsg',undefined);} +mask.remove();data(dom,'mask',undefined);me.removeClass([XMASKED,XMASKEDRELATIVE]);}},isMasked:function(){var m=data(this.dom,'mask');return m&&m.isVisible();},createShim:function(){var el=document.createElement('iframe'),shim;el.frameBorder='0';el.className='ext-shim';el.src=Ext.SSL_SECURE_URL;shim=Ext.get(this.dom.parentNode.insertBefore(el,this.dom));shim.autoBoxAdjust=false;return shim;}};}());Ext.Element.addMethods({addKeyListener:function(key,fn,scope){var config;if(typeof key!='object'||Ext.isArray(key)){config={key:key,fn:fn,scope:scope};}else{config={key:key.key,shift:key.shift,ctrl:key.ctrl,alt:key.alt,fn:fn,scope:scope};} +return new Ext.KeyMap(this,config);},addKeyMap:function(config){return new Ext.KeyMap(this,config);}});Ext.CompositeElementLite.importElementMethods();Ext.apply(Ext.CompositeElementLite.prototype,{addElements:function(els,root){if(!els){return this;} +if(typeof els=="string"){els=Ext.Element.selectorFunction(els,root);} +var yels=this.elements;Ext.each(els,function(e){yels.push(Ext.get(e));});return this;},first:function(){return this.item(0);},last:function(){return this.item(this.getCount()-1);},contains:function(el){return this.indexOf(el)!=-1;},removeElement:function(keys,removeDom){var me=this,els=this.elements,el;Ext.each(keys,function(val){if((el=(els[val]||els[val=me.indexOf(val)]))){if(removeDom){if(el.dom){el.remove();}else{Ext.removeNode(el);}} +els.splice(val,1);}});return this;}});Ext.CompositeElement=Ext.extend(Ext.CompositeElementLite,{constructor:function(els,root){this.elements=[];this.add(els,root);},getElement:function(el){return el;},transformElement:function(el){return Ext.get(el);}});Ext.Element.select=function(selector,unique,root){var els;if(typeof selector=="string"){els=Ext.Element.selectorFunction(selector,root);}else if(selector.length!==undefined){els=selector;}else{throw"Invalid selector";} +return(unique===true)?new Ext.CompositeElement(els):new Ext.CompositeElementLite(els);};Ext.select=Ext.Element.select;Ext.UpdateManager=Ext.Updater=Ext.extend(Ext.util.Observable,function(){var BEFOREUPDATE="beforeupdate",UPDATE="update",FAILURE="failure";function processSuccess(response){var me=this;me.transaction=null;if(response.argument.form&&response.argument.reset){try{response.argument.form.reset();}catch(e){}} +if(me.loadScripts){me.renderer.render(me.el,response,me,updateComplete.createDelegate(me,[response]));}else{me.renderer.render(me.el,response,me);updateComplete.call(me,response);}} +function updateComplete(response,type,success){this.fireEvent(type||UPDATE,this.el,response);if(Ext.isFunction(response.argument.callback)){response.argument.callback.call(response.argument.scope,this.el,Ext.isEmpty(success)?true:false,response,response.argument.options);}} +function processFailure(response){updateComplete.call(this,response,FAILURE,!!(this.transaction=null));} +return{constructor:function(el,forceNew){var me=this;el=Ext.get(el);if(!forceNew&&el.updateManager){return el.updateManager;} +me.el=el;me.defaultUrl=null;me.addEvents(BEFOREUPDATE,UPDATE,FAILURE);Ext.apply(me,Ext.Updater.defaults);me.transaction=null;me.refreshDelegate=me.refresh.createDelegate(me);me.updateDelegate=me.update.createDelegate(me);me.formUpdateDelegate=(me.formUpdate||function(){}).createDelegate(me);me.renderer=me.renderer||me.getDefaultRenderer();Ext.Updater.superclass.constructor.call(me);},setRenderer:function(renderer){this.renderer=renderer;},getRenderer:function(){return this.renderer;},getDefaultRenderer:function(){return new Ext.Updater.BasicRenderer();},setDefaultUrl:function(defaultUrl){this.defaultUrl=defaultUrl;},getEl:function(){return this.el;},update:function(url,params,callback,discardUrl){var me=this,cfg,callerScope;if(me.fireEvent(BEFOREUPDATE,me.el,url,params)!==false){if(Ext.isObject(url)){cfg=url;url=cfg.url;params=params||cfg.params;callback=callback||cfg.callback;discardUrl=discardUrl||cfg.discardUrl;callerScope=cfg.scope;if(!Ext.isEmpty(cfg.nocache)){me.disableCaching=cfg.nocache;};if(!Ext.isEmpty(cfg.text)){me.indicatorText='
      '+cfg.text+"
      ";};if(!Ext.isEmpty(cfg.scripts)){me.loadScripts=cfg.scripts;};if(!Ext.isEmpty(cfg.timeout)){me.timeout=cfg.timeout;};} +me.showLoading();if(!discardUrl){me.defaultUrl=url;} +if(Ext.isFunction(url)){url=url.call(me);} +var o=Ext.apply({},{url:url,params:(Ext.isFunction(params)&&callerScope)?params.createDelegate(callerScope):params,success:processSuccess,failure:processFailure,scope:me,callback:undefined,timeout:(me.timeout*1000),disableCaching:me.disableCaching,argument:{"options":cfg,"url":url,"form":null,"callback":callback,"scope":callerScope||window,"params":params}},cfg);me.transaction=Ext.Ajax.request(o);}},formUpdate:function(form,url,reset,callback){var me=this;if(me.fireEvent(BEFOREUPDATE,me.el,form,url)!==false){if(Ext.isFunction(url)){url=url.call(me);} +form=Ext.getDom(form);me.transaction=Ext.Ajax.request({form:form,url:url,success:processSuccess,failure:processFailure,scope:me,timeout:(me.timeout*1000),argument:{"url":url,"form":form,"callback":callback,"reset":reset}});me.showLoading.defer(1,me);}},startAutoRefresh:function(interval,url,params,callback,refreshNow){var me=this;if(refreshNow){me.update(url||me.defaultUrl,params,callback,true);} +if(me.autoRefreshProcId){clearInterval(me.autoRefreshProcId);} +me.autoRefreshProcId=setInterval(me.update.createDelegate(me,[url||me.defaultUrl,params,callback,true]),interval*1000);},stopAutoRefresh:function(){if(this.autoRefreshProcId){clearInterval(this.autoRefreshProcId);delete this.autoRefreshProcId;}},isAutoRefreshing:function(){return!!this.autoRefreshProcId;},showLoading:function(){if(this.showLoadIndicator){this.el.dom.innerHTML=this.indicatorText;}},abort:function(){if(this.transaction){Ext.Ajax.abort(this.transaction);}},isUpdating:function(){return this.transaction?Ext.Ajax.isLoading(this.transaction):false;},refresh:function(callback){if(this.defaultUrl){this.update(this.defaultUrl,null,callback,true);}}};}());Ext.Updater.defaults={timeout:30,disableCaching:false,showLoadIndicator:true,indicatorText:'
      Loading...
      ',loadScripts:false,sslBlankUrl:Ext.SSL_SECURE_URL};Ext.Updater.updateElement=function(el,url,params,options){var um=Ext.get(el).getUpdater();Ext.apply(um,options);um.update(url,params,options?options.callback:null);};Ext.Updater.BasicRenderer=function(){};Ext.Updater.BasicRenderer.prototype={render:function(el,response,updateManager,callback){el.update(response.responseText,updateManager.loadScripts,callback);}};(function(){Date.useStrict=false;function xf(format){var args=Array.prototype.slice.call(arguments,1);return format.replace(/\{(\d+)\}/g,function(m,i){return args[i];});} +Date.formatCodeToRegex=function(character,currentGroup){var p=Date.parseCodes[character];if(p){p=typeof p=='function'?p():p;Date.parseCodes[character]=p;} +return p?Ext.applyIf({c:p.c?xf(p.c,currentGroup||"{0}"):p.c},p):{g:0,c:null,s:Ext.escapeRe(character)};};var $f=Date.formatCodeToRegex;Ext.apply(Date,{parseFunctions:{"M$":function(input,strict){var re=new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');var r=(input||'').match(re);return r?new Date(((r[1]||'')+r[2])*1):null;}},parseRegexes:[],formatFunctions:{"M$":function(){return'\\/Date('+this.getTime()+')\\/';}},y2kYear:50,MILLI:"ms",SECOND:"s",MINUTE:"mi",HOUR:"h",DAY:"d",MONTH:"mo",YEAR:"y",defaults:{},dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNumbers:{Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11},getShortMonthName:function(month){return Date.monthNames[month].substring(0,3);},getShortDayName:function(day){return Date.dayNames[day].substring(0,3);},getMonthNumber:function(name){return Date.monthNumbers[name.substring(0,1).toUpperCase()+name.substring(1,3).toLowerCase()];},formatCodes:{d:"String.leftPad(this.getDate(), 2, '0')",D:"Date.getShortDayName(this.getDay())",j:"this.getDate()",l:"Date.dayNames[this.getDay()]",N:"(this.getDay() ? this.getDay() : 7)",S:"this.getSuffix()",w:"this.getDay()",z:"this.getDayOfYear()",W:"String.leftPad(this.getWeekOfYear(), 2, '0')",F:"Date.monthNames[this.getMonth()]",m:"String.leftPad(this.getMonth() + 1, 2, '0')",M:"Date.getShortMonthName(this.getMonth())",n:"(this.getMonth() + 1)",t:"this.getDaysInMonth()",L:"(this.isLeapYear() ? 1 : 0)",o:"(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",Y:"String.leftPad(this.getFullYear(), 4, '0')",y:"('' + this.getFullYear()).substring(2, 4)",a:"(this.getHours() < 12 ? 'am' : 'pm')",A:"(this.getHours() < 12 ? 'AM' : 'PM')",g:"((this.getHours() % 12) ? this.getHours() % 12 : 12)",G:"this.getHours()",h:"String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",H:"String.leftPad(this.getHours(), 2, '0')",i:"String.leftPad(this.getMinutes(), 2, '0')",s:"String.leftPad(this.getSeconds(), 2, '0')",u:"String.leftPad(this.getMilliseconds(), 3, '0')",O:"this.getGMTOffset()",P:"this.getGMTOffset(true)",T:"this.getTimezone()",Z:"(this.getTimezoneOffset() * -60)",c:function(){for(var c="Y-m-dTH:i:sP",code=[],i=0,l=c.length;i= 0 && y >= 0){","v = new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);","v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);","}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){","v = null;","}else{","v = new Date(y < 100 ? 100 : y, m, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);","}","}","}","if(v){","if(zz != null){","v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);","}else if(o){","v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));","}","}","return v;"].join('\n');return function(format){var regexNum=Date.parseRegexes.length,currentGroup=1,calc=[],regex=[],special=false,ch="",i=0,obj,last;for(;i Date.y2kYear ? 1900 + ty : 2000 + ty;\n",s:"(\\d{1,2})"},a:function(){return $f("A");},A:{calcLast:true,g:1,c:"if (/(am)/i.test(results[{0}])) {\n" ++"if (!h || h == 12) { h = 0; }\n" ++"} else { if (!h || h < 12) { h = (h || 0) + 12; }}",s:"(AM|PM|am|pm)"},g:function(){return $f("G");},G:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{1,2})"},h:function(){return $f("H");},H:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},i:{g:1,c:"i = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},s:{g:1,c:"s = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},u:{g:1,c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",s:"(\\d+)"},O:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),","mn = o.substring(3,5) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+\-]\\d{4})"},P:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),","mn = o.substring(4,6) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+\-]\\d{2}:\\d{2})"},T:{g:0,c:null,s:"[A-Z]{1,4}"},Z:{g:1,c:"zz = results[{0}] * 1;\n" ++"zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",s:"([+\-]?\\d{1,5})"},c:function(){var calc=[],arr=[$f("Y",1),$f("m",2),$f("d",3),$f("h",4),$f("i",5),$f("s",6),{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"},{c:["if(results[8]) {","if(results[8] == 'Z'){","zz = 0;","}else if (results[8].indexOf(':') > -1){",$f("P",8).c,"}else{",$f("O",8).c,"}","}"].join('\n')}];for(var i=0,l=arr.length;i0?"-":"+") ++String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset())/60),2,"0") ++(colon?":":"") ++String.leftPad(Math.abs(this.getTimezoneOffset()%60),2,"0");},getDayOfYear:function(){var num=0,d=this.clone(),m=this.getMonth(),i;for(i=0,d.setDate(1),d.setMonth(0);i28){day=Math.min(day,this.getFirstDateOfMonth().add('mo',value).getLastDateOfMonth().getDate());} +d.setDate(day);d.setMonth(this.getMonth()+value);break;case Date.YEAR:d.setFullYear(this.getFullYear()+value);break;} +return d;},between:function(start,end){var t=this.getTime();return start.getTime()<=t&&t<=end.getTime();}});Date.prototype.format=Date.prototype.dateFormat;if(Ext.isSafari&&(navigator.userAgent.match(/WebKit\/(\d+)/)[1]||NaN)<420){Ext.apply(Date.prototype,{_xMonth:Date.prototype.setMonth,_xDate:Date.prototype.setDate,setMonth:function(num){if(num<=-1){var n=Math.ceil(-num),back_year=Math.ceil(n/12),month=(n%12)?12-n%12:0;this.setFullYear(this.getFullYear()-back_year);return this._xMonth(month);}else{return this._xMonth(num);}},setDate:function(d){return this.setTime(this.getTime()-(this.getDate()-d)*864e5);}});} +Ext.util.MixedCollection=function(allowFunctions,keyFn){this.items=[];this.map={};this.keys=[];this.length=0;this.addEvents('clear','add','replace','remove','sort');this.allowFunctions=allowFunctions===true;if(keyFn){this.getKey=keyFn;} +Ext.util.MixedCollection.superclass.constructor.call(this);};Ext.extend(Ext.util.MixedCollection,Ext.util.Observable,{allowFunctions:false,add:function(key,o){if(arguments.length==1){o=arguments[0];key=this.getKey(o);} +if(typeof key!='undefined'&&key!==null){var old=this.map[key];if(typeof old!='undefined'){return this.replace(key,o);} +this.map[key]=o;} +this.length++;this.items.push(o);this.keys.push(key);this.fireEvent('add',this.length-1,o,key);return o;},getKey:function(o){return o.id;},replace:function(key,o){if(arguments.length==1){o=arguments[0];key=this.getKey(o);} +var old=this.map[key];if(typeof key=='undefined'||key===null||typeof old=='undefined'){return this.add(key,o);} +var index=this.indexOfKey(key);this.items[index]=o;this.map[key]=o;this.fireEvent('replace',key,old,o);return o;},addAll:function(objs){if(arguments.length>1||Ext.isArray(objs)){var args=arguments.length>1?arguments:objs;for(var i=0,len=args.length;i=this.length){return this.add(key,o);} +this.length++;this.items.splice(index,0,o);if(typeof key!='undefined'&&key!==null){this.map[key]=o;} +this.keys.splice(index,0,key);this.fireEvent('add',index,o,key);return o;},remove:function(o){return this.removeAt(this.indexOf(o));},removeAt:function(index){if(index=0){this.length--;var o=this.items[index];this.items.splice(index,1);var key=this.keys[index];if(typeof key!='undefined'){delete this.map[key];} +this.keys.splice(index,1);this.fireEvent('remove',o,key);return o;} +return false;},removeKey:function(key){return this.removeAt(this.indexOfKey(key));},getCount:function(){return this.length;},indexOf:function(o){return this.items.indexOf(o);},indexOfKey:function(key){return this.keys.indexOf(key);},item:function(key){var mk=this.map[key],item=mk!==undefined?mk:(typeof key=='number')?this.items[key]:undefined;return typeof item!='function'||this.allowFunctions?item:null;},itemAt:function(index){return this.items[index];},key:function(key){return this.map[key];},contains:function(o){return this.indexOf(o)!=-1;},containsKey:function(key){return typeof this.map[key]!='undefined';},clear:function(){this.length=0;this.items=[];this.keys=[];this.map={};this.fireEvent('clear');},first:function(){return this.items[0];},last:function(){return this.items[this.length-1];},_sort:function(property,dir,fn){var i,len,dsc=String(dir).toUpperCase()=='DESC'?-1:1,c=[],keys=this.keys,items=this.items;fn=fn||function(a,b){return a-b;};for(i=0,len=items.length;iv2?1:(v1=end;i--){r[r.length]=items[i];}} +return r;},filter:function(property,value,anyMatch,caseSensitive){if(Ext.isEmpty(value,false)){return this.clone();} +value=this.createValueMatcher(value,anyMatch,caseSensitive);return this.filterBy(function(o){return o&&value.test(o[property]);});},filterBy:function(fn,scope){var r=new Ext.util.MixedCollection();r.getKey=this.getKey;var k=this.keys,it=this.items;for(var i=0,len=it.length;i]+>/gi,stripScriptsRe=/(?:)((\n|\r|.)*?)(?:<\/script>)/ig,nl2brRe=/\r?\n/g;return{ellipsis:function(value,len,word){if(value&&value.length>len){if(word){var vs=value.substr(0,len-2),index=Math.max(vs.lastIndexOf(' '),vs.lastIndexOf('.'),vs.lastIndexOf('!'),vs.lastIndexOf('?'));if(index==-1||index<(len-15)){return value.substr(0,len-3)+"...";}else{return vs.substr(0,index)+"...";}}else{return value.substr(0,len-3)+"...";}} +return value;},undef:function(value){return value!==undefined?value:"";},defaultValue:function(value,defaultValue){return value!==undefined&&value!==''?value:defaultValue;},htmlEncode:function(value){return!value?value:String(value).replace(/&/g,"&").replace(/>/g,">").replace(/").replace(/</g,"<").replace(/"/g,'"').replace(/&/g,"&");},trim:function(value){return String(value).replace(trimRe,"");},substr:function(value,start,length){return String(value).substr(start,length);},lowercase:function(value){return String(value).toLowerCase();},uppercase:function(value){return String(value).toUpperCase();},capitalize:function(value){return!value?value:value.charAt(0).toUpperCase()+value.substr(1).toLowerCase();},call:function(value,fn){if(arguments.length>2){var args=Array.prototype.slice.call(arguments,2);args.unshift(value);return eval(fn).apply(window,args);}else{return eval(fn).call(window,value);}},usMoney:function(v){v=(Math.round((v-0)*100))/100;v=(v==Math.floor(v))?v+".00":((v*10==Math.floor(v*10))?v+"0":v);v=String(v);var ps=v.split('.'),whole=ps[0],sub=ps[1]?'.'+ps[1]:'.00',r=/(\d+)(\d{3})/;while(r.test(whole)){whole=whole.replace(r,'$1'+','+'$2');} +v=whole+sub;if(v.charAt(0)=='-'){return'-$'+v.substr(1);} +return"$"+v;},date:function(v,format){if(!v){return"";} +if(!Ext.isDate(v)){v=new Date(Date.parse(v));} +return v.dateFormat(format||"m/d/Y");},dateRenderer:function(format){return function(v){return Ext.util.Format.date(v,format);};},stripTags:function(v){return!v?v:String(v).replace(stripTagsRE,"");},stripScripts:function(v){return!v?v:String(v).replace(stripScriptsRe,"");},fileSize:function(size){if(size<1024){return size+" bytes";}else if(size<1048576){return(Math.round(((size*10)/1024))/10)+" KB";}else{return(Math.round(((size*10)/1048576))/10)+" MB";}},math:function(){var fns={};return function(v,a){if(!fns[a]){fns[a]=new Function('v','return v '+a+';');} +return fns[a](v);};}(),round:function(value,precision){var result=Number(value);if(typeof precision=='number'){precision=Math.pow(10,precision);result=Math.round(value*precision)/precision;} +return result;},number:function(v,format){if(!format){return v;} +v=Ext.num(v,NaN);if(isNaN(v)){return'';} +var comma=',',dec='.',i18n=false,neg=v<0;v=Math.abs(v);if(format.substr(format.length-2)=='/i'){format=format.substr(0,format.length-2);i18n=true;comma='.';dec=',';} +var hasComma=format.indexOf(comma)!=-1,psplit=(i18n?format.replace(/[^\d\,]/g,''):format.replace(/[^\d\.]/g,'')).split(dec);if(1');}};}();Ext.XTemplate=function(){Ext.XTemplate.superclass.constructor.apply(this,arguments);var me=this,s=me.html,re=/]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,nameRe=/^]*?for="(.*?)"/,ifRe=/^]*?if="(.*?)"/,execRe=/^]*?exec="(.*?)"/,m,id=0,tpls=[],VALUES='values',PARENT='parent',XINDEX='xindex',XCOUNT='xcount',RETURN='return ',WITHVALUES='with(values){ ';s=['',s,''].join('');while((m=s.match(re))){var m2=m[0].match(nameRe),m3=m[0].match(ifRe),m4=m[0].match(execRe),exp=null,fn=null,exec=null,name=m2&&m2[1]?m2[1]:'';if(m3){exp=m3&&m3[1]?m3[1]:null;if(exp){fn=new Function(VALUES,PARENT,XINDEX,XCOUNT,WITHVALUES+RETURN+(Ext.util.Format.htmlDecode(exp))+'; }');}} +if(m4){exp=m4&&m4[1]?m4[1]:null;if(exp){exec=new Function(VALUES,PARENT,XINDEX,XCOUNT,WITHVALUES+(Ext.util.Format.htmlDecode(exp))+'; }');}} +if(name){switch(name){case'.':name=new Function(VALUES,PARENT,WITHVALUES+RETURN+VALUES+'; }');break;case'..':name=new Function(VALUES,PARENT,WITHVALUES+RETURN+PARENT+'; }');break;default:name=new Function(VALUES,PARENT,WITHVALUES+RETURN+name+'; }');}} +tpls.push({id:id,target:name,exec:exec,test:fn,body:m[1]||''});s=s.replace(m[0],'{xtpl'+id+'}');++id;} +for(var i=tpls.length-1;i>=0;--i){me.compileTpl(tpls[i]);} +me.master=tpls[tpls.length-1];me.tpls=tpls;};Ext.extend(Ext.XTemplate,Ext.Template,{re:/\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,codeRe:/\{\[((?:\\\]|.|\n)*?)\]\}/g,applySubTemplate:function(id,values,parent,xindex,xcount){var me=this,len,t=me.tpls[id],vs,buf=[];if((t.test&&!t.test.call(me,values,parent,xindex,xcount))||(t.exec&&t.exec.call(me,values,parent,xindex,xcount))){return'';} +vs=t.target?t.target.call(me,values,parent):values;len=vs.length;parent=t.target?values:parent;if(t.target&&Ext.isArray(vs)){for(var i=0,len=vs.length;i=0;--j){rules[ssRules[j].selectorText.toLowerCase()]=ssRules[j];}}catch(e){}},getRules:function(refreshCache){if(rules===null||refreshCache){rules={};var ds=doc.styleSheets;for(var i=0,len=ds.length;i=37&&k<=40){e.stopEvent();}},destroy:function(){this.disable();},enable:function(){if(this.disabled){if(Ext.isSafari2){this.el.on('keyup',this.stopKeyUp,this);} +this.el.on(this.isKeydown()?'keydown':'keypress',this.relay,this);this.disabled=false;}},disable:function(){if(!this.disabled){if(Ext.isSafari2){this.el.un('keyup',this.stopKeyUp,this);} +this.el.un(this.isKeydown()?'keydown':'keypress',this.relay,this);this.disabled=true;}},setDisabled:function(disabled){this[disabled?"disable":"enable"]();},isKeydown:function(){return this.forceKeyDown||Ext.EventManager.useKeydown;}};Ext.KeyMap=function(el,config,eventName){this.el=Ext.get(el);this.eventName=eventName||"keydown";this.bindings=[];if(config){this.addBinding(config);} +this.enable();};Ext.KeyMap.prototype={stopEvent:false,addBinding:function(config){if(Ext.isArray(config)){Ext.each(config,function(c){this.addBinding(c);},this);return;} +var keyCode=config.key,fn=config.fn||config.handler,scope=config.scope;if(config.stopEvent){this.stopEvent=config.stopEvent;} +if(typeof keyCode=="string"){var ks=[];var keyString=keyCode.toUpperCase();for(var j=0,len=keyString.length;j2)?argv[2]:null;var path=(argc>3)?argv[3]:'/';var domain=(argc>4)?argv[4]:null;var secure=(argc>5)?argv[5]:false;document.cookie=name+"="+escape(value)+((expires===null)?"":("; expires="+expires.toGMTString()))+((path===null)?"":("; path="+path))+((domain===null)?"":("; domain="+domain))+((secure===true)?"; secure":"");},get:function(name){var arg=name+"=";var alen=arg.length;var clen=document.cookie.length;var i=0;var j=0;while(i0){return this.ownerCt.items.itemAt(index-1);}} +return null;},getBubbleTarget:function(){return this.ownerCt;}});Ext.reg('component',Ext.Component);Ext.Action=Ext.extend(Object,{constructor:function(config){this.initialConfig=config;this.itemId=config.itemId=(config.itemId||config.id||Ext.id());this.items=[];},isAction:true,setText:function(text){this.initialConfig.text=text;this.callEach('setText',[text]);},getText:function(){return this.initialConfig.text;},setIconClass:function(cls){this.initialConfig.iconCls=cls;this.callEach('setIconClass',[cls]);},getIconClass:function(){return this.initialConfig.iconCls;},setDisabled:function(v){this.initialConfig.disabled=v;this.callEach('setDisabled',[v]);},enable:function(){this.setDisabled(false);},disable:function(){this.setDisabled(true);},isDisabled:function(){return this.initialConfig.disabled;},setHidden:function(v){this.initialConfig.hidden=v;this.callEach('setVisible',[!v]);},show:function(){this.setHidden(false);},hide:function(){this.setHidden(true);},isHidden:function(){return this.initialConfig.hidden;},setHandler:function(fn,scope){this.initialConfig.handler=fn;this.initialConfig.scope=scope;this.callEach('setHandler',[fn,scope]);},each:function(fn,scope){Ext.each(this.items,fn,scope);},callEach:function(fnName,args){var cs=this.items;for(var i=0,len=cs.length;ivw+s.left){x=vw-w-so;moved=true;} +if((y+h)>vh+s.top){y=vh-h-so;moved=true;} +if(x=ay){y=ay-h-5;}} +xy=[x,y];this.storeXY(xy);supr.setXY.call(this,xy);this.sync();}} +return this;},getConstrainOffset:function(){return this.shadowOffset;},isVisible:function(){return this.visible;},showAction:function(){this.visible=true;if(this.useDisplay===true){this.setDisplayed('');}else if(this.lastXY){supr.setXY.call(this,this.lastXY);}else if(this.lastLT){supr.setLeftTop.call(this,this.lastLT[0],this.lastLT[1]);}},hideAction:function(){this.visible=false;if(this.useDisplay===true){this.setDisplayed(false);}else{this.setLeftTop(-10000,-10000);}},setVisible:function(v,a,d,c,e){if(v){this.showAction();} +if(a&&v){var cb=function(){this.sync(true);if(c){c();}}.createDelegate(this);supr.setVisible.call(this,true,true,d,cb,e);}else{if(!v){this.hideUnders(true);} +var cb=c;if(a){cb=function(){this.hideAction();if(c){c();}}.createDelegate(this);} +supr.setVisible.call(this,v,a,d,cb,e);if(v){this.sync(true);}else if(!a){this.hideAction();}} +return this;},storeXY:function(xy){delete this.lastLT;this.lastXY=xy;},storeLeftTop:function(left,top){delete this.lastXY;this.lastLT=[left,top];},beforeFx:function(){this.beforeAction();return Ext.Layer.superclass.beforeFx.apply(this,arguments);},afterFx:function(){Ext.Layer.superclass.afterFx.apply(this,arguments);this.sync(this.isVisible());},beforeAction:function(){if(!this.updating&&this.shadow){this.shadow.hide();}},setLeft:function(left){this.storeLeftTop(left,this.getTop(true));supr.setLeft.apply(this,arguments);this.sync();return this;},setTop:function(top){this.storeLeftTop(this.getLeft(true),top);supr.setTop.apply(this,arguments);this.sync();return this;},setLeftTop:function(left,top){this.storeLeftTop(left,top);supr.setLeftTop.apply(this,arguments);this.sync();return this;},setXY:function(xy,a,d,c,e){this.fixDisplay();this.beforeAction();this.storeXY(xy);var cb=this.createCB(c);supr.setXY.call(this,xy,a,d,cb,e);if(!a){cb();} +return this;},createCB:function(c){var el=this;return function(){el.constrainXY();el.sync(true);if(c){c();}};},setX:function(x,a,d,c,e){this.setXY([x,this.getY()],a,d,c,e);return this;},setY:function(y,a,d,c,e){this.setXY([this.getX(),y],a,d,c,e);return this;},setSize:function(w,h,a,d,c,e){this.beforeAction();var cb=this.createCB(c);supr.setSize.call(this,w,h,a,d,cb,e);if(!a){cb();} +return this;},setWidth:function(w,a,d,c,e){this.beforeAction();var cb=this.createCB(c);supr.setWidth.call(this,w,a,d,cb,e);if(!a){cb();} +return this;},setHeight:function(h,a,d,c,e){this.beforeAction();var cb=this.createCB(c);supr.setHeight.call(this,h,a,d,cb,e);if(!a){cb();} +return this;},setBounds:function(x,y,w,h,a,d,c,e){this.beforeAction();var cb=this.createCB(c);if(!a){this.storeXY([x,y]);supr.setXY.call(this,[x,y]);supr.setSize.call(this,w,h,a,d,cb,e);cb();}else{supr.setBounds.call(this,x,y,w,h,a,d,cb,e);} +return this;},setZIndex:function(zindex){this.zindex=zindex;this.setStyle('z-index',zindex+2);if(this.shadow){this.shadow.setZIndex(zindex+1);} +if(this.shim){this.shim.setStyle('z-index',zindex);} +return this;}});})();Ext.Shadow=function(config){Ext.apply(this,config);if(typeof this.mode!="string"){this.mode=this.defaultMode;} +var o=this.offset,a={h:0},rad=Math.floor(this.offset/2);switch(this.mode.toLowerCase()){case"drop":a.w=0;a.l=a.t=o;a.t-=1;if(Ext.isIE){a.l-=this.offset+rad;a.t-=this.offset+rad;a.w-=rad;a.h-=rad;a.t+=1;} +break;case"sides":a.w=(o*2);a.l=-o;a.t=o-1;if(Ext.isIE){a.l-=(this.offset-rad);a.t-=this.offset+rad;a.l+=1;a.w-=(this.offset-rad)*2;a.w-=rad+1;a.h-=1;} +break;case"frame":a.w=a.h=(o*2);a.l=a.t=-o;a.t+=1;a.h-=2;if(Ext.isIE){a.l-=(this.offset-rad);a.t-=(this.offset-rad);a.l+=1;a.w-=(this.offset+rad+1);a.h-=(this.offset+rad);a.h+=1;} +break;};this.adjusts=a;};Ext.Shadow.prototype={offset:4,defaultMode:"drop",show:function(target){target=Ext.get(target);if(!this.el){this.el=Ext.Shadow.Pool.pull();if(this.el.dom.nextSibling!=target.dom){this.el.insertBefore(target);}} +this.el.setStyle("z-index",this.zIndex||parseInt(target.getStyle("z-index"),10)-1);if(Ext.isIE){this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")";} +this.realign(target.getLeft(true),target.getTop(true),target.getWidth(),target.getHeight());this.el.dom.style.display="block";},isVisible:function(){return this.el?true:false;},realign:function(l,t,w,h){if(!this.el){return;} +var a=this.adjusts,d=this.el.dom,s=d.style,iea=0,sw=(w+a.w),sh=(h+a.h),sws=sw+"px",shs=sh+"px",cn,sww;s.left=(l+a.l)+"px";s.top=(t+a.t)+"px";if(s.width!=sws||s.height!=shs){s.width=sws;s.height=shs;if(!Ext.isIE){cn=d.childNodes;sww=Math.max(0,(sw-12))+"px";cn[0].childNodes[1].style.width=sww;cn[1].childNodes[1].style.width=sww;cn[2].childNodes[1].style.width=sww;cn[1].style.height=Math.max(0,(sh-12))+"px";}}},hide:function(){if(this.el){this.el.dom.style.display="none";Ext.Shadow.Pool.push(this.el);delete this.el;}},setZIndex:function(z){this.zIndex=z;if(this.el){this.el.setStyle("z-index",z);}}};Ext.Shadow.Pool=function(){var p=[],markup=Ext.isIE?'
      ':'
      ';return{pull:function(){var sh=p.shift();if(!sh){sh=Ext.get(Ext.DomHelper.insertHtml("beforeBegin",document.body.firstChild,markup));sh.autoBoxAdjust=false;} +return sh;},push:function(sh){p.push(sh);}};}();Ext.BoxComponent=Ext.extend(Ext.Component,{initComponent:function(){Ext.BoxComponent.superclass.initComponent.call(this);this.addEvents('resize','move');},boxReady:false,deferHeight:false,setSize:function(w,h){if(typeof w=='object'){h=w.height;w=w.width;} +if(Ext.isDefined(w)&&Ext.isDefined(this.boxMinWidth)&&(wthis.boxMaxWidth)){w=this.boxMaxWidth;} +if(Ext.isDefined(h)&&Ext.isDefined(this.boxMaxHeight)&&(h>this.boxMaxHeight)){h=this.boxMaxHeight;} +if(!this.boxReady){this.width=w;this.height=h;return this;} +if(this.cacheSizes!==false&&this.lastSize&&this.lastSize.width==w&&this.lastSize.height==h){return this;} +this.lastSize={width:w,height:h};var adj=this.adjustSize(w,h),aw=adj.width,ah=adj.height,rz;if(aw!==undefined||ah!==undefined){rz=this.getResizeEl();if(!this.deferHeight&&aw!==undefined&&ah!==undefined){rz.setSize(aw,ah);}else if(!this.deferHeight&&ah!==undefined){rz.setHeight(ah);}else if(aw!==undefined){rz.setWidth(aw);} +this.onResize(aw,ah,w,h);this.fireEvent('resize',this,aw,ah,w,h);} +return this;},setWidth:function(width){return this.setSize(width);},setHeight:function(height){return this.setSize(undefined,height);},getSize:function(){return this.getResizeEl().getSize();},getWidth:function(){return this.getResizeEl().getWidth();},getHeight:function(){return this.getResizeEl().getHeight();},getOuterSize:function(){var el=this.getResizeEl();return{width:el.getWidth()+el.getMargins('lr'),height:el.getHeight()+el.getMargins('tb')};},getPosition:function(local){var el=this.getPositionEl();if(local===true){return[el.getLeft(true),el.getTop(true)];} +return this.xy||el.getXY();},getBox:function(local){var pos=this.getPosition(local);var s=this.getSize();s.x=pos[0];s.y=pos[1];return s;},updateBox:function(box){this.setSize(box.width,box.height);this.setPagePosition(box.x,box.y);return this;},getResizeEl:function(){return this.resizeEl||this.el;},setAutoScroll:function(scroll){if(this.rendered){this.getContentTarget().setOverflow(scroll?'auto':'');} +this.autoScroll=scroll;return this;},setPosition:function(x,y){if(x&&typeof x[1]=='number'){y=x[1];x=x[0];} +this.x=x;this.y=y;if(!this.boxReady){return this;} +var adj=this.adjustPosition(x,y);var ax=adj.x,ay=adj.y;var el=this.getPositionEl();if(ax!==undefined||ay!==undefined){if(ax!==undefined&&ay!==undefined){el.setLeftTop(ax,ay);}else if(ax!==undefined){el.setLeft(ax);}else if(ay!==undefined){el.setTop(ay);} +this.onPosition(ax,ay);this.fireEvent('move',this,ax,ay);} +return this;},setPagePosition:function(x,y){if(x&&typeof x[1]=='number'){y=x[1];x=x[0];} +this.pageX=x;this.pageY=y;if(!this.boxReady){return;} +if(x===undefined||y===undefined){return;} +var p=this.getPositionEl().translatePoints(x,y);this.setPosition(p.left,p.top);return this;},afterRender:function(){Ext.BoxComponent.superclass.afterRender.call(this);if(this.resizeEl){this.resizeEl=Ext.get(this.resizeEl);} +if(this.positionEl){this.positionEl=Ext.get(this.positionEl);} +this.boxReady=true;Ext.isDefined(this.autoScroll)&&this.setAutoScroll(this.autoScroll);this.setSize(this.width,this.height);if(this.x||this.y){this.setPosition(this.x,this.y);}else if(this.pageX||this.pageY){this.setPagePosition(this.pageX,this.pageY);}},syncSize:function(){delete this.lastSize;this.setSize(this.autoWidth?undefined:this.getResizeEl().getWidth(),this.autoHeight?undefined:this.getResizeEl().getHeight());return this;},onResize:function(adjWidth,adjHeight,rawWidth,rawHeight){},onPosition:function(x,y){},adjustSize:function(w,h){if(this.autoWidth){w='auto';} +if(this.autoHeight){h='auto';} +return{width:w,height:h};},adjustPosition:function(x,y){return{x:x,y:y};}});Ext.reg('box',Ext.BoxComponent);Ext.Spacer=Ext.extend(Ext.BoxComponent,{autoEl:'div'});Ext.reg('spacer',Ext.Spacer);Ext.SplitBar=function(dragElement,resizingElement,orientation,placement,existingProxy){this.el=Ext.get(dragElement,true);this.el.dom.unselectable="on";this.resizingEl=Ext.get(resizingElement,true);this.orientation=orientation||Ext.SplitBar.HORIZONTAL;this.minSize=0;this.maxSize=2000;this.animate=false;this.useShim=false;this.shim=null;if(!existingProxy){this.proxy=Ext.SplitBar.createProxy(this.orientation);}else{this.proxy=Ext.get(existingProxy).dom;} +this.dd=new Ext.dd.DDProxy(this.el.dom.id,"XSplitBars",{dragElId:this.proxy.id});this.dd.b4StartDrag=this.onStartProxyDrag.createDelegate(this);this.dd.endDrag=this.onEndProxyDrag.createDelegate(this);this.dragSpecs={};this.adapter=new Ext.SplitBar.BasicLayoutAdapter();this.adapter.init(this);if(this.orientation==Ext.SplitBar.HORIZONTAL){this.placement=placement||(this.el.getX()>this.resizingEl.getX()?Ext.SplitBar.LEFT:Ext.SplitBar.RIGHT);this.el.addClass("x-splitbar-h");}else{this.placement=placement||(this.el.getY()>this.resizingEl.getY()?Ext.SplitBar.TOP:Ext.SplitBar.BOTTOM);this.el.addClass("x-splitbar-v");} +this.addEvents("resize","moved","beforeresize","beforeapply");Ext.SplitBar.superclass.constructor.call(this);};Ext.extend(Ext.SplitBar,Ext.util.Observable,{onStartProxyDrag:function(x,y){this.fireEvent("beforeresize",this);this.overlay=Ext.DomHelper.append(document.body,{cls:"x-drag-overlay",html:" "},true);this.overlay.unselectable();this.overlay.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true));this.overlay.show();Ext.get(this.proxy).setDisplayed("block");var size=this.adapter.getElementSize(this);this.activeMinSize=this.getMinimumSize();this.activeMaxSize=this.getMaximumSize();var c1=size-this.activeMinSize;var c2=Math.max(this.activeMaxSize-size,0);if(this.orientation==Ext.SplitBar.HORIZONTAL){this.dd.resetConstraints();this.dd.setXConstraint(this.placement==Ext.SplitBar.LEFT?c1:c2,this.placement==Ext.SplitBar.LEFT?c2:c1,this.tickSize);this.dd.setYConstraint(0,0);}else{this.dd.resetConstraints();this.dd.setXConstraint(0,0);this.dd.setYConstraint(this.placement==Ext.SplitBar.TOP?c1:c2,this.placement==Ext.SplitBar.TOP?c2:c1,this.tickSize);} +this.dragSpecs.startSize=size;this.dragSpecs.startPoint=[x,y];Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd,x,y);},onEndProxyDrag:function(e){Ext.get(this.proxy).setDisplayed(false);var endPoint=Ext.lib.Event.getXY(e);if(this.overlay){Ext.destroy(this.overlay);delete this.overlay;} +var newSize;if(this.orientation==Ext.SplitBar.HORIZONTAL){newSize=this.dragSpecs.startSize+ +(this.placement==Ext.SplitBar.LEFT?endPoint[0]-this.dragSpecs.startPoint[0]:this.dragSpecs.startPoint[0]-endPoint[0]);}else{newSize=this.dragSpecs.startSize+ +(this.placement==Ext.SplitBar.TOP?endPoint[1]-this.dragSpecs.startPoint[1]:this.dragSpecs.startPoint[1]-endPoint[1]);} +newSize=Math.min(Math.max(newSize,this.activeMinSize),this.activeMaxSize);if(newSize!=this.dragSpecs.startSize){if(this.fireEvent('beforeapply',this,newSize)!==false){this.adapter.setElementSize(this,newSize);this.fireEvent("moved",this,newSize);this.fireEvent("resize",this,newSize);}}},getAdapter:function(){return this.adapter;},setAdapter:function(adapter){this.adapter=adapter;this.adapter.init(this);},getMinimumSize:function(){return this.minSize;},setMinimumSize:function(minSize){this.minSize=minSize;},getMaximumSize:function(){return this.maxSize;},setMaximumSize:function(maxSize){this.maxSize=maxSize;},setCurrentSize:function(size){var oldAnimate=this.animate;this.animate=false;this.adapter.setElementSize(this,size);this.animate=oldAnimate;},destroy:function(removeEl){Ext.destroy(this.shim,Ext.get(this.proxy));this.dd.unreg();if(removeEl){this.el.remove();} +this.purgeListeners();}});Ext.SplitBar.createProxy=function(dir){var proxy=new Ext.Element(document.createElement("div"));document.body.appendChild(proxy.dom);proxy.unselectable();var cls='x-splitbar-proxy';proxy.addClass(cls+' '+(dir==Ext.SplitBar.HORIZONTAL?cls+'-h':cls+'-v'));return proxy.dom;};Ext.SplitBar.BasicLayoutAdapter=function(){};Ext.SplitBar.BasicLayoutAdapter.prototype={init:function(s){},getElementSize:function(s){if(s.orientation==Ext.SplitBar.HORIZONTAL){return s.resizingEl.getWidth();}else{return s.resizingEl.getHeight();}},setElementSize:function(s,newSize,onComplete){if(s.orientation==Ext.SplitBar.HORIZONTAL){if(!s.animate){s.resizingEl.setWidth(newSize);if(onComplete){onComplete(s,newSize);}}else{s.resizingEl.setWidth(newSize,true,.1,onComplete,'easeOut');}}else{if(!s.animate){s.resizingEl.setHeight(newSize);if(onComplete){onComplete(s,newSize);}}else{s.resizingEl.setHeight(newSize,true,.1,onComplete,'easeOut');}}}};Ext.SplitBar.AbsoluteLayoutAdapter=function(container){this.basic=new Ext.SplitBar.BasicLayoutAdapter();this.container=Ext.get(container);};Ext.SplitBar.AbsoluteLayoutAdapter.prototype={init:function(s){this.basic.init(s);},getElementSize:function(s){return this.basic.getElementSize(s);},setElementSize:function(s,newSize,onComplete){this.basic.setElementSize(s,newSize,this.moveSplitter.createDelegate(this,[s]));},moveSplitter:function(s){var yes=Ext.SplitBar;switch(s.placement){case yes.LEFT:s.el.setX(s.resizingEl.getRight());break;case yes.RIGHT:s.el.setStyle("right",(this.container.getWidth()-s.resizingEl.getLeft())+"px");break;case yes.TOP:s.el.setY(s.resizingEl.getBottom());break;case yes.BOTTOM:s.el.setY(s.resizingEl.getTop()-s.el.getHeight());break;}}};Ext.SplitBar.VERTICAL=1;Ext.SplitBar.HORIZONTAL=2;Ext.SplitBar.LEFT=1;Ext.SplitBar.RIGHT=2;Ext.SplitBar.TOP=3;Ext.SplitBar.BOTTOM=4;Ext.Container=Ext.extend(Ext.BoxComponent,{bufferResize:50,autoDestroy:true,forceLayout:false,defaultType:'panel',resizeEvent:'resize',bubbleEvents:['add','remove'],initComponent:function(){Ext.Container.superclass.initComponent.call(this);this.addEvents('afterlayout','beforeadd','beforeremove','add','remove');var items=this.items;if(items){delete this.items;this.add(items);}},initItems:function(){if(!this.items){this.items=new Ext.util.MixedCollection(false,this.getComponentId);this.getLayout();}},setLayout:function(layout){if(this.layout&&this.layout!=layout){this.layout.setContainer(null);} +this.layout=layout;this.initItems();layout.setContainer(this);},afterRender:function(){Ext.Container.superclass.afterRender.call(this);if(!this.layout){this.layout='auto';} +if(Ext.isObject(this.layout)&&!this.layout.layout){this.layoutConfig=this.layout;this.layout=this.layoutConfig.type;} +if(Ext.isString(this.layout)){this.layout=new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig);} +this.setLayout(this.layout);if(this.activeItem!==undefined&&this.layout.setActiveItem){var item=this.activeItem;delete this.activeItem;this.layout.setActiveItem(item);} +if(!this.ownerCt){this.doLayout(false,true);} +if(this.monitorResize===true){Ext.EventManager.onWindowResize(this.doLayout,this,[false]);}},getLayoutTarget:function(){return this.el;},getComponentId:function(comp){return comp.getItemId();},add:function(comp){this.initItems();var args=arguments.length>1;if(args||Ext.isArray(comp)){var result=[];Ext.each(args?arguments:comp,function(c){result.push(this.add(c));},this);return result;} +var c=this.lookupComponent(this.applyDefaults(comp));var index=this.items.length;if(this.fireEvent('beforeadd',this,c,index)!==false&&this.onBeforeAdd(c)!==false){this.items.add(c);c.onAdded(this,index);this.onAdd(c);this.fireEvent('add',this,c,index);} +return c;},onAdd:function(c){},onAdded:function(container,pos){this.ownerCt=container;this.initRef();this.cascade(function(c){c.initRef();});this.fireEvent('added',this,container,pos);},insert:function(index,comp){var args=arguments,length=args.length,result=[],i,c;this.initItems();if(length>2){for(i=length-1;i>=1;--i){result.push(this.insert(index,args[i]));} +return result;} +c=this.lookupComponent(this.applyDefaults(comp));index=Math.min(index,this.items.length);if(this.fireEvent('beforeadd',this,c,index)!==false&&this.onBeforeAdd(c)!==false){if(c.ownerCt==this){this.items.remove(c);} +this.items.insert(index,c);c.onAdded(this,index);this.onAdd(c);this.fireEvent('add',this,c,index);} +return c;},applyDefaults:function(c){var d=this.defaults;if(d){if(Ext.isFunction(d)){d=d.call(this,c);} +if(Ext.isString(c)){c=Ext.ComponentMgr.get(c);Ext.apply(c,d);}else if(!c.events){Ext.applyIf(c.isAction?c.initialConfig:c,d);}else{Ext.apply(c,d);}} +return c;},onBeforeAdd:function(item){if(item.ownerCt){item.ownerCt.remove(item,false);} +if(this.hideBorders===true){item.border=(item.border===true);}},remove:function(comp,autoDestroy){this.initItems();var c=this.getComponent(comp);if(c&&this.fireEvent('beforeremove',this,c)!==false){this.doRemove(c,autoDestroy);this.fireEvent('remove',this,c);} +return c;},onRemove:function(c){},doRemove:function(c,autoDestroy){var l=this.layout,hasLayout=l&&this.rendered;if(hasLayout){l.onRemove(c);} +this.items.remove(c);c.onRemoved();this.onRemove(c);if(autoDestroy===true||(autoDestroy!==false&&this.autoDestroy)){c.destroy();} +if(hasLayout){l.afterRemove(c);}},removeAll:function(autoDestroy){this.initItems();var item,rem=[],items=[];this.items.each(function(i){rem.push(i);});for(var i=0,len=rem.length;i','','
      ','
      ','');t.disableFormats=true;return t.compile();})(),destroy:function(){if(this.resizeTask&&this.resizeTask.cancel){this.resizeTask.cancel();} +if(this.container){this.container.un(this.container.resizeEvent,this.onResize,this);} +if(!Ext.isEmpty(this.targetCls)){var target=this.container.getLayoutTarget();if(target){target.removeClass(this.targetCls);}}}});Ext.layout.AutoLayout=Ext.extend(Ext.layout.ContainerLayout,{type:'auto',monitorResize:true,onLayout:function(ct,target){Ext.layout.AutoLayout.superclass.onLayout.call(this,ct,target);var cs=this.getRenderedItems(ct),len=cs.length,i,c;for(i=0;i0){item.setSize(size);}}});Ext.Container.LAYOUTS['fit']=Ext.layout.FitLayout;Ext.layout.CardLayout=Ext.extend(Ext.layout.FitLayout,{deferredRender:false,layoutOnCardChange:false,renderHidden:true,type:'card',setActiveItem:function(item){var ai=this.activeItem,ct=this.container;item=ct.getComponent(item);if(item&&ai!=item){if(ai){ai.hide();if(ai.hidden!==true){return false;} +ai.fireEvent('deactivate',ai);} +var layout=item.doLayout&&(this.layoutOnCardChange||!item.rendered);this.activeItem=item;delete item.deferLayout;item.show();this.layout();if(layout){item.doLayout();} +item.fireEvent('activate',item);}},renderAll:function(ct,target){if(this.deferredRender){this.renderItem(this.activeItem,undefined,target);}else{Ext.layout.CardLayout.superclass.renderAll.call(this,ct,target);}}});Ext.Container.LAYOUTS['card']=Ext.layout.CardLayout;Ext.layout.AnchorLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:'anchor',defaultAnchor:'100%',parseAnchorRE:/^(r|right|b|bottom)$/i,getLayoutTargetSize:function(){var target=this.container.getLayoutTarget(),ret={};if(target){ret=target.getViewSize();if(Ext.isIE&&Ext.isStrict&&ret.width==0){ret=target.getStyleSize();} +ret.width-=target.getPadding('lr');ret.height-=target.getPadding('tb');} +return ret;},onLayout:function(container,target){Ext.layout.AnchorLayout.superclass.onLayout.call(this,container,target);var size=this.getLayoutTargetSize(),containerWidth=size.width,containerHeight=size.height,overflow=target.getStyle('overflow'),components=this.getRenderedItems(container),len=components.length,boxes=[],box,anchorWidth,anchorHeight,component,anchorSpec,calcWidth,calcHeight,anchorsArray,totalHeight=0,i,el;if(containerWidth<20&&containerHeight<20){return;} +if(container.anchorSize){if(typeof container.anchorSize=='number'){anchorWidth=container.anchorSize;}else{anchorWidth=container.anchorSize.width;anchorHeight=container.anchorSize.height;}}else{anchorWidth=container.initialConfig.width;anchorHeight=container.initialConfig.height;} +for(i=0;i ');tt.disableFormats=true;tt.compile();Ext.layout.BorderLayout.Region.prototype.toolTemplate=tt;} +this.collapsedEl=this.targetEl.createChild({cls:"x-layout-collapsed x-layout-collapsed-"+this.position,id:this.panel.id+'-xcollapsed'});this.collapsedEl.enableDisplayMode('block');if(this.collapseMode=='mini'){this.collapsedEl.addClass('x-layout-cmini-'+this.position);this.miniCollapsedEl=this.collapsedEl.createChild({cls:"x-layout-mini x-layout-mini-"+this.position,html:" "});this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');this.collapsedEl.addClassOnOver("x-layout-collapsed-over");this.collapsedEl.on('click',this.onExpandClick,this,{stopEvent:true});}else{if(this.collapsible!==false&&!this.hideCollapseTool){var t=this.expandToolEl=this.toolTemplate.append(this.collapsedEl.dom,{id:'expand-'+this.position},true);t.addClassOnOver('x-tool-expand-'+this.position+'-over');t.on('click',this.onExpandClick,this,{stopEvent:true});} +if(this.floatable!==false||this.titleCollapse){this.collapsedEl.addClassOnOver("x-layout-collapsed-over");this.collapsedEl.on("click",this[this.floatable?'collapseClick':'onExpandClick'],this);}}} +return this.collapsedEl;},onExpandClick:function(e){if(this.isSlid){this.panel.expand(false);}else{this.panel.expand();}},onCollapseClick:function(e){this.panel.collapse();},beforeCollapse:function(p,animate){this.lastAnim=animate;if(this.splitEl){this.splitEl.hide();} +this.getCollapsedEl().show();var el=this.panel.getEl();this.originalZIndex=el.getStyle('z-index');el.setStyle('z-index',100);this.isCollapsed=true;this.layout.layout();},onCollapse:function(animate){this.panel.el.setStyle('z-index',1);if(this.lastAnim===false||this.panel.animCollapse===false){this.getCollapsedEl().dom.style.visibility='visible';}else{this.getCollapsedEl().slideIn(this.panel.slideAnchor,{duration:.2});} +this.state.collapsed=true;this.panel.saveState();},beforeExpand:function(animate){if(this.isSlid){this.afterSlideIn();} +var c=this.getCollapsedEl();this.el.show();if(this.position=='east'||this.position=='west'){this.panel.setSize(undefined,c.getHeight());}else{this.panel.setSize(c.getWidth(),undefined);} +c.hide();c.dom.style.visibility='hidden';this.panel.el.setStyle('z-index',this.floatingZIndex);},onExpand:function(){this.isCollapsed=false;if(this.splitEl){this.splitEl.show();} +this.layout.layout();this.panel.el.setStyle('z-index',this.originalZIndex);this.state.collapsed=false;this.panel.saveState();},collapseClick:function(e){if(this.isSlid){e.stopPropagation();this.slideIn();}else{e.stopPropagation();this.slideOut();}},onHide:function(){if(this.isCollapsed){this.getCollapsedEl().hide();}else if(this.splitEl){this.splitEl.hide();}},onShow:function(){if(this.isCollapsed){this.getCollapsedEl().show();}else if(this.splitEl){this.splitEl.show();}},isVisible:function(){return!this.panel.hidden;},getMargins:function(){return this.isCollapsed&&this.cmargins?this.cmargins:this.margins;},getSize:function(){return this.isCollapsed?this.getCollapsedEl().getSize():this.panel.getSize();},setPanel:function(panel){this.panel=panel;},getMinWidth:function(){return this.minWidth;},getMinHeight:function(){return this.minHeight;},applyLayoutCollapsed:function(box){var ce=this.getCollapsedEl();ce.setLeftTop(box.x,box.y);ce.setSize(box.width,box.height);},applyLayout:function(box){if(this.isCollapsed){this.applyLayoutCollapsed(box);}else{this.panel.setPosition(box.x,box.y);this.panel.setSize(box.width,box.height);}},beforeSlide:function(){this.panel.beforeEffect();},afterSlide:function(){this.panel.afterEffect();},initAutoHide:function(){if(this.autoHide!==false){if(!this.autoHideHd){this.autoHideSlideTask=new Ext.util.DelayedTask(this.slideIn,this);this.autoHideHd={"mouseout":function(e){if(!e.within(this.el,true)){this.autoHideSlideTask.delay(500);}},"mouseover":function(e){this.autoHideSlideTask.cancel();},scope:this};} +this.el.on(this.autoHideHd);this.collapsedEl.on(this.autoHideHd);}},clearAutoHide:function(){if(this.autoHide!==false){this.el.un("mouseout",this.autoHideHd.mouseout);this.el.un("mouseover",this.autoHideHd.mouseover);this.collapsedEl.un("mouseout",this.autoHideHd.mouseout);this.collapsedEl.un("mouseover",this.autoHideHd.mouseover);}},clearMonitor:function(){Ext.getDoc().un("click",this.slideInIf,this);},slideOut:function(){if(this.isSlid||this.el.hasActiveFx()){return;} +this.isSlid=true;var ts=this.panel.tools,dh,pc;if(ts&&ts.toggle){ts.toggle.hide();} +this.el.show();pc=this.panel.collapsed;this.panel.collapsed=false;if(this.position=='east'||this.position=='west'){dh=this.panel.deferHeight;this.panel.deferHeight=false;this.panel.setSize(undefined,this.collapsedEl.getHeight());this.panel.deferHeight=dh;}else{this.panel.setSize(this.collapsedEl.getWidth(),undefined);} +this.panel.collapsed=pc;this.restoreLT=[this.el.dom.style.left,this.el.dom.style.top];this.el.alignTo(this.collapsedEl,this.getCollapseAnchor());this.el.setStyle("z-index",this.floatingZIndex+2);this.panel.el.replaceClass('x-panel-collapsed','x-panel-floating');if(this.animFloat!==false){this.beforeSlide();this.el.slideIn(this.getSlideAnchor(),{callback:function(){this.afterSlide();this.initAutoHide();Ext.getDoc().on("click",this.slideInIf,this);},scope:this,block:true});}else{this.initAutoHide();Ext.getDoc().on("click",this.slideInIf,this);}},afterSlideIn:function(){this.clearAutoHide();this.isSlid=false;this.clearMonitor();this.el.setStyle("z-index","");this.panel.el.replaceClass('x-panel-floating','x-panel-collapsed');this.el.dom.style.left=this.restoreLT[0];this.el.dom.style.top=this.restoreLT[1];var ts=this.panel.tools;if(ts&&ts.toggle){ts.toggle.show();}},slideIn:function(cb){if(!this.isSlid||this.el.hasActiveFx()){Ext.callback(cb);return;} +this.isSlid=false;if(this.animFloat!==false){this.beforeSlide();this.el.slideOut(this.getSlideAnchor(),{callback:function(){this.el.hide();this.afterSlide();this.afterSlideIn();Ext.callback(cb);},scope:this,block:true});}else{this.el.hide();this.afterSlideIn();}},slideInIf:function(e){if(!e.within(this.el)){this.slideIn();}},anchors:{"west":"left","east":"right","north":"top","south":"bottom"},sanchors:{"west":"l","east":"r","north":"t","south":"b"},canchors:{"west":"tl-tr","east":"tr-tl","north":"tl-bl","south":"bl-tl"},getAnchor:function(){return this.anchors[this.position];},getCollapseAnchor:function(){return this.canchors[this.position];},getSlideAnchor:function(){return this.sanchors[this.position];},getAlignAdj:function(){var cm=this.cmargins;switch(this.position){case"west":return[0,0];break;case"east":return[0,0];break;case"north":return[0,0];break;case"south":return[0,0];break;}},getExpandAdj:function(){var c=this.collapsedEl,cm=this.cmargins;switch(this.position){case"west":return[-(cm.right+c.getWidth()+cm.left),0];break;case"east":return[cm.right+c.getWidth()+cm.left,0];break;case"north":return[0,-(cm.top+cm.bottom+c.getHeight())];break;case"south":return[0,cm.top+cm.bottom+c.getHeight()];break;}},destroy:function(){if(this.autoHideSlideTask&&this.autoHideSlideTask.cancel){this.autoHideSlideTask.cancel();} +Ext.destroyMembers(this,'miniCollapsedEl','collapsedEl','expandToolEl');}};Ext.layout.BorderLayout.SplitRegion=function(layout,config,pos){Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this,layout,config,pos);this.applyLayout=this.applyFns[pos];};Ext.extend(Ext.layout.BorderLayout.SplitRegion,Ext.layout.BorderLayout.Region,{splitTip:"Drag to resize.",collapsibleSplitTip:"Drag to resize. Double click to hide.",useSplitTips:false,splitSettings:{north:{orientation:Ext.SplitBar.VERTICAL,placement:Ext.SplitBar.TOP,maxFn:'getVMaxSize',minProp:'minHeight',maxProp:'maxHeight'},south:{orientation:Ext.SplitBar.VERTICAL,placement:Ext.SplitBar.BOTTOM,maxFn:'getVMaxSize',minProp:'minHeight',maxProp:'maxHeight'},east:{orientation:Ext.SplitBar.HORIZONTAL,placement:Ext.SplitBar.RIGHT,maxFn:'getHMaxSize',minProp:'minWidth',maxProp:'maxWidth'},west:{orientation:Ext.SplitBar.HORIZONTAL,placement:Ext.SplitBar.LEFT,maxFn:'getHMaxSize',minProp:'minWidth',maxProp:'maxWidth'}},applyFns:{west:function(box){if(this.isCollapsed){return this.applyLayoutCollapsed(box);} +var sd=this.splitEl.dom,s=sd.style;this.panel.setPosition(box.x,box.y);var sw=sd.offsetWidth;s.left=(box.x+box.width-sw)+'px';s.top=(box.y)+'px';s.height=Math.max(0,box.height)+'px';this.panel.setSize(box.width-sw,box.height);},east:function(box){if(this.isCollapsed){return this.applyLayoutCollapsed(box);} +var sd=this.splitEl.dom,s=sd.style;var sw=sd.offsetWidth;this.panel.setPosition(box.x+sw,box.y);s.left=(box.x)+'px';s.top=(box.y)+'px';s.height=Math.max(0,box.height)+'px';this.panel.setSize(box.width-sw,box.height);},north:function(box){if(this.isCollapsed){return this.applyLayoutCollapsed(box);} +var sd=this.splitEl.dom,s=sd.style;var sh=sd.offsetHeight;this.panel.setPosition(box.x,box.y);s.left=(box.x)+'px';s.top=(box.y+box.height-sh)+'px';s.width=Math.max(0,box.width)+'px';this.panel.setSize(box.width,box.height-sh);},south:function(box){if(this.isCollapsed){return this.applyLayoutCollapsed(box);} +var sd=this.splitEl.dom,s=sd.style;var sh=sd.offsetHeight;this.panel.setPosition(box.x,box.y+sh);s.left=(box.x)+'px';s.top=(box.y)+'px';s.width=Math.max(0,box.width)+'px';this.panel.setSize(box.width,box.height-sh);}},render:function(ct,p){Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this,ct,p);var ps=this.position;this.splitEl=ct.createChild({cls:"x-layout-split x-layout-split-"+ps,html:" ",id:this.panel.id+'-xsplit'});if(this.collapseMode=='mini'){this.miniSplitEl=this.splitEl.createChild({cls:"x-layout-mini x-layout-mini-"+ps,html:" "});this.miniSplitEl.addClassOnOver('x-layout-mini-over');this.miniSplitEl.on('click',this.onCollapseClick,this,{stopEvent:true});} +var s=this.splitSettings[ps];this.split=new Ext.SplitBar(this.splitEl.dom,p.el,s.orientation);this.split.tickSize=this.tickSize;this.split.placement=s.placement;this.split.getMaximumSize=this[s.maxFn].createDelegate(this);this.split.minSize=this.minSize||this[s.minProp];this.split.on("beforeapply",this.onSplitMove,this);this.split.useShim=this.useShim===true;this.maxSize=this.maxSize||this[s.maxProp];if(p.hidden){this.splitEl.hide();} +if(this.useSplitTips){this.splitEl.dom.title=this.collapsible?this.collapsibleSplitTip:this.splitTip;} +if(this.collapsible){this.splitEl.on("dblclick",this.onCollapseClick,this);}},getSize:function(){if(this.isCollapsed){return this.collapsedEl.getSize();} +var s=this.panel.getSize();if(this.position=='north'||this.position=='south'){s.height+=this.splitEl.dom.offsetHeight;}else{s.width+=this.splitEl.dom.offsetWidth;} +return s;},getHMaxSize:function(){var cmax=this.maxSize||10000;var center=this.layout.center;return Math.min(cmax,(this.el.getWidth()+center.el.getWidth())-center.getMinWidth());},getVMaxSize:function(){var cmax=this.maxSize||10000;var center=this.layout.center;return Math.min(cmax,(this.el.getHeight()+center.el.getHeight())-center.getMinHeight());},onSplitMove:function(split,newSize){var s=this.panel.getSize();this.lastSplitSize=newSize;if(this.position=='north'||this.position=='south'){this.panel.setSize(s.width,newSize);this.state.height=newSize;}else{this.panel.setSize(newSize,s.height);this.state.width=newSize;} +this.layout.layout();this.panel.saveState();return false;},getSplitBar:function(){return this.split;},destroy:function(){Ext.destroy(this.miniSplitEl,this.split,this.splitEl);Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this);}});Ext.Container.LAYOUTS['border']=Ext.layout.BorderLayout;Ext.layout.FormLayout=Ext.extend(Ext.layout.AnchorLayout,{labelSeparator:':',trackLabels:true,type:'form',onRemove:function(c){Ext.layout.FormLayout.superclass.onRemove.call(this,c);if(this.trackLabels){c.un('show',this.onFieldShow,this);c.un('hide',this.onFieldHide,this);} +var el=c.getPositionEl(),ct=c.getItemCt&&c.getItemCt();if(c.rendered&&ct){if(el&&el.dom){el.insertAfter(ct);} +Ext.destroy(ct);Ext.destroyMembers(c,'label','itemCt');if(c.customItemCt){Ext.destroyMembers(c,'getItemCt','customItemCt');}}},setContainer:function(ct){Ext.layout.FormLayout.superclass.setContainer.call(this,ct);if(ct.labelAlign){ct.addClass('x-form-label-'+ct.labelAlign);} +if(ct.hideLabels){Ext.apply(this,{labelStyle:'display:none',elementStyle:'padding-left:0;',labelAdjust:0});}else{this.labelSeparator=Ext.isDefined(ct.labelSeparator)?ct.labelSeparator:this.labelSeparator;ct.labelWidth=ct.labelWidth||100;if(Ext.isNumber(ct.labelWidth)){var pad=Ext.isNumber(ct.labelPad)?ct.labelPad:5;Ext.apply(this,{labelAdjust:ct.labelWidth+pad,labelStyle:'width:'+ct.labelWidth+'px;',elementStyle:'padding-left:'+(ct.labelWidth+pad)+'px'});} +if(ct.labelAlign=='top'){Ext.apply(this,{labelStyle:'width:auto;',labelAdjust:0,elementStyle:'padding-left:0;'});}}},isHide:function(c){return c.hideLabel||this.container.hideLabels;},onFieldShow:function(c){c.getItemCt().removeClass('x-hide-'+c.hideMode);if(c.isComposite){c.doLayout();}},onFieldHide:function(c){c.getItemCt().addClass('x-hide-'+c.hideMode);},getLabelStyle:function(s){var ls='',items=[this.labelStyle,s];for(var i=0,len=items.length;i=cols)||(this.cells[rowIndex]&&this.cells[rowIndex][colIndex])){if(cols&&colIndex>=cols){rowIndex++;colIndex=0;}else{colIndex++;}} +return[colIndex,rowIndex];},renderItem:function(c,position,target){if(!this.table){this.table=target.createChild(Ext.apply({tag:'table',cls:'x-table-layout',cellspacing:0,cn:{tag:'tbody'}},this.tableAttrs),null,true);} +if(c&&!c.rendered){c.render(this.getNextCell(c));this.configureItem(c);}else if(c&&!this.isValidParent(c,target)){var container=this.getNextCell(c);container.insertBefore(c.getPositionEl().dom,null);c.container=Ext.get(container);this.configureItem(c);}},isValidParent:function(c,target){return c.getPositionEl().up('table',5).dom.parentNode===(target.dom||target);},destroy:function(){delete this.table;Ext.layout.TableLayout.superclass.destroy.call(this);}});Ext.Container.LAYOUTS['table']=Ext.layout.TableLayout;Ext.layout.AbsoluteLayout=Ext.extend(Ext.layout.AnchorLayout,{extraCls:'x-abs-layout-item',type:'absolute',onLayout:function(ct,target){target.position();this.paddingLeft=target.getPadding('l');this.paddingTop=target.getPadding('t');Ext.layout.AbsoluteLayout.superclass.onLayout.call(this,ct,target);},adjustWidthAnchor:function(value,comp){return value?value-comp.getPosition(true)[0]+this.paddingLeft:value;},adjustHeightAnchor:function(value,comp){return value?value-comp.getPosition(true)[1]+this.paddingTop:value;}});Ext.Container.LAYOUTS['absolute']=Ext.layout.AbsoluteLayout;Ext.layout.BoxLayout=Ext.extend(Ext.layout.ContainerLayout,{defaultMargins:{left:0,top:0,right:0,bottom:0},padding:'0',pack:'start',monitorResize:true,type:'box',scrollOffset:0,extraCls:'x-box-item',targetCls:'x-box-layout-ct',innerCls:'x-box-inner',constructor:function(config){Ext.layout.BoxLayout.superclass.constructor.call(this,config);if(Ext.isString(this.defaultMargins)){this.defaultMargins=this.parseMargins(this.defaultMargins);} +var handler=this.overflowHandler;if(typeof handler=='string'){handler={type:handler};} +var handlerType='none';if(handler&&handler.type!=undefined){handlerType=handler.type;} +var constructor=Ext.layout.boxOverflow[handlerType];if(constructor[this.type]){constructor=constructor[this.type];} +this.overflowHandler=new constructor(this,handler);},onLayout:function(container,target){Ext.layout.BoxLayout.superclass.onLayout.call(this,container,target);var tSize=this.getLayoutTargetSize(),items=this.getVisibleItems(container),calcs=this.calculateChildBoxes(items,tSize),boxes=calcs.boxes,meta=calcs.meta;if(tSize.width>0){var handler=this.overflowHandler,method=meta.tooNarrow?'handleOverflow':'clearOverflow';var results=handler[method](calcs,tSize);if(results){if(results.targetSize){tSize=results.targetSize;} +if(results.recalculate){items=this.getVisibleItems(container);calcs=this.calculateChildBoxes(items,tSize);boxes=calcs.boxes;}}} +this.layoutTargetLastSize=tSize;this.childBoxCache=calcs;this.updateInnerCtSize(tSize,calcs);this.updateChildBoxes(boxes);this.handleTargetOverflow(tSize,container,target);},updateChildBoxes:function(boxes){for(var i=0,length=boxes.length;i(None)',constructor:function(layout){Ext.layout.boxOverflow.Menu.superclass.constructor.apply(this,arguments);this.menuItems=[];},createInnerElements:function(){if(!this.afterCt){this.afterCt=this.layout.innerCt.insertSibling({cls:this.afterCls},'before');}},clearOverflow:function(calculations,targetSize){var newWidth=targetSize.width+(this.afterCt?this.afterCt.getWidth():0),items=this.menuItems;this.hideTrigger();for(var index=0,length=items.length;indextargetSize.width;return calcs;};},handleOverflow:function(calculations,targetSize){this.showTrigger();var newWidth=targetSize.width-this.afterCt.getWidth(),boxes=calculations.boxes,usedWidth=0,recalculate=false;for(var index=0,length=boxes.length;index
      +

      + type == "photo"): ?> +
      + + type}s/{$item->id}") ?>"> +
      + + description) ?> +

      + ]]> + + + + type == "photo" && $view_full): ?> + + + type == "photo"): ?> + + + + + + type == "photo" && $view_full): ?> + + + + + + diff --git a/modules/rss/views/rss_block.html.php b/modules/rss/views/rss_block.html.php new file mode 100644 index 0000000..210c72a --- /dev/null +++ b/modules/rss/views/rss_block.html.php @@ -0,0 +1,13 @@ + + diff --git a/modules/search/controllers/search.php b/modules/search/controllers/search.php new file mode 100644 index 0000000..753d9b6 --- /dev/null +++ b/modules/search/controllers/search.php @@ -0,0 +1,123 @@ +get("q"); + $q_with_more_terms = search::add_query_terms($q); + $show = Input::instance()->get("show"); + + $album_id = Input::instance()->get("album", item::root()->id); + $album = ORM::factory("item", $album_id); + if (!access::can("view", $album) || !$album->is_album()) { + $album = item::root(); + } + + if ($show) { + $child = ORM::factory("item", $show); + $index = search::get_position_within_album($child, $q_with_more_terms, $album); + if ($index) { + $page = ceil($index / $page_size); + url::redirect(url::abs_site("search" . + "?q=" . urlencode($q) . + "&album=" . urlencode($album->id) . + ($page == 1 ? "" : "&page=$page"))); + } + } + + $page = Input::instance()->get("page", 1); + + // Make sure that the page references a valid offset + if ($page < 1) { + $page = 1; + } + + $offset = ($page - 1) * $page_size; + + list ($count, $result) = + search::search_within_album($q_with_more_terms, $album, $page_size, $offset); + + $max_pages = max(ceil($count / $page_size), 1); + + $template = new Theme_View("page.html", "collection", "search"); + $root = item::root(); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($q, url::abs_site("search?q=" . urlencode($q)))->set_last(), + ), + "children_count" => $count)); + + $template->content = new View("search.html"); + $template->content->album = $album; + $template->content->items = $result; + $template->content->q = $q; + + print $template; + + item::set_display_context_callback("Search_Controller::get_display_context", $album, $q); + } + + static function get_display_context($item, $album, $q) { + $q_with_more_terms = search::add_query_terms($q); + $position = search::get_position_within_album($item, $q_with_more_terms, $album); + + if ($position > 1) { + list ($count, $result_data) = + search::search_within_album($q_with_more_terms, $album, 3, $position - 2); + list ($previous_item, $ignore, $next_item) = $result_data; + } else { + $previous_item = null; + list ($count, $result_data) = + search::search_within_album($q_with_more_terms, $album, 1, $position); + list ($next_item) = $result_data; + } + + $search_url = url::abs_site("search" . + "?q=" . urlencode($q) . + "&album=" . urlencode($album->id) . + "&show={$item->id}"); + $root = item::root(); + + return array("position" => $position, + "previous_item" => $previous_item, + "next_item" => $next_item, + "sibling_count" => $count, + "siblings_callback" => array("Search_Controller::get_siblings", array($q, $album)), + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance(t("Search: %q", array("q" => $q)), $search_url), + Breadcrumb::instance($item->title, $item->url())->set_last())); + } + + static function get_siblings($q, $album, $limit, $offset) { + if (!isset($limit)) { + $limit = 100; + } + if (!isset($offset)) { + $offset = 1; + } + $result = search::search_within_album(search::add_query_terms($q), $album, $limit, $offset); + return $result[1]; + } +} diff --git a/modules/search/helpers/search.php b/modules/search/helpers/search.php new file mode 100644 index 0000000..c84b70b --- /dev/null +++ b/modules/search/helpers/search.php @@ -0,0 +1,163 @@ +query($query); + $count = $db->query("SELECT FOUND_ROWS() as c")->current()->c; + + return array($count, new ORM_Iterator(ORM::factory("item"), $data)); + } + + private static function _build_query_base($q, $album, $where=array()) { + $db = Database::instance(); + $q = $db->escape($q); + + if (!identity::active_user()->admin) { + foreach (identity::group_ids_for_active_user() as $id) { + $fields[] = "`view_$id` = TRUE"; // access::ALLOW + } + $access_sql = " AND (" . join(" OR ", $fields) . ")"; + } else { + $access_sql = ""; + } + + if ($album->id == item::root()->id) { + $album_sql = ""; + } else { + $album_sql = + " AND {items}.left_ptr > " . $db->escape($album->left_ptr) . + " AND {items}.right_ptr <= " . $db->escape($album->right_ptr); + } + + return + "SELECT SQL_CALC_FOUND_ROWS {items}.*, " . + " MATCH({search_records}.`data`) AGAINST ('$q') AS `score` " . + "FROM {items} JOIN {search_records} ON ({items}.`id` = {search_records}.`item_id`) " . + "WHERE MATCH({search_records}.`data`) AGAINST ('$q' IN BOOLEAN MODE)" . + $album_sql . + (empty($where) ? "" : " AND " . join(" AND ", $where)) . + $access_sql; + } + + /** + * @return string An error message suitable for inclusion in the task log + */ + static function check_index() { + list ($remaining) = search::stats(); + if ($remaining) { + site_status::warning( + t('Your search index needs to be updated. Fix this now', + array("url" => html::mark_clean(url::site("admin/maintenance/start/search_task::update_index?csrf=__CSRF__")))), + "search_index_out_of_date"); + } + } + + static function update($item) { + $data = new ArrayObject(); + $record = ORM::factory("search_record")->where("item_id", "=", $item->id)->find(); + if (!$record->loaded()) { + $record->item_id = $item->id; + } + + $item = $record->item(); + module::event("item_index_data", $item, $data); + $record->data = join(" ", (array)$data); + $record->dirty = 0; + $record->save(); + } + + static function stats() { + $remaining = db::build() + ->from("items") + ->join("search_records", "items.id", "search_records.item_id", "left") + ->and_open() + ->where("search_records.item_id", "IS", null) + ->or_where("search_records.dirty", "=", 1) + ->close() + ->count_records(); + + $total = ORM::factory("item")->count_all(); + $percent = round(100 * ($total - $remaining) / $total); + + return array($remaining, $total, $percent); + } + + static function get_position($item, $q) { + return search::get_position_within_album($item, $q, item::root()); + } + + static function get_position_within_album($item, $q, $album) { + $page_size = module::get_var("gallery", "page_size", 9); + $query = self::_build_query_base($q, $album, array("{items}.id = " . $item->id)) . + " ORDER BY `score` DESC"; + $db = Database::instance(); + + // Truncate the score by two decimal places as this resolves the issues + // that arise due to inexact numeric conversions. + $current = $db->query($query)->current(); + if (!$current) { + // We can't find this result in our result set - perhaps we've fallen out of context? Clear + // the context and try again. + item::clear_display_context_callback(); + url::redirect(url::current()); + } + $score = $current->score; + if (strlen($score) > 7) { + $score = substr($score, 0, strlen($score) - 2); + } + + // Redo the query but only look for results greater than or equal to our current location + // then seek backwards until we find our item. + $data = $db->query(self::_build_query_base($q, $album) . " HAVING `score` >= " . $score . + " ORDER BY `score` DESC"); + $data->seek($data->count() - 1); + + while ($data->get("id") != $item->id && $data->prev()->valid()) { + } + + return $data->key() + 1; + } +} diff --git a/modules/search/helpers/search_event.php b/modules/search/helpers/search_event.php new file mode 100644 index 0000000..a20935b --- /dev/null +++ b/modules/search/helpers/search_event.php @@ -0,0 +1,39 @@ +delete("search_records") + ->where("item_id", "=", $item->id) + ->execute(); + } + + static function item_related_update($item) { + search::update($item); + } +} diff --git a/modules/search/helpers/search_installer.php b/modules/search/helpers/search_installer.php new file mode 100644 index 0000000..c9e8f26 --- /dev/null +++ b/modules/search/helpers/search_installer.php @@ -0,0 +1,50 @@ +query("CREATE TABLE {search_records} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + `dirty` boolean default 1, + `data` LONGTEXT default NULL, + PRIMARY KEY (`id`), + KEY(`item_id`), + FULLTEXT INDEX (`data`)) + ENGINE=MyISAM + DEFAULT CHARSET=utf8;"); + } + + static function activate() { + // Update the root item. This is a quick hack because the search module is activated as part + // of the official install, so this way we don't start off with a "your index is out of date" + // banner. + search::update(model_cache::get("item", 1)); + search::check_index(); + } + + static function deactivate() { + site_status::clear("search_index_out_of_date"); + } + + static function uninstall() { + Database::instance()->query("DROP TABLE {search_records}"); + } +} diff --git a/modules/search/helpers/search_task.php b/modules/search/helpers/search_task.php new file mode 100644 index 0000000..18348a2 --- /dev/null +++ b/modules/search/helpers/search_task.php @@ -0,0 +1,84 @@ +delete("search_records") + ->where("item_id", "NOT IN", db::build()->select("id")->from("items")) + ->execute(); + + list ($remaining, $total, $percent) = search::stats(); + return array(Task_Definition::factory() + ->callback("search_task::update_index") + ->name(t("Update Search Index")) + ->description( + $remaining + ? t2("1 photo or album needs to be scanned", + "%count (%percent%) of your photos and albums need to be scanned", + $remaining, array("percent" => (100 - $percent))) + : t("Search data is up-to-date")) + ->severity($remaining ? log::WARNING : log::SUCCESS)); + } + + static function update_index($task) { + try { + $completed = $task->get("completed", 0); + + $start = microtime(true); + foreach (ORM::factory("item") + ->join("search_records", "items.id", "search_records.item_id", "left") + ->where("search_records.item_id", "IS", null) + ->or_where("search_records.dirty", "=", 1) + ->find_all(100) as $item) { + // The query above can take a long time, so start the timer after its done + // to give ourselves a little time to actually process rows. + if (!isset($start)) { + $start = microtime(true); + } + + search::update($item); + $completed++; + + if (microtime(true) - $start > .75) { + break; + } + } + + list ($remaining, $total, $percent) = search::stats(); + $task->set("completed", $completed); + if ($remaining == 0 || !($remaining + $completed)) { + $task->done = true; + $task->state = "success"; + site_status::clear("search_index_out_of_date"); + $task->percent_complete = 100; + } else { + $task->percent_complete = round(100 * $completed / ($remaining + $completed)); + } + $task->status = t2("one record updated, index is %percent% up-to-date", + "%count records updated, index is %percent% up-to-date", + $completed, array("percent" => $percent)); + } catch (Exception $e) { + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + } + } +} diff --git a/modules/search/helpers/search_theme.php b/modules/search/helpers/search_theme.php new file mode 100644 index 0000000..e8735d1 --- /dev/null +++ b/modules/search/helpers/search_theme.php @@ -0,0 +1,29 @@ +page_subtype() != "login") { + $view = new View("search_link.html"); + return $view->render(); + } else { + return ""; + } + } +} \ No newline at end of file diff --git a/modules/search/models/search_record.php b/modules/search/models/search_record.php new file mode 100644 index 0000000..be68e72 --- /dev/null +++ b/modules/search/models/search_record.php @@ -0,0 +1,24 @@ +item_id); + } +} diff --git a/modules/search/module.info b/modules/search/module.info new file mode 100644 index 0000000..f1bb1fa --- /dev/null +++ b/modules/search/module.info @@ -0,0 +1,7 @@ +name = "Search" +description = "Allows users to search their Gallery" +version = 1 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:search" +discuss_url = "http://galleryproject.org/forum_module_search" diff --git a/modules/search/views/search.html.php b/modules/search/views/search.html.php new file mode 100644 index 0000000..a42c31d --- /dev/null +++ b/modules/search/views/search.html.php @@ -0,0 +1,65 @@ + + +
      " id="g-search-form" class="g-short-form"> +
      + + + +
        +
      • + id == item::root()->id): ?> + + + + + + +
      • +
      • + for_html_attr() ?>" class="submit" /> +
      • +
      +
      +
      + +
      +

      + + id == item::root()->id): ?> +
      + +
      + +
      + %album.", array("album" => html::purify($album->title))) ?> + item::root()->id))) ?>"> +
      + + + + + paginator() ?> + + +

      + %term", array("term" => $q)) ?> +

      + + +
      diff --git a/modules/search/views/search_link.html.php b/modules/search/views/search_link.html.php new file mode 100644 index 0000000..4f9abc1 --- /dev/null +++ b/modules/search/views/search_link.html.php @@ -0,0 +1,22 @@ + +
      " id="g-quick-search-form" class="g-short-form"> + + is_album() ? $item->id : $item->parent_id; ?> + + id; ?> + +
        +
      • + id): ?> + + + + + + +
      • +
      • + for_html_attr() ?>" class="submit" /> +
      • +
      +
      diff --git a/modules/server_add/controllers/admin_server_add.php b/modules/server_add/controllers/admin_server_add.php new file mode 100644 index 0000000..ba2b9b3 --- /dev/null +++ b/modules/server_add/controllers/admin_server_add.php @@ -0,0 +1,97 @@ +page_title = t("Add from server"); + $view->content = new View("admin_server_add.html"); + $view->content->form = $this->_get_admin_form(); + $paths = unserialize(module::get_var("server_add", "authorized_paths", "a:0:{}")); + $view->content->paths = array_keys($paths); + + print $view; + } + + public function add_path() { + access::verify_csrf(); + + $form = $this->_get_admin_form(); + $paths = unserialize(module::get_var("server_add", "authorized_paths", "a:0:{}")); + if ($form->validate()) { + $path = html_entity_decode($form->add_path->path->value); + if (is_link($path)) { + $form->add_path->path->add_error("is_symlink", 1); + } else if (!is_readable($path)) { + $form->add_path->path->add_error("not_readable", 1); + } else { + $paths[$path] = 1; + module::set_var("server_add", "authorized_paths", serialize($paths)); + message::success(t("Added path %path", array("path" => $path))); + server_add::check_config($paths); + url::redirect("admin/server_add"); + } + } + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_server_add.html"); + $view->content->form = $form; + $view->content->paths = array_keys($paths); + print $view; + } + + public function remove_path() { + access::verify_csrf(); + + $path = Input::instance()->get("path"); + $paths = unserialize(module::get_var("server_add", "authorized_paths")); + if (isset($paths[$path])) { + unset($paths[$path]); + message::success(t("Removed path %path", array("path" => $path))); + module::set_var("server_add", "authorized_paths", serialize($paths)); + server_add::check_config($paths); + } + url::redirect("admin/server_add"); + } + + public function autocomplete() { + $directories = array(); + + $path_prefix = Input::instance()->get("q"); + foreach (glob("{$path_prefix}*") as $file) { + if (is_dir($file) && !is_link($file)) { + $directories[] = html::clean($file); + } + } + + ajax::response(implode("\n", $directories)); + } + + private function _get_admin_form() { + $form = new Forge("admin/server_add/add_path", "", "post", + array("id" => "g-server-add-admin-form", "class" => "g-short-form")); + $add_path = $form->group("add_path"); + $add_path->input("path")->label(t("Path"))->rules("required")->id("g-path") + ->error_messages("not_readable", t("This directory is not readable by the webserver")) + ->error_messages("is_symlink", t("Symbolic links are not allowed")); + $add_path->submit("add")->value(t("Add Path")); + + return $form; + } +} diff --git a/modules/server_add/controllers/server_add.php b/modules/server_add/controllers/server_add.php new file mode 100644 index 0000000..f6e0a94 --- /dev/null +++ b/modules/server_add/controllers/server_add.php @@ -0,0 +1,315 @@ +where("task_id", "NOT IN", db::build()->select("id")->from("tasks")) + ->delete("server_add_entries") + ->execute(); + + $item = ORM::factory("item", $id); + $view = new View("server_add_tree_dialog.html"); + $view->item = $item; + $view->tree = new View("server_add_tree.html"); + $view->tree->files = $files; + $view->tree->parents = array(); + print $view; + } + + public function children() { + $path = Input::instance()->get("path"); + + $tree = new View("server_add_tree.html"); + $tree->files = array(); + $tree->parents = array(); + + // Make a tree with the parents back up to the authorized path, and all the children under the + // current path. + if (server_add::is_valid_path($path)) { + $tree->parents[] = $path; + while (server_add::is_valid_path(dirname($tree->parents[0]))) { + array_unshift($tree->parents, dirname($tree->parents[0])); + } + + $glob_path = str_replace(array("{", "}", "[", "]"), array("\{", "\}", "\[", "\]"), $path); + foreach (glob("$glob_path/*") as $file) { + if (!is_readable($file)) { + continue; + } + if (!is_dir($file)) { + $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + if (!legal_file::get_extensions($ext)) { + continue; + } + } + + $tree->files[] = $file; + } + } else { + // Missing or invalid path; print out the list of authorized path + $paths = unserialize(module::get_var("server_add", "authorized_paths")); + foreach (array_keys($paths) as $path) { + $tree->files[] = $path; + } + } + print $tree; + } + + /** + * Begin the task of adding photos. + */ + public function start() { + access::verify_csrf(); + $item = ORM::factory("item", Input::instance()->get("item_id")); + + $task_def = Task_Definition::factory() + ->callback("Server_Add_Controller::add") + ->description(t("Add photos or movies from the local server")) + ->name(t("Add from server")); + $task = task::create($task_def, array("item_id" => $item->id)); + + foreach (Input::instance()->post("paths") as $path) { + if (server_add::is_valid_path($path)) { + $entry = ORM::factory("server_add_entry"); + $entry->path = $path; + $entry->is_directory = intval(is_dir($path)); + $entry->parent_id = null; + $entry->task_id = $task->id; + $entry->save(); + } + } + + json::reply( + array("result" => "started", + "status" => (string)$task->status, + "url" => url::site("server_add/run/$task->id?csrf=" . access::csrf_token()))); + } + + /** + * Run the task of adding photos + */ + function run($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded() || $task->owner_id != identity::active_user()->id) { + access::forbidden(); + } + + $task = task::run($task_id); + // Prevent the JavaScript code from breaking by forcing a period as + // decimal separator for all locales with sprintf("%F", $value). + json::reply(array("done" => (bool)$task->done, + "status" => (string)$task->status, + "percent_complete" => sprintf("%F", $task->percent_complete))); + } + + /** + * This is the task code that adds photos and albums. It first examines all the target files + * and creates a set of Server_Add_Entry_Models, then runs through the list of models and adds + * them one at a time. + */ + static function add($task) { + $mode = $task->get("mode", "init"); + $start = microtime(true); + + switch ($mode) { + case "init": + $task->set("mode", "build-file-list"); + $task->set("dirs_scanned", 0); + $task->percent_complete = 0; + $task->status = t("Starting up"); + batch::start(); + break; + + case "build-file-list": // 0% to 10% + // We can't fit an arbitrary number of paths in a task, so store them in a separate table. + // Don't use an iterator here because we can't get enough control over it when we're dealing + // with a deep hierarchy and we don't want to go over our time quota. + $paths = unserialize(module::get_var("server_add", "authorized_paths")); + $dirs_scanned = $task->get("dirs_scanned"); + while (microtime(true) - $start < 0.5) { + // Process every directory that doesn't yet have a parent id, these are the + // paths that we're importing. + $entry = ORM::factory("server_add_entry") + ->where("task_id", "=", $task->id) + ->where("is_directory", "=", 1) + ->where("checked", "=", 0) + ->order_by("id", "ASC") + ->find(); + + if ($entry->loaded()) { + $child_paths = glob(preg_quote($entry->path) . "/*"); + if (!$child_paths) { + $child_paths = glob("{$entry->path}/*"); + } + foreach ($child_paths as $child_path) { + if (!is_dir($child_path)) { + $ext = strtolower(pathinfo($child_path, PATHINFO_EXTENSION)); + if (!legal_file::get_extensions($ext) || !filesize($child_path)) { + // Not importable, skip it. + continue; + } + } + + $child_entry = ORM::factory("server_add_entry"); + $child_entry->task_id = $task->id; + $child_entry->path = $child_path; + $child_entry->parent_id = $entry->id; // null if the parent was a staging dir + $child_entry->is_directory = is_dir($child_path); + $child_entry->save(); + } + + // We've processed this entry, mark it as done. + $entry->checked = 1; + $entry->save(); + $dirs_scanned++; + } + } + + // We have no idea how long this can take because we have no idea how deep the tree + // hierarchy rabbit hole goes. Leave ourselves room here for 100 iterations and don't go + // over 10% in percent_complete. + $task->set("dirs_scanned", $dirs_scanned); + $task->percent_complete = min($task->percent_complete + 0.1, 10); + $task->status = t2("Scanned one directory", "Scanned %count directories", $dirs_scanned); + + if (!$entry->loaded()) { + $task->set("mode", "add-files"); + $task->set( + "total_files", + ORM::factory("server_add_entry")->where("task_id", "=", $task->id)->count_all()); + $task->percent_complete = 10; + } + break; + + case "add-files": // 10% to 100% + $completed_files = $task->get("completed_files", 0); + $total_files = $task->get("total_files"); + + // Ordering by id ensures that we add them in the order that we created the entries, which + // will create albums first. Ignore entries which already have an Item_Model attached, + // they're done. + $entries = ORM::factory("server_add_entry") + ->where("task_id", "=", $task->id) + ->where("item_id", "IS", null) + ->order_by("id", "ASC") + ->limit(10) + ->find_all(); + if ($entries->count() == 0) { + // Out of entries, we're done. + $task->set("mode", "done"); + } + + $owner_id = identity::active_user()->id; + foreach ($entries as $entry) { + if (microtime(true) - $start > 0.5) { + break; + } + + // Look up the parent item for this entry. By now it should exist, but if none was + // specified, then this belongs as a child of the current item. + $parent_entry = ORM::factory("server_add_entry", $entry->parent_id); + if (!$parent_entry->loaded()) { + $parent = ORM::factory("item", $task->get("item_id")); + } else { + $parent = ORM::factory("item", $parent_entry->item_id); + } + + $name = basename($entry->path); + $title = item::convert_filename_to_title($name); + if ($entry->is_directory) { + $album = ORM::factory("item"); + $album->type = "album"; + $album->parent_id = $parent->id; + $album->name = $name; + $album->title = $title; + $album->owner_id = $owner_id; + $album->sort_order = $parent->sort_order; + $album->sort_column = $parent->sort_column; + $album->save(); + $entry->item_id = $album->id; + } else { + try { + $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + if (legal_file::get_photo_extensions($extension)) { + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->parent_id = $parent->id; + $photo->set_data_file($entry->path); + $photo->name = $name; + $photo->title = $title; + $photo->owner_id = $owner_id; + $photo->save(); + $entry->item_id = $photo->id; + } else if (legal_file::get_movie_extensions($extension)) { + $movie = ORM::factory("item"); + $movie->type = "movie"; + $movie->parent_id = $parent->id; + $movie->set_data_file($entry->path); + $movie->name = $name; + $movie->title = $title; + $movie->owner_id = $owner_id; + $movie->save(); + $entry->item_id = $movie->id; + } else { + // This should never happen, because we don't add stuff to the list that we can't + // process. But just in, case.. set this to a non-null value so that we skip this + // entry. + $entry->item_id = 0; + $task->log("Skipping unknown file type: {$entry->path}"); + } + } catch (Exception $e) { + // This can happen if a photo file is invalid, like a BMP masquerading as a .jpg + $entry->item_id = 0; + $task->log("Skipping invalid file: {$entry->path}"); + } + } + + $completed_files++; + $entry->save(); + } + $task->set("completed_files", $completed_files); + $task->status = t("Adding photos / albums (%completed of %total)", + array("completed" => $completed_files, + "total" => $total_files)); + $task->percent_complete = $total_files ? 10 + 100 * ($completed_files / $total_files) : 100; + break; + + case "done": + batch::stop(); + $task->done = true; + $task->state = "success"; + $task->percent_complete = 100; + ORM::factory("server_add_entry") + ->where("task_id", "=", $task->id) + ->delete_all(); + message::info(t2("Successfully added one photo / album", + "Successfully added %count photos / albums", + $task->get("completed_files"))); + } + } +} diff --git a/modules/server_add/css/server_add.css b/modules/server_add/css/server_add.css new file mode 100644 index 0000000..36746ab --- /dev/null +++ b/modules/server_add/css/server_add.css @@ -0,0 +1,38 @@ +#g-server-add button { + margin-bottom: .5em; +} + +#g-server-add-tree { + cursor: pointer; + padding-left: 4px; + width: 95%; +} + +#g-server-add-tree li { + padding: 0; + float: none; +} + +#g-server-add-tree span.selected { + background: #ddd; +} + +#g-server-add-tree { + border: 1px solid #ccc; + height: 20em; + overflow: auto; + margin-bottom: .5em; + padding: .5em; +} + +#g-server-add ul ul li { + padding-left: 1.2em; +} + +#g-server-add-paths li .ui-icon { + margin-top: .4em; +} + +#g-server-add-admin-form .textbox { + width: 400px; +} diff --git a/modules/server_add/helpers/server_add.php b/modules/server_add/helpers/server_add.php new file mode 100644 index 0000000..5cf08ce --- /dev/null +++ b/modules/server_add/helpers/server_add.php @@ -0,0 +1,49 @@ +Configure it now!", + array("url" => html::mark_clean(url::site("admin/server_add")))), + "server_add_configuration"); + } else { + site_status::clear("server_add_configuration"); + } + } + + static function is_valid_path($path) { + if (!is_readable($path) || is_link($path)) { + return false; + } + + $authorized_paths = unserialize(module::get_var("server_add", "authorized_paths")); + foreach (array_keys($authorized_paths) as $valid_path) { + if (strpos($path, $valid_path) === 0) { + return true; + } + } + + return false; + } +} diff --git a/modules/server_add/helpers/server_add_event.php b/modules/server_add/helpers/server_add_event.php new file mode 100644 index 0000000..a718e2f --- /dev/null +++ b/modules/server_add/helpers/server_add_event.php @@ -0,0 +1,42 @@ +get("settings_menu") + ->append(Menu::factory("link") + ->id("server_add") + ->label(t("Server add")) + ->url(url::site("admin/server_add"))); + } + + static function site_menu($menu, $theme) { + $item = $theme->item(); + $paths = unserialize(module::get_var("server_add", "authorized_paths")); + + if ($item && identity::active_user()->admin && $item->is_album() && !empty($paths) && + is_writable($item->is_album() ? $item->file_path() : $item->parent()->file_path())) { + $menu->get("add_menu") + ->append(Menu::factory("dialog") + ->id("server_add") + ->label(t("Server add")) + ->url(url::site("server_add/browse/$item->id"))); + } + } +} diff --git a/modules/server_add/helpers/server_add_installer.php b/modules/server_add/helpers/server_add_installer.php new file mode 100644 index 0000000..b62bbcf --- /dev/null +++ b/modules/server_add/helpers/server_add_installer.php @@ -0,0 +1,73 @@ +query("CREATE TABLE {server_add_entries} ( + `id` int(9) NOT NULL auto_increment, + `checked` boolean default 0, + `is_directory` boolean default 0, + `item_id` int(9), + `parent_id` int(9), + `path` varchar(255) NOT NULL, + `task_id` int(9) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + server_add::check_config(); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("CREATE TABLE {server_add_files} ( + `id` int(9) NOT NULL auto_increment, + `task_id` int(9) NOT NULL, + `file` varchar(255) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + module::set_version("server_add", $version = 2); + } + + if ($version == 2) { + $db->query("ALTER TABLE {server_add_files} ADD COLUMN `item_id` int(9)"); + $db->query("ALTER TABLE {server_add_files} ADD COLUMN `parent_id` int(9)"); + module::set_version("server_add", $version = 3); + } + + if ($version == 3) { + $db->query("DROP TABLE {server_add_files}"); + $db->query("CREATE TABLE {server_add_entries} ( + `id` int(9) NOT NULL auto_increment, + `checked` boolean default 0, + `is_directory` boolean default 0, + `item_id` int(9), + `parent_id` int(9), + `path` varchar(255) NOT NULL, + `task_id` int(9) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + module::set_version("server_add", $version = 4); + } + } + + static function deactivate() { + site_status::clear("server_add_configuration"); + } +} diff --git a/modules/server_add/helpers/server_add_theme.php b/modules/server_add/helpers/server_add_theme.php new file mode 100644 index 0000000..7dfe658 --- /dev/null +++ b/modules/server_add/helpers/server_add_theme.php @@ -0,0 +1,27 @@ +admin) { + return $theme->css("server_add.css") + . $theme->script("server_add.js"); + } + } +} \ No newline at end of file diff --git a/modules/server_add/js/server_add.js b/modules/server_add/js/server_add.js new file mode 100644 index 0000000..02dda4c --- /dev/null +++ b/modules/server_add/js/server_add.js @@ -0,0 +1,125 @@ +(function($) { + $.widget("ui.gallery_server_add", { + _init: function() { + var self = this; + $("#g-server-add-add-button", this.element).click(function(event) { + event.preventDefault(); + $(".g-progress-bar", this.element). + progressbar(). + progressbar("value", 0); + $("#g-server-add-progress", this.element).slideDown("fast", function() { self.start_add(); }); + }); + $("#g-server-add-pause-button", this.element).click(function(event) { + self.pause = true; + $("#g-server-add-pause-button", this.element).hide(); + $("#g-server-add-continue-button", this.element).show(); + }); + $("#g-server-add-continue-button", this.element).click(function(event) { + self.pause = false; + $("#g-server-add-pause-button", this.element).show(); + $("#g-server-add-continue-button", this.element).hide(); + self.run_add(); + }); + $("#g-server-add-close-button", this.element).click(function(event) { + $("#g-dialog").dialog("close"); + window.location.reload(); + }); + $("#g-server-add-tree span.g-directory", this.element).dblclick(function(event) { + self.open_dir(event); + }); + $("#g-server-add-tree span.g-file, #g-server-add-tree span.g-directory", this.element).click(function(event) { + self.select_file(event); + }); + $("#g-server-add-tree span.g-directory", this.element).dblclick(function(event) { + self.open_dir(event); + }); + $("#g-dialog").bind("dialogclose", function(event, ui) { + window.location.reload(); + }); + }, + + taskURL: null, + pause: false, + + start_add: function() { + var self = this; + var paths = []; + $.each($("span.selected", self.element), function () { + paths.push($(this).attr("ref")); + }); + + $("#g-server-add-add-button", this.element).hide(); + $("#g-server-add-pause-button", this.element).show(); + + $.ajax({ + url: START_URL, + type: "POST", + async: false, + data: { "paths[]": paths }, + dataType: "json", + success: function(data, textStatus) { + $("#g-status").html(data.status); + $(".g-progress-bar", self.element).progressbar("value", data.percent_complete); + self.taskURL = data.url; + setTimeout(function() { self.run_add(); }, 25); + } + }); + return false; + }, + + run_add: function () { + var self = this; + $.ajax({ + url: self.taskURL, + async: false, + dataType: "json", + success: function(data, textStatus) { + $("#g-status").html(data.status); + $(".g-progress-bar", self.element).progressbar("value", data.percent_complete); + if (data.done) { + $("#g-server-add-progress", this.element).slideUp(); + $("#g-server-add-add-button", this.element).show(); + $("#g-server-add-pause-button", this.element).hide(); + $("#g-server-add-continue-button", this.element).hide(); + } else { + if (!self.pause) { + setTimeout(function() { self.run_add(); }, 25); + } + } + } + }); + }, + + /** + * Load a new directory + */ + open_dir: function(event) { + var self = this; + var path = $(event.target).attr("ref"); + $.ajax({ + url: GET_CHILDREN_URL.replace("__PATH__", path), + success: function(data, textStatus) { + $("#g-server-add-tree", self.element).html(data); + $("#g-server-add-tree span.g-directory", self.element).dblclick(function(event) { + self.open_dir(event); + }); + $("#g-server-add-tree span.g-file, #g-server-add-tree span.g-directory", this.element).click(function(event) { + self.select_file(event); + }); + } + }); + }, + + /** + * Manage file selection state. + */ + select_file: function (event) { + $(event.target).toggleClass("selected"); + if ($("#g-server-add span.selected").length) { + $("#g-server-add-add-button").enable(true).removeClass("ui-state-disabled"); + } else { + $("#g-server-add-add-button").enable(false).addClass("ui-state-disabled"); + } + } + }); +})(jQuery); diff --git a/modules/server_add/models/server_add_entry.php b/modules/server_add/models/server_add_entry.php new file mode 100644 index 0000000..572ebcb --- /dev/null +++ b/modules/server_add/models/server_add_entry.php @@ -0,0 +1,21 @@ + +css("server_add.css") ?> +css("jquery.autocomplete.css") ?> +script("jquery.autocomplete.js") ?> + + +
      +

      +
      + +

      + +
      +
      diff --git a/modules/server_add/views/server_add_tree.html.php b/modules/server_add/views/server_add_tree.html.php new file mode 100644 index 0000000..9135432 --- /dev/null +++ b/modules/server_add/views/server_add_tree.html.php @@ -0,0 +1,37 @@ + +
    • + + + + +
        + + +
      • + + + + +
          + + + +
        • + "> + " + ref="" > + + +
        • + + +
        • + + + +
        +
      • + + +
      +
    • diff --git a/modules/server_add/views/server_add_tree_dialog.html.php b/modules/server_add/views/server_add_tree_dialog.html.php new file mode 100644 index 0000000..824a86a --- /dev/null +++ b/modules/server_add/views/server_add_tree_dialog.html.php @@ -0,0 +1,52 @@ + + + +
      +

      html::purify($item->title))) ?>

      + +

      +
        + + parents() as $parent): ?> + > title) ?> + + +
      • title) ?>
      • +
      + +
        + +
      + + + + + + + + + + + + + +
      diff --git a/modules/slideshow/helpers/slideshow_event.php b/modules/slideshow/helpers/slideshow_event.php new file mode 100644 index 0000000..add1e33 --- /dev/null +++ b/modules/slideshow/helpers/slideshow_event.php @@ -0,0 +1,80 @@ +module == "rss") { + $data->messages["warn"][] = t("The Slideshow module requires the RSS module."); + } + } + + static function module_change($changes) { + if (!module::is_active("rss") || in_array("rss", $changes->deactivate)) { + site_status::warning( + t("The Slideshow module requires the RSS module. Activate the RSS module now", + array("url" => html::mark_clean(url::site("admin/modules")))), + "slideshow_needs_rss"); + } else { + site_status::clear("slideshow_needs_rss"); + } + } + + static function album_menu($menu, $theme) { + $max_scale = module::get_var("slideshow", "max_scale"); + if ($theme->item()->descendants_count(array(array("type", "=", "photo")))) { + $menu->append(Menu::factory("link") + ->id("slideshow") + ->label(t("View slideshow")) + ->url("javascript:cooliris.embed.show(" . + "{maxScale:$max_scale,feed:'" . self::_feed_url($theme) . "'})") + ->css_id("g-slideshow-link")); + } + } + + static function photo_menu($menu, $theme) { + $max_scale = module::get_var("slideshow", "max_scale"); + $menu->append(Menu::factory("link") + ->id("slideshow") + ->label(t("View slideshow")) + ->url("javascript:cooliris.embed.show(" . + "{maxScale:$max_scale,feed:'" . self::_feed_url($theme) . "'})") + ->css_id("g-slideshow-link")); + } + + static function tag_menu($menu, $theme) { + $max_scale = module::get_var("slideshow", "max_scale"); + $menu->append(Menu::factory("link") + ->id("slideshow") + ->label(t("View slideshow")) + ->url("javascript:cooliris.embed.show(" . + "{maxScale:$max_scale,feed:'" . self::_feed_url($theme) . "'})") + ->css_id("g-slideshow-link")); + } + + private static function _feed_url($theme) { + if ($item = $theme->item()) { + if (!$item->is_album()) { + $item = $item->parent(); + } + return rss::url("gallery/album/{$item->id}"); + } else { + return rss::url("tag/tag/{$theme->tag()->id}"); + } + } +} diff --git a/modules/slideshow/helpers/slideshow_installer.php b/modules/slideshow/helpers/slideshow_installer.php new file mode 100644 index 0000000..22bd953 --- /dev/null +++ b/modules/slideshow/helpers/slideshow_installer.php @@ -0,0 +1,43 @@ +"; + } +} diff --git a/modules/slideshow/module.info b/modules/slideshow/module.info new file mode 100644 index 0000000..2d71f71 --- /dev/null +++ b/modules/slideshow/module.info @@ -0,0 +1,7 @@ +name = "Slideshow" +description = "Allows users to view a slideshow of photos" +version = 2 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:slideshow" +discuss_url = "http://galleryproject.org/forum_module_slideshow" diff --git a/modules/tag/controllers/admin_tags.php b/modules/tag/controllers/admin_tags.php new file mode 100644 index 0000000..19906c6 --- /dev/null +++ b/modules/tag/controllers/admin_tags.php @@ -0,0 +1,119 @@ +get("filter"); + + $view = new Admin_View("admin.html"); + $view->page_title = t("Manage tags"); + $view->content = new View("admin_tags.html"); + $view->content->filter = $filter; + + $query = ORM::factory("tag"); + if ($filter) { + $query->like("name", $filter); + } + $view->content->tags = $query->order_by("name", "ASC")->find_all(); + print $view; + } + + public function form_delete($id) { + $tag = ORM::factory("tag", $id); + if ($tag->loaded()) { + print tag::get_delete_form($tag); + } + } + + public function delete($id) { + access::verify_csrf(); + + $tag = ORM::factory("tag", $id); + if (!$tag->loaded()) { + throw new Kohana_404_Exception(); + } + + $form = tag::get_delete_form($tag); + if ($form->validate()) { + $name = $tag->name; + $tag->delete(); + message::success(t("Deleted tag %tag_name", array("tag_name" => $name))); + log::success("tags", t("Deleted tag %tag_name", array("tag_name" => $name))); + + json::reply(array("result" => "success", "location" => url::site("admin/tags"))); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function form_rename($id) { + $tag = ORM::factory("tag", $id); + if ($tag->loaded()) { + print InPlaceEdit::factory($tag->name) + ->action("admin/tags/rename/$id") + ->render(); + } + } + + public function rename($id) { + access::verify_csrf(); + + $tag = ORM::factory("tag", $id); + if (!$tag->loaded()) { + throw new Kohana_404_Exception(); + } + + $in_place_edit = InPlaceEdit::factory($tag->name) + ->action("admin/tags/rename/$tag->id") + ->rules(array("required", "length[1,64]")); + + if ($in_place_edit->validate()) { + $old_name = $tag->name; + $new_name_or_list = $in_place_edit->value(); + $tag_list = explode(",", $new_name_or_list); + + $tag->name = array_shift($tag_list); + $tag->save(); + + if (!empty($tag_list)) { + $this->_copy_items_for_tags($tag, $tag_list); + $message = t("Split tag %old_name into %tag_list", + array("old_name" => $old_name, "tag_list" => $new_name_or_list)); + } else { + $message = t("Renamed tag %old_name to %new_name", + array("old_name" => $old_name, "new_name" => $tag->name)); + } + + message::success($message); + log::success("tags", $message); + + json::reply(array("result" => "success", "location" => url::site("admin/tags"))); + } else { + json::reply(array("result" => "error", "form" => (string)$in_place_edit->render())); + } + } + + private function _copy_items_for_tags($tag, $tag_list) { + foreach ($tag->items() as $item) { + foreach ($tag_list as $new_tag_name) { + tag::add($item, trim($new_tag_name)); + } + } + } +} diff --git a/modules/tag/controllers/tag.php b/modules/tag/controllers/tag.php new file mode 100644 index 0000000..bada9ba --- /dev/null +++ b/modules/tag/controllers/tag.php @@ -0,0 +1,96 @@ +where("id", "=", $tag_id)->find(); + $page_size = module::get_var("gallery", "page_size", 9); + + $input = Input::instance(); + $show = $input->get("show"); + + if ($show) { + $child = ORM::factory("item", $show); + $index = tag::get_position($tag, $child); + if ($index) { + $page = ceil($index / $page_size); + $uri = "tag/$tag_id/" . urlencode($tag->name); + url::redirect($uri . ($page == 1 ? "" : "?page=$page")); + } + } else { + $page = (int) $input->get("page", "1"); + } + + $children_count = $tag->items_count(); + $offset = ($page-1) * $page_size; + $max_pages = max(ceil($children_count / $page_size), 1); + + // Make sure that the page references a valid offset + if ($page < 1) { + url::redirect(url::merge(array("page" => 1))); + } else if ($page > $max_pages) { + url::redirect(url::merge(array("page" => $max_pages))); + } + + $root = item::root(); + $template = new Theme_View("page.html", "collection", "tag"); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "tag" => $tag, + "children" => $tag->items($page_size, $offset), + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance(t("Tag: %tag_name", array("tag_name" => $tag->name)), + $tag->url())->set_last()), + "children_count" => $children_count)); + $template->content = new View("dynamic.html"); + $template->content->title = t("Tag: %tag_name", array("tag_name" => $tag->name)); + print $template; + + item::set_display_context_callback("Tag_Controller::get_display_context", $tag->id); + } + + static function get_display_context($item, $tag_id) { + $tag = ORM::factory("tag", $tag_id); + $where = array(array("type", "!=", "album")); + + $position = tag::get_position($tag, $item, $where); + if ($position > 1) { + list ($previous_item, $ignore, $next_item) = $tag->items(3, $position - 2, $where); + } else { + $previous_item = null; + list ($next_item) = $tag->items(1, $position, $where); + } + + $root = item::root(); + return array("position" => $position, + "previous_item" => $previous_item, + "next_item" => $next_item, + "sibling_count" => $tag->items_count($where), + "siblings_callback" => array(array($tag, "items"), array()), + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance(t("Tag: %tag_name", array("tag_name" => $tag->name)), + $tag->url("show={$item->id}")), + Breadcrumb::instance($item->title, $item->url())->set_last())); + } +} diff --git a/modules/tag/controllers/tag_name.php b/modules/tag/controllers/tag_name.php new file mode 100644 index 0000000..f96ab69 --- /dev/null +++ b/modules/tag/controllers/tag_name.php @@ -0,0 +1,33 @@ +where("name", "=", $tag_name)->find(); + if (!$tag->loaded()) { + // No matching tag was found. If this was an imported tag, this is probably a bug. + // If the user typed the URL manually, it might just be wrong + throw new Kohana_404_Exception(); + } + + url::redirect($tag->abs_url()); + } + +} diff --git a/modules/tag/controllers/tags.php b/modules/tag/controllers/tags.php new file mode 100644 index 0000000..77d45a9 --- /dev/null +++ b/modules/tag/controllers/tags.php @@ -0,0 +1,65 @@ +validate()) { + foreach (explode(",", $form->add_tag->inputs["name"]->value) as $tag_name) { + $tag_name = trim($tag_name); + if ($tag_name) { + $tag = tag::add($item, $tag_name); + } + } + + json::reply(array("result" => "success", "cloud" => (string)tag::cloud(30))); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function autocomplete() { + $tags = array(); + $tag_parts = explode(",", Input::instance()->get("q")); + $limit = Input::instance()->get("limit"); + $tag_part = ltrim(end($tag_parts)); + $tag_list = ORM::factory("tag") + ->where("name", "LIKE", Database::escape_for_like($tag_part) . "%") + ->order_by("name", "ASC") + ->limit($limit) + ->find_all(); + foreach ($tag_list as $tag) { + $tags[] = html::clean($tag->name); + } + + ajax::response(implode("\n", $tags)); + } +} diff --git a/modules/tag/css/tag.css b/modules/tag/css/tag.css new file mode 100644 index 0000000..8a64960 --- /dev/null +++ b/modules/tag/css/tag.css @@ -0,0 +1,102 @@ +/* Tag cloud ~~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-tag-cloud ul { + font-size: 1.2em; + text-align: justify; +} + +#g-tag-cloud ul li { + display: inline; + line-height: 1.5em; + text-align: justify; +} + +#g-tag-cloud ul li a { + text-decoration: none; +} + +#g-tag-cloud ul li span { + display: none; +} + +#g-tag-cloud ul li.size0 a { + color: #9cf; + font-size: 70%; + font-weight: 100; +} + +#g-tag-cloud ul li.size1 a { + color: #9cf; + font-size: 80%; + font-weight: 100; +} + +#g-tag-cloud ul li.size2 a { + color: #69f; + font-size: 90%; + font-weight: 300; +} + +#g-tag-cloud ul li.size3 a { + color: #69c; + font-size: 100%; + font-weight: 500; +} + +#g-tag-cloud ul li.size4 a { + color: #369; + font-size: 110%; + font-weight: 700; +} + +#g-tag-cloud ul li.size5 a { + color: #0e2b52; + font-size: 120%; + font-weight: 900; +} + +#g-tag-cloud ul li.size6 a { + color: #0e2b52; + font-size: 130%; + font-weight: 900; +} + +#g-tag-cloud ul li.size7 a { + color: #0e2b52; + font-size: 140%; + font-weight: 900; +} + +#g-tag-cloud ul li a:hover { + color: #f30; + text-decoration: underline; +} + +/* Add tag form ~~~~~~~~~~~~~~~~~~~~ */ + +#g-sidebar .g-short-form .textbox { + width: 11em; +} + +/* Tag admin ~~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-tag-admin { + table-layout: fixed; +} + +#g-tag-admin td { + border: 0; + vertical-align: top; +} + +#g-tag-admin ul { + margin-bottom: 2em; +} + +#g-tag-admin li { + padding: .1em 0 .2em 0; +} + +#g-tag-admin form ul { + margin-bottom: 0; +} diff --git a/modules/tag/helpers/item_tags_rest.php b/modules/tag/helpers/item_tags_rest.php new file mode 100644 index 0000000..c153cb9 --- /dev/null +++ b/modules/tag/helpers/item_tags_rest.php @@ -0,0 +1,66 @@ +url); + $tags = array(); + foreach (tag::item_tags($item) as $tag) { + $tags[] = rest::url("tag_item", $tag, $item); + } + + return array( + "url" => $request->url, + "members" => $tags); + } + + static function post($request) { + $tag = rest::resolve($request->params->entity->tag); + $item = rest::resolve($request->params->entity->item); + access::required("view", $item); + + tag::add($item, $tag->name); + return array( + "url" => rest::url("tag_item", $tag, $item), + "members" => array( + rest::url("tag", $tag), + rest::url("item", $item))); + } + + static function delete($request) { + $item = rest::resolve($request->url); + access::required("edit", $item); + + // Deleting this collection means removing all tags associated with the item. + tag::clear_all($item); + } + + static function resolve($id) { + $item = ORM::factory("item", $id); + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + + return $item; + } + + static function url($item) { + return url::abs_site("rest/item_tags/{$item->id}"); + } +} diff --git a/modules/tag/helpers/tag.php b/modules/tag/helpers/tag.php new file mode 100644 index 0000000..a946e01 --- /dev/null +++ b/modules/tag/helpers/tag.php @@ -0,0 +1,181 @@ +id}") + */ + static function add($item, $tag_name) { + if (empty($tag_name)) { + throw new exception("@todo MISSING_TAG_NAME"); + } + + $tag = ORM::factory("tag")->where("name", "=", $tag_name)->find(); + if (!$tag->loaded()) { + $tag->name = $tag_name; + } + + $tag->add($item); + return $tag->save(); + } + + /** + * Return the N most popular tags. + * + * @return ORM_Iterator of Tag_Model in descending tag count order + */ + static function popular_tags($count) { + $count = max($count, 1); + return ORM::factory("tag") + ->order_by("count", "DESC") + ->limit($count) + ->find_all(); + } + + /** + * Return a rendering of the cloud for the N most popular tags. + * + * @param integer $count the number of tags + * @return View + */ + static function cloud($count) { + $tags = tag::popular_tags($count)->as_array(); + if ($tags) { + $cloud = new View("tag_cloud.html"); + $cloud->max_count = $tags[0]->count; + if (!$cloud->max_count) { + return; + } + usort($tags, array("tag", "sort_by_name")); + $cloud->tags = $tags; + return $cloud; + } + } + + static function sort_by_name($tag1, $tag2) { + return strcasecmp($tag1->name, $tag2->name); + } + + /** + * Return all the tags for a given item. + * @return array + */ + static function item_tags($item) { + return ORM::factory("tag") + ->join("items_tags", "tags.id", "items_tags.tag_id", "left") + ->where("items_tags.item_id", "=", $item->id) + ->find_all(); + } + + /** + * Return all the items for a given tag. + * @return array + */ + static function tag_items($tag) { + return ORM::factory("item") + ->join("items_tags", "items_tags.item_id", "items.id", "left") + ->where("items_tags.tag_id", "=", $tag->id) + ->find_all(); + } + + static function get_add_form($item) { + $form = new Forge("tags/create/{$item->id}", "", "post", array("id" => "g-add-tag-form", "class" => "g-short-form")); + $label = $item->is_album() ? + t("Add tag to album") : + ($item->is_photo() ? t("Add tag to photo") : t("Add tag to movie")); + + $group = $form->group("add_tag")->label("Add Tag"); + $group->input("name")->label($label)->rules("required")->id("name"); + $group->hidden("item_id")->value($item->id); + $group->submit("")->value(t("Add Tag")); + return $form; + } + + static function get_delete_form($tag) { + $form = new Forge("admin/tags/delete/$tag->id", "", "post", array("id" => "g-delete-tag-form")); + $group = $form->group("delete_tag") + ->label(t("Really delete tag %tag_name?", array("tag_name" => $tag->name))); + $group->submit("")->value(t("Delete Tag")); + return $form; + } + + /** + * Delete all tags associated with an item + */ + static function clear_all($item) { + db::build() + ->update("tags") + ->set("count", db::expr("`count` - 1")) + ->where("count", ">", 0) + ->where("id", "IN", db::build()->select("tag_id")->from("items_tags")->where("item_id", "=", $item->id)) + ->execute(); + db::build() + ->delete("items_tags") + ->where("item_id", "=", $item->id) + ->execute(); + } + + /** + * Remove all items from a tag + */ + static function remove_items($tag) { + db::build() + ->delete("items_tags") + ->where("tag_id", "=", $tag->id) + ->execute(); + $tag->count = 0; + $tag->save(); + } + + /** + * Get rid of any tags that have no associated items. + */ + static function compact() { + // @todo There's a potential race condition here which we can solve by adding a lock around + // this and all the cases where we create/update tags. I'm loathe to do that since it's an + // extremely rare case. + db::build()->delete("tags")->where("count", "=", 0)->execute(); + } + + /** + * Find the position of the given item in the tag collection. The resulting + * value is 1-indexed, so the first child in the album is at position 1. + * + * @param Tag_Model $tag + * @param Item_Model $item + * @param array $where an array of arrays, each compatible with ORM::where() + */ + static function get_position($tag, $item, $where=array()) { + return ORM::factory("item") + ->viewable() + ->join("items_tags", "items.id", "items_tags.item_id") + ->where("items_tags.tag_id", "=", $tag->id) + ->where("items.id", "<=", $item->id) + ->merge_where($where) + ->order_by("items.id") + ->count_all(); + } +} \ No newline at end of file diff --git a/modules/tag/helpers/tag_block.php b/modules/tag/helpers/tag_block.php new file mode 100644 index 0000000..e7a7144 --- /dev/null +++ b/modules/tag/helpers/tag_block.php @@ -0,0 +1,45 @@ + t("Popular tags")); + } + + static function get($block_id, $theme) { + $block = ""; + switch ($block_id) { + case "tag": + $block = new Block(); + $block->css_id = "g-tag"; + $block->title = t("Popular tags"); + $block->content = new View("tag_block.html"); + $block->content->cloud = tag::cloud(module::get_var("tag", "tag_cloud_size", 30)); + + if ($theme->item() && $theme->page_subtype() != "tag" && access::can("edit", $theme->item())) { + $controller = new Tags_Controller(); + $block->content->form = tag::get_add_form($theme->item()); + } else { + $block->content->form = ""; + } + break; + } + return $block; + } +} \ No newline at end of file diff --git a/modules/tag/helpers/tag_event.php b/modules/tag/helpers/tag_event.php new file mode 100644 index 0000000..d62ae36 --- /dev/null +++ b/modules/tag/helpers/tag_event.php @@ -0,0 +1,165 @@ +is_photo()) { + $path = $photo->file_path(); + $size = getimagesize($photo->file_path(), $info); + if (is_array($info) && !empty($info["APP13"])) { + $iptc = iptcparse($info["APP13"]); + if (!empty($iptc["2#025"])) { + foreach($iptc["2#025"] as $tag) { + $tag = str_replace("\0", "", $tag); + foreach (explode(",", $tag) as $word) { + $word = trim($word); + $word = encoding::convert_to_utf8($word); + $tags[$word] = 1; + } + } + } + } + } + + // @todo figure out how to read the keywords from xmp + foreach(array_keys($tags) as $tag) { + try { + tag::add($photo, $tag); + } catch (Exception $e) { + Kohana_Log::add("error", "Error adding tag: $tag\n" . + $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } + + return; + } + + static function item_deleted($item) { + tag::clear_all($item); + if (!batch::in_progress()) { + tag::compact(); + } + } + + static function batch_complete() { + tag::compact(); + } + + static function item_edit_form($item, $form) { + $url = url::site("tags/autocomplete"); + $form->script("") + ->text("$('form input[name=tags]').ready(function() { + $('form input[name=tags]').gallery_autocomplete( + '$url', {max: 30, multiple: true, multipleSeparator: ',', cacheLength: 1}); + });"); + + $tag_names = array(); + foreach (tag::item_tags($item) as $tag) { + $tag_names[] = $tag->name; + } + $form->edit_item->input("tags")->label(t("Tags (comma separated)")) + ->value(implode(", ", $tag_names)); + } + + static function item_edit_form_completed($item, $form) { + tag::clear_all($item); + foreach (explode(",", $form->edit_item->tags->value) as $tag_name) { + if ($tag_name) { + tag::add($item, trim($tag_name)); + } + } + module::event("item_related_update", $item); + tag::compact(); + } + + static function admin_menu($menu, $theme) { + $menu->get("content_menu") + ->append(Menu::factory("link") + ->id("tags") + ->label(t("Tags")) + ->url(url::site("admin/tags"))); + } + + static function item_index_data($item, $data) { + foreach (tag::item_tags($item) as $tag) { + $data[] = $tag->name; + } + } + + static function add_photos_form($album, $form) { + $group = $form->add_photos; + if (!is_object($group->uploadify)) { + return; + } + + $group->input("tags") + ->label(t("Add tags to all uploaded files")) + ->value(""); + $group->uploadify->script_data("tags", ""); + + $autocomplete_url = url::site("tags/autocomplete"); + $group->script("") + ->text("$('input[name=tags]') + .gallery_autocomplete( + '$autocomplete_url', + {max: 30, multiple: true, multipleSeparator: ',', cacheLength: 1} + ); + $('input[name=tags]') + .change(function (event) { + $('#g-uploadify').uploadifySettings('scriptData', {'tags': $(this).val()}); + });"); + } + + static function add_photos_form_completed($album, $form) { + $group = $form->add_photos; + if (!is_object($group->uploadify)) { + return; + } + + foreach (explode(",", $form->add_photos->tags->value) as $tag_name) { + $tag_name = trim($tag_name); + if ($tag_name) { + $tag = tag::add($album, $tag_name); + } + } + } + + static function info_block_get_metadata($block, $item) { + $tags = array(); + foreach (tag::item_tags($item) as $tag) { + $tags[] = "url()}\">" . + html::clean($tag->name) . ""; + } + if ($tags) { + $info = $block->content->metadata; + $info["tags"] = array( + "label" => t("Tags:"), + "value" => implode(", ", $tags) + ); + $block->content->metadata = $info; + } + } +} diff --git a/modules/tag/helpers/tag_installer.php b/modules/tag/helpers/tag_installer.php new file mode 100644 index 0000000..1fd18f3 --- /dev/null +++ b/modules/tag/helpers/tag_installer.php @@ -0,0 +1,59 @@ +query("CREATE TABLE IF NOT EXISTS {tags} ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(128) NOT NULL, + `count` int(10) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE IF NOT EXISTS {items_tags} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9) NOT NULL, + `tag_id` int(9) NOT NULL, + PRIMARY KEY (`id`), + KEY(`tag_id`, `id`), + KEY(`item_id`, `id`)) + DEFAULT CHARSET=utf8;"); + module::set_var("tag", "tag_cloud_size", 30); + } + + static function upgrade($version) { + $db = Database::instance(); + if ($version == 1) { + $db->query("ALTER TABLE {tags} MODIFY COLUMN `name` VARCHAR(128)"); + module::set_version("tag", $version = 2); + } + if ($version == 2) { + module::set_var("tag", "tag_cloud_size", 30); + module::set_version("tag", $version = 3); + } + } + + static function uninstall() { + $db = Database::instance(); + $db->query("DROP TABLE IF EXISTS {tags};"); + $db->query("DROP TABLE IF EXISTS {items_tags};"); + } +} diff --git a/modules/tag/helpers/tag_item_rest.php b/modules/tag/helpers/tag_item_rest.php new file mode 100644 index 0000000..0b7a77f --- /dev/null +++ b/modules/tag/helpers/tag_item_rest.php @@ -0,0 +1,51 @@ +url); + return array( + "url" => $request->url, + "entity" => array( + "tag" => rest::url("tag", $tag), + "item" => rest::url("item", $item))); + } + + static function delete($request) { + list ($tag, $item) = rest::resolve($request->url); + access::required("edit", $item); + $tag->remove($item); + $tag->save(); + } + + static function resolve($tuple) { + list ($tag_id, $item_id) = explode(",", $tuple); + $tag = ORM::factory("tag", $tag_id); + $item = ORM::factory("item", $item_id); + if (!$tag->loaded() || !$item->loaded() || !$tag->has($item) || !access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + + return array($tag, $item); + } + + static function url($tag, $item) { + return url::abs_site("rest/tag_item/{$tag->id},{$item->id}"); + } +} diff --git a/modules/tag/helpers/tag_items_rest.php b/modules/tag/helpers/tag_items_rest.php new file mode 100644 index 0000000..5d9919e --- /dev/null +++ b/modules/tag/helpers/tag_items_rest.php @@ -0,0 +1,64 @@ +url); + $items = array(); + foreach ($tag->items() as $item) { + if (access::can("view", $item)) { + $items[] = rest::url("tag_item", $tag, $item); + } + } + + return array( + "url" => $request->url, + "members" => $items); + } + + static function post($request) { + $tag = rest::resolve($request->params->entity->tag); + $item = rest::resolve($request->params->entity->item); + access::required("view", $item); + + if (!$tag->loaded()) { + throw new Kohana_404_Exception(); + } + + tag::add($item, $tag->name); + return array( + "url" => rest::url("tag_item", $tag, $item), + "members" => array( + "tag" => rest::url("tag", $tag), + "item" => rest::url("item", $item))); + } + + static function delete($request) { + $tag = rest::resolve($request->url); + $tag->remove_items(); + } + + static function resolve($id) { + return ORM::factory("tag", $id); + } + + static function url($tag) { + return url::abs_site("rest/tag_items/{$tag->id}"); + } +} diff --git a/modules/tag/helpers/tag_rest.php b/modules/tag/helpers/tag_rest.php new file mode 100644 index 0000000..9ccb67c --- /dev/null +++ b/modules/tag/helpers/tag_rest.php @@ -0,0 +1,88 @@ +url); + $tag_items = array(); + foreach ($tag->items() as $item) { + if (access::can("view", $item)) { + $tag_items[] = rest::url("tag_item", $tag, $item); + } + } + + return array( + "url" => $request->url, + "entity" => $tag->as_array(), + "relationships" => array( + "items" => array( + "url" => rest::url("tag_items", $tag), + "members" => $tag_items))); + } + + static function put($request) { + // Who can we allow to edit a tag name? If we allow anybody to do it then any logged in + // user can rename all your tags to something offensive. Right now limit renaming to admins. + if (!identity::active_user()->admin) { + access::forbidden(); + } + $tag = rest::resolve($request->url); + if (isset($request->params->entity->name)) { + $tag->name = $request->params->entity->name; + $tag->save(); + } + } + + static function delete($request) { + // Restrict deleting tags to admins. Otherwise, a logged in user can do great harm to an + // install. + if (!identity::active_user()->admin) { + access::forbidden(); + } + $tag = rest::resolve($request->url); + $tag->delete(); + } + + static function relationships($resource_type, $resource) { + switch ($resource_type) { + case "item": + $tags = array(); + foreach (tag::item_tags($resource) as $tag) { + $tags[] = rest::url("tag_item", $tag, $resource); + } + return array( + "tags" => array( + "url" => rest::url("item_tags", $resource), + "members" => $tags)); + } + } + + static function resolve($id) { + $tag = ORM::factory("tag", $id); + if (!$tag->loaded()) { + throw new Kohana_404_Exception(); + } + + return $tag; + } + + static function url($tag) { + return url::abs_site("rest/tag/{$tag->id}"); + } +} diff --git a/modules/tag/helpers/tag_rss.php b/modules/tag/helpers/tag_rss.php new file mode 100644 index 0000000..b432e23 --- /dev/null +++ b/modules/tag/helpers/tag_rss.php @@ -0,0 +1,48 @@ +id}"] = + t("Tag feed for %tag_name", array("tag_name" => $tag->name)); + return $feeds; + } + return array(); + } + + static function feed($feed_id, $offset, $limit, $id) { + if ($feed_id == "tag") { + $tag = ORM::factory("tag", $id); + if (!$tag->loaded()) { + throw new Kohana_404_Exception(); + } + + $feed = new stdClass(); + $feed->items = $tag->items($limit, $offset, "photo"); + $feed->max_pages = ceil($tag->count / $limit); + $feed->title = t("%site_title - %tag_name", + array("site_title" => item::root()->title, "tag_name" => $tag->name)); + $feed->description = t("Photos related to %tag_name", array("tag_name" => $tag->name)); + + return $feed; + } + } +} diff --git a/modules/tag/helpers/tag_task.php b/modules/tag/helpers/tag_task.php new file mode 100644 index 0000000..af9f2f1 --- /dev/null +++ b/modules/tag/helpers/tag_task.php @@ -0,0 +1,97 @@ +callback("tag_task::clean_up_tags") + ->name(t("Clean up tags")) + ->description(t("Correct tag counts and remove tags with no items")) + ->severity(log::SUCCESS); + return $tasks; + } + + /** + * Fix up tag counts and delete any tags that have no associated items. + * @param Task_Model the task + */ + static function clean_up_tags($task) { + $errors = array(); + try { + $start = microtime(true); + $last_tag_id = $task->get("last_tag_id", null); + $current = 0; + $total = 0; + + switch ($task->get("mode", "init")) { + case "init": + $task->set("total", ORM::factory("tag")->count_all()); + $task->set("mode", "clean_up_tags"); + $task->set("completed", 0); + $task->set("last_tag_id", 0); + + case "clean_up_tags": + $completed = $task->get("completed"); + $total = $task->get("total"); + $last_tag_id = $task->get("last_tag_id"); + $tags = ORM::factory("tag")->where("id", ">", $last_tag_id)->find_all(25); + Kohana_Log::add("error",print_r(Database::instance()->last_query(),1)); + while ($current < $total && microtime(true) - $start < 1 && $tag = $tags->current()) { + $last_tag_id = $tag->id; + $real_count = $tag->items_count(); + if ($tag->count != $real_count) { + $tag->count = $real_count; + if ($tag->count) { + $task->log( + "Fixing count for tag {$tag->name} (id: {$tag->id}, new count: {$tag->count})"); + $tag->save(); + } else { + $task->log("Deleting empty tag {$tag->name} ({$tag->id})"); + $tag->delete(); + } + } + + $completed++; + $tags->next(); + } + $task->percent_complete = $completed / $total * 100; + $task->set("completed", $completed); + $task->set("last_tag_id", $last_tag_id); + } + + $task->status = t2("Examined %count tag", "Examined %count tags", $completed); + + if ($completed == $total) { + $task->done = true; + $task->state = "success"; + $task->percent_complete = 100; + } + } catch (Exception $e) { + Kohana_Log::add("error",(string)$e); + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + $errors[] = (string)$e; + } + if ($errors) { + $task->log($errors); + } + } +} \ No newline at end of file diff --git a/modules/tag/helpers/tag_theme.php b/modules/tag/helpers/tag_theme.php new file mode 100644 index 0000000..81d1352 --- /dev/null +++ b/modules/tag/helpers/tag_theme.php @@ -0,0 +1,31 @@ +css("jquery.autocomplete.css") + . $theme->script("jquery.autocomplete.js") + . $theme->css("tag.css"); + } + + static function admin_head($theme) { + return $theme->css("tag.css") + . $theme->script("gallery.in_place_edit.js"); + } +} \ No newline at end of file diff --git a/modules/tag/helpers/tags_rest.php b/modules/tag/helpers/tags_rest.php new file mode 100644 index 0000000..88a349b --- /dev/null +++ b/modules/tag/helpers/tags_rest.php @@ -0,0 +1,77 @@ +params)) { + $p = $request->params; + $num = isset($p->num) ? min((int)$p->num, 100) : 10; + $start = isset($p->start) ? (int)$p->start : 0; + } + + foreach (ORM::factory("tag")->find_all($num, $start) as $tag) { + $tags[] = rest::url("tag", $tag); + } + return array("url" => rest::url("tags"), + "members" => $tags); + } + + static function post($request) { + // The user must have some edit permission somewhere to create a tag. + if (!identity::active_user()->admin) { + $query = db::build()->from("access_caches")->and_open(); + foreach (identity::active_user()->groups() as $group) { + $query->or_where("edit_{$group->id}", "=", access::ALLOW); + } + $has_any_edit_perm = $query->close()->count_records(); + if (!$has_any_edit_perm) { + access::forbidden(); + } + } + + if (empty($request->params->entity->name)) { + throw new Rest_Exception("Bad Request", 400); + } + + $tag = ORM::factory("tag")->where("name", "=", $request->params->entity->name)->find(); + if (!$tag->loaded()) { + $tag->name = $request->params->entity->name; + $tag->count = 0; + $tag->save(); + } + + return array("url" => rest::url("tag", $tag)); + } + + static function url() { + return url::abs_site("rest/tags"); + } +} diff --git a/modules/tag/models/tag.php b/modules/tag/models/tag.php new file mode 100644 index 0000000..0550910 --- /dev/null +++ b/modules/tag/models/tag.php @@ -0,0 +1,169 @@ +loaded()) { + // Set reasonable defaults + $this->count = 0; + } + } + + /** + * Return all viewable items associated with this tag. + * @param integer $limit number of rows to limit result to + * @param integer $offset offset in result to start returning rows from + * @param string $where an array of arrays, each compatible with ORM::where() + * @return ORM_Iterator + */ + public function items($limit=null, $offset=null, $where=array()) { + if (is_scalar($where)) { + // backwards compatibility + $where = array(array("items.type", "=", $where)); + } + return ORM::factory("item") + ->viewable() + ->join("items_tags", "items.id", "items_tags.item_id") + ->where("items_tags.tag_id", "=", $this->id) + ->merge_where($where) + ->order_by("items.id") + ->find_all($limit, $offset); + } + + /** + * Return the count of all viewable items associated with this tag. + * @param string $where an array of arrays, each compatible with ORM::where() + * @return integer + */ + public function items_count($where=array()) { + if (is_scalar($where)) { + // backwards compatibility + $where = array(array("items.type", "=", $where)); + } + return $model = ORM::factory("item") + ->viewable() + ->join("items_tags", "items.id", "items_tags.item_id") + ->where("items_tags.tag_id", "=", $this->id) + ->merge_where($where) + ->count_all(); + } + + /** + * Overload ORM::save() to trigger an item_related_update event for all items that are related + * to this tag. + */ + public function save() { + // Check to see if another tag exists with the same name + $duplicate_tag = ORM::factory("tag") + ->where("name", "=", $this->name) + ->where("id", "!=", $this->id) + ->find(); + if ($duplicate_tag->loaded()) { + // If so, tag its items with this tag so as to merge it + $duplicate_tag_items = ORM::factory("item") + ->join("items_tags", "items.id", "items_tags.item_id") + ->where("items_tags.tag_id", "=", $duplicate_tag->id) + ->find_all(); + foreach ($duplicate_tag_items as $item) { + $this->add($item); + } + + // ... and remove the duplicate tag + $duplicate_tag->delete(); + } + + if (isset($this->object_relations["items"])) { + $added = array_diff($this->changed_relations["items"], $this->object_relations["items"]); + $removed = array_diff($this->object_relations["items"], $this->changed_relations["items"]); + if (isset($this->changed_relations["items"])) { + $changed = array_merge($added, $removed); + } + $this->count = count($this->object_relations["items"]) + count($added) - count($removed); + } + + $result = parent::save(); + + if (!empty($changed)) { + foreach (ORM::factory("item")->where("id", "IN", $changed)->find_all() as $item) { + module::event("item_related_update", $item); + } + } + + return $result; + } + + /** + * Overload ORM::delete() to trigger an item_related_update event for all items that are + * related to this tag, and delete all items_tags relationships. + */ + public function delete($ignored_id=null) { + $related_item_ids = array(); + foreach (db::build() + ->select("item_id") + ->from("items_tags") + ->where("tag_id", "=", $this->id) + ->execute() as $row) { + $related_item_ids[$row->item_id] = 1; + } + + db::build()->delete("items_tags")->where("tag_id", "=", $this->id)->execute(); + $result = parent::delete(); + + if ($related_item_ids) { + foreach (ORM::factory("item") + ->where("id", "IN", array_keys($related_item_ids)) + ->find_all() as $item) { + module::event("item_related_update", $item); + } + } + return $result; + } + + /** + * Return the server-relative url to this item, eg: + * /gallery3/index.php/tags/35/Bob + * + * @param string $query the query string (eg "page=3") + */ + public function url($query=null) { + $url = url::site("tag/{$this->id}/" . urlencode($this->name)); + if ($query) { + $url .= "?$query"; + } + return $url; + } + + /** + * Return the full url to this item, eg: + * http://example.com/gallery3/index.php/tags/35/Bob + * + * @param string $query the query string (eg "page=3") + */ + public function abs_url($query=null) { + $url = url::abs_site("tag/{$this->id}/" . urlencode($this->name)); + if ($query) { + $url .= "?$query"; + } + return $url; + } +} diff --git a/modules/tag/module.info b/modules/tag/module.info new file mode 100644 index 0000000..19fbdb4 --- /dev/null +++ b/modules/tag/module.info @@ -0,0 +1,7 @@ +name = "Tags" +description = "Allows users to tag photos and albums" +version = 3 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:tag" +discuss_url = "http://galleryproject.org/forum_module_tag" diff --git a/modules/tag/views/admin_tags.html.php b/modules/tag/views/admin_tags.html.php new file mode 100644 index 0000000..e1db387 --- /dev/null +++ b/modules/tag/views/admin_tags.html.php @@ -0,0 +1,59 @@ + + + +count()/5 ?> + + +
      +

      + +
      + + + + + + +
      + count()) ?> +
      + $tag): ?> + name, 0, 1)) ?> + + + +
        + +
      + $tags_per_column): /* new column */ ?> + +
      + + + +
      +
      +
      diff --git a/modules/tag/views/tag_block.html.php b/modules/tag/views/tag_block.html.php new file mode 100644 index 0000000..d25b8dc --- /dev/null +++ b/modules/tag/views/tag_block.html.php @@ -0,0 +1,30 @@ + + +
      + + +
      + diff --git a/modules/tag/views/tag_cloud.html.php b/modules/tag/views/tag_cloud.html.php new file mode 100644 index 0000000..c900ae7 --- /dev/null +++ b/modules/tag/views/tag_cloud.html.php @@ -0,0 +1,9 @@ + +
        + +
      • + count ?> photos are tagged with + name) ?> +
      • + +
      diff --git a/modules/thumbnav/changelog.log b/modules/thumbnav/changelog.log new file mode 100644 index 0000000..2efdcc2 --- /dev/null +++ b/modules/thumbnav/changelog.log @@ -0,0 +1,20 @@ +Thumb Navigator Changelog + +version 1.8 +- ADMIN: Added option to hide albums from the list +- Module info adjusted to match new format in G3 3.0.2+ +- Fix: Added translation logic for help info +- Fix: Added check for security access to the photo +- Added support for modules which "hack" the item logic - instead of using access functionality, changed to use ->viewable() + +version 1.7: +- Fix opacity settings for IE9 + +version 1.6: +- Some minor optimizations in Admin section + +version 1.5: +- Some minor fixes + +version 1.4: +- Fixed uninitialized state for content variable \ No newline at end of file diff --git a/modules/thumbnav/controllers/admin_thumbnav.php b/modules/thumbnav/controllers/admin_thumbnav.php new file mode 100644 index 0000000..63b4b36 --- /dev/null +++ b/modules/thumbnav/controllers/admin_thumbnav.php @@ -0,0 +1,65 @@ +content = new View("admin_thumbnav.html"); + $view->content->form = $this->_get_setting_form(); + $view->content->help = $this->get_edit_form_help(); + print $view; + } + + public function save() { + access::verify_csrf(); + + $form = $this->_get_setting_form(); + if ($form->validate()): + $thumb_count = $form->g_admin_thumbnavcfg->thumb_count->value; + if ($thumb_count==9): + module::clear_var("thumbnav", "thumb_count"); + else: + module::set_var("thumbnav", "thumb_count", $thumb_count); + endif; + if ($form->g_admin_thumbnavcfg->hidealbum->value): + module::set_var("thumbnav", "hide_albums", TRUE); + else: + module::clear_var("thumbnav", "hide_albums"); + endif; + + message::success("Settings have been Saved."); + url::redirect("admin/thumbnav"); + endif; + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_thumbnav.html"); + $view->content->form = $form; + $view->content->help = $this->get_edit_form_help(); + print $view; + } + + private function _get_setting_form() { + $form = new Forge("admin/thumbnav/save", "", "post", array("id" => "g-admin-thumbnav-form")); + $group = $form->group("g_admin_thumbnavcfg")->label(t("Settings")); + $group->input("thumb_count") + ->label(t("Thumbs displayed")) + ->rules("required|valid_digit") + ->value(module::get_var("thumbnav", "thumb_count", 9)); + $group->checkbox("hidealbum")->label(t("Hide Albums in the list")) + ->checked(module::get_var("thumbnav", "hide_albums")); + $form->submit("")->value(t("Save")); + return $form; + } + + protected function get_edit_form_help() { + $help = '
      '; + $help .= 'Help
        '; + $help .= '
      • Settings

        +

        Adjust Max Number of images in the block using Thumbs displayed. Default is 9. +

      • '; + + $help .= '
      '; + return t($help); + } + +} diff --git a/modules/thumbnav/css/thumbnav.css b/modules/thumbnav/css/thumbnav.css new file mode 100644 index 0000000..549058e --- /dev/null +++ b/modules/thumbnav/css/thumbnav.css @@ -0,0 +1,11 @@ +/* Thumb Navigator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-thumbnav { text-align: center; padding: 0; } +.g-navthumb { width: 62px; height: 62px; filter:alpha(opacity=55); opacity:.55; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=55)"; } + +.g-thumbnav ul { display: inline-block; padding: 0; margin: 0 0.5em; } +.g-thumbnav li { float: left; border: transparent 1px solid;} +.g-thumbnav li.g-current { border-color: #ddd; } + +.g-thumbnav li.g-current .g-navthumb, +.g-navthumb:hover { background: #fff; filter:alpha(opacity=100); opacity:1; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; } diff --git a/modules/thumbnav/helpers/thumbnav_block.php b/modules/thumbnav/helpers/thumbnav_block.php new file mode 100644 index 0000000..898d691 --- /dev/null +++ b/modules/thumbnav/helpers/thumbnav_block.php @@ -0,0 +1,89 @@ + t("Navigator")); + } + + static function get($block_id, $theme) { + $block = ""; + switch ($block_id) { + case "thumbnav_block": + $item = $theme->item; + if ((!isset($item)) or (!$item->is_photo())): // Only should be used in photo pages + break; + endif; + + $hide_albums = module::get_var("thumbnav", "hide_albums", TRUE); + + $siblings = $item->parent()->children(); + $itemlist = Array(); + foreach ($siblings as $sibling): + if (isset($sibling)): + if ($sibling->viewable()): + if (($hide_albums and ($sibling->is_photo())) or (!$hide_albums)): + $itemlist[] = $sibling; + endif; + endif; + endif; + endforeach; + + $current = -1; + $total = count($itemlist); + + $thumb_count = module::get_var("thumbnav", "thumb_count", 9); + $thumb_count = min($thumb_count, $total); + + $shift_right = floor($thumb_count / 2); + $shift_left = $thumb_count - $shift_right - 1; + + for ($i = 1; $i <= $total; $i++): + if ($itemlist[$i-1]->rand_key == $item->rand_key): + $current = $i; + break; + endif; + endfor; + + $content = '
        '; + if ($current >= 1): + $first = $current - $shift_left; + $last = $current + $shift_right; + if ($first <= 0): + $last = min($last - $first + 1, $total); + $first = 1; + elseif ($last > $total): + $first = max($first - ($last - $total), 1); + $last = $total; + endif; + + for ($i = $first; $i <= $last; $i++): + $thumb_item = $itemlist[$i - 1]; + + if ($i == $current): + $content .= '
      • '; + else: + $content .= '
      • '; + endif; + $content .= ''; + $content .= $thumb_item->thumb_img(array("class" => "g-navthumb"), 60); + $content .= '
      • '; + endfor; + endif; + + $content .= "
      "; + $content .= "
      "; + + $block = new Block(); + $block->css_id = "g-thumbnav-block"; + $block->title = t("Navigator"); + $block->content = new View("thumbnav_block.html"); + $block->content->player = $content; + break; + } + + return $block; + } +} + +?> \ No newline at end of file diff --git a/modules/thumbnav/helpers/thumbnav_event.php b/modules/thumbnav/helpers/thumbnav_event.php new file mode 100644 index 0000000..c0e8243 --- /dev/null +++ b/modules/thumbnav/helpers/thumbnav_event.php @@ -0,0 +1,20 @@ +get("settings_menu") + ->append(Menu::factory("link") + ->id("thumbnav") + ->label(t("Thumb Navigator")) + ->url(url::site("admin/thumbnav"))); + } +} diff --git a/modules/thumbnav/helpers/thumbnav_theme.php b/modules/thumbnav/helpers/thumbnav_theme.php new file mode 100644 index 0000000..6f11b30 --- /dev/null +++ b/modules/thumbnav/helpers/thumbnav_theme.php @@ -0,0 +1,8 @@ +css("thumbnav.css"); + } +} \ No newline at end of file diff --git a/modules/thumbnav/module.info b/modules/thumbnav/module.info new file mode 100644 index 0000000..fcb03ce --- /dev/null +++ b/modules/thumbnav/module.info @@ -0,0 +1,7 @@ +name = "Thumb Navigator" +description = "Sidebar block: Adds thumb navigation option in Photo View.
      Version 1.8 | By Serguei Dosyukov | Visit plugin Site | Support | Settings" +version = 18 +author_name = "Serguei Dosyukov" +author_url = "http://blog.dragonsoft.us/gallery-3/" +info_url = "http://codex.gallery2.org/Gallery3:Modules:thumbnav" +discuss_url = "http://gallery.menalto.com/node/95116" diff --git a/modules/thumbnav/views/admin_include.html.php b/modules/thumbnav/views/admin_include.html.php new file mode 100644 index 0000000..1853596 --- /dev/null +++ b/modules/thumbnav/views/admin_include.html.php @@ -0,0 +1,95 @@ + + + + + +version / 10, 1, '.', ''); + else: + $admin_info = new ArrayObject(parse_ini_file(THEMEPATH . $name . "/theme.info"), ArrayObject::ARRAY_AS_PROPS); + $version = $admin_info->version; + endif; +?> + +
      +
      +
      name) ?> -
      + +
      +
      + +
      +
      + +
      +
      diff --git a/modules/thumbnav/views/admin_thumbnav.html.php b/modules/thumbnav/views/admin_thumbnav.html.php new file mode 100644 index 0000000..b542705 --- /dev/null +++ b/modules/thumbnav/views/admin_thumbnav.html.php @@ -0,0 +1,10 @@ + +is_module = TRUE; + $view->name = "thumbnav"; + $view->form = $form; + $view->help = $help; + print $view; +?> diff --git a/modules/thumbnav/views/thumbnav_block.html.php b/modules/thumbnav/views/thumbnav_block.html.php new file mode 100644 index 0000000..d4c2c76 --- /dev/null +++ b/modules/thumbnav/views/thumbnav_block.html.php @@ -0,0 +1,5 @@ + + +
      + +
      \ No newline at end of file diff --git a/modules/user/config/identity.php b/modules/user/config/identity.php new file mode 100644 index 0000000..f2fcebf --- /dev/null +++ b/modules/user/config/identity.php @@ -0,0 +1,37 @@ + "gallery", + "allow_updates" => true, + "params" => array(), +); diff --git a/modules/user/controllers/admin_users.php b/modules/user/controllers/admin_users.php new file mode 100644 index 0000000..ed589a3 --- /dev/null +++ b/modules/user/controllers/admin_users.php @@ -0,0 +1,442 @@ +page_title = t("Users and groups"); + $view->page_type = "collection"; + $view->page_subtype = "admin_users"; + $view->content = new View("admin_users.html"); + + // @todo: add this as a config option + $page_size = module::get_var("user", "page_size", 10); + $page = Input::instance()->get("page", "1"); + $builder = db::build(); + $user_count = $builder->from("users")->count_records(); + + // Pagination info + $view->page = $page; + $view->page_size = $page_size; + $view->children_count = $user_count; + $view->max_pages = ceil($view->children_count / $view->page_size); + + $view->content->pager = new Pagination(); + $view->content->pager->initialize( + array("query_string" => "page", + "total_items" => $user_count, + "items_per_page" => $page_size, + "style" => "classic")); + + // Make sure that the page references a valid offset + if ($page < 1) { + url::redirect(url::merge(array("page" => 1))); + } else if ($page > $view->content->pager->total_pages) { + url::redirect(url::merge(array("page" => $view->content->pager->total_pages))); + } + + // Join our users against the items table so that we can get a count of their items + // in the same query. + $view->content->users = ORM::factory("user") + ->order_by("users.name", "ASC") + ->find_all($page_size, $view->content->pager->sql_offset); + $view->content->groups = ORM::factory("group")->order_by("name", "ASC")->find_all(); + + print $view; + } + + public function add_user() { + access::verify_csrf(); + + $form = $this->_get_user_add_form_admin(); + try { + $user = ORM::factory("user"); + $valid = $form->validate(); + $user->name = $form->add_user->inputs["name"]->value; + $user->full_name = $form->add_user->full_name->value; + $user->password = $form->add_user->password->value; + $user->email = $form->add_user->email->value; + $user->url = $form->add_user->url->value; + $user->locale = $form->add_user->locale->value; + $user->admin = $form->add_user->admin->checked; + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->add_user->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $user->save(); + module::event("user_add_form_admin_completed", $user, $form); + message::success(t("Created user %user_name", array("user_name" => $user->name))); + json::reply(array("result" => "success")); + } else { + print json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function add_user_form() { + print $this->_get_user_add_form_admin(); + } + + public function delete_user($id) { + access::verify_csrf(); + + if ($id == identity::active_user()->id || $id == user::guest()->id) { + access::forbidden(); + } + + $user = user::lookup($id); + if (empty($user)) { + throw new Kohana_404_Exception(); + } + + $form = $this->_get_user_delete_form_admin($user); + if($form->validate()) { + $name = $user->name; + $user->delete(); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + + $message = t("Deleted user %user_name", array("user_name" => $name)); + log::success("user", $message); + message::success($message); + json::reply(array("result" => "success")); + } + + public function delete_user_form($id) { + $user = user::lookup($id); + if (empty($user)) { + throw new Kohana_404_Exception(); + } + $v = new View("admin_users_delete_user.html"); + $v->user = $user; + $v->form = $this->_get_user_delete_form_admin($user); + print $v; + } + + public function edit_user($id) { + access::verify_csrf(); + + $user = user::lookup($id); + if (empty($user)) { + throw new Kohana_404_Exception(); + } + + $form = $this->_get_user_edit_form_admin($user); + try { + $valid = $form->validate(); + $user->name = $form->edit_user->inputs["name"]->value; + $user->full_name = $form->edit_user->full_name->value; + if ($form->edit_user->password->value) { + $user->password = $form->edit_user->password->value; + } + $user->email = $form->edit_user->email->value; + $user->url = $form->edit_user->url->value; + $user->locale = $form->edit_user->locale->value; + if ($user->id != identity::active_user()->id) { + $user->admin = $form->edit_user->admin->checked; + } + + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_user->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $user->save(); + module::event("user_edit_form_admin_completed", $user, $form); + message::success(t("Changed user %user_name", array("user_name" => $user->name))); + json::reply(array("result" => "success")); + } else { + json::reply(array("result" => "error", "html" => (string) $form)); + } + } + + public function edit_user_form($id) { + $user = user::lookup($id); + if (empty($user)) { + throw new Kohana_404_Exception(); + } + + print $this->_get_user_edit_form_admin($user); + } + + public function add_user_to_group($user_id, $group_id) { + access::verify_csrf(); + $group = group::lookup($group_id); + $user = user::lookup($user_id); + $group->add($user); + $group->save(); + } + + public function remove_user_from_group($user_id, $group_id) { + access::verify_csrf(); + $group = group::lookup($group_id); + $user = user::lookup($user_id); + $group->remove($user); + $group->save(); + } + + public function group($group_id) { + $view = new View("admin_users_group.html"); + $view->group = group::lookup($group_id); + print $view; + } + + public function add_group() { + access::verify_csrf(); + + $form = $this->_get_group_add_form_admin(); + try { + $valid = $form->validate(); + $group = ORM::factory("group"); + $group->name = $form->add_group->inputs["name"]->value; + $group->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->add_group->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $group->save(); + message::success( + t("Created group %group_name", array("group_name" => $group->name))); + json::reply(array("result" => "success")); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function add_group_form() { + print $this->_get_group_add_form_admin(); + } + + public function delete_group($id) { + access::verify_csrf(); + + $group = group::lookup($id); + if (empty($group)) { + throw new Kohana_404_Exception(); + } + + $form = $this->_get_group_delete_form_admin($group); + if ($form->validate()) { + $name = $group->name; + $group->delete(); + } else { + json::reply(array("result" => "error", "html" => (string) $form)); + } + + $message = t("Deleted group %group_name", array("group_name" => $name)); + log::success("group", $message); + message::success($message); + json::reply(array("result" => "success")); + } + + public function delete_group_form($id) { + $group = group::lookup($id); + if (empty($group)) { + throw new Kohana_404_Exception(); + } + + print $this->_get_group_delete_form_admin($group); + } + + public function edit_group($id) { + access::verify_csrf(); + + $group = group::lookup($id); + if (empty($group)) { + throw new Kohana_404_Exception(); + } + + $form = $this->_get_group_edit_form_admin($group); + try { + $valid = $form->validate(); + $group->name = $form->edit_group->inputs["name"]->value; + $group->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_group->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $group->save(); + message::success( + t("Changed group %group_name", array("group_name" => $group->name))); + json::reply(array("result" => "success")); + } else { + $group->reload(); + message::error( + t("Failed to change group %group_name", array("group_name" => $group->name))); + json::reply(array("result" => "error", "html" => (string) $form)); + } + } + + public function edit_group_form($id) { + $group = group::lookup($id); + if (empty($group)) { + throw new Kohana_404_Exception(); + } + + print $this->_get_group_edit_form_admin($group); + } + + /* User Form Definitions */ + static function _get_user_edit_form_admin($user) { + $form = new Forge( + "admin/users/edit_user/$user->id", "", "post", array("id" => "g-edit-user-form")); + $group = $form->group("edit_user")->label(t("Edit user")); + $group->input("name")->label(t("Username"))->id("g-username")->value($user->name) + ->error_messages("required", t("A name is required")) + ->error_messages("conflict", t("There is already a user with that username")) + ->error_messages("length", t("This name is too long")); + $group->input("full_name")->label(t("Full name"))->id("g-fullname")->value($user->full_name) + ->error_messages("length", t("This name is too long")); + $group->password("password")->label(t("Password"))->id("g-password") + ->error_messages("min_length", t("This password is too short")); + $group->script("") + ->text( + '$("form").ready(function(){$(\'input[name="password"]\').user_password_strength();});'); + $group->password("password2")->label(t("Confirm password"))->id("g-password2") + ->error_messages("matches", t("The passwords you entered do not match")) + ->matches($group->password); + $group->input("email")->label(t("Email"))->id("g-email")->value($user->email) + ->error_messages("required", t("You must enter a valid email address")) + ->error_messages("length", t("This email address is too long")) + ->error_messages("email", t("You must enter a valid email address")); + $group->input("url")->label(t("URL"))->id("g-url")->value($user->url) + ->error_messages("url", t("You must enter a valid URL")); + self::_add_locale_dropdown($group, $user); + $group->checkbox("admin")->label(t("Admin"))->id("g-admin")->checked($user->admin); + + // Don't allow the user to control their own admin bit, else you can lock yourself out + if ($user->id == identity::active_user()->id) { + $group->admin->disabled(1); + } + + module::event("user_edit_form_admin", $user, $form); + $group->submit("")->value(t("Modify user")); + return $form; + } + + static function _get_user_add_form_admin() { + $form = new Forge("admin/users/add_user", "", "post", array("id" => "g-add-user-form")); + $group = $form->group("add_user")->label(t("Add user")); + $group->input("name")->label(t("Username"))->id("g-username") + ->error_messages("required", t("A name is required")) + ->error_messages("length", t("This name is too long")) + ->error_messages("conflict", t("There is already a user with that username")); + $group->input("full_name")->label(t("Full name"))->id("g-fullname") + ->error_messages("length", t("This name is too long")); + $group->password("password")->label(t("Password"))->id("g-password") + ->error_messages("min_length", t("This password is too short")); + $group->script("") + ->text( + '$("form").ready(function(){$(\'input[name="password"]\').user_password_strength();});'); + $group->password("password2")->label(t("Confirm password"))->id("g-password2") + ->error_messages("matches", t("The passwords you entered do not match")) + ->matches($group->password); + $group->input("email")->label(t("Email"))->id("g-email") + ->error_messages("required", t("You must enter a valid email address")) + ->error_messages("length", t("This email address is too long")) + ->error_messages("email", t("You must enter a valid email address")); + $group->input("url")->label(t("URL"))->id("g-url") + ->error_messages("url", t("You must enter a valid URL")); + self::_add_locale_dropdown($group); + $group->checkbox("admin")->label(t("Admin"))->id("g-admin"); + + module::event("user_add_form_admin", $user, $form); + $group->submit("")->value(t("Add user")); + return $form; + } + + private static function _add_locale_dropdown(&$form, $user=null) { + $locales = locales::installed(); + foreach ($locales as $locale => $display_name) { + $locales[$locale] = SafeString::of_safe_html($display_name); + } + + // Put "none" at the first position in the array + $locales = array_merge(array("" => t("« none »")), $locales); + $selected_locale = ($user && $user->locale) ? $user->locale : ""; + $form->dropdown("locale") + ->label(t("Language preference")) + ->options($locales) + ->selected($selected_locale); + } + + private function _get_user_delete_form_admin($user) { + $form = new Forge("admin/users/delete_user/$user->id", "", "post", + array("id" => "g-delete-user-form")); + $group = $form->group("delete_user")->label( + t("Delete user %name?", array("name" => $user->display_name()))); + $group->submit("")->value(t("Delete")); + return $form; + } + + /* Group Form Definitions */ + private function _get_group_edit_form_admin($group) { + $form = new Forge("admin/users/edit_group/$group->id", "", "post", array("id" => "g-edit-group-form")); + $form_group = $form->group("edit_group")->label(t("Edit group")); + $form_group->input("name")->label(t("Name"))->id("g-name")->value($group->name) + ->error_messages("required", t("A name is required")); + $form_group->inputs["name"]->error_messages("conflict", t("There is already a group with that name")) + ->error_messages("required", t("You must enter a group name")) + ->error_messages("length", + t("The group name must be less than %max_length characters", + array("max_length" => 255))); + $form_group->submit("")->value(t("Save")); + return $form; + } + + private function _get_group_add_form_admin() { + $form = new Forge("admin/users/add_group", "", "post", array("id" => "g-add-group-form")); + $form_group = $form->group("add_group")->label(t("Add group")); + $form_group->input("name")->label(t("Name"))->id("g-name"); + $form_group->inputs["name"]->error_messages("conflict", t("There is already a group with that name")) + ->error_messages("required", t("You must enter a group name")); + $form_group->submit("")->value(t("Add group")); + return $form; + } + + private function _get_group_delete_form_admin($group) { + $form = new Forge("admin/users/delete_group/$group->id", "", "post", + array("id" => "g-delete-group-form")); + $form_group = $form->group("delete_group")->label( + t("Are you sure you want to delete group %group_name?", array("group_name" => $group->name))); + $form_group->submit("")->value(t("Delete")); + return $form; + } +} diff --git a/modules/user/controllers/password.php b/modules/user/controllers/password.php new file mode 100644 index 0000000..ea14144 --- /dev/null +++ b/modules/user/controllers/password.php @@ -0,0 +1,141 @@ +validate()) { + $this->_send_reset($form); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } else { + print $form; + } + } + + public function do_reset() { + if (request::method() == "post") { + $this->_change_password(); + } else { + $user = user::lookup_by_hash(Input::instance()->get("key")); + if (!empty($user)) { + print $this->_new_password_form($user->hash); + } else { + throw new Exception("@todo FORBIDDEN", 503); + } + } + } + + private function _send_reset($form) { + $user_name = $form->reset->inputs["name"]->value; + $user = user::lookup_by_name($user_name); + if ($user && !empty($user->email)) { + $user->hash = random::hash(); + $user->save(); + $message = new View("reset_password.html"); + $message->confirm_url = url::abs_site("password/do_reset?key=$user->hash"); + $message->user = $user; + + Sendmail::factory() + ->to($user->email) + ->subject(t("Password Reset Request")) + ->header("Mime-Version", "1.0") + ->header("Content-type", "text/html; charset=UTF-8") + ->message($message->render()) + ->send(); + + log::success( + "user", + t("Password reset email sent for user %name", array("name" => $user->name))); + } else if (!$user) { + // Don't include the username here until you're sure that it's XSS safe + log::warning( + "user", t("Password reset email requested for user %user_name, which does not exist.", + array("user_name" => $user_name))); + } else { + log::warning( + "user", t("Password reset failed for %user_name (has no email address on record).", + array("user_name" => $user->name))); + } + + // Always pretend that an email has been sent to avoid leaking + // information on what user names are actually real. + message::success(t("Password reset email sent")); + json::reply(array("result" => "success")); + } + + private static function _reset_form() { + $form = new Forge(url::current(true), "", "post", array("id" => "g-reset-form")); + $group = $form->group("reset")->label(t("Reset Password")); + $group->input("name")->label(t("Username"))->id("g-name")->class(null) + ->rules("required") + ->error_messages("required", t("You must enter a user name")); + $group->submit("")->value(t("Reset")); + + return $form; + } + + private function _new_password_form($hash=null) { + $template = new Theme_View("page.html", "other", "reset"); + + $form = new Forge("password/do_reset", "", "post", array("id" => "g-change-password-form")); + $group = $form->group("reset")->label(t("Change Password")); + $hidden = $group->hidden("hash"); + if (!empty($hash)) { + $hidden->value($hash); + } + $minimum_length = module::get_var("user", "minimum_password_length", 5); + $input_password = $group->password("password")->label(t("Password"))->id("g-password") + ->rules($minimum_length ? "required|length[$minimum_length, 40]" : "length[40]"); + $group->password("password2")->label(t("Confirm Password"))->id("g-password2") + ->matches($group->password); + $group->inputs["password2"]->error_messages( + "mistyped", t("The password and the confirm password must match")); + $group->submit("")->value(t("Update")); + + $template->content = $form; + return $template; + } + + private function _change_password() { + $view = $this->_new_password_form(); + if ($view->content->validate()) { + $user = user::lookup_by_hash(Input::instance()->post("hash")); + if (empty($user)) { + throw new Exception("@todo FORBIDDEN", 503); + } + + $user->password = $view->content->reset->password->value; + $user->hash = null; + $user->save(); + message::success(t("Password reset successfully")); + url::redirect(item::root()->abs_url()); + } else { + print $view; + } + } +} \ No newline at end of file diff --git a/modules/user/controllers/users.php b/modules/user/controllers/users.php new file mode 100644 index 0000000..ee81344 --- /dev/null +++ b/modules/user/controllers/users.php @@ -0,0 +1,239 @@ +guest || $user->id != identity::active_user()->id) { + access::forbidden(); + } + + $form = $this->_get_edit_form($user); + try { + $valid = $form->validate(); + $user->full_name = $form->edit_user->full_name->value; + $user->url = $form->edit_user->url->value; + + if (count(locales::installed()) > 1 && + $user->locale != $form->edit_user->locale->value) { + $user->locale = $form->edit_user->locale->value; + $flush_locale_cookie = true; + } + + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->edit_user->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + if (isset($flush_locale_cookie)) { + // Delete the session based locale preference + setcookie("g_locale", "", time() - 24 * 3600, "/"); + } + + $user->save(); + module::event("user_edit_form_completed", $user, $form); + message::success(t("User information updated")); + json::reply(array("result" => "success", + "resource" => url::site("users/{$user->id}"))); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function change_password($id) { + $user = user::lookup($id); + if (!$user || $user->guest || $user->id != identity::active_user()->id) { + access::forbidden(); + } + + $form = $this->_get_change_password_form($user); + try { + $valid = $form->validate(); + $user->password = $form->change_password->password->value; + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->change_password->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $user->save(); + module::event("user_change_password_form_completed", $user, $form); + message::success(t("Password changed")); + module::event("user_auth", $user); + module::event("user_password_change", $user); + json::reply(array("result" => "success", + "resource" => url::site("users/{$user->id}"))); + } else { + log::warning("user", t("Failed password change for %name", array("name" => $user->name))); + $name = $user->name; + module::event("user_auth_failed", $name); + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function change_email($id) { + $user = user::lookup($id); + if (!$user || $user->guest || $user->id != identity::active_user()->id) { + access::forbidden(); + } + + $form = $this->_get_change_email_form($user); + try { + $valid = $form->validate(); + $user->email = $form->change_email->email->value; + $user->validate(); + } catch (ORM_Validation_Exception $e) { + // Translate ORM validation errors into form error messages + foreach ($e->validation->errors() as $key => $error) { + $form->change_email->inputs[$key]->add_error($error, 1); + } + $valid = false; + } + + if ($valid) { + $user->save(); + module::event("user_change_email_form_completed", $user, $form); + message::success(t("Email address changed")); + module::event("user_auth", $user); + json::reply(array("result" => "success", + "resource" => url::site("users/{$user->id}"))); + } else { + log::warning("user", t("Failed email change for %name", array("name" => $user->name))); + $name = $user->name; + module::event("user_auth_failed", $name); + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function form_edit($id) { + $user = user::lookup($id); + if (!$user || $user->guest || $user->id != identity::active_user()->id) { + access::forbidden(); + } + + print $this->_get_edit_form($user); + } + + public function form_change_password($id) { + $user = user::lookup($id); + if (!$user || $user->guest || $user->id != identity::active_user()->id) { + access::forbidden(); + } + + print $this->_get_change_password_form($user); + } + + public function form_change_email($id) { + $user = user::lookup($id); + if (!$user || $user->guest || $user->id != identity::active_user()->id) { + access::forbidden(); + } + + print $this->_get_change_email_form($user); + } + + private function _get_change_password_form($user) { + $form = new Forge( + "users/change_password/$user->id", "", "post", array("id" => "g-change-password-user-form")); + $group = $form->group("change_password")->label(t("Change your password")); + $group->password("old_password")->label(t("Old password"))->id("g-password") + ->callback("auth::validate_too_many_failed_auth_attempts") + ->callback("user::valid_password") + ->error_messages("invalid_password", t("Incorrect password")) + ->error_messages( + "too_many_failed_auth_attempts", + t("Too many incorrect passwords. Try again later")); + $group->password("password")->label(t("New password"))->id("g-password") + ->error_messages("min_length", t("Your new password is too short")); + $group->script("") + ->text( + '$("form").ready(function(){$(\'input[name="password"]\').user_password_strength();});'); + $group->password("password2")->label(t("Confirm new password"))->id("g-password2") + ->matches($group->password) + ->error_messages("matches", t("The passwords you entered do not match")); + + module::event("user_change_password_form", $user, $form); + $group->submit("")->value(t("Save")); + return $form; + } + + private function _get_change_email_form($user) { + $form = new Forge( + "users/change_email/$user->id", "", "post", array("id" => "g-change-email-user-form")); + $group = $form->group("change_email")->label(t("Change your email address")); + $group->password("password")->label(t("Current password"))->id("g-password") + ->callback("auth::validate_too_many_failed_auth_attempts") + ->callback("user::valid_password") + ->error_messages("invalid_password", t("Incorrect password")) + ->error_messages( + "too_many_failed_auth_attempts", + t("Too many incorrect passwords. Try again later")); + $group->input("email")->label(t("New email address"))->id("g-email")->value($user->email) + ->error_messages("email", t("You must enter a valid email address")) + ->error_messages("length", t("Your email address is too long")) + ->error_messages("required", t("You must enter a valid email address")); + + module::event("user_change_email_form", $user, $form); + $group->submit("")->value(t("Save")); + return $form; + } + + private function _get_edit_form($user) { + $form = new Forge("users/update/$user->id", "", "post", array("id" => "g-edit-user-form")); + $group = $form->group("edit_user")->label(t("Edit your profile")); + $group->input("full_name")->label(t("Full Name"))->id("g-fullname")->value($user->full_name) + ->error_messages("length", t("Your name is too long")); + self::_add_locale_dropdown($group, $user); + $group->input("url")->label(t("URL"))->id("g-url")->value($user->url) + ->error_messages("url", t("You must enter a valid url")); + + module::event("user_edit_form", $user, $form); + $group->submit("")->value(t("Save")); + return $form; + } + + /** @todo combine with Admin_Users_Controller::_add_locale_dropdown */ + private function _add_locale_dropdown(&$form, $user=null) { + $locales = locales::installed(); + if (count($locales) <= 1) { + return; + } + + foreach ($locales as $locale => $display_name) { + $locales[$locale] = SafeString::of_safe_html($display_name); + } + + // Put "none" at the first position in the array + $locales = array_merge(array("" => t("« none »")), $locales); + $selected_locale = ($user && $user->locale) ? $user->locale : ""; + $form->dropdown("locale") + ->label(t("Language preference")) + ->options($locales) + ->selected($selected_locale); + } +} diff --git a/modules/user/css/user.css b/modules/user/css/user.css new file mode 100644 index 0000000..93e3d02 --- /dev/null +++ b/modules/user/css/user.css @@ -0,0 +1,120 @@ +/* User- and group-related form width ~~~~ */ + +#g-login-form, +#g-add-user-form +#g-edit-user-form, +#g-delete-user-form, +#g-user-admin { + width: 270px; +} + +/* User/group admin ~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-user-admin { + width: auto; + margin-bottom: 4em; +} + +#g-group-admin { +} + +#g-user-admin-list .g-admin { + color: #55f; + font-weight: bold; +} + +.g-group { + display: block; + border: 1px solid #999; + margin: 0 1em 1em 0; + padding: 0; + width: 200px; +} + +.g-group h4 { + background-color: #eee; + border-bottom: 1px dashed #ccc; + padding: .5em 0 .5em .5em; +} + +.g-group .g-button { + padding: 0; +} + +.g-group .g-member-list, +.g-group div { + height: 180px; + margin: 1px; + overflow: auto; +} + +.g-group p { + margin-top: 1em; + padding: .5em; + text-align: center; +} + +.g-group .g-user { + padding: .2em 0 0 .5em; +} + +.g-group .g-user .g-button { + vertical-align: middle; +} + +.g-default-group h4, +.g-default-group .g-user { + color: #999; +} + +.g-group.ui-droppable { + padding: 0 !important; +} + +/* Password strength meter ~~~~~~~~~~~~~~~ */ + +.g-password-strength0 { + background: url(../images/progressImg1.png) no-repeat 0 0; + width: 138px; + height: 7px; +} + +.g-password-strength10 { + background-position:0 -7px; +} + +.g-password-strength20 { + background-position:0 -14px; +} + +.g-password-strength30 { + background-position:0 -21px; +} + +.g-password-strength40 { + background-position:0 -28px; +} + +.g-password-strength50 { + background-position:0 -35px; +} + +.g-password-strength60 { + background-position:0 -42px; +} + +.g-password-strength70 { + background-position:0 -49px; +} + +.g-password-strength80 { + background-position:0 -56px; +} + +.g-password-strength90 { + background-position:0 -63px; +} + +.g-password-strength100 { + background-position:0 -70px; +} diff --git a/modules/user/helpers/group.php b/modules/user/helpers/group.php new file mode 100644 index 0000000..59c9859 --- /dev/null +++ b/modules/user/helpers/group.php @@ -0,0 +1,82 @@ +loaded()) { + return $group; + } + } catch (Exception $e) { + if (strpos($e->getMessage(), "MISSING_MODEL") === false) { + throw $e; + } + } + return null; + } +} diff --git a/modules/user/helpers/user.php b/modules/user/helpers/user.php new file mode 100644 index 0000000..f59cf76 --- /dev/null +++ b/modules/user/helpers/user.php @@ -0,0 +1,176 @@ +admin) { + return $active; + } + + return ORM::factory("user")->where("admin", "=", 1)->order_by("id", "ASC")->find(); + } + + /** + * Is the password provided correct? + * + * @param user User Model + * @param string $password a plaintext password + * @return boolean true if the password is correct + */ + static function is_correct_password($user, $password) { + $valid = $user->password; + + // Try phpass first, since that's what we generate. + if (strlen($valid) == 34) { + require_once(MODPATH . "user/lib/PasswordHash.php"); + $hashGenerator = new PasswordHash(10, true); + return $hashGenerator->CheckPassword($password, $valid); + } + + $salt = substr($valid, 0, 4); + // Support both old (G1 thru 1.4.0; G2 thru alpha-4) and new password schemes: + $guess = (strlen($valid) == 32) ? md5($password) : ($salt . md5($salt . $password)); + if (!strcmp($guess, $valid)) { + return true; + } + + // Passwords with <&"> created by G2 prior to 2.1 were hashed with entities + $sanitizedPassword = html::chars($password, false); + $guess = (strlen($valid) == 32) ? md5($sanitizedPassword) + : ($salt . md5($salt . $sanitizedPassword)); + if (!strcmp($guess, $valid)) { + return true; + } + + return false; + } + + static function valid_password($password_input) { + if (!user::is_correct_password(identity::active_user(), $password_input->value)) { + $password_input->add_error("invalid_password", 1); + } + } + + static function valid_username($text_input) { + if (!self::lookup_by_name($text_input->value)) { + $text_input->add_error("invalid_username", 1); + } + } + + /** + * Create the hashed passwords. + * @param string $password a plaintext password + * @return string hashed password + */ + static function hash_password($password) { + require_once(MODPATH . "user/lib/PasswordHash.php"); + $hashGenerator = new PasswordHash(10, true); + return $hashGenerator->HashPassword($password); + } + + /** + * Look up a user by id. + * @param integer $id the user id + * @return User_Model the user object, or null if the id was invalid. + */ + static function lookup($id) { + return self::_lookup_user_by_field("id", $id); + } + + /** + * Look up a user by name. + * @param integer $name the user name + * @return User_Model the user object, or null if the name was invalid. + */ + static function lookup_by_name($name) { + return self::_lookup_user_by_field("name", $name); + } + + /** + * Look up a user by hash. + * @param integer $hash the user hash value + * @return User_Model the user object, or null if the name was invalid. + */ + static function lookup_by_hash($hash) { + return self::_lookup_user_by_field("hash", $hash); + } + + /** + * List the users + * @param mixed filters (@see Database.php + * @return array the user list. + */ + static function get_user_list($filter=array()) { + $user = ORM::factory("user"); + + foreach($filter as $method => $args) { + switch ($method) { + case "in": + $user->in($args[0], $args[1]); + break; + default: + $user->$method($args); + } + } + return $user->find_all(); + } + + /** + * Look up a user by field value. + * @param string search field + * @param string search value + * @return User_Core the user object, or null if the name was invalid. + */ + private static function _lookup_user_by_field($field_name, $value) { + try { + $user = model_cache::get("user", $value, $field_name); + if ($user->loaded()) { + return $user; + } + } catch (Exception $e) { + if (strpos($e->getMessage(), "MISSING_MODEL") === false) { + throw $e; + } + } + return null; + } +} \ No newline at end of file diff --git a/modules/user/helpers/user_event.php b/modules/user/helpers/user_event.php new file mode 100644 index 0000000..40f6dde --- /dev/null +++ b/modules/user/helpers/user_event.php @@ -0,0 +1,30 @@ +add_after("appearance_menu", Menu::factory("link") + ->id("users_groups") + ->label(t("Users/Groups")) + ->url(url::site("admin/users"))); + + return $menu; + } +} diff --git a/modules/user/helpers/user_installer.php b/modules/user/helpers/user_installer.php new file mode 100644 index 0000000..67f6a3d --- /dev/null +++ b/modules/user/helpers/user_installer.php @@ -0,0 +1,142 @@ + array(IdentityProvider::confirmation_message())); + } + + static function activate() { + IdentityProvider::change_provider("user"); + // Set the latest version in initialize() below + } + + static function upgrade($version) { + if ($version == 1) { + module::set_var("user", "mininum_password_length", 5); + module::set_version("user", $version = 2); + } + + if ($version == 2) { + db::build() + ->update("users") + ->set("email", "unknown@unknown.com") + ->where("guest", "=", 0) + ->and_open() + ->where("email", "IS", null) + ->or_where("email", "=", "") + ->close() + ->execute(); + module::set_version("user", $version = 3); + } + + if ($version == 3) { + $password_length = module::get_var("user", "mininum_password_length", 5); + module::set_var("user", "minimum_password_length", $password_length); + module::clear_var("user", "mininum_password_length"); + module::set_version("user", $version = 4); + } + } + + static function uninstall() { + // Delete all users and groups so that we give other modules an opportunity to clean up + foreach (ORM::factory("user")->find_all() as $user) { + $user->delete(); + } + + foreach (ORM::factory("group")->find_all() as $group) { + $group->delete(); + } + + $db = Database::instance(); + $db->query("DROP TABLE IF EXISTS {users};"); + $db->query("DROP TABLE IF EXISTS {groups};"); + $db->query("DROP TABLE IF EXISTS {groups_users};"); + } + + static function initialize() { + $db = Database::instance(); + $db->query("CREATE TABLE IF NOT EXISTS {users} ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(32) NOT NULL, + `full_name` varchar(255) NOT NULL, + `password` varchar(64) NOT NULL, + `login_count` int(10) unsigned NOT NULL DEFAULT 0, + `last_login` int(10) unsigned NOT NULL DEFAULT 0, + `email` varchar(64) default NULL, + `admin` BOOLEAN default 0, + `guest` BOOLEAN default 0, + `hash` char(32) default NULL, + `url` varchar(255) default NULL, + `locale` char(10) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`hash`), + UNIQUE KEY(`name`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE IF NOT EXISTS {groups} ( + `id` int(9) NOT NULL auto_increment, + `name` char(64) default NULL, + `special` BOOLEAN default 0, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE IF NOT EXISTS {groups_users} ( + `group_id` int(9) NOT NULL, + `user_id` int(9) NOT NULL, + PRIMARY KEY (`group_id`, `user_id`), + UNIQUE KEY(`user_id`, `group_id`)) + DEFAULT CHARSET=utf8;"); + + $everybody = ORM::factory("group"); + $everybody->name = "Everybody"; + $everybody->special = true; + $everybody->save(); + + $registered = ORM::factory("group"); + $registered->name = "Registered Users"; + $registered->special = true; + $registered->save(); + + $guest = ORM::factory("user"); + $guest->name = "guest"; + $guest->full_name = "Guest User"; + $guest->password = ""; + $guest->guest = true; + $guest->save(); + + $admin = ORM::factory("user"); + $admin->name = "admin"; + $admin->full_name = "Gallery Administrator"; + $admin->password = "admin"; + $admin->email = "unknown@unknown.com"; + $admin->admin = true; + $admin->save(); + + $root = ORM::factory("item", 1); + access::allow($everybody, "view", $root); + access::allow($everybody, "view_full", $root); + + access::allow($registered, "view", $root); + access::allow($registered, "view_full", $root); + + module::set_var("user", "minimum_password_length", 5); + } +} \ No newline at end of file diff --git a/modules/user/helpers/user_theme.php b/modules/user/helpers/user_theme.php new file mode 100644 index 0000000..c608cdf --- /dev/null +++ b/modules/user/helpers/user_theme.php @@ -0,0 +1,30 @@ +css("user.css") + . $theme->script("password_strength.js"); + } + + static function admin_head($theme) { + return $theme->css("user.css") + . $theme->script("password_strength.js"); + } +} \ No newline at end of file diff --git a/modules/user/images/progressImg1.png b/modules/user/images/progressImg1.png new file mode 100644 index 0000000..a909364 Binary files /dev/null and b/modules/user/images/progressImg1.png differ diff --git a/modules/user/js/password_strength.js b/modules/user/js/password_strength.js new file mode 100644 index 0000000..2442b8d --- /dev/null +++ b/modules/user/js/password_strength.js @@ -0,0 +1,39 @@ +(function($) { + // Based on the Password Strength Indictor By Benjamin Sterling + // http://benjaminsterling.com/password-strength-indicator-and-generator/ + $.widget("ui.user_password_strength", { + _init: function() { + var self = this; + $(this.element).keyup(function() { + var strength = self.calculateStrength (this.value); + var index = Math.min(Math.floor( strength / 10 ), 10); + $("#g-password-gauge") + .removeAttr('class') + .addClass( "g-password-strength0" ) + .addClass( self.options.classes[ index ] ); + }).after("
      "); + }, + + calculateStrength: function(value) { + // Factor in the length of the password + var strength = Math.min(5, value.length) * 10 - 20; + // Factor in the number of numbers + strength += Math.min(3, value.length - value.replace(/[0-9]/g,"").length) * 10; + // Factor in the number of non word characters + strength += Math.min(3, value.length - value.replace(/\W/g,"").length) * 15; + // Factor in the number of Upper case letters + strength += Math.min(3, value.length - value.replace(/[A-Z]/g,"").length) * 10; + + // Normalizxe between 0 and 100 + return Math.max(0, Math.min(100, strength)); + } + }); + $.extend($.ui.user_password_strength, { + defaults: { + classes : ['g-password-strength10', 'g-password-strength20', 'g-password-strength30', + 'g-password-strength40', 'g-password-strength50', 'g-password-strength60', + 'g-password-strength70',' g-password-strength80',' g-password-strength90', + 'g-password-strength100'] + } + }); + })(jQuery); diff --git a/modules/user/lib/PasswordHash.php b/modules/user/lib/PasswordHash.php new file mode 100644 index 0000000..d6783c0 --- /dev/null +++ b/modules/user/lib/PasswordHash.php @@ -0,0 +1,248 @@ + in 2004-2006 and placed in +# the public domain. +# +# There's absolutely no warranty. +# +# The homepage URL for this framework is: +# +# http://www.openwall.com/phpass/ +# +# Please be sure to update the Version line if you edit this file in any way. +# It is suggested that you leave the main version number intact, but indicate +# your project name (after the slash) and add your own revision information. +# +# Please do not change the "private" password hashing method implemented in +# here, thereby making your hashes incompatible. However, if you must, please +# change the hash type identifier (the "$P$") to something different. +# +# Obviously, since this code is in the public domain, the above are not +# requirements (there can be none), but merely suggestions. +# +class PasswordHash { + var $itoa64; + var $iteration_count_log2; + var $portable_hashes; + var $random_state; + + function PasswordHash($iteration_count_log2, $portable_hashes) + { + $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) + $iteration_count_log2 = 8; + $this->iteration_count_log2 = $iteration_count_log2; + + $this->portable_hashes = $portable_hashes; + + $this->random_state = microtime() . getmypid(); + } + + function get_random_bytes($count) + { + $output = ''; + if (($fh = @fopen('/dev/urandom', 'rb'))) { + $output = fread($fh, $count); + fclose($fh); + } + + if (strlen($output) < $count) { + $output = ''; + for ($i = 0; $i < $count; $i += 16) { + $this->random_state = + md5(microtime() . $this->random_state); + $output .= + pack('H*', md5($this->random_state)); + } + $output = substr($output, 0, $count); + } + + return $output; + } + + function encode64($input, $count) + { + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + if ($i < $count) + $value |= ord($input[$i]) << 8; + $output .= $this->itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) + break; + if ($i < $count) + $value |= ord($input[$i]) << 16; + $output .= $this->itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) + break; + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + function gensalt_private($input) + { + $output = '$P$'; + $output .= $this->itoa64[min($this->iteration_count_log2 + + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; + $output .= $this->encode64($input, 6); + + return $output; + } + + function crypt_private($password, $setting) + { + $output = '*0'; + if (substr($setting, 0, 2) == $output) + $output = '*1'; + + if (substr($setting, 0, 3) != '$P$') + return $output; + + $count_log2 = strpos($this->itoa64, $setting[3]); + if ($count_log2 < 7 || $count_log2 > 30) + return $output; + + $count = 1 << $count_log2; + + $salt = substr($setting, 4, 8); + if (strlen($salt) != 8) + return $output; + + # We're kind of forced to use MD5 here since it's the only + # cryptographic primitive available in all versions of PHP + # currently in use. To implement our own low-level crypto + # in PHP would result in much worse performance and + # consequently in lower iteration counts and hashes that are + # quicker to crack (by non-PHP code). + if (PHP_VERSION >= '5') { + $hash = md5($salt . $password, TRUE); + do { + $hash = md5($hash . $password, TRUE); + } while (--$count); + } else { + $hash = pack('H*', md5($salt . $password)); + do { + $hash = pack('H*', md5($hash . $password)); + } while (--$count); + } + + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, 16); + + return $output; + } + + function gensalt_extended($input) + { + $count_log2 = min($this->iteration_count_log2 + 8, 24); + # This should be odd to not reveal weak DES keys, and the + # maximum valid value is (2**24 - 1) which is odd anyway. + $count = (1 << $count_log2) - 1; + + $output = '_'; + $output .= $this->itoa64[$count & 0x3f]; + $output .= $this->itoa64[($count >> 6) & 0x3f]; + $output .= $this->itoa64[($count >> 12) & 0x3f]; + $output .= $this->itoa64[($count >> 18) & 0x3f]; + + $output .= $this->encode64($input, 3); + + return $output; + } + + function gensalt_blowfish($input) + { + # This one needs to use a different order of characters and a + # different encoding scheme from the one in encode64() above. + # We care because the last character in our encoded string will + # only represent 2 bits. While two known implementations of + # bcrypt will happily accept and correct a salt string which + # has the 4 unused bits set to non-zero, we do not want to take + # chances and we also do not want to waste an additional byte + # of entropy. + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = '$2a$'; + $output .= chr(ord('0') + $this->iteration_count_log2 / 10); + $output .= chr(ord('0') + $this->iteration_count_log2 % 10); + $output .= '$'; + + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } + + function HashPassword($password) + { + $random = ''; + + if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { + $random = $this->get_random_bytes(16); + $hash = + crypt($password, $this->gensalt_blowfish($random)); + if (strlen($hash) == 60) + return $hash; + } + + if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { + if (strlen($random) < 3) + $random = $this->get_random_bytes(3); + $hash = + crypt($password, $this->gensalt_extended($random)); + if (strlen($hash) == 20) + return $hash; + } + + if (strlen($random) < 6) + $random = $this->get_random_bytes(6); + $hash = + $this->crypt_private($password, + $this->gensalt_private($random)); + if (strlen($hash) == 34) + return $hash; + + # Returning '*' on error is safe here, but would _not_ be safe + # in a crypt(3)-like function used _both_ for generating new + # hashes and for validating passwords against existing hashes. + return '*'; + } + + function CheckPassword($password, $stored_hash) + { + $hash = $this->crypt_private($password, $stored_hash); + if ($hash[0] == '*') + $hash = crypt($password, $stored_hash); + + return $hash == $stored_hash; + } +} + +?> diff --git a/modules/user/libraries/drivers/IdentityProvider/Gallery.php b/modules/user/libraries/drivers/IdentityProvider/Gallery.php new file mode 100644 index 0000000..67da33d --- /dev/null +++ b/modules/user/libraries/drivers/IdentityProvider/Gallery.php @@ -0,0 +1,164 @@ +name = $name; + $user->full_name = $full_name; + $user->password = $password; + $user->email = $email; + return $user->save(); + } + + /** + * @see IdentityProvider_Driver::is_correct_password. + */ + public function is_correct_password($user, $password) { + $valid = $user->password; + + // Try phpass first, since that's what we generate. + if (strlen($valid) == 34) { + require_once(MODPATH . "user/lib/PasswordHash.php"); + $hashGenerator = new PasswordHash(10, true); + return $hashGenerator->CheckPassword($password, $valid); + } + + $salt = substr($valid, 0, 4); + // Support both old (G1 thru 1.4.0; G2 thru alpha-4) and new password schemes: + $guess = (strlen($valid) == 32) ? md5($password) : ($salt . md5($salt . $password)); + if (!strcmp($guess, $valid)) { + return true; + } + + // Passwords with <&"> created by G2 prior to 2.1 were hashed with entities + $sanitizedPassword = html::chars($password, false); + $guess = (strlen($valid) == 32) ? md5($sanitizedPassword) + : ($salt . md5($salt . $sanitizedPassword)); + if (!strcmp($guess, $valid)) { + return true; + } + + return false; + } + + /** + * @see IdentityProvider_Driver::lookup_user. + */ + public function lookup_user($id) { + return user::lookup($id); + } + + /** + * @see IdentityProvider_Driver::lookup_user_by_name. + */ + public function lookup_user_by_name($name) { + return user::lookup_by_name($name); + } + + /** + * @see IdentityProvider_Driver::create_group. + */ + public function create_group($name) { + $group = ORM::factory("group"); + $group->name = $name; + return $group->save(); + } + + /** + * @see IdentityProvider_Driver::everybody. + */ + public function everybody() { + return group::everybody(); + } + + /** + * @see IdentityProvider_Driver::registered_users. + */ + public function registered_users() { + return group::registered_users(); + } + + /** + * @see IdentityProvider_Driver::lookup_group. + */ + public function lookup_group($id) { + return group::lookup($id); + } + + /** + * @see IdentityProvider_Driver::lookup_group_by_name. + */ + public function lookup_group_by_name($name) { + return group::lookup_by_name($name); + } + + /** + * @see IdentityProvider_Driver::get_user_list. + */ + public function get_user_list($ids) { + return ORM::factory("user") + ->where("id", "IN", $ids) + ->find_all(); + } + + /** + * @see IdentityProvider_Driver::groups. + */ + public function groups() { + return ORM::factory("group")->find_all(); + } + + /** + * @see IdentityProvider_Driver::add_user_to_group. + */ + public function add_user_to_group($user, $group) { + $group->add($user); + $group->save(); + } + + /** + * @see IdentityProvider_Driver::remove_user_to_group. + */ + public function remove_user_from_group($user, $group) { + $group->remove($user); + $group->save(); + } +} // End Identity Gallery Driver + diff --git a/modules/user/models/group.php b/modules/user/models/group.php new file mode 100644 index 0000000..a5d7c5b --- /dev/null +++ b/modules/user/models/group.php @@ -0,0 +1,89 @@ +delete("groups_users") + ->where("group_id", "=", empty($id) ? $old->id : $id) + ->execute(); + + module::event("group_deleted", $old); + $this->users_cache = null; + } + + public function users() { + if (!$this->users_cache) { + $this->users_cache = $this->users->find_all()->as_array(); + } + return $this->users_cache; + } + + /** + * Specify our rules here so that we have access to the instance of this model. + */ + public function validate(Validation $array=null) { + // validate() is recursive, only modify the rules on the outermost call. + if (!$array) { + $this->rules = array( + "name" => array("rules" => array("required", "length[1,255]"), + "callbacks" => array(array($this, "valid_name")))); + } + + parent::validate($array); + } + + public function save() { + if (!$this->loaded()) { + // New group + parent::save(); + module::event("group_created", $this); + } else { + // Updated group + $original = ORM::factory("group", $this->id); + parent::save(); + module::event("group_updated", $original, $this); + } + + $this->users_cache = null; + return $this; + } + + /** + * Validate the user name. Make sure there are no conflicts. + */ + public function valid_name(Validation $v, $field) { + if (db::build()->from("groups") + ->where("name", "=", $this->name) + ->where("id", "<>", $this->id) + ->count_records() == 1) { + $v->add_error("name", "conflict"); + } + } +} \ No newline at end of file diff --git a/modules/user/models/user.php b/modules/user/models/user.php new file mode 100644 index 0000000..af05c0c --- /dev/null +++ b/modules/user/models/user.php @@ -0,0 +1,185 @@ +password_length = strlen($value); + $value = user::hash_password($value); + break; + } + parent::__set($column, $value); + } + + /** + * @see ORM::delete() + */ + public function delete($id=null) { + $old = clone $this; + module::event("user_before_delete", $this); + parent::delete($id); + + db::build() + ->delete("groups_users") + ->where("user_id", "=", empty($id) ? $old->id : $id) + ->execute(); + + module::event("user_deleted", $old); + $this->groups_cache = null; + } + + /** + * Return a url to the user's avatar image. + * @param integer $size the target size of the image (default 80px) + * @return string a url + */ + public function avatar_url($size=80, $default=null) { + return sprintf("http://www.gravatar.com/avatar/%s.jpg?s=%d&r=pg%s", + md5($this->email), $size, $default ? "&d=" . urlencode($default) : ""); + } + + public function groups() { + if (!$this->groups_cache) { + $this->groups_cache = $this->groups->find_all()->as_array(); + } + return $this->groups_cache; + } + + /** + * Specify our rules here so that we have access to the instance of this model. + */ + public function validate(Validation $array=null) { + // validate() is recursive, only modify the rules on the outermost call. + if (!$array) { + $this->rules = array( + "admin" => array("callbacks" => array(array($this, "valid_admin"))), + "email" => array("rules" => array("length[1,255]", "valid::email"), + "callbacks" => array(array($this, "valid_email"))), + "full_name" => array("rules" => array("length[0,255]")), + "locale" => array("rules" => array("length[2,10]")), + "name" => array("rules" => array("length[1,32]", "required"), + "callbacks" => array(array($this, "valid_name"))), + "password" => array("callbacks" => array(array($this, "valid_password"))), + "url" => array("rules" => array("valid::url")), + ); + } + + parent::validate($array); + } + + /** + * Handle any business logic necessary to create or update a user. + * @see ORM::save() + * + * @return ORM User_Model + */ + public function save() { + if ($this->full_name === null) { + $this->full_name = ""; + } + + if (!$this->loaded()) { + // New user + $this->add(group::everybody()); + if (!$this->guest) { + $this->add(group::registered_users()); + } + + parent::save(); + module::event("user_created", $this); + } else { + // Updated user + $original = ORM::factory("user", $this->id); + parent::save(); + module::event("user_updated", $original, $this); + } + + $this->groups_cache = null; + return $this; + } + + /** + * Return the best version of the user's name. Either their specified full name, or fall back + * to the user name. + * @return string + */ + public function display_name() { + return empty($this->full_name) ? $this->name : $this->full_name; + } + + /** + * Validate the user name. Make sure there are no conflicts. + */ + public function valid_name(Validation $v, $field) { + if (db::build()->from("users") + ->where("name", "=", $this->name) + ->merge_where($this->id ? array(array("id", "<>", $this->id)) : null) + ->count_records() == 1) { + $v->add_error("name", "conflict"); + } + } + + /** + * Validate the password. + */ + public function valid_password(Validation $v, $field) { + if ($this->guest) { + return; + } + + if (!$this->loaded() || isset($this->password_length)) { + $minimum_length = module::get_var("user", "minimum_password_length", 5); + if ($this->password_length < $minimum_length) { + $v->add_error("password", "min_length"); + } + } + } + + /** + * Validate the admin bit. + */ + public function valid_admin(Validation $v, $field) { + $active = identity::active_user(); + if ($this->id == $active->id && $active->admin && !$this->admin) { + $v->add_error("admin", "locked"); + } + } + + /** + * Validate the email field. + */ + public function valid_email(Validation $v, $field) { + if ($this->guest) { // guests don't require an email address + return; + } + + if (empty($this->email)) { + $v->add_error("email", "required"); + } + } +} diff --git a/modules/user/module.info b/modules/user/module.info new file mode 100644 index 0000000..d5128db --- /dev/null +++ b/modules/user/module.info @@ -0,0 +1,8 @@ +name = "Users and Groups" +description = "Gallery 3 user and group management" +version = 4 + +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:user" +discuss_url = "http://galleryproject.org/forum_module_user" diff --git a/modules/user/views/admin_users.html.php b/modules/user/views/admin_users.html.php new file mode 100644 index 0000000..033c9da --- /dev/null +++ b/modules/user/views/admin_users.html.php @@ -0,0 +1,142 @@ + + + +
      +

      + +
      + +
      + " + class="g-dialog-link g-button g-right ui-icon-left ui-state-default ui-corner-all" + title="for_html_attr() ?>"> + + + + +

      + +
      + + + + + + + + + + + $user): ?> + g-user admin ? "g-admin" : "" ?>"> + + + + + + + + +
      + " + title="for_html_attr() ?>" + alt="name) ?>" + width="20" + height="20" /> + name) ?> + + full_name) ?> + + email) ?> + + last_login == 0) ? "" : gallery::date($user->last_login) ?> + + from("items")->where("owner_id", "=", $user->id)->count_records() ?> + + id") ?>" + open_text="" + class="g-panel-link g-button ui-state-default ui-corner-all ui-icon-left"> + + id != $user->id && !$user->guest): ?> + id") ?>" + class="g-dialog-link g-button ui-state-default ui-corner-all ui-icon-left"> + + + for_html_attr() ?>" + class="g-button ui-state-disabled ui-corner-all ui-icon-left"> + + +
      + +
      + paginator() ?> +
      + +
      +
      + + + +
      +
      diff --git a/modules/user/views/admin_users_delete_user.html.php b/modules/user/views/admin_users_delete_user.html.php new file mode 100644 index 0000000..44777ae --- /dev/null +++ b/modules/user/views/admin_users_delete_user.html.php @@ -0,0 +1,7 @@ + +
      +

      + %name? Any photos, movies or albums owned by this user will transfer ownership to %new_owner.", array("name" => $user->display_name(), "new_owner" => identity::active_user()->display_name())) ?> +

      + +
      diff --git a/modules/user/views/admin_users_group.html.php b/modules/user/views/admin_users_group.html.php new file mode 100644 index 0000000..31b9135 --- /dev/null +++ b/modules/user/views/admin_users_group.html.php @@ -0,0 +1,40 @@ + +

      + id") ?>" + title=" $group->name))->for_html_attr() ?>" + class="g-dialog-link">name) ?> + special): ?> + id") ?>" + title=" $group->name))->for_html_attr() ?>" + class="g-dialog-link g-button g-right"> + + + for_html_attr() ?>" + class="g-button g-right ui-state-disabled ui-icon-left"> + + +

      + +users->count_all() > 0): ?> + + +
      +

      + +

      +
      + diff --git a/modules/user/views/reset_password.html.php b/modules/user/views/reset_password.html.php new file mode 100644 index 0000000..d939ad4 --- /dev/null +++ b/modules/user/views/reset_password.html.php @@ -0,0 +1,18 @@ + + + + <?= t("Password reset request") ?> + + +

      +

      + $user->full_name ? $user->full_name : $user->name)) ?> +

      +

      + %base_url. If you made this request, you can confirm it by clicking this link. If you didn't request this password reset, it's ok to ignore this mail.", + array("site_url" => html::mark_clean(url::abs_site("/")), + "base_url" => html::mark_clean(url::base(false)), + "confirm_url" => $confirm_url)) ?> +

      + + diff --git a/modules/watermark/controllers/admin_watermarks.php b/modules/watermark/controllers/admin_watermarks.php new file mode 100644 index 0000000..222279e --- /dev/null +++ b/modules/watermark/controllers/admin_watermarks.php @@ -0,0 +1,158 @@ +page_title = t("Watermarks"); + $view->content = new View("admin_watermarks.html"); + if ($name) { + $view->content->name = module::get_var("watermark", "name"); + $view->content->url = url::file("var/modules/watermark/$name"); + $view->content->width = module::get_var("watermark", "width"); + $view->content->height = module::get_var("watermark", "height"); + $view->content->position = module::get_var("watermark", "position"); + } + print $view; + } + + public function form_edit() { + print watermark::get_edit_form(); + } + + public function edit() { + access::verify_csrf(); + + $form = watermark::get_edit_form(); + if ($form->validate()) { + module::set_var("watermark", "position", $form->edit_watermark->position->value); + module::set_var("watermark", "transparency", $form->edit_watermark->transparency->value); + $this->_update_graphics_rules(); + + log::success("watermark", t("Watermark changed")); + message::success(t("Watermark changed")); + json::reply( + array("result" => "success", + "location" => url::site("admin/watermarks"))); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function form_delete() { + print watermark::get_delete_form(); + } + + public function delete() { + access::verify_csrf(); + + $form = watermark::get_delete_form(); + if ($form->validate()) { + if ($name = basename(module::get_var("watermark", "name"))) { + system::delete_later(VARPATH . "modules/watermark/$name"); + + module::clear_var("watermark", "name"); + module::clear_var("watermark", "width"); + module::clear_var("watermark", "height"); + module::clear_var("watermark", "mime_type"); + module::clear_var("watermark", "position"); + $this->_update_graphics_rules(); + + log::success("watermark", t("Watermark deleted")); + message::success(t("Watermark deleted")); + } + json::reply(array("result" => "success", "location" => url::site("admin/watermarks"))); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + public function form_add() { + print watermark::get_add_form(); + } + + public function add() { + access::verify_csrf(); + + $form = watermark::get_add_form(); + // For TEST_MODE, we want to simulate a file upload. Because this is not a true upload, Forge's + // validation logic will correctly reject it. So, we skip validation when we're running tests. + if (TEST_MODE || $form->validate()) { + $file = $_POST["file"]; + // Forge prefixes files with "uploadfile-xxxxxxx" for uniqueness + $name = preg_replace("/uploadfile-[^-]+-(.*)/", '$1', basename($file)); + + try { + list ($width, $height, $mime_type, $extension) = photo::get_file_metadata($file); + // Sanitize filename, which ensures a valid extension. This renaming prevents the issues + // addressed in ticket #1855, where an image that looked valid (header said jpg) with a + // php extension was previously accepted without changing its extension. + $name = legal_file::sanitize_filename($name, $extension, "photo"); + } catch (Exception $e) { + message::error(t("Invalid or unidentifiable image file")); + system::delete_later($file); + return; + } + + rename($file, VARPATH . "modules/watermark/$name"); + module::set_var("watermark", "name", $name); + module::set_var("watermark", "width", $width); + module::set_var("watermark", "height", $height); + module::set_var("watermark", "mime_type", $mime_type); + module::set_var("watermark", "position", $form->add_watermark->position->value); + module::set_var("watermark", "transparency", $form->add_watermark->transparency->value); + $this->_update_graphics_rules(); + system::delete_later($file); + + message::success(t("Watermark saved")); + log::success("watermark", t("Watermark saved")); + json::reply(array("result" => "success", "location" => url::site("admin/watermarks"))); + } else { + // rawurlencode the results because the JS code that uploads the file buffers it in an + // iframe which entitizes the HTML and makes it difficult for the JS to process. If we url + // encode it now, it passes through cleanly. See ticket #797. + json::reply(array("result" => "error", "html" => rawurlencode((string)$form))); + } + + // Override the application/json mime type. The dialog based HTML uploader uses an iframe to + // buffer the reply, and on some browsers (Firefox 3.6) it does not know what to do with the + // JSON that it gets back so it puts up a dialog asking the user what to do with it. So force + // the encoding type back to HTML for the iframe. + // See: http://jquery.malsup.com/form/#file-upload + header("Content-Type: text/html; charset=" . Kohana::CHARSET); + } + + private function _update_graphics_rules() { + graphics::remove_rules("watermark"); + if ($name = module::get_var("watermark", "name")) { + foreach (array("thumb", "resize") as $target) { + graphics::add_rule( + "watermark", $target, "gallery_graphics::composite", + array("file" => VARPATH . "modules/watermark/$name", + "width" => module::get_var("watermark", "width"), + "height" => module::get_var("watermark", "height"), + "position" => module::get_var("watermark", "position"), + "transparency" => 101 - module::get_var("watermark", "transparency")), + 1000); + } + } + } +} \ No newline at end of file diff --git a/modules/watermark/helpers/watermark.php b/modules/watermark/helpers/watermark.php new file mode 100644 index 0000000..3357c14 --- /dev/null +++ b/modules/watermark/helpers/watermark.php @@ -0,0 +1,82 @@ + "g-add-watermark-form")); + $group = $form->group("add_watermark")->label(t("Upload watermark")); + $group->upload("file")->label(t("Watermark"))->rules("allow[jpg,png,gif]|size[1MB]|required") + ->error_messages("required", "You must select a watermark") + ->error_messages("invalid_type", "The watermark must be a JPG, GIF or PNG") + ->error_messages("max_size", "The watermark is too big (1 MB max)"); + $group->dropdown("position")->label(t("Watermark position")) + ->options(self::positions()) + ->selected("southeast"); + $group->dropdown("transparency")->label(t("Transparency (100% = completely transparent)")) + ->options($range) + ->selected(1); + $group->submit("")->value(t("Upload")); + return $form; + } + + static function get_edit_form() { + for ($i = 1; $i <= 100; $i++) { + $range[$i] = "$i%"; + } + + $form = new Forge("admin/watermarks/edit", "", "post", array("id" => "g-edit-watermark-form")); + $group = $form->group("edit_watermark")->label(t("Edit Watermark")); + $group->dropdown("position")->label(t("Watermark Position")) + ->options(self::positions()) + ->selected(module::get_var("watermark", "position")); + $group->dropdown("transparency")->label(t("Transparency (100% = completely transparent)")) + ->options($range) + ->selected(module::get_var("watermark", "transparency")); + $group->submit("")->value(t("Save")); + return $form; + } + + static function get_delete_form() { + $form = new Forge("admin/watermarks/delete", "", "post", array("id" => "g-delete-watermark-form")); + $group = $form->group("delete_watermark")->label(t("Really delete Watermark?")); + $group->submit("")->value(t("Delete")); + return $form; + } + + static function positions() { + return array("northwest" => t("Northwest"), + "north" => t("North"), + "northeast" => t("Northeast"), + "west" => t("West"), + "center" => t("Center"), + "east" => t("East"), + "southwest" => t("Southwest"), + "south" => t("South"), + "southeast" => t("Southeast")); + } + + static function position($key) { + $positions = self::positions(); + return $positions[$key]; + } +} \ No newline at end of file diff --git a/modules/watermark/helpers/watermark_event.php b/modules/watermark/helpers/watermark_event.php new file mode 100644 index 0000000..7547515 --- /dev/null +++ b/modules/watermark/helpers/watermark_event.php @@ -0,0 +1,29 @@ +get("content_menu") + ->append( + Menu::factory("link") + ->id("watermarks") + ->label(t("Watermarks")) + ->url(url::site("admin/watermarks"))); + } +} diff --git a/modules/watermark/helpers/watermark_installer.php b/modules/watermark/helpers/watermark_installer.php new file mode 100644 index 0000000..1333891 --- /dev/null +++ b/modules/watermark/helpers/watermark_installer.php @@ -0,0 +1,42 @@ +query("CREATE TABLE IF NOT EXISTS {watermarks} ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(32) NOT NULL, + `width` int(9) NOT NULL, + `height` int(9) NOT NULL, + `active` boolean default 0, + `position` boolean default 0, + `mime_type` varchar(64) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + DEFAULT CHARSET=utf8;"); + + @mkdir(VARPATH . "modules/watermark"); + } + + static function uninstall() { + Database::instance()->query("DROP TABLE {watermarks}"); + dir::unlink(VARPATH . "modules/watermark"); + } +} diff --git a/modules/watermark/module.info b/modules/watermark/module.info new file mode 100644 index 0000000..e5003cd --- /dev/null +++ b/modules/watermark/module.info @@ -0,0 +1,7 @@ +name = "Watermarks" +description = "Allows users to watermark their photos" +version = 2 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:watermark" +discuss_url = "http://galleryproject.org/forum_module_watermark" diff --git a/modules/watermark/views/admin_watermarks.html.php b/modules/watermark/views/admin_watermarks.html.php new file mode 100644 index 0000000..fc634b8 --- /dev/null +++ b/modules/watermark/views/admin_watermarks.html.php @@ -0,0 +1,39 @@ + + diff --git a/php.ini b/php.ini new file mode 100644 index 0000000..d2f96f5 --- /dev/null +++ b/php.ini @@ -0,0 +1,19 @@ +; Set some reasonable defaults for PHP. Most of these cannot be set +; inside the script itself. These settings are mirrored in the +; .htaccess file for hosts that support .htaccess but not per-dir +; php.ini files. +; +[PHP] +short_open_tag = On +magic_quotes_gpc = Off +magic_quotes_sybase = Off +magic_quotes_runtime = Off +register_globals = Off +upload_max_filesize = 20M +post_max_size = 100M + +[Session] +session.auto_start = Off + +[suhosin] +suhosin.session.encrypt = Off diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..000aac9 --- /dev/null +++ b/robots.txt @@ -0,0 +1,14 @@ +User-agent: * +Disallow: /application/ +Disallow: /installer/ +Disallow: /lib/ +Disallow: /modules/ +Disallow: /system/ +Disallow: /themes/ +Disallow: /var/logs/ +Disallow: /var/modules/ +Disallow: /var/resizes/ +Disallow: /var/thumbs/ +Disallow: /var/tmp/ +Disallow: /var/uploads/ +Disallow: /digibug/print_photo/ diff --git a/serveradd/Raphael_2013-10-18_optimiert.mpg b/serveradd/Raphael_2013-10-18_optimiert.mpg new file mode 100644 index 0000000..5c1cff3 Binary files /dev/null and b/serveradd/Raphael_2013-10-18_optimiert.mpg differ diff --git a/system/KohanaLicense.html b/system/KohanaLicense.html new file mode 100644 index 0000000..bc4bce2 --- /dev/null +++ b/system/KohanaLicense.html @@ -0,0 +1,30 @@ + + + + + +Kohana License + + + + +

      Kohana License Agreement

      + +

      This license is a legal agreement between you and the Kohana Software Foundation for the use of Kohana Framework (the "Software"). By obtaining the Software you agree to comply with the terms and conditions of this license.

      + +

      Copyright (c) 2007-2009 Kohana Team
      All rights reserved.

      + +

      Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

      + +
        +
      • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
      • +
      • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
      • +
      • Neither the name of the Kohana nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
      • +
      + +

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

      + +

      NOTE: This license is modeled after the BSD software license.

      + + + \ No newline at end of file diff --git a/system/config/cache.php b/system/config/cache.php new file mode 100644 index 0000000..6b2f787 --- /dev/null +++ b/system/config/cache.php @@ -0,0 +1,37 @@ + 'file', + 'params' => array('directory' => APPPATH.'cache', 'gc_probability' => 1000), + 'lifetime' => 1800, + 'prefix' => NULL +); diff --git a/system/config/cookie.php b/system/config/cookie.php new file mode 100644 index 0000000..5340f05 --- /dev/null +++ b/system/config/cookie.php @@ -0,0 +1,55 @@ + array + ( + 'length' => '13,14,15,16,17,18,19', + 'prefix' => '', + 'luhn' => TRUE + ), + 'american express' => array + ( + 'length' => '15', + 'prefix' => '3[47]', + 'luhn' => TRUE + ), + 'diners club' => array + ( + 'length' => '14,16', + 'prefix' => '36|55|30[0-5]', + 'luhn' => TRUE + ), + 'discover' => array + ( + 'length' => '16', + 'prefix' => '6(?:5|011)', + 'luhn' => TRUE, + ), + 'jcb' => array + ( + 'length' => '15,16', + 'prefix' => '3|1800|2131', + 'luhn' => TRUE + ), + 'maestro' => array + ( + 'length' => '16,18', + 'prefix' => '50(?:20|38)|6(?:304|759)', + 'luhn' => TRUE + ), + 'mastercard' => array + ( + 'length' => '16', + 'prefix' => '5[1-5]', + 'luhn' => TRUE + ), + 'visa' => array + ( + 'length' => '13,16', + 'prefix' => '4', + 'luhn' => TRUE + ), +); \ No newline at end of file diff --git a/system/config/database.php b/system/config/database.php new file mode 100644 index 0000000..dd6bf46 --- /dev/null +++ b/system/config/database.php @@ -0,0 +1,53 @@ + 'mysql://dbuser:secret@localhost/kohana' + * - character_set - Database character set + * - table_prefix - Database table prefix + * - object - Enable or disable object results + * - cache - Enable or disable query caching + * - escape - Enable automatic query builder escaping + */ +$config['default'] = array +( + 'benchmark' => FALSE, + 'persistent' => FALSE, + 'connection' => array + ( + 'type' => 'mysql', + 'user' => 'dbuser', + 'pass' => 'p@ssw0rd', + 'host' => 'localhost', + 'port' => FALSE, + 'socket' => FALSE, + 'database' => 'kohana', + 'params' => NULL, + ), + 'character_set' => 'utf8', + 'table_prefix' => '', + 'object' => TRUE, + 'cache' => FALSE, + 'escape' => TRUE +); \ No newline at end of file diff --git a/system/config/encryption.php b/system/config/encryption.php new file mode 100644 index 0000000..12ff7ae --- /dev/null +++ b/system/config/encryption.php @@ -0,0 +1,39 @@ + 'K0H@NA+PHP_7hE-SW!FtFraM3w0R|<', + 'mode' => MCRYPT_MODE_NOFB, + 'cipher' => MCRYPT_RIJNDAEL_128 +); diff --git a/system/config/http.php b/system/config/http.php new file mode 100644 index 0000000..7cc11dd --- /dev/null +++ b/system/config/http.php @@ -0,0 +1,30 @@ + 'children', + 'clothes' => 'clothing', + 'man' => 'men', + 'movie' => 'movies', + 'person' => 'people', + 'woman' => 'women', + 'mouse' => 'mice', + 'goose' => 'geese', + 'ox' => 'oxen', + 'leaf' => 'leaves', + 'course' => 'courses', + 'size' => 'sizes', +); diff --git a/system/config/locale.php b/system/config/locale.php new file mode 100644 index 0000000..3f73b8f --- /dev/null +++ b/system/config/locale.php @@ -0,0 +1,26 @@ + 1, + 'alert' => 2, + 'info' => 3, + 'debug' => 4, +); + +/** + * See different log levels above + */ +$config['log_threshold'] = 1; + +/** + * Log Date format + */ +$config['date_format'] = 'Y-m-d H:i:s P'; + +/** + * We can define multiple logging backends at the same time. + */ +$config['drivers'] = array('file'); \ No newline at end of file diff --git a/system/config/mimes.php b/system/config/mimes.php new file mode 100644 index 0000000..f8f8aaf --- /dev/null +++ b/system/config/mimes.php @@ -0,0 +1,228 @@ + array('text/h323'), + '7z' => array('application/x-7z-compressed'), + 'abw' => array('application/x-abiword'), + 'acx' => array('application/internet-property-stream'), + 'ai' => array('application/postscript'), + 'aif' => array('audio/x-aiff'), + 'aifc' => array('audio/x-aiff'), + 'aiff' => array('audio/x-aiff'), + 'asf' => array('video/x-ms-asf'), + 'asr' => array('video/x-ms-asf'), + 'asx' => array('video/x-ms-asf'), + 'atom' => array('application/atom+xml'), + 'avi' => array('video/avi', 'video/msvideo', 'video/x-msvideo'), + 'bin' => array('application/octet-stream','application/macbinary'), + 'bmp' => array('image/bmp'), + 'c' => array('text/x-csrc'), + 'c++' => array('text/x-c++src'), + 'cab' => array('application/x-cab'), + 'cc' => array('text/x-c++src'), + 'cda' => array('application/x-cdf'), + 'class' => array('application/octet-stream'), + 'cpp' => array('text/x-c++src'), + 'cpt' => array('application/mac-compactpro'), + 'csh' => array('text/x-csh'), + 'css' => array('text/css'), + 'csv' => array('text/x-comma-separated-values', 'application/vnd.ms-excel', 'text/comma-separated-values', 'text/csv'), + 'dbk' => array('application/docbook+xml'), + 'dcr' => array('application/x-director'), + 'deb' => array('application/x-debian-package'), + 'diff' => array('text/x-diff'), + 'dir' => array('application/x-director'), + 'divx' => array('video/divx'), + 'dll' => array('application/octet-stream', 'application/x-msdos-program'), + 'dmg' => array('application/x-apple-diskimage'), + 'dms' => array('application/octet-stream'), + 'doc' => array('application/msword'), + 'dvi' => array('application/x-dvi'), + 'dxr' => array('application/x-director'), + 'eml' => array('message/rfc822'), + 'eps' => array('application/postscript'), + 'evy' => array('application/envoy'), + 'exe' => array('application/x-msdos-program', 'application/octet-stream'), + 'fla' => array('application/octet-stream'), + 'flac' => array('application/x-flac'), + 'flc' => array('video/flc'), + 'fli' => array('video/fli'), + 'flv' => array('video/x-flv'), + 'gif' => array('image/gif'), + 'gtar' => array('application/x-gtar'), + 'gz' => array('application/x-gzip'), + 'h' => array('text/x-chdr'), + 'h++' => array('text/x-c++hdr'), + 'hh' => array('text/x-c++hdr'), + 'hpp' => array('text/x-c++hdr'), + 'hqx' => array('application/mac-binhex40'), + 'hs' => array('text/x-haskell'), + 'htm' => array('text/html'), + 'html' => array('text/html'), + 'ico' => array('image/x-icon'), + 'ics' => array('text/calendar'), + 'iii' => array('application/x-iphone'), + 'ins' => array('application/x-internet-signup'), + 'iso' => array('application/x-iso9660-image'), + 'isp' => array('application/x-internet-signup'), + 'jar' => array('application/java-archive'), + 'java' => array('application/x-java-applet'), + 'jpe' => array('image/jpeg', 'image/pjpeg'), + 'jpeg' => array('image/jpeg', 'image/pjpeg'), + 'jpg' => array('image/jpeg', 'image/pjpeg'), + 'js' => array('application/x-javascript'), + 'json' => array('application/json'), + 'latex' => array('application/x-latex'), + 'lha' => array('application/octet-stream'), + 'log' => array('text/plain', 'text/x-log'), + 'lzh' => array('application/octet-stream'), + 'm4a' => array('audio/mpeg'), + 'm4p' => array('video/mp4v-es'), + 'm4v' => array('video/mp4'), + 'man' => array('application/x-troff-man'), + 'mdb' => array('application/x-msaccess'), + 'midi' => array('audio/midi'), + 'mid' => array('audio/midi'), + 'mif' => array('application/vnd.mif'), + 'mka' => array('audio/x-matroska'), + 'mkv' => array('video/x-matroska'), + 'mov' => array('video/quicktime'), + 'movie' => array('video/x-sgi-movie'), + 'mp2' => array('audio/mpeg'), + 'mp3' => array('audio/mpeg'), + 'mp4' => array('application/mp4','audio/mp4','video/mp4'), + 'mpa' => array('video/mpeg'), + 'mpe' => array('video/mpeg'), + 'mpeg' => array('video/mpeg'), + 'mpg' => array('video/mpeg'), + 'mpg4' => array('video/mp4'), + 'mpga' => array('audio/mpeg'), + 'mpp' => array('application/vnd.ms-project'), + 'mpv' => array('video/x-matroska'), + 'mpv2' => array('video/mpeg'), + 'ms' => array('application/x-troff-ms'), + 'msg' => array('application/msoutlook','application/x-msg'), + 'msi' => array('application/x-msi'), + 'nws' => array('message/rfc822'), + 'oda' => array('application/oda'), + 'odb' => array('application/vnd.oasis.opendocument.database'), + 'odc' => array('application/vnd.oasis.opendocument.chart'), + 'odf' => array('application/vnd.oasis.opendocument.forumla'), + 'odg' => array('application/vnd.oasis.opendocument.graphics'), + 'odi' => array('application/vnd.oasis.opendocument.image'), + 'odm' => array('application/vnd.oasis.opendocument.text-master'), + 'odp' => array('application/vnd.oasis.opendocument.presentation'), + 'ods' => array('application/vnd.oasis.opendocument.spreadsheet'), + 'odt' => array('application/vnd.oasis.opendocument.text'), + 'oga' => array('audio/ogg'), + 'ogg' => array('application/ogg'), + 'ogv' => array('video/ogg'), + 'otg' => array('application/vnd.oasis.opendocument.graphics-template'), + 'oth' => array('application/vnd.oasis.opendocument.web'), + 'otp' => array('application/vnd.oasis.opendocument.presentation-template'), + 'ots' => array('application/vnd.oasis.opendocument.spreadsheet-template'), + 'ott' => array('application/vnd.oasis.opendocument.template'), + 'p' => array('text/x-pascal'), + 'pas' => array('text/x-pascal'), + 'patch' => array('text/x-diff'), + 'pbm' => array('image/x-portable-bitmap'), + 'pdf' => array('application/pdf', 'application/x-download'), + 'php' => array('application/x-httpd-php'), + 'php3' => array('application/x-httpd-php'), + 'php4' => array('application/x-httpd-php'), + 'php5' => array('application/x-httpd-php'), + 'phps' => array('application/x-httpd-php-source'), + 'phtml' => array('application/x-httpd-php'), + 'pl' => array('text/x-perl'), + 'pm' => array('text/x-perl'), + 'png' => array('image/png', 'image/x-png'), + 'po' => array('text/x-gettext-translation'), + 'pot' => array('application/vnd.ms-powerpoint'), + 'pps' => array('application/vnd.ms-powerpoint'), + 'ppt' => array('application/powerpoint'), + 'ps' => array('application/postscript'), + 'psd' => array('application/x-photoshop', 'image/x-photoshop'), + 'pub' => array('application/x-mspublisher'), + 'py' => array('text/x-python'), + 'qt' => array('video/quicktime'), + 'ra' => array('audio/x-realaudio'), + 'ram' => array('audio/x-realaudio', 'audio/x-pn-realaudio'), + 'rar' => array('application/rar'), + 'rgb' => array('image/x-rgb'), + 'rm' => array('audio/x-pn-realaudio'), + 'rpm' => array('audio/x-pn-realaudio-plugin', 'application/x-redhat-package-manager'), + 'rss' => array('application/rss+xml'), + 'rtf' => array('text/rtf'), + 'rtx' => array('text/richtext'), + 'rv' => array('video/vnd.rn-realvideo'), + 'sea' => array('application/octet-stream'), + 'sh' => array('text/x-sh'), + 'shtml' => array('text/html'), + 'sit' => array('application/x-stuffit'), + 'smi' => array('application/smil'), + 'smil' => array('application/smil'), + 'so' => array('application/octet-stream'), + 'src' => array('application/x-wais-source'), + 'svg' => array('image/svg+xml'), + 'swf' => array('application/x-shockwave-flash'), + 't' => array('application/x-troff'), + 'tar' => array('application/x-tar'), + 'tcl' => array('text/x-tcl'), + 'tex' => array('application/x-tex'), + 'text' => array('text/plain'), + 'texti' => array('application/x-texinfo'), + 'textinfo' => array('application/x-texinfo'), + 'tgz' => array('application/x-tar'), + 'tif' => array('image/tiff'), + 'tiff' => array('image/tiff'), + 'torrent' => array('application/x-bittorrent'), + 'tr' => array('application/x-troff'), + 'tsv' => array('text/tab-separated-values'), + 'txt' => array('text/plain'), + 'wav' => array('audio/x-wav'), + 'wax' => array('audio/x-ms-wax'), + 'wbxml' => array('application/wbxml'), + 'wm' => array('video/x-ms-wm'), + 'wma' => array('audio/x-ms-wma'), + 'wmd' => array('application/x-ms-wmd'), + 'wmlc' => array('application/wmlc'), + 'wmv' => array('video/x-ms-wmv', 'application/octet-stream'), + 'wmx' => array('video/x-ms-wmx'), + 'wmz' => array('application/x-ms-wmz'), + 'word' => array('application/msword', 'application/octet-stream'), + 'wp5' => array('application/wordperfect5.1'), + 'wpd' => array('application/vnd.wordperfect'), + 'wvx' => array('video/x-ms-wvx'), + 'xbm' => array('image/x-xbitmap'), + 'xcf' => array('image/xcf'), + 'xhtml' => array('application/xhtml+xml'), + 'xht' => array('application/xhtml+xml'), + 'xl' => array('application/excel', 'application/vnd.ms-excel'), + 'xla' => array('application/excel', 'application/vnd.ms-excel'), + 'xlc' => array('application/excel', 'application/vnd.ms-excel'), + 'xlm' => array('application/excel', 'application/vnd.ms-excel'), + 'xls' => array('application/excel', 'application/vnd.ms-excel'), + 'xlt' => array('application/excel', 'application/vnd.ms-excel'), + 'xml' => array('text/xml'), + 'xof' => array('x-world/x-vrml'), + 'xpm' => array('image/x-xpixmap'), + 'xsl' => array('text/xml'), + 'xvid' => array('video/x-xvid'), + 'xwd' => array('image/x-xwindowdump'), + 'z' => array('application/x-compress'), + 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed') +); diff --git a/system/config/profiler.php b/system/config/profiler.php new file mode 100644 index 0000000..d30a1b4 --- /dev/null +++ b/system/config/profiler.php @@ -0,0 +1,16 @@ + array('type' => 'string', 'exact' => TRUE), + 'bit varying' => array('type' => 'string'), + 'character' => array('type' => 'string', 'exact' => TRUE), + 'character varying' => array('type' => 'string'), + 'date' => array('type' => 'string'), + 'decimal' => array('type' => 'float', 'exact' => TRUE), + 'double precision' => array('type' => 'float'), + 'float' => array('type' => 'float'), + 'integer' => array('type' => 'int', 'min' => -2147483648, 'max' => 2147483647), + 'interval' => array('type' => 'string'), + 'national character' => array('type' => 'string', 'exact' => TRUE), + 'national character varying' => array('type' => 'string'), + 'numeric' => array('type' => 'float', 'exact' => TRUE), + 'real' => array('type' => 'float'), + 'smallint' => array('type' => 'int', 'min' => -32768, 'max' => 32767), + 'time' => array('type' => 'string'), + 'time with time zone' => array('type' => 'string'), + 'timestamp' => array('type' => 'string'), + 'timestamp with time zone' => array('type' => 'string'), + + // SQL:1999 + //'array','ref','row' + 'binary large object' => array('type' => 'string', 'binary' => TRUE), + 'boolean' => array('type' => 'boolean'), + 'character large object' => array('type' => 'string'), + 'national character large object' => array('type' => 'string'), + + // SQL:2003 + 'bigint' => array('type' => 'int', 'min' => -9223372036854775808, 'max' => 9223372036854775807), + + // SQL:2008 + 'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE), + 'binary varying' => array('type' => 'string', 'binary' => TRUE), + + // MySQL + 'bigint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 18446744073709551615), + 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => 0.0), + 'double unsigned' => array('type' => 'float', 'min' => 0.0), + 'float unsigned' => array('type' => 'float', 'min' => 0.0), + 'integer unsigned' => array('type' => 'int', 'min' => 0, 'max' => 4294967295), + 'mediumint' => array('type' => 'int', 'min' => -8388608, 'max' => 8388607), + 'mediumint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 16777215), + 'real unsigned' => array('type' => 'float', 'min' => 0.0), + 'smallint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 65535), + 'text' => array('type' => 'string'), + 'tinyint' => array('type' => 'int', 'min' => -128, 'max' => 127), + 'tinyint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 255), + 'year' => array('type' => 'string'), +); + +// SQL-92 +$config['char'] = $config['character']; +$config['char varying'] = $config['character varying']; +$config['dec'] = $config['decimal']; +$config['int'] = $config['integer']; +$config['nchar'] = $config['national char'] = $config['national character']; +$config['nchar varying'] = $config['national char varying'] = $config['national character varying']; +$config['varchar'] = $config['character varying']; + +// SQL:1999 +$config['blob'] = $config['binary large object']; +$config['clob'] = $config['char large object'] = $config['character large object']; +$config['nclob'] = $config['nchar large object'] = $config['national character large object']; +$config['time without time zone'] = $config['time']; +$config['timestamp without time zone'] = $config['timestamp']; + +// SQL:2008 +$config['varbinary'] = $config['binary varying']; + +// MySQL +$config['bool'] = $config['boolean']; +$config['datetime'] = $config['timestamp']; +$config['double'] = $config['double precision']; +$config['double precision unsigned'] = $config['double unsigned']; +$config['enum'] = $config['set'] = $config['character varying']; +$config['fixed'] = $config['decimal']; +$config['fixed unsigned'] = $config['decimal unsigned']; +$config['int unsigned'] = $config['integer unsigned']; +$config['longblob'] = $config['mediumblob'] = $config['tinyblob'] = $config['binary large object']; +$config['longtext'] = $config['mediumtext'] = $config['tinytext'] = $config['text']; +$config['numeric unsigned'] = $config['decimal unsigned']; +$config['nvarchar'] = $config['national varchar'] = $config['national character varying']; diff --git a/system/config/upload.php b/system/config/upload.php new file mode 100644 index 0000000..833082c --- /dev/null +++ b/system/config/upload.php @@ -0,0 +1,24 @@ + 'Windows Vista', + 'windows nt 5.2' => 'Windows 2003', + 'windows nt 5.0' => 'Windows 2000', + 'windows nt 5.1' => 'Windows XP', + 'windows nt 4.0' => 'Windows NT', + 'winnt4.0' => 'Windows NT', + 'winnt 4.0' => 'Windows NT', + 'winnt' => 'Windows NT', + 'windows 98' => 'Windows 98', + 'win98' => 'Windows 98', + 'windows 95' => 'Windows 95', + 'win95' => 'Windows 95', + 'windows' => 'Unknown Windows OS', + 'os x' => 'Mac OS X', + 'intel mac' => 'Intel Mac', + 'ppc mac' => 'PowerPC Mac', + 'powerpc' => 'PowerPC', + 'ppc' => 'PowerPC', + 'cygwin' => 'Cygwin', + 'linux' => 'Linux', + 'debian' => 'Debian', + 'openvms' => 'OpenVMS', + 'sunos' => 'Sun Solaris', + 'amiga' => 'Amiga', + 'beos' => 'BeOS', + 'apachebench' => 'ApacheBench', + 'freebsd' => 'FreeBSD', + 'netbsd' => 'NetBSD', + 'bsdi' => 'BSDi', + 'openbsd' => 'OpenBSD', + 'os/2' => 'OS/2', + 'warp' => 'OS/2', + 'aix' => 'AIX', + 'irix' => 'Irix', + 'osf' => 'DEC OSF', + 'hp-ux' => 'HP-UX', + 'hurd' => 'GNU/Hurd', + 'unix' => 'Unknown Unix OS', +); + +/** + * The order of this array should NOT be changed. Many browsers return + * multiple browser types so we want to identify the sub-type first. + */ +$config['browser'] = array +( + 'Opera' => 'Opera', + 'MSIE' => 'Internet Explorer', + 'Internet Explorer' => 'Internet Explorer', + 'Shiira' => 'Shiira', + 'Firefox' => 'Firefox', + 'Chimera' => 'Chimera', + 'Phoenix' => 'Phoenix', + 'Firebird' => 'Firebird', + 'Camino' => 'Camino', + 'Netscape' => 'Netscape', + 'OmniWeb' => 'OmniWeb', + 'Chrome' => 'Chrome', + 'Safari' => 'Safari', + 'Konqueror' => 'Konqueror', + 'Epiphany' => 'Epiphany', + 'Galeon' => 'Galeon', + 'Mozilla' => 'Mozilla', + 'icab' => 'iCab', + 'lynx' => 'Lynx', + 'links' => 'Links', + 'hotjava' => 'HotJava', + 'amaya' => 'Amaya', + 'IBrowse' => 'IBrowse', +); + +$config['mobile'] = array +( + 'mobileexplorer' => 'Mobile Explorer', + 'openwave' => 'Open Wave', + 'opera mini' => 'Opera Mini', + 'operamini' => 'Opera Mini', + 'elaine' => 'Palm', + 'palmsource' => 'Palm', + 'digital paths' => 'Palm', + 'avantgo' => 'Avantgo', + 'xiino' => 'Xiino', + 'palmscape' => 'Palmscape', + 'nokia' => 'Nokia', + 'ericsson' => 'Ericsson', + 'blackBerry' => 'BlackBerry', + 'motorola' => 'Motorola', + 'iphone' => 'iPhone', + 'android' => 'Android', +); + +/** + * There are hundreds of bots but these are the most common. + */ +$config['robot'] = array +( + 'googlebot' => 'Googlebot', + 'msnbot' => 'MSNBot', + 'slurp' => 'Inktomi Slurp', + 'yahoo' => 'Yahoo', + 'askjeeves' => 'AskJeeves', + 'fastcrawler' => 'FastCrawler', + 'infoseek' => 'InfoSeek Robot 1.0', + 'lycos' => 'Lycos', +); \ No newline at end of file diff --git a/system/config/view.php b/system/config/view.php new file mode 100644 index 0000000..b6a25b1 --- /dev/null +++ b/system/config/view.php @@ -0,0 +1,25 @@ +template = new View($this->template); + + if ($this->auto_render == TRUE) + { + // Render the template immediately after the controller method + Event::add('system.post_controller', array($this, '_render')); + } + } + + /** + * Render the loaded template. + */ + public function _render() + { + if ($this->auto_render == TRUE) + { + // Render the template when the class is destroyed + $this->template->render(TRUE); + } + } + +} // End Template_Controller \ No newline at end of file diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php new file mode 100644 index 0000000..79fadb6 --- /dev/null +++ b/system/core/Benchmark.php @@ -0,0 +1,126 @@ + $name)); + + if ( ! isset(self::$marks[$name])) + { + self::$marks[$name] = array(); + } + + $mark = array + ( + 'start' => microtime(TRUE), + 'stop' => FALSE, + 'memory_start' => self::memory_usage(), + 'memory_stop' => FALSE + ); + + array_unshift(self::$marks[$name], $mark); + } + + /** + * Set a benchmark stop point. + * + * @param string benchmark name + * @return void + */ + public static function stop($name) + { + if (isset(self::$marks[$name]) AND self::$marks[$name][0]['stop'] === FALSE) + { + self::$marks[$name][0]['stop'] = microtime(TRUE); + self::$marks[$name][0]['memory_stop'] = self::memory_usage(); + } + } + + /** + * Get the elapsed time between a start and stop. + * + * @param string benchmark name, TRUE for all + * @param integer number of decimal places to count to + * @return array + */ + public static function get($name, $decimals = 4) + { + if ($name === TRUE) + { + $times = array(); + $names = array_keys(self::$marks); + + foreach ($names as $name) + { + // Get each mark recursively + $times[$name] = self::get($name, $decimals); + } + + // Return the array + return $times; + } + + if ( ! isset(self::$marks[$name])) + return FALSE; + + if (self::$marks[$name][0]['stop'] === FALSE) + { + // Stop the benchmark to prevent mis-matched results + self::stop($name); + } + + // Return a string version of the time between the start and stop points + // Properly reading a float requires using number_format or sprintf + $time = $memory = 0; + for ($i = 0; $i < count(self::$marks[$name]); $i++) + { + $time += self::$marks[$name][$i]['stop'] - self::$marks[$name][$i]['start']; + $memory += self::$marks[$name][$i]['memory_stop'] - self::$marks[$name][$i]['memory_start']; + } + + return array + ( + 'time' => number_format($time, $decimals), + 'memory' => $memory, + 'count' => count(self::$marks[$name]) + ); + } + + /** + * Returns the current memory usage. This is only possible if the + * memory_get_usage function is supported in PHP. + * + * @return integer + */ + private static function memory_usage() + { + static $func; + + if ($func === NULL) + { + // Test if memory usage can be seen + $func = function_exists('memory_get_usage'); + } + + return $func ? memory_get_usage() : 0; + } + +} // End Benchmark diff --git a/system/core/Event.php b/system/core/Event.php new file mode 100644 index 0000000..ab839f4 --- /dev/null +++ b/system/core/Event.php @@ -0,0 +1,231 @@ + $event_callback) + { + if ($callback === $event_callback) + { + unset(Event::$events[$name][$i]); + } + } + } + } + + /** + * Execute all of the callbacks attached to an event. + * + * @param string event name + * @param array data can be processed as Event::$data by the callbacks + * @return void + */ + public static function run($name, & $data = NULL) + { + if ( ! empty(Event::$events[$name])) + { + // So callbacks can access Event::$data + Event::$data =& $data; + $callbacks = Event::get($name); + + foreach ($callbacks as $callback) + { + call_user_func_array($callback, array(&$data)); + } + + // Do this to prevent data from getting 'stuck' + $clear_data = ''; + Event::$data =& $clear_data; + } + + // The event has been run! + Event::$has_run[$name] = $name; + } + + /** + * Check if a given event has been run. + * + * @param string event name + * @return boolean + */ + public static function has_run($name) + { + return isset(Event::$has_run[$name]); + } + +} // End Event \ No newline at end of file diff --git a/system/core/Kohana.php b/system/core/Kohana.php new file mode 100644 index 0000000..96d969e --- /dev/null +++ b/system/core/Kohana.php @@ -0,0 +1,1118 @@ +loaded() === FALSE) + { + // Re-parse the include paths + Kohana::include_paths(TRUE); + } + + if (Kohana::$cache_lifetime = Kohana::config('core.internal_cache')) + { + // Are we using encryption for caches? + Kohana::$internal_cache_encrypt = Kohana::config('core.internal_cache_encrypt'); + + if(Kohana::$internal_cache_encrypt===TRUE) + { + Kohana::$internal_cache_key = Kohana::config('core.internal_cache_key'); + + // Be sure the key is of acceptable length for the mcrypt algorithm used + Kohana::$internal_cache_key = substr(Kohana::$internal_cache_key, 0, 24); + } + + // Set the directory to be used for the internal cache + if ( ! Kohana::$internal_cache_path = Kohana::config('core.internal_cache_path')) + { + Kohana::$internal_cache_path = APPPATH.'cache/'; + } + + // Load cached configuration and language files + Kohana::$internal_cache['configuration'] = Kohana::cache('configuration', Kohana::$cache_lifetime); + Kohana::$internal_cache['language'] = Kohana::cache('language', Kohana::$cache_lifetime); + + // Load cached file paths + Kohana::$internal_cache['find_file_paths'] = Kohana::cache('find_file_paths', Kohana::$cache_lifetime); + + // Enable cache saving + Event::add('system.shutdown', array('Kohana', 'internal_cache_save')); + } + + // Start output buffering + ob_start(array('Kohana', 'output_buffer')); + + // Save buffering level + Kohana::$buffer_level = ob_get_level(); + + // Set autoloader + spl_autoload_register(array('Kohana', 'auto_load')); + + // Register a shutdown function to handle system.shutdown events + register_shutdown_function(array('Kohana', 'shutdown')); + + // Send default text/html UTF-8 header + header('Content-Type: text/html; charset='.Kohana::CHARSET); + + // Load i18n + new I18n; + + // Enable exception handling + Kohana_Exception::enable(); + + // Enable error handling + Kohana_PHP_Exception::enable(); + + // Load locales + $locales = Kohana::config('locale.language'); + + // Make first locale the defined Kohana charset + $locales[0] .= '.'.Kohana::CHARSET; + + // Set locale information + Kohana::$locale = setlocale(LC_ALL, $locales); + + // Default to the default locale when none of the user defined ones where accepted + Kohana::$locale = ! Kohana::$locale ? Kohana::LOCALE.'.'.Kohana::CHARSET : Kohana::$locale; + + // Set locale for the I18n system + I18n::set_locale(Kohana::$locale); + + // Set and validate the timezone + date_default_timezone_set(Kohana::config('locale.timezone')); + + // register_globals is enabled + if (ini_get('register_globals')) + { + if (isset($_REQUEST['GLOBALS'])) + { + // Prevent GLOBALS override attacks + exit('Global variable overload attack.'); + } + + // Destroy the REQUEST global + $_REQUEST = array(); + + // These globals are standard and should not be removed + $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION'); + + // This loop has the same effect as disabling register_globals + foreach (array_diff(array_keys($GLOBALS), $preserve) as $key) + { + global $$key; + $$key = NULL; + + // Unset the global variable + unset($GLOBALS[$key], $$key); + } + + // Warn the developer about register globals + Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals'); + } + + // Enable Kohana routing + Event::add('system.routing', array('Router', 'find_uri')); + Event::add('system.routing', array('Router', 'setup')); + + // Enable Kohana controller initialization + Event::add('system.execute', array('Kohana', 'instance')); + + // Enable Kohana 404 pages + Event::add('system.404', array('Kohana_404_Exception', 'trigger')); + + if (Kohana::config('core.enable_hooks') === TRUE) + { + // Find all the hook files + $hooks = Kohana::list_files('hooks', TRUE); + + foreach ($hooks as $file) + { + // Load the hook + include $file; + } + } + + // Stop the environment setup routine + Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup'); + } + + /** + * Cleans up the PHP environment. Disables error/exception handling and the + * auto-loading method and closes the output buffer. + * + * This method does not need to be called during normal system execution, + * however in some advanced situations it can be helpful. @see #1781 + * + * @return void + */ + public static function cleanup() + { + static $run; + + // Only run this function once + if ($run === TRUE) + return; + + $run = TRUE; + + Kohana_Exception::disable(); + + Kohana_PHP_Exception::disable(); + + spl_autoload_unregister(array('Kohana', 'auto_load')); + + Kohana::close_buffers(); + } + + /** + * Loads the controller and initializes it. Runs the pre_controller, + * post_controller_constructor, and post_controller events. Triggers + * a system.404 event when the route cannot be mapped to a controller. + * + * This method is benchmarked as controller_setup and controller_execution. + * + * @return object instance of controller + */ + public static function & instance() + { + if (Kohana::$instance === NULL) + { + Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup'); + + // Include the Controller file + require_once Router::$controller_path; + + try + { + // Start validation of the controller + $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller'); + } + catch (ReflectionException $e) + { + // Controller does not exist + Event::run('system.404'); + } + + if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE)) + { + // Controller is not allowed to run in production + Event::run('system.404'); + } + + // Run system.pre_controller + Event::run('system.pre_controller'); + + // Create a new controller instance + $controller = $class->newInstance(); + + // Controller constructor has been executed + Event::run('system.post_controller_constructor'); + + try + { + // Load the controller method + $method = $class->getMethod(Router::$method); + + // Method exists + if (Router::$method[0] === '_') + { + // Do not allow access to hidden methods + Event::run('system.404'); + } + + if ($method->isProtected() or $method->isPrivate()) + { + // Do not attempt to invoke protected methods + throw new ReflectionException('protected controller method'); + } + + // Default arguments + $arguments = Router::$arguments; + } + catch (ReflectionException $e) + { + // Use __call instead + $method = $class->getMethod('__call'); + + // Use arguments in __call format + $arguments = array(Router::$method, Router::$arguments); + } + + // Stop the controller setup benchmark + Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup'); + + // Start the controller execution benchmark + Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution'); + + // Execute the controller method + $method->invokeArgs($controller, $arguments); + + // Controller method has been executed + Event::run('system.post_controller'); + + // Stop the controller execution benchmark + Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution'); + } + + return Kohana::$instance; + } + + /** + * Get all include paths. APPPATH is the first path, followed by module + * paths in the order they are configured, follow by the SYSPATH. + * + * @param boolean re-process the include paths + * @return array + */ + public static function include_paths($process = FALSE) + { + if ($process === TRUE) + { + // Add APPPATH as the first path + Kohana::$include_paths = array(APPPATH); + + foreach (Kohana::config('core.modules') as $path) + { + if ($path = str_replace('\\', '/', realpath($path))) + { + // Add a valid path + Kohana::$include_paths[] = $path.'/'; + } + } + + // Add SYSPATH as the last path + Kohana::$include_paths[] = SYSPATH; + + Kohana::$include_paths_hash = md5(serialize(Kohana::$include_paths)); + } + + return Kohana::$include_paths; + } + + /** + * Get a config item or group proxies Kohana_Config. + * + * @param string item name + * @param boolean force a forward slash (/) at the end of the item + * @param boolean is the item required? + * @return mixed + */ + public static function config($key, $slash = FALSE, $required = FALSE) + { + return Kohana_Config::instance()->get($key,$slash,$required); + } + + /** + * Load data from a simple cache file. This should only be used internally, + * and is NOT a replacement for the Cache library. + * + * @param string unique name of cache + * @param integer expiration in seconds + * @return mixed + */ + public static function cache($name, $lifetime) + { + if ($lifetime > 0) + { + $path = Kohana::$internal_cache_path.'kohana_'.$name; + + if (is_file($path)) + { + // Check the file modification time + if ((time() - filemtime($path)) < $lifetime) + { + // Cache is valid! Now, do we need to decrypt it? + if(Kohana::$internal_cache_encrypt===TRUE) + { + $data = file_get_contents($path); + + $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); + $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); + + $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv); + + $cache = unserialize($decrypted_text); + + // If the key changed, delete the cache file + if(!$cache) + unlink($path); + + // If cache is false (as above) return NULL, otherwise, return the cache + return ($cache ? $cache : NULL); + } + else + { + return unserialize(file_get_contents($path)); + } + } + else + { + // Cache is invalid, delete it + unlink($path); + } + } + } + + // No cache found + return NULL; + } + + /** + * Save data to a simple cache file. This should only be used internally, and + * is NOT a replacement for the Cache library. + * + * @param string cache name + * @param mixed data to cache + * @param integer expiration in seconds + * @return boolean + */ + public static function cache_save($name, $data, $lifetime) + { + if ($lifetime < 1) + return FALSE; + + $path = Kohana::$internal_cache_path.'kohana_'.$name; + + if ($data === NULL) + { + // Delete cache + return (is_file($path) and unlink($path)); + } + else + { + // Using encryption? Encrypt the data when we write it + if(Kohana::$internal_cache_encrypt===TRUE) + { + // Encrypt and write data to cache file + $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); + $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); + + // Serialize and encrypt! + $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv); + + return (bool) file_put_contents($path, $encrypted_text); + } + else + { + // Write data to cache file + return (bool) file_put_contents($path, serialize($data)); + } + } + } + + /** + * Kohana output handler. Called during ob_clean, ob_flush, and their variants. + * + * @param string current output buffer + * @return string + */ + public static function output_buffer($output) + { + // Could be flushing, so send headers first + if ( ! Event::has_run('system.send_headers')) + { + // Run the send_headers event + Event::run('system.send_headers'); + } + + // Set final output + Kohana::$output = $output; + + // Set and return the final output + return Kohana::$output; + } + + /** + * Closes all open output buffers, either by flushing or cleaning, and stores + * output buffer for display during shutdown. + * + * @param boolean disable to clear buffers, rather than flushing + * @return void + */ + public static function close_buffers($flush = TRUE) + { + if (ob_get_level() >= Kohana::$buffer_level) + { + // Set the close function + $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean'; + + while (ob_get_level() > Kohana::$buffer_level) + { + // Flush or clean the buffer + $close(); + } + + // Store the Kohana output buffer. Apparently there was a change in PHP + // 5.4 such that if you call this you wind up with a blank page. + // Disabling it for now. See ticket #1839 + if (version_compare(PHP_VERSION, "5.4", "<")) { + ob_end_clean(); + } + } + } + + /** + * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event. + * + * @return void + */ + public static function shutdown() + { + static $run; + + // Only run this function once + if ($run === TRUE) + return; + + $run = TRUE; + + // Run system.shutdown event + Event::run('system.shutdown'); + + // Close output buffers + Kohana::close_buffers(TRUE); + + // Run the output event + Event::run('system.display', Kohana::$output); + + // Render the final output + Kohana::render(Kohana::$output); + } + + /** + * Inserts global Kohana variables into the generated output and prints it. + * + * @param string final output that will displayed + * @return void + */ + public static function render($output) + { + if (Kohana::config('core.render_stats') === TRUE) + { + // Fetch memory usage in MB + $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0; + + // Fetch benchmark for page execution time + $benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution'); + + // Replace the global template variables + $output = str_replace( + array + ( + '{kohana_version}', + '{kohana_codename}', + '{execution_time}', + '{memory_usage}', + '{included_files}', + ), + array + ( + KOHANA::VERSION, + KOHANA::CODENAME, + $benchmark['time'], + number_format($memory, 2).'MB', + count(get_included_files()), + ), + $output + ); + } + + if ($level = Kohana::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) + { + if ($compress = request::preferred_encoding(array('gzip','deflate'), TRUE)) + { + if ($level < 1 OR $level > 9) + { + // Normalize the level to be an integer between 1 and 9. This + // step must be done to prevent gzencode from triggering an error + $level = max(1, min($level, 9)); + } + + if ($compress === 'gzip') + { + // Compress output using gzip + $output = gzencode($output, $level); + } + elseif ($compress === 'deflate') + { + // Compress output using zlib (HTTP deflate) + $output = gzdeflate($output, $level); + } + + // This header must be sent with compressed content to prevent + // browser caches from breaking + header('Vary: Accept-Encoding'); + + // Send the content encoding header + header('Content-Encoding: '.$compress); + + // Sending Content-Length in CGI can result in unexpected behavior + if (stripos(Kohana::$server_api, 'cgi') === FALSE) + { + header('Content-Length: '.strlen($output)); + } + } + } + + echo $output; + } + + /** + * Provides class auto-loading. + * + * @throws Kohana_Exception + * @param string name of class + * @return bool + */ + public static function auto_load($class) + { + if (class_exists($class, FALSE) OR interface_exists($class, FALSE)) + return TRUE; + + if (($suffix = strrpos($class, '_')) > 0) + { + // Find the class suffix + $suffix = substr($class, $suffix + 1); + } + else + { + // No suffix + $suffix = FALSE; + } + + if ($suffix === 'Core') + { + $type = 'libraries'; + $file = substr($class, 0, -5); + } + elseif ($suffix === 'Controller') + { + $type = 'controllers'; + // Lowercase filename + $file = strtolower(substr($class, 0, -11)); + } + elseif ($suffix === 'Model') + { + $type = 'models'; + // Lowercase filename + $file = strtolower(substr($class, 0, -6)); + } + elseif ($suffix === 'Driver') + { + $type = 'libraries/drivers'; + $file = str_replace('_', '/', substr($class, 0, -7)); + } + else + { + // This could be either a library or a helper, but libraries must + // always be capitalized, so we check if the first character is + // uppercase. If it is, we are loading a library, not a helper. + $type = ($class[0] < 'a') ? 'libraries' : 'helpers'; + $file = $class; + } + + if ($filename = Kohana::find_file($type, $file)) + { + // Load the class + require $filename; + } + else + { + // The class could not be found + return FALSE; + } + + if ($filename = Kohana::find_file($type, Kohana::config('core.extension_prefix').$class)) + { + // Load the class extension + require $filename; + } + elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE)) + { + // Class extension to be evaluated + $extension = 'class '.$class.' extends '.$class.'_Core { }'; + + // Start class analysis + $core = new ReflectionClass($class.'_Core'); + + if ($core->isAbstract()) + { + // Make the extension abstract + $extension = 'abstract '.$extension; + } + + // Transparent class extensions are handled using eval. This is + // a disgusting hack, but it gets the job done. + eval($extension); + } + + return TRUE; + } + + /** + * Find a resource file in a given directory. Files will be located according + * to the order of the include paths. Configuration and i18n files will be + * returned in reverse order. + * + * @throws Kohana_Exception if file is required and not found + * @param string directory to search in + * @param string filename to look for (without extension) + * @param boolean file required + * @param string file extension + * @return array if the type is config, i18n or l10n + * @return string if the file is found + * @return FALSE if the file is not found + */ + public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE) + { + // NOTE: This test MUST be not be a strict comparison (===), or empty + // extensions will be allowed! + if ($ext == '') + { + // Use the default extension + $ext = EXT; + } + else + { + // Add a period before the extension + $ext = '.'.$ext; + } + + // Search path + $search = $directory.'/'.$filename.$ext; + + if (isset(Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search])) + return Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search]; + + // Load include paths + $paths = Kohana::$include_paths; + + // Nothing found, yet + $found = NULL; + + if ($directory === 'config' OR $directory === 'messages' OR $directory === 'i18n') + { + // Search in reverse, for merging + $paths = array_reverse($paths); + + foreach ($paths as $path) + { + if (is_file($path.$search)) + { + // A matching file has been found + $found[] = $path.$search; + } + } + } + else + { + foreach ($paths as $path) + { + if (is_file($path.$search)) + { + // A matching file has been found + $found = $path.$search; + + // Stop searching + break; + } + } + } + + if ($found === NULL) + { + if ($required === TRUE) + { + // If the file is required, throw an exception + throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => __($directory), ':file:' =>$filename)); + } + else + { + // Nothing was found, return FALSE + $found = FALSE; + } + } + + if ( ! isset(Kohana::$write_cache['find_file_paths'])) + { + // Write cache at shutdown + Kohana::$write_cache['find_file_paths'] = TRUE; + } + + return Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search] = $found; + } + + /** + * Lists all files and directories in a resource path. + * + * @param string directory to search + * @param boolean list all files to the maximum depth? + * @param string list all files having extension $ext + * @param string full path to search (used for recursion, *never* set this manually) + * @return array filenames and directories + */ + public static function list_files($directory, $recursive = FALSE, $ext = EXT, $path = FALSE) + { + $files = array(); + + if ($path === FALSE) + { + $paths = array_reverse(Kohana::include_paths()); + + foreach ($paths as $path) + { + // Recursively get and merge all files + $files = array_merge($files, Kohana::list_files($directory, $recursive, $ext, $path.$directory)); + } + } + else + { + $path = rtrim($path, '/').'/'; + + if (is_readable($path) AND $items = glob($path.'*')) + { + $ext_pos = 0 - strlen($ext); + + foreach ($items as $index => $item) + { + $item = str_replace('\\', '/', $item); + + if (is_dir($item)) + { + // Handle recursion + if ($recursive === TRUE) + { + // Filename should only be the basename + $item = pathinfo($item, PATHINFO_BASENAME); + + // Append sub-directory search + $files = array_merge($files, Kohana::list_files($directory, TRUE, $ext, $path.$item)); + } + } + else + { + // File extension must match + if ($ext_pos === 0 OR substr($item, $ext_pos) === $ext) + { + $files[] = $item; + } + } + } + } + } + + return $files; + } + + + /** + * Fetch a message item. + * + * @param string language key to fetch + * @param array additional information to insert into the line + * @return string i18n language string, or the requested key if the i18n item is not found + */ + public static function message($key, $args = array()) + { + // Extract the main group from the key + $group = explode('.', $key, 2); + $group = $group[0]; + + if ( ! isset(Kohana::$internal_cache['messages'][$group])) + { + // Messages for this group + $messages = array(); + + if ($file = Kohana::find_file('messages', $group)) + { + include $file[0]; + } + + if ( ! isset(Kohana::$write_cache['messages'])) + { + // Write language cache + Kohana::$write_cache['messages'] = TRUE; + } + + Kohana::$internal_cache['messages'][$group] = $messages; + } + + // Get the line from cache + $line = Kohana::key_string(Kohana::$internal_cache['messages'], $key); + + if ($line === NULL) + { + Kohana_Log::add('error', 'Missing messages entry '.$key.' for message '.$group); + + // Return the key string as fallback + return $key; + } + + if (is_string($line) AND func_num_args() > 1) + { + $args = array_slice(func_get_args(), 1); + + // Add the arguments into the line + $line = vsprintf($line, is_array($args[0]) ? $args[0] : $args); + } + + return $line; + } + + /** + * Returns the value of a key, defined by a 'dot-noted' string, from an array. + * + * @param array array to search + * @param string dot-noted string: foo.bar.baz + * @return string if the key is found + * @return void if the key is not found + */ + public static function key_string($array, $keys) + { + if (empty($array)) + return NULL; + + // Prepare for loop + $keys = explode('.', $keys); + + do + { + // Get the next key + $key = array_shift($keys); + + if (isset($array[$key])) + { + if (is_array($array[$key]) AND ! empty($keys)) + { + // Dig down to prepare the next loop + $array = $array[$key]; + } + else + { + // Requested key was found + return $array[$key]; + } + } + else + { + // Requested key is not set + break; + } + } + while ( ! empty($keys)); + + return NULL; + } + + /** + * Sets values in an array by using a 'dot-noted' string. + * + * @param array array to set keys in (reference) + * @param string dot-noted string: foo.bar.baz + * @return mixed fill value for the key + * @return void + */ + public static function key_string_set( & $array, $keys, $fill = NULL) + { + if (is_object($array) AND ($array instanceof ArrayObject)) + { + // Copy the array + $array_copy = $array->getArrayCopy(); + + // Is an object + $array_object = TRUE; + } + else + { + if ( ! is_array($array)) + { + // Must always be an array + $array = (array) $array; + } + + // Copy is a reference to the array + $array_copy =& $array; + } + + if (empty($keys)) + return $array; + + // Create keys + $keys = explode('.', $keys); + + // Create reference to the array + $row =& $array_copy; + + for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++) + { + // Get the current key + $key = $keys[$i]; + + if ( ! isset($row[$key])) + { + if (isset($keys[$i + 1])) + { + // Make the value an array + $row[$key] = array(); + } + else + { + // Add the fill key + $row[$key] = $fill; + } + } + elseif (isset($keys[$i + 1])) + { + // Make the value an array + $row[$key] = (array) $row[$key]; + } + + // Go down a level, creating a new row reference + $row =& $row[$key]; + } + + if (isset($array_object)) + { + // Swap the array back in + $array->exchangeArray($array_copy); + } + } + + /** + * Quick debugging of any variable. Any number of parameters can be set. + * + * @return string + */ + public static function debug() + { + if (func_num_args() === 0) + return; + + // Get params + $params = func_get_args(); + $output = array(); + + foreach ($params as $var) + { + $value = is_bool($var) ? ($var ? 'true' : 'false') : print_r($var, TRUE); + $output[] = '
      ('.gettype($var).') '.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET).'
      '; + } + + return implode("\n", $output); + } + + /** + * Saves the internal caches: configuration, include paths, etc. + * + * @return boolean + */ + public static function internal_cache_save() + { + if ( ! is_array(Kohana::$write_cache)) + return FALSE; + + // Get internal cache names + $caches = array_keys(Kohana::$write_cache); + + // Nothing written + $written = FALSE; + + foreach ($caches as $cache) + { + if (isset(Kohana::$internal_cache[$cache])) + { + // Write the cache file + Kohana::cache_save($cache, Kohana::$internal_cache[$cache], Kohana::config('core.internal_cache')); + + // A cache has been written + $written = TRUE; + } + } + + return $written; + } + +} // End Kohana diff --git a/system/core/Kohana_Config.php b/system/core/Kohana_Config.php new file mode 100644 index 0000000..9abc5b6 --- /dev/null +++ b/system/core/Kohana_Config.php @@ -0,0 +1,329 @@ +array( + ), 'internal_cache'=>FALSE + )); + $core_config = $config->get('core'); + Kohana_Config::$instance = new Kohana_Config($core_config); + } + + // Return the Kohana_Config driver requested + return Kohana_Config::$instance; + } + + /** + * The drivers for this object + * + * @var Kohana_Config_Driver + */ + protected $drivers; + + /** + * Kohana_Config constructor to load the supplied driver. + * Enforces the singleton pattern. + * + * @param string driver + * @access protected + */ + protected function __construct(array $core_config) + { + $drivers = $core_config['config_drivers']; + + //remove array if it's found in config + if (in_array('array', $drivers)) + unset($drivers[array_search('array', $drivers)]); + + //add array at the very end + $this->drivers = $drivers = array_merge($drivers, array( + 'array' + )); + + foreach ($this->drivers as & $driver) + { + // Create the driver name + $driver = 'Config_'.ucfirst($driver).'_Driver'; + + // Ensure the driver loads correctly + if (!Kohana::auto_load($driver)) + throw new Kohana_Exception('The :driver: driver for the :library: library could not be found.', array( + ':driver:' => $driver, ':library:' => get_class($this) + )); + + // Load the new driver + $driver = new $driver($core_config); + + // Ensure the new driver is valid + if (!$driver instanceof Config_Driver) + throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface', array( + ':driver:' => $driver, ':library:' => get_class($this), ':interface:' => 'Config_Driver' + )); + } + } + + /** + * Gets a value from the configuration driver + * + * @param string key + * @param bool slash + * @param bool required + * @return mixed + * @access public + */ + public function get($key, $slash = FALSE, $required = FALSE) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->get($key, $slash, $required); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Sets a value to the configuration drivers + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function set($key, $value) + { + foreach ($this->drivers as $driver) + { + try + { + $driver->set($key, $value); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } + + /** + * Clears a group from configuration + * + * @param string group + * @return bool + * @access public + */ + public function clear($group) + { + foreach ($this->drivers as $driver) + { + try + { + $driver->clear($group); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } + + /** + * Loads a configuration group + * + * @param string group + * @param bool required + * @return array + * @access public + */ + public function load($group, $required = FALSE) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->load($group, $required); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Returns true or false if any config has been loaded(either manually or from cache) + * + * @return boolean + */ + public function loaded() + { + return $this->drivers[(count($this->drivers) - 1)]->loaded; + } + + /** + * The following allows access using + * array syntax. + * + * @example $config['core.site_domain'] + */ + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @return mixed + * @access public + */ + public function offsetGet($key) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->get($key); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function offsetSet($key, $value) + { + foreach ($this->drivers as $driver) + { + try + { + $driver->set($key, $value); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @return bool + * @access public + */ + public function offsetExists($key) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->setting_exists($key); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @return bool + * @access public + */ + public function offsetUnset($key) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->set($key, NULL); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } +} // End KohanaConfig + +class Kohana_Config_Exception extends Kohana_Exception {} diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php new file mode 100644 index 0000000..bc0efd1 --- /dev/null +++ b/system/core/Kohana_Exception.php @@ -0,0 +1,622 @@ +instance_identifier = uniqid(); + + // Translate the error message + $message = __($message, $variables); + + // Sets $this->message the proper way + parent::__construct($message, $code); + } + + /** + * Enable Kohana exception handling. + * + * @uses Kohana_Exception::$template + * @return void + */ + public static function enable() + { + if ( ! Kohana_Exception::$enabled) + { + set_exception_handler(array('Kohana_Exception', 'handle')); + + Kohana_Exception::$enabled = TRUE; + } + } + + /** + * Disable Kohana exception handling. + * + * @return void + */ + public static function disable() + { + if (Kohana_Exception::$enabled) + { + restore_exception_handler(); + + Kohana_Exception::$enabled = FALSE; + } + } + + /** + * Get a single line of text representing the exception: + * + * Error [ Code ]: Message ~ File [ Line ] + * + * @param object Exception + * @return string + */ + public static function text($e) + { + return sprintf('%s [ %s ]: %s ~ %s [ %d ]', + get_class($e), $e->getCode(), strip_tags($e->getMessage()), Kohana_Exception::debug_path($e->getFile()), $e->getLine()); + } + + /** + * exception handler, displays the error message, source of the + * exception, and the stack trace of the error. + * + * @uses Kohana::message() + * @uses Kohana_Exception::text() + * @param object exception object + * @return void + */ + public static function handle(Exception $e) + { + try + { + // Get the exception information + $type = get_class($e); + $code = $e->getCode(); + $message = $e->getMessage(); + + // Create a text version of the exception + $error = Kohana_Exception::text($e); + + // Add this exception to the log + Kohana_Log::add('error', $error); + + // Manually save logs after exceptions + Kohana_Log::save(); + + if (Kohana::config('kohana/core.display_errors') === FALSE) + { + // Do not show the details + $file = $line = NULL; + $trace = array(); + + $template = '_disabled'; + } + else + { + $file = $e->getFile(); + $line = $e->getLine(); + $trace = $e->getTrace(); + + $template = ''; + } + + if ($e instanceof Kohana_Exception) + { + $template = $e->getTemplate().$template; + + if ( ! headers_sent()) + { + $e->sendHeaders(); + } + + // Use the human-readable error name + $code = Kohana::message('kohana/core.errors.'.$code); + } + else + { + $template = Kohana_Exception::$template.$template; + + if ( ! headers_sent()) + { + header('HTTP/1.1 500 Internal Server Error'); + } + + if ($e instanceof ErrorException) + { + // Use the human-readable error name + $code = Kohana::message('kohana/core.errors.'.$e->getSeverity()); + + if (version_compare(PHP_VERSION, '5.3', '<')) + { + // Workaround for a bug in ErrorException::getTrace() that exists in + // all PHP 5.2 versions. @see http://bugs.php.net/45895 + for ($i = count($trace) - 1; $i > 0; --$i) + { + if (isset($trace[$i - 1]['args'])) + { + // Re-position the arguments + $trace[$i]['args'] = $trace[$i - 1]['args']; + + unset($trace[$i - 1]['args']); + } + } + } + } + } + + // Clean the output buffer if one exists + ob_get_level() and ob_clean(); + + if ($template = Kohana::find_file('views', $template)) + { + include $template; + } + } + catch (Exception $e) + { + // Clean the output buffer if one exists + ob_get_level() and ob_clean(); + + // Display the exception text + echo Kohana_Exception::text($e), "\n"; + } + + if (Kohana::$server_api === 'cli') + { + // Exit with an error status + exit(1); + } + } + + /** + * Returns the template for this exception. + * + * @uses Kohana_Exception::$template + * @return string + */ + public function getTemplate() + { + return Kohana_Exception::$template; + } + + /** + * Sends an Internal Server Error header. + * + * @return void + */ + public function sendHeaders() + { + // Send the 500 header + header('HTTP/1.1 500 Internal Server Error'); + } + + /** + * Returns an HTML string of information about a single variable. + * + * Borrows heavily on concepts from the Debug class of {@link http://nettephp.com/ Nette}. + * + * @param mixed variable to dump + * @param integer maximum length of strings + * @param integer maximum levels of recursion + * @return string + */ + public static function dump($value, $length = 128, $max_level = 5) + { + return Kohana_Exception::_dump($value, $length, $max_level); + } + + /** + * Helper for Kohana_Exception::dump(), handles recursion in arrays and objects. + * + * @param mixed variable to dump + * @param integer maximum length of strings + * @param integer maximum levels of recursion + * @param integer current recursion level (internal) + * @return string + */ + private static function _dump( & $var, $length = 128, $max_level = 5, $level = 0) + { + if ($var === NULL) + { + return 'NULL'; + } + elseif (is_bool($var)) + { + return 'bool '.($var ? 'TRUE' : 'FALSE'); + } + elseif (is_float($var)) + { + return 'float '.$var; + } + elseif (is_resource($var)) + { + if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var)) + { + $meta = stream_get_meta_data($var); + + if (isset($meta['uri'])) + { + $file = $meta['uri']; + + if (function_exists('stream_is_local')) + { + // Only exists on PHP >= 5.2.4 + if (stream_is_local($file)) + { + $file = Kohana_Exception::debug_path($file); + } + } + + return 'resource('.$type.') '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::CHARSET); + } + } + else + { + return 'resource('.$type.')'; + } + } + elseif (is_string($var)) + { + if (strlen($var) > $length) + { + // Encode the truncated string + $str = htmlspecialchars(substr($var, 0, $length), ENT_NOQUOTES, Kohana::CHARSET).' …'; + } + else + { + // Encode the string + $str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::CHARSET); + } + + return 'string('.strlen($var).') "'.$str.'"'; + } + elseif (is_array($var)) + { + $output = array(); + + // Indentation for this variable + $space = str_repeat($s = ' ', $level); + + static $marker; + + if ($marker === NULL) + { + // Make a unique marker + $marker = uniqid("\x00"); + } + + if (empty($var)) + { + // Do nothing + } + elseif (isset($var[$marker])) + { + $output[] = "(\n$space$s*RECURSION*\n$space)"; + } + elseif ($level <= $max_level) + { + $output[] = "("; + + $var[$marker] = TRUE; + foreach ($var as $key => & $val) + { + if ($key === $marker) continue; + if ( ! is_int($key)) + { + $key = '"'.$key.'"'; + } + + $output[] = "$space$s$key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1); + } + unset($var[$marker]); + + $output[] = "$space)"; + } + else + { + // Depth too great + $output[] = "(\n$space$s...\n$space)"; + } + + return 'array('.count($var).') '.implode("\n", $output); + } + elseif (is_object($var)) + { + // Copy the object as an array + $array = (array) $var; + + $output = array(); + + // Indentation for this variable + $space = str_repeat($s = ' ', $level); + + $hash = spl_object_hash($var); + + // Objects that are being dumped + static $objects = array(); + + if (empty($var)) + { + // Do nothing + } + elseif (isset($objects[$hash])) + { + $output[] = "{\n$space$s*RECURSION*\n$space}"; + } + elseif ($level <= $max_level) + { + $output[] = "{"; + + $objects[$hash] = TRUE; + foreach ($array as $key => & $val) + { + if ($key[0] === "\x00") + { + // Determine if the access is private or protected + $access = ''.($key[1] === '*' ? 'protected' : 'private').''; + + // Remove the access level from the variable name + $key = substr($key, strrpos($key, "\x00") + 1); + } + else + { + $access = 'public'; + } + + $output[] = "$space$s$access $key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1); + } + unset($objects[$hash]); + + $output[] = "$space}"; + } + else + { + // Depth too great + $output[] = "{\n$space$s...\n$space}"; + } + + return 'object '.get_class($var).'('.count($array).') '.implode("\n", $output); + } + else + { + return ''.gettype($var).' '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::CHARSET); + } + } + + /** + * Removes APPPATH, SYSPATH, MODPATH, and DOCROOT from filenames, replacing + * them with the plain text equivalents. + * + * @param string path to sanitize + * @return string + */ + public static function debug_path($file) + { + // Normalize directory separator + $file = str_replace('\\', '/', $file); + + if (strpos($file, APPPATH) === 0) + { + $file = 'APPPATH/'.substr($file, strlen(APPPATH)); + } + elseif (strpos($file, SYSPATH) === 0) + { + $file = 'SYSPATH/'.substr($file, strlen(SYSPATH)); + } + elseif (strpos($file, MODPATH) === 0) + { + $file = 'MODPATH/'.substr($file, strlen(MODPATH)); + } + elseif (strpos($file, DOCROOT) === 0) + { + $file = 'DOCROOT/'.substr($file, strlen(DOCROOT)); + } + + return $file; + } + + /** + * Returns an array of lines from a file. + * + * // Returns the current line of the current file + * echo Kohana_Exception::debug_source(__FILE__, __LINE__); + * + * @param string file to open + * @param integer line number to find + * @param integer number of padding lines + * @return array + */ + public static function debug_source($file, $line_number, $padding = 5) + { + // Make sure we can read the source file + if ( ! is_readable($file)) + return array(); + + // Open the file and set the line position + $file = fopen($file, 'r'); + $line = 0; + + // Set the reading range + $range = array('start' => $line_number - $padding, 'end' => $line_number + $padding); + + // Set the zero-padding amount for line numbers + $format = '% '.strlen($range['end']).'d'; + + $source = array(); + while (($row = fgets($file)) !== FALSE) + { + // Increment the line number + if (++$line > $range['end']) + break; + + if ($line >= $range['start']) + { + $source[sprintf($format, $line)] = $row; + } + } + + // Close the file + fclose($file); + + return $source; + } + + /** + * Returns an array of strings that represent each step in the backtrace. + * + * @param array trace to analyze + * @return array + */ + public static function trace($trace = NULL) + { + if ($trace === NULL) + { + // Start a new trace + $trace = debug_backtrace(); + } + + // Non-standard function calls + $statements = array('include', 'include_once', 'require', 'require_once'); + + $output = array(); + foreach ($trace as $step) + { + if ( ! isset($step['function'])) + { + // Invalid trace step + continue; + } + + if (isset($step['file']) AND isset($step['line'])) + { + // Include the source of this step + $source = Kohana_Exception::debug_source($step['file'], $step['line']); + } + + if (isset($step['file'])) + { + $file = $step['file']; + + if (isset($step['line'])) + { + $line = $step['line']; + } + } + + // function() + $function = $step['function']; + + if (in_array($step['function'], $statements)) + { + if (empty($step['args'])) + { + // No arguments + $args = array(); + } + else + { + // Sanitize the file path + $args = array($step['args'][0]); + } + } + elseif (isset($step['args'])) + { + if ($step['function'] === '{closure}') + { + // Introspection on closures in a stack trace is impossible + $params = NULL; + } + else + { + if (isset($step['class'])) + { + if (method_exists($step['class'], $step['function'])) + { + $reflection = new ReflectionMethod($step['class'], $step['function']); + } + else + { + $reflection = new ReflectionMethod($step['class'], '__call'); + } + } + else + { + $reflection = new ReflectionFunction($step['function']); + } + + // Get the function parameters + $params = $reflection->getParameters(); + } + + $args = array(); + + foreach ($step['args'] as $i => $arg) + { + if (isset($params[$i])) + { + // Assign the argument by the parameter name + $args[$params[$i]->name] = $arg; + } + else + { + // Assign the argument by number + $args[$i] = $arg; + } + } + } + + if (isset($step['class'])) + { + // Class->method() or Class::method() + $function = $step['class'].$step['type'].$step['function']; + } + + $output[] = array( + 'function' => $function, + 'args' => isset($args) ? $args : NULL, + 'file' => isset($file) ? $file : NULL, + 'line' => isset($line) ? $line : NULL, + 'source' => isset($source) ? $source : NULL, + ); + + unset($function, $args, $file, $line, $source); + } + + return $output; + } + +} // End Kohana Exception diff --git a/system/helpers/arr.php b/system/helpers/arr.php new file mode 100644 index 0000000..19026fa --- /dev/null +++ b/system/helpers/arr.php @@ -0,0 +1,273 @@ + $value) + { + $value = ($keep_keys === TRUE) ? $value : array_values($value); + foreach ($value as $k => $v) + { + $new_array[$k][$key] = $v; + } + } + + return $new_array; + } + + /** + * Removes a key from an array and returns the value. + * + * @param string key to return + * @param array array to work on + * @return mixed value of the requested array key + */ + public static function remove($key, & $array) + { + if ( ! array_key_exists($key, $array)) + return NULL; + + $val = $array[$key]; + unset($array[$key]); + + return $val; + } + + + /** + * Extract one or more keys from an array. Each key given after the first + * argument (the array) will be extracted. Keys that do not exist in the + * search array will be NULL in the extracted data. + * + * @param array array to search + * @param string key name + * @return array + */ + public static function extract(array $search, $keys) + { + // Get the keys, removing the $search array + $keys = array_slice(func_get_args(), 1); + + $found = array(); + foreach ($keys as $key) + { + $found[$key] = isset($search[$key]) ? $search[$key] : NULL; + } + + return $found; + } + + /** + * Get the value of array[key]. If it doesn't exist, return default. + * + * @param array array to search + * @param string key name + * @param mixed default value + * @return mixed + */ + public static function get(array $array, $key, $default = NULL) + { + return isset($array[$key]) ? $array[$key] : $default; + } + + /** + * Because PHP does not have this function. + * + * @param array array to unshift + * @param string key to unshift + * @param mixed value to unshift + * @return array + */ + public static function unshift_assoc( array & $array, $key, $val) + { + $array = array_reverse($array, TRUE); + $array[$key] = $val; + $array = array_reverse($array, TRUE); + + return $array; + } + + /** + * Because PHP does not have this function, and array_walk_recursive creates + * references in arrays and is not truly recursive. + * + * @param mixed callback to apply to each member of the array + * @param array array to map to + * @return array + */ + public static function map_recursive($callback, array $array) + { + foreach ($array as $key => $val) + { + // Map the callback to the key + $array[$key] = is_array($val) ? arr::map_recursive($callback, $val) : call_user_func($callback, $val); + } + + return $array; + } + + /** + * Emulates array_merge_recursive, but appends numeric keys and replaces + * associative keys, instead of appending all keys. + * + * @param array any number of arrays + * @return array + */ + public static function merge() + { + $total = func_num_args(); + + $result = array(); + for ($i = 0; $i < $total; $i++) + { + foreach (func_get_arg($i) as $key => $val) + { + if (isset($result[$key])) + { + if (is_array($val)) + { + // Arrays are merged recursively + $result[$key] = arr::merge($result[$key], $val); + } + elseif (is_int($key)) + { + // Indexed arrays are appended + array_push($result, $val); + } + else + { + // Associative arrays are replaced + $result[$key] = $val; + } + } + else + { + // New values are added + $result[$key] = $val; + } + } + } + + return $result; + } + + /** + * Overwrites an array with values from input array(s). + * Non-existing keys will not be appended! + * + * @param array key array + * @param array input array(s) that will overwrite key array values + * @return array + */ + public static function overwrite($array1, $array2) + { + foreach (array_intersect_key($array2, $array1) as $key => $value) + { + $array1[$key] = $value; + } + + if (func_num_args() > 2) + { + foreach (array_slice(func_get_args(), 2) as $array2) + { + foreach (array_intersect_key($array2, $array1) as $key => $value) + { + $array1[$key] = $value; + } + } + } + + return $array1; + } + + /** + * Recursively convert an array to an object. + * + * @param array array to convert + * @return object + */ + public static function to_object(array $array, $class = 'stdClass') + { + $object = new $class; + + foreach ($array as $key => $value) + { + if (is_array($value)) + { + // Convert the array to an object + $value = arr::to_object($value, $class); + } + + // Add the value to the object + $object->{$key} = $value; + } + + return $object; + } + + /** + * Returns specific key/column from an array of objects. + * + * @param string|integer $key The key or column number to pluck from each object. + * @param array $array The array of objects to pluck from. + * @return array + */ + public static function pluck($key, $array) + { + $result = array(); + foreach ($array as $i => $object) + { + $result[$i] = isset($object[$key]) ? $object[$key] : NULL; + } + return $result; + } +} // End arr diff --git a/system/helpers/cookie.php b/system/helpers/cookie.php new file mode 100644 index 0000000..3680ae3 --- /dev/null +++ b/system/helpers/cookie.php @@ -0,0 +1,149 @@ + $value) + { + $cookies[$key] = cookie::get($key, $default, $xss_clean); + } + return $cookies; + } + + if ( ! isset($_COOKIE[$name])) + { + return $default; + } + + // Get the cookie value + $cookie = $_COOKIE[$name]; + + // Find the position of the split between salt and contents + $split = strlen(cookie::salt($name, NULL)); + + if (isset($cookie[$split]) AND $cookie[$split] === '~') + { + // Separate the salt and the value + list ($hash, $value) = explode('~', $cookie, 2); + + if (cookie::salt($name, $value) === $hash) + { + if ($xss_clean === TRUE AND Kohana::config('core.global_xss_filtering') === FALSE) + { + return Input::instance()->xss_clean($value); + } + // Cookie signature is valid + return $value; + } + + // The cookie signature is invalid, delete it + cookie::delete($name); + } + + return $default; + } + + /** + * Nullify and unset a cookie. + * + * @param string cookie name + * @param string URL path + * @param string URL domain + * @return boolean + */ + public static function delete($name, $path = NULL, $domain = NULL) + { + // Delete the cookie from globals + unset($_COOKIE[$name]); + + // Sets the cookie value to an empty string, and the expiration to 24 hours ago + return cookie::set($name, '', -86400, $path, $domain, FALSE, FALSE); + } + + /** + * Generates a salt string for a cookie based on the name and value. + * + * @param string $name name of cookie + * @param string $value value of cookie + * @return string sha1 hash + */ + public static function salt($name, $value) + { + // Determine the user agent + $agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : 'unknown'; + + // Cookie salt. + $salt = Kohana::config('cookie.salt'); + + return sha1($agent.$name.$value.$salt); + } + + final private function __construct() + { + // Static class. + } + +} // End cookie \ No newline at end of file diff --git a/system/helpers/date.php b/system/helpers/date.php new file mode 100644 index 0000000..0b29d28 --- /dev/null +++ b/system/helpers/date.php @@ -0,0 +1,395 @@ +> 1); + } + + /** + * Converts a DOS timestamp to UNIX format. + * + * @param integer DOS timestamp + * @return integer + */ + public static function dos2unix($timestamp = FALSE) + { + $sec = 2 * ($timestamp & 0x1f); + $min = ($timestamp >> 5) & 0x3f; + $hrs = ($timestamp >> 11) & 0x1f; + $day = ($timestamp >> 16) & 0x1f; + $mon = ($timestamp >> 21) & 0x0f; + $year = ($timestamp >> 25) & 0x7f; + + return mktime($hrs, $min, $sec, $mon, $day, $year + 1980); + } + + /** + * Returns the offset (in seconds) between two time zones. + * @see http://php.net/timezones + * + * @param string timezone to find the offset of + * @param string|boolean timezone used as the baseline + * @param string time at which to calculate + * @return integer + */ + public static function offset($remote, $local = TRUE, $when = 'now') + { + if ($local === TRUE) + { + $local = date_default_timezone_get(); + } + + // Create timezone objects + $remote = new DateTimeZone($remote); + $local = new DateTimeZone($local); + + // Create date objects from timezones + $time_there = new DateTime($when, $remote); + $time_here = new DateTime($when, $local); + + // Find the offset + return $remote->getOffset($time_there) - $local->getOffset($time_here); + } + + /** + * Number of seconds in a minute, incrementing by a step. + * + * @param integer amount to increment each step by, 1 to 30 + * @param integer start value + * @param integer end value + * @return array A mirrored (foo => foo) array from 1-60. + */ + public static function seconds($step = 1, $start = 0, $end = 60) + { + // Always integer + $step = (int) $step; + + $seconds = array(); + + for ($i = $start; $i < $end; $i += $step) + { + $seconds[$i] = ($i < 10) ? '0'.$i : $i; + } + + return $seconds; + } + + /** + * Number of minutes in an hour, incrementing by a step. + * + * @param integer amount to increment each step by, 1 to 30 + * @return array A mirrored (foo => foo) array from 1-60. + */ + public static function minutes($step = 5) + { + // Because there are the same number of minutes as seconds in this set, + // we choose to re-use seconds(), rather than creating an entirely new + // function. Shhhh, it's cheating! ;) There are several more of these + // in the following methods. + return date::seconds($step); + } + + /** + * Number of hours in a day. + * + * @param integer amount to increment each step by + * @param boolean use 24-hour time + * @param integer the hour to start at + * @return array A mirrored (foo => foo) array from start-12 or start-23. + */ + public static function hours($step = 1, $long = FALSE, $start = NULL) + { + // Default values + $step = (int) $step; + $long = (bool) $long; + $hours = array(); + + // Set the default start if none was specified. + if ($start === NULL) + { + $start = ($long === FALSE) ? 1 : 0; + } + + $hours = array(); + + // 24-hour time has 24 hours, instead of 12 + $size = ($long === TRUE) ? 23 : 12; + + for ($i = $start; $i <= $size; $i += $step) + { + $hours[$i] = $i; + } + + return $hours; + } + + /** + * Returns AM or PM, based on a given hour. + * + * @param integer number of the hour + * @return string + */ + public static function ampm($hour) + { + // Always integer + $hour = (int) $hour; + + return ($hour > 11) ? 'PM' : 'AM'; + } + + /** + * Adjusts a non-24-hour number into a 24-hour number. + * + * @param integer hour to adjust + * @param string AM or PM + * @return string + */ + public static function adjust($hour, $ampm) + { + $hour = (int) $hour; + $ampm = strtolower($ampm); + + switch ($ampm) + { + case 'am': + if ($hour == 12) + $hour = 0; + break; + case 'pm': + if ($hour < 12) + $hour += 12; + break; + } + + return sprintf('%02s', $hour); + } + + /** + * Number of days in month. + * + * @param integer number of month + * @param integer number of year to check month, defaults to the current year + * @return array A mirrored (foo => foo) array of the days. + */ + public static function days($month, $year = FALSE) + { + static $months; + + // Always integers + $month = (int) $month; + $year = (int) $year; + + // Use the current year by default + $year = ($year == FALSE) ? date('Y') : $year; + + // We use caching for months, because time functions are used + if (empty($months[$year][$month])) + { + $months[$year][$month] = array(); + + // Use date to find the number of days in the given month + $total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1; + + for ($i = 1; $i < $total; $i++) + { + $months[$year][$month][$i] = $i; + } + } + + return $months[$year][$month]; + } + + /** + * Number of months in a year + * + * @return array A mirrored (foo => foo) array from 1-12. + */ + public static function months() + { + return date::hours(); + } + + /** + * Returns an array of years between a starting and ending year. + * Uses the current year +/- 5 as the max/min. + * + * @param integer starting year + * @param integer ending year + * @return array + */ + public static function years($start = FALSE, $end = FALSE) + { + // Default values + $start = ($start === FALSE) ? date('Y') - 5 : (int) $start; + $end = ($end === FALSE) ? date('Y') + 5 : (int) $end; + + $years = array(); + + // Add one, so that "less than" works + $end += 1; + + for ($i = $start; $i < $end; $i++) + { + $years[$i] = $i; + } + + return $years; + } + + /** + * Returns time difference between two timestamps, in human readable format. + * + * @param integer timestamp + * @param integer timestamp, defaults to the current time + * @param string formatting string + * @return string|array + */ + public static function timespan($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') + { + // Array with the output formats + $output = preg_split('/[^a-z]+/', strtolower((string) $output)); + + // Invalid output + if (empty($output)) + return FALSE; + + // Make the output values into keys + extract(array_flip($output), EXTR_SKIP); + + // Default values + $time1 = max(0, (int) $time1); + $time2 = empty($time2) ? time() : max(0, (int) $time2); + + // Calculate timespan (seconds) + $timespan = abs($time1 - $time2); + + // All values found using Google Calculator. + // Years and months do not match the formula exactly, due to leap years. + + // Years ago, 60 * 60 * 24 * 365 + isset($years) and $timespan -= 31556926 * ($years = (int) floor($timespan / 31556926)); + + // Months ago, 60 * 60 * 24 * 30 + isset($months) and $timespan -= 2629744 * ($months = (int) floor($timespan / 2629743.83)); + + // Weeks ago, 60 * 60 * 24 * 7 + isset($weeks) and $timespan -= 604800 * ($weeks = (int) floor($timespan / 604800)); + + // Days ago, 60 * 60 * 24 + isset($days) and $timespan -= 86400 * ($days = (int) floor($timespan / 86400)); + + // Hours ago, 60 * 60 + isset($hours) and $timespan -= 3600 * ($hours = (int) floor($timespan / 3600)); + + // Minutes ago, 60 + isset($minutes) and $timespan -= 60 * ($minutes = (int) floor($timespan / 60)); + + // Seconds ago, 1 + isset($seconds) and $seconds = $timespan; + + // Remove the variables that cannot be accessed + unset($timespan, $time1, $time2); + + // Deny access to these variables + $deny = array_flip(array('deny', 'key', 'difference', 'output')); + + // Return the difference + $difference = array(); + foreach ($output as $key) + { + if (isset($$key) AND ! isset($deny[$key])) + { + // Add requested key to the output + $difference[$key] = $$key; + } + } + + // Invalid output formats string + if (empty($difference)) + return FALSE; + + // If only one output format was asked, don't put it in an array + if (count($difference) === 1) + return current($difference); + + // Return array + return $difference; + } + + /** + * Returns time difference between two timestamps, in the format: + * N year, N months, N weeks, N days, N hours, N minutes, and N seconds ago + * + * @param integer timestamp + * @param integer timestamp, defaults to the current time + * @param string formatting string + * @return string + */ + public static function timespan_string($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') + { + if ($difference = date::timespan($time1, $time2, $output) AND is_array($difference)) + { + // Determine the key of the last item in the array + $last = end($difference); + $last = key($difference); + + $span = array(); + foreach ($difference as $name => $amount) + { + if ($amount === 0) + { + // Skip empty amounts + continue; + } + + // Add the amount to the span + $span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name); + } + + // If the difference is less than 60 seconds, remove the preceding and. + if (count($span) === 1) + { + $span[0] = ltrim($span[0], 'and '); + } + + // Replace difference by making the span into a string + $difference = trim(implode('', $span), ','); + } + elseif (is_int($difference)) + { + // Single-value return + $difference = $difference.' '.($difference === 1 ? inflector::singular($output) : $output); + } + + return $difference; + } + +} // End date \ No newline at end of file diff --git a/system/helpers/db.php b/system/helpers/db.php new file mode 100644 index 0000000..941bb2e --- /dev/null +++ b/system/helpers/db.php @@ -0,0 +1,47 @@ +select($columns); + } + + public static function insert($table = NULL, $set = NULL) + { + return db::build()->insert($table, $set); + } + + public static function update($table = NULL, $set = NULL, $where = NULL) + { + return db::build()->update($table, $set, $where); + } + + public static function delete($table = NULL, $where = NULL) + { + return db::build()->delete($table, $where); + } + + public static function expr($expression) + { + return new Database_Expression($expression); + } + +} // End db diff --git a/system/helpers/download.php b/system/helpers/download.php new file mode 100644 index 0000000..0bdb04d --- /dev/null +++ b/system/helpers/download.php @@ -0,0 +1,135 @@ +channel) ? $feed->xpath('//item') : $feed->entry; + + $i = 0; + $items = array(); + + foreach ($feed as $item) + { + if ($limit > 0 AND $i++ === $limit) + break; + + $items[] = (array) $item; + } + + return $items; + } + + /** + * Creates a feed from the given parameters. + * + * @param array feed information + * @param array items to add to the feed + * @param string define which format to use + * @param string define which encoding to use + * @return string + */ + public static function create($info, $items, $format = 'rss2', $encoding = 'UTF-8') + { + $info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP'); + + $feed = ''; + $feed = simplexml_load_string($feed); + + foreach ($info as $name => $value) + { + if (($name === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value))) + { + // Convert timestamps to RFC 822 formatted dates + $value = date(DATE_RFC822, $value); + } + elseif (($name === 'link' OR $name === 'docs') AND strpos($value, '://') === FALSE) + { + // Convert URIs to URLs + $value = url::site($value, 'http'); + } + + // Add the info to the channel + $feed->channel->addChild($name, $value); + } + + foreach ($items as $item) + { + // Add the item to the channel + $row = $feed->channel->addChild('item'); + + foreach ($item as $name => $value) + { + if ($name === 'pubDate' AND (is_int($value) OR ctype_digit($value))) + { + // Convert timestamps to RFC 822 formatted dates + $value = date(DATE_RFC822, $value); + } + elseif (($name === 'link' OR $name === 'guid') AND strpos($value, '://') === FALSE) + { + // Convert URIs to URLs + $value = url::site($value, 'http'); + } + + // Add the info to the row + $row->addChild($name, $value); + } + } + + return $feed->asXML(); + } + +} // End feed \ No newline at end of file diff --git a/system/helpers/file.php b/system/helpers/file.php new file mode 100644 index 0000000..48fbfc1 --- /dev/null +++ b/system/helpers/file.php @@ -0,0 +1,184 @@ +'."\n"; + + // Add hidden fields immediate after opening tag + empty($hidden) or $form .= form::hidden($hidden); + + return $form; + } + + /** + * Generates an opening HTML form tag that can be used for uploading files. + * + * @param string form action attribute + * @param array extra attributes + * @param array hidden fields to be created immediately after the form tag + * @return string + */ + public static function open_multipart($action = NULL, $attr = array(), $hidden = array()) + { + // Set multi-part form type + $attr['enctype'] = 'multipart/form-data'; + + return form::open($action, $attr, $hidden); + } + + /** + * Creates a HTML form hidden input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function hidden($data, $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'hidden'; + + return form::input($data, $value, $extra); + } + + /** + * Creates an HTML form input tag. Defaults to a text type. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function input($data, $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + // Type and value are required attributes + $data += array + ( + 'type' => 'text', + 'value' => $value + ); + + return ''; + } + + /** + * Creates a HTML form password input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function password($data, $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'password'; + + return form::input($data, $value, $extra); + } + + /** + * Creates an HTML form upload input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function upload($data, $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'file'; + + return form::input($data, $value, $extra); + } + + /** + * Creates an HTML form textarea tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @param boolean encode existing entities + * @return string + */ + public static function textarea($data, $value = '', $extra = '', $double_encode = TRUE) + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + if ( ! isset($data['rows'])) + { + $data['rows'] = ''; + } + + if ( ! isset($data['cols'])) + { + $data['cols'] = ''; + } + + // Use the value from $data if possible, or use $value + $value = isset($data['value']) ? $data['value'] : $value; + + // Value is not part of the attributes + unset($data['value']); + + return ''.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET, $double_encode).''; + } + + /** + * Creates an HTML form select tag, or "dropdown menu". + * + * @param string|array input name or an array of HTML attributes + * @param array select options, when using a name + * @param string|array option key(s) that should be selected by default + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function dropdown($data, $options = NULL, $selected = NULL, $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + else + { + if (isset($data['options'])) + { + // Use data options + $options = $data['options']; + } + + if (isset($data['selected'])) + { + // Use data selected + $selected = $data['selected']; + } + } + + if (is_array($selected)) + { + // Multi-select box + $data['multiple'] = 'multiple'; + } + else + { + // Single selection (but converted to an array) + $selected = array($selected); + } + + $input = ''."\n"; + foreach ((array) $options as $key => $val) + { + // Key should always be a string + $key = (string) $key; + + if (is_array($val)) + { + $input .= ''."\n"; + foreach ($val as $inner_key => $inner_val) + { + // Inner key should always be a string + $inner_key = (string) $inner_key; + + $sel = in_array($inner_key, $selected) ? ' selected="selected"' : ''; + $input .= ''."\n"; + } + $input .= ''."\n"; + } + else + { + $sel = in_array($key, $selected) ? ' selected="selected"' : ''; + $input .= ''."\n"; + } + } + $input .= ''; + + return $input; + } + + /** + * Creates an HTML form checkbox input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param boolean make the checkbox checked by default + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function checkbox($data, $value = '', $checked = FALSE, $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'checkbox'; + + if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE)) + { + $data['checked'] = 'checked'; + } + else + { + unset($data['checked']); + } + + return form::input($data, $value, $extra); + } + + /** + * Creates an HTML form radio input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param boolean make the radio selected by default + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function radio($data = '', $value = '', $checked = FALSE, $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + $data['type'] = 'radio'; + + if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE)) + { + $data['checked'] = 'checked'; + } + else + { + unset($data['checked']); + } + + return form::input($data, $value, $extra); + } + + /** + * Creates an HTML form submit input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function submit($data = '', $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + if (empty($data['name'])) + { + // Remove the name if it is empty + unset($data['name']); + } + + $data['type'] = 'submit'; + + return form::input($data, $value, $extra); + } + + /** + * Creates an HTML form button input tag. + * + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function button($data = '', $value = '', $extra = '') + { + if ( ! is_array($data)) + { + $data = array('name' => $data); + } + + if (empty($data['name'])) + { + // Remove the name if it is empty + unset($data['name']); + } + + if (isset($data['value']) AND empty($value)) + { + $value = arr::remove('value', $data); + } + + return ''.$value.''; + } + + /** + * Creates an HTML form label tag. + * + * @param string|array label "for" name or an array of HTML attributes + * @param string label text or HTML + * @param string a string to be attached to the end of the attributes + * @return string + */ + public static function label($data = '', $text = NULL, $extra = '') + { + if ( ! is_array($data)) + { + if (is_string($data)) + { + // Specify the input this label is for + $data = array('for' => $data); + } + else + { + // No input specified + $data = array(); + } + } + + if ($text === NULL AND isset($data['for'])) + { + // Make the text the human-readable input name + $text = ucwords(inflector::humanize($data['for'])); + } + + return ''.$text.''; + } + + /** + * Sorts a key/value array of HTML attributes, putting form attributes first, + * and returns an attribute string. + * + * @param array HTML attributes array + * @return string + */ + public static function attributes($attr, $type = NULL) + { + if (empty($attr)) + return ''; + + $order = array + ( + 'action', + 'method', + 'type', + 'id', + 'name', + 'value', + 'src', + 'size', + 'maxlength', + 'rows', + 'cols', + 'accept', + 'tabindex', + 'accesskey', + 'align', + 'alt', + 'title', + 'class', + 'style', + 'selected', + 'checked', + 'readonly', + 'disabled' + ); + + $sorted = array(); + foreach ($order as $key) + { + if (isset($attr[$key])) + { + // Move the attribute to the sorted array + $sorted[$key] = $attr[$key]; + + // Remove the attribute from unsorted array + unset($attr[$key]); + } + } + + // Combine the sorted and unsorted attributes and create an HTML string + return html::attributes(array_merge($sorted, $attr)); + } + +} // End form diff --git a/system/helpers/format.php b/system/helpers/format.php new file mode 100644 index 0000000..7494046 --- /dev/null +++ b/system/helpers/format.php @@ -0,0 +1,112 @@ +' + // Title empty? Use the parsed URL + .($escape_title ? htmlspecialchars((($title === NULL) ? $site_url : $title), ENT_QUOTES, Kohana::CHARSET, FALSE) : (($title === NULL) ? $site_url : $title)).''; + } + + /** + * Creates an HTML anchor to a file. + * + * @param string name of file to link to + * @param string link text + * @param array HTML anchor attributes + * @param string non-default protocol, eg: ftp + * @return string + */ + public static function file_anchor($file, $title = NULL, $attributes = NULL, $protocol = NULL) + { + return + // Base URL + URI = full URL + '' + // Title empty? Use the filename part of the URI + .(($title === NULL) ? end(explode('/', $file)) : $title) .''; + } + + /** + * Generates an obfuscated version of an email address. + * + * @param string email address + * @return string + */ + public static function email($email) + { + $safe = ''; + foreach (str_split($email) as $letter) + { + switch (($letter === '@') ? rand(1, 2) : rand(1, 3)) + { + // HTML entity code + case 1: $safe .= '&#'.ord($letter).';'; break; + // Hex character code + case 2: $safe .= '&#x'.dechex(ord($letter)).';'; break; + // Raw (no) encoding + case 3: $safe .= $letter; + } + } + + return $safe; + } + + /** + * Creates an email anchor. + * + * @param string email address to send to + * @param string link text + * @param array HTML anchor attributes + * @return string + */ + public static function mailto($email, $title = NULL, $attributes = NULL) + { + if (empty($email)) + return $title; + + // Remove the subject or other parameters that do not need to be encoded + if (strpos($email, '?') !== FALSE) + { + // Extract the parameters from the email address + list ($email, $params) = explode('?', $email, 2); + + // Make the params into a query string, replacing spaces + $params = '?'.str_replace(' ', '%20', $params); + } + else + { + // No parameters + $params = ''; + } + + // Obfuscate email address + $safe = html::email($email); + + // Title defaults to the encoded email address + empty($title) and $title = $safe; + + // Parse attributes + empty($attributes) or $attributes = html::attributes($attributes); + + // Encoded start of the href="" is a static encoded version of 'mailto:' + return ''.$title.''; + } + + /** + * Generate a "breadcrumb" list of anchors representing the URI. + * + * @param array segments to use as breadcrumbs, defaults to using Router::$segments + * @return string + */ + public static function breadcrumb($segments = NULL) + { + empty($segments) and $segments = Router::$segments; + + $array = array(); + while ($segment = array_pop($segments)) + { + $array[] = html::anchor + ( + // Complete URI for the URL + implode('/', $segments).'/'.$segment, + // Title for the current segment + ucwords(inflector::humanize($segment)) + ); + } + + // Retrun the array of all the segments + return array_reverse($array); + } + + /** + * Creates a meta tag. + * + * @param string|array tag name, or an array of tags + * @param string tag "content" value + * @return string + */ + public static function meta($tag, $value = NULL) + { + if (is_array($tag)) + { + $tags = array(); + foreach ($tag as $t => $v) + { + // Build each tag and add it to the array + $tags[] = html::meta($t, $v); + } + + // Return all of the tags as a string + return implode("\n", $tags); + } + + // Set the meta attribute value + $attr = in_array(strtolower($tag), Kohana::config('http.meta_equiv')) ? 'http-equiv' : 'name'; + + return ''; + } + + /** + * Creates a stylesheet link. + * + * @param string|array filename, or array of filenames to match to array of medias + * @param string|array media type of stylesheet, or array to match filenames + * @param boolean include the index_page in the link + * @return string + */ + public static function stylesheet($style, $media = FALSE, $index = FALSE) + { + return html::link($style, 'stylesheet', 'text/css', $media, $index); + } + + /** + * Creates a link tag. + * + * @param string|array filename + * @param string|array relationship + * @param string|array mimetype + * @param string|array specifies on what device the document will be displayed + * @param boolean include the index_page in the link + * @return string + */ + public static function link($href, $rel, $type, $media = FALSE, $index = FALSE) + { + $compiled = ''; + + if (is_array($href)) + { + foreach ($href as $_href) + { + $_rel = is_array($rel) ? array_shift($rel) : $rel; + $_type = is_array($type) ? array_shift($type) : $type; + $_media = is_array($media) ? array_shift($media) : $media; + + $compiled .= html::link($_href, $_rel, $_type, $_media, $index); + } + } + else + { + if (strpos($href, '://') === FALSE) + { + // Make the URL absolute + $href = url::base($index).$href; + } + + $attr = array + ( + 'rel' => $rel, + 'type' => $type, + 'href' => $href, + ); + + if ( ! empty($media)) + { + // Add the media type to the attributes + $attr['media'] = $media; + } + + $compiled = ''; + } + + return $compiled."\n"; + } + + /** + * Creates a script link. + * + * @param string|array filename + * @param boolean include the index_page in the link + * @return string + */ + public static function script($script, $index = FALSE) + { + $compiled = ''; + + if (is_array($script)) + { + foreach ($script as $name) + { + $compiled .= html::script($name, $index); + } + } + else + { + if (strpos($script, '://') === FALSE) + { + // Add the suffix only when it's not already present + $script = url::base((bool) $index).$script; + } + + $compiled = ''; + } + + return $compiled."\n"; + } + + /** + * Creates a image link. + * + * @param string image source, or an array of attributes + * @param string|array image alt attribute, or an array of attributes + * @param boolean include the index_page in the link + * @return string + */ + public static function image($src = NULL, $alt = NULL, $index = FALSE) + { + // Create attribute list + $attributes = is_array($src) ? $src : array('src' => $src); + + if (is_array($alt)) + { + $attributes += $alt; + } + elseif ( ! empty($alt)) + { + // Add alt to attributes + $attributes['alt'] = $alt; + } + + if (strpos($attributes['src'], '://') === FALSE) + { + // Make the src attribute into an absolute URL + $attributes['src'] = url::base($index).$attributes['src']; + } + + return ''; + } + + /** + * Compiles an array of HTML attributes into an attribute string. + * + * @param string|array array of attributes + * @return string + */ + public static function attributes($attrs) + { + if (empty($attrs)) + return ''; + + if (is_string($attrs)) + return ' '.$attrs; + + $compiled = ''; + foreach ($attrs as $key => $val) + { + $compiled .= ' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'"'; + } + + return $compiled; + } + +} // End html diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php new file mode 100644 index 0000000..e723707 --- /dev/null +++ b/system/helpers/inflector.php @@ -0,0 +1,252 @@ + 1) + return $str; + + // Cache key name + $key = 'singular_'.$str.$count; + + if (isset(inflector::$cache[$key])) + return inflector::$cache[$key]; + + if (inflector::uncountable($str)) + return inflector::$cache[$key] = $str; + + if (empty(inflector::$irregular)) + { + // Cache irregular words + inflector::$irregular = Kohana::config('inflector.irregular'); + } + + if ($irregular = array_search($str, inflector::$irregular)) + { + $str = $irregular; + } + elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str)) + { + // Remove "es" + $str = substr($str, 0, -2); + } + elseif (preg_match('/[^aeiou]ies$/', $str)) + { + $str = substr($str, 0, -3).'y'; + } + elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss') + { + $str = substr($str, 0, -1); + } + + return inflector::$cache[$key] = $str; + } + + /** + * Makes a singular word plural. + * + * @param string word to pluralize + * @return string + */ + public static function plural($str, $count = NULL) + { + if ( ! $str) + return $str; + + $parts = explode('_', $str); + + $last = inflector::_plural(array_pop($parts), $count); + + $pre = implode('_', $parts); + if (strlen($pre)) + $pre .= '_'; + + return $pre.$last; + } + + + /** + * Makes a singular word plural. + * + * @param string word to pluralize + * @return string + */ + public static function _plural($str, $count = NULL) + { + // Remove garbage + $str = strtolower(trim($str)); + + if (is_string($count)) + { + // Convert to integer when using a digit string + $count = (int) $count; + } + + // Do nothing with singular + if ($count === 1) + return $str; + + // Cache key name + $key = 'plural_'.$str.$count; + + if (isset(inflector::$cache[$key])) + return inflector::$cache[$key]; + + if (inflector::uncountable($str)) + return inflector::$cache[$key] = $str; + + if (empty(inflector::$irregular)) + { + // Cache irregular words + inflector::$irregular = Kohana::config('inflector.irregular'); + } + + if (isset(inflector::$irregular[$str])) + { + $str = inflector::$irregular[$str]; + } + elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str)) + { + $str .= 'es'; + } + elseif (preg_match('/[^aeiou]y$/', $str)) + { + // Change "y" to "ies" + $str = substr_replace($str, 'ies', -1); + } + else + { + $str .= 's'; + } + + // Set the cache and return + return inflector::$cache[$key] = $str; + } + + /** + * Makes a word possessive. + * + * @param string word to to make possessive + * @return string + */ + public static function possessive($string) + { + $length = strlen($string); + + if (substr($string, $length - 1, $length) == 's') + { + return $string.'\''; + } + + return $string.'\'s'; + } + + /** + * Makes a phrase camel case. + * + * @param string phrase to camelize + * @return string + */ + public static function camelize($str) + { + $str = 'x'.strtolower(trim($str)); + $str = ucwords(preg_replace('/[\s_]+/', ' ', $str)); + + return substr(str_replace(' ', '', $str), 1); + } + + /** + * Makes a phrase underscored instead of spaced. + * + * @param string phrase to underscore + * @return string + */ + public static function underscore($str) + { + return trim(preg_replace('/[\s_]+/', '_', $str), '_'); + } + + /** + * Makes an underscored or dashed phrase human-readable. + * + * @param string phrase to make human-readable + * @return string + */ + public static function humanize($str) + { + return trim(preg_replace('/[_-\s]+/', ' ', $str)); + } + +} // End inflector \ No newline at end of file diff --git a/system/helpers/num.php b/system/helpers/num.php new file mode 100644 index 0000000..c9e9843 --- /dev/null +++ b/system/helpers/num.php @@ -0,0 +1,24 @@ + $method)); + + return $method; + } + + /** + * Retrieves current user agent information + * keys: browser, version, platform, mobile, robot + * + * @param string key + * @return mixed NULL or the parsed value + */ + public static function user_agent($key = 'agent') + { + // Retrieve raw user agent without parsing + if ($key === 'agent') + { + if (request::$user_agent === NULL) + return request::$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''; + + if (is_array(request::$user_agent)) + return request::$user_agent['agent']; + + return request::$user_agent; + } + + if ( ! is_array(request::$user_agent)) + { + request::$user_agent = array(); + request::$user_agent['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''; + + // Parse the user agent and extract basic information + foreach (Kohana::config('user_agents') as $type => $data) + { + foreach ($data as $fragment => $name) + { + if (stripos(request::$user_agent['agent'], $fragment) !== FALSE) + { + if ($type === 'browser' AND preg_match('|'.preg_quote($fragment).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', request::$user_agent['agent'], $match)) + { + // Set the browser version + request::$user_agent['version'] = $match[1]; + } + + // Set the agent name + request::$user_agent[$type] = $name; + break; + } + } + } + } + + return isset(request::$user_agent[$key]) ? request::$user_agent[$key] : NULL; + } + + /** + * Returns boolean of whether client accepts content type. + * + * @param string content type + * @param boolean set to TRUE to disable wildcard checking + * @return boolean + */ + public static function accepts($type = NULL, $explicit_check = FALSE) + { + request::parse_accept_content_header(); + + if ($type === NULL) + return request::$accept_types; + + return (request::accepts_at_quality($type, $explicit_check) > 0); + } + + /** + * Returns boolean indicating if the client accepts a charset + * + * @param string + * @return boolean + */ + public static function accepts_charset($charset = NULL) + { + request::parse_accept_charset_header(); + + if ($charset === NULL) + return request::$accept_charsets; + + return (request::accepts_charset_at_quality($charset) > 0); + } + + /** + * Returns boolean indicating if the client accepts an encoding + * + * @param string + * @param boolean set to TRUE to disable wildcard checking + * @return boolean + */ + public static function accepts_encoding($encoding = NULL, $explicit_check = FALSE) + { + request::parse_accept_encoding_header(); + + if ($encoding === NULL) + return request::$accept_encodings; + + return (request::accepts_encoding_at_quality($encoding, $explicit_check) > 0); + } + + /** + * Returns boolean indicating if the client accepts a language tag + * + * @param string language tag + * @param boolean set to TRUE to disable prefix and wildcard checking + * @return boolean + */ + public static function accepts_language($tag = NULL, $explicit_check = FALSE) + { + request::parse_accept_language_header(); + + if ($tag === NULL) + return request::$accept_languages; + + return (request::accepts_language_at_quality($tag, $explicit_check) > 0); + } + + /** + * Compare the q values for given array of content types and return the one with the highest value. + * If items are found to have the same q value, the first one encountered in the given array wins. + * If all items in the given array have a q value of 0, FALSE is returned. + * + * @param array content types + * @param boolean set to TRUE to disable wildcard checking + * @return mixed string mime type with highest q value, FALSE if none of the given types are accepted + */ + public static function preferred_accept($types, $explicit_check = FALSE) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($types as $type) + { + $q = request::accepts_at_quality($type, $explicit_check); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $type; + } + } + + return $preferred; + } + + /** + * Compare the q values for a given array of character sets and return the + * one with the highest value. If items are found to have the same q value, + * the first one encountered takes precedence. If all items in the given + * array have a q value of 0, FALSE is returned. + * + * @param array character sets + * @return mixed + */ + public static function preferred_charset($charsets) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($charsets as $charset) + { + $q = request::accepts_charset_at_quality($charset); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $charset; + } + } + + return $preferred; + } + + /** + * Compare the q values for a given array of encodings and return the one with + * the highest value. If items are found to have the same q value, the first + * one encountered takes precedence. If all items in the given array have + * a q value of 0, FALSE is returned. + * + * @param array encodings + * @param boolean set to TRUE to disable wildcard checking + * @return mixed + */ + public static function preferred_encoding($encodings, $explicit_check = FALSE) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($encodings as $encoding) + { + $q = request::accepts_encoding_at_quality($encoding, $explicit_check); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $encoding; + } + } + + return $preferred; + } + + /** + * Compare the q values for a given array of language tags and return the + * one with the highest value. If items are found to have the same q value, + * the first one encountered takes precedence. If all items in the given + * array have a q value of 0, FALSE is returned. + * + * @param array language tags + * @param boolean set to TRUE to disable prefix and wildcard checking + * @return mixed + */ + public static function preferred_language($tags, $explicit_check = FALSE) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($tags as $tag) + { + $q = request::accepts_language_at_quality($tag, $explicit_check); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $tag; + } + } + + return $preferred; + } + + /** + * Returns quality factor at which the client accepts content type + * + * @param string content type (e.g. "image/jpg", "jpg") + * @param boolean set to TRUE to disable wildcard checking + * @return integer|float + */ + public static function accepts_at_quality($type, $explicit_check = FALSE) + { + request::parse_accept_content_header(); + + // Normalize type + $type = strtolower($type); + + // General content type (e.g. "jpg") + if (strpos($type, '/') === FALSE) + { + // Don't accept anything by default + $q = 0; + + // Look up relevant mime types + foreach ((array) Kohana::config('mimes.'.$type) as $type) + { + $q2 = request::accepts_at_quality($type, $explicit_check); + $q = ($q2 > $q) ? $q2 : $q; + } + + return $q; + } + + // Content type with subtype given (e.g. "image/jpg") + $type = explode('/', $type, 2); + + // Exact match + if (isset(request::$accept_types[$type[0]][$type[1]])) + return request::$accept_types[$type[0]][$type[1]]; + + if ($explicit_check === FALSE) + { + // Wildcard match + if (isset(request::$accept_types[$type[0]]['*'])) + return request::$accept_types[$type[0]]['*']; + + // Catch-all wildcard match + if (isset(request::$accept_types['*']['*'])) + return request::$accept_types['*']['*']; + } + + // Content type not accepted + return 0; + } + + /** + * Returns quality factor at which the client accepts a charset + * + * @param string charset (e.g., "ISO-8859-1", "utf-8") + * @return integer|float + */ + public static function accepts_charset_at_quality($charset) + { + request::parse_accept_charset_header(); + + // Normalize charset + $charset = strtolower($charset); + + // Exact match + if (isset(request::$accept_charsets[$charset])) + return request::$accept_charsets[$charset]; + + if (isset(request::$accept_charsets['*'])) + return request::$accept_charsets['*']; + + if ($charset === 'iso-8859-1') + return 1; + + return 0; + } + + /** + * Returns quality factor at which the client accepts an encoding + * + * @param string encoding (e.g., "gzip", "deflate") + * @param boolean set to TRUE to disable wildcard checking + * @return integer|float + */ + public static function accepts_encoding_at_quality($encoding, $explicit_check = FALSE) + { + request::parse_accept_encoding_header(); + + // Normalize encoding + $encoding = strtolower($encoding); + + // Exact match + if (isset(request::$accept_encodings[$encoding])) + return request::$accept_encodings[$encoding]; + + if ($explicit_check === FALSE) + { + if (isset(request::$accept_encodings['*'])) + return request::$accept_encodings['*']; + + if ($encoding === 'identity') + return 1; + } + + return 0; + } + + /** + * Returns quality factor at which the client accepts a language + * + * @param string tag (e.g., "en", "en-us", "fr-ca") + * @param boolean set to TRUE to disable prefix and wildcard checking + * @return integer|float + */ + public static function accepts_language_at_quality($tag, $explicit_check = FALSE) + { + request::parse_accept_language_header(); + + $tag = explode('-', strtolower($tag), 2); + + if (isset(request::$accept_languages[$tag[0]])) + { + if (isset($tag[1])) + { + // Exact match + if (isset(request::$accept_languages[$tag[0]][$tag[1]])) + return request::$accept_languages[$tag[0]][$tag[1]]; + + // A prefix matches + if ($explicit_check === FALSE AND isset(request::$accept_languages[$tag[0]]['*'])) + return request::$accept_languages[$tag[0]]['*']; + } + else + { + // No subtags + if (isset(request::$accept_languages[$tag[0]]['*'])) + return request::$accept_languages[$tag[0]]['*']; + } + } + + if ($explicit_check === FALSE AND isset(request::$accept_languages['*'])) + return request::$accept_languages['*']; + + return 0; + } + + /** + * Parses a HTTP Accept or Accept-* header for q values + * + * @param string header data + * @return array + */ + protected static function parse_accept_header($header) + { + $result = array(); + + // Remove linebreaks and parse the HTTP Accept header + foreach (explode(',', str_replace(array("\r", "\n"), '', strtolower($header))) as $entry) + { + // Explode each entry in content type and possible quality factor + $entry = explode(';', trim($entry), 2); + + $q = (isset($entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $entry[1], $match)) ? (float) $match[1] : 1; + + // Overwrite entries with a smaller q value + if ( ! isset($result[$entry[0]]) OR $q > $result[$entry[0]]) + { + $result[$entry[0]] = $q; + } + } + + return $result; + } + + /** + * Parses a client's HTTP Accept-Charset header + */ + protected static function parse_accept_charset_header() + { + // Run this function just once + if (request::$accept_charsets !== NULL) + return; + + // No HTTP Accept-Charset header found + if (empty($_SERVER['HTTP_ACCEPT_CHARSET'])) + { + // Accept everything + request::$accept_charsets['*'] = 1; + } + else + { + request::$accept_charsets = request::parse_accept_header($_SERVER['HTTP_ACCEPT_CHARSET']); + } + } + + /** + * Parses a client's HTTP Accept header + */ + protected static function parse_accept_content_header() + { + // Run this function just once + if (request::$accept_types !== NULL) + return; + + // No HTTP Accept header found + if (empty($_SERVER['HTTP_ACCEPT'])) + { + // Accept everything + request::$accept_types['*']['*'] = 1; + } + else + { + request::$accept_types = array(); + + foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT']) as $type => $q) + { + // Explode each content type (e.g. "text/html") + $type = explode('/', $type, 2); + + // Skip invalid content types + if ( ! isset($type[1])) + continue; + + request::$accept_types[$type[0]][$type[1]] = $q; + } + } + } + + /** + * Parses a client's HTTP Accept-Encoding header + */ + protected static function parse_accept_encoding_header() + { + // Run this function just once + if (request::$accept_encodings !== NULL) + return; + + // No HTTP Accept-Encoding header found + if ( ! isset($_SERVER['HTTP_ACCEPT_ENCODING'])) + { + // Accept everything + request::$accept_encodings['*'] = 1; + } + elseif ($_SERVER['HTTP_ACCEPT_ENCODING'] === '') + { + // Accept only identity + request::$accept_encodings['identity'] = 1; + } + else + { + request::$accept_encodings = request::parse_accept_header($_SERVER['HTTP_ACCEPT_ENCODING']); + } + } + + /** + * Parses a client's HTTP Accept-Language header + */ + protected static function parse_accept_language_header() + { + // Run this function just once + if (request::$accept_languages !== NULL) + return; + + // No HTTP Accept-Language header found + if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + { + // Accept everything + request::$accept_languages['*'] = 1; + } + else + { + request::$accept_languages = array(); + + foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag => $q) + { + // Explode each language (e.g. "en-us") + $tag = explode('-', $tag, 2); + + request::$accept_languages[$tag[0]][isset($tag[1]) ? $tag[1] : '*'] = $q; + } + } + } + +} // End request diff --git a/system/helpers/security.php b/system/helpers/security.php new file mode 100644 index 0000000..7103bd1 --- /dev/null +++ b/system/helpers/security.php @@ -0,0 +1,35 @@ +xss_clean($str, $tool); + } + + /** + * Remove image tags from a string. + * + * @param string string to sanitize + * @return string + */ + public static function strip_image_tags($str) + { + return preg_replace('#\s]*)["\']?[^>]*)?>#is', '$1', $str); + } + +} // End security \ No newline at end of file diff --git a/system/helpers/text.php b/system/helpers/text.php new file mode 100644 index 0000000..f7f040c --- /dev/null +++ b/system/helpers/text.php @@ -0,0 +1,596 @@ + 1) + { + if (ctype_alpha($str)) + { + // Add a random digit + $str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57)); + } + elseif (ctype_digit($str)) + { + // Add a random letter + $str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90)); + } + } + + return $str; + } + + /** + * Reduces multiple slashes in a string to single slashes. + * + * @param string string to reduce slashes of + * @return string + */ + public static function reduce_slashes($str) + { + return preg_replace('#(? $badword) + { + $badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword)); + } + + $regex = '('.implode('|', $badwords).')'; + + if ($replace_partial_words === FALSE) + { + // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself + $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)'; + } + + $regex = '!'.$regex.'!ui'; + + if (mb_strlen($replacement) == 1) + { + $regex .= 'e'; + return preg_replace($regex, 'str_repeat($replacement, mb_strlen(\'$1\'))', $str); + } + + return preg_replace($regex, $replacement, $str); + } + + /** + * Finds the text that is similar between a set of words. + * + * @param array words to find similar text of + * @return string + */ + public static function similar(array $words) + { + // First word is the word to match against + $word = current($words); + + for ($i = 0, $max = strlen($word); $i < $max; ++$i) + { + foreach ($words as $w) + { + // Once a difference is found, break out of the loops + if ( ! isset($w[$i]) OR $w[$i] !== $word[$i]) + break 2; + } + } + + // Return the similar text + return substr($word, 0, $i); + } + + /** + * An alternative to the php levenshtein() function that work out the + * distance between 2 words using the Damerau–Levenshtein algorithm. + * Credit: http://forums.devnetwork.net/viewtopic.php?f=50&t=89094 + * + * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance + * @param string first word + * @param string second word + * @return int distance between words + */ + public static function distance($string1, $string2) + { + $string1_length = strlen($string1); + $string2_length = strlen($string2); + + // Here we start building the table of values + $matrix = array(); + + // String1 length + 1 = rows. + for ($i = 0; $i <= $string1_length; ++$i) + { + $matrix[$i][0] = $i; + } + + // String2 length + 1 columns. + for ($j = 0; $j <= $string2_length; ++$j) + { + $matrix[0][$j] = $j; + } + + for ($i = 1; $i <= $string1_length; ++$i) + { + for ($j = 1; $j <= $string2_length; ++$j) + { + $cost = substr($string1, $i - 1, 1) == substr($string2, $j - 1, 1) ? 0 : 1; + + $matrix[$i][$j] = min( + $matrix[$i - 1][$j] + 1, // deletion + $matrix[$i][$j - 1] + 1, // insertion + $matrix[$i - 1][$j - 1] + $cost // substitution + ); + + if ($i > 1 && $j > 1 && (substr($string1, $i - 1, 1) == substr($string2, $j - 2, 1)) + && (substr($string1, $i - 2, 1) == substr($string2, $j - 1, 1))) + { + $matrix[$i][$j] = min( + $matrix[$i][$j], + $matrix[$i - 2][$j - 2] + $cost // transposition + ); + } + } + } + + return $matrix[$string1_length][$string2_length]; + } + + /** + * Converts text anchors into links. + * + * @param string text to auto link + * @return string + */ + public static function auto_link_urls($text) + { + + $regex = '~\\b' + .'((?:ht|f)tps?://)?' // protocol + .'(?:[-a-zA-Z0-9]{1,63}\.)+' // host name + .'(?:[0-9]{1,3}|aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)' // tlds + .'(?:/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?' // path + .'(?:\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // query + .'(?:#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // fragment + .'(?=[?.!,;:"]?(?:\s|$))~'; // punctuation and url end + + $result = ""; + $position = 0; + + while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $position)) + { + list($url, $url_pos) = $match[0]; + + // Add the text before the url + $result .= substr($text, $position, $url_pos - $position); + + // Default to http:// + $full_url = empty($match[1][0]) ? 'http://'.$url : $url; + + // Add the hyperlink. + $result .= html::anchor($full_url, $url); + + // New position to start parsing + $position = $url_pos + strlen($url); + } + + return $result.substr($text, $position); + } + + /** + * Converts text email addresses into links. + * + * @param string text to auto link + * @return string + */ + public static function auto_link_emails($text) + { + // Finds all email addresses that are not part of an existing html mailto anchor + // Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors + // The html entity for a colon (:) is : or : or : etc. + if (preg_match_all('~\b(?|58;)(?!\.)[-+_a-z0-9.]++(? and
      markup to text. Basically nl2br() on steroids. + * + * @param string subject + * @param boolean convert single linebreaks to
      + * @return string + */ + public static function auto_p($str, $br = TRUE) + { + // Trim whitespace + if (($str = trim($str)) === '') + return ''; + + // Standardize newlines + $str = str_replace(array("\r\n", "\r"), "\n", $str); + + // Trim whitespace on each line + $str = preg_replace('~^[ \t]+~m', '', $str); + $str = preg_replace('~[ \t]+$~m', '', $str); + + // The following regexes only need to be executed if the string contains html + if ($html_found = (strpos($str, '<') !== FALSE)) + { + // Elements that should not be surrounded by p tags + $no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))'; + + // Put at least two linebreaks before and after $no_p elements + $str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str); + $str = preg_replace('~$~im', "$0\n", $str); + } + + // Do the

      magic! + $str = '

      '.trim($str).'

      '; + $str = preg_replace('~\n{2,}~', "

      \n\n

      ", $str); + + // The following regexes only need to be executed if the string contains html + if ($html_found !== FALSE) + { + // Remove p tags around $no_p elements + $str = preg_replace('~

      (?=]*+>)~i', '', $str); + $str = preg_replace('~(]*+>)

      ~i', '$1', $str); + } + + // Convert single linebreaks to
      + if ($br === TRUE) + { + $str = preg_replace('~(?\n", $str); + } + + return $str; + } + + /** + * Returns human readable sizes. + * @see Based on original functions written by: + * @see Aidan Lister: http://aidanlister.com/repos/v/function.size_readable.php + * @see Quentin Zervaas: http://www.phpriot.com/d/code/strings/filesize-format/ + * + * @param integer size in bytes + * @param string a definitive unit + * @param string the return string format + * @param boolean whether to use SI prefixes or IEC + * @return string + */ + public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE) + { + // Format string + $format = ($format === NULL) ? '%01.2f %s' : (string) $format; + + // IEC prefixes (binary) + if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE) + { + $units = array(__('B'), __('KiB'), __('MiB'), __('GiB'), __('TiB'), __('PiB')); + $mod = 1024; + } + // SI prefixes (decimal) + else + { + $units = array(__('B'), __('kB'), __('MB'), __('GB'), __('TB'), __('PB')); + $mod = 1000; + } + + // Determine unit to use + if (($power = array_search((string) $force_unit, $units)) === FALSE) + { + $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0; + } + + return sprintf($format, $bytes / pow($mod, $power), $units[$power]); + } + + /** + * Prevents widow words by inserting a non-breaking space between the last two words. + * @see http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin + * + * @param string string to remove widows from + * @return string + */ + public static function widont($str) + { + $str = rtrim($str); + $space = strrpos($str, ' '); + + if ($space !== FALSE) + { + $str = substr($str, 0, $space).' '.substr($str, $space + 1); + } + + return $str; + } + + /** + * Tests whether a string contains only 7bit ASCII bytes. This is used to + * determine when to use native functions or UTF-8 functions. + * + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to check + * @return bool + */ + public static function is_ascii($str) + { + return is_string($str) AND ! preg_match('/[^\x00-\x7F]/S', $str); + } + + /** + * Strips out device control codes in the ASCII range. + * + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to clean + * @return string + */ + public static function strip_ascii_ctrl($str) + { + return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str); + } + + /** + * Strips out all non-7bit ASCII bytes. + * + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to clean + * @return string + */ + public static function strip_non_ascii($str) + { + return preg_replace('/[^\x00-\x7F]+/S', '', $str); + } + + /** + * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'. + * + * @author Andreas Gohr + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to transliterate + * @param integer -1 lowercase only, +1 uppercase only, 0 both cases + * @return string + */ + public static function transliterate_to_ascii($str, $case = 0) + { + static $UTF8_LOWER_ACCENTS = NULL; + static $UTF8_UPPER_ACCENTS = NULL; + + if ($case <= 0) + { + if ($UTF8_LOWER_ACCENTS === NULL) + { + $UTF8_LOWER_ACCENTS = array( + 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o', + 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k', + 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o', + 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o', + 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c', + 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't', + 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l', + 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z', + 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't', + 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o', + 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j', + 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o', + 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g', + 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a', + 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', 'ı' => 'i', + ); + } + + $str = str_replace( + array_keys($UTF8_LOWER_ACCENTS), + array_values($UTF8_LOWER_ACCENTS), + $str + ); + } + + if ($case >= 0) + { + if ($UTF8_UPPER_ACCENTS === NULL) + { + $UTF8_UPPER_ACCENTS = array( + 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O', + 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E', + 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O', + 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O', + 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C', + 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T', + 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L', + 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z', + 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T', + 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O', + 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J', + 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O', + 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G', + 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A', + 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'İ' => 'I', + ); + } + + $str = str_replace( + array_keys($UTF8_UPPER_ACCENTS), + array_values($UTF8_UPPER_ACCENTS), + $str + ); + } + + return $str; + } + +} // End text \ No newline at end of file diff --git a/system/helpers/upload.php b/system/helpers/upload.php new file mode 100644 index 0000000..62de674 --- /dev/null +++ b/system/helpers/upload.php @@ -0,0 +1,157 @@ + $directory)); + + if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename)) + { + if ($chmod !== FALSE) + { + // Set permissions on filename + chmod($filename, $chmod); + } + + // Return new file path + return $filename; + } + + return FALSE; + } + + /* Validation Rules */ + + /** + * Tests if input data is valid file type, even if no upload is present. + * + * @param array $_FILES item + * @return bool + */ + public static function valid($file) + { + return (is_array($file) + AND isset($file['error']) + AND isset($file['name']) + AND isset($file['type']) + AND isset($file['tmp_name']) + AND isset($file['size'])); + } + + /** + * Tests if input data has valid upload data. + * + * @param array $_FILES item + * @return bool + */ + public static function required(array $file) + { + return (isset($file['tmp_name']) + AND isset($file['error']) + AND is_uploaded_file($file['tmp_name']) + AND (int) $file['error'] === UPLOAD_ERR_OK); + } + + /** + * Validation rule to test if an uploaded file is allowed by extension. + * + * @param array $_FILES item + * @param array allowed file extensions + * @return bool + */ + public static function type(array $file, array $allowed_types) + { + if ((int) $file['error'] !== UPLOAD_ERR_OK) + return TRUE; + + // Get the default extension of the file + $extension = strtolower(substr(strrchr($file['name'], '.'), 1)); + + // Make sure there is an extension and that the extension is allowed + return ( ! empty($extension) AND in_array($extension, $allowed_types)); + } + + /** + * Validation rule to test if an uploaded file is allowed by file size. + * File sizes are defined as: SB, where S is the size (1, 15, 300, etc) and + * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes. + * Eg: to limit the size to 1MB or less, you would use "1M". + * + * @param array $_FILES item + * @param array maximum file size + * @return bool + */ + public static function size(array $file, array $size) + { + if ((int) $file['error'] !== UPLOAD_ERR_OK) + return TRUE; + + // Only one size is allowed + $size = strtoupper($size[0]); + + if ( ! preg_match('/[0-9]++[BKMG]/', $size)) + return FALSE; + + // Make the size into a power of 1024 + switch (substr($size, -1)) + { + case 'G': $size = intval($size) * pow(1024, 3); break; + case 'M': $size = intval($size) * pow(1024, 2); break; + case 'K': $size = intval($size) * pow(1024, 1); break; + default: $size = intval($size); break; + } + + // Test that the file is under or equal to the max size + return ($file['size'] <= $size); + } + +} // End upload \ No newline at end of file diff --git a/system/helpers/url.php b/system/helpers/url.php new file mode 100644 index 0000000..02956fc --- /dev/null +++ b/system/helpers/url.php @@ -0,0 +1,264 @@ + 'Refresh', + '300' => 'Multiple Choices', + '301' => 'Moved Permanently', + '302' => 'Found', + '303' => 'See Other', + '304' => 'Not Modified', + '305' => 'Use Proxy', + '307' => 'Temporary Redirect' + ); + + // Validate the method and default to 302 + $method = isset($codes[$method]) ? (string) $method : '302'; + + if ($method === '300') + { + $uri = (array) $uri; + + $output = '
        '; + foreach ($uri as $link) + { + $output .= '
      • '.html::anchor($link).'
      • '; + } + $output .= '
      '; + + // The first URI will be used for the Location header + $uri = $uri[0]; + } + else + { + $output = '

      '.html::anchor($uri).'

      '; + } + + // Run the redirect event + Event::run('system.redirect', $uri); + + if (strpos($uri, '://') === FALSE) + { + // HTTP headers expect absolute URLs + $uri = url::site($uri, request::protocol()); + } + + if ($method === 'refresh') + { + header('Refresh: 0; url='.$uri); + } + else + { + header('HTTP/1.1 '.$method.' '.$codes[$method]); + header('Location: '.$uri); + } + + // We are about to exit, so run the send_headers event + Event::run('system.send_headers'); + + exit('

      '.$method.' - '.$codes[$method].'

      '.$output); + } + +} // End url \ No newline at end of file diff --git a/system/helpers/utf8.php b/system/helpers/utf8.php new file mode 100644 index 0000000..0123cfb --- /dev/null +++ b/system/helpers/utf8.php @@ -0,0 +1,744 @@ + + * + * @param string input string + * @param string replacement string + * @param integer offset + * @return string + */ + public static function substr_replace($str, $replacement, $offset, $length = NULL) + { + if (text::is_ascii($str)) + return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length); + + $length = ($length === NULL) ? mb_strlen($str) : (int) $length; + preg_match_all('/./us', $str, $str_array); + preg_match_all('/./us', $replacement, $replacement_array); + + array_splice($str_array[0], $offset, $length, $replacement_array[0]); + return implode('', $str_array[0]); + } + + /** + * Makes a UTF-8 string's first character uppercase. + * @see http://php.net/ucfirst + * + * @author Harry Fuecks + * + * @param string mixed case string + * @return string + */ + public static function ucfirst($str) + { + if (text::is_ascii($str)) + return ucfirst($str); + + preg_match('/^(.?)(.*)$/us', $str, $matches); + return mb_strtoupper($matches[1]).$matches[2]; + } + + /** + * Case-insensitive UTF-8 string comparison. + * @see http://php.net/strcasecmp + * + * @author Harry Fuecks + * + * @param string string to compare + * @param string string to compare + * @return integer less than 0 if str1 is less than str2 + * @return integer greater than 0 if str1 is greater than str2 + * @return integer 0 if they are equal + */ + public static function strcasecmp($str1, $str2) + { + if (text::is_ascii($str1) AND text::is_ascii($str2)) + return strcasecmp($str1, $str2); + + $str1 = mb_strtolower($str1); + $str2 = mb_strtolower($str2); + return strcmp($str1, $str2); + } + + /** + * Returns a string or an array with all occurrences of search in subject (ignoring case). + * replaced with the given replace value. + * @see http://php.net/str_ireplace + * + * @note It's not fast and gets slower if $search and/or $replace are arrays. + * @author Harry Fuecks $val) + { + $str[$key] = utf8::str_ireplace($search, $replace, $val, $count); + } + return $str; + } + + if (is_array($search)) + { + $keys = array_keys($search); + + foreach ($keys as $k) + { + if (is_array($replace)) + { + if (array_key_exists($k, $replace)) + { + $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count); + } + else + { + $str = utf8::str_ireplace($search[$k], '', $str, $count); + } + } + else + { + $str = utf8::str_ireplace($search[$k], $replace, $str, $count); + } + } + return $str; + } + + $search = mb_strtolower($search); + $str_lower = mb_strtolower($str); + + $total_matched_strlen = 0; + $i = 0; + + while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches)) + { + $matched_strlen = strlen($matches[0]); + $str_lower = substr($str_lower, $matched_strlen); + + $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1)); + $str = substr_replace($str, $replace, $offset, strlen($search)); + + $total_matched_strlen += $matched_strlen; + $i++; + } + + $count += $i; + return $str; + } + + /** + * Case-insenstive UTF-8 version of strstr. Returns all of input string + * from the first occurrence of needle to the end. + * @see http://php.net/stristr + * + * @author Harry Fuecks + * + * @param string input string + * @param string needle + * @return string matched substring if found + * @return boolean FALSE if the substring was not found + */ + public static function stristr($str, $search) + { + if (text::is_ascii($str) AND text::is_ascii($search)) + return stristr($str, $search); + + if ($search == '') + return $str; + + $str_lower = mb_strtolower($str); + $search_lower = mb_strtolower($search); + + preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches); + + if (isset($matches[1])) + return substr($str, strlen($matches[1])); + + return FALSE; + } + + /** + * Finds the length of the initial segment matching mask. + * @see http://php.net/strspn + * + * @author Harry Fuecks + * + * @param string input string + * @param string mask for search + * @param integer start position of the string to examine + * @param integer length of the string to examine + * @return integer length of the initial segment that contains characters in the mask + */ + public static function strspn($str, $mask, $offset = NULL, $length = NULL) + { + if ($str == '' OR $mask == '') + return 0; + + if (text::is_ascii($str) AND text::is_ascii($mask)) + return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length)); + + if ($offset !== NULL OR $length !== NULL) + { + $str = mb_substr($str, $offset, $length); + } + + // Escape these characters: - [ ] . : \ ^ / + // The . and : are escaped to prevent possible warnings about POSIX regex elements + $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask); + preg_match('/^[^'.$mask.']+/u', $str, $matches); + + return isset($matches[0]) ? mb_strlen($matches[0]) : 0; + } + + /** + * Finds the length of the initial segment not matching mask. + * @see http://php.net/strcspn + * + * @author Harry Fuecks + * + * @param string input string + * @param string mask for search + * @param integer start position of the string to examine + * @param integer length of the string to examine + * @return integer length of the initial segment that contains characters not in the mask + */ + public static function strcspn($str, $mask, $offset = NULL, $length = NULL) + { + if ($str == '' OR $mask == '') + return 0; + + if (text::is_ascii($str) AND text::is_ascii($mask)) + return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length)); + + if ($str !== NULL OR $length !== NULL) + { + $str = mb_substr($str, $offset, $length); + } + + // Escape these characters: - [ ] . : \ ^ / + // The . and : are escaped to prevent possible warnings about POSIX regex elements + $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask); + preg_match('/^[^'.$mask.']+/u', $str, $matches); + + return isset($matches[0]) ? mb_strlen($matches[0]) : 0; + } + + /** + * Pads a UTF-8 string to a certain length with another string. + * @see http://php.net/str_pad + * + * @author Harry Fuecks + * + * @param string input string + * @param integer desired string length after padding + * @param string string to use as padding + * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH + * @return string + */ + public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT) + { + if (text::is_ascii($str) AND text::is_ascii($pad_str)) + { + return str_pad($str, $final_str_length, $pad_str, $pad_type); + } + + $str_length = mb_strlen($str); + + if ($final_str_length <= 0 OR $final_str_length <= $str_length) + { + return $str; + } + + $pad_str_length = mb_strlen($pad_str); + $pad_length = $final_str_length - $str_length; + + if ($pad_type == STR_PAD_RIGHT) + { + $repeat = ceil($pad_length / $pad_str_length); + return mb_substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length); + } + + if ($pad_type == STR_PAD_LEFT) + { + $repeat = ceil($pad_length / $pad_str_length); + return mb_substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str; + } + + if ($pad_type == STR_PAD_BOTH) + { + $pad_length /= 2; + $pad_length_left = floor($pad_length); + $pad_length_right = ceil($pad_length); + $repeat_left = ceil($pad_length_left / $pad_str_length); + $repeat_right = ceil($pad_length_right / $pad_str_length); + + $pad_left = mb_substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left); + $pad_right = mb_substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left); + return $pad_left.$str.$pad_right; + } + + trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR); + } + + /** + * Converts a UTF-8 string to an array. + * @see http://php.net/str_split + * + * @author Harry Fuecks + * + * @param string input string + * @param integer maximum length of each chunk + * @return array + */ + public static function str_split($str, $split_length = 1) + { + $split_length = (int) $split_length; + + if (text::is_ascii($str)) + { + return str_split($str, $split_length); + } + + if ($split_length < 1) + { + return FALSE; + } + + if (mb_strlen($str) <= $split_length) + { + return array($str); + } + + preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches); + + return $matches[0]; + } + + /** + * Reverses a UTF-8 string. + * @see http://php.net/strrev + * + * @author Harry Fuecks + * + * @param string string to be reversed + * @return string + */ + public static function strrev($str) + { + if (text::is_ascii($str)) + return strrev($str); + + preg_match_all('/./us', $str, $matches); + return implode('', array_reverse($matches[0])); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the beginning and + * end of a string. + * @see http://php.net/trim + * + * @author Andreas Gohr + * + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function trim($str, $charlist = NULL) + { + if ($charlist === NULL) + return trim($str); + + return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the beginning of a string. + * @see http://php.net/ltrim + * + * @author Andreas Gohr + * + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function ltrim($str, $charlist = NULL) + { + if ($charlist === NULL) + return ltrim($str); + + if (text::is_ascii($charlist)) + return ltrim($str, $charlist); + + $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist); + + return preg_replace('/^['.$charlist.']+/u', '', $str); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the end of a string. + * @see http://php.net/rtrim + * + * @author Andreas Gohr + * + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function rtrim($str, $charlist = NULL) + { + if ($charlist === NULL) + return rtrim($str); + + if (text::is_ascii($charlist)) + return rtrim($str, $charlist); + + $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist); + + return preg_replace('/['.$charlist.']++$/uD', '', $str); + } + + /** + * Returns the unicode ordinal for a character. + * @see http://php.net/ord + * + * @author Harry Fuecks + * + * @param string UTF-8 encoded character + * @return integer + */ + public static function ord($chr) + { + $ord0 = ord($chr); + + if ($ord0 >= 0 AND $ord0 <= 127) + { + return $ord0; + } + + if ( ! isset($chr[1])) + { + trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING); + return FALSE; + } + + $ord1 = ord($chr[1]); + + if ($ord0 >= 192 AND $ord0 <= 223) + { + return ($ord0 - 192) * 64 + ($ord1 - 128); + } + + if ( ! isset($chr[2])) + { + trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING); + return FALSE; + } + + $ord2 = ord($chr[2]); + + if ($ord0 >= 224 AND $ord0 <= 239) + { + return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128); + } + + if ( ! isset($chr[3])) + { + trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING); + return FALSE; + } + + $ord3 = ord($chr[3]); + + if ($ord0 >= 240 AND $ord0 <= 247) + { + return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128); + } + + if ( ! isset($chr[4])) + { + trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING); + return FALSE; + } + + $ord4 = ord($chr[4]); + + if ($ord0 >= 248 AND $ord0 <= 251) + { + return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128); + } + + if ( ! isset($chr[5])) + { + trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING); + return FALSE; + } + + if ($ord0 >= 252 AND $ord0 <= 253) + { + return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128); + } + + if ($ord0 >= 254 AND $ord0 <= 255) + { + trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING); + return FALSE; + } + } + + /** + * Takes an UTF-8 string and returns an array of ints representing the Unicode characters. + * Astral planes are supported i.e. the ints in the output can be > 0xFFFF. + * Occurrances of the BOM are ignored. Surrogates are not allowed. + * + * The Original Code is Mozilla Communicator client code. + * The Initial Developer of the Original Code is Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. + * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/. + * Slight modifications to fit with phputf8 library by Harry Fuecks . + * + * @param string UTF-8 encoded string + * @return array unicode code points + * @return boolean FALSE if the string is invalid + */ + public static function to_unicode($str) + { + $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + $out = array(); + + $len = strlen($str); + + for ($i = 0; $i < $len; $i++) + { + $in = ord($str[$i]); + + if ($mState == 0) + { + // When mState is zero we expect either a US-ASCII character or a + // multi-octet sequence. + if (0 == (0x80 & $in)) + { + // US-ASCII, pass straight through. + $out[] = $in; + $mBytes = 1; + } + elseif (0xC0 == (0xE0 & $in)) + { + // First octet of 2 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } + elseif (0xE0 == (0xF0 & $in)) + { + // First octet of 3 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } + elseif (0xF0 == (0xF8 & $in)) + { + // First octet of 4 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } + elseif (0xF8 == (0xFC & $in)) + { + // First octet of 5 octet sequence. + // + // This is illegal because the encoded codepoint must be either + // (a) not the shortest form or + // (b) outside the Unicode range of 0-0x10FFFF. + // Rather than trying to resynchronize, we will carry on until the end + // of the sequence and let the later error handling code catch it. + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } + elseif (0xFC == (0xFE & $in)) + { + // First octet of 6 octet sequence, see comments for 5 octet sequence. + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } + else + { + // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence. + trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING); + return FALSE; + } + } + else + { + // When mState is non-zero, we expect a continuation of the multi-octet sequence + if (0x80 == (0xC0 & $in)) + { + // Legal continuation + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output + if (0 == --$mState) + { + // Check for illegal sequences and codepoints + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR + ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR + ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR + (4 < $mBytes) OR + // From Unicode 3.2, surrogate characters are illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) OR + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF)) + { + trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING); + return FALSE; + } + + if (0xFEFF != $mUcs4) + { + // BOM is legal but we don't want to output it + $out[] = $mUcs4; + } + + // Initialize UTF-8 cache + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + } + } + else + { + // ((0xC0 & (*in) != 0x80) AND (mState != 0)) + // Incomplete multi-octet sequence + trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING); + return FALSE; + } + } + } + + return $out; + } + + /** + * Takes an array of ints representing the Unicode characters and returns a UTF-8 string. + * Astral planes are supported i.e. the ints in the input can be > 0xFFFF. + * Occurrances of the BOM are ignored. Surrogates are not allowed. + * + * The Original Code is Mozilla Communicator client code. + * The Initial Developer of the Original Code is Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. + * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/. + * Slight modifications to fit with phputf8 library by Harry Fuecks . + * + * @param array unicode code points representing a string + * @return string utf8 string of characters + * @return boolean FALSE if a code point cannot be found + */ + public static function from_unicode($arr) + { + ob_start(); + + $keys = array_keys($arr); + + foreach ($keys as $k) + { + // ASCII range (including control chars) + if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f)) + { + echo chr($arr[$k]); + } + // 2 byte sequence + elseif ($arr[$k] <= 0x07ff) + { + echo chr(0xc0 | ($arr[$k] >> 6)); + echo chr(0x80 | ($arr[$k] & 0x003f)); + } + // Byte order mark (skip) + elseif ($arr[$k] == 0xFEFF) + { + // nop -- zap the BOM + } + // Test for illegal surrogates + elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF) + { + // Found a surrogate + trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING); + return FALSE; + } + // 3 byte sequence + elseif ($arr[$k] <= 0xffff) + { + echo chr(0xe0 | ($arr[$k] >> 12)); + echo chr(0x80 | (($arr[$k] >> 6) & 0x003f)); + echo chr(0x80 | ($arr[$k] & 0x003f)); + } + // 4 byte sequence + elseif ($arr[$k] <= 0x10ffff) + { + echo chr(0xf0 | ($arr[$k] >> 18)); + echo chr(0x80 | (($arr[$k] >> 12) & 0x3f)); + echo chr(0x80 | (($arr[$k] >> 6) & 0x3f)); + echo chr(0x80 | ($arr[$k] & 0x3f)); + } + // Out of range + else + { + trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING); + return FALSE; + } + } + + $result = ob_get_contents(); + ob_end_clean(); + return $result; + } + +} // End utf8 \ No newline at end of file diff --git a/system/helpers/valid.php b/system/helpers/valid.php new file mode 100644 index 0000000..1f7be22 --- /dev/null +++ b/system/helpers/valid.php @@ -0,0 +1,453 @@ +add_rules('phone', 'required', 'valid::phone[7, 10, 11, 14]') + * + * if ($data->validate()) + * { + * echo 'The phone number is valid'; + * } + * else + * { + * echo Kohana::debug($data->errors()); + * } + * + * [!!] The *valid::* part of the rule is optional, but is recommended to avoid conflicts with php functions. + * + * For more informaiton see the [Validation] Library. + * + * ###### Standalone Example: + * if (valid::phone($_POST['phone'], array(7, 10, 11, 14)) + * { + * echo 'The phone number is valid'; + * } + * else + * { + * echo 'Not valid'; + * } + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class valid_Core { + + /** + * Validate an email address. This method is more strict than valid::email_rfc(); + * + * ###### Example: + * $email = 'bill@gates.com'; + * if (valid::email($email)) + * { + * echo "Valid email"; + * } + * else + * { + * echo "Invalid email"; + * } + * + * @param string A email address + * @return boolean + */ + public static function email($email) + { + return (bool) preg_match('/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?= 0; $i -= 2) + { + // Add up every 2nd digit, starting from the right + $checksum += substr($number, $i, 1); + } + + for ($i = $length - 2; $i >= 0; $i -= 2) + { + // Add up every 2nd digit doubled, starting from the right + $double = substr($number, $i, 1) * 2; + + // Subtract 9 from the double where value is greater than 10 + $checksum += ($double >= 10) ? $double - 9 : $double; + } + + // If the checksum is a multiple of 10, the number is valid + return ($checksum % 10 === 0); + } + + /** + * Checks if a phone number is valid. This function will strip all non-digit + * characters from the phone number for testing. + * + * ###### Example: + * $phone_number = '(201) 664-0274'; + * if (valid::phone($phone_number)) + * { + * echo "Valid phone number"; + * } + * else + * { + * echo "Invalid phone number"; + * } + * + * @param string phone number to check + * @return boolean + */ + public static function phone($number, $lengths = NULL) + { + if ( ! is_array($lengths)) + { + $lengths = array(7,10,11); + } + + // Remove all non-digit characters from the number + $number = preg_replace('/\D+/', '', $number); + + // Check if the number is within range + return in_array(strlen($number), $lengths); + } + + /** + * Tests if a string is a valid date using the php + * [strtotime()](http://php.net/strtotime) function + * + * @param string date to check + * @return boolean + */ + public static function date($str) + { + return (strtotime($str) !== FALSE); + } + + /** + * Checks whether a string consists of alphabetical characters only. + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function alpha($str, $utf8 = FALSE) + { + return ($utf8 === TRUE) + ? (bool) preg_match('/^\pL++$/uD', (string) $str) + : ctype_alpha((string) $str); + } + + /** + * Checks whether a string consists of alphabetical characters and numbers only. + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function alpha_numeric($str, $utf8 = FALSE) + { + return ($utf8 === TRUE) + ? (bool) preg_match('/^[\pL\pN]++$/uD', (string) $str) + : ctype_alnum((string) $str); + } + + /** + * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only. + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function alpha_dash($str, $utf8 = FALSE) + { + return ($utf8 === TRUE) + ? (bool) preg_match('/^[-\pL\pN_]++$/uD', (string) $str) + : (bool) preg_match('/^[-a-z0-9_]++$/iD', (string) $str); + } + + /** + * Checks whether a string consists of digits only (no dots or dashes). + * + * @param string input string + * @param boolean trigger UTF-8 compatibility + * @return boolean + */ + public static function digit($str, $utf8 = FALSE) + { + return ($utf8 === TRUE) + ? (bool) preg_match('/^\pN++$/uD', (string) $str) + : ctype_digit((string) $str); + } + + /** + * Checks whether a string is a valid number (negative and decimal numbers allowed). + * This function uses [localeconv()](http://www.php.net/manual/en/function.localeconv.php) + * to support international number formats. + * + * @param string input string + * @return boolean + */ + public static function numeric($str) + { + // Use localeconv to set the decimal_point value: Usually a comma or period. + $locale = localeconv(); + return (bool) preg_match('/^-?[0-9'.$locale['decimal_point'].']++$/D', (string) $str); + } + + /** + * Tests if an integer is within a range. + * + * @param integer number to check + * @param array valid range of input + * @return boolean + */ + public static function range($number, array $range) + { + // Invalid by default + $status = FALSE; + + if (is_int($number) OR ctype_digit($number)) + { + if (count($range) > 1) + { + if ($number >= $range[0] AND $number <= $range[1]) + { + // Number is within the required range + $status = TRUE; + } + } + elseif ($number >= $range[0]) + { + // Number is greater than the minimum + $status = TRUE; + } + } + + return $status; + } + + /** + * Checks if a string is a proper decimal format. The format array can be + * used to specify a decimal length, or a number and decimal length, eg: + * array(2) would force the number to have 2 decimal places, array(4,2) + * would force the number to have 4 digits and 2 decimal places. + * + * ###### Example: + * $decimal = '4.5'; + * if (valid::decimal($decimal, array(2,1))) + * { + * echo "Valid decimal"; + * } + * else + * { + * echo "Invalid decimal"; + * } + * + * Output: Invalid decimal + * + * @param string input string + * @param array decimal format: y or x,y + * @return boolean + */ + public static function decimal($str, $format = NULL) + { + // Create the pattern + $pattern = '/^[0-9]%s\.[0-9]%s$/'; + + if ( ! empty($format)) + { + if (count($format) > 1) + { + // Use the format for number and decimal length + $pattern = sprintf($pattern, '{'.$format[0].'}', '{'.$format[1].'}'); + } + elseif (count($format) > 0) + { + // Use the format as decimal length + $pattern = sprintf($pattern, '+', '{'.$format[0].'}'); + } + } + else + { + // No format + $pattern = sprintf($pattern, '+', '+'); + } + + return (bool) preg_match($pattern, (string) $str); + } + + /** + * Checks if a string is a proper hexadecimal HTML color value. The validation + * is quite flexible as it does not require an initial "#" and also allows for + * the short notation using only three instead of six hexadecimal characters. + * You may want to normalize these values with format::color(). + * + * @param string input string + * @return boolean + */ + public static function color($str) + { + return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str); + } + +} // End valid \ No newline at end of file diff --git a/system/libraries/Cache.php b/system/libraries/Cache.php new file mode 100644 index 0000000..c7954d3 --- /dev/null +++ b/system/libraries/Cache.php @@ -0,0 +1,248 @@ + $name)); + } + + if (is_array($config)) + { + // Append the default configuration options + $config += Kohana::config('cache.default'); + } + else + { + // Load the default group + $config = Kohana::config('cache.default'); + } + + // Cache the config in the object + $this->config = $config; + + // Set driver name + $driver = 'Cache_'.ucfirst($this->config['driver']).'_Driver'; + + // Load the driver + if ( ! Kohana::auto_load($driver)) + throw new Cache_Exception('The :driver: driver for the :class: library could not be found', + array(':driver:' => $this->config['driver'], ':class:' => get_class($this))); + + // Initialize the driver + $this->driver = new $driver($this->config['params']); + + // Validate the driver + if ( ! ($this->driver instanceof Cache_Driver)) + throw new Cache_Exception('The :driver: driver for the :library: library must implement the :interface: interface', + array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver')); + + Kohana_Log::add('debug', 'Cache Library initialized'); + } + + /** + * Set cache items + */ + public function set($key, $value = NULL, $tags = NULL, $lifetime = NULL) + { + if ($lifetime === NULL) + { + $lifetime = $this->config['lifetime']; + } + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + if ($this->config['prefix'] !== NULL) + { + $key = $this->add_prefix($key); + + if ($tags !== NULL) + { + $tags = $this->add_prefix($tags, FALSE); + } + } + + return $this->driver->set($key, $tags, $lifetime); + } + + /** + * Get a cache items by key + */ + public function get($keys) + { + $single = FALSE; + + if ( ! is_array($keys)) + { + $keys = array($keys); + $single = TRUE; + } + + if ($this->config['prefix'] !== NULL) + { + $keys = $this->add_prefix($keys, FALSE); + + if ( ! $single) + { + return $this->strip_prefix($this->driver->get($keys, $single)); + } + + } + + return $this->driver->get($keys, $single); + } + + /** + * Get cache items by tags + */ + public function get_tag($tags) + { + if ( ! is_array($tags)) + { + $tags = array($tags); + } + + if ($this->config['prefix'] !== NULL) + { + $tags = $this->add_prefix($tags, FALSE); + return $this->strip_prefix($this->driver->get_tag($tags)); + } + else + { + return $this->driver->get_tag($tags); + } + } + + /** + * Delete cache item by key + */ + public function delete($keys) + { + if ( ! is_array($keys)) + { + $keys = array($keys); + } + + if ($this->config['prefix'] !== NULL) + { + $keys = $this->add_prefix($keys, FALSE); + } + + return $this->driver->delete($keys); + } + + /** + * Delete cache items by tag + */ + public function delete_tag($tags) + { + if ( ! is_array($tags)) + { + $tags = array($tags); + } + + if ($this->config['prefix'] !== NULL) + { + $tags = $this->add_prefix($tags, FALSE); + } + + return $this->driver->delete_tag($tags); + } + + /** + * Empty the cache + */ + public function delete_all() + { + return $this->driver->delete_all(); + } + + /** + * Add a prefix to keys or tags + */ + protected function add_prefix($array, $to_key = TRUE) + { + $out = array(); + + foreach($array as $key => $value) + { + if ($to_key) + { + $out[$this->config['prefix'].$key] = $value; + } + else + { + $out[$key] = $this->config['prefix'].$value; + } + } + + return $out; + } + + /** + * Strip a prefix to keys or tags + */ + protected function strip_prefix($array) + { + $out = array(); + + $start = strlen($this->config['prefix']); + + foreach($array as $key => $value) + { + $out[substr($key, $start)] = $value; + } + + return $out; + } + +} // End Cache Library \ No newline at end of file diff --git a/system/libraries/Cache_Exception.php b/system/libraries/Cache_Exception.php new file mode 100644 index 0000000..706dc09 --- /dev/null +++ b/system/libraries/Cache_Exception.php @@ -0,0 +1,11 @@ +config = $config; + + if ($this->config['cache'] !== FALSE) + { + if (is_string($this->config['cache'])) + { + // Use Cache library + $this->cache = new Cache($this->config['cache']); + } + elseif ($this->config['cache'] === TRUE) + { + // Use array + $this->cache = array(); + } + } + } + + public function __destruct() + { + $this->disconnect(); + } + + /** + * Connects to the database + * + * @return void + */ + abstract public function connect(); + + /** + * Disconnects from the database + * + * @return void + */ + abstract public function disconnect(); + + /** + * Sets the character set + * + * @return void + */ + abstract public function set_charset($charset); + + /** + * Executes the query + * + * @param string SQL + * @return Database_Result + */ + abstract public function query_execute($sql); + + /** + * Escapes the given value + * + * @param mixed Value + * @return mixed Escaped value + */ + abstract public function escape($value); + + /** + * List constraints for the given table + * + * @param string Table name + * @return array + */ + abstract public function list_constraints($table); + + /** + * List fields for the given table + * + * @param string Table name + * @return array + */ + abstract public function list_fields($table); + + /** + * List tables for the given connection (checks for prefix) + * + * @return array + */ + abstract public function list_tables(); + + /** + * Converts the given DSN string to an array of database connection components + * + * @param string DSN string + * @return array + */ + public static function parse_dsn($dsn) + { + $db = array + ( + 'type' => FALSE, + 'user' => FALSE, + 'pass' => FALSE, + 'host' => FALSE, + 'port' => FALSE, + 'socket' => FALSE, + 'database' => FALSE + ); + + // Get the protocol and arguments + list ($db['type'], $connection) = explode('://', $dsn, 2); + + if ($connection[0] === '/') + { + // Strip leading slash + $db['database'] = substr($connection, 1); + } + else + { + $connection = parse_url('http://'.$connection); + + if (isset($connection['user'])) + { + $db['user'] = $connection['user']; + } + + if (isset($connection['pass'])) + { + $db['pass'] = $connection['pass']; + } + + if (isset($connection['port'])) + { + $db['port'] = $connection['port']; + } + + if (isset($connection['host'])) + { + if ($connection['host'] === 'unix(') + { + list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2); + } + else + { + $db['host'] = $connection['host']; + } + } + + if (isset($connection['path']) AND $connection['path']) + { + // Strip leading slash + $db['database'] = substr($connection['path'], 1); + } + } + + return $db; + } + + /** + * Returns the last executed query for this database + * + * @return string + */ + public function last_query() + { + return $this->last_query; + } + + /** + * Executes the given query, returning the cached version if enabled + * + * @param string SQL query + * @return Database_Result + */ + public function query($sql) + { + // Start the benchmark + $start = microtime(TRUE); + + if (is_array($this->cache)) + { + $hash = $this->query_hash($sql); + + if (isset($this->cache[$hash])) + { + // Use cached result + $result = $this->cache[$hash]; + + // It's from cache + $sql .= ' [CACHE]'; + } + else + { + // No cache, execute query and store in cache + $result = $this->cache[$hash] = $this->query_execute($sql); + } + } + else + { + // Execute the query, cache is off + $result = $this->query_execute($sql); + } + + // Stop the benchmark + $stop = microtime(TRUE); + + if ($this->config['benchmark'] === TRUE) + { + // Benchmark the query + Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); + } + + return $result; + } + + /** + * Performs the query on the cache (and caches it if it's not found) + * + * @param string query + * @param int time-to-live (NULL for Cache default) + * @return Database_Cache_Result + */ + public function query_cache($sql, $ttl) + { + if ( ! $this->cache instanceof Cache) + { + throw new Database_Exception('Database :name has not been configured to use the Cache library.'); + } + + // Start the benchmark + $start = microtime(TRUE); + + $hash = $this->query_hash($sql); + + if (($data = $this->cache->get($hash)) !== NULL) + { + // Found in cache, create result + $result = new Database_Cache_Result($data, $sql, $this->config['object']); + + // It's from the cache + $sql .= ' [CACHE]'; + } + else + { + // Run the query and return the full array of rows + $data = $this->query_execute($sql)->as_array(TRUE); + + // Set the Cache + $this->cache->set($hash, $data, NULL, $ttl); + + // Create result + $result = new Database_Cache_Result($data, $sql, $this->config['object']); + } + + // Stop the benchmark + $stop = microtime(TRUE); + + if ($this->config['benchmark'] === TRUE) + { + // Benchmark the query + Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); + } + + return $result; + } + + /** + * Generates a hash for the given query + * + * @param string SQL query string + * @return string + */ + protected function query_hash($sql) + { + return sha1(str_replace("\n", ' ', trim($sql))); + } + + /** + * Clears the internal query cache. + * + * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query + * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST + * @return Database + */ + public function clear_cache($sql = NULL, $type = NULL) + { + if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST)) + { + // Using cross-request Cache library + if ($sql === TRUE) + { + $this->cache->delete($this->query_hash($this->last_query)); + } + elseif (is_string($sql)) + { + $this->cache->delete($this->query_hash($sql)); + } + else + { + $this->cache->delete_all(); + } + } + elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST)) + { + // Using per-request memory cache + if ($sql === TRUE) + { + unset($this->cache[$this->query_hash($this->last_query)]); + } + elseif (is_string($sql)) + { + unset($this->cache[$this->query_hash($sql)]); + } + else + { + $this->cache = array(); + } + } + } + + /** + * Quotes the given value + * + * @param mixed value + * @return mixed + */ + public function quote($value) + { + if ( ! $this->config['escape']) + return $value; + + if ($value === NULL) + { + return 'NULL'; + } + elseif ($value === TRUE) + { + return 'TRUE'; + } + elseif ($value === FALSE) + { + return 'FALSE'; + } + elseif (is_int($value)) + { + return (int) $value; + } + elseif ($value instanceof Database_Expression) + { + return (string) $value; + } + elseif (is_float($value)) + { + // Convert to non-locale aware float to prevent possible commas + return sprintf('%F', $value); + } + + return '\''.$this->escape($value).'\''; + } + + /** + * Quotes a table, adding the table prefix + * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk) + * + * @param string|array table name or array - 'users u' or array('u' => 'users') both valid + * @param string table alias + * @return string + */ + public function quote_table($table, $alias = NULL) + { + if (is_array($table)) + { + // Using array('u' => 'user') + list($alias, $table) = each($table); + } + elseif (strpos(' ', $table) !== FALSE) + { + // Using format 'user u' + list($table, $alias) = explode(' ', $table); + } + + if ($table instanceof Database_Expression) + { + if ($alias) + { + if ($this->config['escape']) + { + $alias = $this->quote.$alias.$this->quote; + } + + return $table.' AS '.$alias; + } + + return (string) $table; + } + + if ($this->config['table_prefix']) + { + $table = $this->config['table_prefix'].$table; + } + + if ($alias) + { + if ($this->config['escape']) + { + $table = $this->quote.$table.$this->quote; + $alias = $this->quote.$alias.$this->quote; + } + + return $table.' AS '.$alias; + } + + if ($this->config['escape']) + { + $table = $this->quote.$table.$this->quote; + } + + return $table; + } + + /** + * Quotes column or table.column, adding the table prefix if necessary + * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk) + * Complex column names must have table/columns in double quotes, e.g. array('mycount' => 'COUNT("users.id")') + * + * @param string|array column name or array('u' => 'COUNT("*")') + * @param string column alias + * @return string + */ + public function quote_column($column, $alias = NULL) + { + if ($column === '*') + return $column; + + if (is_array($column)) + { + list($alias, $column) = each($column); + } + + if ($column instanceof Database_Expression) + { + if ($alias) + { + if ($this->config['escape']) + { + $alias = $this->quote.$alias.$this->quote; + } + + return $column.' AS '.$alias; + } + + return (string) $column; + } + + if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE) + { + if (strpos($column, '"') !== FALSE) + { + // Find "table.column" and replace them with "[prefix]table.column" + $column = preg_replace('/"([^.]++)\.([^"]++)"/', '"'.$this->config['table_prefix'].'$1.$2"', $column); + } + else + { + // Attach table prefix if table.column format + $column = $this->config['table_prefix'].$column; + } + } + + if ($this->config['escape']) + { + if (strpos($column, '"') === FALSE) + { + // Quote the column + $column = $this->quote.$column.$this->quote; + } + elseif ($this->quote !== '"') + { + // Replace double quotes + $column = str_replace('"', $this->quote, $column); + } + + // Replace . with "." + $column = str_replace('.', $this->quote.'.'.$this->quote, $column); + + // Unescape any asterisks + $column = str_replace($this->quote.'*'.$this->quote, '*', $column); + + if ($alias) + { + // Quote the alias + return $column.' AS '.$this->quote.$alias.$this->quote; + } + + return $column; + } + + // Strip double quotes + $column = str_replace('"', '', $column); + + if ($alias) + return $column.' AS '.$alias; + + return $column; + } + + /** + * Get the table prefix + * + * @param string Optional new table prefix to set + * @return string + */ + public function table_prefix($new_prefix = NULL) + { + $prefix = $this->config['table_prefix']; + + if ($new_prefix !== NULL) + { + // Set a new prefix + $this->config['table_prefix'] = $new_prefix; + } + + return $prefix; + } + + /** + * Fetches SQL type information about a field, in a generic format. + * + * @param string field datatype + * @return array + */ + protected function sql_type($str) + { + static $sql_types; + + if ($sql_types === NULL) + { + // Load SQL data types + $sql_types = Kohana::config('sql_types'); + } + + $str = trim($str); + + if (($open = strpos($str, '(')) !== FALSE) + { + // Closing bracket + $close = strpos($str, ')', $open); + + // Length without brackets + $length = substr($str, $open + 1, $close - 1 - $open); + + // Type without the length + $type = substr($str, 0, $open).substr($str, $close + 1); + } + else + { + // No length + $type = $str; + } + + if (empty($sql_types[$type])) + throw new Database_Exception('Undefined field type :type', array(':type' => $str)); + + // Fetch the field definition + $field = $sql_types[$type]; + + $field['sql_type'] = $type; + + if (isset($length)) + { + // Add the length to the field info + $field['length'] = $length; + } + + return $field; + } + +} // End Database diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php new file mode 100644 index 0000000..e86ce37 --- /dev/null +++ b/system/libraries/Database_Builder.php @@ -0,0 +1,1241 @@ +select() + * ->where('name', '=', 'Kohana') + * ->from('frameworks') + * ->execute(); + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Builder_Core { + + // Valid ORDER BY directions + protected $order_directions = array('ASC', 'DESC', 'RAND()'); + + // Database object + protected $db; + + // Builder members + protected $select = array(); + protected $from = array(); + protected $join = array(); + protected $where = array(); + protected $group_by = array(); + protected $having = array(); + protected $order_by = array(); + protected $limit = NULL; + protected $offset = NULL; + protected $set = array(); + protected $columns = array(); + protected $values = array(); + protected $type; + protected $distinct = FALSE; + protected $reset = TRUE; + + // TTL for caching (using Cache library) + protected $ttl = FALSE; + + public function __construct($db = 'default') + { + $this->db = $db; + } + + /** + * Compiles the builder object into a SQL query. Useful for debugging + * + * ##### Example + * + * echo $builder->select()->from('products'); + * // Output: SELECT * FROM `products` + * + * @return string Compiled query + */ + public function __toString() + { + return $this->compile(); + } + + /** + * Creates a `SELECT` query with support for column aliases, database functions, + * subqueries or a [Database_Expression] + * + * ##### Examples + * + * // Simple select + * echo $builder->select()->from('products'); + * + * // Select with database function + * echo $builder->select(array('records_found' => 'COUNT("*")'))->from('products'); + * + * // Select with sub query + * echo $builder->select(array('field', 'test' => db::select('test')->from('table')))->from('products'); + * + * @chainable + * @param string|array column name or array(alias => column) + * @return Database_Builder + */ + public function select($columns = NULL) + { + $this->type = Database::SELECT; + + if ($columns === NULL) + { + $columns = array('*'); + } + elseif ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->select = array_merge($this->select, $columns); + + return $this; + } + + /** + * Creates a `DISTINCT SELECT` query. For more information see see [Database_Builder::select]. + * + * @chainable + * @param string|array column name or array(alias => column) + * @return Database_Builder + */ + public function select_distinct($columns = NULL) + { + $this->select($columns); + $this->distinct = TRUE; + return $this; + } + + /** + * Add tables to the FROM portion of the builder + * + * ##### Example + * + * $builder->select()->from('products') + * ->from(array('other' => 'other_table')); + * // Output: SELECT * FROM `products`, `other_table` AS `other` + * + * @chainable + * @param string|array table name or array(alias => table) + * @return Database_Builder + */ + public function from($tables) + { + if ( ! is_array($tables)) + { + $tables = func_get_args(); + } + + $this->from = array_merge($this->from, $tables); + + return $this; + } + + /** + * Add conditions to the `WHERE` clause. Alias for [Database_Builder::and_where]. + * + * @chainable + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function where($columns, $op = '=', $value = NULL) + { + return $this->and_where($columns, $op, $value); + } + + /** + * Add conditions to the `WHERE` clause separating multiple conditions with `AND`. + * This function supports all `WHERE` operators including `LIKE` and `IN`. It can + * also be used with a [Database_Expression] or subquery. + * + * ##### Examples + * + * // Basic where condition + * $builder->where('field', '=', 'value'); + * + * // Multiple conditions with an array (you can also chain where() function calls) + * $builder->where(array(array('field', '=', 'value'), array(...))); + * + * // With a database expression + * $builder->where('field', '=', db::expr('field + 1')); + * // or a function + * $builder->where('field', '=', db::expr('UNIX_TIMESTAMP()')); + * + * // With a subquery + * $builder->where('field', 'IN', db::select('id')->from('table')); + * + * [!!] You must manually escape all data you pass into a database expression! + * + * @chainable + * @param mixed Column name or array of triplets + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function and_where($columns, $op = '=', $value = NULL) + { + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->where[] = array('AND' => $column); + } + } + else + { + $this->where[] = array('AND' => array($columns, $op, $value)); + } + return $this; + } + + /** + * Add conditions to the `WHERE` clause separating multiple conditions with `OR`. + * For more information about building a `WHERE` clause see [Database_Builder::and_where] + * + * @chainable + * @param mixed Column name or array of triplets + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function or_where($columns, $op = '=', $value = NULL) + { + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->where[] = array('OR' => $column); + } + } + else + { + $this->where[] = array('OR' => array($columns, $op, $value)); + } + return $this; + } + + /** + * Join tables to the builder + * + * ##### Example + * + * // Basic join + * db::select()->from('products') + * ->join('reviews', 'reviews.product_id', 'products.id'); + * + * // Advanced joins + * echo db::select()->from('products') + * ->join('reviews', 'field', db::expr('advanced condition here'), 'RIGHT'); + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @param string Join type (LEFT, RIGHT, INNER, etc.) + * @return Database_Builder + */ + public function join($table, $keys, $value = NULL, $type = NULL) + { + if (is_string($keys)) + { + $keys = array($keys => $value); + } + + if ($type !== NULL) + { + $type = strtoupper($type); + } + + $this->join[] = array($table, $keys, $type); + + return $this; + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `LEFT`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function left_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'LEFT'); + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `RIGHT`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function right_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'RIGHT'); + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `INNER`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'INNER'); + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `OUTER`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function outer_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'OUTER'); + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `FULL`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function full_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'FULL'); + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `LEFT INNER`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function left_inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'LEFT INNER'); + } + + /** + * This function is an alias for [Database_Builder::join] + * with the join type set to `RIGHT INNER`. + * + * @chainable + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @return Database_Builder + */ + public function right_inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'RIGHT INNER'); + } + + /** + * Add fields to the GROUP BY portion + * + * ##### Example + * + * db::select()->from('products') + * ->group_by(array('name', 'cat_id')); + * // Output: SELECT * FROM `products` GROUP BY `name`, `cat_id` + * + * @chainable + * @param mixed Field names or an array of fields + * @return Database_Builder + */ + public function group_by($columns) + { + if ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->group_by = array_merge($this->group_by, $columns); + + return $this; + } + + /** + * Add conditions to the HAVING clause (AND) + * + * @chainable + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function having($columns, $op = '=', $value = NULL) + { + return $this->and_having($columns, $op, $value); + } + + /** + * Add conditions to the HAVING clause (AND) + * + * @chainable + * @param mixed Column name or array of triplets + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function and_having($columns, $op = '=', $value = NULL) + { + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->having[] = array('AND' => $column); + } + } + else + { + $this->having[] = array('AND' => array($columns, $op, $value)); + } + return $this; + } + + /** + * Add conditions to the HAVING clause (OR) + * + * @chainable + * @param mixed Column name or array of triplets + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function or_having($columns, $op = '=', $value = NULL) + { + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->having[] = array('OR' => $column); + } + } + else + { + $this->having[] = array('OR' => array($columns, $op, $value)); + } + return $this; + } + + /** + * Add fields to the ORDER BY portion + * + * @chainable + * @param mixed Field names or an array of fields (field => direction) + * @param string Direction or NULL for ascending + * @return Database_Builder + */ + public function order_by($columns, $direction = NULL) + { + if (is_array($columns)) + { + foreach ($columns as $column => $direction) + { + if (is_string($column)) + { + $this->order_by[] = array($column => $direction); + } + else + { + // $direction is the column name when the array key is numeric + $this->order_by[] = array($direction => NULL); + } + } + } + else + { + $this->order_by[] = array($columns => $direction); + } + return $this; + } + + /** + * Limit rows returned + * + * @chainable + * @param int Number of rows + * @return Database_Builder + */ + public function limit($number) + { + $this->limit = (int) $number; + + return $this; + } + + /** + * Offset into result set + * + * @chainable + * @param int Offset + * @return Database_Builder + */ + public function offset($number) + { + $this->offset = (int) $number; + + return $this; + } + + /** + * Alias for [Database_Builder::and_open] + * + * @chainable + * @param string Clause (WHERE OR HAVING) + * @return Database_Builder + */ + public function open($clause = 'WHERE') + { + return $this->and_open($clause); + } + + /** + * Open new **ANDs** parenthesis set + * + * @chainable + * @param string Clause (WHERE OR HAVING) + * @return Database_Builder + */ + public function and_open($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array('AND' => '('); + } + else + { + $this->having[] = array('AND' => '('); + } + + return $this; + } + + /** + * Open new **OR** parenthesis set + * + * @chainable + * @param string Clause (WHERE OR HAVING) + * @return Database_Builder + */ + public function or_open($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array('OR' => '('); + } + else + { + $this->having[] = array('OR' => '('); + } + + return $this; + } + + /** + * Close close parenthesis set + * + * @chainable + * @param string Clause (WHERE OR HAVING) + * @return Database_Builder + */ + public function close($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array(')'); + } + else + { + $this->having[] = array(')'); + } + + return $this; + } + + /** + * Set values for UPDATE + * + * @chainable + * @param mixed Column name or array of columns => vals + * @param mixed Value (can be a Database_Expression) + * @return Database_Builder + */ + public function set($keys, $value = NULL) + { + if (is_string($keys)) + { + $keys = array($keys => $value); + } + + $this->set = array_merge($keys, $this->set); + + return $this; + } + + /** + * Columns used for INSERT queries + * + * @chainable + * @param array Columns + * @return Database_Builder + */ + public function columns($columns) + { + if ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->columns = $columns; + + return $this; + } + + /** + * Values used for INSERT queries + * + * @chainable + * @param array Values + * @return Database_Builder + */ + public function values($values) + { + if ( ! is_array($values)) + { + $values = func_get_args(); + } + + $this->values[] = $values; + + return $this; + } + + /** + * Set caching for the query + * + * @chainable + * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise) + * @return Database_Builder + */ + public function cache($ttl = NULL) + { + $this->ttl = $ttl; + + return $this; + } + + /** + * Resets the database builder after execution. By default after you `execute()` a query + * the database builder will reset to its default state. You can use `reset(FALSE)` + * to stop this from happening. This is useful for pagination when you might want to + * apply a limit to the previous query. + * + * ##### Example + * + * $db = new Database_Builder; + * $all_results = $db->select() + * ->where('id', '=', 3) + * ->from('products') + * ->reset(FALSE) + * ->execute(); + * + * // Run the query again with a limit of 10 + * $ten_results = $db->limit(10) + * ->execute(); + * @chainable + * @param bool reset builder + * @return Database_Builder + */ + public function reset($reset = TRUE) + { + $this->reset = (bool) $reset; + return $this; + } + + /** + * Compiles the given clause's conditions + * + * @param array Clause conditions + * @return string + */ + protected function compile_conditions($groups) + { + $last_condition = NULL; + + $sql = ''; + foreach ($groups as $group) + { + // Process groups of conditions + foreach ($group as $logic => $condition) + { + if ($condition === '(') + { + if ( ! empty($sql) AND $last_condition !== '(') + { + // Include logic operator + $sql .= ' '.$logic.' '; + } + + $sql .= '('; + } + elseif ($condition === ')') + { + $sql .= ')'; + } + else + { + list($columns, $op, $value) = $condition; + + // Stores each individual condition + $vals = array(); + + if ($columns instanceof Database_Expression) + { + // Add directly to condition list + $vals[] = (string) $columns; + } + else + { + $op = strtoupper($op); + + if ( ! is_array($columns)) + { + $columns = array($columns => $value); + } + + foreach ($columns as $column => $value) + { + if ($value instanceof Database_Builder) + { + // Using a subquery + $value->db = $this->db; + $value = '('.(string) $value.')'; + } + elseif (is_array($value)) + { + if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN') + { + // Falls between two values + $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]); + } + else + { + // Return as list + $value = array_map(array($this->db, 'quote'), $value); + $value = '('.implode(', ', $value).')'; + } + } + else + { + $value = $this->db->quote($value); + } + + if ( ! empty($column)) + { + // Ignore blank columns + $column = $this->db->quote_column($column); + } + + // Add to condition list + $vals[] = $column.' '.$op.' '.$value; + } + } + + if ( ! empty($sql) AND $last_condition !== '(') + { + // Add the logic operator + $sql .= ' '.$logic.' '; + } + + // Join the condition list items together by the given logic operator + $sql .= implode(' '.$logic.' ', $vals); + } + + $last_condition = $condition; + } + } + + return $sql; + } + + /** + * Compiles the columns portion of the query for INSERT + * + * @return string + */ + protected function compile_columns() + { + return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')'; + } + + /** + * Compiles the VALUES portion of the query for INSERT + * + * @return string + */ + protected function compile_values() + { + $values = array(); + foreach ($this->values as $group) + { + // Each set of values to be inserted + $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')'; + } + + return implode(', ', $values); + } + + /** + * Create an UPDATE query + * + * @chainable + * @param string Table name + * @param array Array of Keys => Values + * @param array WHERE conditions + * @return Database_Builder + */ + public function update($table = NULL, $set = NULL, $where = NULL) + { + $this->type = Database::UPDATE; + + if (is_array($set)) + { + $this->set($set); + } + + if ($where !== NULL) + { + $this->where($where); + } + + if ($table !== NULL) + { + $this->from($table); + } + + return $this; + } + + /** + * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts + * + * @chainable + * @param string Table name + * @param array Array of Keys => Values + * @return Database_Builder + */ + public function insert($table = NULL, $set = NULL) + { + $this->type = Database::INSERT; + + if (is_array($set)) + { + $this->columns(array_keys($set)); + $this->values(array_values($set)); + } + + if ($table !== NULL) + { + $this->from($table); + } + + return $this; + } + + /** + * Create a DELETE query + * + * @chainable + * @param string Table name + * @param array WHERE conditions + * @return Database_Builder + */ + public function delete($table, $where = NULL) + { + $this->type = Database::DELETE; + + if ($where !== NULL) + { + $this->where($where); + } + + if ($table !== NULL) + { + $this->from($table); + } + + return $this; + } + + /** + * Count records for a given table + * + * @param string Table name + * @param array WHERE conditions + * @return int + */ + public function count_records($table = FALSE, $where = NULL) + { + if (count($this->from) < 1) + { + if ($table === FALSE) + throw new Database_Exception('Database count_records requires a table'); + + $this->from($table); + } + + if ($where !== NULL) + { + $this->where($where); + } + + // Grab the count AS records_found + $result = $this->select(array('records_found' => 'COUNT("*")'))->execute(); + + return $result->get('records_found'); + } + + /** + * Executes the built query + * + * @param mixed Database name or object + * @return Database_Result + */ + public function execute($db = NULL) + { + if ($db !== NULL) + { + $this->db = $db; + } + + if ( ! is_object($this->db)) + { + // Get the database instance + $this->db = Database::instance($this->db); + } + + $query = $this->compile(); + + if ($this->reset) + { + // Reset the query after executing + $this->_reset(); + } + + if ($this->ttl !== FALSE AND $this->type === Database::SELECT) + { + // Return result from cache (only allowed with SELECT) + return $this->db->query_cache($query, $this->ttl); + } + else + { + // Load the result (no caching) + return $this->db->query($query); + } + } + + /** + * Compiles the builder object into a SQL query + * + * @return string Compiled query + */ + protected function compile() + { + if ( ! is_object($this->db)) + { + // Use default database for compiling to string if none is given + $this->db = Database::instance($this->db); + } + + if ($this->type === Database::SELECT) + { + // SELECT columns FROM table + $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT '; + $sql .= $this->compile_select(); + + if ( ! empty($this->from)) + { + $sql .= "\nFROM ".$this->compile_from(); + } + } + elseif ($this->type === Database::UPDATE) + { + $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set(); + } + elseif ($this->type === Database::INSERT) + { + $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values(); + } + elseif ($this->type === Database::DELETE) + { + $sql = 'DELETE FROM '.$this->compile_from(); + } + + if ( ! empty($this->join)) + { + $sql .= $this->compile_join(); + } + + if ( ! empty($this->where)) + { + $sql .= "\n".'WHERE '.$this->compile_conditions($this->where); + } + + if ( ! empty($this->group_by)) + { + $sql .= "\n".'GROUP BY '.$this->compile_group_by(); + } + + if ( ! empty($this->having)) + { + $sql .= "\n".'HAVING '.$this->compile_conditions($this->having); + } + + if ( ! empty($this->order_by)) + { + $sql .= "\nORDER BY ".$this->compile_order_by(); + } + + if (is_int($this->limit)) + { + $sql .= "\nLIMIT ".$this->limit; + } + + if (is_int($this->offset)) + { + $sql .= "\nOFFSET ".$this->offset; + } + + return $sql; + } + + /** + * Compiles the SELECT portion of the query + * + * @return string + */ + protected function compile_select() + { + $vals = array(); + + foreach ($this->select as $alias => $name) + { + if ($name instanceof Database_Builder) + { + // Using a subquery + $name->db = $this->db; + $vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias); + } + elseif (is_string($alias)) + { + $vals[] = $this->db->quote_column($name, $alias); + } + else + { + $vals[] = $this->db->quote_column($name); + } + } + + return implode(', ', $vals); + } + + /** + * Compiles the FROM portion of the query + * + * @return string + */ + protected function compile_from() + { + $vals = array(); + + foreach ($this->from as $alias => $name) + { + if (is_string($alias)) + { + // Using AS format so escape both + $vals[] = $this->db->quote_table($name, $alias); + } + else + { + // Just using the table name itself + $vals[] = $this->db->quote_table($name); + } + } + + return implode(', ', $vals); + } + + /** + * Compiles the JOIN portion of the query + * + * @return string + */ + protected function compile_join() + { + $sql = ''; + foreach ($this->join as $join) + { + list($table, $keys, $type) = $join; + + if ($type !== NULL) + { + // Join type + $sql .= ' '.$type; + } + + $sql .= ' JOIN '.$this->db->quote_table($table); + + $condition = ''; + if ($keys instanceof Database_Expression) + { + $condition = (string) $keys; + } + elseif (is_array($keys)) + { + // ON condition is an array of matches + foreach ($keys as $key => $value) + { + if ( ! empty($condition)) + { + $condition .= ' AND '; + } + + $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value); + } + } + + if ( ! empty($condition)) + { + // Add ON condition + $sql .= ' ON ('.$condition.')'; + } + } + + return $sql; + } + + /** + * Compiles the GROUP BY portion of the query + * + * @return string + */ + protected function compile_group_by() + { + $vals = array(); + + foreach ($this->group_by as $column) + { + // Escape the column + $vals[] = $this->db->quote_column($column); + } + + return implode(', ', $vals); + } + + /** + * Compiles the ORDER BY portion of the query + * + * @return string + */ + protected function compile_order_by() + { + $ordering = array(); + + foreach ($this->order_by as $column => $order_by) + { + list($column, $direction) = each($order_by); + + $column = $this->db->quote_column($column); + + if ($direction !== NULL) + { + $direction = ' '.$direction; + } + + $ordering[] = $column.$direction; + } + + return implode(', ', $ordering); + } + + /** + * Compiles the SET portion of the query for UPDATE + * + * @return string + */ + protected function compile_set() + { + $vals = array(); + + foreach ($this->set as $key => $value) + { + // Using an UPDATE so Key = Val + $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value); + } + + return implode(', ', $vals); + } + + /** + * Resets all query components + */ + protected function _reset() + { + $this->select = array(); + $this->from = array(); + $this->join = array(); + $this->where = array(); + $this->group_by = array(); + $this->having = array(); + $this->order_by = array(); + $this->limit = NULL; + $this->offset = NULL; + $this->set = array(); + $this->values = array(); + $this->type = NULL; + $this->distinct = FALSE; + $this->reset = TRUE; + $this->ttl = FALSE; + } + +} // End Database_Builder diff --git a/system/libraries/Database_Cache_Result.php b/system/libraries/Database_Cache_Result.php new file mode 100644 index 0000000..3945c42 --- /dev/null +++ b/system/libraries/Database_Cache_Result.php @@ -0,0 +1,81 @@ +data = $data; + $this->sql = $sql; + $this->total_rows = count($data); + $this->return_objects = $return_objects; + } + + public function __destruct() + { + // Not used + } + + public function as_array($return = FALSE) + { + // Return arrays rather than objects + $this->return_objects = FALSE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return the entire array of rows + return $this->data; + } + + public function as_object($class = NULL, $return = FALSE) + { + if ($class !== NULL) + throw new Database_Exception('Database cache results do not support object casting'); + + // Return objects of type $class (or stdClass if none given) + $this->return_objects = TRUE; + + return $this; + } + + public function seek($offset) + { + if ( ! $this->offsetExists($offset)) + return FALSE; + + $this->current_row = $offset; + + return TRUE; + } + + public function current() + { + if ($this->return_objects) + { + // Return a new object with the current row of data + return (object) $this->data[$this->current_row]; + } + else + { + // Return an array of the row + return $this->data[$this->current_row]; + } + } + +} // End Database_Cache_Result \ No newline at end of file diff --git a/system/libraries/Database_Exception.php b/system/libraries/Database_Exception.php new file mode 100644 index 0000000..0f6bb75 --- /dev/null +++ b/system/libraries/Database_Exception.php @@ -0,0 +1,15 @@ +expression = $expression; + } + + public function __toString() + { + return $this->expression; + } +} diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php new file mode 100644 index 0000000..a325cbc --- /dev/null +++ b/system/libraries/Database_Mysql.php @@ -0,0 +1,226 @@ +connection) + return; + + if (Database_Mysql::$set_names === NULL) + { + // Determine if we can use mysql_set_charset(), which is only + // available on PHP 5.2.3+ when compiled against MySQL 5.0+ + Database_Mysql::$set_names = ! function_exists('mysql_set_charset'); + } + + extract($this->config['connection']); + + $host = isset($host) ? $host : $socket; + $port = isset($port) ? ':'.$port : ''; + + try + { + // Connect to the database + $this->connection = ($this->config['persistent'] === TRUE) + ? mysql_pconnect($host.$port, $user, $pass, $params) + : mysql_connect($host.$port, $user, $pass, TRUE, $params); + } + catch (Kohana_PHP_Exception $e) + { + // No connection exists + $this->connection = NULL; + + // Unable to connect to the database + throw new Database_Exception('#:errno: :error', + array(':error' => mysql_error(), + ':errno' => mysql_errno())); + } + + if ( ! mysql_select_db($database, $this->connection)) + { + // Unable to select database + throw new Database_Exception('#:errno: :error', + array(':error' => mysql_error($this->connection), + ':errno' => mysql_errno($this->connection))); + } + + if (isset($this->config['character_set'])) + { + // Set the character set + $this->set_charset($this->config['character_set']); + } + } + + public function disconnect() + { + try + { + // Database is assumed disconnected + $status = TRUE; + + if (is_resource($this->connection)) + { + $status = mysql_close($this->connection); + } + } + catch (Exception $e) + { + // Database is probably not disconnected + $status = is_resource($this->connection); + } + + return $status; + } + + public function set_charset($charset) + { + // Make sure the database is connected + $this->connection or $this->connect(); + + if (Database_Mysql::$set_names === TRUE) + { + // PHP is compiled against MySQL 4.x + $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->connection); + } + else + { + // PHP is compiled against MySQL 5.x + $status = mysql_set_charset($charset, $this->connection); + } + + if ($status === FALSE) + { + // Unable to set charset + throw new Database_Exception('#:errno: :error', + array(':error' => mysql_error($this->connection), + ':errno' => mysql_errno($this->connection))); + } + } + + public function query_execute($sql) + { + // Make sure the database is connected + $this->connection or $this->connect(); + + $result = mysql_query($sql, $this->connection); + + // Set the last query + $this->last_query = $sql; + + return new Database_Mysql_Result($result, $sql, $this->connection, $this->config['object']); + } + + public function escape($value) + { + // Make sure the database is connected + $this->connection or $this->connect(); + + if (($value = mysql_real_escape_string($value, $this->connection)) === FALSE) + { + throw new Database_Exception('#:errno: :error', + array(':error' => mysql_error($this->connection), + ':errno' => mysql_errno($this->connection))); + } + + return $value; + } + + public function list_constraints($table) + { + $prefix = strlen($this->table_prefix()); + $result = array(); + + $constraints = $this->query(' + SELECT c.constraint_name, c.constraint_type, k.column_name, k.referenced_table_name, k.referenced_column_name + FROM information_schema.table_constraints c + JOIN information_schema.key_column_usage k ON (k.table_schema = c.table_schema AND k.table_name = c.table_name AND k.constraint_name = c.constraint_name) + WHERE c.table_schema = '.$this->quote($this->config['connection']['database']).' + AND c.table_name = '.$this->quote($this->table_prefix().$table).' + AND (k.referenced_table_schema IS NULL OR k.referenced_table_schema ='.$this->quote($this->config['connection']['database']).') + ORDER BY k.ordinal_position + '); + + foreach ($constraints->as_array() as $row) + { + switch ($row['constraint_type']) + { + case 'FOREIGN KEY': + if (isset($result[$row['constraint_name']])) + { + $result[$row['constraint_name']][1][] = $row['column_name']; + $result[$row['constraint_name']][3][] = $row['referenced_column_name']; + } + else + { + $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']), substr($row['referenced_table_name'], $prefix), array($row['referenced_column_name'])); + } + break; + case 'PRIMARY KEY': + case 'UNIQUE': + if (isset($result[$row['constraint_name']])) + { + $result[$row['constraint_name']][1][] = $row['column_name']; + } + else + { + $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name'])); + } + break; + } + } + + return $result; + } + + public function list_fields($table) + { + $result = array(); + + foreach ($this->query('SHOW COLUMNS FROM '.$this->quote_table($table))->as_array() as $row) + { + $column = $this->sql_type($row['Type']); + + $column['default'] = $row['Default']; + $column['nullable'] = $row['Null'] === 'YES'; + $column['sequenced'] = $row['Extra'] === 'auto_increment'; + + if (isset($column['length']) AND $column['type'] === 'float') + { + list($column['precision'], $column['scale']) = explode(',', $column['length']); + } + + $result[$row['Field']] = $column; + } + + return $result; + } + + public function list_tables() + { + $prefix = strlen($this->table_prefix()); + $tables = array(); + + foreach ($this->query('SHOW TABLES LIKE '.$this->quote($this->table_prefix().'%'))->as_array() as $row) + { + // The value is the table name + $tables[] = substr(current($row), $prefix); + } + + return $tables; + } + +} // End Database_MySQL diff --git a/system/libraries/Database_Mysql_Result.php b/system/libraries/Database_Mysql_Result.php new file mode 100644 index 0000000..0f89898 --- /dev/null +++ b/system/libraries/Database_Mysql_Result.php @@ -0,0 +1,174 @@ +return_objects = $return_objects; + + $this->total_rows = mysql_num_rows($result); + } + elseif (is_bool($result)) + { + if ($result == FALSE) + { + throw new Database_Exception('#:errno: :error [ :query ]', + array(':error' => mysql_error($link), + ':query' => $sql, + ':errno' => mysql_errno($link))); + + } + else + { + // It's a DELETE, INSERT, REPLACE, or UPDATE query + $this->insert_id = mysql_insert_id($link); + $this->total_rows = mysql_affected_rows($link); + } + } + + // Store the result locally + $this->result = $result; + + $this->sql = $sql; + } + + public function __destruct() + { + if (is_resource($this->result)) + { + mysql_free_result($this->result); + } + } + + public function as_array($return = FALSE) + { + // Return arrays rather than objects + $this->return_objects = FALSE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + mysql_data_seek($this->result, 0); + + while ($row = mysql_fetch_assoc($this->result)) + { + // Add each row to the array + $array[] = $row; + } + + $this->internal_row = $this->total_rows; + } + + return $array; + } + + public function as_object($class = NULL, $return = FALSE) + { + // Return objects of type $class (or stdClass if none given) + $this->return_objects = ($class !== NULL) ? $class : TRUE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + mysql_data_seek($this->result, 0); + + if (is_string($this->return_objects)) + { + while ($row = mysql_fetch_object($this->result, $this->return_objects)) + { + // Add each row to the array + $array[] = $row; + } + } + else + { + while ($row = mysql_fetch_object($this->result)) + { + // Add each row to the array + $array[] = $row; + } + } + + $this->internal_row = $this->total_rows; + } + + return $array; + } + + /** + * SeekableIterator: seek + */ + public function seek($offset) + { + if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset)) + { + // Set the current row to the offset + $this->current_row = $this->internal_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Iterator: current + */ + public function current() + { + if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row)) + return NULL; + + ++$this->internal_row; + + if ($this->return_objects) + { + if (is_string($this->return_objects)) + { + return mysql_fetch_object($this->result, $this->return_objects); + } + else + { + return mysql_fetch_object($this->result); + } + } + else + { + // Return an array of the row + return mysql_fetch_assoc($this->result); + } + } + +} // End Database_MySQL_Result \ No newline at end of file diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php new file mode 100644 index 0000000..41b635d --- /dev/null +++ b/system/libraries/Database_Mysqli.php @@ -0,0 +1,90 @@ +connection)) + return; + + extract($this->config['connection']); + + // Persistent connections are supported as of PHP 5.3 + if (RUNS_MYSQLND AND $this->config['persistent'] === TRUE) + { + $host = 'p:'.$host; + } + + $host = isset($host) ? $host : $socket; + + $mysqli = mysqli_init(); + + if ( ! $mysqli->real_connect($host, $user, $pass, $database, $port, $socket, $params)) + throw new Database_Exception('#:errno: :error', + array(':error' => $mysqli->connect_error, ':errno' => $mysqli->connect_errno)); + + $this->connection = $mysqli; + + if (isset($this->config['character_set'])) + { + // Set the character set + $this->set_charset($this->config['character_set']); + } + } + + public function disconnect() + { + if (is_object($this->connection)) + { + $this->connection->close(); + } + + $this->connection = NULL; + } + + public function set_charset($charset) + { + // Make sure the database is connected + is_object($this->connection) or $this->connect(); + + if ( ! $this->connection->set_charset($charset)) + { + // Unable to set charset + throw new Database_Exception('#:errno: :error', + array(':error' => $this->connection->connect_error, + ':errno' => $this->connection->connect_errno)); + } + } + + public function query_execute($sql) + { + // Make sure the database is connected + is_object($this->connection) or $this->connect(); + + $result = $this->connection->query($sql); + + // Set the last query + $this->last_query = $sql; + + return new Database_Mysqli_Result($result, $sql, $this->connection, $this->config['object']); + } + + public function escape($value) + { + // Make sure the database is connected + is_object($this->connection) or $this->connect(); + + return $this->connection->real_escape_string($value); + } + +} // End Database_MySQLi diff --git a/system/libraries/Database_Mysqli_Result.php b/system/libraries/Database_Mysqli_Result.php new file mode 100644 index 0000000..f8b7b58 --- /dev/null +++ b/system/libraries/Database_Mysqli_Result.php @@ -0,0 +1,175 @@ +return_objects = $return_objects; + + $this->total_rows = $result->num_rows; + } + elseif (is_bool($result)) + { + if ($result == FALSE) + { + throw new Database_Exception('#:errno: :error [ :query ]', + array(':error' => $link->error, + ':query' => $sql, + ':errno' => $link->errno)); + } + else + { + // It's a DELETE, INSERT, REPLACE, or UPDATE query + $this->insert_id = $link->insert_id; + $this->total_rows = $link->affected_rows; + } + } + + // Store the result locally + $this->result = $result; + + $this->sql = $sql; + } + + public function __destruct() + { + if (is_object($this->result)) + { + $this->result->free(); + } + } + + public function as_array($return = FALSE) + { + // Return arrays rather than objects + $this->return_objects = FALSE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + if (RUNS_MYSQLND) + return $this->result->fetch_all(MYSQLI_ASSOC); + + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + $this->result->data_seek(0); + + while ($row = $this->result->fetch_assoc()) + { + // Add each row to the array + $array[] = $row; + } + $this->internal_row = $this->total_rows; + } + + return $array; + } + + public function as_object($class = NULL, $return = FALSE) + { + // Return objects of type $class (or stdClass if none given) + $this->return_objects = ($class !== NULL) ? $class : TRUE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + $this->result->data_seek(0); + + if (is_string($this->return_objects)) + { + while ($row = $this->result->fetch_object($this->return_objects)) + { + // Add each row to the array + $array[] = $row; + } + } + else + { + while ($row = $this->result->fetch_object()) + { + // Add each row to the array + $array[] = $row; + } + } + + $this->internal_row = $this->total_rows; + } + + return $array; + } + + /** + * SeekableIterator: seek + */ + public function seek($offset) + { + if ($this->offsetExists($offset) AND $this->result->data_seek($offset)) + { + // Set the current row to the offset + $this->current_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Iterator: current + */ + public function current() + { + if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row)) + return NULL; + + ++$this->internal_row; + + if ($this->return_objects) + { + if (is_string($this->return_objects)) + { + return $this->result->fetch_object($this->return_objects); + } + else + { + return $this->result->fetch_object(); + } + } + else + { + // Return an array of the row + return $this->result->fetch_assoc(); + } + } + +} // End Database_MySQLi_Result \ No newline at end of file diff --git a/system/libraries/Database_Query.php b/system/libraries/Database_Query.php new file mode 100644 index 0000000..d9399d6 --- /dev/null +++ b/system/libraries/Database_Query.php @@ -0,0 +1,95 @@ +sql = $sql; + } + + public function __toString() + { + // Return the SQL of this query + return $this->sql; + } + + public function sql($sql) + { + $this->sql = $sql; + + return $this; + } + + public function value($key, $value) + { + $this->params[$key] = $value; + + return $this; + } + + public function bind($key, & $value) + { + $this->params[$key] =& $value; + + return $this; + } + + public function execute($db = 'default') + { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + + // Import the SQL locally + $sql = $this->sql; + + if ( ! empty($this->params)) + { + // Quote all of the values + $params = array_map(array($db, 'quote'), $this->params); + + // Replace the values in the SQL + $sql = strtr($sql, $params); + } + + if ($this->ttl !== FALSE) + { + // Load the result from the cache + return $db->query_cache($sql, $this->ttl); + } + else + { + // Load the result (no caching) + return $db->query($sql); + } + } + + /** + * Set caching for the query + * + * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise) + * @return Database_Query + */ + public function cache($ttl = NULL) + { + $this->ttl = $ttl; + + return $this; + } + +} // End Database_Query \ No newline at end of file diff --git a/system/libraries/Database_Result.php b/system/libraries/Database_Result.php new file mode 100644 index 0000000..cf2056f --- /dev/null +++ b/system/libraries/Database_Result.php @@ -0,0 +1,170 @@ +insert_id; + } + + /** + * Return the named column from the current row. + * + * @param string Column name + * @return mixed + */ + public function get($name) + { + // Get the current row + $row = $this->current(); + + if ( ! $this->return_objects) + return $row[$name]; + + return $row->$name; + } + + /** + * Countable: count + */ + public function count() + { + return $this->total_rows; + } + + /** + * ArrayAccess: offsetExists + */ + public function offsetExists($offset) + { + return ($offset >= 0 AND $offset < $this->total_rows); + } + + /** + * ArrayAccess: offsetGet + */ + public function offsetGet($offset) + { + if ( ! $this->seek($offset)) + return NULL; + + return $this->current(); + } + + /** + * ArrayAccess: offsetSet + * + * @throws Kohana_Database_Exception + */ + final public function offsetSet($offset, $value) + { + throw new Kohana_Exception('Database results are read-only'); + } + + /** + * ArrayAccess: offsetUnset + * + * @throws Kohana_Database_Exception + */ + final public function offsetUnset($offset) + { + throw new Kohana_Exception('Database results are read-only'); + } + + /** + * Iterator: key + */ + public function key() + { + return $this->current_row; + } + + /** + * Iterator: next + */ + public function next() + { + ++$this->current_row; + return $this; + } + + /** + * Iterator: prev + */ + public function prev() + { + --$this->current_row; + return $this; + } + + /** + * Iterator: rewind + */ + public function rewind() + { + $this->current_row = 0; + return $this; + } + + /** + * Iterator: valid + */ + public function valid() + { + return $this->offsetExists($this->current_row); + } + +} // End Database_Result \ No newline at end of file diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php new file mode 100644 index 0000000..15d4087 --- /dev/null +++ b/system/libraries/Encrypt.php @@ -0,0 +1,176 @@ + $name)); + } + + if (is_array($config)) + { + // Append the default configuration options + $config += Kohana::config('encryption.default'); + } + else + { + // Load the default group + $config = Kohana::config('encryption.default'); + } + + if (empty($config['key'])) + throw new Kohana_Exception('To use the Encrypt library, you must set an encryption key in your config file'); + + // Find the max length of the key, based on cipher and mode + $size = mcrypt_get_key_size($config['cipher'], $config['mode']); + + if (strlen($config['key']) > $size) + { + // Shorten the key to the maximum size + $config['key'] = substr($config['key'], 0, $size); + } + + // Find the initialization vector size + $config['iv_size'] = mcrypt_get_iv_size($config['cipher'], $config['mode']); + + // Cache the config in the object + $this->config = $config; + + Kohana_Log::add('debug', 'Encrypt Library initialized'); + } + + /** + * Encrypts a string and returns an encrypted string that can be decoded. + * + * @param string data to be encrypted + * @return string encrypted data + */ + public function encode($data) + { + // Set the rand type if it has not already been set + if (Encrypt::$rand === NULL) + { + if (KOHANA_IS_WIN) + { + // Windows only supports the system random number generator + Encrypt::$rand = MCRYPT_RAND; + } + else + { + if (defined('MCRYPT_DEV_URANDOM')) + { + // Use /dev/urandom + Encrypt::$rand = MCRYPT_DEV_URANDOM; + } + elseif (defined('MCRYPT_DEV_RANDOM')) + { + // Use /dev/random + Encrypt::$rand = MCRYPT_DEV_RANDOM; + } + else + { + // Use the system random number generator + Encrypt::$rand = MCRYPT_RAND; + } + } + } + + if (Encrypt::$rand === MCRYPT_RAND) + { + // The system random number generator must always be seeded each + // time it is used, or it will not produce true random results + mt_srand(); + } + + // Create a random initialization vector of the proper size for the current cipher + $iv = mcrypt_create_iv($this->config['iv_size'], Encrypt::$rand); + + // Encrypt the data using the configured options and generated iv + $data = mcrypt_encrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv); + + // Use base64 encoding to convert to a string + return base64_encode($iv.$data); + } + + /** + * Decrypts an encoded string back to its original value. + * + * @param string encoded string to be decrypted + * @return string decrypted data or FALSE if decryption fails + */ + public function decode($data) + { + // Convert the data back to binary + $data = base64_decode($data, TRUE); + + if ( ! $data) + { + // Invalid base64 data + return FALSE; + } + + // Extract the initialization vector from the data + $iv = substr($data, 0, $this->config['iv_size']); + + if ($this->config['iv_size'] !== strlen($iv)) + { + // The iv is not the correct size + return FALSE; + } + + // Remove the iv from the data + $data = substr($data, $this->config['iv_size']); + + // Return the decrypted data, trimming the \0 padding bytes from the end of the data + return rtrim(mcrypt_decrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv), "\0"); + } + +} // End Encrypt diff --git a/system/libraries/I18n.php b/system/libraries/I18n.php new file mode 100644 index 0000000..9401ddc --- /dev/null +++ b/system/libraries/I18n.php @@ -0,0 +1,100 @@ + 'gif', + IMAGETYPE_JPEG => 'jpg', + IMAGETYPE_PNG => 'png', + IMAGETYPE_TIFF_II => 'tiff', + IMAGETYPE_TIFF_MM => 'tiff', + ); + + // Driver instance + protected $driver; + + // Driver actions + protected $actions = array(); + + // Reference to the current image filename + protected $image = ''; + + /** + * Creates a new Image instance and returns it. + * + * @param string filename of image + * @param array non-default configurations + * @return object + */ + public static function factory($image, $config = NULL) + { + return new Image($image, $config); + } + + /** + * Creates a new image editor instance. + * + * @throws Kohana_Exception + * @param string filename of image + * @param array non-default configurations + * @return void + */ + public function __construct($image, $config = NULL) + { + static $check; + + // Make the check exactly once + ($check === NULL) and $check = function_exists('getimagesize'); + + if ($check === FALSE) + throw new Kohana_Exception('The Image library requires the getimagesize() PHP function, which is not available in your installation.'); + + // Check to make sure the image exists + if ( ! is_file($image)) + throw new Kohana_Exception('The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.', array(':image:' => $image)); + + // Disable error reporting, to prevent PHP warnings + $ER = error_reporting(0); + + // Fetch the image size and mime type + $image_info = getimagesize($image); + + // Turn on error reporting again + error_reporting($ER); + + // Make sure that the image is readable and valid + if ( ! is_array($image_info) OR count($image_info) < 3) + throw new Kohana_Exception('The file specified, :file:, is not readable or is not an image', array(':file:' => $image)); + + // Check to make sure the image type is allowed + if ( ! isset(Image::$allowed_types[$image_info[2]])) + throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image)); + + // Image has been validated, load it + $this->image = array + ( + 'file' => str_replace('\\', '/', realpath($image)), + 'width' => $image_info[0], + 'height' => $image_info[1], + 'type' => $image_info[2], + 'ext' => Image::$allowed_types[$image_info[2]], + 'mime' => $image_info['mime'] + ); + + $this->determine_orientation(); + + // Load configuration + $this->config = (array) $config + Kohana::config('image'); + + // Set driver class name + $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver'; + + // Load the driver + if ( ! Kohana::auto_load($driver)) + throw new Kohana_Exception('The :driver: driver for the :library: library could not be found', + array(':driver:' => $this->config['driver'], ':library:' => get_class($this))); + + // Initialize the driver + $this->driver = new $driver($this->config['params']); + + // Validate the driver + if ( ! ($this->driver instanceof Image_Driver)) + throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface', + array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Image_Driver')); + } + + /** + * Works out the correct orientation for the image + * + * @return void + */ + protected function determine_orientation() + { + switch (TRUE) + { + case $this->image['height'] > $this->image['width']: + $orientation = Image::PORTRAIT; + break; + + case $this->image['height'] < $this->image['width']: + $orientation = Image::LANDSCAPE; + break; + + default: + $orientation = Image::SQUARE; + } + + $this->image['orientation'] = $orientation; + } + + /** + * Handles retrieval of pre-save image properties + * + * @param string property name + * @return mixed + */ + public function __get($property) + { + if (isset($this->image[$property])) + { + return $this->image[$property]; + } + else + { + throw new Kohana_Exception('The :property: property does not exist in the :class: class.', + array(':property:' => $property, ':class:' => get_class($this))); + } + } + + /** + * Resize an image to a specific width and height. By default, Kohana will + * maintain the aspect ratio using the width as the master dimension. If you + * wish to use height as master dim, set $image->master_dim = Image::HEIGHT + * This method is chainable. + * + * @throws Kohana_Exception + * @param integer width + * @param integer height + * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT + * @return object + */ + public function resize($width, $height, $master = NULL) + { + if ( ! $this->valid_size('width', $width)) + throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width)); + + if ( ! $this->valid_size('height', $height)) + throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height)); + + if (empty($width) AND empty($height)) + throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__)); + + if ($master === NULL) + { + // Maintain the aspect ratio by default + $master = Image::AUTO; + } + elseif ( ! $this->valid_size('master', $master)) + throw new Kohana_Exception('The master dimension specified is not valid.'); + + $this->actions['resize'] = array + ( + 'width' => $width, + 'height' => $height, + 'master' => $master, + ); + + $this->determine_orientation(); + + return $this; + } + + /** + * Crop an image to a specific width and height. You may also set the top + * and left offset. + * This method is chainable. + * + * @throws Kohana_Exception + * @param integer width + * @param integer height + * @param integer top offset, pixel value or one of: top, center, bottom + * @param integer left offset, pixel value or one of: left, center, right + * @return object + */ + public function crop($width, $height, $top = 'center', $left = 'center') + { + if ( ! $this->valid_size('width', $width)) + throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width)); + + if ( ! $this->valid_size('height', $height)) + throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height)); + + if ( ! $this->valid_size('top', $top)) + throw new Kohana_Exception('The top offset you specified, :top:, is not valid.', array(':top:' => $top)); + + if ( ! $this->valid_size('left', $left)) + throw new Kohana_Exception('The left offset you specified, :left:, is not valid.', array(':left:' => $left)); + + if (empty($width) AND empty($height)) + throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__)); + + $this->actions['crop'] = array + ( + 'width' => $width, + 'height' => $height, + 'top' => $top, + 'left' => $left, + ); + + $this->determine_orientation(); + + return $this; + } + + /** + * Allows rotation of an image by 180 degrees clockwise or counter clockwise. + * + * @param integer degrees + * @return object + */ + public function rotate($degrees) + { + $degrees = (int) $degrees; + + if ($degrees > 180) + { + do + { + // Keep subtracting full circles until the degrees have normalized + $degrees -= 360; + } + while($degrees > 180); + } + + if ($degrees < -180) + { + do + { + // Keep adding full circles until the degrees have normalized + $degrees += 360; + } + while($degrees < -180); + } + + $this->actions['rotate'] = $degrees; + + return $this; + } + + /** + * Overlay a second image on top of this one. + * + * @throws Kohana_Exception + * @param string $overlay_file path to an image file + * @param integer $x x offset for the overlay + * @param integer $y y offset for the overlay + * @param integer $transparency transparency percent + */ + public function composite($overlay_file, $x, $y, $transparency) + { + $image_info = getimagesize($overlay_file); + + // Check to make sure the image type is allowed + if ( ! isset(Image::$allowed_types[$image_info[2]])) + throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $overlay_file)); + + $this->actions['composite'] = array + ( + 'overlay_file' => $overlay_file, + 'mime' => $image_info['mime'], + 'x' => $x, + 'y' => $y, + 'transparency' => $transparency + ); + + return $this; + } + + /** + * Flip an image horizontally or vertically. + * + * @throws Kohana_Exception + * @param integer direction + * @return object + */ + public function flip($direction) + { + if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL) + throw new Kohana_Exception('The flip direction specified is not valid.'); + + $this->actions['flip'] = $direction; + + return $this; + } + + /** + * Change the quality of an image. + * + * @param integer quality as a percentage + * @return object + */ + public function quality($amount) + { + $this->actions['quality'] = max(1, min($amount, 100)); + + return $this; + } + + /** + * Sharpen an image. + * + * @param integer amount to sharpen, usually ~20 is ideal + * @return object + */ + public function sharpen($amount) + { + $this->actions['sharpen'] = max(1, min($amount, 100)); + + return $this; + } + + /** + * Save the image to a new image or overwrite this image. + * + * @throws Kohana_Exception + * @param string new image filename + * @param integer permissions for new image + * @param boolean keep or discard image process actions + * @return object + */ + public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE, $background = NULL) + { + // If no new image is defined, use the current image + empty($new_image) and $new_image = $this->image['file']; + + // Separate the directory and filename + $dir = pathinfo($new_image, PATHINFO_DIRNAME); + $file = pathinfo($new_image, PATHINFO_BASENAME); + + // Normalize the path + $dir = str_replace('\\', '/', realpath($dir)).'/'; + + if ( ! is_writable($dir)) + throw new Kohana_Exception('The specified directory, :dir:, is not writable.', array(':dir:' => $dir)); + + if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background)) + { + if ($chmod !== FALSE) + { + // Set permissions + chmod($new_image, $chmod); + } + } + + if ($keep_actions !== TRUE) + { + // Reset actions. Subsequent save() or render() will not apply previous actions. + $this->actions = array(); + } + + return $status; + } + + /** + * Output the image to the browser. + * + * @param boolean keep or discard image process actions + * @return object + */ + public function render($keep_actions = FALSE, $background = NULL) + { + $new_image = $this->image['file']; + + // Separate the directory and filename + $dir = pathinfo($new_image, PATHINFO_DIRNAME); + $file = pathinfo($new_image, PATHINFO_BASENAME); + + // Normalize the path + $dir = str_replace('\\', '/', realpath($dir)).'/'; + + // Process the image with the driver + $status = $this->driver->process($this->image, $this->actions, $dir, $file, TRUE, $background); + + if ($keep_actions !== TRUE) + { + // Reset actions. Subsequent save() or render() will not apply previous actions. + $this->actions = array(); + } + + return $status; + } + + /** + * Sanitize a given value type. + * + * @param string type of property + * @param mixed property value + * @return boolean + */ + protected function valid_size($type, & $value) + { + if (is_null($value)) + return TRUE; + + if ( ! is_scalar($value)) + return FALSE; + + switch ($type) + { + case 'width': + case 'height': + if (is_string($value) AND ! ctype_digit($value)) + { + // Only numbers and percent signs + if ( ! preg_match('/^[0-9]++%$/D', $value)) + return FALSE; + } + else + { + $value = (int) $value; + } + break; + case 'top': + if (is_string($value) AND ! ctype_digit($value)) + { + if ( ! in_array($value, array('top', 'bottom', 'center'))) + return FALSE; + } + else + { + $value = (int) $value; + } + break; + case 'left': + if (is_string($value) AND ! ctype_digit($value)) + { + if ( ! in_array($value, array('left', 'right', 'center'))) + return FALSE; + } + else + { + $value = (int) $value; + } + break; + case 'master': + if ($value !== Image::NONE AND + $value !== Image::AUTO AND + $value !== Image::WIDTH AND + $value !== Image::HEIGHT) + return FALSE; + break; + } + + return TRUE; + } + +} // End Image \ No newline at end of file diff --git a/system/libraries/Input.php b/system/libraries/Input.php new file mode 100644 index 0000000..fa984a8 --- /dev/null +++ b/system/libraries/Input.php @@ -0,0 +1,507 @@ +use_xss_clean = (bool) Kohana::config('core.global_xss_filtering'); + + if (Input::$instance === NULL) + { + // magic_quotes_runtime is enabled + if (get_magic_quotes_runtime()) + { + @set_magic_quotes_runtime(0); + Kohana_Log::add('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes'); + } + + // magic_quotes_gpc is enabled + if (get_magic_quotes_gpc()) + { + $this->magic_quotes_gpc = TRUE; + Kohana_Log::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes'); + } + + if (is_array($_GET)) + { + foreach ($_GET as $key => $val) + { + // Sanitize $_GET + $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val); + } + } + else + { + $_GET = array(); + } + + if (is_array($_POST)) + { + foreach ($_POST as $key => $val) + { + // Sanitize $_POST + $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val); + } + } + else + { + $_POST = array(); + } + + if (is_array($_COOKIE)) + { + foreach ($_COOKIE as $key => $val) + { + // Ignore special attributes in RFC2109 compliant cookies + if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain') + continue; + + // Sanitize $_COOKIE + $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val); + } + } + else + { + $_COOKIE = array(); + } + + // Create a singleton + Input::$instance = $this; + + Kohana_Log::add('debug', 'Global GET, POST and COOKIE data sanitized'); + } + } + + /** + * Fetch an item from the $_GET array. + * + * @param string key to find + * @param mixed default value + * @param boolean XSS clean the value + * @return mixed + */ + public function get($key = array(), $default = NULL, $xss_clean = FALSE) + { + return $this->search_array($_GET, $key, $default, $xss_clean); + } + + /** + * Fetch an item from the $_POST array. + * + * @param string key to find + * @param mixed default value + * @param boolean XSS clean the value + * @return mixed + */ + public function post($key = array(), $default = NULL, $xss_clean = FALSE) + { + return $this->search_array($_POST, $key, $default, $xss_clean); + } + + /** + * Fetch an item from the cookie::get() ($_COOKIE won't work with signed + * cookies.) + * + * @param string key to find + * @param mixed default value + * @param boolean XSS clean the value + * @return mixed + */ + public function cookie($key = array(), $default = NULL, $xss_clean = FALSE) + { + return $this->search_array(cookie::get(), $key, $default, $xss_clean); + } + + /** + * Fetch an item from the $_SERVER array. + * + * @param string key to find + * @param mixed default value + * @param boolean XSS clean the value + * @return mixed + */ + public function server($key = array(), $default = NULL, $xss_clean = FALSE) + { + return $this->search_array($_SERVER, $key, $default, $xss_clean); + } + + /** + * Fetch an item from a global array. + * + * @param array array to search + * @param string key to find + * @param mixed default value + * @param boolean XSS clean the value + * @return mixed + */ + protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE) + { + if ($key === array()) + return $array; + + if ( ! isset($array[$key])) + return $default; + + // Get the value + $value = $array[$key]; + + if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE) + { + // XSS clean the value + $value = $this->xss_clean($value); + } + + return $value; + } + + /** + * Fetch the IP Address. + * + * @return string + */ + public function ip_address() + { + if ($this->ip_address !== NULL) + return $this->ip_address; + + // Server keys that could contain the client IP address + $keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'); + + foreach ($keys as $key) + { + if ($ip = $this->server($key)) + { + $this->ip_address = $ip; + + // An IP address has been found + break; + } + } + + if ($comma = strrpos($this->ip_address, ',') !== FALSE) + { + $this->ip_address = substr($this->ip_address, $comma + 1); + } + + if ( ! valid::ip($this->ip_address)) + { + // Use an empty IP + $this->ip_address = '0.0.0.0'; + } + + return $this->ip_address; + } + + /** + * Clean cross site scripting exploits from string. + * HTMLPurifier may be used if installed, otherwise defaults to built in method. + * Note - This function should only be used to deal with data upon submission. + * It's not something that should be used for general runtime processing + * since it requires a fair amount of processing overhead. + * + * @param string data to clean + * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method) + * @return string + */ + public function xss_clean($data, $tool = NULL) + { + if ($tool === NULL) + { + // Use the default tool + $tool = Kohana::config('core.global_xss_filtering'); + } + + if (is_array($data)) + { + foreach ($data as $key => $val) + { + $data[$key] = $this->xss_clean($val, $tool); + } + + return $data; + } + + // Do not clean empty strings + if (trim($data) === '') + return $data; + + if (is_bool($tool)) + { + $tool = 'default'; + } + elseif ( ! method_exists($this, 'xss_filter_'.$tool)) + { + Kohana_Log::add('error', 'Unable to use Input::xss_filter_'.$tool.'(), no such method exists'); + $tool = 'default'; + } + + $method = 'xss_filter_'.$tool; + + return $this->$method($data); + } + + /** + * Default built-in cross site scripting filter. + * + * @param string data to clean + * @return string + */ + protected function xss_filter_default($data) + { + // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php + // +----------------------------------------------------------------------+ + // | Copyright (c) 2001-2006 Bitflux GmbH | + // +----------------------------------------------------------------------+ + // | 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. | + // +----------------------------------------------------------------------+ + // | Author: Christian Stocker | + // +----------------------------------------------------------------------+ + // + // Kohana Modifications: + // * Changed double quotes to single quotes, changed indenting and spacing + // * Removed magic_quotes stuff + // * Increased regex readability: + // * Used delimeters that aren't found in the pattern + // * Removed all unneeded escapes + // * Deleted U modifiers and swapped greediness where needed + // * Increased regex speed: + // * Made capturing parentheses non-capturing where possible + // * Removed parentheses where possible + // * Split up alternation alternatives + // * Made some quantifiers possessive + // + // Gallery Modifications: + // * Wrap the loop around all the changes to detect nested exploits + + do + { + $old_data = $data; + + // Fix &entity\n; + $data = str_replace(array('&','<','>'), array('&amp;','&lt;','&gt;'), $data); + $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data); + $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data); + $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8'); + + // Remove any attribute starting with "on" or xmlns + $data = preg_replace('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $data); + + // Remove javascript: and vbscript: protocols + $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data); + $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data); + $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data); + + //remove any style attributes, IE allows too much stupid things in them, eg. + // + // and in general you really don't want style declarations in your UGC + $data = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])style[^>]*>#iUu', "$1>", $data); + + // Remove namespaced elements (we do not need them) + $data = preg_replace('#]*+>#i', '', $data); + + // Remove really unwanted tags + $data = preg_replace('#]*+>#i', '', $data); + } + while ($old_data !== $data); + + return $data; + } + + /** + * HTMLPurifier cross site scripting filter. This version assumes the + * existence of the "Standalone Distribution" htmlpurifier library, and is set to not tidy + * input. + * + * @param string data to clean + * @return string + */ + protected function xss_filter_htmlpurifier($data) + { + /** + * @todo License should go here, http://htmlpurifier.org/ + */ + if ( ! class_exists('HTMLPurifier_Config', FALSE)) + { + // Load HTMLPurifier + require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.standalone', TRUE); + } + + // Set configuration + $config = HTMLPurifier_Config::createDefault(); + $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now + + $cache = Kohana::config('html_purifier.cache'); + + if ($cache AND is_string($cache)) + { + $config->set('Cache.SerializerPath', $cache); + } + + // Run HTMLPurifier + $data = HTMLPurifier::instance($config)->purify($data); + + return $data; + } + + /** + * This is a helper method. It enforces W3C specifications for allowed + * key name strings, to prevent malicious exploitation. + * + * @param string string to clean + * @return string + */ + public function clean_input_keys($str) + { + if ( ! preg_match('#^[\pL0-9:_.-]++$#uD', $str)) + { + exit('Disallowed key characters in global data.'); + } + + return $str; + } + + /** + * This is a helper method. It escapes data and forces all newline + * characters to "\n". + * + * @param unknown_type string to clean + * @return string + */ + public function clean_input_data($str) + { + if (is_array($str)) + { + $new_array = array(); + foreach ($str as $key => $val) + { + // Recursion! + $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val); + } + return $new_array; + } + + if ($this->magic_quotes_gpc === TRUE) + { + // Remove annoying magic quotes + $str = stripslashes($str); + } + + if ($this->use_xss_clean === TRUE) + { + $str = $this->xss_clean($str); + } + + if (strpos($str, "\r") !== FALSE) + { + // Standardize newlines + $str = str_replace(array("\r\n", "\r"), "\n", $str); + } + + return $str; + } + + /** + * Recursively cleans arrays, objects, and strings. Removes ASCII control + * codes and converts to UTF-8 while silently discarding incompatible + * UTF-8 characters. + * + * @param string string to clean + * @return string + */ + public static function clean($str) + { + if (is_array($str) OR is_object($str)) + { + foreach ($str as $key => $val) + { + // Recursion! + $str[Input::clean($key)] = Input::clean($val); + } + } + elseif (is_string($str) AND $str !== '') + { + // Remove control characters + $str = text::strip_ascii_ctrl($str); + + if ( ! text::is_ascii($str)) + { + // Disable notices + $ER = error_reporting(~E_NOTICE); + + // iconv is expensive, so it is only used when needed + $str = iconv(Kohana::CHARSET, Kohana::CHARSET.'//IGNORE', $str); + + // Turn notices back on + error_reporting($ER); + } + } + + return $str; + } + +} // End Input Class diff --git a/system/libraries/Kohana_404_Exception.php b/system/libraries/Kohana_404_Exception.php new file mode 100644 index 0000000..7bb7708 --- /dev/null +++ b/system/libraries/Kohana_404_Exception.php @@ -0,0 +1,56 @@ + $page)); + } + + /** + * Throws a new 404 exception. + * + * @throws Kohana_404_Exception + * @return void + */ + public static function trigger($page = NULL) + { + throw new Kohana_404_Exception($page); + } + + /** + * Sends 404 headers, to emulate server behavior. + * + * @return void + */ + public function sendHeaders() + { + // Send the 404 header + header('HTTP/1.1 404 File Not Found'); + } + +} // End Kohana 404 Exception \ No newline at end of file diff --git a/system/libraries/Kohana_Log.php b/system/libraries/Kohana_Log.php new file mode 100644 index 0000000..5126013 --- /dev/null +++ b/system/libraries/Kohana_Log.php @@ -0,0 +1,90 @@ + $driver)); + + // Initialize the driver + $driver = new $driver(array_merge(Kohana::config('log'), Kohana::config('log_'.$driver_name))); + + // Validate the driver + if ( ! ($driver instanceof Log_Driver)) + throw new Kohana_Exception('%driver% does not implement the Log_Driver interface', array('%driver%' => $driver)); + + Kohana_Log::$drivers[] = $driver; + } + + // Always save logs on shutdown + Event::add('system.shutdown', array('Kohana_Log', 'save')); + } + + Kohana_Log::$messages[] = array('date' => time(), 'type' => $type, 'message' => $message); + } + + /** + * Save all currently logged messages. + * + * @return void + */ + public static function save() + { + if (empty(Kohana_Log::$messages)) + return; + + foreach (Kohana_Log::$drivers as $driver) + { + // We can't throw exceptions here or else we will get a + // Exception thrown without a stack frame error + try + { + $driver->save(Kohana_Log::$messages); + } + catch(Exception $e){} + } + + // Reset the messages + Kohana_Log::$messages = array(); + } +} \ No newline at end of file diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php new file mode 100644 index 0000000..779c229 --- /dev/null +++ b/system/libraries/Kohana_PHP_Exception.php @@ -0,0 +1,99 @@ +code = $code; + $this->file = $file; + $this->line = $line; + } + + /** + * PHP error handler. + * + * @throws Kohana_PHP_Exception + * @return void + */ + public static function error_handler($code, $error, $file, $line, $context = NULL) + { + // Respect error_reporting settings + if (error_reporting() & $code) + { + // Throw an exception + throw new Kohana_PHP_Exception($code, $error, $file, $line, $context); + } + } + + /** + * Catches errors that are not caught by the error handler, such as E_PARSE. + * + * @uses Kohana_Exception::handle() + * @return void + */ + public static function shutdown_handler() + { + if (Kohana_PHP_Exception::$enabled AND $error = error_get_last() AND (error_reporting() & $error['type'])) + { + // Fake an exception for nice debugging + Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line'])); + } + } + +} // End Kohana PHP Exception diff --git a/system/libraries/Kohana_User_Exception.php b/system/libraries/Kohana_User_Exception.php new file mode 100644 index 0000000..a0ec3ac --- /dev/null +++ b/system/libraries/Kohana_User_Exception.php @@ -0,0 +1,30 @@ +code = $title; + } + +} // End Kohana User Exception diff --git a/system/libraries/Model.php b/system/libraries/Model.php new file mode 100644 index 0000000..ee50d50 --- /dev/null +++ b/system/libraries/Model.php @@ -0,0 +1,62 @@ +newInstanceArgs($args); + } + + // Database object + protected $db = 'default'; + + /** + * Loads the database instance, if the database is not already loaded. + * + * @return void + */ + public function __construct() + { + if ( ! is_object($this->db)) + { + // Load the default database + $this->db = Database::instance($this->db); + } + } + +} // End Model \ No newline at end of file diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php new file mode 100644 index 0000000..16e047b --- /dev/null +++ b/system/libraries/ORM.php @@ -0,0 +1,1528 @@ +object_name = strtolower(substr(get_class($this), 0, -6)); + $this->object_plural = inflector::plural($this->object_name); + + if ( ! isset($this->sorting)) + { + // Default sorting + $this->sorting = array($this->primary_key => 'asc'); + } + + // Initialize database + $this->__initialize(); + + // Clear the object + $this->clear(); + + if (is_object($id)) + { + // Load an object + $this->_load_values((array) $id); + } + elseif ( ! empty($id)) + { + // Set the object's primary key, but don't load it until needed + $this->object[$this->primary_key] = $id; + + // Object is considered saved until something is set + $this->_saved = TRUE; + } + } + + /** + * Prepares the model database connection, determines the table name, + * and loads column information. + * + * @return void + */ + public function __initialize() + { + if ( ! is_object($this->db)) + { + // Get database instance + $this->db = Database::instance($this->db); + } + + if (empty($this->table_name)) + { + // Table name is the same as the object name + $this->table_name = $this->object_name; + + if ($this->table_names_plural === TRUE) + { + // Make the table name plural + $this->table_name = inflector::plural($this->table_name); + } + } + + if (is_array($this->ignored_columns)) + { + // Make the ignored columns mirrored = mirrored + $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns); + } + + // Load column information + $this->reload_columns(); + + // Initialize the builder + $this->db_builder = db::build(); + } + + /** + * Allows serialization of only the object data and state, to prevent + * "stale" objects being unserialized, which also requires less memory. + * + * @return array + */ + public function __sleep() + { + // Store only information about the object + return array('object_name', 'object', 'changed', '_loaded', '_saved', 'sorting'); + } + + /** + * Prepares the database connection and reloads the object. + * + * @return void + */ + public function __wakeup() + { + // Initialize database + $this->__initialize(); + + if ($this->reload_on_wakeup === TRUE) + { + // Reload the object + $this->reload(); + } + } + + /** + * Handles pass-through to database methods. Calls to query methods + * (query, get, insert, update) are not allowed. Query builder methods + * are chainable. + * + * @param string method name + * @param array method arguments + * @return mixed + */ + public function __call($method, array $args) + { + if (method_exists($this->db_builder, $method)) + { + if (in_array($method, array('execute', 'insert', 'update', 'delete'))) + throw new Kohana_Exception('Query methods cannot be used through ORM'); + + // Method has been applied to the database + $this->db_applied[$method] = $method; + + // Number of arguments passed + $num_args = count($args); + + if ($method === 'select' AND $num_args > 3) + { + // Call select() manually to avoid call_user_func_array + $this->db_builder->select($args); + } + else + { + // We use switch here to manually call the database methods. This is + // done for speed: call_user_func_array can take over 300% longer to + // make calls. Most database methods are 4 arguments or less, so this + // avoids almost any calls to call_user_func_array. + + switch ($num_args) + { + case 0: + if (in_array($method, array('open', 'and_open', 'or_open', 'close', 'cache'))) + { + // Should return ORM, not Database + $this->db_builder->$method(); + } + else + { + // Support for things like reset_select, reset_write, list_tables + return $this->db_builder->$method(); + } + break; + case 1: + $this->db_builder->$method($args[0]); + break; + case 2: + $this->db_builder->$method($args[0], $args[1]); + break; + case 3: + $this->db_builder->$method($args[0], $args[1], $args[2]); + break; + case 4: + $this->db_builder->$method($args[0], $args[1], $args[2], $args[3]); + break; + default: + // Here comes the snail... + call_user_func_array(array($this->db, $method), $args); + break; + } + } + + return $this; + } + else + { + throw new Kohana_Exception('Invalid method :method called in :class', + array(':method' => $method, ':class' => get_class($this))); + } + } + + /** + * Handles retrieval of all model values, relationships, and metadata. + * + * @param string column name + * @return mixed + */ + public function __get($column) + { + if (array_key_exists($column, $this->object)) + { + if( ! $this->loaded() AND ! $this->empty_primary_key()) + { + // Column asked for but the object hasn't been loaded yet, so do it now + // Ignore loading of any columns that have been changed + $this->find($this->object[$this->primary_key], TRUE); + } + + return $this->object[$column]; + } + elseif (isset($this->related[$column])) + { + return $this->related[$column]; + } + elseif ($column === 'primary_key_value') + { + if( ! $this->loaded() AND ! $this->empty_primary_key() AND $this->unique_key($this->object[$this->primary_key]) !== $this->primary_key) + { + // Load if object hasn't been loaded and the key given isn't the primary_key + // that we need (i.e. passing an email address to ORM::factory rather than the id) + $this->find($this->object[$this->primary_key], TRUE); + } + + return $this->object[$this->primary_key]; + } + elseif ($model = $this->related_object($column)) + { + // This handles the has_one and belongs_to relationships + + if (in_array($model->object_name, $this->belongs_to)) + { + if ( ! $this->loaded() AND ! $this->empty_primary_key()) + { + // Load this object first so we know what id to look for in the foreign table + $this->find($this->object[$this->primary_key], TRUE); + } + + // Foreign key lies in this table (this model belongs_to target model) + $where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]); + } + else + { + // Foreign key lies in the target table (this model has_one target model) + $where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value); + } + + // one<>alias:one relationship + return $this->related[$column] = $model->find($where); + } + elseif (isset($this->has_many_through[$column])) + { + // Load the "middle" model + $through = ORM::factory(inflector::singular($this->has_many_through[$column])); + + // Load the "end" model + $model = ORM::factory(inflector::singular($column)); + + // Join ON target model's primary key set to 'through' model's foreign key + // User-defined foreign keys must be defined in the 'through' model + $join_table = $through->table_name; + $join_col1 = $through->foreign_key($model->object_name, $join_table); + $join_col2 = $model->foreign_key(TRUE); + + // one<>alias:many relationship + return $model + ->join($join_table, $join_col1, $join_col2) + ->where($through->foreign_key($this->object_name, $join_table), '=', $this->primary_key_value); + } + elseif (isset($this->has_many[$column])) + { + // one<>many aliased relationship + $model_name = $this->has_many[$column]; + + $model = ORM::factory(inflector::singular($model_name)); + + return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value); + } + elseif (in_array($column, $this->has_many)) + { + // one<>many relationship + $model = ORM::factory(inflector::singular($column)); + return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value); + } + elseif (in_array($column, $this->has_and_belongs_to_many)) + { + // Load the remote model, always singular + $model = ORM::factory(inflector::singular($column)); + + if ($this->has($model, TRUE)) + { + // many<>many relationship + return $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]); + } + else + { + // empty many<>many relationship + return $model->where($model->foreign_key(TRUE), 'IS', NULL); + } + } + elseif (isset($this->ignored_columns[$column])) + { + return NULL; + } + elseif (in_array($column, array + ( + 'object_name', 'object_plural','_valid', // Object + 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table + 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships + ))) + { + // Model meta information + return $this->$column; + } + else + { + throw new Kohana_Exception('The :property property does not exist in the :class class', + array(':property' => $column, ':class' => get_class($this))); + } + } + + /** + * Tells you if the Model has been loaded or not + * + * @return bool + */ + public function loaded() { + if ( ! $this->_loaded AND ! $this->empty_primary_key()) + { + // If returning the loaded member and no load has been attempted, do it now + $this->find($this->object[$this->primary_key], TRUE); + } + return $this->_loaded; + } + + /** + * Tells you if the model was saved successfully or not + * + * @return bool + */ + public function saved() { + return $this->_saved; + } + + /** + * Handles setting of all model values, and tracks changes between values. + * + * @param string column name + * @param mixed column value + * @return void + */ + public function __set($column, $value) + { + if (isset($this->ignored_columns[$column])) + { + return NULL; + } + elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object)) + { + if (isset($this->table_columns[$column])) + { + // Data has changed + $this->changed[$column] = $column; + + // Object is no longer saved or valid + $this->_saved = $this->_valid = FALSE; + } + + $this->object[$column] = $value; + } + elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value)) + { + // Load relations + $model = ORM::factory(inflector::singular($column)); + + if ( ! isset($this->object_relations[$column])) + { + // Load relations + $this->has($model); + } + + // Change the relationships + $this->changed_relations[$column] = $value; + + if (isset($this->related[$column])) + { + // Force a reload of the relationships + unset($this->related[$column]); + } + } + else + { + throw new Kohana_Exception('The :property: property does not exist in the :class: class', + array(':property:' => $column, ':class:' => get_class($this))); + } + } + + /** + * Chainable set method + * + * @param string name of field or array of key => val + * @param mixed value + * @return ORM + */ + public function set($name, $value = NULL) + { + if (is_array($name)) + { + foreach ($name as $key => $value) + { + $this->__set($key, $value); + } + } + else + { + $this->__set($name, $value); + } + + return $this; + } + + /** + * Checks if object data is set. + * + * @param string column name + * @return boolean + */ + public function __isset($column) + { + return (isset($this->object[$column]) OR isset($this->related[$column])); + } + + /** + * Unsets object data. + * + * @param string column name + * @return void + */ + public function __unset($column) + { + unset($this->object[$column], $this->changed[$column], $this->related[$column]); + } + + /** + * Returns the values of this object as an array. + * + * @return array + */ + public function as_array() + { + $object = array(); + + foreach ($this->object as $key => $val) + { + // Reconstruct the array (calls __get) + $object[$key] = $this->$key; + } + + foreach ($this->with_applied as $model => $enabled) + { + // Generate arrays for relationships + if ($enabled) + { + $object[$model] = $this->$model->as_array(); + } + } + + return $object; + } + + /** + * Binds another one-to-one object to this model. One-to-one objects + * can be nested using 'object1:object2' syntax + * + * @param string target model to bind to + * @return void + */ + public function with($target_path) + { + if (isset($this->with_applied[$target_path])) + { + // Don't join anything already joined + return $this; + } + + // Split object parts + $objects = explode(':', $target_path); + $target = $this; + foreach ($objects as $object) + { + // Go down the line of objects to find the given target + $parent = $target; + $target = $parent->related_object($object); + + if ( ! $target) + { + // Can't find related object + return $this; + } + } + + $target_name = $object; + + // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix) + array_pop($objects); + $parent_path = implode(':', $objects); + + if (empty($parent_path)) + { + // Use this table name itself for the parent object + $parent_path = $this->table_name; + } + else + { + if( ! isset($this->with_applied[$parent_path])) + { + // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail) + $this->with($parent_path); + } + } + + // Add to with_applied to prevent duplicate joins + $this->with_applied[$target_path] = TRUE; + + $select = array(); + + // Use the keys of the empty object to determine the columns + foreach (array_keys($target->object) as $column) + { + // Add the prefix so that load_result can determine the relationship + $select[$target_path.':'.$column] = $target_path.'.'.$column; + } + + // Select all of the prefixed keys in the object + $this->db_builder->select($select); + + if (in_array($target->object_name, $parent->belongs_to)) + { + // Parent belongs_to target, use target's primary key as join column + $join_col1 = $target->foreign_key(TRUE, $target_path); + $join_col2 = $parent->foreign_key($target_name, $parent_path); + } + else + { + // Parent has_one target, use parent's primary key as join column + $join_col2 = $parent->foreign_key(TRUE, $parent_path); + $join_col1 = $parent->foreign_key($target_name, $target_path); + } + + // This trick allows for models to use different table prefixes (sharing the same database) + $join_table = array($this->db->quote_table($target_path) => $target->db->quote_table($target->table_name)); + + // Join the related object into the result + // Use Database_Expression to disable prefixing + $this->db_builder->join(new Database_Expression($join_table), $join_col1, $join_col2, 'LEFT'); + + return $this; + } + + /** + * Finds and loads a single database row into the object. + * + * @chainable + * @param mixed primary key or an array of clauses + * @param bool ignore loading of columns that have been modified + * @return ORM + */ + public function find($id = NULL, $ignore_changed = FALSE) + { + if ($id !== NULL) + { + if (is_array($id)) + { + // Search for all clauses + $this->db_builder->where(array($id)); + } + else + { + // Search for a specific column + $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id); + } + } + + return $this->load_result(FALSE, $ignore_changed); + } + + /** + * Finds multiple database rows and returns an iterator of the rows found. + * + * @chainable + * @param integer SQL limit + * @param integer SQL offset + * @return ORM_Iterator + */ + public function find_all($limit = NULL, $offset = NULL) + { + if ($limit !== NULL AND ! isset($this->db_applied['limit'])) + { + // Set limit + $this->limit($limit); + } + + if ($offset !== NULL AND ! isset($this->db_applied['offset'])) + { + // Set offset + $this->offset($offset); + } + + return $this->load_result(TRUE); + } + + /** + * Creates a key/value array from all of the objects available. Uses find_all + * to find the objects. + * + * @param string key column + * @param string value column + * @return array + */ + public function select_list($key = NULL, $val = NULL) + { + if ($key === NULL) + { + $key = $this->primary_key; + } + + if ($val === NULL) + { + $val = $this->primary_val; + } + + // Return a select list from the results + return $this->select($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val); + } + + /** + * Validates the current object. This method requires that rules are set to be useful, if called with out + * any rules set, or if a Validation object isn't passed, nothing happens. + * + * @param object Validation array + * @param boolean Save on validate + * @return ORM + * @chainable + */ + public function validate(Validation $array = NULL) + { + if ($array === NULL) + $array = new Validation($this->object); + + if (count($this->rules) > 0) + { + foreach ($this->rules as $field => $parameters) + { + foreach ($parameters as $type => $value) { + switch ($type) { + case 'pre_filter': + $array->pre_filter($value,$field); + break; + case 'rules': + $rules = array_merge(array($field),$value); + call_user_func_array(array($array,'add_rules'), $rules); + break; + case 'callbacks': + $callbacks = array_merge(array($field),$value); + call_user_func_array(array($array,'add_callbacks'), $callbacks); + break; + } + } + } + } + // Validate the array + if (($this->_valid = $array->validate()) === FALSE) + { + ORM_Validation_Exception::handle_validation($this->table_name, $array); + } + + // Fields may have been modified by filters + $this->object = array_merge($this->object, $array->getArrayCopy()); + + // Return validation status + return $this; + } + + /** + * Saves the current object. + * + * @chainable + * @return ORM + */ + public function save() + { + if ( ! empty($this->changed)) + { + // Require model validation before saving + if ( ! $this->_valid) + { + $this->validate(); + } + + $data = array(); + foreach ($this->changed as $column) + { + // Compile changed data + $data[$column] = $this->object[$column]; + } + + if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key])) + { + // Primary key isn't empty and hasn't been changed so do an update + + if (is_array($this->updated_column)) + { + // Fill the updated column + $column = $this->updated_column['column']; + $format = $this->updated_column['format']; + + $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format); + } + + $query = db::update($this->table_name) + ->set($data) + ->where($this->primary_key, '=', $this->primary_key_value) + ->execute($this->db); + + // Object has been saved + $this->_saved = TRUE; + } + else + { + if (is_array($this->created_column)) + { + // Fill the created column + $column = $this->created_column['column']; + $format = $this->created_column['format']; + + $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format); + } + + $result = db::insert($this->table_name) + ->columns(array_keys($data)) + ->values(array_values($data)) + ->execute($this->db); + + if ($result->count() > 0) + { + if (empty($this->object[$this->primary_key])) + { + // Load the insert id as the primary key + $this->object[$this->primary_key] = $result->insert_id(); + } + + // Object is now loaded and saved + $this->_loaded = $this->_saved = TRUE; + } + } + + if ($this->saved() === TRUE) + { + // All changes have been saved + $this->changed = array(); + } + } + + if ($this->saved() === TRUE AND ! empty($this->changed_relations)) + { + foreach ($this->changed_relations as $column => $values) + { + // All values that were added + $added = array_diff($values, $this->object_relations[$column]); + + // All values that were saved + $removed = array_diff($this->object_relations[$column], $values); + + if (empty($added) AND empty($removed)) + { + // No need to bother + continue; + } + + // Clear related columns + unset($this->related[$column]); + + // Load the model + $model = ORM::factory(inflector::singular($column)); + + if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE) + continue; + + if (is_int($join_table)) + { + // No "through" table, load the default JOIN table + $join_table = $model->join_table($this->table_name); + } + + // Foreign keys for the join table + $object_fk = $this->foreign_key($join_table); + $related_fk = $model->foreign_key($join_table); + + if ( ! empty($added)) + { + foreach ($added as $id) + { + // Insert the new relationship + db::insert($join_table) + ->columns($object_fk, $related_fk) + ->values($this->primary_key_value, $id) + ->execute($this->db); + } + } + + if ( ! empty($removed)) + { + db::delete($join_table) + ->where($object_fk, '=', $this->primary_key_value) + ->where($related_fk, 'IN', $removed) + ->execute($this->db); + } + + // Clear all relations for this column + unset($this->object_relations[$column], $this->changed_relations[$column]); + } + } + + if ($this->saved() === TRUE) + { + // Always force revalidation after saving + $this->_valid = FALSE; + + // Clear the per-request database cache + $this->db->clear_cache(NULL, Database::PER_REQUEST); + } + + return $this; + } + + /** + * Deletes the current object from the database. This does NOT destroy + * relationships that have been created with other objects. + * + * @chainable + * @param mixed id to delete + * @return ORM + */ + public function delete($id = NULL) + { + if ($id === NULL) + { + // Use the the primary key value + $id = $this->primary_key_value; + } + + // Delete this object + db::delete($this->table_name) + ->where($this->primary_key, '=', $id) + ->execute($this->db); + + // Clear the per-request database cache + $this->db->clear_cache(NULL, Database::PER_REQUEST); + + return $this->clear(); + } + + /** + * Delete all objects in the associated table. This does NOT destroy + * relationships that have been created with other objects. + * + * @chainable + * @param array ids to delete + * @return ORM + */ + public function delete_all($ids = NULL) + { + if (is_array($ids)) + { + // Delete only given ids + db::delete($this->table_name) + ->where($this->primary_key, 'IN', $ids) + ->execute($this->db); + } + elseif ($ids === NULL) + { + // Delete all records + db::delete($this->table_name) + ->execute($this->db); + } + else + { + // Do nothing - safeguard + return $this; + } + + // Clear the per-request database cache + $this->db->clear_cache(NULL, Database::PER_REQUEST); + + return $this->clear(); + } + + /** + * Unloads the current object and clears the status. + * + * @chainable + * @return ORM + */ + public function clear() + { + // Create an array with all the columns set to NULL + $columns = array_keys($this->table_columns); + $values = array_combine($columns, array_fill(0, count($columns), NULL)); + + // Replace the current object with an empty one + $this->_load_values($values); + + return $this; + } + + /** + * Reloads the current object from the database. + * + * @chainable + * @return ORM + */ + public function reload() + { + if ($this->_loaded) { + return $this->find($this->object[$this->primary_key]); + } else { + return $this->clear(); + } + } + + /** + * Reload column definitions. + * + * @chainable + * @param boolean force reloading + * @return ORM + */ + public function reload_columns($force = FALSE) + { + if ($force === TRUE OR empty($this->table_columns)) + { + if (isset(ORM::$column_cache[$this->object_name])) + { + // Use cached column information + $this->table_columns = ORM::$column_cache[$this->object_name]; + } + else + { + // Load table columns + ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields(); + } + } + + return $this; + } + + /** + * Tests if this object has a relationship to a different model. + * + * @param object related ORM model + * @param boolean check for any relations to given model + * @return boolean + */ + public function has(ORM $model, $any = FALSE) + { + // Determine plural or singular relation name + $related = ($model->table_names_plural === TRUE) ? $model->object_plural : $model->object_name; + + if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE) + return FALSE; + + if (is_int($join_table)) + { + // No "through" table, load the default JOIN table + $join_table = $model->join_table($this->table_name); + } + + if ( ! isset($this->object_relations[$related])) + { + // Load the object relationships + $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model); + } + + if( ! $model->loaded() AND ! $model->empty_primary_key()) + { + // Load the related model if it hasn't already been + $model->find($model->object[$model->primary_key]); + } + + if ( ! $model->empty_primary_key()) + { + // Check if a specific object exists + return in_array($model->primary_key_value, $this->changed_relations[$related]); + } + elseif ($any) + { + // Check if any relations to given model exist + return ! empty($this->changed_relations[$related]); + } + else + { + return FALSE; + } + } + + /** + * Adds a new relationship to between this model and another. + * + * @param object related ORM model + * @return boolean + */ + public function add(ORM $model) + { + if ($this->has($model)) + return TRUE; + + // Get the faked column name + $column = $model->object_plural; + + // Add the new relation to the update + $this->changed_relations[$column][] = $model->primary_key_value; + + if (isset($this->related[$column])) + { + // Force a reload of the relationships + unset($this->related[$column]); + } + + return TRUE; + } + + /** + * Adds a new relationship to between this model and another. + * + * @param object related ORM model + * @return boolean + */ + public function remove(ORM $model) + { + if ( ! $this->has($model)) + return FALSE; + + // Get the faked column name + $column = $model->object_plural; + + if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE) + return FALSE; + + // Remove the relationship + unset($this->changed_relations[$column][$key]); + + if (isset($this->related[$column])) + { + // Force a reload of the relationships + unset($this->related[$column]); + } + + return TRUE; + } + + /** + * Count the number of records in the table. + * + * @return integer + */ + public function count_all() + { + // Return the total number of records in a table + return $this->db_builder->count_records($this->table_name); + } + + /** + * Proxy method to Database list_fields. + * + * @return array + */ + public function list_fields() + { + // Proxy to database + return $this->db->list_fields($this->table_name); + } + + /** + * Proxy method to Database clear_cache. + * + * @chainable + * @param string SQL query to clear + * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST + * @return ORM + */ + public function clear_cache($sql = NULL, $type = NULL) + { + // Proxy to database + $this->db->clear_cache($sql, $type); + + ORM::$column_cache = array(); + + return $this; + } + + /** + * Returns the unique key for a specific value. This method is expected + * to be overloaded in models if the model has other unique columns. + * + * @param mixed unique value + * @return string + */ + public function unique_key($id) + { + return $this->primary_key; + } + + /** + * Determines the name of a foreign key for a specific table. + * + * @param string related table name + * @param string prefix table name (used for JOINs) + * @return string + */ + public function foreign_key($table = NULL, $prefix_table = NULL) + { + if ($table === TRUE) + { + if (is_string($prefix_table)) + { + // Use prefix table name and this table's PK + return $prefix_table.'.'.$this->primary_key; + } + else + { + // Return the name of this table's PK + return $this->table_name.'.'.$this->primary_key; + } + } + + if (is_string($prefix_table)) + { + // Add a period for prefix_table.column support + $prefix_table .= '.'; + } + + if (isset($this->foreign_key[$table])) + { + // Use the defined foreign key name, no magic here! + $foreign_key = $this->foreign_key[$table]; + } + else + { + if ( ! is_string($table) OR ! array_key_exists($table.'_'.$this->primary_key, $this->object)) + { + // Use this table + $table = $this->table_name; + + if (strpos($table, '.') !== FALSE) + { + // Hack around support for PostgreSQL schemas + list ($schema, $table) = explode('.', $table, 2); + } + + if ($this->table_names_plural === TRUE) + { + // Make the key name singular + $table = inflector::singular($table); + } + } + + $foreign_key = $table.'_'.$this->primary_key; + } + + return $prefix_table.$foreign_key; + } + + /** + * This uses alphabetical comparison to choose the name of the table. + * + * Example: The joining table of users and roles would be roles_users, + * because "r" comes before "u". Joining products and categories would + * result in categories_products, because "c" comes before "p". + * + * Example: zoo > zebra > robber > ocean > angel > aardvark + * + * @param string table name + * @return string + */ + public function join_table($table) + { + if ($this->table_name > $table) + { + $table = $table.'_'.$this->table_name; + } + else + { + $table = $this->table_name.'_'.$table; + } + + return $table; + } + + /** + * Returns an ORM model for the given object name; + * + * @param string object name + * @return ORM + */ + protected function related_object($object) + { + if (isset($this->has_one[$object])) + { + $object = ORM::factory($this->has_one[$object]); + } + elseif (isset($this->belongs_to[$object])) + { + $object = ORM::factory($this->belongs_to[$object]); + } + elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to)) + { + $object = ORM::factory($object); + } + else + { + return FALSE; + } + + return $object; + } + + /** + * Loads an array of values into into the current object. + * + * @chainable + * @param array values to load + * @return ORM + */ + public function load_values(array $values) + { + // Related objects + $related = array(); + + foreach ($values as $column => $value) + { + if (strpos($column, ':') === FALSE) + { + if ( ! isset($this->changed[$column])) + { + if (isset($this->table_columns[$column])) + { + //Update the column, respects __get() + $this->$column = $value; + } + } + } + else + { + list ($prefix, $column) = explode(':', $column, 2); + + $related[$prefix][$column] = $value; + } + } + + if ( ! empty($related)) + { + foreach ($related as $object => $values) + { + // Load the related objects with the values in the result + $this->related[$object] = $this->related_object($object)->load_values($values); + } + } + + return $this; + } + + /** + * Loads an array of values into into the current object. Only used internally + * + * @chainable + * @param array values to load + * @param bool ignore loading of columns that have been modified + * @return ORM + */ + public function _load_values(array $values, $ignore_changed = FALSE) + { + if (array_key_exists($this->primary_key, $values)) + { + if ( ! $ignore_changed) + { + // Replace the object and reset the object status + $this->object = $this->changed = $this->related = array(); + } + + // Set the loaded and saved object status based on the primary key + $this->_loaded = $this->_saved = ($values[$this->primary_key] !== NULL); + } + + // Related objects + $related = array(); + + foreach ($values as $column => $value) + { + if (strpos($column, ':') === FALSE) + { + if ( ! $ignore_changed OR ! isset($this->changed[$column])) + { + $this->object[$column] = $value; + } + } + else + { + list ($prefix, $column) = explode(':', $column, 2); + + $related[$prefix][$column] = $value; + } + } + + if ( ! empty($related)) + { + foreach ($related as $object => $values) + { + // Load the related objects with the values in the result + $this->related[$object] = $this->related_object($object)->_load_values($values); + } + } + + return $this; + } + + /** + * Loads a database result, either as a new object for this model, or as + * an iterator for multiple rows. + * + * @chainable + * @param boolean return an iterator or load a single row + * @param boolean ignore loading of columns that have been modified + * @return ORM for single rows + * @return ORM_Iterator for multiple rows + */ + protected function load_result($array = FALSE, $ignore_changed = FALSE) + { + $this->db_builder->from($this->table_name); + + if ($array === FALSE) + { + // Only fetch 1 record + $this->db_builder->limit(1); + } + + if ( ! isset($this->db_applied['select'])) + { + // Select all columns by default + $this->db_builder->select($this->table_name.'.*'); + } + + if ( ! empty($this->load_with)) + { + foreach ($this->load_with as $alias => $object) + { + // Join each object into the results + if (is_string($alias)) + { + // Use alias + $this->with($alias); + } + else + { + // Use object + $this->with($object); + } + } + } + + if ( ! isset($this->db_applied['order_by']) AND ! empty($this->sorting)) + { + $sorting = array(); + foreach ($this->sorting as $column => $direction) + { + if (strpos($column, '.') === FALSE) + { + // Keeps sorting working properly when using JOINs on + // tables with columns of the same name + $column = $this->table_name.'.'.$column; + } + + $sorting[$column] = $direction; + } + + // Apply the user-defined sorting + $this->db_builder->order_by($sorting); + } + + // Load the result + $result = $this->db_builder->execute($this->db); + $this->db_applied = array(); + + if ($array === TRUE) + { + // Return an iterated result + return new ORM_Iterator($this, $result); + } + + if ($result->count() === 1) + { + // Load object values + $this->_load_values($result->as_array()->current(), $ignore_changed); + } + else + { + // Clear the object, nothing was found + $this->clear(); + } + + return $this; + } + + /** + * Return an array of all the primary keys of the related table. + * + * @param string table name + * @param object ORM model to find relations of + * @return array + */ + protected function load_relations($table, ORM $model) + { + $result = db::select(array('id' => $model->foreign_key($table))) + ->from($table) + ->where($this->foreign_key($table, $table), '=', $this->primary_key_value) + ->execute($this->db) + ->as_object(); + + $relations = array(); + foreach ($result as $row) + { + $relations[] = $row->id; + } + + return $relations; + } + + /** + * Returns whether or not primary key is empty + * + * @return bool + */ + protected function empty_primary_key() + { + return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0'); + } + +} // End ORM diff --git a/system/libraries/ORM_Iterator.php b/system/libraries/ORM_Iterator.php new file mode 100644 index 0000000..0bf2b47 --- /dev/null +++ b/system/libraries/ORM_Iterator.php @@ -0,0 +1,266 @@ +class_name = get_class($model); + $this->primary_key = $model->primary_key; + $this->primary_val = $model->primary_val; + + // Database result (make sure rows are returned as arrays) + $this->result = $result; + } + + /** + * Returns an array of the results as ORM objects or a nested array + * + * @param bool TRUE to return an array of ORM objects, FALSE for an array of arrays + * @param string key column to index on, NULL to ignore + * @return array + */ + public function as_array($objects = TRUE, $key = NULL) + { + $array = array(); + + // Import class name + $class = $this->class_name; + + if ($objects) + { + // Generate an array of objects + foreach ($this->result as $data) + { + if ($key === NULL) + { + // No indexing + $array[] = new $class($data); + } + else + { + // Index on the given key + $array[$data->$key] = new $class($data); + } + } + } + else + { + // Generate an array of arrays (and the subarrays may be nested in the case of relationships) + // This could be done by creating a new ORM object and calling as_array on it, but this is much faster + foreach ($this->result as $data) + { + // Have to do a bit of magic here to handle any relationships and generate a nested array for them + $temp = array(); + + foreach ($data as $key => $val) + { + $ptr = & $temp; + + foreach (explode(':', $key) as $subkey) + { + // Walk thru the relationships (separated in the key name by a ':') + // 'user:email:address' will be array['user']['email']['address'] + $ptr = & $ptr[$subkey]; + } + + // Set the value + $ptr = $val; + } + + // Append the result + $array[] = $temp; + } + } + + return $array; + } + + /** + * Return an array of all of the primary keys for this object. + * + * @return array + */ + public function primary_key_array() + { + $ids = array(); + foreach ($this->result as $row) + { + $ids[] = $row->{$this->primary_key}; + } + return $ids; + } + + /** + * Create a key/value array from the results. + * + * @param string key column + * @param string value column + * @return array + */ + public function select_list($key = NULL, $val = NULL) + { + if ($key === NULL) + { + // Use the default key + $key = $this->primary_key; + } + + if ($val === NULL) + { + // Use the default value + $val = $this->primary_val; + } + + $array = array(); + foreach ($this->result as $row) + { + $array[$row->$key] = $row->$val; + } + return $array; + } + + /** + * Return a range of offsets. + * + * @param integer start + * @param integer end + * @return array + */ + public function range($start, $end) + { + // Array of objects + $array = array(); + + if ($this->result->offsetExists($start)) + { + // Import the class name + $class = $this->class_name; + + // Set the end offset + $end = $this->result->offsetExists($end) ? $end : $this->count(); + + for ($i = $start; $i < $end; $i++) + { + // Insert each object in the range + $array[] = new $class($this->result->offsetGet($i)); + } + } + + return $array; + } + + /** + * Countable: count + */ + public function count() + { + return $this->result->count(); + } + + /** + * Iterator: current + */ + public function current() + { + if ($row = $this->result->current()) + { + // Import class name + $class = $this->class_name; + + $row = new $class($row); + } + + return $row; + } + + /** + * Iterator: key + */ + public function key() + { + return $this->result->key(); + } + + /** + * Iterator: next + */ + public function next() + { + return $this->result->next(); + } + + /** + * Iterator: rewind + */ + public function rewind() + { + $this->result->rewind(); + } + + /** + * Iterator: valid + */ + public function valid() + { + return $this->result->valid(); + } + + /** + * ArrayAccess: offsetExists + */ + public function offsetExists($offset) + { + return $this->result->offsetExists($offset); + } + + /** + * ArrayAccess: offsetGet + */ + public function offsetGet($offset) + { + if ($this->result->offsetExists($offset)) + { + // Import class name + $class = $this->class_name; + + return new $class($this->result->offsetGet($offset)); + } + } + + /** + * ArrayAccess: offsetSet + * + * @throws Kohana_Database_Exception + */ + public function offsetSet($offset, $value) + { + throw new Kohana_Database_Exception('database.result_read_only'); + } + + /** + * ArrayAccess: offsetUnset + * + * @throws Kohana_Database_Exception + */ + public function offsetUnset($offset) + { + throw new Kohana_Database_Exception('database.result_read_only'); + } + +} // End ORM Iterator \ No newline at end of file diff --git a/system/libraries/ORM_Validation_Exception.php b/system/libraries/ORM_Validation_Exception.php new file mode 100644 index 0000000..9044aa6 --- /dev/null +++ b/system/libraries/ORM_Validation_Exception.php @@ -0,0 +1,24 @@ +$table)); + $exception->validation = $array; + throw $exception; + } +} // End ORM_Validation_Exception \ No newline at end of file diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php new file mode 100644 index 0000000..3ac8707 --- /dev/null +++ b/system/libraries/Profiler.php @@ -0,0 +1,306 @@ +styles(); + } + + // Load the profiler view + $data = array + ( + 'profiles' => Profiler::$profiles, + 'styles' => $styles, + 'execution_time' => microtime(TRUE) - $start + ); + $view = new View('profiler/profiler', $data); + + // Return rendered view if $return is TRUE + if ($return === TRUE) + return $view->render(); + + // Add profiler data to the output + if (stripos(Kohana::$output, '') !== FALSE) + { + // Closing body tag was found, insert the profiler data before it + Kohana::$output = str_ireplace('', $view->render().'', Kohana::$output); + } + else + { + // Append the profiler data to the output + Kohana::$output .= $view->render(); + } + } + + /** + * Benchmark times and memory usage from the Benchmark library. + * + * @return void + */ + public static function benchmarks() + { + if ( ! Profiler::show('benchmarks')) + return; + + $table = new Profiler_Table(); + $table->add_column(); + $table->add_column('kp-column kp-data'); + $table->add_column('kp-column kp-data'); + $table->add_column('kp-column kp-data'); + $table->add_row(array(__('Benchmarks'), __('Count'), __('Time'), __('Memory')), 'kp-title', 'background-color: #FFE0E0'); + + $benchmarks = Benchmark::get(TRUE); + + // Moves the first benchmark (total execution time) to the end of the array + $benchmarks = array_slice($benchmarks, 1) + array_slice($benchmarks, 0, 1); + + text::alternate(); + foreach ($benchmarks as $name => $benchmark) + { + // Clean unique id from system benchmark names + $name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name))); + + $data = array(__($name), $benchmark['count'], number_format($benchmark['time'], Kohana::config('profiler.time_decimals')), number_format($benchmark['memory'] / 1024 / 1024, Kohana::config('profiler.memory_decimals')).'MB'); + $class = text::alternate('', 'kp-altrow'); + + if ($name == 'Total Execution') + { + // Clear the count column + $data[1] = ''; + $class = 'kp-totalrow'; + } + + $table->add_row($data, $class); + } + + Profiler::add($table); + } + + /** + * Database query benchmarks. + * + * @return void + */ + public static function database() + { + if ( ! Profiler::show('database')) + return; + + $queries = Database::$benchmarks; + + // Don't show if there are no queries + if (empty($queries)) return; + + $table = new Profiler_Table(); + $table->add_column(); + $table->add_column('kp-column kp-data'); + $table->add_column('kp-column kp-data'); + $table->add_row(array(__('Queries'), __('Time'), __('Rows')), 'kp-title', 'background-color: #E0FFE0'); + + text::alternate(); + $total_time = $total_rows = 0; + foreach ($queries as $query) + { + $data = array($query['query'], number_format($query['time'], Kohana::config('profiler.time_decimals')), $query['rows']); + $class = text::alternate('', 'kp-altrow'); + $table->add_row($data, $class); + $total_time += $query['time']; + $total_rows += $query['rows']; + } + + $data = array(__('Total: ') . count($queries), number_format($total_time, Kohana::config('profiler.time_decimals')), $total_rows); + $table->add_row($data, 'kp-totalrow'); + + Profiler::add($table); + } + + /** + * Session data. + * + * @return void + */ + public static function session() + { + if (empty($_SESSION)) return; + + if ( ! Profiler::show('session')) + return; + + $table = new Profiler_Table(); + $table->add_column('kp-name'); + $table->add_column(); + $table->add_row(array(__('Session'), __('Value')), 'kp-title', 'background-color: #CCE8FB'); + + text::alternate(); + foreach($_SESSION as $name => $value) + { + if (is_object($value)) + { + $value = get_class($value).' [object]'; + } + + $data = array($name, $value); + $class = text::alternate('', 'kp-altrow'); + $table->add_row($data, $class); + } + + Profiler::add($table); + } + + /** + * POST data. + * + * @return void + */ + public static function post() + { + if (empty($_POST)) return; + + if ( ! Profiler::show('post')) + return; + + $table = new Profiler_Table(); + $table->add_column('kp-name'); + $table->add_column(); + $table->add_row(array(__('POST'), __('Value')), 'kp-title', 'background-color: #E0E0FF'); + + text::alternate(); + foreach($_POST as $name => $value) + { + $data = array($name, $value); + $class = text::alternate('', 'kp-altrow'); + $table->add_row($data, $class); + } + + Profiler::add($table); + } + + /** + * Cookie data. + * + * @return void + */ + public static function cookies() + { + if (empty($_COOKIE)) return; + + if ( ! Profiler::show('cookies')) + return; + + $table = new Profiler_Table(); + $table->add_column('kp-name'); + $table->add_column(); + $table->add_row(array(__('Cookies'), __('Value')), 'kp-title', 'background-color: #FFF4D7'); + + text::alternate(); + foreach($_COOKIE as $name => $value) + { + $data = array($name, $value); + $class = text::alternate('', 'kp-altrow'); + $table->add_row($data, $class); + } + + Profiler::add($table); + } +} diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php new file mode 100644 index 0000000..e590ad7 --- /dev/null +++ b/system/libraries/Profiler_Table.php @@ -0,0 +1,67 @@ +columns[] = array('class' => $class, 'style' => $style); + } + + /** + * Add row to table. + * + * @param array data to go in table cells + * @param string CSS class + * @param string CSS style + */ + public function add_row($data, $class = '', $style = '') + { + $this->rows[] = array('data' => $data, 'class' => $class, 'style' => $style); + } + + /** + * Render table. + * + * @return string + */ + public function render() + { + $data['rows'] = $this->rows; + $data['columns'] = $this->columns; + return View::factory('profiler/table', $data)->render(); + } +} \ No newline at end of file diff --git a/system/libraries/Router.php b/system/libraries/Router.php new file mode 100644 index 0000000..c36121d --- /dev/null +++ b/system/libraries/Router.php @@ -0,0 +1,315 @@ + 1) + { + // Custom routing + Router::$routed_uri = Router::routed_uri(Router::$current_uri); + } + } + + // Explode the routed segments by slashes + Router::$rsegments = explode('/', Router::$routed_uri); + + // Prepare to find the controller + $controller_path = ''; + $method_segment = NULL; + + // Paths to search + $paths = Kohana::include_paths(); + + foreach (Router::$rsegments as $key => $segment) + { + // Add the segment to the search path + $controller_path .= $segment; + + $found = FALSE; + foreach ($paths as $dir) + { + // Search within controllers only + $dir .= 'controllers/'; + + if (is_dir($dir.$controller_path) OR is_file($dir.$controller_path.EXT)) + { + // Valid path + $found = TRUE; + + // The controller must be a file that exists with the search path + if ($c = str_replace('\\', '/', realpath($dir.$controller_path.EXT)) + AND is_file($c) AND strpos($c, $dir) === 0) + { + // Set controller name + Router::$controller = $segment; + + // Change controller path + Router::$controller_path = $c; + + // Set the method segment + $method_segment = $key + 1; + + // Stop searching + break; + } + } + } + + if ($found === FALSE) + { + // Maximum depth has been reached, stop searching + break; + } + + // Add another slash + $controller_path .= '/'; + } + + if ($method_segment !== NULL AND isset(Router::$rsegments[$method_segment])) + { + // Set method + Router::$method = Router::$rsegments[$method_segment]; + + if (isset(Router::$rsegments[$method_segment + 1])) + { + // Set arguments + Router::$arguments = array_slice(Router::$rsegments, $method_segment + 1); + } + } + + // Last chance to set routing before a 404 is triggered + Event::run('system.post_routing'); + + if (Router::$controller === NULL) + { + // No controller was found, so no page can be rendered + Event::run('system.404'); + } + } + + /** + * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF. + * + * @return void + */ + public static function find_uri() + { + if (Kohana::$server_api === 'cli') + { + // Command line requires a bit of hacking + if (isset($_SERVER['argv'][1])) + { + Router::$current_uri = $_SERVER['argv'][1]; + + // Remove GET string from segments + if (strpos(Router::$current_uri, '?') !== FALSE) + { + list(Router::$current_uri, $query) = explode('?', Router::$current_uri, 2); + + // Parse the query string into $_GET + parse_str($query, $_GET); + + // Convert $_GET to UTF-8 + $_GET = Input::clean($_GET); + } + } + } + elseif (isset($_GET['kohana_uri'])) + { + // Use the URI defined in the query string + Router::$current_uri = $_GET['kohana_uri']; + + // Remove the URI from $_GET + unset($_GET['kohana_uri']); + + // Remove the URI from $_SERVER['QUERY_STRING'] + $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']); + } + else + { + if (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO']) + { + Router::$current_uri = $_SERVER['PATH_INFO']; + } + elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO']) + { + Router::$current_uri = $_SERVER['ORIG_PATH_INFO']; + } + elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF']) + { + // PATH_INFO is empty during requests to the front controller + Router::$current_uri = $_SERVER['PHP_SELF']; + } + + if (isset($_SERVER['SCRIPT_NAME']) AND $_SERVER['SCRIPT_NAME']) + { + // Clean up PATH_INFO fallbacks + // PATH_INFO may be formatted for ISAPI instead of CGI on IIS + if (strncmp(Router::$current_uri, $_SERVER['SCRIPT_NAME'], strlen($_SERVER['SCRIPT_NAME'])) === 0) + { + // Remove the front controller from the current uri + Router::$current_uri = (string) substr(Router::$current_uri, strlen($_SERVER['SCRIPT_NAME'])); + } + } + } + + // Remove slashes from the start and end of the URI + Router::$current_uri = trim(Router::$current_uri, '/'); + + if (Router::$current_uri !== '') + { + if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE) + { + // Remove the URL suffix + Router::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', Router::$current_uri); + + // Set the URL suffix + Router::$url_suffix = $suffix; + } + + // Reduce multiple slashes into single slashes + Router::$current_uri = preg_replace('#//+#', '/', Router::$current_uri); + } + } + + /** + * Generates routed URI from given URI. + * + * @param string URI to convert + * @return string Routed uri + */ + public static function routed_uri($uri) + { + if (Router::$routes === NULL) + { + // Load routes + Router::$routes = Kohana::config('routes'); + } + + // Prepare variables + $routed_uri = $uri = trim($uri, '/'); + + if (isset(Router::$routes[$uri])) + { + // Literal match, no need for regex + $routed_uri = Router::$routes[$uri]; + } + else + { + // Loop through the routes and see if anything matches + foreach (Router::$routes as $key => $val) + { + if ($key === '_default') continue; + + // Trim slashes + $key = trim($key, '/'); + $val = trim($val, '/'); + + if (preg_match('#^'.$key.'$#u', $uri)) + { + if (strpos($val, '$') !== FALSE) + { + // Use regex routing + $routed_uri = preg_replace('#^'.$key.'$#u', $val, $uri); + } + else + { + // Standard routing + $routed_uri = $val; + } + + // A valid route has been found + break; + } + } + } + + if (isset(Router::$routes[$routed_uri])) + { + // Check for double routing (without regex) + $routed_uri = Router::$routes[$routed_uri]; + } + + return trim($routed_uri, '/'); + } + +} // End Router \ No newline at end of file diff --git a/system/libraries/Session.php b/system/libraries/Session.php new file mode 100644 index 0000000..e57908e --- /dev/null +++ b/system/libraries/Session.php @@ -0,0 +1,500 @@ + session_id(), ':new_session:' => $session_id)); + } + + return Session::$instance; + } + + /** + * Be sure to block the use of __clone. + */ + private function __clone(){} + + /** + * On first session instance creation, sets up the driver and creates session. + * + * @param string Force a specific session_id + */ + protected function __construct($session_id = NULL) + { + $this->input = Input::instance(); + + // This part only needs to be run once + if (Session::$instance === NULL) + { + // Load config + Session::$config = Kohana::config('session'); + + // Makes a mirrored array, eg: foo=foo + Session::$protect = array_combine(Session::$protect, Session::$protect); + + // Configure garbage collection + ini_set('session.gc_probability', (int) Session::$config['gc_probability']); + ini_set('session.gc_divisor', 100); + ini_set('session.gc_maxlifetime', (Session::$config['expiration'] == 0) ? 86400 : Session::$config['expiration']); + + // Create a new session + $this->create(NULL, $session_id); + + if (Session::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % Session::$config['regenerate']) === 0) + { + // Regenerate session id and update session cookie + $this->regenerate(); + } + else + { + // Always update session cookie to keep the session alive + cookie::set(Session::$config['name'], $_SESSION['session_id'], Session::$config['expiration']); + } + + // Close the session on system shutdown (run before sending the headers), so that + // the session cookie(s) can be written. + Event::add('system.shutdown', array($this, 'write_close')); + + // Singleton instance + Session::$instance = $this; + } + + Kohana_Log::add('debug', 'Session Library initialized'); + } + + /** + * Get the session id. + * + * @return string + */ + public function id() + { + return $_SESSION['session_id']; + } + + /** + * Create a new session. + * + * @param array variables to set after creation + * @param string Force a specific session_id + * @return void + */ + public function create($vars = NULL, $session_id = NULL) + { + // Destroy any current sessions + $this->destroy(); + + if (Session::$config['driver'] !== 'native') + { + // Set driver name + $driver = 'Session_'.ucfirst(Session::$config['driver']).'_Driver'; + + // Load the driver + if ( ! Kohana::auto_load($driver)) + throw new Kohana_Exception('The :driver: driver for the :library: library could not be found', + array(':driver:' => Session::$config['driver'], ':library:' => get_class($this))); + + // Initialize the driver + Session::$driver = new $driver(); + + // Validate the driver + if ( ! (Session::$driver instanceof Session_Driver)) + throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface', + array(':driver:' => Session::$config['driver'], ':library:' => get_class($this), ':interface:' => 'Session_Driver')); + + // Register non-native driver as the session handler + session_set_save_handler + ( + array(Session::$driver, 'open'), + array(Session::$driver, 'close'), + array(Session::$driver, 'read'), + array(Session::$driver, 'write'), + array(Session::$driver, 'destroy'), + array(Session::$driver, 'gc') + ); + } + + // Validate the session name + if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', Session::$config['name'])) + throw new Kohana_Exception('The session_name, :session:, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.', array(':session:' => Session::$config['name'])); + + // Name the session, this will also be the name of the cookie + session_name(Session::$config['name']); + + // Set the session cookie parameters + session_set_cookie_params + ( + Session::$config['expiration'], + Kohana::config('cookie.path'), + Kohana::config('cookie.domain'), + Kohana::config('cookie.secure'), + Kohana::config('cookie.httponly') + ); + + $cookie = cookie::get(Session::$config['name']); + + if ($session_id === NULL) + { + // Reopen session from signed cookie value. + $session_id = $cookie; + } + + // Reopen an existing session if supplied + if ( ! is_null($session_id)) + { + session_id($session_id); + } + + // Start the session! + session_start(); + + // Put session_id in the session variable + $_SESSION['session_id'] = session_id(); + + // Set defaults + if ( ! isset($_SESSION['_kf_flash_'])) + { + $_SESSION['total_hits'] = 0; + $_SESSION['_kf_flash_'] = array(); + + $_SESSION['user_agent'] = request::user_agent(); + $_SESSION['ip_address'] = $this->input->ip_address(); + } + + // Set up flash variables + Session::$flash =& $_SESSION['_kf_flash_']; + + // Increase total hits + $_SESSION['total_hits'] += 1; + + // Validate data only on hits after one + if ($_SESSION['total_hits'] > 1) + { + // Validate the session + foreach (Session::$config['validate'] as $valid) + { + switch ($valid) + { + // Check user agent for consistency + case 'user_agent': + if ($_SESSION[$valid] !== request::user_agent()) + return $this->create(); + break; + + // Check ip address for consistency + case 'ip_address': + if ($_SESSION[$valid] !== $this->input->$valid()) + return $this->create(); + break; + + // Check expiration time to prevent users from manually modifying it + case 'expiration': + if (time() - $_SESSION['last_activity'] > ini_get('session.gc_maxlifetime')) + return $this->create(); + break; + } + } + } + + // Expire flash keys + $this->expire_flash(); + + // Update last activity + $_SESSION['last_activity'] = time(); + + // Set the new data + Session::set($vars); + } + + /** + * Regenerates the global session id. + * + * @return void + */ + public function regenerate() + { + if (Session::$config['driver'] === 'native') + { + // Generate a new session id + // Note: also sets a new session cookie with the updated id + session_regenerate_id(TRUE); + + // Update session with new id + $_SESSION['session_id'] = session_id(); + } + else + { + // Pass the regenerating off to the driver in case it wants to do anything special + $_SESSION['session_id'] = Session::$driver->regenerate(); + } + + // Get the session name + $name = session_name(); + + if (isset($_COOKIE[$name])) + { + // Change the cookie value to match the new session id to prevent "lag" + cookie::set($name, $_SESSION['session_id']); + } + } + + /** + * Destroys the current session. + * + * @return void + */ + public function destroy() + { + if (session_id() !== '') + { + // Get the session name + $name = session_name(); + + // Destroy the session + session_destroy(); + + // Re-initialize the array + $_SESSION = array(); + + // Delete the session cookie + cookie::delete($name); + } + } + + /** + * Runs the system.session_write event, then calls session_write_close. + * + * @return void + */ + public function write_close() + { + static $run; + + if ($run === NULL) + { + $run = TRUE; + + // Run the events that depend on the session being open + Event::run('system.session_write'); + + // Expire flash keys + $this->expire_flash(); + + // Close the session + session_write_close(); + } + } + + /** + * Set a session variable. + * + * @param string|array key, or array of values + * @param mixed value (if keys is not an array) + * @return void + */ + public function set($keys, $val = FALSE) + { + if (empty($keys)) + return FALSE; + + if ( ! is_array($keys)) + { + $keys = array($keys => $val); + } + + foreach ($keys as $key => $val) + { + if (isset(Session::$protect[$key])) + continue; + + // Set the key + $_SESSION[$key] = $val; + } + } + + /** + * Set a flash variable. + * + * @param string|array key, or array of values + * @param mixed value (if keys is not an array) + * @return void + */ + public function set_flash($keys, $val = FALSE) + { + if (empty($keys)) + return FALSE; + + if ( ! is_array($keys)) + { + $keys = array($keys => $val); + } + + foreach ($keys as $key => $val) + { + if ($key == FALSE) + continue; + + Session::$flash[$key] = 'new'; + Session::set($key, $val); + } + } + + /** + * Freshen one, multiple or all flash variables. + * + * @param string variable key(s) + * @return void + */ + public function keep_flash($keys = NULL) + { + $keys = ($keys === NULL) ? array_keys(Session::$flash) : func_get_args(); + + foreach ($keys as $key) + { + if (isset(Session::$flash[$key])) + { + Session::$flash[$key] = 'new'; + } + } + } + + /** + * Expires old flash data and removes it from the session. + * + * @return void + */ + public function expire_flash() + { + static $run; + + // Method can only be run once + if ($run === TRUE) + return; + + if ( ! empty(Session::$flash)) + { + foreach (Session::$flash as $key => $state) + { + if ($state === 'old') + { + // Flash has expired + unset(Session::$flash[$key], $_SESSION[$key]); + } + else + { + // Flash will expire + Session::$flash[$key] = 'old'; + } + } + } + + // Method has been run + $run = TRUE; + } + + /** + * Get a variable. Access to sub-arrays is supported with key.subkey. + * + * @param string variable key + * @param mixed default value returned if variable does not exist + * @return mixed Variable data if key specified, otherwise array containing all session data. + */ + public function get($key = FALSE, $default = FALSE) + { + if (empty($key)) + return $_SESSION; + + $result = isset($_SESSION[$key]) ? $_SESSION[$key] : Kohana::key_string($_SESSION, $key); + + return ($result === NULL) ? $default : $result; + } + + /** + * Get a variable, and delete it. + * + * @param string variable key + * @param mixed default value returned if variable does not exist + * @return mixed + */ + public function get_once($key, $default = FALSE) + { + $return = Session::get($key, $default); + Session::delete($key); + + return $return; + } + + /** + * Delete one or more variables. + * + * @param string variable key(s) + * @return void + */ + public function delete($keys) + { + $args = func_get_args(); + + foreach ($args as $key) + { + if (isset(Session::$protect[$key])) + continue; + + // Unset the key + unset($_SESSION[$key]); + } + } + + /** + * Do not save this session. + * This is a performance feature only, if using the native + * session "driver" the save will NOT be aborted. + * + * @return void + */ + public function abort_save() + { + Session::$should_save = FALSE; + } + +} // End Session Class diff --git a/system/libraries/URI.php b/system/libraries/URI.php new file mode 100644 index 0000000..16d101a --- /dev/null +++ b/system/libraries/URI.php @@ -0,0 +1,279 @@ +build_array(URI::$segments, $offset, $associative); + } + + /** + * Returns an array containing all the re-routed URI segments. + * + * @param integer rsegment offset + * @param boolean return an associative array + * @return array + */ + public function rsegment_array($offset = 0, $associative = FALSE) + { + return $this->build_array(URI::$rsegments, $offset, $associative); + } + + /** + * Returns an array containing all the URI arguments. + * + * @param integer segment offset + * @param boolean return an associative array + * @return array + */ + public function argument_array($offset = 0, $associative = FALSE) + { + return $this->build_array(URI::$arguments, $offset, $associative); + } + + /** + * Creates a simple or associative array from an array and an offset. + * Used as a helper for (r)segment_array and argument_array. + * + * @param array array to rebuild + * @param integer offset to start from + * @param boolean create an associative array + * @return array + */ + public function build_array($array, $offset = 0, $associative = FALSE) + { + // Prevent the keys from being improperly indexed + array_unshift($array, 0); + + // Slice the array, preserving the keys + $array = array_slice($array, $offset + 1, count($array) - 1, TRUE); + + if ($associative === FALSE) + return $array; + + $associative = array(); + $pairs = array_chunk($array, 2); + + foreach ($pairs as $pair) + { + // Add the key/value pair to the associative array + $associative[$pair[0]] = isset($pair[1]) ? $pair[1] : ''; + } + + return $associative; + } + + /** + * Returns the complete URI as a string. + * + * @return string + */ + public function string() + { + return URI::$current_uri; + } + + /** + * Magic method for converting an object to a string. + * + * @return string + */ + public function __toString() + { + return URI::$current_uri; + } + + /** + * Returns the total number of URI segments. + * + * @return integer + */ + public function total_segments() + { + return count(URI::$segments); + } + + /** + * Returns the total number of re-routed URI segments. + * + * @return integer + */ + public function total_rsegments() + { + return count(URI::$rsegments); + } + + /** + * Returns the total number of URI arguments. + * + * @return integer + */ + public function total_arguments() + { + return count(URI::$arguments); + } + + /** + * Returns the last URI segment. + * + * @param mixed default value returned if segment does not exist + * @return string + */ + public function last_segment($default = FALSE) + { + if (($end = $this->total_segments()) < 1) + return $default; + + return URI::$segments[$end - 1]; + } + + /** + * Returns the last re-routed URI segment. + * + * @param mixed default value returned if segment does not exist + * @return string + */ + public function last_rsegment($default = FALSE) + { + if (($end = $this->total_segments()) < 1) + return $default; + + return URI::$rsegments[$end - 1]; + } + + /** + * Returns the path to the current controller (not including the actual + * controller), as a web path. + * + * @param boolean return a full url, or only the path specifically + * @return string + */ + public function controller_path($full = TRUE) + { + return ($full) ? url::site(URI::$controller_path) : URI::$controller_path; + } + + /** + * Returns the current controller, as a web path. + * + * @param boolean return a full url, or only the controller specifically + * @return string + */ + public function controller($full = TRUE) + { + return ($full) ? url::site(URI::$controller_path.URI::$controller) : URI::$controller; + } + + /** + * Returns the current method, as a web path. + * + * @param boolean return a full url, or only the method specifically + * @return string + */ + public function method($full = TRUE) + { + return ($full) ? url::site(URI::$controller_path.URI::$controller.'/'.URI::$method) : URI::$method; + } + +} // End URI Class diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php new file mode 100644 index 0000000..9917fbb --- /dev/null +++ b/system/libraries/Validation.php @@ -0,0 +1,815 @@ +errors = array(); + $this->messages = array(); + } + + /** + * Create a copy of the current validation rules and change the array. + * + * @chainable + * @param array new array to validate + * @return Validation + */ + public function copy(array $array) + { + $copy = clone $this; + + $copy->exchangeArray($array); + + return $copy; + } + + /** + * Returns an array of all the field names that have filters, rules, or callbacks. + * + * @return array + */ + public function field_names() + { + // All the fields that are being validated + $fields = array_keys(array_merge + ( + $this->pre_filters, + $this->rules, + $this->callbacks, + $this->post_filters + )); + + // Remove wildcard fields + $fields = array_diff($fields, array('*')); + + return $fields; + } + + /** + * Returns the array values of the current object. + * + * @return array + */ + public function as_array() + { + return $this->getArrayCopy(); + } + + /** + * Returns the ArrayObject values, removing all inputs without rules. + * To choose specific inputs, list the field name as arguments. + * + * @param boolean return only fields with filters, rules, and callbacks + * @return array + */ + public function safe_array() + { + // Load choices + $choices = func_get_args(); + $choices = empty($choices) ? NULL : array_combine($choices, $choices); + + // Get field names + $fields = $this->field_names(); + + $safe = array(); + foreach ($fields as $field) + { + if ($choices === NULL OR isset($choices[$field])) + { + if (isset($this[$field])) + { + $value = $this[$field]; + + if (is_object($value)) + { + // Convert the value back into an array + $value = $value->getArrayCopy(); + } + } + else + { + // Even if the field is not in this array, it must be set + $value = NULL; + } + + // Add the field to the array + $safe[$field] = $value; + } + } + + return $safe; + } + + /** + * Add additional rules that will forced, even for empty fields. All arguments + * passed will be appended to the list. + * + * @chainable + * @param string rule name + * @return object + */ + public function allow_empty_rules($rules) + { + // Any number of args are supported + $rules = func_get_args(); + + // Merge the allowed rules + $this->empty_rules = array_merge($this->empty_rules, $rules); + + return $this; + } + + /** + * Sets or overwrites the label name for a field. + * + * @param string field name + * @param string label + * @return $this + */ + public function label($field, $label = NULL) + { + if ($label === NULL AND ($field !== TRUE OR $field !== '*') AND ! isset($this->labels[$field])) + { + // Set the field label to the field name + $this->labels[$field] = ucfirst(preg_replace('/[^\pL]+/u', ' ', $field)); + } + elseif ($label !== NULL) + { + // Set the label for this field + $this->labels[$field] = $label; + } + + return $this; + } + + /** + * Sets labels using an array. + * + * @param array list of field => label names + * @return $this + */ + public function labels(array $labels) + { + $this->labels = $labels + $this->labels; + + return $this; + } + + /** + * Converts a filter, rule, or callback into a fully-qualified callback array. + * + * @return mixed + */ + protected function callback($callback) + { + if (is_string($callback)) + { + if (strpos($callback, '::') !== FALSE) + { + $callback = explode('::', $callback); + } + elseif (function_exists($callback)) + { + // No need to check if the callback is a method + $callback = $callback; + } + elseif (method_exists($this, $callback)) + { + // The callback exists in Validation + $callback = array($this, $callback); + } + elseif (method_exists('valid', $callback)) + { + // The callback exists in valid:: + $callback = array('valid', $callback); + } + } + + if ( ! is_callable($callback, FALSE)) + { + if (is_array($callback)) + { + if (is_object($callback[0])) + { + // Object instance syntax + $name = get_class($callback[0]).'->'.$callback[1]; + } + else + { + // Static class syntax + $name = $callback[0].'::'.$callback[1]; + } + } + else + { + // Function syntax + $name = $callback; + } + + throw new Kohana_Exception('Callback %name% used for Validation is not callable', array('%name%' => $name)); + } + + return $callback; + } + + /** + * Add a pre-filter to one or more inputs. Pre-filters are applied before + * rules or callbacks are executed. + * + * @chainable + * @param callback filter + * @param string fields to apply filter to, use TRUE for all fields + * @return object + */ + public function pre_filter($filter, $field = TRUE) + { + if ($field === TRUE OR $field === '*') + { + // Use wildcard + $fields = array('*'); + } + else + { + // Add the filter to specific inputs + $fields = func_get_args(); + $fields = array_slice($fields, 1); + } + + // Convert to a proper callback + $filter = $this->callback($filter); + + foreach ($fields as $field) + { + // Add the filter to specified field + $this->pre_filters[$field][] = $filter; + } + + return $this; + } + + /** + * Add a post-filter to one or more inputs. Post-filters are applied after + * rules and callbacks have been executed. + * + * @chainable + * @param callback filter + * @param string fields to apply filter to, use TRUE for all fields + * @return object + */ + public function post_filter($filter, $field = TRUE) + { + if ($field === TRUE) + { + // Use wildcard + $fields = array('*'); + } + else + { + // Add the filter to specific inputs + $fields = func_get_args(); + $fields = array_slice($fields, 1); + } + + // Convert to a proper callback + $filter = $this->callback($filter); + + foreach ($fields as $field) + { + // Add the filter to specified field + $this->post_filters[$field][] = $filter; + } + + return $this; + } + + /** + * Add rules to a field. Validation rules may only return TRUE or FALSE and + * can not manipulate the value of a field. + * + * @chainable + * @param string field name + * @param callback rules (one or more arguments) + * @return object + */ + public function add_rules($field, $rules) + { + // Get the rules + $rules = func_get_args(); + $rules = array_slice($rules, 1); + + // Set a default field label + $this->label($field); + + if ($field === TRUE) + { + // Use wildcard + $field = '*'; + } + + foreach ($rules as $rule) + { + // Arguments for rule + $args = NULL; + + // False rule + $false_rule = FALSE; + + $rule_tmp = trim(is_string($rule) ? $rule : $rule[1]); + + // Should the rule return false? + if ($rule_tmp !== ($rule_name = ltrim($rule_tmp, '! '))) + { + $false_rule = TRUE; + } + + if (is_string($rule)) + { + // Use the updated rule name + $rule = $rule_name; + + // Have arguments? + if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches)) + { + // Split the rule into the function and args + $rule = $matches[1]; + $args = preg_split('/(?array_fields[$field] = $field; + } + + // Convert to a proper callback + $rule = $this->callback($rule); + + // Add the rule, with args, to the field + $this->rules[$field][] = array($rule, $args, $false_rule); + } + + return $this; + } + + /** + * Add callbacks to a field. Callbacks must accept the Validation object + * and the input name. Callback returns are not processed. + * + * @chainable + * @param string field name + * @param callbacks callbacks (unlimited number) + * @return object + */ + public function add_callbacks($field, $callbacks) + { + // Get all callbacks as an array + $callbacks = func_get_args(); + $callbacks = array_slice($callbacks, 1); + + // Set a default field label + $this->label($field); + + if ($field === TRUE) + { + // Use wildcard + $field = '*'; + } + + foreach ($callbacks as $callback) + { + // Convert to a proper callback + $callback = $this->callback($callback); + + // Add the callback to specified field + $this->callbacks[$field][] = $callback; + } + + return $this; + } + + /** + * Validate by processing pre-filters, rules, callbacks, and post-filters. + * All fields that have filters, rules, or callbacks will be initialized if + * they are undefined. Validation will only be run if there is data already + * in the array. + * + * @param object Validation object, used only for recursion + * @param object name of field for errors + * @return bool + */ + public function validate($object = NULL, $field_name = NULL) + { + if ($object === NULL) + { + // Use the current object + $object = $this; + } + + $array = $this->safe_array(); + + // Get all defined field names + $fields = array_keys($array); + + foreach ($this->pre_filters as $field => $callbacks) + { + foreach ($callbacks as $callback) + { + if ($field === '*') + { + foreach ($fields as $f) + { + $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]); + } + } + else + { + $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]); + } + } + } + + foreach ($this->rules as $field => $callbacks) + { + foreach ($callbacks as $callback) + { + // Separate the callback, arguments and is false bool + list ($callback, $args, $is_false) = $callback; + + // Function or method name of the rule + $rule = is_array($callback) ? $callback[1] : $callback; + + if ($field === '*') + { + foreach ($fields as $f) + { + // Note that continue, instead of break, is used when + // applying rules using a wildcard, so that all fields + // will be validated. + + if (isset($this->errors[$f])) + { + // Prevent other rules from being evaluated if an error has occurred + continue; + } + + if (empty($array[$f]) AND ! in_array($rule, $this->empty_rules)) + { + // This rule does not need to be processed on empty fields + continue; + } + + $result = ($args === NULL) ? call_user_func($callback, $array[$f]) : call_user_func($callback, $array[$f], $args); + + if (($result == $is_false)) + { + $this->add_error($f, $rule, $args); + + // Stop validating this field when an error is found + continue; + } + } + } + else + { + if (isset($this->errors[$field])) + { + // Prevent other rules from being evaluated if an error has occurred + break; + } + + if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($array[$field])) + { + // This rule does not need to be processed on empty fields + continue; + } + + // Results of our test + $result = ($args === NULL) ? call_user_func($callback, $array[$field]) : call_user_func($callback, $array[$field], $args); + + if (($result == $is_false)) + { + $rule = $is_false ? '!'.$rule : $rule; + $this->add_error($field, $rule, $args); + + // Stop validating this field when an error is found + break; + } + } + } + } + + foreach ($this->callbacks as $field => $callbacks) + { + foreach ($callbacks as $callback) + { + if ($field === '*') + { + foreach ($fields as $f) + { + // Note that continue, instead of break, is used when + // applying rules using a wildcard, so that all fields + // will be validated. + + if (isset($this->errors[$f])) + { + // Stop validating this field when an error is found + continue; + } + + call_user_func($callback, $this, $f); + } + } + else + { + if (isset($this->errors[$field])) + { + // Stop validating this field when an error is found + break; + } + + call_user_func($callback, $this, $field); + } + } + } + + foreach ($this->post_filters as $field => $callbacks) + { + foreach ($callbacks as $callback) + { + if ($field === '*') + { + foreach ($fields as $f) + { + $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]); + } + } + else + { + $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]); + } + } + } + + // Swap the array back into the object + $this->exchangeArray($array); + + // Return TRUE if there are no errors + return $this->errors === array(); + } + + /** + * Add an error to an input. + * + * @chainable + * @param string input name + * @param string unique error name + * @param string arguments to pass to lang file + * @return object + */ + public function add_error($field, $name, $args = NULL) + { + $this->errors[$field] = array($name, $args); + + return $this; + } + + /** + * Return the errors array. + * + * @param boolean load errors from a message file + * @return array + */ + public function errors($file = NULL) + { + if ($file === NULL) + { + $errors = array(); + foreach($this->errors as $field => $error) + { + $errors[$field] = $error[0]; + } + return $errors; + } + else + { + $errors = array(); + foreach ($this->errors as $input => $error) + { + // Locations to check for error messages + $error_locations = array + ( + "validation/{$file}.{$input}.{$error[0]}", + "validation/{$file}.{$input}.default", + "validation/default.{$error[0]}" + ); + + if (($message = Kohana::message($error_locations[0])) !== $error_locations[0]) + { + // Found a message for this field and error + } + elseif (($message = Kohana::message($error_locations[1])) !== $error_locations[1]) + { + // Found a default message for this field + } + elseif (($message = Kohana::message($error_locations[2])) !== $error_locations[2]) + { + // Found a default message for this error + } + else + { + // No message exists, display the path expected + $message = "validation/{$file}.{$input}.{$error[0]}"; + } + + // Start the translation values list + $values = array(':field' => __($this->labels[$input])); + + if ( ! empty($error[1])) + { + foreach ($error[1] as $key => $value) + { + // Add each parameter as a numbered value, starting from 1 + $values[':param'.($key + 1)] = __($value); + } + } + + // Translate the message using the default language + $errors[$input] = __($message, $values); + } + + return $errors; + } + } + + /** + * Rule: required. Generates an error if the field has an empty value. + * + * @param mixed input value + * @return bool + */ + public function required($str) + { + if (is_object($str) AND $str instanceof ArrayObject) + { + // Get the array from the ArrayObject + $str = $str->getArrayCopy(); + } + + if (is_array($str)) + { + return ! empty($str); + } + else + { + return ! ($str === '' OR $str === NULL OR $str === FALSE); + } + } + + /** + * Rule: matches. Generates an error if the field does not match one or more + * other fields. + * + * @param mixed input value + * @param array input names to match against + * @return bool + */ + public function matches($str, array $inputs) + { + foreach ($inputs as $key) + { + if ($str !== (isset($this[$key]) ? $this[$key] : NULL)) + return FALSE; + } + + return TRUE; + } + + /** + * Rule: length. Generates an error if the field is too long or too short. + * + * @param mixed input value + * @param array minimum, maximum, or exact length to match + * @return bool + */ + public function length($str, array $length) + { + if ( ! is_string($str)) + return FALSE; + + $size = mb_strlen($str); + $status = FALSE; + + if (count($length) > 1) + { + list ($min, $max) = $length; + + if ($size >= $min AND $size <= $max) + { + $status = TRUE; + } + } + else + { + $status = ($size === (int) $length[0]); + } + + return $status; + } + + /** + * Rule: depends_on. Generates an error if the field does not depend on one + * or more other fields. + * + * @param mixed field name + * @param array field names to check dependency + * @return bool + */ + public function depends_on($field, array $fields) + { + foreach ($fields as $depends_on) + { + if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL) + return FALSE; + } + + return TRUE; + } + + /** + * Rule: chars. Generates an error if the field contains characters outside of the list. + * + * @param string field value + * @param array allowed characters + * @return bool + */ + public function chars($value, array $chars) + { + return ! preg_match('![^'.implode('', $chars).']!u', $value); + } + +} // End Validation \ No newline at end of file diff --git a/system/libraries/View.php b/system/libraries/View.php new file mode 100644 index 0000000..ba482bb --- /dev/null +++ b/system/libraries/View.php @@ -0,0 +1,329 @@ +set_filename($name, $type); + } + + if (is_array($data) AND ! empty($data)) + { + // Preload data using array_merge, to allow user extensions + $this->kohana_local_data = array_merge($this->kohana_local_data, $data); + } + } + + /** + * Magic method access to test for view property + * + * @param string View property to test for + * @return boolean + */ + public function __isset($key = NULL) + { + return $this->is_set($key); + } + + /** + * Sets the view filename. + * + * @chainable + * @param string view filename + * @param string view file type + * @return object + */ + public function set_filename($name, $type = NULL) + { + if ($type == NULL) + { + // Load the filename and set the content type + $this->kohana_filename = Kohana::find_file('views', $name, TRUE); + $this->kohana_filetype = EXT; + } + else + { + // Check if the filetype is allowed by the configuration + if ( ! in_array($type, Kohana::config('view.allowed_filetypes'))) + throw new Kohana_Exception('The requested filetype, .:type:, is not allowed in your view configuration file', array(':type:' => $type)); + + // Load the filename and set the content type + $this->kohana_filename = Kohana::find_file('views', $name, TRUE, $type); + $this->kohana_filetype = Kohana::config('mimes.'.$type); + + if ($this->kohana_filetype == NULL) + { + // Use the specified type + $this->kohana_filetype = $type; + } + } + + return $this; + } + + /** + * Sets a view variable. + * + * @param string|array name of variable or an array of variables + * @param mixed value when using a named variable + * @return object + */ + public function set($name, $value = NULL) + { + if (is_array($name)) + { + foreach ($name as $key => $value) + { + $this->__set($key, $value); + } + } + else + { + $this->__set($name, $value); + } + + return $this; + } + + /** + * Checks for a property existence in the view locally or globally. Unlike the built in __isset(), + * this method can take an array of properties to test simultaneously. + * + * @param string $key property name to test for + * @param array $key array of property names to test for + * @return boolean property test result + * @return array associative array of keys and boolean test result + */ + public function is_set( $key = FALSE ) + { + // Setup result; + $result = FALSE; + + // If key is an array + if (is_array($key)) + { + // Set the result to an array + $result = array(); + + // Foreach key + foreach ($key as $property) + { + // Set the result to an associative array + $result[$property] = (array_key_exists($property, $this->kohana_local_data)) ? TRUE : FALSE; + } + } + else + { + // Otherwise just check one property + $result = (array_key_exists($key, $this->kohana_local_data)) ? TRUE : FALSE; + } + + // Return the result + return $result; + } + + /** + * Sets a bound variable by reference. + * + * @param string name of variable + * @param mixed variable to assign by reference + * @return object + */ + public function bind($name, & $var) + { + $this->kohana_local_data[$name] =& $var; + + return $this; + } + + /** + * Magically sets a view variable. + * + * @param string variable key + * @param string variable value + * @return void + */ + public function __set($key, $value) + { + $this->kohana_local_data[$key] = $value; + } + + /** + * Magically gets a view variable. + * + * @param string variable key + * @return mixed variable value if the key is found + * @return void if the key is not found + */ + public function &__get($key) + { + if (isset($this->kohana_local_data[$key])) + { + return $this->kohana_local_data[$key]; + } + elseif (isset($this->$key)) + { + return $this->$key; + } + else + { + throw new Kohana_Exception('Undefined view variable: :var', + array(':var' => $key)); + } + } + + /** + * Magically converts view object to string. + * + * @return string + */ + public function __toString() + { + try + { + return $this->render(); + } + catch (Exception $e) + { + Kohana_Exception::handle($e); + return (string) ''; + } + } + + /** + * Renders a view. + * + * @param boolean set to TRUE to echo the output instead of returning it + * @param callback special renderer to pass the output through + * @param callback modifier to pass the data through before rendering + * @return string if print is FALSE + * @return void if print is TRUE + */ + public function render($print = FALSE, $renderer = FALSE, $modifier = FALSE) + { + if (empty($this->kohana_filename)) + throw new Kohana_Exception('You must set the the view filename before calling render'); + + if (is_string($this->kohana_filetype)) + { + // Merge global and local data, local overrides global with the same name + $data = $this->kohana_local_data; + + if ($modifier !== FALSE AND is_callable($modifier, TRUE)) + { + // Pass the data through the user defined modifier + $data = call_user_func($modifier, $data); + } + + $output = $this->load_view($this->kohana_filename, $data); + + if ($renderer !== FALSE AND is_callable($renderer, TRUE)) + { + // Pass the output through the user defined renderer + $output = call_user_func($renderer, $output); + } + + if ($print === TRUE) + { + // Display the output + echo $output; + return; + } + } + else + { + // Set the content type and size + header('Content-Type: '.$this->kohana_filetype[0]); + + if ($print === TRUE) + { + if ($file = fopen($this->kohana_filename, 'rb')) + { + // Display the output + fpassthru($file); + fclose($file); + } + return; + } + + // Fetch the file contents + $output = file_get_contents($this->kohana_filename); + } + + return $output; + } + + /** + * Includes a View within the controller scope. + * + * @param string view filename + * @param array array of view variables + * @return string + */ + public function load_view($kohana_view_filename, $kohana_input_data) + { + if ($kohana_view_filename == '') + return; + + // Buffering on + ob_start(); + + // Import the view variables to local namespace + extract($kohana_input_data, EXTR_SKIP); + + try + { + include $kohana_view_filename; + } + catch (Exception $e) + { + ob_end_clean(); + throw $e; + } + + // Fetch the output and close the buffer + return ob_get_clean(); + } +} // End View diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php new file mode 100644 index 0000000..9741509 --- /dev/null +++ b/system/libraries/drivers/Cache.php @@ -0,0 +1,42 @@ +config = $config; + $this->config['directory'] = str_replace('\\', '/', realpath($this->config['directory'])).'/'; + + if ( ! is_dir($this->config['directory']) OR ! is_writable($this->config['directory'])) + throw new Cache_Exception('The configured cache directory, :directory:, is not writable.', array(':directory:' => $this->config['directory'])); + } + + /** + * Finds an array of files matching the given id or tag. + * + * @param string cache key or tag + * @param bool search for tags + * @return array of filenames matching the id or tag + */ + public function exists($keys, $tag = FALSE) + { + if ($keys === TRUE) + { + // Find all the files + return glob($this->config['directory'].'*~*~*'); + } + elseif ($tag === TRUE) + { + // Find all the files that have the tag name + $paths = array(); + + foreach ( (array) $keys as $tag) + { + $paths = array_merge($paths, glob($this->config['directory'].'*~*'.$tag.'*~*')); + } + + // Find all tags matching the given tag + $files = array(); + + foreach ($paths as $path) + { + // Split the files + $tags = explode('~', basename($path)); + + // Find valid tags + if (count($tags) !== 3 OR empty($tags[1])) + continue; + + // Split the tags by plus signs, used to separate tags + $item_tags = explode('+', $tags[1]); + + // Check each supplied tag, and match aginst the tags on each item. + foreach ($keys as $tag) + { + if (in_array($tag, $item_tags)) + { + // Add the file to the array, it has the requested tag + $files[] = $path; + } + } + } + + return $files; + } + else + { + $paths = array(); + + foreach ( (array) $keys as $key) + { + // Find the file matching the given key + $paths = array_merge($paths, glob($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~*')); + } + + return $paths; + } + } + + public function set($items, $tags = NULL, $lifetime = NULL) + { + if ($lifetime !== 0) + { + // File driver expects unix timestamp + $lifetime += time(); + } + + + if ( ! is_null($tags) AND ! empty($tags)) + { + // Convert the tags into a string list + $tags = implode('+', (array) $tags); + } + + $success = TRUE; + + foreach ($items as $key => $value) + { + if (is_resource($value)) + throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.'); + + // Remove old cache file + $this->delete($key); + + if ( ! (bool) file_put_contents($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~'.$tags.'~'.$lifetime, serialize($value))) + { + $success = FALSE; + } + } + + return $success; + } + + public function get($keys, $single = FALSE) + { + $items = array(); + + if ($files = $this->exists($keys)) + { + // Turn off errors while reading the files + $ER = error_reporting(0); + + foreach ($files as $file) + { + // Validate that the item has not expired + if ($this->expired($file)) + continue; + + list($key, $junk) = explode('~', basename($file), 2); + + if (($data = file_get_contents($file)) !== FALSE) + { + // Unserialize the data + $data = unserialize($data); + } + else + { + $data = NULL; + } + + $items[$key] = $data; + } + + // Turn errors back on + error_reporting($ER); + } + + // Reutrn a single item if only one key was requested + if ($single) + { + return (count($items) > 0) ? current($items) : NULL; + } + else + { + return $items; + } + } + + /** + * Get cache items by tag + */ + public function get_tag($tags) + { + // An array will always be returned + $result = array(); + + if ($paths = $this->exists($tags, TRUE)) + { + // Find all the files with the given tag + foreach ($paths as $path) + { + // Get the id from the filename + list($key, $junk) = explode('~', basename($path), 2); + + if (($data = $this->get($key, TRUE)) !== FALSE) + { + // Add the result to the array + $result[$key] = $data; + } + } + } + + return $result; + } + + /** + * Delete cache items by keys or tags + */ + public function delete($keys, $tag = FALSE) + { + $success = TRUE; + + $paths = $this->exists($keys, $tag); + + // Disable all error reporting while deleting + $ER = error_reporting(0); + + foreach ($paths as $path) + { + // Remove the cache file + if ( ! unlink($path)) + { + Kohana_Log::add('error', 'Cache: Unable to delete cache file: '.$path); + $success = FALSE; + } + } + + // Turn on error reporting again + error_reporting($ER); + + return $success; + } + + /** + * Delete cache items by tag + */ + public function delete_tag($tags) + { + return $this->delete($tags, TRUE); + } + + /** + * Empty the cache + */ + public function delete_all() + { + return $this->delete(TRUE); + } + + /** + * Check if a cache file has expired by filename. + * + * @param string|array array of filenames + * @return bool + */ + protected function expired($file) + { + // Get the expiration time + $expires = (int) substr($file, strrpos($file, '~') + 1); + + // Expirations of 0 are "never expire" + return ($expires !== 0 AND $expires <= time()); + } +} // End Cache Memcache Driver diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php new file mode 100644 index 0000000..13d61d8 --- /dev/null +++ b/system/libraries/drivers/Cache/Memcache.php @@ -0,0 +1,132 @@ +config = $config; + $this->backend = new Memcache; + + $this->flags = (isset($config['compression']) AND $config['compression']) ? MEMCACHE_COMPRESSED : FALSE; + + foreach ($config['servers'] as $server) + { + // Make sure all required keys are set + $server += array('host' => '127.0.0.1', + 'port' => 11211, + 'persistent' => FALSE, + 'weight' => 1, + 'timeout' => 1, + 'retry_interval' => 15 + ); + + // Add the server to the pool + $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'], (int) $server['weight'], (int) $server['timeout'], (int) $server['retry_interval'], TRUE, array($this,'_memcache_failure_callback')); + } + } + + public function _memcache_failure_callback($host, $port) + { + $this->backend->setServerParams($host, $port, 1, -1, FALSE); + Kohana_Log::add('error', __('Cache: Memcache server down: :host:::port:',array(':host:' => $host,':port:' => $port))); + } + + public function set($items, $tags = NULL, $lifetime = NULL) + { + if ($lifetime !== 0) + { + // Memcache driver expects unix timestamp + $lifetime += time(); + } + + if ($tags !== NULL) + throw new Cache_Exception('Memcache driver does not support tags'); + + foreach ($items as $key => $value) + { + if (is_resource($value)) + throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.'); + + if ( ! $this->backend->set($key, $value, $this->flags, $lifetime)) + { + return FALSE; + } + } + + return TRUE; + } + + public function get($keys, $single = FALSE) + { + $items = $this->backend->get($keys); + + if ($single) + { + if ($items === FALSE) + return NULL; + + return (count($items) > 0) ? current($items) : NULL; + } + else + { + return ($items === FALSE) ? array() : $items; + } + } + + /** + * Get cache items by tag + */ + public function get_tag($tags) + { + throw new Cache_Exception('Memcache driver does not support tags'); + } + + /** + * Delete cache item by key + */ + public function delete($keys) + { + foreach ($keys as $key) + { + if ( ! $this->backend->delete($key)) + { + return FALSE; + } + } + + return TRUE; + } + + /** + * Delete cache items by tag + */ + public function delete_tag($tags) + { + throw new Cache_Exception('Memcache driver does not support tags'); + } + + /** + * Empty the cache + */ + public function delete_all() + { + return $this->backend->flush(); + } +} // End Cache Memcache Driver diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php new file mode 100644 index 0000000..6761983 --- /dev/null +++ b/system/libraries/drivers/Cache/Xcache.php @@ -0,0 +1,161 @@ +config = $config; + } + + public function set($items, $tags = NULL, $lifetime = NULL) + { + if ($tags !== NULL) + { + Kohana_Log::add('debug', __('Cache: XCache driver does not support tags')); + } + + foreach ($items as $key => $value) + { + if (is_resource($value)) + throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.'); + + if ( ! xcache_set($key, $value, $lifetime)) + { + return FALSE; + } + } + + return TRUE; + } + + public function get($keys, $single = FALSE) + { + $items = array(); + + foreach ($keys as $key) + { + if (xcache_isset($key)) + { + $items[$key] = xcache_get($key); + } + else + { + $items[$key] = NULL; + } + } + + if ($single) + { + return ($items === FALSE OR count($items) > 0) ? current($items) : NULL; + } + else + { + return ($items === FALSE) ? array() : $items; + } + } + + /** + * Get cache items by tag + */ + public function get_tag($tags) + { + Kohana_Log::add('debug', __('Cache: XCache driver does not support tags')); + return NULL; + } + + /** + * Delete cache item by key + */ + public function delete($keys) + { + foreach ($keys as $key) + { + if ( ! xcache_unset($key)) + { + return FALSE; + } + } + + return TRUE; + } + + /** + * Delete cache items by tag + */ + public function delete_tag($tags) + { + Kohana_Log::add('debug', __('Cache: XCache driver does not support tags')); + return NULL; + } + + /** + * Empty the cache + */ + public function delete_all() + { + $this->auth(); + $result = TRUE; + + for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) + { + if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL) + { + $result = FALSE; + break; + } + } + + // Undo the login + $this->auth(TRUE); + + return $result; + } + + private function auth($reverse = FALSE) + { + static $backup = array(); + + $keys = array('PHP_AUTH_USER', 'PHP_AUTH_PW'); + + foreach ($keys as $key) + { + if ($reverse) + { + if (isset($backup[$key])) + { + $_SERVER[$key] = $backup[$key]; + unset($backup[$key]); + } + else + { + unset($_SERVER[$key]); + } + } + else + { + $value = getenv($key); + + if ( ! empty($value)) + { + $backup[$key] = $value; + } + + $_SERVER[$key] = $this->config->{$key}; + } + } + } +} // End Cache XCache Driver diff --git a/system/libraries/drivers/Config.php b/system/libraries/drivers/Config.php new file mode 100644 index 0000000..a82684b --- /dev/null +++ b/system/libraries/drivers/Config.php @@ -0,0 +1,257 @@ +cache_lifetime = $cache_setting; + // Restore the cached configuration + $this->config = $this->load_cache(); + + if (count($this->config) > 0) + $this->loaded = TRUE; + + // Add the save cache method to system.shutshut event + Event::add('system.shutdown', array($this, 'save_cache')); + } + + } + + /** + * Gets a value from config. If required is TRUE + * then get will throw an exception if value cannot + * be loaded. + * + * @param string key the setting to get + * @param bool slash remove trailing slashes + * @param bool required is setting required? + * @return mixed + * @access public + */ + public function get($key, $slash = FALSE, $required = FALSE) + { + // Get the group name from the key + $group = explode('.', $key, 2); + $group = $group[0]; + + // Check for existing value and load it dynamically if required + if ( ! isset($this->config[$group])) + $this->config[$group] = $this->load($group, $required); + + // Get the value of the key string + $value = Kohana::key_string($this->config, $key); + + if ($slash === TRUE AND is_string($value) AND $value !== '') + { + // Force the value to end with "/" + $value = rtrim($value, '/').'/'; + } + + if (($required === TRUE) AND ($value === null)) + throw new Kohana_Config_Exception('Value not found in config driver'); + + $this->loaded = TRUE; + return $value; + } + + /** + * Sets a new value to the configuration + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function set($key, $value) + { + // Do this to make sure that the config array is already loaded + $this->get($key); + + if (substr($key, 0, 7) === 'routes.') + { + // Routes cannot contain sub keys due to possible dots in regex + $keys = explode('.', $key, 2); + } + else + { + // Convert dot-noted key string to an array + $keys = explode('.', $key); + } + + // Used for recursion + $conf =& $this->config; + $last = count($keys) - 1; + + foreach ($keys as $i => $k) + { + if ($i === $last) + { + $conf[$k] = $value; + } + else + { + $conf =& $conf[$k]; + } + } + + if (substr($key,0,12) === 'core.modules') + { + // Reprocess the include paths + Kohana::include_paths(TRUE); + } + + // Set config to changed + return $this->changed = TRUE; + } + + /** + * Clear the configuration + * + * @param string group + * @return bool + * @access public + */ + public function clear($group) + { + // Remove the group from config + unset($this->config[$group]); + + // Set config to changed + return $this->changed = TRUE; + } + + /** + * Checks whether the setting exists in + * config + * + * @param string $key + * @return bool + * @access public + */ + public function setting_exists($key) + { + return $this->get($key) === NULL; + } + + /** + * Loads a configuration group based on the setting + * + * @param string group + * @param bool required + * @return array + * @access public + * @abstract + */ + abstract public function load($group, $required = FALSE); + + /** + * Loads the cached version of this configuration driver + * + * @return array + * @access public + */ + public function load_cache() + { + // Load the cache for this configuration + $cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime); + + // If the configuration wasn't loaded from the cache + if ($cached_config === NULL) + $cached_config = array(); + + // Return the cached config + return $cached_config; + } + + /** + * Saves a cached version of this configuration driver + * + * @return bool + * @access public + */ + public function save_cache() + { + // If this configuration has changed + if ($this->get('core.internal_cache') !== FALSE AND $this->changed) + { + $data = $this->config; + + // Save the cache + return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime); + } + + return TRUE; + } +} // End Kohana_Config_Driver \ No newline at end of file diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php new file mode 100644 index 0000000..b2ca19b --- /dev/null +++ b/system/libraries/drivers/Config/Array.php @@ -0,0 +1,83 @@ + $args) + { + if ( ! $this->$func($args)) + return FALSE; + } + + return TRUE; + } + + /** + * Sanitize and normalize a geometry array based on the temporary image + * width and height. Valid properties are: width, height, top, left. + * + * @param array geometry properties + * @return void + */ + protected function sanitize_geometry( & $geometry) + { + list($width, $height) = $this->properties(); + + // Turn off error reporting + $reporting = error_reporting(0); + + // Width and height cannot exceed current image size + $geometry['width'] = min($geometry['width'], $width); + $geometry['height'] = min($geometry['height'], $height); + + // Set standard coordinates if given, otherwise use pixel values + if ($geometry['top'] === 'center') + { + $geometry['top'] = floor(($height / 2) - ($geometry['height'] / 2)); + } + elseif ($geometry['top'] === 'top') + { + $geometry['top'] = 0; + } + elseif ($geometry['top'] === 'bottom') + { + $geometry['top'] = $height - $geometry['height']; + } + + // Set standard coordinates if given, otherwise use pixel values + if ($geometry['left'] === 'center') + { + $geometry['left'] = floor(($width / 2) - ($geometry['width'] / 2)); + } + elseif ($geometry['left'] === 'left') + { + $geometry['left'] = 0; + } + elseif ($geometry['left'] === 'right') + { + $geometry['left'] = $width - $geometry['height']; + } + + // Restore error reporting + error_reporting($reporting); + } + + /** + * Return the current width and height of the temporary image. This is mainly + * needed for sanitizing the geometry. + * + * @return array width, height + */ + abstract protected function properties(); + + /** + * Process an image with a set of actions. + * + * @param string image filename + * @param array actions to execute + * @param string destination directory path + * @param string destination filename + * @param boolean render the image + * @param string background color + * @return boolean + */ + abstract public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL); + + /** + * Flip an image. Valid directions are horizontal and vertical. + * + * @param integer direction to flip + * @return boolean + */ + abstract function flip($direction); + + /** + * Crop an image. Valid properties are: width, height, top, left. + * + * @param array new properties + * @return boolean + */ + abstract function crop($properties); + + /** + * Resize an image. Valid properties are: width, height, and master. + * + * @param array new properties + * @return boolean + */ + abstract public function resize($properties); + + /** + * Rotate an image. Valid amounts are -180 to 180. + * + * @param integer amount to rotate + * @return boolean + */ + abstract public function rotate($amount); + + /** + * Sharpen and image. Valid amounts are 1 to 100. + * + * @param integer amount to sharpen + * @return boolean + */ + abstract public function sharpen($amount); + + /** + * Overlay a second image. Valid properties are: overlay_file, mime, x, y and transparency. + * + * @return boolean + */ + abstract public function composite($properties); + +} // End Image Driver \ No newline at end of file diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php new file mode 100644 index 0000000..6ffffe8 --- /dev/null +++ b/system/libraries/drivers/Image/GD.php @@ -0,0 +1,440 @@ + $image['file'])); + + // Make sure the image type is supported for saving + if (empty($save) OR ! function_exists($save)) + throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $dir.$file)); + + // Load the image + $this->image = $image; + + // Create the GD image resource + $this->tmp_image = $create($image['file']); + + // Get the quality setting from the actions + $quality = arr::remove('quality', $actions); + + if ($status = $this->execute($actions)) + { + // Prevent the alpha from being lost + imagealphablending($this->tmp_image, TRUE); + imagesavealpha($this->tmp_image, TRUE); + + switch ($save) + { + case 'imagejpeg': + // Default the quality to 95 + ($quality === NULL) and $quality = 95; + break; + case 'imagegif': + // Remove the quality setting, GIF doesn't use it + unset($quality); + break; + case 'imagepng': + // Always use a compression level of 9 for PNGs. This does not + // affect quality, it only increases the level of compression! + $quality = 9; + break; + } + + if ($render === FALSE) + { + // Set the status to the save return value, saving with the quality requested + $status = isset($quality) ? $save($this->tmp_image, $dir.$file, $quality) : $save($this->tmp_image, $dir.$file); + } + else + { + // Output the image directly to the browser + switch ($save) + { + case 'imagejpeg': + header('Content-Type: image/jpeg'); + break; + case 'imagegif': + header('Content-Type: image/gif'); + break; + case 'imagepng': + header('Content-Type: image/png'); + break; + } + + $status = isset($quality) ? $save($this->tmp_image, NULL, $quality) : $save($this->tmp_image); + } + + // Destroy the temporary image + imagedestroy($this->tmp_image); + } + + return $status; + } + + public function flip($direction) + { + // Get the current width and height + $width = imagesx($this->tmp_image); + $height = imagesy($this->tmp_image); + + // Create the flipped image + $flipped = $this->imagecreatetransparent($width, $height); + + if ($direction === Image::HORIZONTAL) + { + for ($x = 0; $x < $width; $x++) + { + $status = imagecopy($flipped, $this->tmp_image, $x, 0, $width - $x - 1, 0, 1, $height); + } + } + elseif ($direction === Image::VERTICAL) + { + for ($y = 0; $y < $height; $y++) + { + $status = imagecopy($flipped, $this->tmp_image, 0, $y, 0, $height - $y - 1, $width, 1); + } + } + else + { + // Do nothing + return TRUE; + } + + if ($status === TRUE) + { + // Swap the new image for the old one + imagedestroy($this->tmp_image); + $this->tmp_image = $flipped; + } + + return $status; + } + + public function crop($properties) + { + // Sanitize the cropping settings + $this->sanitize_geometry($properties); + + // Get the current width and height + $width = imagesx($this->tmp_image); + $height = imagesy($this->tmp_image); + + // Create the temporary image to copy to + $img = $this->imagecreatetransparent($properties['width'], $properties['height']); + + // Execute the crop + if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, $properties['left'], $properties['top'], $width, $height, $width, $height)) + { + // Swap the new image for the old one + imagedestroy($this->tmp_image); + $this->tmp_image = $img; + } + + return $status; + } + + public function resize($properties) + { + // Get the current width and height + $width = imagesx($this->tmp_image); + $height = imagesy($this->tmp_image); + + if (substr($properties['width'], -1) === '%') + { + // Recalculate the percentage to a pixel size + $properties['width'] = round($width * (substr($properties['width'], 0, -1) / 100)); + } + + if (substr($properties['height'], -1) === '%') + { + // Recalculate the percentage to a pixel size + $properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100)); + } + + // Recalculate the width and height, if they are missing + empty($properties['width']) and $properties['width'] = round($width * $properties['height'] / $height); + empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width); + + if ($properties['master'] === Image::AUTO) + { + // Change an automatic master dim to the correct type + $properties['master'] = (($width / $properties['width']) > ($height / $properties['height'])) ? Image::WIDTH : Image::HEIGHT; + } + + if (empty($properties['height']) OR $properties['master'] === Image::WIDTH) + { + // Recalculate the height based on the width + $properties['height'] = round($height * $properties['width'] / $width); + } + + if (empty($properties['width']) OR $properties['master'] === Image::HEIGHT) + { + // Recalculate the width based on the height + $properties['width'] = round($width * $properties['height'] / $height); + } + + // Test if we can do a resize without resampling to speed up the final resize + if ($properties['width'] > $width / 2 AND $properties['height'] > $height / 2) + { + // Presize width and height + $pre_width = $width; + $pre_height = $height; + + // The maximum reduction is 10% greater than the final size + $max_reduction_width = round($properties['width'] * 1.1); + $max_reduction_height = round($properties['height'] * 1.1); + + // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction + while ($pre_width / 2 > $max_reduction_width AND $pre_height / 2 > $max_reduction_height) + { + $pre_width /= 2; + $pre_height /= 2; + } + + // Create the temporary image to copy to + $img = $this->imagecreatetransparent($pre_width, $pre_height); + + if ($status = imagecopyresized($img, $this->tmp_image, 0, 0, 0, 0, $pre_width, $pre_height, $width, $height)) + { + // Swap the new image for the old one + imagedestroy($this->tmp_image); + $this->tmp_image = $img; + } + + // Set the width and height to the presize + $width = $pre_width; + $height = $pre_height; + } + + // Create the temporary image to copy to + $img = $this->imagecreatetransparent($properties['width'], $properties['height']); + + // Execute the resize + if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, 0, 0, $properties['width'], $properties['height'], $width, $height)) + { + // Swap the new image for the old one + imagedestroy($this->tmp_image); + $this->tmp_image = $img; + } + + return $status; + } + + public function rotate($amount) + { + // Use current image to rotate + $img = $this->tmp_image; + + // White, with an alpha of 0 + $transparent = imagecolorallocatealpha($img, 255, 255, 255, 127); + + // Rotate, setting the transparent color + $img = imagerotate($img, 360 - $amount, $transparent, -1); + + // Fill the background with the transparent "color" + imagecolortransparent($img, $transparent); + + // Merge the images + if ($status = imagecopymerge($this->tmp_image, $img, 0, 0, 0, 0, imagesx($this->tmp_image), imagesy($this->tmp_image), 100)) + { + // Prevent the alpha from being lost + imagealphablending($img, TRUE); + imagesavealpha($img, TRUE); + + // Swap the new image for the old one + imagedestroy($this->tmp_image); + $this->tmp_image = $img; + } + + return $status; + } + + public function sharpen($amount) + { + // Make sure that the sharpening function is available + if ( ! function_exists('imageconvolution')) + throw new Kohana_Exception('Your configured driver does not support the :method: image transformation.', array(':method:' => __FUNCTION__)); + + // Amount should be in the range of 18-10 + $amount = round(abs(-18 + ($amount * 0.08)), 2); + + // Gaussian blur matrix + $matrix = array + ( + array(-1, -1, -1), + array(-1, $amount, -1), + array(-1, -1, -1), + ); + + // Perform the sharpen + return imageconvolution($this->tmp_image, $matrix, $amount - 8, 0); + } + + public function composite($properties) + { + switch($properties['mime']) + { + case "image/jpeg": + $overlay_img = imagecreatefromjpeg($properties['overlay_file']); + break; + + case "image/gif": + $overlay_img = imagecreatefromgif($properties['overlay_file']); + break; + + case "image/png": + $overlay_img = imagecreatefrompng($properties['overlay_file']); + break; + } + + $this->imagecopymerge_alpha($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']); + + imagedestroy($overlay_img); + + return TRUE; + } + + /** + * A replacement for php's imagecopymerge() function that supports the alpha channel + * See php bug #23815: http://bugs.php.net/bug.php?id=23815 + * + * @param resource $dst_im Destination image link resource + * @param resource $src_im Source image link resource + * @param integer $dst_x x-coordinate of destination point + * @param integer $dst_y y-coordinate of destination point + * @param integer $src_x x-coordinate of source point + * @param integer $src_y y-coordinate of source point + * @param integer $src_w Source width + * @param integer $src_h Source height + * @param integer $pct Transparency percent (0 to 100) + */ + protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) + { + // Create a new blank image the site of our source image + $cut = imagecreatetruecolor($src_w, $src_h); + + // Copy the blank image into the destination image where the source goes + imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h); + + // Place the source image in the destination image + imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h); + imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); + } + + protected function properties() + { + return array(imagesx($this->tmp_image), imagesy($this->tmp_image)); + } + + /** + * Returns an image with a transparent background. Used for rotating to + * prevent unfilled backgrounds. + * + * @param integer image width + * @param integer image height + * @return resource + */ + protected function imagecreatetransparent($width, $height) + { + if ($width < 1) + { + $width = 1; + } + + if ($height < 1) + { + $height = 1; + } + + if (self::$blank_png === NULL) + { + // Decode the blank PNG if it has not been done already + self::$blank_png = imagecreatefromstring(base64_decode + ( + 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29'. + 'mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVHjaYvz//z/DYAYAAcTEMMgBQAANegcCBN'. + 'CgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQ'. + 'AANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoH'. + 'AgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB'. + '3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAgAEAMpcDTTQWJVEAAAAASUVORK5CYII=' + )); + + // Set the blank PNG width and height + self::$blank_png_width = imagesx(self::$blank_png); + self::$blank_png_height = imagesy(self::$blank_png); + } + + $img = imagecreatetruecolor($width, $height); + + // Resize the blank image + imagecopyresized($img, self::$blank_png, 0, 0, 0, 0, $width, $height, self::$blank_png_width, self::$blank_png_height); + + // Prevent the alpha from being lost + imagealphablending($img, FALSE); + imagesavealpha($img, TRUE); + + return $img; + } + +} // End Image GD Driver \ No newline at end of file diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php new file mode 100644 index 0000000..89b40b4 --- /dev/null +++ b/system/libraries/drivers/Image/GraphicsMagick.php @@ -0,0 +1,225 @@ +ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : ''; + + // Check to make sure the provided path is correct + if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext)) + throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program, :gm:.', array(':gm:' => 'gm'.$this->ext)); + + + // Set the installation directory + $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/'; + } + + /** + * Creates a temporary image and executes the given actions. By creating a + * temporary copy of the image before manipulating it, this process is atomic. + */ + public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL) + { + // Need to implement $background support + if ($background !== NULL) + throw new Kohana_Exception('The GraphicsMagick driver does not support setting a background color'); + + // We only need the filename + $image = $image['file']; + + // Unique temporary filename + $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.')); + + // Copy the image to the temporary file + copy($image, $this->tmp_image); + + // Quality change is done last + $quality = (int) arr::remove('quality', $actions); + + // Use 95 for the default quality + empty($quality) and $quality = 95; + + // All calls to these will need to be escaped, so do it now + $this->cmd_image = escapeshellarg($this->tmp_image); + $this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file); + + if ($status = $this->execute($actions)) + { + // Use convert to change the image into its final version. This is + // done to allow the file type to change correctly, and to handle + // the quality conversion in the most effective way possible. + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image)) + { + $this->errors[] = $error; + } + else + { + // Output the image directly to the browser + if ($render !== FALSE) + { + $contents = file_get_contents($this->tmp_image); + switch (substr($file, strrpos($file, '.') + 1)) + { + case 'jpg': + case 'jpeg': + header('Content-Type: image/jpeg'); + break; + case 'gif': + header('Content-Type: image/gif'); + break; + case 'png': + header('Content-Type: image/png'); + break; + } + echo $contents; + } + } + } + + // Remove the temporary image + unlink($this->tmp_image); + $this->tmp_image = ''; + + return $status; + } + + public function crop($prop) + { + // Sanitize and normalize the properties into geometry + $this->sanitize_geometry($prop); + + // Set the IM geometry based on the properties + $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']); + + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function flip($dir) + { + // Convert the direction into a GM command + $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip'; + + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function resize($prop) + { + switch ($prop['master']) + { + case Image::WIDTH: // Wx + $dim = escapeshellarg($prop['width'].'x'); + break; + case Image::HEIGHT: // xH + $dim = escapeshellarg('x'.$prop['height']); + break; + case Image::AUTO: // WxH + $dim = escapeshellarg($prop['width'].'x'.$prop['height']); + break; + case Image::NONE: // WxH! + $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!'); + break; + } + + // Use "convert" to change the width and height + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function rotate($amt) + { + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function sharpen($amount) + { + // Set the sigma, radius, and amount. The amount formula allows a nice + // spread between 1 and 100 without pixelizing the image badly. + $sigma = 0.5; + $radius = $sigma * 2; + $amount = round(($amount / 80) * 3.14, 2); + + // Convert the amount to an GM command + $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0'); + + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function composite($properties) + { + if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' composite').' -geometry ' . escapeshellarg('+'.$properties['x'].'+'.$properties['y']).' -dissolve '.escapeshellarg($properties['transparency']).' '.escapeshellarg($properties['overlay_file']).' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + return TRUE; + } + + protected function properties() + { + return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE); + } + +} // End Image GraphicsMagick Driver \ No newline at end of file diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php new file mode 100644 index 0000000..55c0ba2 --- /dev/null +++ b/system/libraries/drivers/Image/ImageMagick.php @@ -0,0 +1,233 @@ +ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : ''; + + // Check to make sure the provided path is correct + if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext)) + throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program, :im:', array(':im:' => 'convert'.$this->ext)); + + // Set the installation directory + $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/'; + } + + /** + * Creates a temporary image and executes the given actions. By creating a + * temporary copy of the image before manipulating it, this process is atomic. + */ + public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL) + { + // We only need the filename + $image = $image['file']; + + // Unique temporary filename + $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.')); + + // Copy the image to the temporary file + copy($image, $this->tmp_image); + + // Quality change is done last + $quality = (int) arr::remove('quality', $actions); + + // Use 95 for the default quality + empty($quality) and $quality = 95; + + if (is_string($background)) + { + // Set the background color + $this->background = escapeshellarg($background); + } + else + { + // Use a transparent background + $this->background = 'transparent'; + } + + // All calls to these will need to be escaped, so do it now + $this->cmd_image = escapeshellarg($this->tmp_image); + $this->new_image = $render ? $this->cmd_image : escapeshellarg($dir.$file); + + if ($status = $this->execute($actions)) + { + // Use convert to change the image into its final version. This is + // done to allow the file type to change correctly, and to handle + // the quality conversion in the most effective way possible. + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image)) + { + $this->errors[] = $error; + } + else + { + // Output the image directly to the browser + if ($render === TRUE) + { + $contents = file_get_contents($this->tmp_image); + switch (substr($file, strrpos($file, '.') + 1)) + { + case 'jpg': + case 'jpeg': + header('Content-Type: image/jpeg'); + break; + case 'gif': + header('Content-Type: image/gif'); + break; + case 'png': + header('Content-Type: image/png'); + break; + } + echo $contents; + } + } + } + + // Remove the temporary image + unlink($this->tmp_image); + $this->tmp_image = ''; + + return $status; + } + + public function crop($prop) + { + // Sanitize and normalize the properties into geometry + $this->sanitize_geometry($prop); + + // Set the IM geometry based on the properties + $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']); + + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -crop '.$geometry.'! '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function flip($dir) + { + // Convert the direction into a IM command + $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip'; + + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten '.$dir.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function resize($prop) + { + switch ($prop['master']) + { + case Image::WIDTH: // Wx + $dim = escapeshellarg($prop['width'].'x'); + break; + case Image::HEIGHT: // xH + $dim = escapeshellarg('x'.$prop['height']); + break; + case Image::AUTO: // WxH + $dim = escapeshellarg($prop['width'].'x'.$prop['height']); + break; + case Image::NONE: // WxH! + $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!'); + break; + } + + // Use "convert" to change the width and height + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function rotate($amt) + { + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function sharpen($amount) + { + // Set the sigma, radius, and amount. The amount formula allows a nice + // spread between 1 and 100 without pixelizing the image badly. + $sigma = 0.5; + $radius = $sigma * 2; + $amount = round(($amount / 80) * 3.14, 2); + + // Convert the amount to an IM command + $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0'); + + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + + return TRUE; + } + + public function composite($properties) + { + if ($error = exec(escapeshellcmd($this->dir.'composite'.$this->ext).' -geometry ' . escapeshellarg('+'.$properties['x'].'+'.$properties['y']).' -dissolve '.escapeshellarg($properties['transparency']).' '.escapeshellarg($properties['overlay_file']).' '.$this->cmd_image.' '.$this->cmd_image)) + { + $this->errors[] = $error; + return FALSE; + } + return TRUE; + } + + protected function properties() + { + return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE); + } + +} // End Image ImageMagick Driver \ No newline at end of file diff --git a/system/libraries/drivers/Log.php b/system/libraries/drivers/Log.php new file mode 100644 index 0000000..cd6dba7 --- /dev/null +++ b/system/libraries/drivers/Log.php @@ -0,0 +1,22 @@ +config = $config; + } + + abstract public function save(array $messages); +} \ No newline at end of file diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php new file mode 100644 index 0000000..19db974 --- /dev/null +++ b/system/libraries/drivers/Log/Database.php @@ -0,0 +1,40 @@ +config['group']) + ->insert($this->config['table']) + ->columns(array('date', 'level', 'message')); + + $run_insert = FALSE; + + foreach ($messages AS $message) + { + if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold']) + { + // Add new message to database + $insert->values($message); + + // There is data to insert + $run_insert = TRUE; + } + } + + // Update the database + if ($run_insert) + { + $insert->execute(); + } + } +} \ No newline at end of file diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php new file mode 100644 index 0000000..6ad565b --- /dev/null +++ b/system/libraries/drivers/Log/File.php @@ -0,0 +1,44 @@ +config['log_directory'].'/'.date('Y-m-d').'.log'.EXT; + + if ( ! is_file($filename)) + { + // Write the SYSPATH checking header + file_put_contents($filename, + ''.PHP_EOL.PHP_EOL); + + // Prevent external writes + chmod($filename, $this->config['posix_permissions']); + } + + foreach ($messages AS $message) + { + if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold']) + { + // Add a new message line + $messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message']; + } + } + + if ( ! empty($messages_to_write)) + { + // Write messages to log file + file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND); + } + } +} \ No newline at end of file diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php new file mode 100644 index 0000000..5da5d25 --- /dev/null +++ b/system/libraries/drivers/Log/Syslog.php @@ -0,0 +1,34 @@ + LOG_ERR, + 'alert' => LOG_WARNING, + 'info' => LOG_INFO, + 'debug' => LOG_DEBUG); + + public function save(array $messages) + { + // Open the connection to syslog + openlog($this->config['ident'], LOG_CONS, LOG_USER); + + do + { + // Load the next message + list ($date, $type, $text) = array_shift($messages); + + syslog($this->syslog_levels[$type], $text); + } + while ( ! empty($messages)); + + // Close connection to syslog + closelog(); + } +} \ No newline at end of file diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php new file mode 100644 index 0000000..e591b91 --- /dev/null +++ b/system/libraries/drivers/Session.php @@ -0,0 +1,70 @@ + 'apc', + * 'requests' => 10000 + * ); + * Lifetime does not need to be set as it is + * overridden by the session expiration setting. + * + * $Id: Cache.php 4729 2009-12-29 20:35:19Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Session_Cache_Driver implements Session_Driver { + + protected $cache; + protected $encrypt; + + public function __construct() + { + // Load Encrypt library + if (Kohana::config('session.encryption')) + { + $this->encrypt = new Encrypt; + } + + Kohana_Log::add('debug', 'Session Cache Driver Initialized'); + } + + public function open($path, $name) + { + $config = Kohana::config('session.storage'); + + if (empty($config)) + { + // Load the default group + $config = Kohana::config('cache.default'); + } + elseif (is_string($config)) + { + $name = $config; + + // Test the config group name + if (($config = Kohana::config('cache.'.$config)) === NULL) + throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name)); + } + + $config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration'); + $this->cache = new Cache($config); + + return is_object($this->cache); + } + + public function close() + { + return TRUE; + } + + public function read($id) + { + $id = 'session_'.$id; + if ($data = $this->cache->get($id)) + { + return Kohana::config('session.encryption') ? $this->encrypt->decode($data) : $data; + } + + // Return value must be string, NOT a boolean + return ''; + } + + public function write($id, $data) + { + if ( ! Session::$should_save) + return TRUE; + + $id = 'session_'.$id; + $data = Kohana::config('session.encryption') ? $this->encrypt->encode($data) : $data; + + return $this->cache->set($id, $data); + } + + public function destroy($id) + { + $id = 'session_'.$id; + return $this->cache->delete($id); + } + + public function regenerate() + { + session_regenerate_id(TRUE); + + // Return new session id + return session_id(); + } + + public function gc($maxlifetime) + { + // Just return, caches are automatically cleaned up + return TRUE; + } + +} // End Session Cache Driver diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php new file mode 100644 index 0000000..88f5e21 --- /dev/null +++ b/system/libraries/drivers/Session/Cookie.php @@ -0,0 +1,83 @@ +cookie_name = Kohana::config('session.name').'_data'; + + if (Kohana::config('session.encryption')) + { + $this->encrypt = Encrypt::instance(); + } + + Kohana_Log::add('debug', 'Session Cookie Driver Initialized'); + } + + public function open($path, $name) + { + return TRUE; + } + + public function close() + { + return TRUE; + } + + public function read($id) + { + $data = (string) cookie::get($this->cookie_name); + + if ($data == '') + return $data; + + return empty($this->encrypt) ? base64_decode($data) : $this->encrypt->decode($data); + } + + public function write($id, $data) + { + if ( ! Session::$should_save) + return TRUE; + + $data = empty($this->encrypt) ? base64_encode($data) : $this->encrypt->encode($data); + + if (strlen($data) > 4048) + { + Kohana_Log::add('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.'); + return FALSE; + } + + return cookie::set($this->cookie_name, $data, Kohana::config('session.expiration')); + } + + public function destroy($id) + { + return cookie::delete($this->cookie_name); + } + + public function regenerate() + { + session_regenerate_id(TRUE); + + // Return new id + return session_id(); + } + + public function gc($maxlifetime) + { + return TRUE; + } + +} // End Session Cookie Driver Class \ No newline at end of file diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php new file mode 100644 index 0000000..7b372d2 --- /dev/null +++ b/system/libraries/drivers/Session/Database.php @@ -0,0 +1,178 @@ +encrypt = Encrypt::instance(); + } + + if (is_array($config['storage'])) + { + if ( ! empty($config['storage']['group'])) + { + // Set the group name + $this->db = $config['storage']['group']; + } + + if ( ! empty($config['storage']['table'])) + { + // Set the table name + $this->table = $config['storage']['table']; + } + } + + Kohana_Log::add('debug', 'Session Database Driver Initialized'); + } + + public function open($path, $name) + { + return TRUE; + } + + public function close() + { + return TRUE; + } + + public function read($id) + { + // Load the session + $query = db::select('data') + ->from($this->table) + ->where('session_id', '=', $id) + ->limit(1) + ->execute($this->db); + + if ($query->count() === 0) + { + // No current session + $this->session_id = NULL; + + return ''; + } + + // Set the current session id + $this->session_id = $id; + + // Load the data + $data = $query->current()->data; + + return ($this->encrypt === NULL) ? base64_decode($data) : $this->encrypt->decode($data); + } + + public function write($id, $data) + { + if ( ! Session::$should_save) + return TRUE; + + $data = array + ( + 'session_id' => $id, + 'last_activity' => time(), + 'data' => ($this->encrypt === NULL) ? base64_encode($data) : $this->encrypt->encode($data) + ); + + if ($this->session_id === NULL) + { + // Insert a new session + $query = db::insert($this->table, $data) + ->execute($this->db); + } + elseif ($id === $this->session_id) + { + // Do not update the session_id + unset($data['session_id']); + + // Update the existing session + $query = db::update($this->table) + ->set($data) + ->where('session_id', '=', $id) + ->execute($this->db); + } + else + { + // Update the session and id + $query = db::update($this->table) + ->set($data) + ->where('session_id', '=', $this->session_id) + ->execute($this->db); + + // Set the new session id + $this->session_id = $id; + } + + return (bool) $query->count(); + } + + public function destroy($id) + { + // Delete the requested session + db::delete($this->table) + ->where('session_id', '=', $id) + ->execute($this->db); + + // Session id is no longer valid + $this->session_id = NULL; + + return TRUE; + } + + public function regenerate() + { + // Generate a new session id + session_regenerate_id(); + + // Return new session id + return session_id(); + } + + public function gc($maxlifetime) + { + // Delete all expired sessions + $query = db::delete($this->table) + ->where('last_activity', '<', time() - $maxlifetime) + ->execute($this->db); + + Kohana_Log::add('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.'); + + return TRUE; + } + +} // End Session Database Driver diff --git a/system/messages/kohana/core.php b/system/messages/kohana/core.php new file mode 100644 index 0000000..0fa66b0 --- /dev/null +++ b/system/messages/kohana/core.php @@ -0,0 +1,37 @@ + array + ( + E_KOHANA => 'Framework Error', + E_PAGE_NOT_FOUND => 'Page Not Found', + E_DATABASE_ERROR => 'Database Error', + E_RECOVERABLE_ERROR => 'Recoverable Error', + E_ERROR => 'Fatal Error', + E_COMPILE_ERROR => 'Fatal Error', + E_CORE_ERROR => 'Fatal Error', + E_USER_ERROR => 'Fatal Error', + E_PARSE => 'Syntax Error', + E_WARNING => 'Warning Message', + E_COMPILE_WARNING => 'Warning Message', + E_CORE_WARNING => 'Warning Message', + E_USER_WARNING => 'Warning Message', + E_STRICT => 'Strict Mode Error', + E_NOTICE => 'Runtime Message', + E_USER_NOTICE => 'Runtime Message', + ), +); + +// E_DEPRECATED is only defined in PHP >= 5.3.0 +if (defined('E_DEPRECATED')) +{ + $messages['errors'][E_DEPRECATED] = 'Deprecated'; +} \ No newline at end of file diff --git a/system/messages/validation/default.php b/system/messages/validation/default.php new file mode 100644 index 0000000..88580a6 --- /dev/null +++ b/system/messages/validation/default.php @@ -0,0 +1,25 @@ + 'The :field field is required', + 'length' => 'The :field field must be between :param1 and :param2 characters long', + 'depends_on' => 'The :field field requires the :param1 field', + 'matches' => 'The :field field must be the same as :param1', + 'email' => 'The :field field must be a valid email address', + 'decimal' => 'The :field field must be a decimal with :param1 places', + 'digit' => 'The :field field must be a digit', + 'in_array' => 'The :field field must be one of the available options', + 'alpha_numeric' => 'The :field field must consist only of alphabetical or numeric characters', + 'alpha_dash ' => 'The :field field must consist only of alphabetical, numeric, underscore and dash characters', + 'numeric ' => 'The :field field must be a valid number', + 'url' => 'The :field field must be a valid url', + 'phone' => 'The :field field must be a valid phone number', +); diff --git a/system/views/kohana/error.php b/system/views/kohana/error.php new file mode 100644 index 0000000..aa6770c --- /dev/null +++ b/system/views/kohana/error.php @@ -0,0 +1,252 @@ + + + +
      +

      + + [ ]: + + + + +

      +
      +

      + +[ ] + +

      + + +
       $row) : ?>
      + + + +
        + $step): ?> +
      1. +

        + + + + [ ] + + [ ] + + + {} + + + » + ( +) +

        + + + + + + +
      2. + + +
      + + +
      +

      + +
      diff --git a/system/views/kohana/error_disabled.php b/system/views/kohana/error_disabled.php new file mode 100644 index 0000000..1024eb1 --- /dev/null +++ b/system/views/kohana/error_disabled.php @@ -0,0 +1,19 @@ + + + + + + <?php echo htmlspecialchars(__('Unable to Complete Request'), ENT_QUOTES, Kohana::CHARSET) ?> + + +
      +

      +

      +home page or try again.', + array('%site%' => htmlspecialchars(url::site(), ENT_QUOTES, Kohana::CHARSET), '%uri%' => htmlspecialchars(url::site(Router::$current_uri), ENT_QUOTES, Kohana::CHARSET))); +?> +

      +
      + + diff --git a/system/views/kohana/template.php b/system/views/kohana/template.php new file mode 100644 index 0000000..84ddbff --- /dev/null +++ b/system/views/kohana/template.php @@ -0,0 +1,36 @@ + + + + + + + + <?php echo html::chars(__($title)) ?> + + + + + + +

      + + + + + + \ No newline at end of file diff --git a/system/views/profiler/profiler.php b/system/views/profiler/profiler.php new file mode 100644 index 0000000..7b9ae95 --- /dev/null +++ b/system/views/profiler/profiler.php @@ -0,0 +1,37 @@ + + +
      +render(); +} +?> +

      number_format($execution_time, 3))) ?>

      +
      \ No newline at end of file diff --git a/system/views/profiler/table.css b/system/views/profiler/table.css new file mode 100644 index 0000000..41a1c9a --- /dev/null +++ b/system/views/profiler/table.css @@ -0,0 +1,53 @@ +#kohana-profiler .kp-table +{ + font-size: 1.0em; + color: #4D6171; + width: 100%; + border-collapse: collapse; + border-top: 1px solid #E5EFF8; + border-right: 1px solid #E5EFF8; + border-left: 1px solid #E5EFF8; + margin-bottom: 10px; +} +#kohana-profiler .kp-table td +{ + background-color: #FFFFFF; + border-bottom: 1px solid #E5EFF8; + padding: 3px; + vertical-align: top; +} +#kohana-profiler .kp-table .kp-title td +{ + font-weight: bold; + background-color: inherit; +} +#kohana-profiler .kp-table .kp-altrow td +{ + background-color: #F7FBFF; +} +#kohana-profiler .kp-table .kp-totalrow td +{ + background-color: #FAFAFA; + border-top: 1px solid #D2DCE5; + font-weight: bold; +} +#kohana-profiler .kp-table .kp-column +{ + width: 100px; + border-left: 1px solid #E5EFF8; + text-align: center; +} +#kohana-profiler .kp-table .kp-data, #kohana-profiler .kp-table .kp-name +{ + background-color: #FAFAFB; + vertical-align: top; +} +#kohana-profiler .kp-table .kp-name +{ + width: 200px; + border-right: 1px solid #E5EFF8; +} +#kohana-profiler .kp-table .kp-altrow .kp-data, #kohana-profiler .kp-table .kp-altrow .kp-name +{ + background-color: #F6F8FB; +} \ No newline at end of file diff --git a/system/views/profiler/table.php b/system/views/profiler/table.php new file mode 100644 index 0000000..7cdf79d --- /dev/null +++ b/system/views/profiler/table.php @@ -0,0 +1,24 @@ + + + + > + $column) + { + $class = empty($column['class']) ? '' : ' class="'.$column['class'].'"'; + $style = empty($column['style']) ? '' : ' style="'.$column['style'].'"'; + $value = $row['data'][$index]; + $value = (is_array($value) OR is_object($value)) ? '
      '.htmlspecialchars(print_r($value, TRUE), ENT_QUOTES, Kohana::CHARSET).'
      ' : htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET); + echo '' . wordwrap($value, 100, '
      ', true) . ''; + } + ?> + + +
      diff --git a/themes/admin_wind/css/fix-ie.css b/themes/admin_wind/css/fix-ie.css new file mode 100644 index 0000000..4546f9f --- /dev/null +++ b/themes/admin_wind/css/fix-ie.css @@ -0,0 +1,18 @@ +/** + * Fix display in IE 6 and 7 + */ + +.g-unavailable { + filter: alpha(opacity=40); +} + +.g-unavailable:hover { + filter: alpha(opacity=100); +} + +tr.g-error td, +tr.g-info td, +tr.g-success td, +tr.g-warning td { + background: none !important; +} diff --git a/themes/admin_wind/css/screen-rtl.css b/themes/admin_wind/css/screen-rtl.css new file mode 100644 index 0000000..79afabb --- /dev/null +++ b/themes/admin_wind/css/screen-rtl.css @@ -0,0 +1,400 @@ +/** + * Gallery 3 Admin Wind Theme Right-to-Left Screen Styles + */ + +.rtl { + direction: rtl; +} + +#g-header, +#g-content, +#g-sidebar, +#g-footer, +caption, +th, +#g-dialog, +.g-context-menu li a, +.g-message-box li, +#g-site-status li { + text-align: right; +} + +.g-text-right { + text-align: left; +} + +/* Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-text li, +.g-text li { + margin-left: 0; + margin-right: 1em; +} + +/* Messages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-error, +.g-info, +.g-success, +.g-warning, +#g-add-photos-status .g-success, +#g-add-photos-status .g-error { + background-position: center right; + padding-right: 30px !important; +} + +form li.g-error, +form li.g-info, +form li.g-success, +form li.g-warning { + padding-right: 0 !important; +} + +.g-left, +.g-inline li, +#g-content #g-album-grid .g-item, +.sf-menu li, +.g-breadcrumbs li, +.g-paginator li, +.g-buttonset li, +.ui-icon-left .ui-icon, +.g-short-form li, +form ul ul li, +input[type="submit"], +input[type="reset"], +input.checkbox, +input[type=checkbox], +input.radio, +input[type=radio] { + float: right; +} + +.g-right, +.ui-icon-right .ui-icon { + float: left; +} + +.g-inline li { + margin-right: 1em; +} + +.g-inline li.g-first { + margin-right: 0; +} + +.g-breadcrumbs li { + background: transparent url('../images/ico-separator-rtl.gif') no-repeat scroll right center; + padding: 1em 18px 1em 8px; +} + +.g-breadcrumbs .g-first { + background: none; + padding-right: 0; +} + +input.checkbox, +input[type="checkbox"], +input.radio, +input[type="radio"] { + margin-right: 0; + margin-left: .4em; +} + +#g-add-comment { + right: inherit; + left: 0; +} + +.ui-icon-left .ui-icon { + margin-left: .2em; +} + +.ui-icon-right .ui-icon { + margin-right: .2em; +} + +.g-group h4 { + padding: .5em .5em .5em 0; +} + +.g-group .g-user { + padding: .2em .5em 0 0; +} + +/* RTL Corner radius ~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-buttonset .ui-corner-tl { + -moz-border-radius-topleft: 0; + -webkit-border-top-left-radius: 0; + border-top-left-radius: 0; + -moz-border-radius-topright: 5px !important; + -webkit-border-top-right-radius: 5px !important; + border-top-right-radius: 5px !important; +} + +.g-buttonset .ui-corner-tr { + -moz-border-radius-topright: 0; + -webkit-border-top-right-radius: 0; + border-top-right-radius: 0; + -moz-border-radius-topleft: 5px !important; + -webkit-border-top-left-radius: 5px !important; + border-top-left-radius: 5px !important; +} + +.g-buttonset .ui-corner-bl { + -moz-border-radius-bottomleft: 0; + -webkit-border-bottom-left-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomright: 5px !important; + -webkit-border-bottom-right-radius: 5px !important; + border-bottom-right-radius: 5px !important; +} + +.g-buttonset .ui-corner-br { + -moz-border-radius-bottomright: 0; + -webkit-border-bottom-right-radius: 0; + border-bottom-right-radius: 0; + -moz-border-radius-bottomleft: 5px !important; + -webkit-border-bottom-left-radius: 5px !important; + border-bottom-left-radius: 5px !important; +} + +.g-buttonset .ui-corner-right, +.ui-progressbar .ui-corner-right { + -moz-border-radius-topright: 0; + -webkit-border-top-right-radius: 0; + border-top-right-radius: 0; + -moz-border-radius-topleft: 5px !important; + -webkit-border-top-left-radius: 5px !important; + border-top-left-radius: 5px !important; + -moz-border-radius-bottomright: 0; + -webkit-border-bottom-right-radius: 0; + border-bottom-right-radius: 0; + -moz-border-radius-bottomleft: 5px !important; + -webkit-border-bottom-left-radius: 5px !important; + border-bottom-left-radius: 5px !important; +} + +.g-buttonset .ui-corner-left, +.ui-progressbar .ui-corner-left { + -moz-border-radius-topleft: 0; + -webkit-border-top-left-radius: 0; + border-top-left-radius: 0; + -moz-border-radius-topright: 5px !important; + -webkit-border-top-right-radius: 5px !important; + border-top-right-radius: 5px !important; + -moz-border-radius-bottomleft: 0; + -webkit-border-bottom-left-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomright: 5px !important; + -webkit-border-bottom-right-radius: 5px !important; + border-bottom-right-radius: 5px !important; +} + +/* RTL Superfish ~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.sf-menu a { + border-left: none; + border-right:1px solid #fff; +} + +.sf-menu a.sf-with-ul { + padding-left: 2.25em; + padding-right: 1em; +} + +.sf-sub-indicator { + left: .75em !important; + right: auto; + background: url('../../../lib/superfish/images/arrows-ffffff-rtl.png') no-repeat -10px -100px; +} + +a > .sf-sub-indicator { + top: .8em; + background-position: -10px -100px; +} + +a:focus > .sf-sub-indicator, +a:hover > .sf-sub-indicator, +a:active > .sf-sub-indicator, +li:hover > a > .sf-sub-indicator, +li.sfHover > a > .sf-sub-indicator { + background-position: 0 -100px; +} + +.sf-menu ul .sf-sub-indicator { + background-position: 0 0; +} + +.sf-menu ul a > .sf-sub-indicator { + background-position: -10px 0; +} + +.sf-menu ul a:focus > .sf-sub-indicator, +.sf-menu ul a:hover > .sf-sub-indicator, +.sf-menu ul a:active > .sf-sub-indicator, +.sf-menu ul li:hover > a > .sf-sub-indicator, +.sf-menu ul li.sfHover > a > .sf-sub-indicator { + background-position: 0 0; +} + +.sf-menu li:hover ul, +.sf-menu li.sfHover ul { + right: 0; + left: auto; +} + +ul.sf-menu li li:hover ul, +ul.sf-menu li li.sfHover ul { + right: 12em; + left: auto; +} +ul.sf-menu li li li:hover ul, +ul.sf-menu li li li.sfHover ul { + right: 12em; + left: auto; +} + +.sf-shadow ul { + background: url('../../../lib/superfish/images/shadow.png') no-repeat bottom left; + padding: 0 0 9px 8px; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomleft: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-left-radius: 0; + -moz-border-radius-topleft: 17px; + -moz-border-radius-bottomright: 17px; + -webkit-border-top-left-radius: 17px; + -webkit-border-bottom-right-radius: 17px; + border-top-left-radius: 17px; + border-bottom-right-radius: 17px; +} + +/* RTL ThemeRoller ~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.ui-dialog .ui-dialog-titlebar { + padding: 0.5em 1em 0.3em 0.3em; +} + +.ui-dialog .ui-dialog-title { + float: right; +} + +.ui-dialog .ui-dialog-titlebar-close { + left: 0.3em; + right: auto; +} + +.ui-tabs .ui-tabs-nav li { + float: right; +} + +#g-content #g-album-grid .g-item, +#g-site-theme, +#g-admin-theme, +.g-selected img, +.g-available .g-block img, +#g-content #g-photo-stream .g-item, +li.g-group, +#g-server-add-admin { + float: right; +} + +#g-site-theme { + margin-right: 0; + margin-left: 1em; +} + +#g-admin-graphics .g-available .g-block { + float: right; + margin-left: 1em; + margin-right: 0em; +} + +#g-site-admin-menu { + left: auto; + right: 150px; +} + +#g-header #g-login-menu { + float: left; +} + +#g-header #g-login-menu li { + margin-left: 0; + padding-left: 0; + padding-right: 1.2em; +} + +#g-sidebar .g-block-content { + padding-left: 0em; + padding-right: 1em; +} + +.g-selected img, +.g-available .g-block img { + margin: 0 0 1em 1em; +} + +.g-button { + margin: 0 0 0 4px; +} + +/* RTL paginator ~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-paginator .g-info { + width: 35%; +} + +.g-paginator .g-text-right { + margin-left: 0; +} + +.g-paginator .ui-icon-seek-end { + background-position: -80px -160px; +} + +.g-paginator .ui-icon-seek-next { + background-position: -48px -160px; +} + +.g-paginator .ui-icon-seek-prev { + background-position: -32px -160px; +} + +.g-paginator .ui-icon-seek-first { + background-position: -64px -160px; +} + +#g-header #g-login-menu, +#g-header #g-quick-search-form { + clear: left; + float: left; +} + +#g-header #g-login-menu li { + margin-left: 0; + padding-left: 0; + padding-right: 1.2em; +} + +#g-site-menu { + left: auto; + right: 240px; +} + +#g-view-menu #g-slideshow-link { + background-image: url('../images/ico-view-slideshow-rtl.png'); +} + +#g-sidebar .g-block-content { + padding-right: 1em; + padding-left: 0; +} + +#g-footer #g-credits li { + padding-left: 1.2em !important; + padding-right: 0; +} diff --git a/themes/admin_wind/css/screen.css b/themes/admin_wind/css/screen.css new file mode 100644 index 0000000..5894238 --- /dev/null +++ b/themes/admin_wind/css/screen.css @@ -0,0 +1,1058 @@ +/** + * Gallery 3 Admin Wind Theme Screen Styles + * + * @requires YUI reset, font, grids CSS + * + * Sheet organization: + * 1) Basic HTML elements + * 2) Reusable content blocks + * 3) Page layout containers + * 4) Content blocks in specific layout containers + * 5) States and interactions + * 6) Positioning and order + * 7) Navigation and menus + * 8) jQuery and jQuery UI + * + * @todo Review g-buttonset-vertical + */ + +/** ******************************************************************* + * 1) Basic HTML elements + **********************************************************************/ + +body, html { + background-color: #ccc; + font-family: 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; +} + +p, +#g-content ul, +#g-content ol, +#g-content dl { + margin-bottom: 1em; +} + +em { + font-style: oblique; +} + +h1, h2, h3, h4, h5, strong, th { + font-weight: bold; +} + +h1 { + font-size: 1.7em; +} + +#g-dialog h1 { + font-size: 1.1em; +} + +h2 { + font-size: 1.4em; +} + +#g-sidebar .g-block h2 { + font-size: 1.2em; +} + +#g-sidebar .g-block li { + margin-bottom: .6em; +} + +h3 { + font-size: 1.2em; +} + +/* Links ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +a, +.g-menu a, +#g-dialog a, +.g-button, +.g-button:hover, +.g-button:active, +a.ui-state-hover, +input.ui-state-hover, +button.ui-state-hover { + color: #5382BF !important; + text-decoration: none; + -moz-outline-style: none; +} + +a:hover, +#g-dialog a:hover { + text-decoration: underline; +} + +.g-menu a:hover { + text-decoration: none; +} + +/* Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +ul.g-text li, +.g-text ul li { + list-style-type: disc; +} + +ol.g-text li, +.g-text ol li { + list-style-type: decimal; +} + +.g-text li, +.g-text li { + margin-left: 1em; +} + +/* Forms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +form { + margin: 0; +} + +fieldset { + border: 1px solid #ccc; + padding: 0 1em .8em 1em; +} + +#g-banner fieldset, +#g-sidebar fieldset { + border: none; + padding: 0; +} + +legend { + font-weight: bold; + margin: 0; + padding: 0 .2em; +} + +#g-banner legend, +#g-sidebar legend, +input[type="hidden"] { + display: none; +} + +input.textbox, +input[type="text"], +input[type="password"], +textarea { + border: 1px solid #e8e8e8; + border-top-color: #ccc; + border-left-color: #ccc; + clear: both; + color: #333; + width: 50%; +} + +textarea { + height: 12em; + width: 97%; +} + +input:focus, +input.textbox:focus, +input[type=text]:focus, +textarea:focus, +option:focus { + background-color: #ffc; + color: #000; +} + +input.checkbox, +input[type=checkbox], +input.radio, +input[type=radio] { + float: left; + margin-right: .4em; +} + +/* Form layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +form li { + margin: 0; + padding: 0 0 .2em 0; +} + +form ul { + margin-top: 0; +} + +form ul ul { + clear: both; +} + +form ul ul li { + float: left; +} + +input, +select, +textarea { + display: block; + clear: both; + padding: .2em; +} + +input[type="submit"], +input[type="reset"] { + display: inline; + clear: none; + float: left; +} + +/* Forms in dialogs and panels ~~~~~~~~~ */ + +#g-dialog ul li { + padding-bottom: .8em; +} + +#g-dialog fieldset, +#g-panel fieldset { + border: none; + padding: 0; +} + +#g-panel legend { + display: none; +} + +input[readonly] { + background-color: #F4F4FC; +} + +#g-dialog input.textbox, +#g-dialog input[type=text], +#g-dialog input[type=password], +#g-dialog textarea { + width: 97%; +} + +/* Short forms ~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-short-form legend, +.g-short-form label { + display: none; +} + +.g-short-form fieldset { + border: none; + padding: 0; +} + +.g-short-form li { + float: left; + margin: 0 !important; + padding: .4em 0; +} + +.g-short-form .textbox, +.g-short-form input[type=text] { + color: #666; + padding: .3em .6em; + width: 100%; +} + +.g-short-form .textbox.g-error { + border: 1px solid #f00; + color: #f00; + padding-left: 24px; +} + +.g-short-form .g-cancel { + display: block; + margin: .3em .8em; +} + +#g-sidebar .g-short-form li { + padding-left: 0; + padding-right: 0; +} + +fieldset { + margin-bottom: 1em; +} + +#g-content form ul li { + padding: .4em 0; +} + +#g-dialog form { + width: 270px; +} + +#g-dialog fieldset { + margin-bottom: 0; +} + +/* Tables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +table { + width: 100%; +} + +#g-content table { + margin: .6em 0 2em 0; +} + +caption, +th { + text-align: left; +} + +th, +td { + border: none; + border-bottom: 1px solid #ccc; + padding: .5em; + vertical-align: middle; +} + +th { + vertical-align: bottom; + white-space: nowrap; +} + +/* Text ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-text-small { + font-size: .8em; +} + +.g-text-big { + font-size: 1.2em; +} + +.g-text-right { + text-align: right; +} + +/** ******************************************************************* + * 2) Reusable content blocks + *********************************************************************/ + +.g-block, +#g-content #g-admin-dashboard .g-block { + background-color: #fff; + border: 1px solid #ccc; + padding: 1em; +} + +.g-block h2 { + background-color: #e8e8e8; + padding: .3em .8em; +} + +.g-block-content { + margin-top: 1em; +} + +#g-content .g-block { + border: none; + padding: 0; +} + +#g-content .g-selected, +#g-content .g-available .g-block { + border: 1px solid #ccc; + padding: .8em; +} + +.g-selected img, +.g-available .g-block img { + float: left; + margin: 0 1em 1em 0; +} + +.g-selected { + background: #e8e8e8; +} + +.g-available .g-installed-toolkit:hover { + cursor: pointer; + background: #eee; +} + +.g-available .g-button { + width: 96%; +} + +.g-selected .g-button { + display: none; +} + +.g-unavailable { + border-color: #999; + color: black; + opacity: .6; +} + +.g-info td { + background-color: transparent; + background-image: none; +} + +.g-success td { + background-color: transparent; + background-image: none; +} + +.g-error td { + background-color: #f6cbca; + background-image: none; +} + +.g-warning td { + background-color: #fcf9ce; + background-image: none; +} + +.g-module-status.g-info, +#g-log-entries .g-info, +.g-module-status.g-success, +#g-log-entries .g-success { + background-color: #fff; +} + +/*** ****************************************************************** + * 3) Page layout containers + *********************************************************************/ + +/* Dimension and scale ~~~~~~~~~~~~~~~~~~~ */ + +.g-one-quarter { + width: 25%; +} + +.g-one-third { + width: 33%; +} + +.g-one-half { + width: 50%; +} + +.g-two-thirds { + width: 66%; +} + +.g-three-quarters { + width: 75%; +} + +.g-whole { + width: 100%; +} + +/* Header ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-header #g-login-menu { + margin-top: 1em; + float: right; +} + +/* View container ~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-view { + background-color: #fff; + border: 1px solid #ccc; + border-bottom: none; + min-width: 974px !important; +} + +/* Layout containers ~~~~~~~~~~~~~~~~~~~~~ */ + +#g-header { + background-color: #e8e8e8; + border-bottom: 1px solid #ccc; + font-size: .8em; + margin-bottom: 20px; + padding: 0 20px; + position: relative; +} + +#g-content { + font-size: 1.1em; + padding: 0 2em; + width: 96%; +} + +#g-sidebar { + background-color: #fff; + font-size: .9em; + padding: 0 20px; + width: 220px; +} + +#g-footer { + background-color: #e8e8e8; + border-top: 1px solid #ccc; + font-size: .8em; + margin-top: 20px; + padding: 10px 20px; +} + +/** ******************************************************************* + * 4) Content blocks in specific layout containers + *********************************************************************/ + +/* Header ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-header #g-logo { + background: transparent url('../../../lib/images/logo.png') no-repeat 0 .5em; + color: #A5A5A5 !important; + display: block; + height: 65px; + padding-top: 5px; + width: 105px; +} + +#g-header #g-logo:hover { + color: #f60 !important; + text-decoration: none; +} + +#g-content .g-block h2 { + background-color: transparent; + padding-left: 0; +} + +#g-sidebar .g-block-content { + padding-left: 1em; +} + +.g-block .ui-dialog-titlebar { + margin: -1em -1em 0; +} + +#g-sidebar .g-block h2 { + background: none; +} + +/* Photo stream ~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-photo-stream { + background-color: #e8e8e8; +} + +#g-photo-stream .g-block-content ul { + border-right: 1px solid #e8e8e8; + height: 135px; + overflow: auto; + overflow: -moz-scrollbars-horizontal; /* for FF */ + overflow-x: scroll; /* scroll horizontal */ + overflow-y: hidden; /* Hide vertical*/ +} + +#g-content #g-photo-stream .g-item { + background-color: #fff; + border: 1px solid #e8e8e8; + border-right-color: #ccc; + border-bottom-color: #ccc; + float: left; + height: 90px; + overflow: hidden; + text-align: center; + width: 90px; +} + +#g-content .g-item { + background-color: #fff; + border: 1px solid #e8e8e8; + border-right-color: #ccc; + border-bottom-color: #ccc; + height: 90px; + padding: 14px 8px; + text-align: center; + width: 90px; +} + +/* Graphics settings ~~~~~~~~~~~~~~~~~~~~~ */ + +#g-admin-graphics .g-available .g-block { + clear: none; + float: left; + margin-right: 1em; + width: 30%; +} + +/* Appearance settings ~~~~~~~~~~~~~~~~~~~ */ + +#g-site-theme, +#g-admin-theme { + float: left; + width: 48%; +} + +#g-site-theme { + margin-right: 1em; +} + +/* Block admin ~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-admin-blocks-list { + float: left; + margin: 0 2em 2em 0; + width: 30%; +} + +.g-admin-blocks-list div:last-child { + border: .1em solid; + height: 100%; +} + +.g-admin-blocks-list ul { + height: 98%; + margin: .1em .1em; + padding: .1em; +} + +.g-admin-blocks-list ul li.g-draggable { + background-color: #e8e8e8; + margin: .5em; + padding: .3em .8em; +} + +/* In-line editing ~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-in-place-edit-message { + background-color: #FFF; +} + +/* Language options ~~~~~~~~~~~~~~~~~~~~~ */ + +#g-share-translations-form fieldset { + border: 0px; + margin: 0px; + padding: 0px; +} + +#g-share-translations-form fieldset legend { + display: none; +} + +/** ******************************************************************* + * 5) States and interactions + **********************************************************************/ + +.g-active, +.g-enabled, +.g-available, +.g-selected, +.g-highlight { + font-weight: bold; +} + +.g-inactive, +.g-disabled, +.g-unavailable, +.g-uneditable, +.g-locked, +.g-deselected, +.g-understate { + color: #ccc; + font-weight: normal; +} + +.g-editable { + padding: .2em .3em; +} + +.g-editable:hover { + background-color: #ffc; + cursor: text; +} + +.g-error, +.g-info, +.g-success, +.g-warning { + padding-left: 30px; +} + +form li.g-error, +form li.g-info, +form li.g-success, +form li.g-warning { + background-image: none; + padding: .3em .8em .3em 0; +} + +.g-short-form li.g-error { + padding: .3em 0; +} + +form.g-error input[type="text"], +li.g-error input[type="text"], +form.g-error input[type="password"], +li.g-error input[type="password"], +form.g-error input[type="checkbox"], +li.g-error input[type="checkbox"], +form.g-error input[type="radio"], +li.g-error input[type="radio"], +form.g-error textarea, +li.g-error textarea, +form.g-error select, +li.g-error select { + border: 2px solid #f00; + margin-bottom: .2em; +} + +.g-error, +.g-denied, +tr.g-error td.g-error, +#g-add-photos-status .g-error { + background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; + color: #f00; +} + +.g-info { + background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; +} + +.g-success, +.g-allowed, +#g-add-photos-status .g-success { + background: #d9efc2 url('../images/ico-success.png') no-repeat .4em 50%; +} + +tr.g-success { + background-image: none; +} + +tr.g-success td.g-success { + background-image: url('../images/ico-success.png'); +} + +.g-warning, +tr.g-warning td.g-warning { + background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; +} + +form .g-error { + background-color: #fff; + padding-left: 20px; +} + +.g-open { +} + +.g-closed { +} + +.g-installed { + background-color: #eeeeee; +} + +.g-default { + background-color: #c5dbec; + font-weight: bold; +} + +.g-draggable { + cursor: move; +} + +.g-draggable:hover { + border: 1px dashed #000; +} + +.ui-sortable .g-target, +.ui-state-highlight { + background-color: #fcf9ce; + border: 2px dotted #999; + height: 2em; + margin: 1em 0; +} + +/* Ajax loading indicator ~~~~~~~~~~~~~~~~ */ + +.g-loading-large, +.g-dialog-loading-large { + background: #e8e8e8 url('../images/loading-large.gif') no-repeat center center !important; +} + +.g-loading-small { + background: #e8e8e8 url('../images/loading-small.gif') no-repeat center center !important; +} + +/** ******************************************************************* + * 6) Positioning and order + **********************************************************************/ + +.g-left { + clear: none; + float: left; +} + +.g-right { + clear: none; + float: right; +} + +.g-first { +} + +.g-last { +} + +.g-even { + background-color: #fff; +} + +.g-odd { + background-color: #eee; +} + +/** ******************************************************************* + * 7) Navigation and menus + *********************************************************************/ + +/* Site Menu ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#g-site-admin-menu { + bottom: 0; + font-size: 1.2em; + left: 140px; + position: absolute; +} + +#g-site-admin-menu ul { + margin-bottom: 0; +} + +/** ******************************************************************* + * 8) jQuery and jQuery UI + *********************************************************************/ + +/* Generic block container ~~~~~~~~~~~~~~~ */ + +.g-block { + clear: both; + margin-bottom: 2.5em; +} + +.g-block-content { +} + +/* Buttons ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-button { + display: inline-block; + margin: 0 4px 0 0; + padding: .2em .4em; +} + +.g-button, +.g-button:hover, +.g-button:active { + cursor: pointer !important; + outline: 0; + text-decoration: none; + -moz-outline-style: none; +} + +button { + padding: 2px 4px 2px 4px; +} + +/* jQuery UI ThemeRoller buttons ~~~~~~~~~ */ + +.g-buttonset { + padding-left: 1px; +} + +.g-buttonset li { + float: left; +} + +.g-buttonset .g-button { + margin: 0; +} + +.ui-icon-left .ui-icon { + float: left; + margin-right: .2em; +} + +.ui-icon-right .ui-icon { + float: right; + margin-left: .2em; +} + +/* Rotate icon, ThemeRoller only provides one of these */ + +.ui-icon-rotate-ccw { + background-position: -192px -64px; +} + +.ui-icon-rotate-cw { + background-position: -208px -64px; +} + +.g-progress-bar { + height: 1em; + width: 100%; + margin: .5em 0; + display: inline-block; +} + +/* Status and validation messages ~~~~ */ + +.g-message-block { + background-position: .4em .3em; + border: 1px solid #ccc; + padding: 0; + margin-bottom: 1em; +} + +#g-action-status { +} + +#g-action-status li, +p#g-action-status, +div#g-action-status { + padding: .3em .3em .3em 30px; +} + +#g-site-status li { + border-bottom: 1px solid #ccc; + padding: .3em .3em .3em 30px; +} + +.g-module-status { + clear: both; + margin-bottom: 1em; +} + +.g-message { + background-position: 0 50%; +} + +/* Breadcrumbs ~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-breadcrumbs { + clear: both; + padding: 0 20px; +} + +.g-breadcrumbs li { + background: transparent url('../images/ico-separator.gif') no-repeat scroll left center; + float: left; + padding: 1em 8px 1em 18px; +} + +.g-breadcrumbs .g-first { + background: none; + padding-left: 0; +} + +.g-breadcrumbs li a, +.g-breadcrumbs li span { + display: block; +} + +#g-dialog ul.g-breadcrumbs { + margin-left: 0; + padding-left: 0; +} + +/* Pagination ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +.g-paginator { + padding: .2em 0; + width: 100%; +} + +.g-paginator li { + float: left; + width: 30%; +} + +.g-paginator .g-info { + background: none; + padding: .2em 0; + text-align: center; + width: 40%; +} + +/* Dialogs and panels ~~~~~~~~~~~~~~~~~~ */ + +#g-dialog { + text-align: left; +} + +#g-dialog legend { + display: none; +} + +#g-dialog .g-cancel { + margin: .4em 1em; +} + +#g-panel { + display: none; + padding: 1em; +} + +/* Inline layout ~~~~~~~~~~ */ + +.g-inline li { + float: left; + margin-left: 1.8em; + padding-left: 0 !important; +} + +.g-inline li.g-first { + margin-left: 0; +} + +/* Superfish menu overrides ~~~~~~~~~~~~~~ */ + +.sf-menu ul { + width: 12em; +} + +ul.sf-menu li li:hover ul, +ul.sf-menu li li.sfHover ul { + left: 12em; +} + +ul.sf-menu li li li:hover ul, +ul.sf-menu li li li.sfHover ul { + left: 12em; +} + + +.sf-menu li li, +.sf-menu li li ul li { + background-color: #bdd2ff; +} + +.sf-menu li:hover { + background-color: #dfe9ff; +} + +/* jQuery UI Dialog ~~~~~~~~~~~~~~~~~~~~~~ */ + +.ui-widget-overlay { + background: #000; + opacity: .7; +} + +#g-admin-dashboard .ui-state-highlight, +#g-sidebar .ui-state-highlight { + height: 2em; + margin-bottom: 1em; +} + +.g-buttonset-vertical a { + width: 8em !important; +} + +#g-admin-dashboard .ui-dialog-titlebar, +#g-admin-dashboard-sidebar .ui-dialog-titlebar { + padding: .2em .4em; +} diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_flat_0_aaaaaa_40x100.png b/themes/admin_wind/css/themeroller/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000..5b5dab2 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_flat_55_fbec88_40x100.png b/themes/admin_wind/css/themeroller/images/ui-bg_flat_55_fbec88_40x100.png new file mode 100644 index 0000000..47acaad Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_flat_55_fbec88_40x100.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_glass_75_d0e5f5_1x400.png b/themes/admin_wind/css/themeroller/images/ui-bg_glass_75_d0e5f5_1x400.png new file mode 100644 index 0000000..9fb564f Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_glass_75_d0e5f5_1x400.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_glass_85_dfeffc_1x400.png b/themes/admin_wind/css/themeroller/images/ui-bg_glass_85_dfeffc_1x400.png new file mode 100644 index 0000000..0149515 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_glass_85_dfeffc_1x400.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_glass_95_fef1ec_1x400.png b/themes/admin_wind/css/themeroller/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000..4443fdc Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/themes/admin_wind/css/themeroller/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png new file mode 100644 index 0000000..0cdbda3 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_f5f8f9_1x100.png b/themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_f5f8f9_1x100.png new file mode 100644 index 0000000..4f3faf8 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_f5f8f9_1x100.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_fcfdfd_1x100.png new file mode 100644 index 0000000..38c3833 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-bg_inset-hard_100_fcfdfd_1x100.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_217bc0_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_217bc0_256x240.png new file mode 100644 index 0000000..7719d48 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_217bc0_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_2e83ff_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..d9897d2 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_2e83ff_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_469bdd_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_469bdd_256x240.png new file mode 100644 index 0000000..d816185 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_469bdd_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_6da8d5_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_6da8d5_256x240.png new file mode 100644 index 0000000..b3c7d66 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_6da8d5_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_cd0a0a_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..2db88b7 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_cd0a0a_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_d8e7f3_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_d8e7f3_256x240.png new file mode 100644 index 0000000..2c8aac4 Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_d8e7f3_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/images/ui-icons_f9bd01_256x240.png b/themes/admin_wind/css/themeroller/images/ui-icons_f9bd01_256x240.png new file mode 100644 index 0000000..e81603f Binary files /dev/null and b/themes/admin_wind/css/themeroller/images/ui-icons_f9bd01_256x240.png differ diff --git a/themes/admin_wind/css/themeroller/ui.base.css b/themes/admin_wind/css/themeroller/ui.base.css new file mode 100644 index 0000000..1a1810c --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.base.css @@ -0,0 +1,7 @@ +@import "ui.core.css"; +@import "ui.theme.css"; +@import "ui.datepicker.css"; +@import "ui.dialog.css"; +@import "ui.progressbar.css"; +@import "ui.resizable.css"; +@import "ui.tabs.css"; diff --git a/themes/admin_wind/css/themeroller/ui.core.css b/themes/admin_wind/css/themeroller/ui.core.css new file mode 100644 index 0000000..d832ad7 --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.core.css @@ -0,0 +1,37 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://ui.jquery.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } \ No newline at end of file diff --git a/themes/admin_wind/css/themeroller/ui.datepicker.css b/themes/admin_wind/css/themeroller/ui.datepicker.css new file mode 100644 index 0000000..92986c9 --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.datepicker.css @@ -0,0 +1,62 @@ +/* Datepicker +----------------------------------*/ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; } +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:left; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +} \ No newline at end of file diff --git a/themes/admin_wind/css/themeroller/ui.dialog.css b/themes/admin_wind/css/themeroller/ui.dialog.css new file mode 100644 index 0000000..f10f409 --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.dialog.css @@ -0,0 +1,13 @@ +/* Dialog +----------------------------------*/ +.ui-dialog { position: relative; padding: .2em; width: 300px; } +.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } \ No newline at end of file diff --git a/themes/admin_wind/css/themeroller/ui.progressbar.css b/themes/admin_wind/css/themeroller/ui.progressbar.css new file mode 100644 index 0000000..bc0939e --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.progressbar.css @@ -0,0 +1,4 @@ +/* Progressbar +----------------------------------*/ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/themes/admin_wind/css/themeroller/ui.resizable.css b/themes/admin_wind/css/themeroller/ui.resizable.css new file mode 100644 index 0000000..44efeb2 --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.resizable.css @@ -0,0 +1,13 @@ +/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;} \ No newline at end of file diff --git a/themes/admin_wind/css/themeroller/ui.tabs.css b/themes/admin_wind/css/themeroller/ui.tabs.css new file mode 100644 index 0000000..70ed3ef --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.tabs.css @@ -0,0 +1,9 @@ +/* Tabs +----------------------------------*/ +.ui-tabs {padding: .2em;} +.ui-tabs .ui-tabs-nav { padding: .2em .2em 0 .2em; position: relative; } +.ui-tabs .ui-tabs-nav li { float: left; border-bottom: 0 !important; margin: 0 .2em -1px 0; padding: 0; list-style: none; } +.ui-tabs .ui-tabs-nav li a { display:block; text-decoration: none; padding: .5em 1em; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: .1em; border-bottom: 0; } +.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border: 0; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } \ No newline at end of file diff --git a/themes/admin_wind/css/themeroller/ui.theme.css b/themes/admin_wind/css/themeroller/ui.theme.css new file mode 100644 index 0000000..477252e --- /dev/null +++ b/themes/admin_wind/css/themeroller/ui.theme.css @@ -0,0 +1,243 @@ + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://ui.jquery.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://ui.jquery.com/themeroller/?tr=&ffDefault=Lucida%20Grande,%20Lucida%20Sans,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1.1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1em; } +.ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } +.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; outline: none; } +.ui-state-default a { color: #2e6e9e; text-decoration: none; outline: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; outline: none; } +.ui-state-hover a { color: #1d5987; text-decoration: none; outline: none; } +.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; outline: none; } +.ui-state-active a { color: #e17009; outline: none; text-decoration: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; } +.ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } +.ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } +.ui-corner-top { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } +.ui-corner-right { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } +.ui-corner-left { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } +.ui-corner-all { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; } \ No newline at end of file diff --git a/themes/admin_wind/images/avatar.jpg b/themes/admin_wind/images/avatar.jpg new file mode 100644 index 0000000..acad931 Binary files /dev/null and b/themes/admin_wind/images/avatar.jpg differ diff --git a/themes/admin_wind/images/ico-denied-inactive.png b/themes/admin_wind/images/ico-denied-inactive.png new file mode 100644 index 0000000..56db3ff Binary files /dev/null and b/themes/admin_wind/images/ico-denied-inactive.png differ diff --git a/themes/admin_wind/images/ico-denied-passive.png b/themes/admin_wind/images/ico-denied-passive.png new file mode 100644 index 0000000..1e99223 Binary files /dev/null and b/themes/admin_wind/images/ico-denied-passive.png differ diff --git a/themes/admin_wind/images/ico-denied.png b/themes/admin_wind/images/ico-denied.png new file mode 100644 index 0000000..08f2493 Binary files /dev/null and b/themes/admin_wind/images/ico-denied.png differ diff --git a/themes/admin_wind/images/ico-error.png b/themes/admin_wind/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/admin_wind/images/ico-error.png differ diff --git a/themes/admin_wind/images/ico-info.png b/themes/admin_wind/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/admin_wind/images/ico-info.png differ diff --git a/themes/admin_wind/images/ico-lock.png b/themes/admin_wind/images/ico-lock.png new file mode 100644 index 0000000..2ebc4f6 Binary files /dev/null and b/themes/admin_wind/images/ico-lock.png differ diff --git a/themes/admin_wind/images/ico-separator-rtl.gif b/themes/admin_wind/images/ico-separator-rtl.gif new file mode 100644 index 0000000..d9061a4 Binary files /dev/null and b/themes/admin_wind/images/ico-separator-rtl.gif differ diff --git a/themes/admin_wind/images/ico-separator.gif b/themes/admin_wind/images/ico-separator.gif new file mode 100644 index 0000000..3de2d0d Binary files /dev/null and b/themes/admin_wind/images/ico-separator.gif differ diff --git a/themes/admin_wind/images/ico-success-inactive.png b/themes/admin_wind/images/ico-success-inactive.png new file mode 100644 index 0000000..74b2032 Binary files /dev/null and b/themes/admin_wind/images/ico-success-inactive.png differ diff --git a/themes/admin_wind/images/ico-success-passive.png b/themes/admin_wind/images/ico-success-passive.png new file mode 100644 index 0000000..dc8d1de Binary files /dev/null and b/themes/admin_wind/images/ico-success-passive.png differ diff --git a/themes/admin_wind/images/ico-success.png b/themes/admin_wind/images/ico-success.png new file mode 100644 index 0000000..a9925a0 Binary files /dev/null and b/themes/admin_wind/images/ico-success.png differ diff --git a/themes/admin_wind/images/ico-warning.png b/themes/admin_wind/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/admin_wind/images/ico-warning.png differ diff --git a/themes/admin_wind/images/loading-large.gif b/themes/admin_wind/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/admin_wind/images/loading-large.gif differ diff --git a/themes/admin_wind/images/loading-small.gif b/themes/admin_wind/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/admin_wind/images/loading-small.gif differ diff --git a/themes/admin_wind/js/ui.init.js b/themes/admin_wind/js/ui.init.js new file mode 100644 index 0000000..4ed912f --- /dev/null +++ b/themes/admin_wind/js/ui.init.js @@ -0,0 +1,62 @@ +/** + * Initialize jQuery UI and Gallery Plugins + * @todo Move ui-corner-all assignments to theme admin views + */ + +$(document).ready(function(){ + + // Initialize Superfish menus + $("#g-site-admin-menu .g-menu").hide().addClass("sf-menu"); + $("#g-site-admin-menu .g-menu").superfish({ + delay: 500, + animation: { + opacity: "show", + height: "show" + }, + pathClass: "g-selected", + speed: "fast" + }).show(); + + // Initialize status message effects + $("#g-action-status li").gallery_show_message(); + + // Initialize modal dialogs + $(".g-dialog-link").gallery_dialog(); + + // Initialize short forms + $(".g-short-form").gallery_short_form(); + + // Initialize ajax links + $(".g-ajax-link").gallery_ajax(); + + // Initialize panels + $(".g-panel-link").gallery_panel(); + + if ($("#g-photo-stream").length) { + // Vertically align thumbs in photostream + $(".g-item").gallery_valign(); + } + + // Apply jQuery UI button css to submit inputs + $("input[type=submit]:not(.g-short-form input)").addClass("ui-state-default ui-corner-all"); + + // Round view menu buttons + if ($("#g-admin-comments-menu").length) { + $("#g-admin-comments-menu ul").removeClass("g-menu"); + $("#g-admin-comments-menu").addClass("g-buttonset"); + $("#g-admin-comments-menu a").addClass("g-button ui-state-default"); + $("#g-admin-comments-menu ul li:first a").addClass("ui-corner-left"); + $("#g-admin-comments-menu ul li:last a").addClass("ui-corner-right"); + } + + // Round corners + $(".g-selected").addClass("ui-corner-all"); + $(".g-available .g-block").addClass("ui-corner-all"); + $(".g-unavailable").addClass("ui-corner-all"); + + // Remove titles for menu options since we're displaying that text anyway + $(".sf-menu a, .sf-menu li").removeAttr("title"); + + // Initialize button hover effect + $.fn.gallery_hover_init(); +}); diff --git a/themes/admin_wind/theme.info b/themes/admin_wind/theme.info new file mode 100644 index 0000000..e2be928 --- /dev/null +++ b/themes/admin_wind/theme.info @@ -0,0 +1,10 @@ +name = "Gallery Wind" +description = "A crisp Site Administration theme with soft colors and drop down menus." +version = 1 +author = "Gallery Team" +admin = 1 +site = 0 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Themes:admin_wind" +discuss_url = "http://galleryproject.org/forum_theme_admin_wind" diff --git a/themes/admin_wind/thumbnail.png b/themes/admin_wind/thumbnail.png new file mode 100644 index 0000000..b07a4cc Binary files /dev/null and b/themes/admin_wind/thumbnail.png differ diff --git a/themes/admin_wind/views/admin.html.php b/themes/admin_wind/views/admin.html.php new file mode 100644 index 0000000..9a14914 --- /dev/null +++ b/themes/admin_wind/views/admin.html.php @@ -0,0 +1,104 @@ + + +html_attributes() ?> xml:lang="en" lang="en"> + + + start_combining("script,css") ?> + + <? if ($page_title): ?> + <?= t("Gallery Admin: %page_title", array("page_title" => $page_title)) ?> + <? else: ?> + <?= t("Admin dashboard") ?> + <? endif ?> + + " + type="image/x-icon" /> + " /> + + script("jquery.js") ?> + script("jquery.form.js") ?> + script("jquery-ui.js") ?> + script("gallery.common.js") ?> + + + script("gallery.ajax.js") ?> + script("gallery.dialog.js") ?> + script("superfish/js/superfish.js") ?> + script("jquery.scrollTo.js") ?> + + admin_head() ?> + + + script("ui.init.js") ?> + css("yui/reset-fonts-grids.css") ?> + css("themeroller/ui.base.css") ?> + css("superfish/css/superfish.css") ?> + css("screen.css") ?> + + css("screen-rtl.css") ?> + + + + + get_combined("css") ?> + + + get_combined("script") ?> + + + body_attributes() ?>> + admin_page_top() ?> + +
      + +
      + + site_status() ?> +
      + admin_header_top() ?> + + user_menu() ?> + + + + admin_header_bottom() ?> +
      +
      +
      +
      +
      + messages() ?> + +
      +
      +
      + +
      + +
      + +
      + +
      + admin_page_bottom() ?> + + diff --git a/themes/admin_wind/views/block.html.php b/themes/admin_wind/views/block.html.php new file mode 100644 index 0000000..d1d2d08 --- /dev/null +++ b/themes/admin_wind/views/block.html.php @@ -0,0 +1,18 @@ + + + + + diff --git a/themes/admin_wind/views/paginator.html.php b/themes/admin_wind/views/paginator.html.php new file mode 100644 index 0000000..b46d974 --- /dev/null +++ b/themes/admin_wind/views/paginator.html.php @@ -0,0 +1,88 @@ + + + + +
        +
      • + + + + + + + + + + + + + + + + + +
      • + +
      • + + + $first_visible_position, + "to_number" => $last_visible_position, + "count" => $total)) ?> + + $position, "total" => $total)) ?> + + +
      • + +
      • + + + + + + + + + + + + + + + + + +
      • +
      + \ No newline at end of file diff --git a/themes/greydragon/admin/controllers/admin_theme_options.php b/themes/greydragon/admin/controllers/admin_theme_options.php new file mode 100644 index 0000000..e8094e8 --- /dev/null +++ b/themes/greydragon/admin/controllers/admin_theme_options.php @@ -0,0 +1,860 @@ + +load_theme_info(); + return ($theme_info->version); + } + + private function get_theme_name() { + $theme_info = $this->load_theme_info(); + return ($theme_info->name); + } + + private function get_packlist($type, $filename) { + $packlist = array(); + $packroot = THEMEPATH . 'greydragon/css/' . $type . '/'; + + foreach (scandir($packroot) as $pack_name): + if (file_exists($packroot . "$pack_name/css/" . $filename . ".css")): + if ($pack_name[0] == "."): + continue; + endif; + + $packlist[$pack_name] = t($pack_name); + endif; + endforeach; + return $packlist; + } + + private function get_colorpacks() { + return $this->get_packlist('colorpacks', 'colors'); + } + + private function get_framepacks() { + return $this->get_packlist('framepacks', 'frame'); + } + + private function prerequisite_check($group, $id, $is_ok, $caption, $caption_ok, $caption_failed, $iswarning, $msg_error) { + $confirmation_caption = ($is_ok)? $caption_ok : $caption_failed; + $checkbox = $group->checkbox($id) + ->label($caption . " " . $confirmation_caption) + ->checked($is_ok) + ->disabled(true); + if ($is_ok): + $checkbox->class("g-success"); + elseif ($iswarning): + $checkbox->class("g-prerequisite g-warning")->error_messages("failed", $msg_error)->add_error("failed", 1); + else: + $checkbox->class("g-error")->error_messages("failed", $msg_error)->add_error("failed", 1); + endif; + } + + /* Convert old values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + protected function upgrade_settings() { + if (module::get_var("th_greydragon", "hide_thumbmeta")): + module::set_var("th_greydragon", "thumb_metamode", "hide"); + module::clear_var("th_greydragon", "hide_thumbmeta"); + endif; + + if (module::get_var("gallery", "appletouchicon_url")): + $temp = module::get_var("gallery", "appletouchicon_url"); + module::set_var("gallery", "apple_touch_icon_url", $temp); + module::clear_var("gallery", "appletouchicon_url"); + endif; + + if (module::get_var("th_greydragon", "flex_rows", FALSE)): + module::set_var("th_greydragon", "column_count", -1); + module::clear_var("th_greydragon", "flex_rows"); + endif; + + if (module::get_var("th_greydragon", "thumb_descmode") == "overlay_static"): + module::set_var("th_greydragon", "thumb_descmode", "overlay_top"); + endif; + + if (module::get_var("th_greydragon", "mainmenu_position") == "1"): + module::set_var("th_greydragon", "mainmenu_position", "top"); + endif; + + if (module::get_var("th_greydragon", "hide_breadcrumbs")): + module::set_var("th_greydragon", "breadcrumbs_position", "hide"); + module::clear_var("th_greydragon", "hide_breadcrumbs"); + endif; + + if (module::get_var("th_greydragon", "photonav_position")): + $temp = module::get_var("th_greydragon", "photonav_position"); + module::set_var("th_greydragon", "paginator_album", $temp); + module::set_var("th_greydragon", "paginator_photo", $temp); + module::clear_var("th_greydragon", "photonav_position"); + endif; + + if (module::get_var("th_greydragon", "sidebar_allowed") == "none"): + module::set_var("th_greydragon", "sidebar_allowed", "default"); + endif; + + if (module::get_var("th_greydragon", "thumb_topalign")): + module::set_var("th_greydragon", "thumb_imgalign", "top"); + module::clear_var("th_greydragon", "thumb_topalign"); + elseif ((module::get_var("th_greydragon", "thumb_ratio") == "photo") && (!module::get_var("th_greydragon", "thumb_imgalign"))): + module::set_var("th_greydragon", "thumb_imgalign", "center"); + endif; + } + + protected function isCurlInstalled() { + if (in_array('curl', get_loaded_extensions())) { + return true; + } else { + return false; + } + } + + protected function get_edit_form_admin() { + $this->upgrade_settings(); + + $form = new Forge("admin/theme_options/save/", "", null, array("id" =>"g-theme-options-form")); + + $rssmodulecheck = (module::is_active("rss") && module::info("rss")); + + /* Prerequisites ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("requirements")->label(t("Prerequisites")); + $gallery_ver = module::get_version("gallery"); + $this->prerequisite_check($group, "vercheck", $gallery_ver >= $this->min_gallery_ver, + t("Gallery 3 Core v.") . $this->min_gallery_ver . "+", t("Installed"), t("Required"), FALSE, sprintf(t("Check Failed. Minimum Required Version is %s. Found %s."), $this->min_gallery_ver, $gallery_ver)); + if (module::get_var("th_greydragon", "allow_root_page")): + $this->prerequisite_check($group, "rsscheck", $rssmodulecheck, + t("RSS Module"), t("Found"), t("not Found"), TRUE, t("Install RSS module to Enable Root Page Support")); + endif; + $this->prerequisite_check($group, "curlcheck", ($this->isCurlInstalled()), + t("PHP CURL Support is"), t("Enabled"), t("Disabled"), TRUE, t("Please make sure CURL support is enabled in PHP")); + + /* Suggested Modules ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("recommended")->label(t("Suggested Modules")); + + $check_infos = array(); + if (!module::get_var("th_greydragon", "hide_thumbmeta")): + $this->prerequisite_check($group, "info", (module::is_active("info") and module::info("info")), + t("Info Module"), t("Found"), t("Required"), FALSE, t("Check Failed. Module is required to display Thumb metadata.")); + endif; + + if (module::is_active("fancybox") && module::info("fancybox")): + $check_infos[] = array("module" => "fancybox", "module_name" => "Fancybox", "link" => ''); + endif; + if (module::is_active("colorbox") && module::info("colorbox")): + $check_infos[] = array("module" => "colorbox", "module_name" => "Colorbox", "link" => ''); + endif; + if (module::is_active("shadowbox") && module::info("shadowbox")): + $check_infos[] = array("module" => "shadowbox", "module_name" => "Shadowbox", "link" => ''); + endif; + + switch (count($check_infos)): + case 0: + $check_infos[] = array("module" => "fancybox", "module_name" => "Fancybox", "link" => ''); + $this->prerequisite_check($group, "fancybox", FALSE, + t("Fancybox/Colorbox/Shadowbox") . " " . t("Module"), t("Found"), t("not Found"), TRUE, sprintf(t("Install %smodule%s to Enable %s Support"), '', '', t("Fancybox"))); + break; + case 1: + $check_info = $check_infos[0]; + $this->prerequisite_check($group, $check_info["module"], TRUE, + t($check_info["module_name"]) . " " . t("Module"), t("Found"), t("not Found"), TRUE, sprintf(t("Install %smodule%s to Enable %s Support"), $check_info["link"], '', t($check_info["module_name"]))); + break; + default: + $list = ""; + $first = TRUE; + foreach ($check_infos as $key => $check_info): + if ($first): + $list .= $check_infos[$key]["module_name"]; + $first = FALSE; + else: + $list .= ", " . $check_infos[$key]["module_name"]; + endif; + endforeach; + + $this->prerequisite_check($group, "fancybox", FALSE, + t($list . " Modules are Active"), + "", + "", + TRUE, + t("Slideshow feature would not work correctly. Please activate just one of these modules.")); + + break; + endswitch; + + $check_info = $check_infos[0]; + $thumbnavcheck = module::is_active("thumbnav") and module::info("thumbnav"); + + $this->prerequisite_check($group, "kbdnavcheck", ((module::is_active("kbd_nav")) and (module::info("kbd_nav"))), + t("Kbd Navigation Module"), t("Found"), t("not Found"), TRUE, sprintf(t("Install %smodule%s to Enable Keyboard Navigation Support"), '', '')); + $this->prerequisite_check($group, "thumbnavcheck", $thumbnavcheck, + t("ThumbNav Module"), t("Found"), t("not Found"), TRUE, sprintf(t("Install %smodule%s to Enable Thumb Navigation Support"), '', '')); + + $thumb_ratio = module::get_var("th_greydragon", "thumb_ratio", "photo"); + $thumb_ratio_ex = FALSE; + switch ($thumb_ratio): + case "photo_ex": + $thumb_ratio = "photo"; + $thumb_ratio_ex = TRUE; + break; + case "film_ex": + $thumb_ratio = "film"; + $thumb_ratio_ex = TRUE; + break; + case "digital_ex": + $thumb_ratio = "digital"; + $thumb_ratio_ex = TRUE; + break; + case "wide_ex": + $thumb_ratio = "wide"; + $thumb_ratio_ex = TRUE; + break; + default: + break; + endswitch; + + /* General Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $sidebar_allowed = module::get_var("th_greydragon", "sidebar_allowed"); + $sidebar_visible = module::get_var("th_greydragon", "sidebar_visible"); + + $group = $form->group("edit_theme")->label(t("General Settings")); + $group->hidden("g_auto_delay") + ->value(module::get_var("th_greydragon", "auto_delay", 30)); + $group->input("row_count") + ->label(t("Rows per Album Page")) + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("th_greydragon", "row_count", 3)); + $group->dropdown("column_count") + ->label(t("Columns per Album Page")) + ->options(array("2" => t("2 columns"), "3" => t("3 columns"), "4" => t("4 columns"), "5" => t("5 columns"), "-1" => t("Flexible (3 x Number of Rows)"))) + ->selected(module::get_var("th_greydragon", "column_count", 3)); + $group->input("resize_size") + ->label(t("Resized Image Size (in pixels)")) + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("gallery", "resize_size")); + $group->input("logo_path") + ->label(t("Alternate Logo Image")) + ->value(module::get_var("th_greydragon", "logo_path")); + $group->input("favicon") + ->label(t("URL (or relative path) to your favicon.ico")) + ->value(module::get_var("gallery", "favicon_url")); + $group->input("appletouchicon") + ->label(t("URL (or relative path) to your Apple Touch icon")) + ->value(module::get_var("gallery", "apple_touch_icon_url")); + $group->input("header_text") + ->label(t("Header Text")) + ->value(module::get_var("gallery", "header_text")); + $group->input("footer_text") + ->label(t("Footer Text")) + ->value(module::get_var("gallery", "footer_text")); + $group->input("copyright") + ->label(t("Copyright Message")) + ->value(module::get_var("th_greydragon", "copyright")); + $group->dropdown("colorpack") + ->label(t("Color Pack/Site theme")) + ->options(self::get_colorpacks()) + ->selected(module::get_var("th_greydragon", "color_pack", "greydragon")); + $group->dropdown("framepack") + ->label(t("Thumb Frame Pack")) + ->options(self::get_framepacks()) + ->selected(module::get_var("th_greydragon", "frame_pack", "greydragon")); + + /* Advanced Options - General ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("edit_theme_adv_main")->label(t("Advanced Options - General")); + $group->dropdown("viewmode") + ->label(t("Theme View Mode")) + ->options(array("default" => t("Full Mode (Default)"), "mini" => t("Mini Mode"))) + ->selected(module::get_var("th_greydragon", "viewmode", "default")); + $group->dropdown("mainmenu_position") + ->label(t("Main Menu Position")) + ->options(array("default" => t("Bottom-Left (Default)"), "top" => t("Top-Left"), "bar" => t("Top Bar"))) + ->selected(module::get_var("th_greydragon", "mainmenu_position")); + $group->dropdown("loginmenu_position") + ->label(t("Login Menu Position")) + ->options(array("header" => t("Header"), "default" => t("Footer (Default)"), "hide" => t("Hide"))) + ->selected(module::get_var("th_greydragon", "loginmenu_position", "default")); + $group->dropdown("breadcrumbs_position") + ->label(t("Breadcrumbs Position")) + ->options(array("default" => t("Bottom-Right (Default)"), "bottom-left" => t("Bottom-Left"), "top-right" => t("Top-Right"), "top-left" => t("Top-Left"), "hide" => t("Hide"))) + ->selected(module::get_var("th_greydragon", "breadcrumbs_position")); + $group->dropdown("title_source") + ->label(t("Title Source")) + ->options(array("default" => t("Title (Default)"), "no-filename" => t("Title/Suppress File Name"), "description" => t("Description"))) + ->selected(module::get_var("th_greydragon", "title_source", "default")); + $group->input("custom_css_path") + ->label(t("File Name of custom.css or equivalent")) + ->value(module::get_var("th_greydragon", "custom_css_path")); + $group->input("resize_quality") + ->label(t("Resized Image Quallity (in %)")) + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("gallery", "image_quality", 100)); + $group->input("visible_title_length") + ->label(t("Visible Title Length")) + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("gallery", "visible_title_length", 15)); + + $group->checkbox("show_guest_menu") + ->label(t("Show Main Menu for Guest Users")) + ->checked(module::get_var("th_greydragon", "show_guest_menu")); + $group->checkbox("toolbar_large") + ->label(t("Use Large Toolbar Buttons")) + ->checked(module::get_var("th_greydragon", "toolbar_large")); + $group->checkbox("show_credits") + ->label(t("Show Site Credits")) + ->checked(module::get_var("gallery", "show_credits")); + $group->checkbox("breadcrumbs_showinroot") + ->label(t("Show Breadcrumbs in root album/root page")) + ->checked(module::get_var("th_greydragon", "breadcrumbs_showinroot")); + $group->checkbox("disable_seosupport") + ->label(t("Disallow Search Engine Indexing (No Bots)")) + ->checked(module::get_var("th_greydragon", "disable_seosupport")); + $group->checkbox("desc_allowbbcode") + ->label(t("Allow BBCode/HTML in Descriptions")) + ->checked(module::get_var("th_greydragon", "desc_allowbbcode")); + $group->checkbox("use_permalinks") + ->label(t("Use Permalinks for Navigation")) + ->checked(module::get_var("th_greydragon", "use_permalinks")); + + /* Advanced Options - Album page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("edit_theme_adv_album")->label(t("Advanced Options - Album Page")); + $group->dropdown("paginator_album") + ->label(t("Paginator Position")) + ->options(array("top" => t("Top"), "bottom" => t("Bottom"), "both" => t("Both"), "none" => t("None"))) + ->selected(module::get_var("th_greydragon", "paginator_album")); + $group->dropdown("album_descmode") + ->label(t("Description Display Mode")) + ->options(array("hide" => t("Hide"), "top" => t("Top"), "bottom" => t("Bottom"))) + ->selected(module::get_var("th_greydragon", "album_descmode")); + $group->checkbox("disablephotopage") + ->label(t("Disable Photo Page (use Slideshow Mode)")) + ->checked(module::get_var("th_greydragon", "disablephotopage")); + $group->checkbox("hidecontextmenu") + ->label(t("Hide Context Menu")) + ->checked(module::get_var("th_greydragon", "hidecontextmenu")); + + /* Advanced Options - Album page - Thumbs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("edit_theme_adv_thumb")->label(t("Advanced Options - Album page - Thumbs")); + $group->dropdown("thumb_ratio") + ->label(t("Aspect Ratio")) + ->options(array("photo" => t("Actual"), "film" => t("Film/Full Frame 3:2"), "digital" => t("Digital 4:3"), "wide" => t("Wide/HDTV 16:9"))) + ->selected($thumb_ratio); + $group->checkbox("thumb_ratio_ex") + ->label(t("Expanded Aspect Ratio (300px wide)")) + ->checked($thumb_ratio_ex); + $group->dropdown("thumb_imgalign") + ->label(t("Thumb Image Align")) + ->options(array("top" => t("Top"), "center" => t("Center"), "bottom" => t("Bottom"), "fit" => t("Fit"))) + ->selected(module::get_var("th_greydragon", "thumb_imgalign")); + + $group->dropdown("thumb_descmode_a") + ->label(t("Title Display Mode (Album)")) + ->options(array("overlay" => t("Overlay Top"), "overlay_top" => t("Overlay Top (Static)"), + "overlay_bottom" => t("Overlay Bottom (Static)"), "bottom" => t("Bottom"), "hide" => t("Hide"))) + ->selected(module::get_var("th_greydragon", "thumb_descmode_a")); + $group->dropdown("thumb_descmode") + ->label(t("Title Display Mode (Photo)")) + ->options(array("overlay" => t("Overlay Top"), "overlay_top" => t("Overlay Top (Static)"), + "overlay_bottom" => t("Overlay Bottom (Static)"), "bottom" => t("Bottom"), "hide" => t("Hide"))) + ->selected(module::get_var("th_greydragon", "thumb_descmode")); + $group->dropdown("thumb_metamode") + ->label(t("Meta Data Display Mode")) + ->options(array("default" => t("Overlay (Default)"), "merged" => t("Merge with Title"), "hide" => t("Hide"))) + ->selected(module::get_var("th_greydragon", "thumb_metamode", "default")); + $group->checkbox("thumb_random") + ->label(t("Randomize Thumb Image")) + ->checked(module::get_var("th_greydragon", "thumb_random")); + + /* Advanced Options - Photo page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("edit_theme_adv_photo")->label(t("Advanced Options - Photo Page")); + $group->dropdown("paginator_photo") + ->label(t("Paginator Position")) + ->options(array("top" => t("Top"), "bottom" => t("Bottom"), "both" => t("Both"), "none" => t("None"))) + ->selected(module::get_var("th_greydragon", "paginator_photo")); + $group->dropdown("photo_descmode") + ->label(t("Description Display Mode")) + ->options(array("overlay_top" => t("Overlay Top"), "overlay_bottom" => t("Overlay Bottom"), "overlay_top_s" => t("Overlay Top (Static)"), + "overlay_bottom_s" => t("Overlay Bottom (Static)"), "bottom" => t("Bottom"), "top" => t("Top"), "hide" => t("Hide"))) + ->selected(module::get_var("th_greydragon", "photo_descmode")); + $group->dropdown("photo_popupbox") + ->label(t($check_info["module_name"]) . " " . t("Mode")) + ->options(array("default" => t("Default (Slideshow/Preview)"), "preview" => t("Preview Only"), "none" => t("Disable"))) + ->selected(module::get_var("th_greydragon", "photo_popupbox")); + $group->checkbox("thumb_inpage") + ->label(t("Keep Thumb Nav Block on the side")) + ->checked(module::get_var("th_greydragon", "thumb_inpage")); + if (!$thumbnavcheck): + $group->thumb_inpage->disabled(true); + endif; + $group->checkbox("hide_photometa") + ->label(t("Hide Item Meta Data")) + ->checked(module::get_var("th_greydragon", "hide_photometa", TRUE)); + + /* Advanced Options - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("edit_theme_adv_root")->label(t("Advanced Options - Root Page")); + $group->checkbox("allow_root_page") + ->label(t("Allow root page")) + ->checked(module::get_var("th_greydragon", "allow_root_page")); + $group->checkbox("show_root_desc") + ->label(t("Show Gallery Description")) + ->checked(!module::get_var("th_greydragon", "hide_root_desc")); + $group->input("root_feed") + ->label(t("Slideshow RSS Feed URL")) + ->value(module::get_var("th_greydragon", "root_feed", "/gallery3/index.php/rss/feed/gallery/latest")); + $group->dropdown("root_cyclemode") + ->label(t("Cycle Effect (Default: Fade)")) + ->options(array("fade" => t("Fade"), "fadeout" => t("Fade Out"), "fadeZoom" => t("Fade Zoom"), "blindX" => t("Blind X"), "blindY" => t("Blind Y"), + "blindZ" => t("Blind Z"), "cover" => t("Cover"), "curtainX" => t("Curtain X"), "curtainY" => t("Curtain Y"), "growX" => t("Grow X"), + "growY" => t("Grow Y"), "none" => t("None"), "scrollUp" => t("Scroll Up"), "scrollDown" => t("Scroll Down"), "scrollLeft" => t("Scroll Left"), + "scrollRight" => t("Scroll Right"), "scrollHorz" => t("Scroll Horz"), "scrollVert" => t("Scroll Vert"), "shuffle" => t("Shuffle"), + "slideX" => t("Slide X"), "slideY" => t("Slide Y"), "toss" => t("Toss"), "turnUp" => t("Turn Up"), "turnDown" => t("Turn Down"), + "turnLeft" => t("Turn Left"), "turnRight" => t("Turn Right"), "uncover" => t("Uncover"), "wipe" => t("Wipe"), "zoom" => t("Zoom"))) + ->selected(module::get_var("th_greydragon", "root_cyclemode")); + $group->input("root_delay") + ->label(t("Slideshow Delay (Default: 15)")) + ->rules("required|valid_digit") + ->error_messages("required", t("You must enter a number")) + ->error_messages("valid_digit", t("You must enter a number")) + ->value(module::get_var("th_greydragon", "root_delay", "15")); + $group->checkbox("hide_root_sidebar") + ->label(t("Hide Sidebar")) + ->checked(module::get_var("th_greydragon", "hide_root_sidebar")); + $group->textarea("root_description") + ->label(t("Alternative Description (optional)")) + ->value(module::get_var("th_greydragon", "root_description")); + + /* Sidebar Options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("edit_theme_side")->label(t("Sidebar Options")); + $group->checkbox("hide_blockheader") + ->label(t("Hide Block Header")) + ->checked(module::get_var("th_greydragon", "hide_blockheader")); + $group->checkbox("sidebar_albumonly") + ->label(t("Show Sidebar for Albums Only")) + ->checked(module::get_var("th_greydragon", "sidebar_albumonly")); + $group->checkbox("sidebar_hideguest") + ->label(t("Show Sidebar for Guest Users")) + ->checked(!module::get_var("th_greydragon", "sidebar_hideguest")); + $group->dropdown("sidebar_allowed") + ->label(t("Allowed Sidebar Positions")) + ->options(array("any" => t("Any"), "left" => t("Left"), "right" => t("Right"), "bottom" => t("Bottom"), "top" => t("Top"), "default" => t("Default Only"))) + ->selected($sidebar_allowed); + $group->dropdown("sidebar_visible") + ->label(t("Default Sidebar Position")) + ->options(array("right" => t("Right"), "left" => t("Left"), "bottom" => t("Bottom"), "top" => t("Top"), "none" => t("No sidebar"))) + ->selected($sidebar_visible); + + /* Maintenance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + $group = $form->group("maintenance")->label(t("Maintenance")); + $group->checkbox("build_thumbs")->label(t("Mark all Thumbnails for Rebuild"))->checked(false); + $group->checkbox("build_resize")->label(t("Mark all Image Resizes for Rebuild"))->checked(false); + $group->checkbox("build_exif")->label(t("Mark Exif Info data for reload"))->checked(false); + $group->checkbox("purge_cache")->label(t("Purge cache data"))->checked(false); + $group->checkbox("reset_theme")->label(t("Reset Theme to a Default State"))->checked(false); + + module::event("theme_edit_form", $form); + + $form->submit("g-theme-options-save")->value(t("Save Changes")); + + return $form; + } + + protected function get_edit_form_help() { + $help = '
      '; + $help .= '' . t("Help") . '
        '; + $help .= '
      • ' . t("Prerequisites") . '

        +

        ' . t("REQUIREMENTS NEED TO BE MET FOR THE THEME TO FUNCTION PROPERLY.") . '
        + ' . t("Please refer to the section on the left.") . ' +

      • '; + + $help .= '
      • ' . t("General Settings") . '

        + ' . t("

        Theme is designed to display thumbnails as a table. + You can choose different number of Rows/Columns per Album Page or can use Flexible Columns. +

        Default G3 logo can be replaced with your own by providing Alternate Logo Image. Recommended logo size + is within 300x80px. If you need bigger space for your logo, CSS would have to be adjusted. Otherwise, Logo could be + suppressed altogether by providing Header Text to replace it. Footer Text would be simply placed next + to Site's credits. +

        Copyright Message can be placed in the right top corner of the footer. +

        Important feature of the theme is a support for Color Packs which is managed by Selected Color Pack Option. + Color Pack allows changing colors, styles, theme for the pages. By default 6 color packs are included, + but it could be easily extended. Visit Theme's Download page for additional information. + Frame Pack allows change Frames used for Thumbs in album pages.") . ' +

      • '; + + $help .= '
      • ' . t("Advanced Options - General") . '

        + ' . t("

        In order to allow easier integration of Gallery 3 within other site infrastructure, Theme supports Special View Mode + - when Mini Mode is selected, attempt would be made to minimize space associated with Header/Footer by trimming of + the some information. Use with caution. Theme supports CMD parameter to override this selection - add + \"?viewmode=default|full|mini\" to site URL in order to trigger a switch for specific browser session. When browser session + is restarted, view mode would revert back to default set in Admin Panel. +

        Show your appreciation for our hard work by allowing Show Site Credits or contributing to our Coffee Fund. +

        If main menu has functionality intended for guest users you can use Show Main Menu for Guest Users to keep it + visible. +
        You can go even further and move main menu to the top of the header with breadcrumbs taking it place by selecting + Alternate Header Layout.
        Then if you prefer breadcrumbs not being used, simply hide it with Hide + Breadcrumbs option. +
        Then you can decide if you want to Show Breadcrumbs in the root album/root page. +

        If you like to add some cool effects while navigating between pages, + enable Blend Page Transition. +

        Paginator Position could be changed to display it above and/or below the main content. +

        You can further fine tune theme using custom CSS file by providing file name (file need to be located in themes/custom + folder) or more specific location in form of your_path/your_file_name.css if different. +

        Block web crawlers from indexing your Gallery with Disallow Search Engine Indexing Option.") . ' +

      • '; + $help .= '
      • ' . t("Advanced Options - Album page - Thumbs") . '

        + ' . t("

        Options in this section adjust how Photo's Thumb images are displayed. +

        Aspect Ratio specifies layout/size of the thumb. Setting should be used with understanding that some + information may be out of visible area (crop). When switching to/from Actual Size, it is recommended to rebuild + thumbs so that proper settings are used for thumb resize logic (see Maintenance section below). Combined with + Expanded Aspect Ratio (300px wide) you can switch between 200px and 300px wide thumb images. +

        Title Display Mode and Meta Data Display Mode allows changing how Item's caption and Meta Data is + displayed. And selecting Merge with Title would place meta data with the Title. +

        Randomize your thumbs by enabling Randomize Thumb Image (Please use with caution as it does introduce extra + load for database.).") . ' +

      • '; + $help .= '
      • ' . t("Advanced Options - Photo Page") . '

        + ' . t("

        Options in this section adjust how individual Photo are presented to the viewer. +

        With ShadowBox Mode, theme's logic could be adjusted in how Slideshow module is integrated. +
        As with Title in Photo Thumbs, Description Display Mode changes how Photo Description is displayed. +
        You can choose to Keep Thumb Nav Block on the side of the Photo when Page is displayed with bottom aligned + or hidden Sidebar. +
        And if metadata (owner/clicks/etc.) is unnecessary, it could be removed with Hide Item Meta Data. +

        Theme allows use of BBCode/HTML in item's descriptions, to enable it select Allow BBCode/HTML in Descriptions.") . ' +

      • '; + $help .= '
      • ' . t("Advanced Options - Root Page") . '

        + ' . t("

        Special option which allows adding special root/landing page with slideshow utilizing specified Slideshow RSS Feed + URL (Default: /gallery3/index.php/rss/feed/gallery/latest). +

        To enable it, select Allow root page. +

        Add small description on the side of the slideshow with Show Gallery Description. +
        Adjust rotation delay with Slideshow Delay. +
        If Sidebar is not desired in the Root Page it could be hidden with Hide Sidebar option. +
        By default, description content is populated from description of the root album, but by providing Alternative + Description you can overwrite it.") . ' +

      • '; + $help .= '
      • ' . t("Sidebar Options") . '

        + ' . t("

        If Block's header is not desired, it could be removed using Hide Block Header. +

        Sidebar visibility could be limited to individual Photo pages with + Show Sidebar for Albums Only. +

        When sidebar is visible it can be placed on the left or right of the + screen or removed altogether using Allowed Sidebar Positions. + If more than one position is allowed, Default Sidebar Position + would indicate default state, but visitor would able change it later.") . ' +

      • '; + $help .= '
      • ' . t("Maintenance") . '

        + ' . t("

        Without changing image size, you can Mark all Resizes for Rebuild. + Then you need to visit Admin\Maintenance to initiate the process. +

        Same can be done for image thumbs with Mark all Thumbnails for Rebuild. +

        Mark Exif/IPTC Info for reload would mark all Exif or IPTC records as \"Dirty\" allowing it to be repopulated. +

        And just in case you think that something is not right, you can always Reset Theme to a Default State.") . ' +

      • '; + $help .= '
      '; + return $help; + } + + private function save_item_state($statename, $state, $value) { + if ($state): + module::set_var("th_greydragon", $statename, $value); + else: + module::clear_var("th_greydragon", $statename); + endif; + } + + protected function legacy() { + module::clear_var("th_greydragon", "photonav_top"); + module::clear_var("th_greydragon", "photonav_bottom"); + module::clear_var("th_greydragon", "hide_sidebar_photo"); + module::clear_var("th_greydragon", "hide_thumbdesc"); + module::clear_var("th_greydragon", "use_detailview"); + module::clear_var("th_greydragon", "horizontal_crop"); + module::clear_var("th_greydragon", "photo_shadowbox"); + module::clear_var("th_greydragon", "root_text"); + module::clear_var("th_greydragon", "enable_pagecache"); + + module::clear_var("th_greydragon", "navigator_album"); + module::clear_var("th_greydragon", "navigator_photo"); + + module::clear_var("th_greydragon", "blendpagetrans"); + module::clear_var("th_greydragon", "last_update"); + module::clear_var("th_greydragon", "thumb_quality"); + } + + protected function reset_theme() { + // Default core theme settings + module::set_var("gallery", "page_size", 9); + module::set_var("gallery", "resize_size", 640); + module::set_var("gallery", "thumb_size", 200); + module::set_var("gallery", "header_text", ""); + module::set_var("gallery", "footer_text", ""); + module::set_var("gallery", "show_credits", FALSE); + + module::clear_all_vars("th_greydragon"); + } + + public function save() { + site_status::clear("gd_init_configuration"); + access::verify_csrf(); + + $form = self::get_edit_form_admin(); + + if ($form->validate()): + $this->legacy(); + + if ($form->maintenance->reset_theme->value): + $this->reset_theme(); + module::event("theme_edit_form_completed", $form); + message::success(t("Theme details are reset")); + else: + // * General Settings **************************************************** + + $resize_size = $form->edit_theme->resize_size->value; + + $build_resize = $form->maintenance->build_resize->value; + $build_thumbs = $form->maintenance->build_thumbs->value; + $build_exif = $form->maintenance->build_exif->value; + if (module::is_active("iptc") and module::info("iptc")): + $build_iptc = $form->maintenance->build_iptc->value; + else: + $build_iptc = FALSE; + endif; + $purge_cache = $form->maintenance->purge_cache->value; + + $color_pack = $form->edit_theme->colorpack->value; + $frame_pack = $form->edit_theme->framepack->value; + $thumb_imgalign = $form->edit_theme_adv_thumb->thumb_imgalign->value; + $thumb_descmode_a = $form->edit_theme_adv_thumb->thumb_descmode_a->value; + $thumb_descmode = $form->edit_theme_adv_thumb->thumb_descmode->value; + $thumb_metamode = $form->edit_theme_adv_thumb->thumb_metamode->value; + $photo_descmode = $form->edit_theme_adv_photo->photo_descmode->value; + $photo_popupbox = $form->edit_theme_adv_photo->photo_popupbox->value; + $resize_quality = $form->edit_theme_adv_main->resize_quality->value; + + if ($build_resize): + graphics::remove_rule("gallery", "resize", "gallery_graphics::resize"); + graphics::add_rule("gallery", "resize", "gallery_graphics::resize", + array("width" => $resize_size, "height" => $resize_size, "master" => Image::AUTO), 100); + endif; + + if (module::get_var("gallery", "image_quality") != $resize_quality): + module::set_var("gallery", "image_quality", $resize_quality); + endif; + + if (module::get_var("gallery", "resize_size") != $resize_size): + module::set_var("gallery", "resize_size", $resize_size); + endif; + + $_priorratio = module::get_var("th_greydragon", "thumb_ratio", "photo"); + $thumb_ratio = $form->edit_theme_adv_thumb->thumb_ratio->value; + $thumb_ratio_ex = $form->edit_theme_adv_thumb->thumb_ratio_ex->value; + if ($thumb_ratio_ex): + $thumb_ratio .= "_ex"; + endif; + + if ($thumb_ratio_ex): + $thumb_size = 300; + else: + $thumb_size = 200; + endif; + + if ($thumb_ratio == "photo"): + $rule = Image::AUTO; + else: + $rule = Image::WIDTH; + endif; + + if ($build_thumbs): + graphics::remove_rule("gallery", "thumb", "gallery_graphics::resize"); + graphics::add_rule("gallery", "thumb", "gallery_graphics::resize", + array("width" => $thumb_size, "height" => $thumb_size, "master" => $rule), 100); + endif; + + if (module::get_var("gallery", "thumb_size") != $thumb_size): + module::set_var("gallery", "thumb_size", $thumb_size); + endif; + + $row_count = $form->edit_theme->row_count->value; + $column_count = $form->edit_theme->column_count->value; + $this->save_item_state("row_count", 3, $row_count); + $this->save_item_state("column_count", 3, $column_count); + if ($column_count == -1): + $column_count = 3; + endif; + module::set_var("gallery", "page_size", $row_count * $column_count); + module::set_var("gallery", "header_text", $form->edit_theme->header_text->value); + module::set_var("gallery", "footer_text", $form->edit_theme->footer_text->value); + module::set_var("gallery", "favicon_url", $form->edit_theme->favicon->value); + module::set_var("gallery", "apple_touch_icon_url", $form->edit_theme->appletouchicon->value); + + $this->save_item_state("copyright", $form->edit_theme->copyright->value, $form->edit_theme->copyright->value); + $this->save_item_state("logo_path", $form->edit_theme->logo_path->value, $form->edit_theme->logo_path->value); + $this->save_item_state("color_pack", (($color_pack) and ($color_pack != "greydragon")), $color_pack); + $this->save_item_state("frame_pack", (($frame_pack) and ($frame_pack != "greydragon")), $frame_pack); + + $auto_delay = $form->edit_theme->g_auto_delay->value; + $this->save_item_state("auto_delay", $auto_delay != 30, $auto_delay); + + // * Advanced Options - General ****************************************** + + $this->save_item_state("viewmode", $form->edit_theme_adv_main->viewmode->value != "default", $form->edit_theme_adv_main->viewmode->value); + $this->save_item_state("toolbar_large", $form->edit_theme_adv_main->toolbar_large->value, TRUE); + module::set_var("gallery", "show_credits", $form->edit_theme_adv_main->show_credits->value); + $this->save_item_state("show_guest_menu", $form->edit_theme_adv_main->show_guest_menu->value, TRUE); + $this->save_item_state("loginmenu_position", $form->edit_theme_adv_main->loginmenu_position->value != "default", $form->edit_theme_adv_main->loginmenu_position->value); + $this->save_item_state("mainmenu_position", $form->edit_theme_adv_main->mainmenu_position->value != "default", $form->edit_theme_adv_main->mainmenu_position->value); + $this->save_item_state("breadcrumbs_position", $form->edit_theme_adv_main->breadcrumbs_position->value != "default", $form->edit_theme_adv_main->breadcrumbs_position->value); + $this->save_item_state("breadcrumbs_showinroot",$form->edit_theme_adv_main->breadcrumbs_showinroot->value, TRUE); + $this->save_item_state("custom_css_path", $form->edit_theme_adv_main->custom_css_path->value != "", $form->edit_theme_adv_main->custom_css_path->value); + $this->save_item_state("disable_seosupport", $form->edit_theme_adv_main->disable_seosupport->value, TRUE); + $this->save_item_state("desc_allowbbcode", $form->edit_theme_adv_main->desc_allowbbcode->value, TRUE); + module::set_var("gallery", "visible_title_length", $form->edit_theme_adv_main->visible_title_length->value); + $this->save_item_state("title_source", $form->edit_theme_adv_main->title_source->value != "default", $form->edit_theme_adv_main->title_source->value); + $this->save_item_state("use_permalinks", $form->edit_theme_adv_main->use_permalinks->value, TRUE); + + // * Advanced Options - Album page *************************************** + + $this->save_item_state("album_descmode", $form->edit_theme_adv_album->album_descmode->value != "hide", $form->edit_theme_adv_album->album_descmode->value); + $this->save_item_state("paginator_album", $form->edit_theme_adv_album->paginator_album->value != "top", $form->edit_theme_adv_album->paginator_album->value); + $this->save_item_state("disablephotopage", $form->edit_theme_adv_album->disablephotopage->value, TRUE); + $this->save_item_state("hidecontextmenu", $form->edit_theme_adv_album->hidecontextmenu->value, TRUE); + + // * Advanced Options - Album page - Thumbs ****************************** + + $this->save_item_state("thumb_ratio", $thumb_ratio != "photo", $thumb_ratio); + $this->save_item_state("thumb_imgalign", $thumb_imgalign != "top", $thumb_imgalign); + $this->save_item_state("thumb_descmode_a", $thumb_descmode_a != "overlay", $thumb_descmode_a); + $this->save_item_state("thumb_descmode", $thumb_descmode != "overlay", $thumb_descmode); + $this->save_item_state("thumb_metamode", $thumb_metamode != "default", $thumb_metamode); + $this->save_item_state("thumb_random", $form->edit_theme_adv_thumb->thumb_random->value, TRUE); + + // * Advanced Options - Photo page *************************************** + + $this->save_item_state("paginator_photo", $form->edit_theme_adv_photo->paginator_photo->value != "top", $form->edit_theme_adv_photo->paginator_photo->value); + $this->save_item_state("photo_descmode", $photo_descmode != "overlay_top", $photo_descmode); + $this->save_item_state("photo_popupbox", $photo_popupbox != "default", $photo_popupbox); + $this->save_item_state("thumb_inpage", $form->edit_theme_adv_photo->thumb_inpage->value, TRUE); + $this->save_item_state("hide_photometa", !$form->edit_theme_adv_photo->hide_photometa->value, FALSE); + + // * Advanced Options - Root page **************************************** + + $rssmodulecheck = module::is_active("rss") and module::info("rss"); + + $root_feed = $form->edit_theme_adv_root->root_feed->value; + $root_cyclemode = $form->edit_theme_adv_root->root_cyclemode->value; + $root_delay = $form->edit_theme_adv_root->root_delay->value; + $root_description = $form->edit_theme_adv_root->root_description->value; + + $this->save_item_state("allow_root_page", $form->edit_theme_adv_root->allow_root_page->value, TRUE); + $this->save_item_state("hide_root_desc", !$form->edit_theme_adv_root->show_root_desc->value, TRUE); + $this->save_item_state("root_feed", $root_feed != "gallery/latest", $root_feed); + $this->save_item_state("root_cyclemode", $root_cyclemode != "fade", $root_cyclemode); + $this->save_item_state("root_delay", $root_delay != "15", $root_delay); + $this->save_item_state("hide_root_sidebar", $form->edit_theme_adv_root->hide_root_sidebar->value, TRUE); + $this->save_item_state("root_description", $root_description, $root_description); + + // * Sidebar Options ***************************************************** + + $sidebar_allowed = $form->edit_theme_side->sidebar_allowed->value; + $sidebar_visible = $form->edit_theme_side->sidebar_visible->value; + + if ($sidebar_allowed == "right"): + $sidebar_visible = "right"; + endif; + if ($sidebar_allowed == "left"): + $sidebar_visible = "left"; + endif; + + $this->save_item_state("hide_blockheader", $form->edit_theme_side->hide_blockheader->value, TRUE); + $this->save_item_state("sidebar_albumonly", $form->edit_theme_side->sidebar_albumonly->value, TRUE); + $this->save_item_state("sidebar_hideguest", !$form->edit_theme_side->sidebar_hideguest->value, TRUE); + $this->save_item_state("sidebar_allowed", $sidebar_allowed != "any", $sidebar_allowed); + $this->save_item_state("sidebar_visible", $sidebar_visible != "right", $sidebar_visible); + + $this->save_item_state("last_update", TRUE, time()); + + module::event("theme_edit_form_completed", $form); + + if ($_priorratio != $thumb_ratio): + message::warning(t("Thumb aspect ratio has been changed. Consider rebuilding thumbs if needed.")); + endif; + + message::success(t("Updated theme details")); + + if ($build_exif): + db::update('exif_records') + ->set(array('dirty'=>'1')) + ->execute(); + endif; + + if ($build_iptc): + db::update('iptc_records') + ->set(array('dirty'=>'1')) + ->execute(); + endif; + + if ($purge_cache): + db::build() + ->delete("caches") + ->execute(); + endif; + endif; + url::redirect("admin/theme_options"); + else: + print $this->get_admin_view(); + endif; + } + + protected function get_admin_view() { + $view = new Admin_View("admin.html"); + $view->page_title = t("Grey Dragon Theme"); + $view->content = new View("admin_theme_options.html"); + $view->content->name = self::get_theme_name(); + $view->content->version = self::get_theme_version(); + $view->content->form = self::get_edit_form_admin(); + $view->content->help = self::get_edit_form_help(); + return $view; + } + + public function index() { + site_status::clear("gd_init_configuration"); + print $this->get_admin_view(); + } +} +?> \ No newline at end of file diff --git a/themes/greydragon/admin/views/admin_theme_options.html.php b/themes/greydragon/admin/views/admin_theme_options.html.php new file mode 100644 index 0000000..0e9f404 --- /dev/null +++ b/themes/greydragon/admin/views/admin_theme_options.html.php @@ -0,0 +1,38 @@ + +is_module = FALSE; + $view->downloadid = 1; + $view->name = "greydragon"; + $view->form = $form; + $view->help = $help; + else: + $view = '
      '; + $view .= "

      " . t("Prerequisite") . "


      "; + $view .= "

      " . t("This theme requires GreyDragon shared module to be installed and actived first.") . "

      "; + $view .= "

      " . t("Please download it") . ' ' . t("here") . " " . t("and install. Make sure it is activated.") . "

      "; + $view .= "
      "; + endif; + + print $view; +?> + diff --git a/themes/greydragon/changelog.txt b/themes/greydragon/changelog.txt new file mode 100644 index 0000000..04ca0d4 --- /dev/null +++ b/themes/greydragon/changelog.txt @@ -0,0 +1,641 @@ +=== Grey Dragon Theme === +Grey Dragon Theme - a custom theme for Gallery 3 + +This theme was designed and built by Serguei Dosyukov, whose blog you will find +at http://blog.dragonsoft.us/ +Copyright (C) 2009-2012 Serguei Dosyukov + +Tested up to: G3 3.0.3 build 55 +Minimum requirement: G3 3.0.2 build 49 + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street Fifth Floor, Boston, MA 02110-1301, USA. + +=== Open issues === +- RTL layout support + +=== Changelog === +version 4.0.2 +- Incompatability fix for PHP5.4 and below causing crash and blank page. http://galleryproject.org/forum_theme_greydragon?page=48#comment-408923 +- Fix to allow thumbnail rotation update when operation is performed. http://galleryproject.org/forum_theme_greydragon?page=48#comment-408993 +- Cycle JS: Added fadeout support. Fixed issue in root page when "Click to Enter" link would disappear with some effects + +version 4.0.1 +- Code converted to use latest jQuery library +- Added support for RWInfo meta data injection +- CSS: Fixes a phenomenon where dialog content not setting proper width in FF/Ch +- CSS: Fixes a phenomenon where context menu shows empty space above first item +- Added support for g-movie class in thumbs +- Admin: Better help translation +- Logic to load frame template optimized to prevent exception +- thumb_top()/thumb_botom() call moved inside div.g-thumbslide per Dave's request +- Markup cleanup for W3C validation +- CSS: small layout size adjustments in Simple Frame Pack +- Photo Page: fixed issue with image preload logic when one of the items is an album +- Added code protection to paginator when item is not well defined - missing ->url() method +- Breadcrumbs to take advantage of new logic introduced in latest G3 core for tags and search +- Added support for tag_albums module for pagination +- FP: Fix for Book frame pack - missing frame for movie thumbs +- Photo pages in Tag based collections do not support full item list. As result First/Last page paginator buttons and page list need to be disabled. +- Updated jQuery Cycle plug-in to latest version +- CSS: fixes for movie thumbnail in greydragon framepack + +version 3.2.2 +- CSS: Fixes a phenomenon where dialog content not aligning properly under some conditions +- CSS: Fixes a phenomenon where login dialog for error/login and reauthenticate pages were not in sync +- Movie thumb and Photo IDs are adjusted to comply with 3.0.3 notation +- Photo page image precache javascript moved into .ready to speed up the page load +- CP: Roundrobin styles improvements - top login positioning adjusted, removed padding for root page when sidebar is used +- ADMIN: Added prerequisite check for CURL support in PHP + +version 3.2.1 +- Logic to process command line parameters changed to prevent unhandled exception on some LAMP instances + +version 3.2.0 +- Theme is now HTML 5 +- CSS: Dropped support for IE 6 +- IMPORTANT: .g-extended class moved from .g-item level into a to allow layout manipulation based on thumb formats. Styles are affected. +- Added class marker to page's element to indicate current sidebar mode - g-sidebar-xxxxxxxx +- CSS: Min width of the gallery is now enforced based on number of columns and sidebar visibility - extended thumbs 3/4 columns +- CSS: Decrease size of the column for 3 column layout to 31% to prevent wrapping in some browser modes +- ADMIN: Logic added to inform site owner about new versions of the theme +- ADMIN: Admin page layout is now based on shared GreyDragon module which is now required +- ADMIN: "Visible Title Length" option - access to G3 title length setting. +- ADMIN: Use Permalinks for navigation - allows /root, /enter be used for root page access. + Requires the following rules added in .htaccess section + + ########## Enable /root url ################ + # + RewriteRule ^root ?root=yes [NC] + RewriteRule ^enter ?root=no [NC] + # + ############################################ + +- Visible Title Length is now taken in consideration when displaying Photo Titles in thumbs, breadcrumbs, etc. +- Improved file name detection for Title Source +- CP: new color pack - Round Robin \ +- FP: new frame pack - Round Corners / combined would give look of Clean Canvas theme +- FP: Book frame pack : removed right padding to prevent wrapping on smaller resolutions +- FP: Wall frame pack : min width decreased to all proper alignment of tiles +- jquery.cycle.js updated to v.2.9999.4 +- Synchronize Calendar page with page.html.php +- CSS: forms.css - fixed two misspelled rules +- Added code protection from Camel case in CMD parameters +- Fixed issue with missing status icons for some colorpacks in Upload dialog +- Added code protection in Root page to detect situation when roller is empty and then do not initialize the cycle plugin +- Added translation support for default thumb caption (Photo #, Movie #, etc) + +version 3.1.4 +- ADMIN: Option to specify image quality level for Photo resizes and Thumbs +- ADMIN: Additional Description Display Mode for Photo page : Overlay Top (Static), Overlay Bottom (Static) +- ADMIN: Title Source - manage what thumb/image title is based on : Title | Title/Suppress Filename | Description +- Added support for normalize.css - see http://necolas.github.com/normalize.css/ for more details +- Added rel=nofollow for sidebar mode switch links to prevent indexing +- Fix: module check failing in admin section when none of colorbox, shadowbox or fancybox is intalled +- Fix: CSS: Added missing rule for size of the thumb in Wall frame pack for "Actual" aspect ratio +- Fix: CSS: Fixed "Forgot password" misalignment in Login dialog +- G3 version requirement increased to 3.0.2 + +version 3.1.3 +- Removed validation for ImageBlockEx preventing use of RootPage option +- Removed JS code - attempt to have fadeIn effect + +version 3.1.2 +- CSS: Fixed issue with breadcrumbs divider in colorpacks +- CSS: Fixed issue with thumb description positioning + +version 3.1.1 +- Removed some redundant code in page.html.php +- Fixed issue with fixed Columns style not being applied +- Added code protection for situation when $position is not defined in paginator.html.php +- CSS: Fixed issues with popup menu positioning + +version 3.1.0 +- CSS: !!! Folder Structure Change !!! colors.css and frame.css added to aggregated CSS. +- ADMIN: Root Page is now iPad/Mac compatible - Flash player has been replaced with jquery.cycle - option added to manage + rotation mode. ImageBlockEx module is no longer required. For hosts where there is a block accessing local RSS feed, + rootpage.html.php_fix can be used (old style) by renaming it and placing into /themes/custom/views folder +- ADMIN: New option - Hide Context Menu for thumbs +- ADMIN: New option - Disable photo page - open FancyBox style slideshow directly from album page +- ADMIN: New option - Show Sidebar for Guest Users +- ADMIN: Option for Thumb alignment extended to Top/Center/Bottom/Fit +- ADMIN: Option's order change in Advanced Options - General +- ADMIN: Large toolbar buttons +- ADMIN: "Blend Page Transitions" is replaced with JS version +- ADMIN: Corrected setting for Apple Touch Fav Icon to use G3 default setting +- ADMIN: "Login Menu Position" expanded to allow login to be hidden. Admin can access Admin area using http://site_url/admin URL +- CP: New color pack - White Hawk +- FP: New frame pack - Wall +- FP: Support for custom frame content added - frame.html.php +- Core: Current framepack is now referenced by the page +- Core: Body class logic moved into theme's library class +- Core: "Up" navigation button now would take you to item's associated page rather then to first page of the album +- Core: Fixed issue of Sidebar position setting not being picked up under some conditions +- Core: Improved detection for root page - if root album id <> 1 +- Core: Framepack and viewmode URL parameter value is now retained for duration of the session +- Core: In "Disable photo page" mode, if EXIF module is disabled, thumb would include clickable reference to EXIF dialog +- Core: Breadcrumbs are now using :: as divider instead of image +- CSS: Column/Colorpack classes (ex: g-column-3, g-wall) moved one level up from ul#g-album-grid to div.g-album-grid-container +- CSS: Fixed issue with meta data alignment in Simple frame pack +- CSS: Fixed issue with permission dialog's breadcrumbs alignment +- CSS: removed left padding for navigator thumbs which was causing image "jumps" +- CSS: Some images were changed +- JS: Shadowbox settings moved out of the theme into module itself. + +version 3.0.8 +- Fixed issue with allowed sidebar position not properly recognized when used as Default +- Fix issue with missing quotes in Admin module +- Fixed issue with uninitialized variable in admin page +- Fixed background for view-calendar.png for some color packs +- CSS: Center content of ThumbNav module. If non default thumb size is used for module, setting may need to be adjusted. +- CSS: Added new style sheet for iPad support + +version 3.0.7 +- ADMIN: Added support for View Mode - Full/Mini (ex: ?viewmode=mini | ?viewmode=full | ?viewmode=default ) +- ADMIN: Paginator position now can be set independently for Album and Photo pages +- ADMIN: Added Colorbox slideshow detection +- Theme.info adjusted to match new guidelines +- CSS: GreyDragon Color Pack - fixed thumb background color issue with GreyDragon Frame Pack +- CSS: changes to improve Basket Module integration +- CSS: #g-album-grid element now references framepack used for better interaction between frame and color packs (ex: class="g-slateblue") +- Fix issue with sidebar not properly displayed in Root Page +- Fix issue with missing thumb image not being aligned properly in "Actual Aspect Ratio" mode + +version 3.0.6 +- Fix: Using Top aligned sidebar causes problem + +version 3.0.5 +- ADMIN: Allow hiding sidebar for root pages -> Advanced Options\Root Page\Hide Sidebar +- ADMIN: Allow breadcrumbs in root page/root album -> Advanced Options\General\Show Breadcrumbs in top album/root page +- Fixed issue with Calendar not displayed properly when no items are returned +- CSS: Increased padding for in-page description block + +version 3.0.4 +- FP: New framepack - book + Please note that if bottom aligned description is used, it need to be the same for both album and photo thumbs. +- CSS: #g-album-grid-container renamed to .g-album-grid-container +- CSS: Fixed slight misalignment of 3 level main menu items +- ADMIN: New position for main menu - Top Bar - top aligned/fixed positioned menu. Useful for admin mode menu +- ADMIN: Added support for separate option for description position in album thumbs + +version 3.0.3 +- ADMIN: Added option for Root Page slideshow delay +- ADMIN: Added option for Alternative Description for Root Page which overwrites Description from Root album +- CSS: Fix issue with context menu hotspot z-index in thumbs +- CSS: Fixed margin in Uploadify dialog to prevent overlap +- ADMIN: Page cache option has been deprecated. It can be achieved via .htaccess instead + +version 3.0.2 +- Wind Color Pack: Fixed issue with missing background image for dialog header +- CSS: Wind Color Pack: Set dialog background to white +- ADMIN: Fixed issue of Maintenance group label not being translated +- Fixed issue with float division for photo page +- Fixed issue with BBCode in info block not being converted (regression error) +- Fixed small misalignment of the image with the "Actual" Aspect Ratio Thumb +- Added CSS class marker for portrait thumb images +- IE9: Added support for Pinned Site mode and Jump List menu + +version 3.0.1 +- ADMIN: Page Header and Footer Text allows BBCode +- ADMIN: Added support for apple-touch-icon - icon similar to favicon to be used on iPad/iPhone/iPod devices +- ADMIN: New option - Album Description Display Mode - allowing placement of the description in the album pages +- ADMIN: Main menu could be now positioned as Top-Left/Bottom-Left aligned +- ADMIN: Bradcrumbs could be now positioned as Top-Left/Bottom-Left/Bottom-Right or Hidden +- Added wrappers for Header and Footer texts allowing further CSS manipulation +- Added support for custom header/footer inclusion - simply drop header.html.php and/or footer.html.php in the views folder and content would be added in the header/footer section +- Info sidebar block logic synchronized to match default but still allow BBCode + +version 3.0.0 +- Due to extensive changes to CSS it is now a major release +- Rules from screen.css moved into base.css. Screen.css used as loader file only. +- Introduced better support for RTL +- Some of the icons moved into ui-icons.png for optimization. Color adjustments for visibility. +- Added support for frame packs. Included: greydragon (default), simple, android, iphone, iphoto, darkglass +- Fixed issue with regular wide thumb format not being set/displayed properly +- Breadcrumb logic extended to take in consideration [visible_title_length] variable value introduced in G3 +- Reordered JS registration to comply with default G3 3.0.1 logic +- Added some missing translation hooks in Admin area +- Some Page load optimization +- Added logic to take in consideration date display format for info module +- Performed validation and necessary adjustments for IE 9/IE 9 compatibility mode +- IE Fix CSS is only applied for IE 6. Older versions are no longer supported + +version 2.7.9 +- Optimized session handling for colorpack cmd param +- Removed validation for colorpack presence + +version 2.7.8 +- Fixed issue with colorpack detection. Reenabled +- Fixed issue with sidebar position could be "stuck" when switching from Grey Dragon Wind Theme +- Fixed introduced error with missing forms.css reference +- Theme's CSS/JS is combined into session based G3 files + +version 2.7.7 +- Adjusted for 3.0.1 release of G3 +- New color pack: blackhawk +- ADMIN: New sidebar position supported - top (new rules added to colorpacks) +- CSS: Some clean up for included colorpacks - CSS/image resources, sprite image optimization for sidebar position buttons +- Added session persistence for colorpack url parameter for demo purposes +- ADMIN: Prerequisites module check extended to make it more relevant when Root Page is allowed +- CSS: Sync positions of main menu and breadcrumbs when in alternative header layout format +- colorpack location validation has been disabled for now until issue is resolved + +version 2.7.6 +- ADMIN: "Large" Thumb sizes are removed from Aspect dropdown and managed via "Expanded Aspect Ratio (300px wide)" option +- ADMIN: New Thumb aspect ratio - Wide/HDTV (16:9), "Actual" aspect is supported in Expanded mode +- ADMIN: Root Page - new option "Show Gallery Description" +- Added check if Shadowbox is loaded so that theme's initialization JS does not produce hidden exception +- Added pointer cursor for clickable overlay area in Root Page until Minislideshow is loaded + +version 2.7.5 +- ADMIN: Full Slideshow RSS feed url now being used. This solves some issues with Flash loader and allows creation of custom playlists to be used in the root page +- Root Page: Fixed issue with Flash Slideshow not being loaded properly + +version 2.7.4 +- Root Page: Added "Click to Enter" to indicate clickable state +- Root Page: Quote is now taken from root album description field. If description is empty, slideshow content is centered. +- Added branding icons for "gallery 3" and theme/color packs. +- Fixed issue with page layout for calendar module. +- Fixed issue with URL parameters not being recognized in some server configurations +- CSS: Fixed aspect ratio for thumbs. Some css alignments optimization +- CSS: Turned back on borders for blocks in bottom aligned sidebar +- ADMIN: New option for Photo Description Display Mode - Overlay Bottom. !!!! Color pack's CSS rules affected. +- ADMIN: Maintenance section: Added "Reset IPIC Info" (if module is active) and "Purge cache data", some tweaks for "Reset EXIF Info" + +version 2.7.3 +- ADMIN: New option - Randomize Thumb Image +- Missing markup in rootpage.html.php +- Root Page - set size of the slideshow to auto so it can be managed via CSS +- Sync page.html.php structure with latest git changes +- CSS: Fixed styles for progress dialog when invoked from non-admin area + +version 2.7.2 +- CSS: Restored visible state of rotation operations for photos +- JS: To resolve issue with Opera OS, background image need to be set for #g-rootpage-link instead of roller object. Finalization JS for root page slideshow extended to clear background for overlay. +- Reenabled support for site status messages + +version 2.7.1 +- Root Page layout/css extended to allow click on the image while slideshow is still loading +- ADMIN: Some changes in setting's titles and help +- Fixed issue with meta data overlay not being shown for some title/meta combinations + +version 2.7.0 +- DEV: new url parameter support added - colorpack. Ex: ?colorpack= +- ADMIN: 2 to 5 columns layout is now supported. Flex column option moved into column selector. +- ADMIN: Added new mode for Description overlay in Album - Overlay Bottom (Static) - places description on the bottom of the thumb as static overlay. Metadata would not be shown. +- ADMIN: Metadata Display Mode for Thumbs introduced. [Hide metadata] merged into it. Metadata can now be merged with description. +- W3C: resolved some validation errors in album page +- CSS: !!! Colorpack !!! - adjusted to reflect changes to thumb description structure +- CSS: Changes for columns CSS (.g-column-3/.g-column-4) - moved to list level - several php files also affected +- CSS: Fixed issue with re-authenticate dialog layout +- CSS: Fixed issue search button in IE7 Compatibility mode for wind color pack +- ShadowBox module config settings changed to prevent flickering - enabled dialog animation. Slideshow timeout increased to 7 sec. Updated version of ShadowBox module is available. +- CSS: Deprecated styles for ShadowBox Ajax dialogs as they are not supported by the library in Core and color pack references + +version 2.6.3 +- ADMIN: New "Title Display Mode" added - Overlay (Static) +- CSS: syncronization between colorpacks. Some simplification + +version 2.6.2 +- Added support for initial background in the Root Page to be removed as soon as slideshow starts (requires imageblockex 2.1) +- ADMIN: Added option allowing feed selection for Root Page, Quote text +- Small changes in color pack and layout for Upload dialog +- Fixed logic in roopage.html.php to properly construct the link for navigation + +version 2.6.0 +- Support for root page added +- Added borders for main wrapper in Slate Blue color pack to accommodate centered layout +- Added JS to preload next/prior images in photo pages +- Added content wrapper for album grid to allow external CSS application - ex: center grid content +- ALERT! Improved rendering of admin panel. This would break admin panels for any modules which uses the same file by inheritance. Please download updated module distros. +- Fixed wrong reference to layout_non_ie.css file - not used +- Removed restrictions for displaying sidebar in satellite pages +- Fixed status message alignment in Upload dialog +- Fixed W3C validation error in Photo page markup +- "controllers" folder deprecated + +version 2.5.1 +- Fixed issue with relative path not being used when changing sidebar display mode +- ADMIN: Added support for Blend effect for page transitions +- ADMIN: Added check to identify module collision between FancyBox and ShadowBox +- ADMIN: New option "Keep Thumb Nav Block on the side" for Photo Pages +- CSS: small css changes for bottom aligned sidebar positioning +- CSS: Fix for dialog overlay div to properly position itself and prevent scrollbars to appear without reason +- CSS: slateblue color pack - make regular text grey +- CSS: set min height for bottom aligned sidebar +- CSS: Fixed issue with font size for dialogs in IE8 Compatability mode +- Custom layout for tag_block.html.php is depricated +- Some W3C Validation clean-up + +version 2.5.0 +- New colorpack - SlateBlue +- Added support for bottom aligned Sidebar +- CSS: opacity in .ui-widget-overlay (forms.css) set to 70 +- CSS: Fixed issue with misallignment of sidebar toolbar area and page header info +- CSS: Removed left pagging for g-photo element to properly align in the div +- CSS: Added thumb size restrictions for Image Block module +- Removed logic to use Shadowbox when showing Exif data + +version 2.4.9 +- ADMIN: Top Align Thumb Image option added - do not center thumb image in the view window. Useful for portrait oriented photos. +- ADMIN: Added a new Advanced option - Relative path to custom.css, if used - allowing inclusion of custom css override +- Ignore albums when building item list for Fancybox/Shadowbox Slideshow in Photo page. Fix issue with extra item added when start with first item. +- Fixed issue with not properly sanitized titles for links for Fancybox/Shadowbox Slideshow in Photo page +- Small adjustment to FancyBox slideshow navigation arrows +- Force min height requirement for H1/H2 elements +- Fixed spelling for g-thumlink -> g-thumblink +- Force size of the thumb link to fill crop area - situation when thumb image is smaller then view area +- Removed custom layout for User Profile page + +version 2.4.8 +- Fixed inverted state of "Disallow Search Engine Indexing (No Bots) +- Fixed JS syntax incompatibility in IE7/IE8 compatibility mode +- Fixed display of thumb title and photo description on hover in IE7/IE8 compatibility mode +- Fixed issue with Dialogs not skinned properly in Carbon/Wind themes +- Fixed issue with metadata for photo thumbs when Aspect Ratio: Actual Size and Title Display Mode: Bottom + +version 2.4.7 +- Fixed issue with some .g-block structures. + Fixed issue with avatar not properly aligned in comment section. + +version 2.4.6 +- Fixed some issues when strict error reporting is enabled for PHP +- Fixed background color for description for bottom Display Title Mode (greydragon colorpack) +- Added support for two new Thumb Sizes - Digital Ex and Film Ex - 300px wide thumbs +- Fixed issue with buttons in the block "jumping" out +- Added check in block view to verify that theme is defined + +version 2.4.5 +- Min G3 core requirement is set to v.41 +- Fixed issue with Exif dialog data integration +- Fixed issue with permission dialog +- CSS: Changes form style management. Fixed issue with breadcrumbs display in dialogs. +- CSS: Important! Colorpack files are affected +- Removed no longer required custom Login dialog logic +- JS cleanup + +version 2.4.4 + +Please drop GD themes folder before uploading a new version + +- CSS: screen.css - fixed layout naming change preventing proper display of authentification form when accessing protected areas +- CSS: Removed some temp files not required by the theme. +- Support for Fancybox added as alternative to Shadowbox +- Added support for CalendarView module + +version 2.4.3 +- Fixed issue with sidebar mode switch link being incorrect +- CSS: small style adjustment to neutralize default style effect for the search entry box + +version 2.4.2 +- Fixed issue some modules requiring scrollTo() function. +- Shadowbox is now optional. Special logic added to allow/disallow SB slideshow mode. If disabled, theme would not support full view for images to be in the popup window, new window will be used instead. +- CSS: Minor improvements for thumb layout in Overlay and Bottom Title Display mode +- ADMIN: Support for flex column layout added +- ADMIN Help: Small improvements + +version 2.4.1 +- Organize module support reenabled +- Register module support reenabled +- Partial support for Shopping Basket module +- CSS clean-up after dialog engine conversion - round #1 +- Fixed issue with Exif dialog not properly rendered + +version 2.4.0 +- Apply g-button style only to forms +- Fix to properly display videos +- Fix to account for changes in Comments module +- Favicon removed from theme's package +- Fixed uninitialized state warnings of the variables in PHP protected mode +- Album thumbs now have "lighter" background +- First draft to change how dialogs are handled - switched to standard code +- ADMIN: G3 Core version requirement is set to 32 +- ADMIN: Added missing settings initializations +- ADMIN: Fixed issue with Navigator position setting not being persisted +- ADMIN: Abstract admin form styles for reuse from regular module admin panels +- ADMIN: Added option to specify favicon location. If not specified default G3 icon is used. + +version 2.3.1 +- Hide Rotate operations for pictures since they are not supported by the theme +- Added use of common gallery.ajax.js. Fix issue with some Ajax based links. +- Layout fixes for Translation form overlay +- Changed CSS styling for buttons to provide unified coverage for buttons and links exposed as buttons. +- ADMIN: Fixed options group styles in Theme's Admin panel +- ADMIN: Advanced Settings for Thumbs and Individual Photo are moved into separate sections. +- ADMIN: New option - display meta data in Photo description section +- ADMIN: New option/fix - SEO indexing is now allowed by default. In order to prevent your site from being indexed, you can now use "Disallow Search Engine Indexing" option + +version 2.3.0 +- Adopted for Gallery 3.0RC2 changes (minor template adjustments, css class name changes, etc.) + +version 2.2.1 +- Redesigned Ready event handler for the theme to ensure proper ShadowBox initialization +- Added support for gallery_dialog() function call used by some 3rd party modules - some sync issues are solved by imposed delay of 1 second +- GPS module - better action list alignment in the sidebar + +version 2.2.0 +- Added support for slideshow mode in Photo Preview +- Fixed issue with Info side block - missing markup +- Fixed issue with Upload dialog layout with some resolutions/fonts +- ADMIN: Added option to hide breadcrumbs +- ADMIN: Added prerequisite check for Info module - required for Thumb meta data display + +version 2.1.7 +- Added support for missing images in the thumbs to allow proper operations with empty albums or albums with broken thumbs +- Some color optimizations +- Color improvements for "Add Image" dialog +- Better support for Basket module + +version 2.1.6 +- Wind colorpack adjusted to closer match default Wind theme + +version 2.1.5 +- Minor changes in ADMIN infrastructure +- ADMIN: added check for Kbd Navigation module +- ADMIN: New color pack - carbon + +version 2.1.4 +- Minor refactoring in paginator +- Added support for keyboard navigation module (http://codex.gallery2.org/Gallery3:Modules:kbd_nav) + +version 2.1.3 +- Sidebar restricted to item related pages (album, photo, movie, etc) +- Fixed issue with bottom border not applied to all instances of H1 tag +- Min footer size set to 4em +- ADMIN: "Photo: Description Display Mode" option added +- ADMIN: Added new maintenance operation - "Reset Exif Info" + +version 2.1.2 +- Fixed issue with Album thumbs - empty space under +- Thumb Item's Title Display Mode expanded to be applied to Item's description in Photo page +- More documentations in CSS files, some movements +- Some cleanup for Wind color pack +- Fixed font name typo in screen.css +- Fixed "Waiting" roller for Wind theme to match background +- Added "up" button in navigation + +version 2.1.1 +- Increased size of Add photo dialog for better display on some lower resolutions. +- ADMIN: New option: "Thumb: Item's Title Display Mode" - specifies how to display item's title in thumbs : Overlay Bottom Hide + +version 2.1.0 +- Custom Info Block to include item's description +- Image is centered when "Actual Size" aspect is used for thumbs +- Added support for color packs - included: greydragon, wind +- ADMIN: Improved error handling +- ADMIN: Disable submit button on click to prevent Dbl-click +- ADMIN: New option: Enable page cache - adds header marker for page to be cached for 60 seconds + +version 2.0.1 +- Enable BBCode/HTML support in individual photo descriptions +- Fixed main menu overlay issue when in top position +- Theme's credits moved into dedicated method +- CSS clean up +- Comments module layout enhancements + +version 2.0.0 +- Major redesign of the gallery flow. + - Added caption and metadata (Admin/optional) overlay for thumbs. + - Added description overlay in individual Photo view (look for "Learn More" marker). + - Based on Admin setting, thumbs are adjusted to fit Digital/Film/Actual size. +- Attempt to fix issue with JS load latency to prevent unhandled AJAX calls +- Added code protection for theme initialization procedure +- ADMIN: Thumb Aspect Ratio option. See help section for more info. + +version 1.8.2 +- Increased based font size +- Layout adjusted to match new settings +- ADMIN: New option - Place Login Link in the Header + +version 1.8.1 +- ADMIN: small adjustments in layout and help info +- 3rd party module's related CSS moved into contrib.css +- Adjust user profile screen to match new layout +- initial design for calendar module + +version 1.8.0 +- ADMIN: Major redesign of the layout. Help section added. +- ADMIN: New option - Show main menu for guest user +- Minimum required Gallery version set to 30 +- When configured not to use sidebar, theme is switched into 4 columns layout + +version 1.7.6 +- Organize module: CSS improvements +- Fixed issue with Chrome browser + +version 1.7.5 +- ADMIN: Added option to reset theme to default state +- CSS: some size adjustments for dialogs. Added minimum height for overlay to keep dialogs from shrinking. + +version 1.7.4 +- ADMIN: Theme Gallery 3 core requirement changed to v.26 +- ADMIN: Most of theme's settings are documented using element's title attribute - hover over to see a description +- Edit Permissions form redesigned and enlarged to fit more information + +version 1.7.3 +- ADMIN: Default states for the theme options are no longer being stored. Please save theme settings at least once to take advantage of a new functionality. +- Photo Navigator default position is set to Top Only + +version 1.7.2 +- Fix in Uploader dialog to keep items inside respected boxes +- Organize module support has been abandoned. Please use GWT Organize module instead. Added item in Prerequisites Checklist. + +version 1.7.1 +- CSS: Fixed visibility of the "Select Photo" button in "Add photo" dialog +- CSS: Fixed "ghost" line for navigation buttons when zoomed-in in IE +- Admin: fixed issue with prerequisite check not detecting deleted modules +- /views/support folder deprecated. Logic moved into Theme_View extension class for Theme_View_Core +- Theme Options Page management, generic Page code and BBCode processor moved into Theme_View class +- HACK: Info block is not displayed if there is no description for the item + +version 1.6.4 +- Admin: Added "Show Sidebar for Albums only" option +- Admin: added error visibility to the requirements validation list +- Small CSS adjustments: Fixed footer min size issue when no site credit info is displayed; added space between Credits in the footer and Footer text area. +- Few missing parts from last git sync + +version 1.6.3 +- Kohana 2.4 support +- Support for Movie files view +- Admin: Allow hide Sidebar Block header + +version 1.6.2 +- Admin: Page navigator option changed to use combobox +- Admin: Added option to hide item description in albums + +version 1.6.2 +- Small CSS adjustments. +- All operation dialogs should be visible now +- Context menu: "Rotate 90..." items are removed due to an issue with image quality affected by the operation +- Context menu: "Choose as the album cover" is now properly handled + +version 1.6.1 +- Admin: When allowed sidebar position is "Default Only", don't disregard selected Default position +- Adjust item's toolbar buttons to align properly when side bar position is fixed +- BBCode parser improved to support stripping of BBCode for Page title and breadcrumbs +- Fixed issue with main menu missing class declaration not allowing open dialogs +- Adjust context dialogs to properly show caption info +- Caption added to Full size Preview +- "New Comment" form styled +- Admin: Option to align main menu to the top and Breadcrumbs to the left + +version 1.6.0 +- Admin: Fixed issue with "Rebuild thumbs" option in theme admin +- Admin: Fixed issue with Item's toolbar not properly aligned in Quirks Mode +- Exif data dialog Layout changes +- Item context menu improvements: + - Fixed issue with submit logic + - Layout fixes for context menu dialogs + +version 1.5.8 +- Admin: First release of the Theme admin option. After theme installation, visit Appearance/Theme + Options to configure the theme. If you had older version of the theme, initial setup is also required. + The following settings are available: + - Rows per album page - theme uses 3 columns layout for pictures, therefore default page_size is computed in x3 increments + - Thumb size is restricted to 200 and therefore not available for administration + - Mark to build resizes/thumbs - allows force rebuilding of images + - Show/Hide top/bottom photo navigators + - Specify allowed and default sidebar position + - Administrator can now specify Copyright message to display in the footer + - Site logo is now default to Gallery 3 logo, but admin can provide a path to custom logo. + - Admin module validates Theme's requirements (Shadowbox module need to be installed/active) +- Sidebar session cookie is set to expire in 365 days + +version 1.5.7 +- Status message has been moved into header as popup to prevent obstruction of the main view. + jQuery is used to fade it out in 10 sec. +- Improved logic for dialogs on submit +- Theme related JS has been moved out of the page.html.php + +version 1.5.6 +- Fixed issue with tollbar buttons not properly aligned/shown when page is resized. +- Copyright info moved into DB. To change default settings add [th_greydragon/copyright] into VARS table. + +version 1.5.5 +- CSS fixes. +- Theme adjusted to be compatible with latest Git. +- Login links are moved into footer. +- Pagination module redesigned to support new structure of paging data. + +version 1.5.4 +- CSS fixes. +- Added support for Comments block. +- Improved support for Modal dialogs. + +version 1.5.3 +- Updated to match latest git. +- Exif menu customization is now part of the theme. +- Sidebar management button is disabled for current mode. + +version 1.5.2 +- Code, layout, css cleanup. +- New thumbs for buttons. +- First set of Ajax dialogs is ready and now operational: Login, user info, edit album, exit info. +- Fixed some browser related issues. \ No newline at end of file diff --git a/themes/greydragon/controllers/greydragon.php b/themes/greydragon/controllers/greydragon.php new file mode 100644 index 0000000..0ecfc4f --- /dev/null +++ b/themes/greydragon/controllers/greydragon.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/themes/greydragon/css/base.css b/themes/greydragon/css/base.css new file mode 100644 index 0000000..735a999 --- /dev/null +++ b/themes/greydragon/css/base.css @@ -0,0 +1,322 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules - Main CSS ruleset + * + * Color rules for font/background/lines can be found in dedicated colorpack files + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* base.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +body { font-family: Arial, verdana, sans-serif; font-size: 0.9em; } + +a { text-decoration: none; outline: none; -moz-outline-style: none; } +a:focus, a:active, a:hover { text-decoration: none; outline: none; } +img { border: none; } +p { text-indent: 0; } +ul { list-style: none none; padding-left: 0; } + +h1 { font-weight: bold; font-size: 1.1em; padding-bottom: 1px; min-height: 1.1em; } +h2 { font-weight: bold; font-size: 1.1em; min-height: 1.1em; } +h3 { font-weight: bold; } +h4 { font-weight: bold; } +h5 { font-weight: bold; } + +.txtright { text-align: right; } +.g-metadata { overflow: hidden; } +.g-avatar { float: right; } +.g-hide { display: none; } + +.ui-icon { display: inline-block; zoom: 1; width: 16px; height: 15px; } +.ui-icon-first { background-position: -162px -178px; } +.ui-icon-first-d { background-position: -162px -162px; } +.ui-icon-prev { background-position: -178px -178px; } +.ui-icon-prev-d { background-position: -178px -162px; } +.ui-icon-parent { background-position: -226px -178px; } +.ui-icon-parent-d { background-position: -226px -162px; } +.ui-icon-next { background-position: -194px -178px; } +.ui-icon-next-d { background-position: -194px -162px; } +.ui-icon-last { background-position: -210px -178px; } +.ui-icon-last-d { background-position: -210px -162px; } +.ui-icon-signal-diag { background-position: -16px -178px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-plus { background-position: -14px -129px; } +.ui-icon-minus { background-position: -46px -129px; } +.ui-icon-note { background-position: -66px -98px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-left .ui-icon { float: left; margin-right: .2em; } +.ui-icon-right .ui-icon { float: right; margin-left: .2em; } + +/* base.css - Inline layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-inline li { float: left; } + +/* base.css - Header ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-header { height: 90px; padding: 0; font-size: 0.9em; } +#g-logo { position: absolute; top: 8px; left: 16px; } + +.g-breadcrumbs { position: absolute; background-color: transparent; } +.g-breadcrumbs.g-default { bottom: 4px; right: 14px; } +.g-breadcrumbs.g-bottom-left { bottom: 4px; left: 21.4em; } +.g-breadcrumbs.g-top-right { top: 4px; right: 14px; } +.g-breadcrumbs.g-top-left { top: 4px; left: 21.4em; } + +.g-breadcrumbs li { display: inline; } +.g-breadcrumbs li.g-first { background-image: none; padding-left: 0; } +.rtl .g-breadcrumbs .g-first { background-image: none; padding-left: 0; } +.g-breadcrumbs li.g-active { padding-right: 0; } + +#g-header .g-message-block { position: absolute; z-index: 10; min-width: 30em; padding: 0; right: 20em; top: 34px; overflow: hidden; font: bold 9pt Arial, verdana, sans-serif; text-align: center; } +#g-header #g-login-menu { position: absolute; top: 0.2em; right: 1em; background-color: transparent; display: none; } +#g-site-status li { padding: .3em .3em .3em 30px; } + +/* base.css - Main ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-main { display: block; margin: 0; } +#g-main-in { display: block; position: relative; } + +#g-column-center, #g-column-centerleft { padding: 6px 6px 6px 10px; } +#g-column-centerfull { padding: 6px 12px 6px 10px; } +#g-column-centerright { padding: 6px 10px 6px 6px; } +#g-column-left { padding: 6px 4px 6px 10px; } +#g-column-right { padding: 6px 10px 6px 4px; } + +/* base.css - Footer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-footer { padding: 6px 0 6px 0; zoom: 1; font-size: 0.9em; } +#g-footer ul { float: left; padding: 0; text-align: left; } +#g-footer li { padding: 0 0 2px 0; } + +#g-footer #g-login-menu { position: absolute; bottom: 0.5em; right: 1em; background-color: transparent; display: none; } + +#g-login-menu li { display: inline; padding-left: 1.2em; } +#g-logout-link { float: none; margin-right: 0; } + +#g-copyright { font-size: x-small; } +#g-footer #g-footer-rightside { float: right; text-align: right; margin-right: 1em; } +#g-credits { margin-left: 14px; margin-right: 14px; } +#g-credits li.g-branding a { float: left; } +#g-credits .g-first { display: none; } +#g-gallery-logo { display: block; width: 70px; height: 18px; background: transparent url('../images/gallery.png') no-repeat; } +#g-theme-logo { display: block; width: 70px; height: 18px; } + +/* base.css - Mini Mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.viewmode-mini #g-header { height: 2.3em; } +.viewmode-mini #g-footer { display: none; } + +/* base.css - Pagination ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-paginator { display: inline-block; width: 100%; padding: 4px 0 0 0; zoom: 1; } +.g-paginator li { display: inline; float: left; margin-left: 0; zoom: 1; } +.g-paginator a { padding: 0; } + +.g-paginator .g-pagination { width: 80%; font-size: 0.8em; } +.g-paginator .g-navigation { text-align: right; width: 20%; } + +/* base.css - Album grid ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-album-grid { padding: 6px 0 0 0; width: 100%; display: inline-block; margin: 0px auto; } +#g-album-grid .g-item { position: relative; float: left; margin: 4px 2px 4px 0; min-width: 212px; zoom: 1; } +.g-extended #g-album-grid .g-item { min-width: 314px; } +.g-column-2 #g-album-grid .g-item { width: 48%; margin-right: 0; } +.g-column-3 #g-album-grid .g-item { width: 30%; margin-right: 0; } +.g-column-4 #g-album-grid .g-item { width: 23%; margin-right: 0; } +.g-column-5 #g-album-grid .g-item { width: 18%; margin-right: 0; } +#g-album-grid .g-item p { text-align: center; } +#g-album-grid h2 { position: absolute; top: 164px; left: 12px; width: 150px; font: 100%/100% Arial, Helvetica, sans-serif; } +#g-album-grid h2 a { display: block; margin-top: 4px; font: bold 0.8em Arial, Helvetica, Verdana, Sans-Serif; letter-spacing: 0.1em; text-transform: uppercase; min-height: 2em; } + +/* base.css - Thumbs : Common ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-thumbslide { font-size: 0.9em; width: 208px; min-height: 139px; padding-top: 6px; padding-left: 6px; overflow: hidden; text-align: left; } +.g-thumbtype-sqr .g-thumbslide { height: 208px; } +.g-thumbtype-flm .g-thumbslide { height: 141px; } +.g-thumbtype-dgt .g-thumbslide { height: 158px; } +.g-thumbtype-wd .g-thumbslide { height: 120px; } + +.g-extended .g-thumbslide { width: 308px; } +.g-extended .g-thumbtype-sqr .g-thumbslide { height: 308px; } +.g-extended .g-thumbtype-flm .g-thumbslide { height: 207px; } +.g-extended .g-thumbtype-dgt .g-thumbslide { height: 233px; } +.g-extended .g-thumbtype-wd .g-thumbslide { height: 176px; } + +.g-thumbcrop { overflow: hidden; position: relative; width: 200px; min-height: 112px; } +.g-thumbtype-sqr .g-thumbcrop { height: 200px; } +.g-thumbtype-flm .g-thumbcrop { height: 133px; } +.g-thumbtype-dgt .g-thumbcrop { height: 150px; } +.g-thumbtype-wd .g-thumbcrop { height: 112px; } + +.g-extended .g-thumbcrop { width: 300px; } +.g-extended .g-thumbtype-sqr .g-thumbcrop { height: 300px; } +.g-extended .g-thumbtype-flm .g-thumbcrop { height: 199px; } +.g-extended .g-thumbtype-dgt .g-thumbcrop { height: 225px; } +.g-extended .g-thumbtype-wd .g-thumbcrop { height: 168px; } + +.g-album .g-description strong { padding-left: 16px; } + +/* Force size of the link to fill thumbcrop */ + +.g-thumbcrop a.g-thumblink { display: block; position: relative; min-width: 200px; } +.g-thumbtype-sqr a.g-thumblink { min-height: 200px; } +.g-thumbtype-flm a.g-thumblink { min-height: 133px; } +.g-thumbtype-dgt a.g-thumblink { min-height: 150px; } +.g-thumbtype-wd a.g-thumblink { min-height: 112px; } + +.g-extended .g-thumbcrop a.g-thumblink { min-width: 300px; } +.g-extended .g-thumbtype-sqr a.g-thumblink { min-height: 300px; } +.g-extended .g-thumbtype-flm a.g-thumblink { min-height: 200px; } +.g-extended .g-thumbtype-dgt a.g-thumblink { min-height: 225px; } +.g-extended .g-thumbtype-wd a.g-thumblink { min-height: 168px; } + +/* base.css - Thumbs : Overlay ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-thumbslide .g-description { display: none; position: absolute; left: 6px; top: 6px; min-height: 24px; width: 184px; overflow: hidden; z-index: 3; text-align: left; padding: 2px 8px; font-size: 0.85em; margin: 10px 0 1px 11px; } +.g-extended .g-thumbslide .g-description { width: 284px; } +.g-thumbslide:hover .g-description { display: block; } +.g-thumbslide .g-description li { display: inline; padding-right: 0.8em; } +.g-thumbslide .g-description .g-title { display: block; font-weight: bold; font-size: 1.1em; letter-spacing: 0.1em; text-transform: uppercase; padding-top: 3px; } +.g-album .g-thumbslide .g-description .g-title { padding-left: 24px; } + +.g-thumbslide .g-description.g-overlay-top { display: block; } +.g-thumbslide .g-description.g-overlay-bottom { display: block; top: auto; bottom: 6px; margin-bottom: 10px; } + +.g-thumbslide .g-metadata { display: none; position: absolute; left: 6px; bottom: 6px; padding: 2px 4px 2px 10px; width: 186px; margin: 10px 0 11px 11px; } +.g-thumbslide .g-metadata li { padding: 0; margin: 0; font-size: 0.9em; } +.g-extended .g-thumbslide .g-metadata { width: 286px; } +.g-thumbslide:hover .g-metadata { display: block; } + +/* base.css - Thumbs : Expanded View mode ~~~~~~~~~~~~~~*/ + +.g-expanded .g-thumbslide { font-size: 0.9em; width: 208px; min-height: 139px; padding-top: 6px; padding-left: 6px; line-height: 1.2em; overflow: hidden; } +.g-thumbtype-sqr.g-expanded .g-thumbslide { height: 238px; } +.g-thumbtype-flm.g-expanded .g-thumbslide { height: 171px; } +.g-thumbtype-dgt.g-expanded .g-thumbslide { height: 188px; } +.g-thumbtype-wd.g-expanded .g-thumbslide { height: 150px; } + +.g-extended .g-expanded .g-thumbslide { width: 308px; } +.g-extended .g-thumbtype-sqr.g-expanded .g-thumbslide { height: 346px; } +.g-extended .g-thumbtype-flm.g-expanded .g-thumbslide { height: 244px; } +.g-extended .g-thumbtype-dgt.g-expanded .g-thumbslide { height: 270px; } +.g-extended .g-thumbtype-wd.g-expanded .g-thumbslide { height: 214px; } + +.g-expanded .g-thumbslide .g-description { position: static; display: block; } +.g-expanded .g-thumbslide .g-description li { display: inline; padding-right: 0.8em; } +.g-expanded .g-thumbslide .g-description .g-title { display: block; font-weight: bold; font-size: 1.1em; letter-spacing: 0.1em; text-transform: uppercase; } +.g-album.g-expanded .g-thumbslide .g-description .g-title { padding-left: 24px; } + +.g-expanded .g-thumbslide .g-description.g-overlay-bottom { width: 192px; } +.g-expanded .g-thumbslide .g-metadata { bottom: 10px; } +.g-expanded .g-thumbslide .g-metadata li { padding: 0; margin: 0; font-size: 0.9em; } +.g-expanded .g-thumbslide:hover .g-metadata { display: block; bottom: 40px; } + +/* base.css - Photo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-item { float: left; height: 100%; width: 100%; } +#g-photo { padding: 6px 0 6px 0; text-align: center; float: left; height: 100%; width: 100%; } +div.g-resize { position: relative; left: 50%; float: left; padding: 5px; font-size: 0.9em; } +div.g-resize>a { float: left; overflow: hidden; } +div.g-resize>a img { float: left; } + +div.g-resize .g-description { display: none; position: absolute; left: 5px; text-align: left; padding: 10px; } +div.g-resize .g-description strong { display: block; margin-bottom: 5px; text-transform: uppercase; } +div.g-resize .g-description.g-align-top { top: 0px; margin-top: 5px; } +div.g-resize .g-description.g-align-bottom { bottom: 4px; } +div.g-resize .g-description.g-align-static { display: block; } +div.g-resize:hover .g-description { display: block; } + +div.g-resize .g-more { display: block; position: absolute; right: 16px; padding: 4px 8px; } +div.g-resize:hover .g-more { display: none; visibility: hidden; } +div.g-resize .g-more.g-align-top { top: 16px; } +div.g-resize .g-more.g-align-bottom { bottom: 20px; } + +.ul-table { text-align: center; margin: 0px auto; padding: 0; list-style-type: none; clear: both; } +.ul-table li { float: left; text-align: center; } + +#g-info { display: inline-block; width: 100%; } +#g-info .g-description { margin-top: .4em; margin-bottom: .4em; padding: .5em 1em; } +#g-movie { padding: 6px 0 6px 6px; position: relative; } + +#g-item a.g-movie { display: block; margin: 0 auto; } + +.g-description .g-metadata { padding: 0.4em 0 0 0; font-size: 0.8em; } +.g-description .g-metadata li { display: inline; padding-right: 1em; } + +/* base.css - Sidebar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* base.css - Sidebar : Common ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-block { margin-bottom: 4px; padding-bottom: 4px; position: relative; clear: both; } +.g-block h2 { padding: 6px 4px 6px 8px; font-size: 1em; } +.g-block-content { margin: 6px 6px 0 6px; display: block; zoom: 1; } + +#g-column-top .g-block, #g-column-bottom .g-block { float: left; clear: none; width: 240px; margin-left: 10px; } +#g-column-top .g-toolbar, #g-column-bottom .g-toolbar { margin-bottom: 0.5em; } +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { display: none; } + +/* base.css - Sidebar : Buttons ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-viewformat { z-index: 5; position: absolute; padding: 0; top: 6px; right: 10px; } +#g-viewformat li { float: left; margin-right: 2px; } +#g-viewformat span { display: block; width: 17px; height: 15px; line-height: 1px; text-indent: -900em; background-repeat: no-repeat; } +#g-viewformat .g-sidebar-left { background-position: -128px -210px; } +#g-viewformat .g-sidebar-top { background-position: -148px -210px; } +#g-viewformat .g-sidebar-full { background-position: -168px -210px; } +#g-viewformat .g-sidebar-right { background-position: -188px -210px; } +#g-viewformat .g-sidebar-bottom { background-position: -208px -210px; } +#g-viewformat .g-sidebar-left:hover, #g-viewformat .g-sidebar-left.g-current { background-position: -128px -225px; } +#g-viewformat .g-sidebar-top:hover, #g-viewformat .g-sidebar-top.g-current { background-position: -148px -225px; } +#g-viewformat .g-sidebar-full:hover, #g-viewformat .g-sidebar-full.g-current { background-position: -168px -225px; } +#g-viewformat .g-sidebar-right:hover, #g-viewformat .g-sidebar-right.g-current { background-position: -188px -225px; } +#g-viewformat .g-sidebar-bottom:hover,#g-viewformat .g-sidebar-bottom.g-current{ background-position: -208px -225px; } + +#g-view-menu { position: absolute; top: 6px; right: 106px; height: 16px; z-index: 5; zoom: 1; margin: 0 0 6px 0; padding: 0 0 4px 0; } +#g-view-menu.g-buttonset-shift { right: 6px; } +.g-toolbar { margin: 0 0 4px 0; } +.g-menu { margin: 0; padding: 0; text-align: left; } +.g-menu li { display: inline; } + +.g-menu-element, +.g-menu-link { display: inline; float: left; margin-right: 4px; background-repeat: no-repeat; background-position: center top; } + +.g-buttonset .g-menu-link { text-indent: -99999px; width: 22px; height: 15px; overflow: hidden; } + +#g-slideshow-link { background-position: -103px -210px; } +#g-slideshow-link:hover { background-position: -103px -225px; } + +.g-fullsize-link:hover, #g-exifdata-link:hover { background-position: left bottom; } + +/* base.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-quote { float: left; width: 300px; padding-top: 6px; font-size: 110%; } +#g-rootpage-roll { float: right; text-align: center; z-index: 1; position: relative; width: 800px; height: 540px; overflow: hidden; } +#g-rootpage-link { z-index: 10; position: absolute; height: 540px; width: 800px; top: 0; left: 0; cursor: pointer; } +#g-rootpage-roll span { z-index: 50; display: block; position: absolute; right: 16px; padding: 4px 8px; top: 20px; cursor: pointer; font-size: 0.9em; } +#g-rootpage-roll.g-full { margin-left: auto; margin-right: auto; float: none; } +#g-rootpage-slideshow div img { border: 0; } + +/* base.css - Large toolbar icons support ~~~~~~~~~~~~~~*/ + +.g-toolbar-large .g-buttonset .g-menu-link { width: 26px; height: 22px; } +.g-toolbar-large h1 { line-height: 24px; height: 24px; } +.g-toolbar-large #g-calendarview-link:hover { background-position: top center; } +.g-toolbar-large #g-viewformat { top: 9px; } + +.g-toolbar-large .g-navigation .ui-icon { width: 23px; height: 22px; } +.g-toolbar-large .ui-icon-first { background-position: 0px -240px; } +.g-toolbar-large .ui-icon-first-d { background-position: 0px -263px; } +.g-toolbar-large .ui-icon-prev { background-position: -23px -240px; } +.g-toolbar-large .ui-icon-prev-d { background-position: -23px -263px; } +.g-toolbar-large .ui-icon-parent { background-position: -47px -240px; } +.g-toolbar-large .ui-icon-parent-d { background-position: -47px -263px; } +.g-toolbar-large .ui-icon-next { background-position: -70px -240px; } +.g-toolbar-large .ui-icon-next-d { background-position: -70px -263px; } +.g-toolbar-large .ui-icon-last { background-position: -93px -240px; } +.g-toolbar-large .ui-icon-last-d { background-position: -93px -263px; } + +/* base.css - Upload dialog ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-status li.g-success { background: url('../images/ico-success.png') transparent no-repeat .4em 50%; } +#g-add-photos-status li.g-error { background: url('../images/ico-error.png') transparent no-repeat .4em 50%; color: #f00; } diff --git a/themes/greydragon/css/colorpacks/blackhawk/css/colors.css b/themes/greydragon/css/colorpacks/blackhawk/css/colors.css new file mode 100644 index 0000000..5ab71d0 --- /dev/null +++ b/themes/greydragon/css/colorpacks/blackhawk/css/colors.css @@ -0,0 +1,169 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: SlateBlue + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #101010; } +body { color: #8C8C8C; background: #101010; } + +h1 { border-bottom: #424242 1px solid; } +a { color: #6392CF !important; } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#g-header { border: #424242 1px solid; } +#g-header .g-message-block { border: 1px #888 solid; background-color: #AAA; color: #000; } +#g-main { border-left: #424242 1px solid; border-right: #424242 1px solid; } +.viewmode-mini #g-main { border-bottom: #424242 1px solid; } + +#g-footer { font-size: 10px; border: #424242 1px solid; } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top, #g-column-bottom { border-left: #424242 1px solid; border-right: #424242 1px solid; } +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info .g-description { border: #424242 1px solid; } + +.g-thumbcrop { border-color: #424142; } +.g-thumbslide, .g-album .g-thumbslide { background: #212021; border-color: #424142 #000 #000 #424142; } +.g-thumbslide .g-description { color: #fff; border-top: 1px solid #424142; border-bottom: 1px solid #424142; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-album.g-expanded .g-thumbslide .g-description { background: transparent url(../images/ico-album.png) no-repeat 8px 4px; } +.g-album .g-thumbslide .g-description { background: #1E1E1E url(../images/ico-album.png) no-repeat 8px 4px; } +.g-thumbslide .g-metadata { border-top: 1px solid #424142; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-expanded .g-thumbslide .g-metadata { border-top: 1px solid #424142; background: #1E1E1E; } + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { background: #212021; border-top: 1px solid #424142; border-left: 1px solid #424142; border-right: 1px solid #000; border-bottom: 1px solid #000; } +div.g-resize .g-description { color: #fff; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #999; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #999; } +div.g-resize .g-more { border: 1px solid #999; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #888; padding: 5px; background: #555; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block h2 { border-top: 1px solid #424142; border-left: 1px solid #424142; border-right: 1px solid #000; border-bottom: 1px solid #000; } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #424142; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background-color: #101415; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background: #101415 url('../images/section.png') repeat-x; } +#sb-counter a { color: #fff !important; font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #101010; border: #303030 1px solid; } +#g-add-photos-status { background-color: #101010; border: #303030 1px solid; } + +.uploadifyQueueItem { color: #000; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #888 1px solid; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile .g-avatar { border: 1px solid #888; background: #555; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li a:hover { color: #000000; background-color: #303030; } +#g-site-menu li:hover, +#g-site-menu li.iemhover { border: #303030 1px solid; background-color: #303030; border-bottom: #000000 1px solid; } +#g-site-menu li ul { border: #000000 1px solid; } +#g-site-menu li ul li { border: #C0C0C0 0px solid; background-color: #212121; } +#g-site-menu li ul li:hover, +#g-site-menu li ul li.iemhover { border: #C0C0C0 0 solid; background-color: #303030; } + +#g-site-menu.g-bar { border: #000000 1px solid; background-color: #212121; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #181818 none; border: 1px #888 solid; } +.g-item .g-context-menu li li a:hover { background-color: #303030; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #303030 1px solid; } +#g-exif-data .g-even { background-color: #404040; } +#g-exif-data .g-odd { background-color: #303030; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #424242; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #888; background: #555; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #424242; color: #8C8C8C; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #424242; } +#g-comment-form { border: 1px dotted #424242; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #8C8C8C; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form input[type="text"] { background-color: transparent; border: 1px solid #424242; color: #8C8C8C; } +#g-quick-search-form input[type="submit"] { background: transparent url(../images/search.png) no-repeat center top; border: none; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background: url(../images/section.png) repeat-x; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #424242; } +.ui-widget-content { border: 1px solid #303030; background-color: #101010; color: #8C8C8C; } +.ui-progressbar .ui-progressbar-value { background: #424242; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar-b.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/view-fullsize-b.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/blackhawk/images/ajax-loading.gif new file mode 100644 index 0000000..0996045 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/colorpack.png b/themes/greydragon/css/colorpacks/blackhawk/images/colorpack.png new file mode 100644 index 0000000..96f5372 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ico-album.png b/themes/greydragon/css/colorpacks/blackhawk/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ico-error.png b/themes/greydragon/css/colorpacks/blackhawk/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ico-help.png b/themes/greydragon/css/colorpacks/blackhawk/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ico-info.png b/themes/greydragon/css/colorpacks/blackhawk/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ico-warning.png b/themes/greydragon/css/colorpacks/blackhawk/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/loading-large.gif b/themes/greydragon/css/colorpacks/blackhawk/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/loading-small.gif b/themes/greydragon/css/colorpacks/blackhawk/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/search.png b/themes/greydragon/css/colorpacks/blackhawk/images/search.png new file mode 100644 index 0000000..1bfa411 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/search.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/ui-icons.png b/themes/greydragon/css/colorpacks/blackhawk/images/ui-icons.png new file mode 100644 index 0000000..a71d438 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-calendar-b.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-calendar-b.png new file mode 100644 index 0000000..c5a1248 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-calendar-b.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-calendar.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-calendar.png new file mode 100644 index 0000000..bd1d03c Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-comments-b.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-comments-b.png new file mode 100644 index 0000000..aa91298 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-comments.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-comments.png new file mode 100644 index 0000000..293c587 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize-b.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize-b.png new file mode 100644 index 0000000..f659d79 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize-b.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize.png new file mode 100644 index 0000000..ed76257 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-info-b.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-info-b.png new file mode 100644 index 0000000..880d3e8 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-info-o.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-info.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-info.png new file mode 100644 index 0000000..521439c Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/blackhawk/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/blackhawk/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/blackhawk/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/css/colors.css b/themes/greydragon/css/colorpacks/carbon/css/colors.css new file mode 100644 index 0000000..618a105 --- /dev/null +++ b/themes/greydragon/css/colorpacks/carbon/css/colors.css @@ -0,0 +1,188 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: Carbon + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #333; } +body { color: #999; background-color: #333; } + +h1 { border-bottom: #6f6f6f 1px solid; } +a { color: #999 !important; font-weight: bold; } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-header .g-message-block { border: 1px #888 solid; background-color: #AAA; color: #000; } +#g-main { background-color: #3f3f3f; margin-left: 10px; margin-right: 10px; } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top { border-top: #737373 1px solid; } +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info .g-description { border: #6f6f6f 1px solid; } + +.g-thumbslide { background: #555; border-color: #303E43; } +.g-album .g-thumbslide { border-color: #6f6f6f; } +.g-thumbcrop { border-color: #303E43; } + +.g-thumbslide .g-description { color: #fff; border-top: 1px solid #303e43; border-bottom: 1px solid #303e43; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-album.g-expanded .g-thumbslide .g-description { background: transparent url(../images/ico-album.png) no-repeat 8px 4px; } +.g-album .g-thumbslide .g-description { background: #3f3f3f url(../images/ico-album.png) no-repeat 8px 4px; } +.g-thumbslide .g-metadata { border-top: 1px solid #303e43; background: #3f3f3f; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; color: #FFF; } +.g-expanded .g-thumbslide .g-metadata { border-top: 1px solid #303e43; background: #3f3f3f; } + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { border: 1px solid #888; background: #555; } +div.g-resize .g-description { color: #fff; background: #3f3f3f; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #999; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #999; } +div.g-resize .g-more { border: 1px solid #999; background: #3f3f3f; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #888; padding: 5px; background: #555; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block { border: 1px solid #6f6f6f; } + +.g-block h2 { background-color: rgb(51, 51, 51); } +#g-column-bottom .g-block h2 { background-color: rgb(41, 41, 41); } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #999; background: #3f3f3f; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li a:hover { color: #000000; background-color: #333; } +#g-site-menu li:hover, +#g-site-menu li.iemhover { border: #303030 1px solid; background-color: #333; border-bottom: #000000 1px solid; } +#g-site-menu li ul { border: #000000 1px solid; } +#g-site-menu li ul li { border: #C0C0C0 0px solid; background-color: #333; } +#g-site-menu li ul li:hover, +#g-site-menu li ul li.iemhover { border: #C0C0C0 0 solid; background-color: #ddf2ff; } + +#g-site-menu.g-bar { border: #000000 1px solid; background-color: #333; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #333 none; border: 1px #888 solid; } +.g-item .g-context-menu li li a:hover { background-color: #ddf2ff; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background-color: #101415; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background-color: #333; } +#sb-counter a { color: #fff !important; font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #101010; border: #303030 1px solid; } +#g-add-photos-button { border: #303030 1px solid; color: #bbb; } +#g-add-photos-status { background-color: #101010; border: #303030 1px solid; } + +.uploadifyQueueItem { color: #000; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile .g-avatar { border: 1px solid #888; background: #555; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li a:hover { color: #000000; background-color: #303030; } +#g-site-menu li:hover, +#g-site-menu li.iemhover { border: #303030 1px solid; background-color: #303030; border-bottom: #000000 1px solid; } +#g-site-menu li ul { border: #000000 1px solid; } +#g-site-menu li ul li { border: #C0C0C0 0px solid; background-color: #212121; } +#g-site-menu li ul li:hover, +#g-site-menu li ul li.iemhover { border: #C0C0C0 0 solid; background-color: #303030; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #181818 none; border: 1px #888 solid; } +.g-item .g-context-menu li li a:hover { background-color: #303030; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #303030 1px solid; } +#g-exif-data .g-even { background-color: #404040; } +#g-exif-data .g-odd { background-color: #303030; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #737373; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #888; background: #555; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #202628; color: #999; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #737373; } +#g-comment-form { border: 1px dotted #737373; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #616b76; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form input[type="text"] { background-color: transparent; border: 1px solid #737373; color: #BBB; } +#g-quick-search-form input[type="submit"] { background: transparent url(../images/search.png) no-repeat center top; border: none; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background: url(../images/section.png) repeat-x; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #333 url('../images/section.png') repeat-x; } +.ui-widget-content { border: 1px solid #303030; background-color: #333; color: #bbb; } +.ui-progressbar .ui-progressbar-value { background: #737373; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #737373 1px solid; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar-b.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/view-fullsize-b.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/carbon/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/carbon/images/ajax-loading.gif new file mode 100644 index 0000000..0996045 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/colorpack.png b/themes/greydragon/css/colorpacks/carbon/images/colorpack.png new file mode 100644 index 0000000..bc51d4f Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/ico-album.png b/themes/greydragon/css/colorpacks/carbon/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/ico-error.png b/themes/greydragon/css/colorpacks/carbon/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/ico-help.png b/themes/greydragon/css/colorpacks/carbon/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/ico-info.png b/themes/greydragon/css/colorpacks/carbon/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/ico-warning.png b/themes/greydragon/css/colorpacks/carbon/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/loading-large.gif b/themes/greydragon/css/colorpacks/carbon/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/loading-small.gif b/themes/greydragon/css/colorpacks/carbon/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/search.png b/themes/greydragon/css/colorpacks/carbon/images/search.png new file mode 100644 index 0000000..1bfa411 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/search.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/section.png b/themes/greydragon/css/colorpacks/carbon/images/section.png new file mode 100644 index 0000000..8180ecb Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/section.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/ui-icons.png b/themes/greydragon/css/colorpacks/carbon/images/ui-icons.png new file mode 100644 index 0000000..ef92612 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-calendar-b.png b/themes/greydragon/css/colorpacks/carbon/images/view-calendar-b.png new file mode 100644 index 0000000..c5a1248 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-calendar-b.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-calendar.png b/themes/greydragon/css/colorpacks/carbon/images/view-calendar.png new file mode 100644 index 0000000..bd1d03c Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-comments-b.png b/themes/greydragon/css/colorpacks/carbon/images/view-comments-b.png new file mode 100644 index 0000000..aa91298 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-comments.png b/themes/greydragon/css/colorpacks/carbon/images/view-comments.png new file mode 100644 index 0000000..293c587 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-fullsize-b.png b/themes/greydragon/css/colorpacks/carbon/images/view-fullsize-b.png new file mode 100644 index 0000000..f659d79 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-fullsize-b.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-fullsize.png b/themes/greydragon/css/colorpacks/carbon/images/view-fullsize.png new file mode 100644 index 0000000..ed76257 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-info-b.png b/themes/greydragon/css/colorpacks/carbon/images/view-info-b.png new file mode 100644 index 0000000..880d3e8 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-info-o.png b/themes/greydragon/css/colorpacks/carbon/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-info.png b/themes/greydragon/css/colorpacks/carbon/images/view-info.png new file mode 100644 index 0000000..521439c Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/carbon/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/carbon/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/carbon/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/css/colors.css b/themes/greydragon/css/colorpacks/greydragon/css/colors.css new file mode 100644 index 0000000..23fb5eb --- /dev/null +++ b/themes/greydragon/css/colorpacks/greydragon/css/colors.css @@ -0,0 +1,173 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: GreyDragon - Default color pack + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #1A2022; } +body { color: #BBB; background: #1A2022; } + +h1 { border-bottom: #737373 1px solid; } +a { color: #6392CF !important; } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#g-header { border-left: #10151c 1px solid; border-right: #10151c 1px solid; background: url(../images/background-top.gif) #1A2022 repeat-x; } +#g-header .g-message-block { border: 1px #888 solid; background-color: #AAA; color: #000; } +#g-main { border-left: #10151c 1px solid; border-right: #10151c 1px solid; background: url(../images/background-bottom.gif) #1A2022 repeat-x; } +.viewmode-mini #g-main { border-bottom: #10151c 1px solid; } +#g-footer { background: url(../images/footer.png) #1A2022 repeat-x top !important; } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top { background: url(../images/background-bottom.gif) #1A2022 repeat-x; border: #10151C 1px solid; } +#g-column-bottom { background-color: #1a2022; border: #10151C 1px solid; } +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info .g-description { border: #737373 1px solid; } + +.g-thumbslide { background: #1E1E1E url('../images/image-thumb.gif') repeat-x; border-color: #303E43; } +.g-album .g-thumbslide { border-color: #43565B; } +.g-thumbcrop { border-color: #303E43; } +.g-thumbslide .g-description { color: #fff; border-top: 1px solid #303e43; border-bottom: 1px solid #303e43; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-album.g-expanded .g-thumbslide .g-description { background: transparent url(../images/ico-album.png) no-repeat 8px 4px; } +.g-album .g-thumbslide .g-description { background: #1E1E1E url(../images/ico-album.png) no-repeat 8px 4px; } +.g-thumbslide .g-metadata { border-top: 1px solid #303e43; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-expanded .g-thumbslide .g-metadata { border-top: 1px solid #303e43; background: #1E1E1E; } +.g-album .g-thumbslide, .g-photo .g-thumbslide { background-color: #1E1E1E; } + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { border: 1px solid #888; background: #555; } +div.g-resize .g-description { color: #fff; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #999; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #999; } +div.g-resize .g-more { border: 1px solid #999; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #888; padding: 5px; background: #555; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block { border: 1px solid #737373; background-color: #101415; } +.g-block h2 { background: url(../images/section.png) repeat-x; } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #999; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background: #101415 url('../images/ajax-loading.gif') no-repeat center center; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background: #101415 url('../images/section.png') repeat-x; } +#sb-counter a { color: #fff !important; font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #101010; border: #303030 1px solid; } +#ag-add-photos-button { border: #303030 1px solid; color: #bbb; } +#g-add-photos-status { background-color: #101010; border: #303030 1px solid; } +.uploadifyQueueItem { color: #000; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile .g-avatar { border: 1px solid #888; background: #555; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li a:hover { color: #000000; background-color: #303030; } +#g-site-menu li:hover, +#g-site-menu li.iemhover { border: #303030 1px solid; background-color: #303030; border-bottom: #000000 1px solid; } +#g-site-menu li ul { border: #000000 1px solid; } +#g-site-menu li ul li { border: #C0C0C0 0px solid; background-color: #212121; } +#g-site-menu li ul li:hover, +#g-site-menu li ul li.iemhover { border: #C0C0C0 0 solid; background-color: #303030; } + +#g-site-menu.g-bar { border: #000000 1px solid; background-color: #212121; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #181818 none; border: 1px #888 solid; } +.g-item .g-context-menu li li a:hover { background-color: #303030; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #303030 1px solid; } +#g-exif-data .g-even { background-color: #404040; } +#g-exif-data .g-odd { background-color: #303030; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #737373; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #888; background: #555; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #202628; color: #999; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #737373; } +#g-comment-form { border: 1px dotted #737373; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #616b76; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form input[type="text"] { background-color: transparent; border: 1px solid #737373; color: #BBB; } +#g-quick-search-form input[type="submit"] { background: transparent url(../images/search.png) no-repeat center top; border: none; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background: url(../images/section.png) repeat-x; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #101415 url('../images/section.png') repeat-x; } +.ui-widget-content { border: 1px solid #303030; background-color: #1a2022; color: #bbb; } +.ui-progressbar .ui-progressbar-value { background: #737373; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #888 1px solid; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar-b.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/view-fullsize-b.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/greydragon/images/ajax-loading.gif new file mode 100644 index 0000000..0996045 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/background-bottom.gif b/themes/greydragon/css/colorpacks/greydragon/images/background-bottom.gif new file mode 100644 index 0000000..1f675f3 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/background-bottom.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/background-top.gif b/themes/greydragon/css/colorpacks/greydragon/images/background-top.gif new file mode 100644 index 0000000..ec70a64 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/background-top.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/background.gif b/themes/greydragon/css/colorpacks/greydragon/images/background.gif new file mode 100644 index 0000000..b808356 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/background.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/colorpack.png b/themes/greydragon/css/colorpacks/greydragon/images/colorpack.png new file mode 100644 index 0000000..b4bd4ca Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/footer.png b/themes/greydragon/css/colorpacks/greydragon/images/footer.png new file mode 100644 index 0000000..04d5ee5 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/footer.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ico-album.png b/themes/greydragon/css/colorpacks/greydragon/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ico-error.png b/themes/greydragon/css/colorpacks/greydragon/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ico-help.png b/themes/greydragon/css/colorpacks/greydragon/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ico-info.png b/themes/greydragon/css/colorpacks/greydragon/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ico-warning.png b/themes/greydragon/css/colorpacks/greydragon/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/image-thumb.gif b/themes/greydragon/css/colorpacks/greydragon/images/image-thumb.gif new file mode 100644 index 0000000..bc3a192 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/image-thumb.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/loading-large.gif b/themes/greydragon/css/colorpacks/greydragon/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/loading-small.gif b/themes/greydragon/css/colorpacks/greydragon/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/search.png b/themes/greydragon/css/colorpacks/greydragon/images/search.png new file mode 100644 index 0000000..1bfa411 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/search.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/section.png b/themes/greydragon/css/colorpacks/greydragon/images/section.png new file mode 100644 index 0000000..8180ecb Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/section.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/ui-icons.png b/themes/greydragon/css/colorpacks/greydragon/images/ui-icons.png new file mode 100644 index 0000000..41ce8ff Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-calendar-b.png b/themes/greydragon/css/colorpacks/greydragon/images/view-calendar-b.png new file mode 100644 index 0000000..c5a1248 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-calendar-b.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-calendar.png b/themes/greydragon/css/colorpacks/greydragon/images/view-calendar.png new file mode 100644 index 0000000..bd1d03c Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-comments-b.png b/themes/greydragon/css/colorpacks/greydragon/images/view-comments-b.png new file mode 100644 index 0000000..aa91298 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-comments.png b/themes/greydragon/css/colorpacks/greydragon/images/view-comments.png new file mode 100644 index 0000000..293c587 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-fullsize-b.png b/themes/greydragon/css/colorpacks/greydragon/images/view-fullsize-b.png new file mode 100644 index 0000000..f659d79 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-fullsize-b.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-fullsize.png b/themes/greydragon/css/colorpacks/greydragon/images/view-fullsize.png new file mode 100644 index 0000000..ed76257 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-info-b.png b/themes/greydragon/css/colorpacks/greydragon/images/view-info-b.png new file mode 100644 index 0000000..880d3e8 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-info-o.png b/themes/greydragon/css/colorpacks/greydragon/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-info.png b/themes/greydragon/css/colorpacks/greydragon/images/view-info.png new file mode 100644 index 0000000..521439c Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/greydragon/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/greydragon/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/greydragon/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/css/colors.css b/themes/greydragon/css/colorpacks/roundrobin/css/colors.css new file mode 100644 index 0000000..6878554 --- /dev/null +++ b/themes/greydragon/css/colorpacks/roundrobin/css/colors.css @@ -0,0 +1,192 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: Wind - White Hawk color pack + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #FFFFFF; } +body { color: #555; margin-top: 1em; padding-left: 0px; padding-right: 0px; background: transparent url(../images/bg-body.jpg) top left; border-radius: 10px; box-shadow: 4px 4px 12px #333; } + +a { color: #555 !important } +a:hover { text-decoration: underline; } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#g-main-in { overflow: visible; } +#g-header { border-bottom: #050505 1px solid; background: transparent url(../images/bg-header.jpg) repeat-x top left; border-top-left-radius: 10px; border-top-right-radius: 10px; } +#g-header .g-message-block { border: 1px #ccc solid; color: #000; } +#g-footer { border-top: #050505 1px solid; background: transparent url(../images/bg-header.jpg) repeat-x top left; border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } + +#g-column-left { position: relative; float: left; right: 40px; top: 16px; background: transparent url(../images/bg-header.jpg) top left; border-radius: 6px; padding-top: 4px; box-shadow: 4px 4px 12px #333; } +#g-column-right { position: relative; float: right; left: 40px; top: 16px; background: transparent url(../images/bg-header.jpg) top left; border-radius: 6px; padding-top: 4px; box-shadow: 4px 4px 12px #333; } + +.g-sidebar-left #g-column-centerright { position: relative; left: -16px; } +.g-sidebar-left #g-view-menu { right: 120px; } +.g-sidebar-left #g-viewformat { right: 24px; } + +.g-sidebar-right #g-column-centerleft { position: relative; left: 16px; } +.g-sidebar-right #g-view-menu { top: 22px; } +.g-sidebar-right #g-viewformat { top: 22px; } +.g-toolbar-large.g-sidebar-right #g-viewformat { top: 26px; } + +#g-gallery-logo { background-image: url('../images/gallery.png'); } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +#g-rootpage.g-sidebar-left #g-column-centerright, +#g-rootpage.g-sidebar-right #g-column-centerleft { left: 0px; padding-left: 26px; } +#g-header #g-login-menu { top: 0.6em; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info h1, #g-album-header h1 { border-bottom: #ccc 1px solid; } +#g-info .g-description { border: #ccc 1px solid; } + +.g-thumbslide { border-color: #ccc; } +.g-album .g-thumbslide { border-color: #ccc; } +.g-thumbcrop { border-color: #ccc; } + +.g-default .g-thumbslide .g-description { color: #000; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-expanded .g-thumbslide .g-description { color: #fff; background: transparent; border: none; } +.g-album .g-thumbslide .g-description { background-image: url(../images/ico-album.png); background-repeat: no-repeat; background-position: 8px 4px; } +.g-thumbslide .g-metadata { border-top: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { border: 1px solid #ccc; } +div.g-resize .g-description { color: #000; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #ccc; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #ccc; } +div.g-resize .g-more { border: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #ccc; padding: 5px; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block { } +.g-block h2 { border-bottom: 1px solid #ccc; background: rgba(255, 255, 255, 0.5); border-radius: 5px; } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background-color: #fff; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background: #e8e8e8; color: #000; } +#sb-title-inner { color: #000; } +#sb-counter a { font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #fff; border: #303030 1px solid; } +#g-add-photos-button { border: #303030 1px solid; } +#g-add-photos-status { background-color: #fff; border: #303030 1px solid; } + +.uploadifyQueueItem { color: #000; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile h1 { border-bottom: #ccc 1px solid; } +#g-user-profile .g-avatar { border: 1px solid #ccc; background: #fff; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; color: #000; } +#g-site-menu li { background-color: #ccc; } +#g-site-menu li ul { border: #ccc 1px solid; } +#g-site-menu li ul li { border: #ccc 0px solid; background-color: #ccc; } + +#g-site-menu>ul>li { background-color: transparent; } + +#g-site-menu.g-bar { border: #ccc 1px solid; background-color: #ccc; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #e8e8e8 none; border: 1px #ccc solid; } +.g-item .g-context-menu li li a:hover { background-color: #e8e8e8; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #ccc 1px solid; } +#g-exif-data .g-even { background-color: #e8e8e8; } +#g-exif-data .g-odd { background-color: #FFFFFF; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #ccc; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #ccc; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #202628; color: #999; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #ccc; } +#g-comment-form { border: 1px dotted #ccc; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #616b76; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form.g-short-form li { margin-left: -2px !important; } +#g-quick-search-form input[type="text"] { background-color: #FFF; border: 1px solid #ccc; color: #666; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } +#ag-quick-search-form input[type="submit"] { border: #c5dbec 1px solid; text-indent: 0; width: auto; height: auto; font: 80% arial, helvetica, clean, sans-serif; font-weight: bold; padding-top: 3px; padding-bottom: 3px; } +#g-quick-search-form input[type="submit"] { background: transparent url(../images/search.png) no-repeat center top; border: none; } + +#g-search-results h1 { border-bottom: #ccc 1px solid; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background-color: #e8e8e8; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #e8e8e8; color: #666666; } +.ui-widget-content { border: 1px solid #303030; background-color: #fff; color: #000; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; background: #303030; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #ccc 1px solid; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar-b.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/view-fullsize-b.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/roundrobin/images/ajax-loading.gif new file mode 100644 index 0000000..53dd589 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/bg-body.jpg b/themes/greydragon/css/colorpacks/roundrobin/images/bg-body.jpg new file mode 100644 index 0000000..08f519b Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/bg-body.jpg differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/bg-header.jpg b/themes/greydragon/css/colorpacks/roundrobin/images/bg-header.jpg new file mode 100644 index 0000000..f33f0c6 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/bg-header.jpg differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/colorpack.png b/themes/greydragon/css/colorpacks/roundrobin/images/colorpack.png new file mode 100644 index 0000000..12fea30 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/gallery.png b/themes/greydragon/css/colorpacks/roundrobin/images/gallery.png new file mode 100644 index 0000000..9ee765c Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/gallery.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ico-album.png b/themes/greydragon/css/colorpacks/roundrobin/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ico-error.png b/themes/greydragon/css/colorpacks/roundrobin/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ico-help.png b/themes/greydragon/css/colorpacks/roundrobin/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ico-info.png b/themes/greydragon/css/colorpacks/roundrobin/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ico-warning.png b/themes/greydragon/css/colorpacks/roundrobin/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/loading-large.gif b/themes/greydragon/css/colorpacks/roundrobin/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/loading-small.gif b/themes/greydragon/css/colorpacks/roundrobin/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/search.png b/themes/greydragon/css/colorpacks/roundrobin/images/search.png new file mode 100644 index 0000000..d9592d8 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/search.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/ui-icons.png b/themes/greydragon/css/colorpacks/roundrobin/images/ui-icons.png new file mode 100644 index 0000000..4082a0e Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-calendar-b.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-calendar-b.png new file mode 100644 index 0000000..f3ab6aa Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-calendar-b.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-calendar.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-calendar.png new file mode 100644 index 0000000..a4d038a Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-comments-b.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-comments-b.png new file mode 100644 index 0000000..aa91298 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-comments.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-comments.png new file mode 100644 index 0000000..5aca071 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize-b.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize-b.png new file mode 100644 index 0000000..fad5e53 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize-b.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize.png new file mode 100644 index 0000000..cc4fbba Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-info-b.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-info-b.png new file mode 100644 index 0000000..90f1bb4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-info-o.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-info.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-info.png new file mode 100644 index 0000000..4bdb724 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/roundrobin/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/roundrobin/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/roundrobin/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/css/colors.css b/themes/greydragon/css/colorpacks/slateblue/css/colors.css new file mode 100644 index 0000000..c684319 --- /dev/null +++ b/themes/greydragon/css/colorpacks/slateblue/css/colors.css @@ -0,0 +1,176 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: SlateBlue + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #1c242e; } +body { color: #BBB; background: #1c242e; } + +h1 { border-bottom: #737373 1px solid; } +a { color: #6392CF !important; } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#g-header { border-left: #10151c 1px solid; border-right: #10151c 1px solid; background: url(../images/background.jpg) #1c242e repeat-x; } +.viewmode-mini #g-header { border-bottom: #10151c 1px solid; } +#g-header .g-message-block { border: 1px #888 solid; background-color: #AAA; color: #000; } +#g-main { border-left: #10151c 1px solid; border-right: #10151c 1px solid; } +.viewmode-mini #g-main { border-bottom: #10151c 1px solid; } +#g-footer { background: #000; font-size: 10px; } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top, #g-column-bottom { background-color: #1a1e27; border: #10151C 1px solid; border-top: none;} +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info .g-description { border: #10151c 1px solid; background-color: #1a1e27; } + +.g-thumbslide { border-color: #303E43; } +.g-album .g-thumbslide { border-color: #43565B; } +.g-thumbcrop { border-color: #303E43; } + +.g-default .g-thumbslide .g-description { color: #fff; background: #1E1E1E; border-top: 1px solid #303e43; border-bottom: 1px solid #303e43; opacity:.85; -ms-filter: "alpha (opacity=85)"; filter: alpha (opacity=85); } +.g-expanded .g-thumbslide .g-description { color: #fff; background: transparent; border: none; } +.g-album .g-thumbslide .g-description { background-image: url(../images/ico-album.png); background-repeat: no-repeat; background-position: 8px 4px; } + +.g-thumbslide .g-metadata { border-top: 1px solid #303e43; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +#g-album-grid.g-panel .g-album .g-thumbslide, #g-album-grid.g-panel .g-thumbslide { border-width: 1px; border-style: solid; border-left-color: #666; border-top-color: #666; border-right-color: #10151C; border-bottom-color: #10151C; } +// #g-album-grid.g-panel .g-album .g-thumbslide, #g-album-grid.g-panel .g-thumbslide { border-width: 1px; border-style: solid; border-left-color: #666; border-top-color: #666; border-right-color: #202020; border-bottom-color: #202020; } + + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { border: #10151c 1px solid; background: #4a4e67; } +div.g-resize .g-description { color: #fff; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #999; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #999; } +div.g-resize .g-more { border: 1px solid #999; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #888; padding: 5px; background: #555; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block { border-width: 1px; border-style: solid; border-left-color: #303e43; border-top-color: #303e43; border-right-color: #10151C; border-bottom-color: #10151C; background-color: #1a1e27; } +.g-block h2 { background: #1c242e; border-bottom: #273444 1px solid; border-top: #273444 1px solid; } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #999; background: #1E1E1E; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background-color: #101415; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background: #101415 url('../images/section.png') repeat-x; } +#sb-counter a { color: #fff !important; font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #101010; border: #303030 1px solid; } +#g-add-photos-status { background-color: #101010; border: #303030 1px solid; } + +.uploadifyQueueItem { color: #000; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #888 1px solid; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile .g-avatar { border: 1px solid #888; background: #555; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li a:hover { color: #000000; background-color: #303030; } +#g-site-menu li:hover, +#g-site-menu li.iemhover { border: #303030 1px solid; background-color: #303030; border-bottom: #000000 1px solid; } +#g-site-menu li ul { border: #000000 1px solid; } +#g-site-menu li ul li { border: #C0C0C0 0px solid; background-color: #212121; } +#g-site-menu li ul li:hover, +#g-site-menu li ul li.iemhover { border: #C0C0C0 0 solid; background-color: #303030; } + +#g-site-menu.g-bar { border: #000000 1px solid; background-color: #212121; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #181818 none; border: 1px #888 solid; } +.g-item .g-context-menu li li a:hover { background-color: #303030; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #303030 1px solid; } +#g-exif-data .g-even { background-color: #404040; } +#g-exif-data .g-odd { background-color: #303030; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #737373; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #888; background: #555; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #202628; color: #999; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #737373; } +#g-comment-form { border: 1px dotted #737373; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #616b76; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form input[type="text"] { background-color: transparent; border: 1px solid #737373; color: #BBB; } +#g-quick-search-form input[type="submit"] { background: transparent url(../images/search.png) no-repeat center top; border: none; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background: url(../images/section.png) repeat-x; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #101415 url('../images/section.png') repeat-x; } +.ui-widget-content { border: 1px solid #303030; background-color: #1a2022; color: #bbb; } +.ui-progressbar .ui-progressbar-value { background: #737373; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar-b.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/view-fullsize-b.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/slateblue/images/ajax-loading.gif new file mode 100644 index 0000000..0996045 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/background.jpg b/themes/greydragon/css/colorpacks/slateblue/images/background.jpg new file mode 100644 index 0000000..7ec958a Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/background.jpg differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/colorpack.png b/themes/greydragon/css/colorpacks/slateblue/images/colorpack.png new file mode 100644 index 0000000..d3fd993 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/footer.png b/themes/greydragon/css/colorpacks/slateblue/images/footer.png new file mode 100644 index 0000000..04d5ee5 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/footer.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ico-album.png b/themes/greydragon/css/colorpacks/slateblue/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ico-error.png b/themes/greydragon/css/colorpacks/slateblue/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ico-help.png b/themes/greydragon/css/colorpacks/slateblue/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ico-info.png b/themes/greydragon/css/colorpacks/slateblue/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ico-warning.png b/themes/greydragon/css/colorpacks/slateblue/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/loading-large.gif b/themes/greydragon/css/colorpacks/slateblue/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/loading-small.gif b/themes/greydragon/css/colorpacks/slateblue/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/search.png b/themes/greydragon/css/colorpacks/slateblue/images/search.png new file mode 100644 index 0000000..1bfa411 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/search.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/section.png b/themes/greydragon/css/colorpacks/slateblue/images/section.png new file mode 100644 index 0000000..8180ecb Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/section.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/ui-icons.png b/themes/greydragon/css/colorpacks/slateblue/images/ui-icons.png new file mode 100644 index 0000000..41ce8ff Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-calendar-b.png b/themes/greydragon/css/colorpacks/slateblue/images/view-calendar-b.png new file mode 100644 index 0000000..c5a1248 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-calendar-b.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-calendar.png b/themes/greydragon/css/colorpacks/slateblue/images/view-calendar.png new file mode 100644 index 0000000..bd1d03c Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-comments-b.png b/themes/greydragon/css/colorpacks/slateblue/images/view-comments-b.png new file mode 100644 index 0000000..aa91298 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-comments.png b/themes/greydragon/css/colorpacks/slateblue/images/view-comments.png new file mode 100644 index 0000000..293c587 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-fullsize-b.png b/themes/greydragon/css/colorpacks/slateblue/images/view-fullsize-b.png new file mode 100644 index 0000000..f659d79 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-fullsize-b.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-fullsize.png b/themes/greydragon/css/colorpacks/slateblue/images/view-fullsize.png new file mode 100644 index 0000000..ed76257 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-info-b.png b/themes/greydragon/css/colorpacks/slateblue/images/view-info-b.png new file mode 100644 index 0000000..880d3e8 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-info-o.png b/themes/greydragon/css/colorpacks/slateblue/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-info.png b/themes/greydragon/css/colorpacks/slateblue/images/view-info.png new file mode 100644 index 0000000..521439c Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/slateblue/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/slateblue/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/slateblue/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/css/colors.css b/themes/greydragon/css/colorpacks/whitehawk/css/colors.css new file mode 100644 index 0000000..cd02f40 --- /dev/null +++ b/themes/greydragon/css/colorpacks/whitehawk/css/colors.css @@ -0,0 +1,172 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: Wind - White Hawk color pack + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #FFFFFF; } +body { color: #666666; padding-left: 10px; padding-right: 10px; } + +a { color: #666666 !important } +a:hover { text-decoration: underline; } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#g-header { border-bottom: #ccc 1px solid; } +#g-header .g-message-block { border: 1px #ccc solid; color: #000; } +#g-footer { border-top: #ccc 1px solid; } + +#g-gallery-logo { background-image: url('../images/gallery.png'); } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info h1, #g-album-header h1 { border-bottom: #ccc 1px solid; } +#g-info .g-description { border: #ccc 1px solid; } + +.g-thumbslide { border-color: #ccc; } +.g-album .g-thumbslide { border-color: #ccc; } +.g-thumbcrop { border-color: #ccc; } + +.g-default .g-thumbslide .g-description { color: #000; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-expanded .g-thumbslide .g-description { color: #fff; background: transparent; border: none; } +.g-album .g-thumbslide .g-description { background-image: url(../images/ico-album.png); background-repeat: no-repeat; background-position: 8px 4px; } +.g-thumbslide .g-metadata { border-top: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { border: 1px solid #ccc; } +div.g-resize .g-description { color: #000; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #ccc; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #ccc; } +div.g-resize .g-more { border: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #ccc; padding: 5px; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block { border: 1px solid #ccc; } +.g-block h2 { border-bottom: 1px solid #ccc; } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #ccc; background: #FFFFFF; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background-color: #fff; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background: #e8e8e8; color: #000; } +#sb-title-inner { color: #000; } +#sb-counter a { font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #fff; border: #303030 1px solid; } +#g-add-photos-button { border: #303030 1px solid; } +#g-add-photos-status { background-color: #fff; border: #303030 1px solid; } + +.uploadifyQueueItem { color: #000; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile h1 { border-bottom: #ccc 1px solid; } +#g-user-profile .g-avatar { border: 1px solid #ccc; background: #fff; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li { background-color: #FFFFFF; } +#g-site-menu li ul { border: #ccc 1px solid; } +#g-site-menu li ul li { border: #ccc 0px solid; background-color: #FFFFFF; } + +#g-site-menu.g-bar { border: #ccc 1px solid; background-color: #FFFFFF; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #e8e8e8 none; border: 1px #ccc solid; } +.g-item .g-context-menu li li a:hover { background-color: #e8e8e8; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #ccc 1px solid; } +#g-exif-data .g-even { background-color: #e8e8e8; } +#g-exif-data .g-odd { background-color: #FFFFFF; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #ccc; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #ccc; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #202628; color: #999; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #ccc; } +#g-comment-form { border: 1px dotted #ccc; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #616b76; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form input[type="text"] { background-color: transparent; border: 1px solid #ccc; color: #666; } +#ag-quick-search-form input[type="submit"] { border: #c5dbec 1px solid; text-indent: 0; width: auto; height: auto; font: 80% arial, helvetica, clean, sans-serif; font-weight: bold; padding-top: 3px; padding-bottom: 3px; } +#g-quick-search-form input[type="submit"] { background: transparent url(../images/search.png) no-repeat center top; border: none; } + +#g-search-results h1 { border-bottom: #ccc 1px solid; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background-color: #e8e8e8; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #e8e8e8; color: #666666; } +.ui-widget-content { border: 1px solid #303030; background-color: #fff; color: #000; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; background: #303030; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #ccc 1px solid; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar-b.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/view-fullsize-b.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/whitehawk/images/ajax-loading.gif new file mode 100644 index 0000000..53dd589 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/colorpack.png b/themes/greydragon/css/colorpacks/whitehawk/images/colorpack.png new file mode 100644 index 0000000..3b7afef Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/gallery.png b/themes/greydragon/css/colorpacks/whitehawk/images/gallery.png new file mode 100644 index 0000000..3ba425d Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/gallery.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ico-album.png b/themes/greydragon/css/colorpacks/whitehawk/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ico-error.png b/themes/greydragon/css/colorpacks/whitehawk/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ico-help.png b/themes/greydragon/css/colorpacks/whitehawk/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ico-info.png b/themes/greydragon/css/colorpacks/whitehawk/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ico-warning.png b/themes/greydragon/css/colorpacks/whitehawk/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/loading-large.gif b/themes/greydragon/css/colorpacks/whitehawk/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/loading-small.gif b/themes/greydragon/css/colorpacks/whitehawk/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/search.png b/themes/greydragon/css/colorpacks/whitehawk/images/search.png new file mode 100644 index 0000000..bb323e5 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/search.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/ui-icons.png b/themes/greydragon/css/colorpacks/whitehawk/images/ui-icons.png new file mode 100644 index 0000000..db1ec71 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-calendar-b.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-calendar-b.png new file mode 100644 index 0000000..f3ab6aa Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-calendar-b.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-calendar.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-calendar.png new file mode 100644 index 0000000..a4d038a Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-comments-b.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-comments-b.png new file mode 100644 index 0000000..aa91298 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-comments.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-comments.png new file mode 100644 index 0000000..5aca071 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize-b.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize-b.png new file mode 100644 index 0000000..fad5e53 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize-b.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize.png new file mode 100644 index 0000000..cc4fbba Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-info-b.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-info-b.png new file mode 100644 index 0000000..90f1bb4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-info-o.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-info.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-info.png new file mode 100644 index 0000000..4bdb724 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/whitehawk/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/whitehawk/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/whitehawk/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/colorpacks/wind/css/colors.css b/themes/greydragon/css/colorpacks/wind/css/colors.css new file mode 100644 index 0000000..2e53eb1 --- /dev/null +++ b/themes/greydragon/css/colorpacks/wind/css/colors.css @@ -0,0 +1,175 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * ColorPack: Wind - Wind theme-like color pack + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* styles.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { background-color: #ccc; } +body { color: #000; background-color: #ccc; padding-left: 10px; padding-right: 10px; } + +a { color: #33629f !important } +.ui-icon, #g-slideshow-link { background-image: url(../images/ui-icons.png); } + +#g-site-status li { border-bottom: 1px solid #ccc; color: #333; } +#g-site-status .g-error { background: #f6cbca url('../images/ico-error.png') no-repeat .4em 50%; } +#g-site-status .g-info { background: #e8e8e8 url('../images/ico-info.png') no-repeat .4em 50%; } +#g-site-status .g-success { background: #d9efc2 url('../../../../images/ico-success.png') no-repeat .4em 50%; } +#g-site-status .g-warning { background: #fcf9ce url('../images/ico-warning.png') no-repeat .4em 50%; } + +/* styles.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +#g-header { background-color: #e8e8e8; border-bottom: #ccc 1px solid; } +#g-header .g-message-block { border: 1px #888 solid; background-color: #aaa; color: #000; } +#g-main { background-color: #fff; } +#g-footer { background-color: #e8e8e8; border-top: #ccc 1px solid; } +#g-theme-logo { background: transparent url('../images/colorpack.png') no-repeat; } + +/* styles.css - Album Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-top, #g-column-bottom { background-color: #e8e8e8; } +#g-column-top .g-toolbar h1, #g-column-bottom .g-toolbar h1 { border: none; } +#g-info h1, #g-album-header h1 { border-bottom: #ccc 1px solid; } +#g-info .g-description { border: #888 1px solid; } + +.g-thumbslide { background: #e8e8e8; border-color: #707E83; } +.g-album .g-thumbslide { border-color: #707E83; } +.g-thumbcrop { border-color: #707E83; } + +.g-default .g-thumbslide .g-description { color: #000; border-top: 1px solid #707E83; border-bottom: 1px solid #707E83; background: #e8e8e8; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +.g-expanded .g-thumbslide .g-description { color: #fff; background: transparent; border: none; } +.g-album .g-thumbslide .g-description { background-image: url(../images/ico-album.png); background-repeat: no-repeat; background-position: 8px 4px; } + +.g-thumbslide .g-metadata { border-top: 1px solid #707E83; background: #e8e8e8; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* styles.css - Photo Layout ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +div.g-resize { border: 1px solid #888; background: #e8e8e8; } +div.g-resize .g-description { color: #000; background: #e8e8e8; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div.g-resize .g-description.g-align-top { border-bottom: 1px solid #999; } +div.g-resize .g-description.g-align-bottom { border-top: 1px solid #999; } +div.g-resize .g-more { border: 1px solid #999; background: #e8e8e8; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } +div#g-movie .g-movie { border: 1px solid #888; padding: 5px; background: #e8e8e8; } + +/* styles.css - Sidebar Blocks : Common ~~~~~~~~~~~~~~*/ + +.g-block { border: 1px solid #ccc; } +.g-block h2 { background-color: #e8e8e8; } + +/* styles.css - Sidebar Blocks : Buttons ~~~~~~~~~~~~~*/ + +.g-fullsize-link { background: url("../images/view-fullsize.png") top left no-repeat; } +#g-exifdata-link { background: url("../images/view-info.png") top left no-repeat; } + +/* styles.css - Root Page ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage-roll span { border: 1px solid #999; background: #e8e8e8; filter:alpha(opacity=85); opacity:.85; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/* styles.css - Photo Slideshow ~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-body { background-color: #fff; } +#sb-title { border-left: #303030 1px solid; border-right: #303030 1px solid; background: #e8e8e8; color: #000; } +#sb-title-inner { color: #000; } +#sb-counter a { font-weight: bold; font-size: 11px; } + +/* forms.css - Add item ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-photos-canvas { background-color: #fff; border: #303030 1px solid; } +#g-add-photos-button { border: #303030 1px solid; } +#g-add-photos-status { background-color: #fff; border: #303030 1px solid; } + +.uploadifyQueueItem { color: #000; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile h1 { border-bottom: #ccc 1px solid; } +#g-user-profile .g-avatar { border: 1px solid #888; background: #fff; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu ul { border: #000000 0 solid; } +#g-site-menu li { background-color: #bdd2ff; } +#g-site-menu li a:hover { color: #000000; background-color: #cfdeff; } +#g-site-menu li:hover, +#g-site-menu li.iemhover { border: #303030 1px solid; background-color: #cfdeff; border-bottom: #cfdeff 1px solid; } +#g-site-menu li ul { border: #cfdeff 1px solid; } +#g-site-menu li ul li { border: #C0C0C0 0px solid; background-color: #bdd2ff; } +#g-site-menu li ul li:hover, +#g-site-menu li ul li.iemhover { border: #C0C0C0 0 solid; background-color: #ddf2ff; } + +#g-site-menu.g-bar { border: #cfdeff 1px solid; background-color: #bdd2ff; } +#g-site-menu.g-bar li:hover, +#g-site-menu.g-bar li.iemhover { border-bottom-color: transparent; } + +.g-item .g-context-menu { background-image: url(../images/ui-icons.png); } +.g-item .g-context-menu:hover { background: #bdd2ff none; border: 1px #888 solid; } +.g-item .g-context-menu li li a:hover { background-color: #ddf2ff; } + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - Exif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data table { border: #303030 1px solid; } +#g-exif-data .g-even { background-color: #A0A0A0; } +#g-exif-data .g-odd { background-color: #C0C0C0; } + +/* modules.css - Info module ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata .g-description { border-top: 1px solid #ccc; } + +/* modules.css - Image block ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-image-block img { border: 1px solid #888; background: #555; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments .g-author { border-bottom: 1px solid #202628; color: #999; } +#g-comments-link { background-image: url(../images/view-comments.png); } +#g-comment-detail>ul>li { border: 1px dotted #ccc; } +#g-comment-form { border: 1px dotted #ccc; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-view-menu #g-calendarview-link { background-image: url(../images/view-calendar.png); } +#g-view-calendar-form ul { border: 1px #888 solid; } +table.calendar { border: #a2adbc 1px solid; color: #616b76; } +table.calendar th { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; background: #d9e2e1; color: #616b76; } +table.calendar td { border-bottom: #a2adbc 1px solid; border-right: #a2adbc 1px solid; } +table.calendar td.title { background-color: #a2adbc; color: #fff; } +table.calendar td.title a { color: #fff !important; } +table.calendar td a { color: red !important; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form input[type="text"] { background-color: transparent; border: 1px solid #ccc; color: #666; } +#g-quick-search-form input[type="submit"] { border: #c5dbec 1px solid; text-indent: 0; width: auto; height: auto; font: 80% arial, helvetica, clean, sans-serif; font-weight: bold; padding-top: 3px; padding-bottom: 3px; } +#g-search-results h1 { border-bottom: #ccc 1px solid; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#checkout legend { background-color: #e8e8e8; } + +/* forms.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.ui-dialog .ui-dialog-titlebar { background: #ccc url('../images/section.png') repeat-x; } +.ui-widget-content { border: 1px solid #303030; background-color: #fff; color: #000; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; background: #303030; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~*/ + +#g-column-centerfull #g-login { border: #888 1px solid; } + +/* Large toolbar icons support ~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-toolbar-large #g-view-menu #g-calendarview-link { background-image: url(../../../../../../modules/calendarview/images/ico-view-calendarview.png); } +.g-toolbar-large #g-slideshow-link { background: url(../images/view-slideshow-b.png) no-repeat top center; } +.g-toolbar-large .g-fullsize-link { background: url(../images/ico-view-fullsize.png) no-repeat top center; } +.g-toolbar-large #g-exifdata-link { background: url(../images/view-info-b.png) no-repeat top center; } +.g-toolbar-large #g-comments-link { background: url(../images/view-comments-b.png) no-repeat top center; } + +.g-thumbcrop a.g-meta-exif-link { background-image: url(../images/view-info-o.png); } \ No newline at end of file diff --git a/themes/greydragon/css/colorpacks/wind/images/ajax-loading.gif b/themes/greydragon/css/colorpacks/wind/images/ajax-loading.gif new file mode 100644 index 0000000..53dd589 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ajax-loading.gif differ diff --git a/themes/greydragon/css/colorpacks/wind/images/colorpack.png b/themes/greydragon/css/colorpacks/wind/images/colorpack.png new file mode 100644 index 0000000..d217920 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/colorpack.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-album.png b/themes/greydragon/css/colorpacks/wind/images/ico-album.png new file mode 100644 index 0000000..ac87ec4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-album.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-error.png b/themes/greydragon/css/colorpacks/wind/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-error.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-help.png b/themes/greydragon/css/colorpacks/wind/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-help.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-info.png b/themes/greydragon/css/colorpacks/wind/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-info.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-view-fullsize.png b/themes/greydragon/css/colorpacks/wind/images/ico-view-fullsize.png new file mode 100644 index 0000000..740183a Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow-rtl.png b/themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow-rtl.png new file mode 100644 index 0000000..9552d30 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow-rtl.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow.png b/themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow.png new file mode 100644 index 0000000..d43a560 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-view-slideshow.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ico-warning.png b/themes/greydragon/css/colorpacks/wind/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ico-warning.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/loading-large.gif b/themes/greydragon/css/colorpacks/wind/images/loading-large.gif new file mode 100644 index 0000000..cc70a7a Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/loading-large.gif differ diff --git a/themes/greydragon/css/colorpacks/wind/images/loading-small.gif b/themes/greydragon/css/colorpacks/wind/images/loading-small.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/loading-small.gif differ diff --git a/themes/greydragon/css/colorpacks/wind/images/section.png b/themes/greydragon/css/colorpacks/wind/images/section.png new file mode 100644 index 0000000..33fc350 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/section.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/ui-icons.png b/themes/greydragon/css/colorpacks/wind/images/ui-icons.png new file mode 100644 index 0000000..7dc8ab9 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/ui-icons.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-calendar.png b/themes/greydragon/css/colorpacks/wind/images/view-calendar.png new file mode 100644 index 0000000..13e0e8f Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-calendar.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-comments-b.png b/themes/greydragon/css/colorpacks/wind/images/view-comments-b.png new file mode 100644 index 0000000..64f8c50 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-comments-b.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-comments.png b/themes/greydragon/css/colorpacks/wind/images/view-comments.png new file mode 100644 index 0000000..f33bdf1 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-comments.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-fullsize.png b/themes/greydragon/css/colorpacks/wind/images/view-fullsize.png new file mode 100644 index 0000000..58b3e0b Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-fullsize.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-info-b.png b/themes/greydragon/css/colorpacks/wind/images/view-info-b.png new file mode 100644 index 0000000..90f1bb4 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-info-b.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-info-o.png b/themes/greydragon/css/colorpacks/wind/images/view-info-o.png new file mode 100644 index 0000000..612ba96 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-info-o.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-info.png b/themes/greydragon/css/colorpacks/wind/images/view-info.png new file mode 100644 index 0000000..2cc7a68 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-info.png differ diff --git a/themes/greydragon/css/colorpacks/wind/images/view-slideshow-b.png b/themes/greydragon/css/colorpacks/wind/images/view-slideshow-b.png new file mode 100644 index 0000000..ad13671 Binary files /dev/null and b/themes/greydragon/css/colorpacks/wind/images/view-slideshow-b.png differ diff --git a/themes/greydragon/css/custom.css b/themes/greydragon/css/custom.css new file mode 100644 index 0000000..d9b3582 --- /dev/null +++ b/themes/greydragon/css/custom.css @@ -0,0 +1,61 @@ +#fam-login { + position: absolute; + left: 50%; + top: 50%; + width: 700px; + height: 300px; + margin-top: -150px; + margin-left: -350px; + background-color: #F1F1F1; + border: 2px solid #DDD; +} + +#fam-login #g-login-form { + margin: 60px auto 0px; + width: 400px; + color: #555; + font-size: 14px; + letter-spacing: 1px; +} + +#fam-login input[type="text"], #fam-login input[type="password"] { + width: 392px; + color: #555; + font-size: 20px; + margin-top: 3px; + background-color: #DDD; + border: 2px solid #CCC; + padding: 2px; +} + +#fam-login input[type="text"]:focus, #fam-login input[type="password"]:focus { + border: 2px solid #ff8000; + background-color: #fff; +} + +#fam-login input[type="submit"] { + position: absolute; + left: 50%; + margin-left: -100px; + margin-top: 2em; + width: 200px; + font-size: 14px; + letter-spacing: 2px; + background-color: #666; + color: #FFF; + padding: 5px; +} + +#fam-login-header { + height: 48px; + background-repeat: no-repeat; + width: 400px; + position: absolute; + left: 50%; + margin-left: -200px; +} + +#fam-login #g-login-form fieldset { + border: none; + padding: 0; +} diff --git a/themes/greydragon/css/forms.css b/themes/greydragon/css/forms.css new file mode 100644 index 0000000..8375de2 --- /dev/null +++ b/themes/greydragon/css/forms.css @@ -0,0 +1,120 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules related to forms/dialogs + */ + +.ui-widget-overlay { position: absolute; top:0; left:0; bottom: 0; right: 0; position: fixed; width:100%; filter: alpha(opacity=70); -moz-opacity: 0.7; -khtml-opacity: 0.7; opacity: 0.7; background-color: #111; zoom: 1; } +.ui-widget-overlay { height: expression(document.documentElement.clientHeight + 'px'); } +.ui-widget-overlay { height: 100%; } + +/* Dialog ----------------------------------*/ + +.ui-dialog { position: relative; width: 300px; display: inline-block; z-index: 100; } +.ui-dialog .ui-dialog-titlebar { height: 1em; padding: .5em .3em .3em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; text-indent: -900em; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; display: block; clear:both; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-dialog ul { padding: 0; } +.ui-dialog .ui-dialog-content .g-right { margin-right: 20px; } + +/* forms.css - Component containers ~~~~~~~~~~~~~~~~~~*/ +.ui-widget-header { color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } +.ui-widget-content a { color: #222222; } + +/* forms.css - Forms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +form { margin: 0; } +form p.g-error { color: red; } +fieldset { border: 1px solid #ccc; margin-left: 0; margin-right: 0; padding: 0 1em .8em 1em; } +legend { display: none; } +input.textbox, input[type="text"], input[type="password"], textarea { border: 1px solid #e8e8e8; border-top-color: #ccc; border-left-color: #ccc; clear: both; color: #333; width: 50%; } +textarea { height: 12em; width: 97%; } +input:focus, input.textbox:focus, input[type=text]:focus, textarea:focus, option:focus { background-color: #ffc; color: #000; } +input.checkbox, input[type=checkbox], input.radio, input[type=radio] { float: left; margin-right: .4em; } + +/* forms.css - Layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +form li { margin: 0; padding: 0 0 .2em 0; } +form ul { margin-top: 0; } +form ul ul { clear: both; } +form ul ul li { float: left; margin-right: 0.6em; } +input, select, textarea { display: block; clear: both; } +input[type="submit"], input[type="reset"] { display: inline; } +input[type="submit"], .g-button, button { display: inline-block; padding: 2px 10px 2px; font-size: 0.8em; color: #333 !important; font-weight: normal; line-height: 1.4em; text-align: center; text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); background-color: #fafafa; background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); background-repeat: no-repeat; filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); border: 1px solid #ccc; border-bottom-color: #bbb; cursor: pointer; cursor: hand; *margin-left: .3em; } + +.ui-dialog .g-cancel { display: none; } + +.g-button { margin: 0 0.4em 0.4em 0; } +.g-right { float: right; } + +/* forms.css - Forms in dialogs and panels ~~~~~~~~~~~*/ + +#g-dialog ul li { padding-bottom: 0.4em; } +#g-dialog fieldset, #g-panel fieldset { border: none; padding: 0; } +#g-panel legend { display: none; } +input[readonly] { background-color: #F4F4FC; } +#g-dialog input.textbox, #g-dialog input[type=text], #g-dialog input[type=password], #g-dialog textarea { width: 97%; } + +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } +.g-progress-bar { height: 1em; width: 100%; margin-top: .5em; display: inline-block; } +#g-progress #g-status { padding: 0.5em 0; } + +/* forms.css - Short forms ~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-short-form legend, .g-short-form label { display: none; } +.g-short-form fieldset { border: none; padding: 0; } +.g-short-form li { float: left; margin: 0 !important; padding: .4em 0; } +.g-short-form .textbox, .g-short-form input[type=text] { color: #666; padding: .3em .6em; width: 100%; } +.g-short-form .textbox.g-error { border: 1px solid #f00; color: #f00; padding-left: 24px; } +.g-short-form .g-cancel { display: block; margin: .3em .8em; } +#g-sidebar .g-short-form li { padding-left: 0; padding-right: 0; } + +#g-quick-search-form input[type="submit"] { filter: none; margin-top: 0; } + +/* forms.css - Reauthentificate ~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-login fieldset { border: none; } +#g-login legend { display: none; } +#g-login label { display: block; } + +#g-column-centerfull #g-error h2 { padding-top: 10px; } +#g-column-centerfull #g-login { width: 270px; margin-top: 10px; padding-top: 10px; } +#g-column-centerfull #g-login input[type='text'], #g-column-centerfull #g-login input[type='password'] { width: 100%; } +#g-column-centerfull #g-login #g-password-reset { position: relative; bottom: 20px; right: 14px; } + +/* forms.css - User Profile ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-user-profile { display: block; } +#g-user-profile h1 { margin: 0 0 1em 0; } +#g-user-profile .g-avatar { float: left; margin: .4em; } +#g-user-profile .g-block { position: static; min-height: 5em; clear: none; margin-left: 4em; } +#g-user-profile .g-block ul { padding: 0.6em 0; } +#g-user-profile th { text-align: left; } + +#g-user-profile-operations { height: 1.5em; } +#g-user-profile #g-rest-key .g-button { margin-left: 1em; } + +/* forms.css - Uploadify ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-add-album-form, +#g-edit-album-form, #g-edit-photo-form { width: 500px; } + +#g-add-photos-form { width: 476px; } +#g-add-photos-canvas { margin-top: 1.1em; } +#g-add-photos-status #g-action-status li { padding-top: 0.3em; padding-bottom: 0.3em; padding-left: 2em; } +.uploadifyQueueItem { padding-top: 6px; padding-bottom: 4ps; } + +/* forms.css - Permission ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-permissions { width: 480px; } +#g-permissions .g-breadcrumbs { position: static; } +#g-permissions th { padding-top: 4px; padding-right: 0.5em; } + +#g-confirm-delete { width: 340px; margin-top: 1em; } diff --git a/themes/greydragon/css/framepacks/android/css/frame.css b/themes/greydragon/css/framepacks/android/css/frame.css new file mode 100644 index 0000000..548b152 --- /dev/null +++ b/themes/greydragon/css/framepacks/android/css/frame.css @@ -0,0 +1,32 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - Android + */ + +#g-album-grid>li { padding: 10px 6px 10px 10px; } +.g-thumbslide { background: transparent; } + +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { border-color: #ADAEAD; } +.g-thumbcrop { border-width: 1px; border-style: solid; border-color: #ADAEAD; } + +.g-thumbtype-sqr { background: url('../images/thumb-sqr.png') no-repeat top left; } +.g-thumbtype-flm { background: url('../images/thumb-flm.png') no-repeat top left; } +.g-thumbtype-dgt { background: url('../images/thumb-dgt.png') no-repeat top left; } +.g-thumbtype-wd { background: url('../images/thumb-wd.png') no-repeat top left; } + +.g-extended .g-thumbtype-sqr { background-image: url('../images/thumb-sqr-ext.png'); } +.g-extended .g-thumbtype-flm { background-image: url('../images/thumb-flm-ext.png'); } +.g-extended .g-thumbtype-dgt { background-image: url('../images/thumb-dgt-ext.png'); } +.g-extended .g-thumbtype-wd { background-image: url('../images/thumb-wd-ext.png'); } + +.g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-e.png'); } +.g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-e.png'); } +.g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-e.png'); } +.g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-e.png'); } + +.g-extended .g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-eext.png'); } +.g-extended .g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-eext.png'); } +.g-extended .g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-eext.png'); } +.g-extended .g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-eext.png'); } diff --git a/themes/greydragon/css/framepacks/android/images/thumb-dgt-e.png b/themes/greydragon/css/framepacks/android/images/thumb-dgt-e.png new file mode 100644 index 0000000..39a793a Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-dgt-e.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-dgt-eext.png b/themes/greydragon/css/framepacks/android/images/thumb-dgt-eext.png new file mode 100644 index 0000000..df4aa49 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-dgt-eext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-dgt-ext.png b/themes/greydragon/css/framepacks/android/images/thumb-dgt-ext.png new file mode 100644 index 0000000..297a456 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-dgt-ext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-dgt.png b/themes/greydragon/css/framepacks/android/images/thumb-dgt.png new file mode 100644 index 0000000..b9cc40e Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-dgt.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-flm-e.png b/themes/greydragon/css/framepacks/android/images/thumb-flm-e.png new file mode 100644 index 0000000..b97c8c1 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-flm-e.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-flm-eext.png b/themes/greydragon/css/framepacks/android/images/thumb-flm-eext.png new file mode 100644 index 0000000..45c38dc Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-flm-eext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-flm-ext.png b/themes/greydragon/css/framepacks/android/images/thumb-flm-ext.png new file mode 100644 index 0000000..7361f20 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-flm-ext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-flm.png b/themes/greydragon/css/framepacks/android/images/thumb-flm.png new file mode 100644 index 0000000..a407409 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-flm.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-sqr-e.png b/themes/greydragon/css/framepacks/android/images/thumb-sqr-e.png new file mode 100644 index 0000000..33bcd99 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-sqr-e.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-sqr-eext.png b/themes/greydragon/css/framepacks/android/images/thumb-sqr-eext.png new file mode 100644 index 0000000..c924637 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-sqr-eext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-sqr-ext.png b/themes/greydragon/css/framepacks/android/images/thumb-sqr-ext.png new file mode 100644 index 0000000..3fdc3a3 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-sqr-ext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-sqr.png b/themes/greydragon/css/framepacks/android/images/thumb-sqr.png new file mode 100644 index 0000000..3a038b0 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-sqr.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-wd-e.png b/themes/greydragon/css/framepacks/android/images/thumb-wd-e.png new file mode 100644 index 0000000..3e78ce8 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-wd-e.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-wd-eext.png b/themes/greydragon/css/framepacks/android/images/thumb-wd-eext.png new file mode 100644 index 0000000..c51abf3 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-wd-eext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-wd-ext.png b/themes/greydragon/css/framepacks/android/images/thumb-wd-ext.png new file mode 100644 index 0000000..a5aa4b2 Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-wd-ext.png differ diff --git a/themes/greydragon/css/framepacks/android/images/thumb-wd.png b/themes/greydragon/css/framepacks/android/images/thumb-wd.png new file mode 100644 index 0000000..36a6a4f Binary files /dev/null and b/themes/greydragon/css/framepacks/android/images/thumb-wd.png differ diff --git a/themes/greydragon/css/framepacks/book/css/frame.css b/themes/greydragon/css/framepacks/book/css/frame.css new file mode 100644 index 0000000..73a0bbb --- /dev/null +++ b/themes/greydragon/css/framepacks/book/css/frame.css @@ -0,0 +1,64 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - Book + */ + +#g-album-grid>li { padding: 10px 10px 10px 14px; } +.g-thumbslide { background: transparent; } +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { background-color: #FFF; border-color: #ADAEAD; color: #111; margin-left: 15px; } +.g-thumbcrop { border: #ADAEAD 1px solid; } +.g-album .g-thumbslide .g-description { background-image: none; } +.g-album .g-thumbslide .g-description .g-title { padding-left: 0; } +.g-expanded.g-album .g-thumbslide .g-description .g-title { padding-left: 0; } + +.g-item .g-context-menu { left: 200px; } +.g-item .g-context-menu:hover { left: 20px; } +.g-extended .g-item .g-context-menu { left: 304px; } +.g-extended .g-item .g-context-menu:hover { left: 20px; } + +.g-album.g-thumbtype-sqr { background: url('../images/a-thumb-sqr.png') no-repeat top left; } +.g-album.g-thumbtype-flm { background: url('../images/a-thumb-flm.png') no-repeat top left; } +.g-album.g-thumbtype-dgt { background: url('../images/a-thumb-dgt.png') no-repeat top left; } +.g-album.g-thumbtype-wd { background: url('../images/a-thumb-wd.png') no-repeat top left; } + +.g-extended .g-album.g-thumbtype-sqr { background-image: url('../images/a-thumb-sqr-ext.png'); } +.g-extended .g-album.g-thumbtype-flm { background-image: url('../images/a-thumb-flm-ext.png'); } +.g-extended .g-album.g-thumbtype-dgt { background-image: url('../images/a-thumb-dgt-ext.png'); } +.g-extended .g-album.g-thumbtype-wd { background-image: url('../images/a-thumb-wd-ext.png'); } + +.g-album.g-thumbtype-sqr.g-expanded { background-image: url('../images/a-thumb-sqr-e.png'); } +.g-album.g-thumbtype-flm.g-expanded { background-image: url('../images/a-thumb-flm-e.png'); } +.g-album.g-thumbtype-dgt.g-expanded { background-image: url('../images/a-thumb-dgt-e.png'); } +.g-album.g-thumbtype-wd.g-expanded { background-image: url('../images/a-thumb-wd-e.png'); } + +.g-extended .g-album.g-thumbtype-sqr.g-expanded { background-image: url('../images/a-thumb-sqr-eext.png'); } +.g-extended .g-album.g-thumbtype-flm.g-expanded { background-image: url('../images/a-thumb-flm-eext.png'); } +.g-extended .g-album.g-thumbtype-dgt.g-expanded { background-image: url('../images/a-thumb-dgt-eext.png'); } +.g-extended .g-album.g-thumbtype-wd.g-expanded { background-image: url('../images/a-thumb-wd-eext.png'); } + +.g-photo.g-thumbtype-sqr { background: url('../images/thumb-sqr.png') no-repeat top left; } +.g-photo.g-thumbtype-flm { background: url('../images/thumb-flm.png') no-repeat top left; } +.g-photo.g-thumbtype-dgt { background: url('../images/thumb-dgt.png') no-repeat top left; } +.g-photo.g-thumbtype-wd { background: url('../images/thumb-wd.png') no-repeat top left; } + +.g-extended .g-photo.g-thumbtype-sqr { background-image: url('../images/thumb-sqr-ext.png'); } +.g-extended .g-photo.g-thumbtype-flm { background-image: url('../images/thumb-flm-ext.png'); } +.g-extended .g-photo.g-thumbtype-dgt { background-image: url('../images/thumb-dgt-ext.png'); } +.g-extended .g-photo.g-thumbtype-wd { background-image: url('../images/thumb-wd-ext.png'); } + +.g-photo.g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-e.png'); } +.g-photo.g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-e.png'); } +.g-photo.g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-e.png'); } +.g-photo.g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-e.png'); } + +.g-extended .g-photo.g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-eext.png'); } +.g-extended .g-photo.g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-eext.png'); } +.g-extended .g-photo.g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-eext.png'); } +.g-extended .g-photo.g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-eext.png'); } + +.g-movie.g-thumbtype-sqr { background: url('../images/thumb-sqr.png') no-repeat top left; } +.g-movie.g-thumbtype-flm { background: url('../images/thumb-flm.png') no-repeat top left; } +.g-movie.g-thumbtype-dgt { background: url('../images/thumb-dgt.png') no-repeat top left; } +.g-movie.g-thumbtype-wd { background: url('../images/thumb-wd.png') no-repeat top left; } \ No newline at end of file diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-e.png b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-e.png new file mode 100644 index 0000000..486f50b Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-eext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-eext.png new file mode 100644 index 0000000..64bd017 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-ext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-ext.png new file mode 100644 index 0000000..bb5089d Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-dgt.png b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt.png new file mode 100644 index 0000000..458cc15 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-dgt.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-flm-e.png b/themes/greydragon/css/framepacks/book/images/a-thumb-flm-e.png new file mode 100644 index 0000000..7be7ffe Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-flm-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-flm-eext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-flm-eext.png new file mode 100644 index 0000000..0ec4d3c Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-flm-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-flm-ext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-flm-ext.png new file mode 100644 index 0000000..a06d963 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-flm-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-flm.png b/themes/greydragon/css/framepacks/book/images/a-thumb-flm.png new file mode 100644 index 0000000..b1ec950 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-flm.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-e.png b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-e.png new file mode 100644 index 0000000..bf972a3 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-eext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-eext.png new file mode 100644 index 0000000..c13dd02 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-ext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-ext.png new file mode 100644 index 0000000..f197016 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-sqr.png b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr.png new file mode 100644 index 0000000..6eb7f26 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-sqr.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-wd-e.png b/themes/greydragon/css/framepacks/book/images/a-thumb-wd-e.png new file mode 100644 index 0000000..2699b36 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-wd-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-wd-eext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-wd-eext.png new file mode 100644 index 0000000..ff78ba5 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-wd-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-wd-ext.png b/themes/greydragon/css/framepacks/book/images/a-thumb-wd-ext.png new file mode 100644 index 0000000..7501df7 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-wd-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/a-thumb-wd.png b/themes/greydragon/css/framepacks/book/images/a-thumb-wd.png new file mode 100644 index 0000000..df1164b Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/a-thumb-wd.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-dgt-e.png b/themes/greydragon/css/framepacks/book/images/thumb-dgt-e.png new file mode 100644 index 0000000..2b9dc15 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-dgt-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-dgt-eext.png b/themes/greydragon/css/framepacks/book/images/thumb-dgt-eext.png new file mode 100644 index 0000000..9d5cbe1 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-dgt-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-dgt-ext.png b/themes/greydragon/css/framepacks/book/images/thumb-dgt-ext.png new file mode 100644 index 0000000..a1afba6 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-dgt-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-dgt.png b/themes/greydragon/css/framepacks/book/images/thumb-dgt.png new file mode 100644 index 0000000..020ed3b Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-dgt.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-flm-e.png b/themes/greydragon/css/framepacks/book/images/thumb-flm-e.png new file mode 100644 index 0000000..89724e8 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-flm-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-flm-eext.png b/themes/greydragon/css/framepacks/book/images/thumb-flm-eext.png new file mode 100644 index 0000000..1c84d91 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-flm-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-flm-ext.png b/themes/greydragon/css/framepacks/book/images/thumb-flm-ext.png new file mode 100644 index 0000000..04bc46a Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-flm-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-flm.png b/themes/greydragon/css/framepacks/book/images/thumb-flm.png new file mode 100644 index 0000000..1709bbe Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-flm.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-sqr-e.png b/themes/greydragon/css/framepacks/book/images/thumb-sqr-e.png new file mode 100644 index 0000000..ede2e1b Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-sqr-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-sqr-eext.png b/themes/greydragon/css/framepacks/book/images/thumb-sqr-eext.png new file mode 100644 index 0000000..48416c6 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-sqr-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-sqr-ext.png b/themes/greydragon/css/framepacks/book/images/thumb-sqr-ext.png new file mode 100644 index 0000000..d7e2e53 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-sqr-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-sqr.png b/themes/greydragon/css/framepacks/book/images/thumb-sqr.png new file mode 100644 index 0000000..95e5890 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-sqr.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-wd-e.png b/themes/greydragon/css/framepacks/book/images/thumb-wd-e.png new file mode 100644 index 0000000..eae6d66 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-wd-e.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-wd-eext.png b/themes/greydragon/css/framepacks/book/images/thumb-wd-eext.png new file mode 100644 index 0000000..ee89913 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-wd-eext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-wd-ext.png b/themes/greydragon/css/framepacks/book/images/thumb-wd-ext.png new file mode 100644 index 0000000..d303a1d Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-wd-ext.png differ diff --git a/themes/greydragon/css/framepacks/book/images/thumb-wd.png b/themes/greydragon/css/framepacks/book/images/thumb-wd.png new file mode 100644 index 0000000..6d344c5 Binary files /dev/null and b/themes/greydragon/css/framepacks/book/images/thumb-wd.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/css/frame.css b/themes/greydragon/css/framepacks/darkglass/css/frame.css new file mode 100644 index 0000000..30d5c9a --- /dev/null +++ b/themes/greydragon/css/framepacks/darkglass/css/frame.css @@ -0,0 +1,32 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - DarkGlass + */ + +#g-album-grid>li { padding: 10px 6px 10px 10px; } +.g-thumbslide { background: transparent; } + +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { border-color: #ADAEAD; } +.g-thumbcrop { border-width: 1px; border-style: solid; border-color: #7F7F7F; } + +.g-thumbtype-sqr { background: url('../images/thumb-sqr.png') no-repeat top left; } +.g-thumbtype-flm { background: url('../images/thumb-flm.png') no-repeat top left; } +.g-thumbtype-dgt { background: url('../images/thumb-dgt.png') no-repeat top left; } +.g-thumbtype-wd { background: url('../images/thumb-wd.png') no-repeat top left; } + +.g-extended .g-thumbtype-sqr { background-image: url('../images/thumb-sqr-ext.png'); } +.g-extended .g-thumbtype-flm { background-image: url('../images/thumb-flm-ext.png'); } +.g-extended .g-thumbtype-dgt { background-image: url('../images/thumb-dgt-ext.png'); } +.g-extended .g-thumbtype-wd { background-image: url('../images/thumb-wd-ext.png'); } + +.g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-e.png'); } +.g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-e.png'); } +.g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-e.png'); } +.g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-e.png'); } + +.g-extended .g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-eext.png'); } +.g-extended .g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-eext.png'); } +.g-extended .g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-eext.png'); } +.g-extended .g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-eext.png'); } diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-e.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-e.png new file mode 100644 index 0000000..c96c40c Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-e.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-eext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-eext.png new file mode 100644 index 0000000..0f87149 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-eext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-ext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-ext.png new file mode 100644 index 0000000..ed89c39 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt-ext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt.png new file mode 100644 index 0000000..ace3e98 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-dgt.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-e.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-e.png new file mode 100644 index 0000000..f8e8841 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-e.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-eext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-eext.png new file mode 100644 index 0000000..4e735c8 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-eext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-ext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-ext.png new file mode 100644 index 0000000..9c0bff5 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm-ext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-flm.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm.png new file mode 100644 index 0000000..460ee7c Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-flm.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-e.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-e.png new file mode 100644 index 0000000..eb28835 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-e.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-eext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-eext.png new file mode 100644 index 0000000..3af47df Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-eext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-ext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-ext.png new file mode 100644 index 0000000..d2e268e Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr-ext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr.png new file mode 100644 index 0000000..da45039 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-sqr.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-e.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-e.png new file mode 100644 index 0000000..62f5f1e Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-e.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-eext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-eext.png new file mode 100644 index 0000000..bf8b2e7 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-eext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-ext.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-ext.png new file mode 100644 index 0000000..bc994bc Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd-ext.png differ diff --git a/themes/greydragon/css/framepacks/darkglass/images/thumb-wd.png b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd.png new file mode 100644 index 0000000..62e7881 Binary files /dev/null and b/themes/greydragon/css/framepacks/darkglass/images/thumb-wd.png differ diff --git a/themes/greydragon/css/framepacks/greydragon/css/frame.css b/themes/greydragon/css/framepacks/greydragon/css/frame.css new file mode 100644 index 0000000..bee1a1b --- /dev/null +++ b/themes/greydragon/css/framepacks/greydragon/css/frame.css @@ -0,0 +1,43 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - GreyDragon + */ + +/* layous.css - Extended Layout - 4 columns ~~~~~~~~~~*/ + +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-footer, +body.g-extended.g-column-4 #g-main, +body.g-extended.g-column-4 #g-main-in, +body.g-extended.g-column-4 #g-column-top, +body.g-extended.g-column-4 #g-column-bottom { min-width: 1302px; } + +/* layous.css - Extended Layout - 4 columns+sidebar ~~*/ +body.g-extended.g-column-4.g-sidebar-right #g-header, +body.g-extended.g-column-4.g-sidebar-right #g-footer, +body.g-extended.g-column-4.g-sidebar-right #g-main, +body.g-extended.g-column-4.g-sidebar-right #g-main-in, +body.g-extended.g-column-4.g-sidebar-left #g-header, +body.g-extended.g-column-4.g-sidebar-left #g-footer, +body.g-extended.g-column-4.g-sidebar-left #g-main, +body.g-extended.g-column-4.g-sidebar-left #g-main-in { min-width: 1546px; } + +.g-thumbslide { margin-right: 6px; } +.g-thumbslide .g-description { margin: 1px 0 0 2px; } +.g-thumbslide .g-metadata { margin-bottom: 4px; margin-left: 2px; } + +.g-extended #g-album-grid .g-item { min-width: 320px; } + +.g-item .g-context-menu { left: 190px; top: 12px; } +.g-item .g-context-menu:hover { left: 6px; top: 6px; } + +.g-extended .g-item .g-context-menu { left: 290px; top: 12px; } +.g-extended .g-item .g-context-menu:hover { left: 8px; top: 8px; } + +.g-thumbslide, .g-thumbslide-ext, .g-thumbcrop { border-width: 1px; border-style: solid; background-color: inherit; } +.g-album .g-thumbslide, .g-album .g-thumbslide-ext { border-right-width: 4px; border-right-style: double; border-bottom-width: 4px; border-bottom-style: double; } +.g-photo .g-thumbslide, .g-photo .g-thumbslide-ext { margin-bottom: 4px; } /* Need to compensate for double border in album's thumbs */ +.g-movie .g-thumbslide, .g-movie .g-thumbslide-ext { margin-bottom: 4px; } /* Need to compensate for double border in album's thumbs */ + diff --git a/themes/greydragon/css/framepacks/iphone/css/frame.css b/themes/greydragon/css/framepacks/iphone/css/frame.css new file mode 100644 index 0000000..0d24188 --- /dev/null +++ b/themes/greydragon/css/framepacks/iphone/css/frame.css @@ -0,0 +1,31 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - iPhone + */ + +#g-album-grid>li { padding: 10px 6px 10px 10px; } +.g-thumbslide { background: transparent; } +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { border-color: #7D7E7D; } +.g-thumbcrop { border-width: 1px; border-style: solid; border-color: #7D7E7D; } + +.g-thumbtype-sqr { background: url('../images/thumb-sqr.png') no-repeat top left; } +.g-thumbtype-flm { background: url('../images/thumb-flm.png') no-repeat top left; } +.g-thumbtype-dgt { background: url('../images/thumb-dgt.png') no-repeat top left; } +.g-thumbtype-wd { background: url('../images/thumb-wd.png') no-repeat top left; } + +.g-extended .g-thumbtype-sqr { background-image: url('../images/thumb-sqr-ext.png'); } +.g-extended .g-thumbtype-flm { background-image: url('../images/thumb-flm-ext.png'); } +.g-extended .g-thumbtype-dgt { background-image: url('../images/thumb-dgt-ext.png'); } +.g-extended .g-thumbtype-wd { background-image: url('../images/thumb-wd-ext.png'); } + +.g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-e.png'); } +.g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-e.png'); } +.g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-e.png'); } +.g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-e.png'); } + +.g-extended .g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-eext.png'); } +.g-extended .g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-eext.png'); } +.g-extended .g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-eext.png'); } +.g-extended .g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-eext.png'); } diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-e.png b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-e.png new file mode 100644 index 0000000..daa2002 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-e.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-eext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-eext.png new file mode 100644 index 0000000..9362e8b Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-ext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-ext.png new file mode 100644 index 0000000..e477616 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-dgt.png b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt.png new file mode 100644 index 0000000..c514620 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-dgt.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-flm-e.png b/themes/greydragon/css/framepacks/iphone/images/thumb-flm-e.png new file mode 100644 index 0000000..3d3ddaa Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-flm-e.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-flm-eext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-flm-eext.png new file mode 100644 index 0000000..aedd558 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-flm-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-flm-ext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-flm-ext.png new file mode 100644 index 0000000..62e62a2 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-flm-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-flm.png b/themes/greydragon/css/framepacks/iphone/images/thumb-flm.png new file mode 100644 index 0000000..0094def Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-flm.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-e.png b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-e.png new file mode 100644 index 0000000..1dc590c Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-e.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-eext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-eext.png new file mode 100644 index 0000000..9f6024b Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-ext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-ext.png new file mode 100644 index 0000000..4233c84 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-sqr.png b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr.png new file mode 100644 index 0000000..c9e5714 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-sqr.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-wd-e.png b/themes/greydragon/css/framepacks/iphone/images/thumb-wd-e.png new file mode 100644 index 0000000..15a0c4c Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-wd-e.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-wd-eext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-wd-eext.png new file mode 100644 index 0000000..0867baa Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-wd-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-wd-ext.png b/themes/greydragon/css/framepacks/iphone/images/thumb-wd-ext.png new file mode 100644 index 0000000..213d494 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-wd-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphone/images/thumb-wd.png b/themes/greydragon/css/framepacks/iphone/images/thumb-wd.png new file mode 100644 index 0000000..a25d477 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphone/images/thumb-wd.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/css/frame.css b/themes/greydragon/css/framepacks/iphoto/css/frame.css new file mode 100644 index 0000000..0dba3e9 --- /dev/null +++ b/themes/greydragon/css/framepacks/iphoto/css/frame.css @@ -0,0 +1,31 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - iPhoto + */ + +#g-album-grid>li { padding: 10px 6px 10px 10px; } +.g-thumbslide { background: transparent; } +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { background-color: #FFF; border-color: #ADAEAD; color: #111; } +.g-thumbcrop { border: #ADAEAD 1px solid; } + +.g-thumbtype-sqr { background: url('../images/thumb-sqr.png') no-repeat top left; } +.g-thumbtype-flm { background: url('../images/thumb-flm.png') no-repeat top left; } +.g-thumbtype-dgt { background: url('../images/thumb-dgt.png') no-repeat top left; } +.g-thumbtype-wd { background: url('../images/thumb-wd.png') no-repeat top left; } + +.g-extended .g-thumbtype-sqr { background-image: url('../images/thumb-sqr-ext.png'); } +.g-extended .g-thumbtype-flm { background-image: url('../images/thumb-flm-ext.png'); } +.g-extended .g-thumbtype-dgt { background-image: url('../images/thumb-dgt-ext.png'); } +.g-extended .g-thumbtype-wd { background-image: url('../images/thumb-wd-ext.png'); } + +.g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-e.png'); } +.g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-e.png'); } +.g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-e.png'); } +.g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-e.png'); } + +.g-extended .g-thumbtype-sqr.g-expanded { background-image: url('../images/thumb-sqr-eext.png'); } +.g-extended .g-thumbtype-flm.g-expanded { background-image: url('../images/thumb-flm-eext.png'); } +.g-extended .g-thumbtype-dgt.g-expanded { background-image: url('../images/thumb-dgt-eext.png'); } +.g-extended .g-thumbtype-wd.g-expanded { background-image: url('../images/thumb-wd-eext.png'); } diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-e.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-e.png new file mode 100644 index 0000000..616d1d5 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-e.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-eext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-eext.png new file mode 100644 index 0000000..828528a Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-ext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-ext.png new file mode 100644 index 0000000..c090406 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt.png new file mode 100644 index 0000000..5190758 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-dgt.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-e.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-e.png new file mode 100644 index 0000000..251216b Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-e.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-eext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-eext.png new file mode 100644 index 0000000..c60ba42 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-ext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-ext.png new file mode 100644 index 0000000..b1f8675 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-flm.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm.png new file mode 100644 index 0000000..44c67c3 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-flm.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-e.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-e.png new file mode 100644 index 0000000..02ddcd2 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-e.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-eext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-eext.png new file mode 100644 index 0000000..a6f6946 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-ext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-ext.png new file mode 100644 index 0000000..de2fae4 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr.png new file mode 100644 index 0000000..57c192f Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-sqr.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-e.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-e.png new file mode 100644 index 0000000..492432e Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-e.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-eext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-eext.png new file mode 100644 index 0000000..901a13a Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-eext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-ext.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-ext.png new file mode 100644 index 0000000..0efa766 Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd-ext.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/images/thumb-wd.png b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd.png new file mode 100644 index 0000000..f6bb30f Binary files /dev/null and b/themes/greydragon/css/framepacks/iphoto/images/thumb-wd.png differ diff --git a/themes/greydragon/css/framepacks/iphoto/views/frame.html.php b/themes/greydragon/css/framepacks/iphoto/views/frame.html.php new file mode 100644 index 0000000..87ed99b --- /dev/null +++ b/themes/greydragon/css/framepacks/iphoto/views/frame.html.php @@ -0,0 +1,5 @@ +
      + +
      +
      +
      diff --git a/themes/greydragon/css/framepacks/panel/css/frame.css b/themes/greydragon/css/framepacks/panel/css/frame.css new file mode 100644 index 0000000..07a3048 --- /dev/null +++ b/themes/greydragon/css/framepacks/panel/css/frame.css @@ -0,0 +1,37 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - Panel + */ + +/* layous.css - Extended Layout - 4 columns ~~~~~~~~~~*/ +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-footer, +body.g-extended.g-column-4 #g-main, +body.g-extended.g-column-4 #g-main-in, +body.g-extended.g-column-4 #g-column-top, +body.g-extended.g-column-4 #g-column-bottom { min-width: 1302px; } + +/* layous.css - Extended Layout - 4 columns+sidebar ~~*/ +body.g-extended.g-column-4.g-sidebar-right #g-header, +body.g-extended.g-column-4.g-sidebar-right #g-footer, +body.g-extended.g-column-4.g-sidebar-right #g-main, +body.g-extended.g-column-4.g-sidebar-right #g-main-in, +body.g-extended.g-column-4.g-sidebar-left #g-header, +body.g-extended.g-column-4.g-sidebar-left #g-footer, +body.g-extended.g-column-4.g-sidebar-left #g-main, +body.g-extended.g-column-4.g-sidebar-left #g-main-in { min-width: 1546px; } + +.g-album .g-thumbslide, .g-thumbslide { border-width: 1px; border-style: solid; } +.g-thumbslide .g-description { margin: 0 0 0 1px; } +.g-thumbslide .g-metadata { margin-bottom: 3px; margin-left: 1px; } + +.g-extended #g-album-grid .g-item { min-width: 320px; } + +.g-item .g-context-menu { left: 188px; top: 12px; } +.g-item .g-context-menu:hover { left: 6px; top: 6px; } +.g-extended .g-item .g-context-menu { left: 288px; top: 12px; } +.g-extended .g-item .g-context-menu:hover { left: 6px; top: 6px; } + +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { border-color: #ADAEAD; } diff --git a/themes/greydragon/css/framepacks/roundcorners/css/frame.css b/themes/greydragon/css/framepacks/roundcorners/css/frame.css new file mode 100644 index 0000000..ffe7c13 --- /dev/null +++ b/themes/greydragon/css/framepacks/roundcorners/css/frame.css @@ -0,0 +1,44 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - Round Corners + */ + +/* layous.css - Extended Layout - 4 columns ~~~~~~~~~~*/ +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-footer, +body.g-extended.g-column-4 #g-main, +body.g-extended.g-column-4 #g-main-in, +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-column-top, +body.g-extended.g-column-4 #g-column-bottom { min-width: 1278px; } + +/* layous.css - Extended Layout - 4 columns+sidebar ~~*/ +body.g-extended.g-column-4.g-sidebar-right #g-header, +body.g-extended.g-column-4.g-sidebar-right #g-footer, +body.g-extended.g-column-4.g-sidebar-right #g-main, +body.g-extended.g-column-4.g-sidebar-right #g-main-in, +body.g-extended.g-column-4.g-sidebar-left #g-header, +body.g-extended.g-column-4.g-sidebar-left #g-footer, +body.g-extended.g-column-4.g-sidebar-left #g-main, +body.g-extended.g-column-4.g-sidebar-left #g-main-in { min-width: 1520px; } + +.g-thumbslide .g-description { margin: 0 0 0 0px; border-top-left-radius: 10px; border-top-right-radius: 10px; } +.g-thumbslide .g-description.g-overlay-bottom { border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; bottom: -3px; } +.g-thumbslide .g-metadata { margin: 0 0 1px 0px; } + +.g-item .g-context-menu { left: 188px; top: 12px; } +.g-item .g-context-menu:hover { left: 6px; top: 6px; } +.g-extended .g-item .g-context-menu { left: 288px; top: 12px; } +.g-extended .g-item .g-context-menu:hover { left: 6px; top: 6px; } + +.g-default .g-thumbslide .g-description, +.g-expanded .g-thumbslide .g-description { border-color: #ADAEAD; color: #3c5580; } +.g-thumbslide .g-metadata { border-color: #ADAEAD; color: #3c5580; border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } +.g-thumbcrop { border-radius: 10px; box-shadow: 3px 3px 8px #333; } +.g-thumbslide { background-image: none; } +.g-thumbslide:hover .g-thumbcrop { box-shadow: 4px 4px 8px #333; } + +#g-photo .g-resize { border: none; background: transparent; } +#g-photo .g-resize a { border-radius: 10px; box-shadow: 3px 3px 8px #333; } \ No newline at end of file diff --git a/themes/greydragon/css/framepacks/simple/css/frame.css b/themes/greydragon/css/framepacks/simple/css/frame.css new file mode 100644 index 0000000..3a0539c --- /dev/null +++ b/themes/greydragon/css/framepacks/simple/css/frame.css @@ -0,0 +1,39 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - Simple + */ + +/* layous.css - Extended Layout - 4 columns ~~~~~~~~~~*/ +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-footer, +body.g-extended.g-column-4 #g-main, +body.g-extended.g-column-4 #g-main-in, +body.g-extended.g-column-4 #g-column-top, +body.g-extended.g-column-4 #g-column-bottom { min-width: 1278px; } + +/* layous.css - Extended Layout - 4 columns+sidebar ~~*/ +body.g-extended.g-column-4.g-sidebar-right #g-header, +body.g-extended.g-column-4.g-sidebar-right #g-footer, +body.g-extended.g-column-4.g-sidebar-right #g-main, +body.g-extended.g-column-4.g-sidebar-right #g-main-in, +body.g-extended.g-column-4.g-sidebar-right #g-column-top, +body.g-extended.g-column-4.g-sidebar-right #g-column-bottom, +body.g-extended.g-column-4.g-sidebar-left #g-header, +body.g-extended.g-column-4.g-sidebar-left #g-footer, +body.g-extended.g-column-4.g-sidebar-left #g-main, +body.g-extended.g-column-4.g-sidebar-left #g-main-in, +body.g-extended.g-column-4.g-sidebar-left #g-column-top, +body.g-extended.g-column-4.g-sidebar-left #g-column-bottom { min-width: 1520px; } + +.g-thumbslide .g-description { margin: 0 0 0 1px; } +.g-thumbslide .g-metadata { margin: 0 0 1px 1px; } + +.g-item .g-context-menu { left: 188px; top: 12px; } +.g-item .g-context-menu:hover { left: 6px; top: 6px; } +.g-extended .g-item .g-context-menu { left: 288px; top: 12px; } +.g-extended .g-item .g-context-menu:hover { left: 6px; top: 6px; } + +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description, .g-thumbslide .g-metadata { border-color: #ADAEAD; } +.g-thumbcrop { border-width: 1px; border-style: solid; border-color: #ADAEAD; } diff --git a/themes/greydragon/css/framepacks/wall/css/frame.css b/themes/greydragon/css/framepacks/wall/css/frame.css new file mode 100644 index 0000000..1a74654 --- /dev/null +++ b/themes/greydragon/css/framepacks/wall/css/frame.css @@ -0,0 +1,48 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2010 Serguei Dosyukov + * + * CSS rules - Frames - Simple + */ + +/* layous.css - Extended Layout - 4 columns ~~~~~~~~~~*/ +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-footer, +body.g-extended.g-column-4 #g-main, +body.g-extended.g-column-4 #g-main-in, +body.g-extended.g-column-4 #g-column-top, +body.g-extended.g-column-4 #g-column-bottom { min-width: 1224px; } + +/* layous.css - Extended Layout - 4 columns+sidebar ~~*/ +body.g-extended.g-column-4.g-sidebar-right #g-header, +body.g-extended.g-column-4.g-sidebar-right #g-footer, +body.g-extended.g-column-4.g-sidebar-right #g-main, +body.g-extended.g-column-4.g-sidebar-right #g-main-in, +body.g-extended.g-column-4.g-sidebar-left #g-header, +body.g-extended.g-column-4.g-sidebar-left #g-footer, +body.g-extended.g-column-4.g-sidebar-left #g-main, +body.g-extended.g-column-4.g-sidebar-left #g-main-in { min-width: 1470px; } + + +.g-thumbslide { padding-top: 0; padding-left: 0; width: 200px; min-height: auto; } +.g-extended .g-thumbslide { width: 300px; } + +.g-thumbtype-flm .g-thumbslide, .g-extended .g-thumbtype-flm .g-thumbslide, +.g-thumbtype-dgt .g-thumbslide, .g-extended .g-thumbtype-dgt .g-thumbslide, +.g-thumbtype-wd .g-thumbslide, .g-extended .g-thumbtype-wd .g-thumbslide, +.g-thumbtype-sqr .g-thumbslide, .g-extended .g-thumbtype-sqr .g-thumbslide { height: auto; } + +.g-item .g-context-menu { left: 184px; top: 2px; } +.g-extended .g-item .g-context-menu { left: 282px; top: 4px; } +.g-item .g-context-menu:hover, .g-extended .g-item .g-context-menu:hover { left: 0px; top: 0; } + +#g-album-grid .g-item { margin: 0; min-width: 200px; } +.g-extended #g-album-grid .g-item { min-width: 300px; } + +.g-album-grid-container .g-wall > li { width: auto; } + +.g-thumbslide .g-description { margin: 0 0 0 0; left: 0; top: 0; min-height: 2.4em; } +.g-thumbslide .g-metadata { margin: 0 0 0 0; left: 0; bottom: 0; min-height: 2.4em; } + +.g-default .g-thumbslide .g-description, .g-expanded .g-thumbslide .g-description { border-color: #ADAEAD; border-top: none; } +.g-thumbcrop { border: none; } diff --git a/themes/greydragon/css/ipad.css b/themes/greydragon/css/ipad.css new file mode 100644 index 0000000..801356f --- /dev/null +++ b/themes/greydragon/css/ipad.css @@ -0,0 +1,20 @@ +/* Start Apple Styles */ +/* These styles support Apple products display of CSS. NOTES (orientation:landscape), (orientation:portrait) can also be used to be more specific */ +/* iPad [portrait + landscape] */ +@media only screen and (min-device-width:768px) and (max-device-width:1024px) +{ + #cboxOverlay { height: 200%; width: 200%; } + .g-bar#g-site-menu { width: 200%; } +} + +/* iPhone 4 [portrait + landscape] */ +@media only screen and (resolution:326dpi) { } + +/* screen and (resolution: 163dpi) */ +/* iPhone 3 [portrait + landscape] */ +@media only screen and (max-device-width:480px) { } + +/* iPod Touch and older [portrait + landscape] */ +@media only screen and (resolution:326dpi) { } + +/* End Apple Styles */ diff --git a/themes/greydragon/css/layout.css b/themes/greydragon/css/layout.css new file mode 100644 index 0000000..97fe65c --- /dev/null +++ b/themes/greydragon/css/layout.css @@ -0,0 +1,77 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules related to general layout + * Defined as 70em wide + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* layout.css - Common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +html { overflow: auto; overflow: -moz-scrollbars-vertical; overflow-y: scroll; } +* { margin: 0px; } +body { min-width: 1020px; padding: 0; } +.g-hideitem { display: none; } + +/* layout.css - Header ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-header { position: relative; min-width: 1020px; z-index: 5; } + +/* layout.css - Main ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-main { min-width: 1020px; height: auto; bottom: auto; } +#g-main-in { min-width: 1020px; height: 100%; aoverflow: auto; bottom: auto; } + +#g-column-left { float: left; width: 230px; min-height: 32em; overflow: hidden; height: 100%; } +#g-column-right { float: right; width: 230px; min-height: 32em; overflow: hidden; height: 100%; } +#g-column-center { margin: 0 17em 0 17em; min-height: 32em; overflow: hidden; height: 100%; } +#g-column-centerleft { min-height: 32em; overflow: hidden; height: 100%; } +#g-column-centerright { min-height: 32em; overflow: hidden; height: 100%; } +#g-column-centerfull { position: relative; min-height: 31em; overflow: hidden; height: 100%; } +#g-column-top { display: block; overflow: hidden; } +#g-column-bottom { display: block; overflow: hidden; } + +/* layout.css - Footer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-footer { position: relative; height: auto; min-width: 1020px; min-height: 2.8em; clear: both; display: block; overflow: auto; } +#g-footer-leftside { float: left; display: inline; } +#g-footer-rightside { float: right; display: inline; } + +/* layout.css - Extended Layout ~~~~~~~~~~~~~~~~~~~~~~*/ + +/* layous.css - Extended Layout - 3 columns+sidebar ~~*/ +body.g-extended.g-column-3.g-sidebar-right #g-header, +body.g-extended.g-column-3.g-sidebar-right #g-footer, +body.g-extended.g-column-3.g-sidebar-right #g-main, +body.g-extended.g-column-3.g-sidebar-right #g-main-in, +body.g-extended.g-column-3.g-sidebar-right #g-column-top, +body.g-extended.g-column-3.g-sidebar-right #g-column-bottom, +body.g-extended.g-column-3.g-sidebar-left #g-header, +body.g-extended.g-column-3.g-sidebar-left #g-footer, +body.g-extended.g-column-3.g-sidebar-left #g-main, +body.g-extended.g-column-3.g-sidebar-left #g-main-in, +body.g-extended.g-column-3.g-sidebar-left #g-column-top, +body.g-extended.g-column-3.g-sidebar-left #g-column-bottom { min-width: 1250px; } + +/* layous.css - Extended Layout - 4 columns ~~~~~~~~~~*/ +body.g-extended.g-column-4 #g-header, +body.g-extended.g-column-4 #g-footer, +body.g-extended.g-column-4 #g-main, +body.g-extended.g-column-4 #g-main-in, +body.g-extended.g-column-4 #g-column-top, +body.g-extended.g-column-4 #g-column-bottom { min-width: 1342px; } + +/* layous.css - Extended Layout - 4 columns+sidebar ~~*/ +body.g-extended.g-column-4.g-sidebar-right #g-header, +body.g-extended.g-column-4.g-sidebar-right #g-footer, +body.g-extended.g-column-4.g-sidebar-right #g-main, +body.g-extended.g-column-4.g-sidebar-right #g-main-in, +body.g-extended.g-column-4.g-sidebar-right #g-column-top, +body.g-extended.g-column-4.g-sidebar-right #g-column-bottom, +body.g-extended.g-column-4.g-sidebar-left #g-header, +body.g-extended.g-column-4.g-sidebar-left #g-footer, +body.g-extended.g-column-4.g-sidebar-left #g-main, +body.g-extended.g-column-4.g-sidebar-left #g-main-in, +body.g-extended.g-column-4.g-sidebar-left #g-column-top, +body.g-extended.g-column-4.g-sidebar-left #g-column-bottom { min-width: 1580px; } diff --git a/themes/greydragon/css/menus.css b/themes/greydragon/css/menus.css new file mode 100644 index 0000000..194e93c --- /dev/null +++ b/themes/greydragon/css/menus.css @@ -0,0 +1,58 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules related to menus + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* menus.css - Main menu ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-site-menu { position: absolute; } +#g-site-menu.g-default { left: 21em; bottom: 0; } +#g-site-menu.g-top { left: 21em; top: 2px; } +#g-site-menu.g-bottom-right { bottom: 0; right: 2em; } +#g-site-menu.g-bar { left: 0; top: 0; width: 100%; padding-left: .5em; position: fixed !important; z-index: 9999; font-size: 0.9em; } + +#g-site-menu ul { float: left; width: 100%; white-space: nowrap; z-index: 102; } +#g-site-menu>ul>li>ul { margin-top: 1px; } + +#g-site-menu ul ul ul { padding-top: 0; } +#g-site-menu a { display: block; padding: 0.2em 0.4em; text-align: center; width: auto; letter-spacing: 0; cursor: pointer; z-index: 102; } +#g-site-menu li { float: left; padding: 0; background-color: transparent; border: transparent 1px solid; z-index: 102; } +#g-site-menu li ul a { text-align: left; padding: 0.3em 0; text-indent: 0.8em; letter-spacing: 0; } +#g-site-menu li ul a:hover { background-image: none; } +#g-site-menu li ul { position: absolute; margin: 0 0 0 -1px; width: 14em; height: auto; left: -999em; } + +#g-site-menu li li { width: 14em; padding-right: 0; } +#g-site-menu li ul a { width: 14em; } +#g-site-menu li ul ul { margin: -1.8em 0 0 14em; } +#g-site-menu li:hover ul ul, +#g-site-menu li:hover ul ul ul, +#g-site-menu li.iemhover ul ul, +#g-site-menu li.iemhover ul ul ul { left: -999em; } +#g-site-menu li:hover ul, +#g-site-menu li li:hover ul, +#g-site-menu li li li:hover ul, +#g-site-menu li.iemhover ul, +#g-site-menu li li.iemhover ul, +#g-site-menu li li li.iemhover ul { left: auto; } + +#g-site-menu>ul>li>ul { display: none; } + +/* menus.css - Context menu ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.g-item .g-context-menu { position: absolute; margin: 0; padding: 0; top: 20px; left: 198px; width: 14px; height: 14px; background-position: -178px -144px; z-index: 4; } +.g-item .g-context-menu li { width: 100%; padding: 0; margin: 0; text-indent: -9999px; } +.g-item .g-context-menu>li>a { font-size: 0em; display: none; } +.g-item .g-context-menu:hover { top: 4px; left: 16px; top: 16px; width: 200px; height: auto; z-index: 102; } + +.g-extended .g-item .g-context-menu { left: 300px; } +.g-extended .g-item .g-context-menu:hover { width: 300px; left: 16px; } +.g-item .g-context-menu ul { padding: 0; margin: 0; } +.g-item .g-context-menu li li { display: none; } +.g-item .g-context-menu li li a { display: block; padding: 4px 6px; } +.g-item .g-context-menu:hover li li { display: block; text-indent: 0px; } + +.g-item.g-detail .g-context-menu { left: auto; right: 6px; } +.g-item.g-detail .g-context-menu:hover { left: auto; right: 6px; } \ No newline at end of file diff --git a/themes/greydragon/css/modules.css b/themes/greydragon/css/modules.css new file mode 100644 index 0000000..ff155d2 --- /dev/null +++ b/themes/greydragon/css/modules.css @@ -0,0 +1,165 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules related to modules + */ + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +/* modules.css - ShadowBox Skin ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#sb-title { overflow: hidden; } +#sb-title-inner { font-size: 10pt; font-weight: bold; padding-left: 10px; } +#sb-nav #sb-nav-close { background-image: url('../images/close.png'); width: 60px; } +#sb-container > #sb-overlay { min-height: 530px; overflow: auto; } + +/* modules.css - FancyBox Skin ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#fancybox-outer { background: #555; } +#fancybox-content { border-color: #555; } +#fancybox-title-over { font-weight: bold; } +.fancybox-title-inside { background: transparent; font-weight: bold; color: #FFF; text-align: left; } +#fancybox-close { background: url(../images/close.png) 0 0 no-repeat; width: 60px; height: 16px; right: -6px; } +#fancybox-left-ico { background: url(../images/arrows_left.png) no-repeat left center; width: 35px; height: 107px; top: 40%; } +#fancybox-left:hover span { left: 10px; } +#fancybox-right-ico { background: url(../images/arrows_right.png) no-repeat left center; width: 35px; height: 107px; top: 40%; } +#fancybox-right:hover span { right: 10px; } + +/* modules.css - Exif Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-data td { padding: 0.2em; } +.g-thumbcrop a.g-meta-exif-link { position: absolute; left: 278px; bottom: 4px; z-index: 100; display: block; background: none center bottom no-repeat; width: 17px; height: 16px; } + +/* modules.css - Image Block ~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-image-block>div { margin-left: 1px; margin-right: 1px; } +.g-image-block { text-align: center; } +.g-image-block img { padding: 5px; max-width: 200px; height: auto; } + +/* modules.css - Image Block Ex ~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-rootpage #g-column-bottom #g-image-block-ex { display: none; } + +/* modules.css - RSS Feeds ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +ul#g-feeds { padding: 0; margin: 0; } + +/* modules.css - Tags ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-tag-cloud ul { padding: 0; font-size: 100%; } +#g-tag-cloud ul li { line-height: 1.2em; } +#g-tag-cloud ul li span { display: none; } + +#g-add-tag-form fieldset ul { float: left; display: inline-block; padding: 0; } +#g-add-tag-form li { float: left; margin: 0; padding: 0; } + +/* modules.css - Comments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-comments { margin-top: 2em; float: left; width: 100%; min-height: 2em; } +#g-comments ul li { margin: 0.4em 0; } + +#g-comments .g-button { right: 0.4em; cursor: pointer; /* hand-shaped cursor */ cursor: hand; /* for IE 5.x */ font-size: 0.8em; color: #333 !important; padding: 2px 10px; margin-top: 0.4em; border: 1px solid; border-color: #999 #666 #666 #999; background-color: #ddd; font-weight: normal; } +.ui-icon-comment { background-position: -210px -144px; } + +#g-comment-detail ul { padding: 0; } +#g-comment-detail>ul>li { margin: 4px 0; padding: 6px; min-height: 40px; } +#g-comment-detail div { margin-top: 6px; padding-bottom: 8px; } +#g-comment-detail li.g-no-comments { display: none; } +#g-comment-detail p.g-no-comments { padding-bottom: 4px; } + +#g-comment-form fieldset { border: none; } +#g-comment-form legend { display: none; width: 100%; } +#g-comment-form ul { padding: 0; } +#g-comment-form>fieldset>ul { margin: 0px 10px; } +#g-comment-form label { display: block; } +#g-comment-form textarea { width: 99%; height: 140px; } +#g-comment-form input[type="text"], +#g-comment-form input[type="password"] { width: 99%; } + +#g-comments .g-author { height: 32px; line-height: 32px; } +#g-comments .g-avatar { height: 32px; margin-right: .4em; width: 32px; } + +/* modules.css - Gallery Stats ~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-gallerystats ul { padding: 0; font-size: x-small; } + +/* modules.css - Info ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-metadata ul { padding: 0; } +#g-metadata .g-description { margin-top: 0.4em; padding: 0.4em 0; } +#g-metadata strong.caption { display: none; } + +/* modules.css - Calendar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-calendarview-link:hover { background-position: left bottom; } + +#g-view-calendar-form fieldset { border: none; } +#g-view-calendar-form ul { padding: 8px; } +#g-view-calendar-form li { padding-top: 8px; display: inline; padding-left: 10px; } +#g-view-calendar-form label { margin: 4px 0; } +#g-view-calendar-form select { margin: 4px 10px; } + +table.calendar { border-spacing: 1px; } +table.calendar td.title a { font-weight: bold; } + +/* modules.css - ClustrMaps ~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-clustrmaps .g-block-content { text-align: center; } + +/* modules.css - GPS Info ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-exif-gps-maps ul { padding-left: 0; } + +/* modules.css - Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-quick-search-form { position: absolute; top: 3em; right: 1em; background: none transparent; } +.viewmode-mini #g-quick-search-form { top: auto; right: auto; left: 1em; bottom: 2px; } +#g-quick-search-form label { display: none; } +#g-quick-search-form li { display: inline; float: left; padding: 0px; } + +#g-quick-search-form input[type="text"] { width: 150px; padding-top: 0.1em; padding-bottom: 0.1em; } +#g-quick-search-form input[type="submit"] { display: block; width: 23px; height: 23px; text-indent: -9999px; overflow: hidden; } + +/* modules.css - Basket ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-header #basket, #g-header a[href="/basket/view_Orders"] { display: none; } +#g-view-basket span.ui-icon { float: none; } + +#g-view-basket .ui-icon-plusthick { background-position: -32px -128px; } +#g-view-basket .ui-icon-clipboard { background-position: -160px -128px; } +#g-view-basket .ui-icon-trash { background-position: -176px -96px; } +#g-view-basket .ui-icon-cart { background-position: -48px -112px; } +#g-view-basket a.g-button { line-height: 2.2em; } +#g-column-centerfull>.g-block { margin-top: 3em; } +#g-column-centerfull>div>.basketbuttons>a.g-button>span { display: none; } + +/* modules.css - Register ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-welcome-message p { padding-bottom: 6px; } +#g-change-password-user-form { height: 100%; } + +/* modules.css - Localization ~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#l10n-client .labels { border-top: white 1px solid; height: 1.7em; } +#l10n-client h2 { padding-top: 0.4em; padding-bottom: 0.3em; } +#l10n-client .label.translation { margin-top: -0.4em; height: 1.7em; } +#l10n-client #l10n-client-toggler { line-height: 1.7em; height: 1.7em; } +#l10n-client .string-list li { font-size: 0.8em; line-height: 1.1em; } +#l10n-client #l10n-client-string-select { width: 24%; } +#l10n-client #l10n-client-string-select .string-list { border: 1px #ccc solid; } +#l10n-client #g-l10n-search-form ul { padding: 0; } +#l10n-client #l10n-client-string-editor { margin-left: 1em; } +#l10n-client-string-editor .source .source-text { margin: 0 0.4em 0 0; border: 1px #ccc solid; padding: 0.4em; line-height: 1em; } +#l10n-client-string-editor .translation { height: 19em; } +#l10n-client #l10n-edit-translation { width: 97%; height: 17em; border: 1px #ccc solid; font-family: monospace; padding: 0.4em; } + +/* modules.css - jcarousel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +.jcarousel-skin-tango .jcarousel-container { padding-top: 0.5em; } + +/* modules.css - ThumbNav ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +#g-thumbnav-block-inline { margin-top: 26px; } +#g-thumbnav-block-inline h2 { display: none; } +#g-thumbnav-block .g-block-content, +#g-thumbnav-block-inline .g-block-content { width: 210px; margin-left: auto; margin-right: auto; } diff --git a/themes/greydragon/css/normalize.css b/themes/greydragon/css/normalize.css new file mode 100644 index 0000000..9776813 --- /dev/null +++ b/themes/greydragon/css/normalize.css @@ -0,0 +1,431 @@ +/*! normalize.css 2011-11-04T15:38 UTC - http://github.com/necolas/normalize.css */ + +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ + +/* + * Corrects block display not defined in IE6/7/8/9 & FF3 + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +/* + * Corrects inline-block display not defined in IE6/7/8/9 & FF3 + */ + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +/* + * Prevents modern browsers from displaying 'audio' without controls + */ + +audio:not([controls]) { + display: none; +} + +/* + * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 + * Known issue: no IE6 support + */ + +[hidden] { + display: none; +} + + +/* ============================================================================= + Base + ========================================================================== */ + +/* + * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units + * http://clagnut.com/blog/348/#c790 + * 2. Keeps page centred in all browsers regardless of content height + * 3. Prevents iOS text size adjust after orientation change, without disabling user zoom + * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ + */ + +html { + font-size: 100%; /* 1 */ + overflow-y: scroll; /* 2 */ + -webkit-text-size-adjust: 100%; /* 3 */ + -ms-text-size-adjust: 100%; /* 3 */ +} + +/* + * Addresses margins handled incorrectly in IE6/7 + */ + +body { + margin: 0; +} + +/* + * Addresses font-family inconsistency between 'textarea' and other form elements. + */ + +body, +button, +input, +select, +textarea { + font-family: sans-serif; +} + + +/* ============================================================================= + Links + ========================================================================== */ + +/* + * Addresses outline displayed oddly in Chrome + */ + +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers + * people.opera.com/patrickl/experiments/keyboard/test + */ + +a:hover, +a:active { + outline: 0; +} + + +/* ============================================================================= + Typography + ========================================================================== */ + +/* + * Neutralise smaller font-size in 'section' and 'article' in FF4+, Chrome, S5 + */ + +h1 { + font-size: 2em; +} + +/* + * Addresses styling not present in IE7/8/9, S5, Chrome + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to 'bolder' in FF3+, S4/5, Chrome +*/ + +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +/* + * Addresses styling not present in S5, Chrome + */ + +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE6/7/8/9 + */ + +mark { + background: #ff0; + color: #000; +} + +/* + * Corrects font family set oddly in IE6, S4/5, Chrome + * en.wikipedia.org/wiki/User:Davidgothberg/Test59 + */ + +pre, +code, +kbd, +samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +/* + * Improves readability of pre-formatted text in all browsers + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* + * 1. Addresses CSS quotes not supported in IE6/7 + * 2. Addresses quote property not supported in S4 + */ + +/* 1 */ + +q { + quotes: none; +} + +/* 2 */ + +q:before, +q:after { + content: ''; + content: none; +} + +small { + font-size: 75%; +} + +/* + * Prevents sub and sup affecting line-height in all browsers + * gist.github.com/413930 + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + + +/* ============================================================================= + Lists + ========================================================================== */ + +ul, +ol { + margin-left: 0; + padding: 0 0 0 40px; +} + +dd { + margin: 0 0 0 40px; +} + +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + + +/* ============================================================================= + Embedded content + ========================================================================== */ + +/* + * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 + * 2. Improves image quality when scaled in IE7 + * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ + */ + +img { + border: 0; /* 1 */ + -ms-interpolation-mode: bicubic; /* 2 */ +} + +/* + * Corrects overflow displayed oddly in IE9 + */ + +svg:not(:root) { + overflow: hidden; +} + + +/* ============================================================================= + Figures + ========================================================================== */ + +/* + * Addresses margin not present in IE6/7/8/9, S5, O11 + */ + +figure { + margin: 0; +} + + +/* ============================================================================= + Forms + ========================================================================== */ + +/* + * Corrects margin displayed oddly in IE6/7 + */ + +form { + margin: 0; +} + +/* + * Define consistent border, margin, and padding + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE6/7/8/9 + * 2. Corrects alignment displayed oddly in IE6/7 + */ + +legend { + border: 0; /* 1 */ + *margin-left: -7px; /* 2 */ +} + +/* + * 1. Corrects font size not being inherited in all browsers + * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome + * 3. Improves appearance and consistency in all browsers + */ + +button, +input, +select, +textarea { + font-size: 100%; /* 1 */ + margin: 0; /* 2 */ + vertical-align: baseline; /* 3 */ + *vertical-align: middle; /* 3 */ +} + +/* + * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet + */ + +button, +input { + line-height: normal; /* 1 */ +} + +/* + * 1. Improves usability and consistency of cursor style between image-type 'input' and others + * 2. Corrects inability to style clickable 'input' types in iOS + * 3. Removes inner spacing in IE7 without affecting normal text inputs + * Known issue: inner spacing remains in IE6 + */ + +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; /* 1 */ + -webkit-appearance: button; /* 2 */ + *overflow: visible; /* 3 */ +} + +/* + * 1. Addresses box sizing set to content-box in IE8/9 + * 2. Removes excess padding in IE8/9 + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Addresses appearance set to searchfield in S5, Chrome + * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding that is displayed in S5, Chrome on OS X + */ + +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in FF3+ + * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE6/7/8/9 + * 2. Improves readability and alignment in all browsers + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + + +/* ============================================================================= + Tables + ========================================================================== */ + +/* + * Remove most spacing between table cells + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/themes/greydragon/css/rtl.css b/themes/greydragon/css/rtl.css new file mode 100644 index 0000000..1d74d90 --- /dev/null +++ b/themes/greydragon/css/rtl.css @@ -0,0 +1,47 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules related to general layout + * Defined as 70em wide + */ + +.rtl { direction: rtl; } + +.rtl #g-header, .rtl #g-main, .rtl #g-sidebar, .rtl #g-footer, +.rtl caption, .rtl th, .rtl #g-dialog, +.rtl .g-context-menu li a, .rtl #g-site-menu a, +.rtl .g-metadata li, .rtl DIV.g-resize .g-description, +.rtl .g-message-box li, .rtl #g-site-status li { text-align: right; } + +.rtl .g-text-right { text-align: left; } + +.rtl #g-site-menu li:hover ul ul, +.rtl #g-site-menu li:hover ul ul ul, +.rtl #g-site-menu li.iemhover ul ul, +.rtl #g-site-menu li.iemhover ul ul ul { left: 999em; } + +.rtl .g-left, .rtl .g-inline li, .rtl .g-breadcrumbs li, .rtl .g-paginator li, +.rtl .g-navigation li .rtl .g-buttonset li, .rtl .ui-icon-left .ui-icon, +.rtl .g-metadata li, +.rtl #g-column-bottom .g-block, +.rtl .g-short-form li, .rtl form ul ul li, .rtl input[type="submit"], +.rtl input[type="reset"], .rtl input.checkbox, .rtl input[type=checkbox], +.rtl input.radio, .rtl input[type=radio] { float: right; } + +.rtl .g-paginator .g-navigation { text-align: left; } + +.rtl #g-column-centerfull #g-album-header h1 { padding-right: 12em; } +.rtl .g-buttonset .g-menu-link { text-indent: 9999px; } + +.rtl .g-thumbslide { padding-left: 0; padding-right: 6px; } +.rtl .g-thumbslide .g-description { padding-left: 10px; } + +.rtl .ui-icon-first { background-position: -210px -178px; } +.rtl .ui-icon-first-d { background-position: -210px -162px; } +.rtl .ui-icon-prev { background-position: -194px -178px; } +.rtl .ui-icon-prev-d { background-position: -194px -162px; } +.rtl .ui-icon-next { background-position: -178px -178px; } +.rtl .ui-icon-next-d { background-position: -178px -162px; } +.rtl .ui-icon-last { background-position: -162px -178px; } +.rtl .ui-icon-last-d { background-position: -162px -162px; } diff --git a/themes/greydragon/css/screen.css b/themes/greydragon/css/screen.css new file mode 100644 index 0000000..d820ad5 --- /dev/null +++ b/themes/greydragon/css/screen.css @@ -0,0 +1,17 @@ +/** + * Gallery 3 Grey Dragon Theme + * Copyright (C) 2006-2011 Serguei Dosyukov + * + * CSS rules - Kitchen sync + * + * Color rules for font/background/lines can be found in dedicated colorpack files + */ + +@import "normalize.css"; +@import "layout.css"; +@import "menus.css"; +@import "modules.css"; +@import "forms.css"; +@import "base.css"; +@import "ipad.css"; +@import "rtl.css"; diff --git a/themes/greydragon/helpers/exif_event.php b/themes/greydragon/helpers/exif_event.php new file mode 100644 index 0000000..02536ff --- /dev/null +++ b/themes/greydragon/helpers/exif_event.php @@ -0,0 +1,45 @@ + +is_album()) { + exif::extract($item); + } + } + + static function item_deleted($item) { + db::build() + ->delete("exif_records") + ->where("item_id", "=", $item->id) + ->execute(); + } + + static function photo_menu($menu, $theme) { + $item = $theme->item(); + $menu->append( + Menu::factory("link") + ->id("exifdata-link") + ->label(t("Photo Details")) + ->url(url::site("exif/show/$item->id")) + ->css_id("g-exifdata-link") + ->css_class("g-dialog-link")); + } +} +?> diff --git a/themes/greydragon/helpers/greydragon_event.php b/themes/greydragon/helpers/greydragon_event.php new file mode 100644 index 0000000..8761b1a --- /dev/null +++ b/themes/greydragon/helpers/greydragon_event.php @@ -0,0 +1,92 @@ + +get("add_menu"); + if (!empty($submenu)) { + $item = $submenu->get("add_photos_item"); + if (!empty($item)) { $item->css_class("ui-icon-plus"); } + + $item = $submenu->get("add_album_item"); + if (!empty($item)) { $item->css_class("ui-icon-note"); } + } + + $submenu = $menu->get("options_menu"); + if (!empty($submenu)) { + $item = $submenu->get("edit_item"); + if (!empty($item)) { $item->css_class("ui-icon-pencil"); } + + $item = $submenu->get("edit_permissions"); + if (!empty($item)) { $item->css_class("ui-icon-key"); } + } + } + + static function read_session_cmdparam($cmd, $cookie, $default) { + try { + $_cmd = $_GET[$cmd]; + } catch (Exception $e) { + }; + + if (isset($_cmd)): + $_var = strtolower($_cmd); + $_from_cmd = TRUE; + if ($_var == "default"): + $_var = $default; + endif; + else: + $_from_cmd = FALSE; + if ($cookie): + try { + $_var = $_COOKIE[$cookie]; + } catch (Exception $e) { + }; + endif; + endif; + + if (!isset($_var)): + $_var = $default; + endif; + + return $_var; + } + + static function add_path($path) { + $config = Kohana_Config::instance(); + $kohana_modules = $config->get("core.modules"); + array_unshift($kohana_modules, THEMEPATH . $path); + $config->set("core.modules", $kohana_modules); + Kohana::include_paths(true); + } + + static function add_path_ex($setting, $cmd, $cookie, $path, $default) { + $value = module::get_var("th_greydragon", $setting, $default); + $value = self::read_session_cmdparam($cmd, $cookie, $value); + + self::add_path("greydragon/css/" . $path . "/" . $value); + } + + static function gallery_ready() { + self::add_path_ex("frame_pack", "framepack", "gd_framepack", "framepacks", "greydragon"); + self::add_path_ex("color_pack", "colorpack", "gd_colorpack", "colorpacks", "greydragon"); + self::add_path("custom"); + } +} +?> \ No newline at end of file diff --git a/themes/greydragon/helpers/greydragon_installer.php b/themes/greydragon/helpers/greydragon_installer.php new file mode 100644 index 0000000..4f19787 --- /dev/null +++ b/themes/greydragon/helpers/greydragon_installer.php @@ -0,0 +1,30 @@ + + \ No newline at end of file diff --git a/themes/greydragon/images/apple-touch-icon.png b/themes/greydragon/images/apple-touch-icon.png new file mode 100644 index 0000000..5017ea4 Binary files /dev/null and b/themes/greydragon/images/apple-touch-icon.png differ diff --git a/themes/greydragon/images/arrows_left.png b/themes/greydragon/images/arrows_left.png new file mode 100644 index 0000000..dc9dcdf Binary files /dev/null and b/themes/greydragon/images/arrows_left.png differ diff --git a/themes/greydragon/images/arrows_right.png b/themes/greydragon/images/arrows_right.png new file mode 100644 index 0000000..80188c1 Binary files /dev/null and b/themes/greydragon/images/arrows_right.png differ diff --git a/themes/greydragon/images/avatar.jpg b/themes/greydragon/images/avatar.jpg new file mode 100644 index 0000000..71166cc Binary files /dev/null and b/themes/greydragon/images/avatar.jpg differ diff --git a/themes/greydragon/images/blue-grad.png b/themes/greydragon/images/blue-grad.png new file mode 100644 index 0000000..868a657 Binary files /dev/null and b/themes/greydragon/images/blue-grad.png differ diff --git a/themes/greydragon/images/button-grad-active-vs.png b/themes/greydragon/images/button-grad-active-vs.png new file mode 100644 index 0000000..dc64172 Binary files /dev/null and b/themes/greydragon/images/button-grad-active-vs.png differ diff --git a/themes/greydragon/images/button-grad-vs.png b/themes/greydragon/images/button-grad-vs.png new file mode 100644 index 0000000..51c55a3 Binary files /dev/null and b/themes/greydragon/images/button-grad-vs.png differ diff --git a/themes/greydragon/images/close.png b/themes/greydragon/images/close.png new file mode 100644 index 0000000..d874f9a Binary files /dev/null and b/themes/greydragon/images/close.png differ diff --git a/themes/greydragon/images/donate.png b/themes/greydragon/images/donate.png new file mode 100644 index 0000000..f36bb57 Binary files /dev/null and b/themes/greydragon/images/donate.png differ diff --git a/themes/greydragon/images/gallery.png b/themes/greydragon/images/gallery.png new file mode 100644 index 0000000..038b18a Binary files /dev/null and b/themes/greydragon/images/gallery.png differ diff --git a/themes/greydragon/images/ico-allowed.png b/themes/greydragon/images/ico-allowed.png new file mode 100644 index 0000000..08f2493 Binary files /dev/null and b/themes/greydragon/images/ico-allowed.png differ diff --git a/themes/greydragon/images/ico-denied-inactive.png b/themes/greydragon/images/ico-denied-inactive.png new file mode 100644 index 0000000..56db3ff Binary files /dev/null and b/themes/greydragon/images/ico-denied-inactive.png differ diff --git a/themes/greydragon/images/ico-denied-passive.png b/themes/greydragon/images/ico-denied-passive.png new file mode 100644 index 0000000..1e99223 Binary files /dev/null and b/themes/greydragon/images/ico-denied-passive.png differ diff --git a/themes/greydragon/images/ico-denied.png b/themes/greydragon/images/ico-denied.png new file mode 100644 index 0000000..08f2493 Binary files /dev/null and b/themes/greydragon/images/ico-denied.png differ diff --git a/themes/greydragon/images/ico-error.png b/themes/greydragon/images/ico-error.png new file mode 100644 index 0000000..c37bd06 Binary files /dev/null and b/themes/greydragon/images/ico-error.png differ diff --git a/themes/greydragon/images/ico-help.png b/themes/greydragon/images/ico-help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/themes/greydragon/images/ico-help.png differ diff --git a/themes/greydragon/images/ico-info.png b/themes/greydragon/images/ico-info.png new file mode 100644 index 0000000..12cd1ae Binary files /dev/null and b/themes/greydragon/images/ico-info.png differ diff --git a/themes/greydragon/images/ico-lock.png b/themes/greydragon/images/ico-lock.png new file mode 100644 index 0000000..2ebc4f6 Binary files /dev/null and b/themes/greydragon/images/ico-lock.png differ diff --git a/themes/greydragon/images/ico-success-inactive.png b/themes/greydragon/images/ico-success-inactive.png new file mode 100644 index 0000000..a209fb6 Binary files /dev/null and b/themes/greydragon/images/ico-success-inactive.png differ diff --git a/themes/greydragon/images/ico-success-passive.png b/themes/greydragon/images/ico-success-passive.png new file mode 100644 index 0000000..c8460d1 Binary files /dev/null and b/themes/greydragon/images/ico-success-passive.png differ diff --git a/themes/greydragon/images/ico-success.png b/themes/greydragon/images/ico-success.png new file mode 100644 index 0000000..a9925a0 Binary files /dev/null and b/themes/greydragon/images/ico-success.png differ diff --git a/themes/greydragon/images/ico-warning.png b/themes/greydragon/images/ico-warning.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/themes/greydragon/images/ico-warning.png differ diff --git a/themes/greydragon/images/missing-img.png b/themes/greydragon/images/missing-img.png new file mode 100644 index 0000000..12b7394 Binary files /dev/null and b/themes/greydragon/images/missing-img.png differ diff --git a/themes/greydragon/js/gallery.ajax.custom.js b/themes/greydragon/js/gallery.ajax.custom.js new file mode 100644 index 0000000..5be029f --- /dev/null +++ b/themes/greydragon/js/gallery.ajax.custom.js @@ -0,0 +1,14 @@ +(function($) { + $.widget("ui.gallery_ajax", { + _init: function() { + this.element.click(function(event) { + eval("var ajax_handler = " + $(event.currentTarget).attr("ajax_handler")); + $.get($(event.currentTarget).attr("href"), function(data) { + ajax_handler(data); + }); + event.preventDefault(); + return false; + }); + } + }); +})(jQuery); diff --git a/themes/greydragon/js/gallery.dialog.custom.js b/themes/greydragon/js/gallery.dialog.custom.js new file mode 100644 index 0000000..2e3b8a1 --- /dev/null +++ b/themes/greydragon/js/gallery.dialog.custom.js @@ -0,0 +1,232 @@ +(function($) { + var fixDialogAutoWidth = $.noop; + + jQuery.browser = {}; + jQuery.browser.mozilla = /mozilla/.test(navigator.userAgent.toLowerCase()) && !/webkit/.test(navigator.userAgent.toLowerCase()); + jQuery.browser.webkit = /webkit/.test(navigator.userAgent.toLowerCase()); + jQuery.browser.opera = /opera/.test(navigator.userAgent.toLowerCase()); + jQuery.browser.msie = /msie/.test(navigator.userAgent.toLowerCase()); + + if ( $.browser.msie ) { + fixDialogAutoWidth = function(content) { + var dialog = $(content).parent('.ui-dialog'); + var width = dialog.innerWidth(); + if ( width ) dialog.css('width', width); + } + } + + var _init = $.ui.dialog.prototype._init; + $.ui.dialog.prototype._init = function() { + // IE magick: (width: 'auto' not working correctly) : + // http://dev.jqueryui.com/ticket/4437 + if ( this.options.width == 'auto' ) { + var open = this.options.open; + this.options.open = function() { + fixDialogAutoWidth(this); + if ( open ) open.apply(this); + } + } + // yet another bug options.hide: 'drop' does not work + // in IE http://dev.jqueryui.com/ticket/5615 + if ( $.browser.msie && this.options.hide == 'drop' ) { + this.options.hide = 'fold'; + } + return _init.apply(this); // calls open() if autoOpen + }; + + $.widget("ui.gallery_dialog", { + _init: function() { + var self = this; + if (!self.options.immediate) { + this.element.click(function(event) { + event.preventDefault(); + self._show($(event.currentTarget).attr("href")); + return false; + }); + } else { + self._show(this.element.attr("href")); + } + }, + + _show: function(sHref) { + var self = this; + var eDialog = '
      '; + + if ($("#g-dialog").length) { + $("#g-dialog").dialog("close"); + } + $("body").append(eDialog); + + if (!self.options.close) { + self.options.close = self.close_dialog; + } + $("#g-dialog").dialog(self.options); + $("#g-dialog").dialog('option', 'resizable', false); + + $("#g-dialog").gallery_show_loading(); + + $.ajax({ + url: sHref, + type: "GET", + success: function(data, textStatus, xhr) { + var mimeType = /^(\w+\/\w+)\;?/.exec(xhr.getResponseHeader("Content-Type")); + + var content = ""; + if (mimeType[1] == "application/json") { + data = JSON.parse(data); + content = data.html; + } else { + content = data; + } + + $("#g-dialog").html(content).gallery_show_loading(); + + if ($("#g-dialog form").length) { + self.form_loaded(null, $("#g-dialog form")); + } + self._layout(); + + $("#g-dialog").dialog("open"); + self._set_title(); + + if ($("#g-dialog form").length) { + self._ajaxify_dialog(); + } + } + }); + $("#g-dialog").dialog("option", "self", self); + }, + + error: function(xhr, textStatus, errorThrown) { + $("#g-dialog").html(xhr.responseText); + self._set_title(); + self._layout(); + }, + + _layout: function() { + var dialogWidth; + var dialogHeight = $("#g-dialog").height(); + var cssWidth = new String($("#g-dialog form").css("width")); + var childWidth = cssWidth.replace(/[^0-9]/g,""); + var size = $.gallery_get_viewport_size(); + if ($("#g-dialog iframe").length) { + dialogWidth = size.width() - 100; + // Set the iframe width and height + $("#g-dialog iframe").width("100%").height(size.height() - 100); + } else if ($("#g-dialog .g-dialog-panel").length) { + dialogWidth = size.width() - 100; + $("#g-dialog").dialog("option", "height", size.height() - 100); + } else if (childWidth == "" ) { + dialogWidth = 500; + } else { + dialogWidth = parseInt(childWidth) + 28; + } + + var leftPos = (size.width() - dialogWidth) / 2; + var rightPos = (size.height() - dialogHeight) / 2; + + $("#g-dialog").dialog('option', 'width', dialogWidth); + $("#g-dialog").dialog('option', 'position', [leftPos, rightPos]); + }, + + form_loaded: function(event, ui) { + // Should be defined (and localized) in the theme + MSG_CANCEL = MSG_CANCEL || 'Cancel'; + var eCancel = '' + MSG_CANCEL + ''; + if ($("#g-dialog .submit").length) { + $("#g-dialog .submit").addClass("ui-state-default ui-corner-all"); + $.fn.gallery_hover_init(); + $("#g-dialog .submit").parent().append(eCancel); + $("#g-dialog .g-cancel").click(function(event) { + $("#g-dialog").dialog("close"); + event.preventDefault(); + }); + } + $("#g-dialog .ui-state-default").hover( + function() { + $(this).addClass("ui-state-hover"); + }, + function() { + $(this).removeClass("ui-state-hover"); + } + ); + }, + + close_dialog: function(event, ui) { + var self = $("#g-dialog").dialog("option", "self"); + if ($("#g-dialog form").length) { + self._trigger("form_closing", null, $("#g-dialog form")); + } + self._trigger("dialog_closing", null, $("#g-dialog")); + $("#g-dialog").dialog("destroy").remove(); + }, + + _ajaxify_dialog: function() { + var self = this; + $("#g-dialog form").ajaxForm({ + dataType: 'json', + beforeSubmit: function(formData, form, options) { + form.find(":submit") + .addClass("ui-state-disabled") + .attr("disabled", "disabled"); + return true; + }, + success: function(data) { + if (data.html) { + $("#g-dialog").html(data.html); + $("#g-dialog").dialog("option", "position", "center"); + $("#g-dialog form :submit").removeClass("ui-state-disabled") + .attr("disabled", null); + self._set_title(); + self._ajaxify_dialog(); + self.form_loaded(null, $("#g-dialog form")); + if (typeof data.reset == 'function') { + eval(data.reset + '()'); + } + } + if (data.result == "success") { + if (data.location) { + window.location = data.location; + } else { + window.location.reload(); + } + } + }, + error: function(xhr, textStatus, errorThrown) { + $("#g-dialog").html(xhr.responseText); + self._set_title(); + self._layout(); + } + }); + }, + + _set_title: function() { + // Remove titlebar for progress dialogs or set title + if ($("#g-dialog #g-progress").length) { + $(".ui-dialog-titlebar").remove(); + } else if ($("#g-dialog h1").length) { + $("#g-dialog").dialog('option', 'title', $("#g-dialog h1:eq(0)").html()); + $("#g-dialog h1:eq(0)").hide(); + } else if ($("#g-dialog fieldset legend").length) { + $("#g-dialog").dialog('option', 'title', $("#g-dialog fieldset legend:eq(0)").html()); + } + }, + + form_closing: function(event, ui) {}, + dialog_closing: function(event, ui) {} + }); + + $.extend($.ui.gallery_dialog, { + defaults: { + autoResize: false + ,show: 'clip' + ,hide: 'clip' + ,height: 'auto' + ,width: 'auto' + ,autoOpen: false + ,modal: true + ,position: 'center' + ,draggable: false + } + }); +})(jQuery); diff --git a/themes/greydragon/js/jquery-ui.min.js b/themes/greydragon/js/jquery-ui.min.js new file mode 100644 index 0000000..efbd3e5 --- /dev/null +++ b/themes/greydragon/js/jquery-ui.min.js @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.10.4 - 2014-03-16 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
      ",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("
      "),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widths?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(t){t.widget("ui.draggable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"!==this.options.helper||/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(t(i.iframeFix===!0?"iframe":i.iframeFix).each(function(){t("
      ").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(t(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offsetParent=this.helper.offsetParent(),this.offsetParentCssPosition=this.offsetParent.css("position"),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.offset.scroll=!1,t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_mouseDrag:function(e,i){if("fixed"===this.offsetParentCssPosition&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"original"!==this.options.helper||t.contains(this.element[0].ownerDocument,this.element[0])?("invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1):!1},_mouseUp:function(e){return t("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return s.parents("body").length||s.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s[0]===this.element[0]||/(fixed|absolute)/.test(s.css("position"))||s.css("position","absolute"),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.element.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;return n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):"document"===n.containment?(this.containment=[0,0,t(document).width()-this.helperProportions.width-this.margins.left,(t(document).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):n.containment.constructor===Array?(this.containment=n.containment,undefined):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e="hidden"!==i.css("overflow"),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=i),undefined):(this.containment=null,undefined)},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent;return this.offset.scroll||(this.offset.scroll={top:n.scrollTop(),left:n.scrollLeft()}),{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top)*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)*s}},_generatePosition:function(e){var i,s,n,a,o=this.options,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=e.pageX,h=e.pageY;return this.offset.scroll||(this.offset.scroll={top:r.scrollTop(),left:r.scrollLeft()}),this.originalPosition&&(this.containment&&(this.relative_container?(s=this.relative_container.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.lefti[2]&&(l=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(h=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,h=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((l-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,l=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a)),{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top),left:l-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s]),"drag"===e&&(this.positionAbs=this._convertPositionTo("absolute")),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i){var s=t(this).data("ui-draggable"),n=s.options,a=t.extend({},i,{item:s.element});s.sortables=[],t(n.connectToSortable).each(function(){var i=t.data(this,"ui-sortable");i&&!i.options.disabled&&(s.sortables.push({instance:i,shouldRevert:i.options.revert}),i.refreshPositions(),i._trigger("activate",e,a))})},stop:function(e,i){var s=t(this).data("ui-draggable"),n=t.extend({},i,{item:s.element});t.each(s.sortables,function(){this.instance.isOver?(this.instance.isOver=0,s.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=this.shouldRevert),this.instance._mouseStop(e),this.instance.options.helper=this.instance.options._helper,"original"===s.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",e,n))})},drag:function(e,i){var s=t(this).data("ui-draggable"),n=this;t.each(s.sortables,function(){var a=!1,o=this;this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(a=!0,t.each(s.sortables,function(){return this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this!==o&&this.instance._intersectsWith(this.instance.containerCache)&&t.contains(o.instance.element[0],this.instance.element[0])&&(a=!1),a})),a?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=t(n).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return i.helper[0]},e.target=this.instance.currentItem[0],this.instance._mouseCapture(e,!0),this.instance._mouseStart(e,!0,!0),this.instance.offset.click.top=s.offset.click.top,this.instance.offset.click.left=s.offset.click.left,this.instance.offset.parent.left-=s.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=s.offset.parent.top-this.instance.offset.parent.top,s._trigger("toSortable",e),s.dropped=this.instance.element,s.currentItem=s.element,this.instance.fromOutside=s),this.instance.currentItem&&this.instance._mouseDrag(e)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",e,this.instance._uiHash(this.instance)),this.instance._mouseStop(e,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),s._trigger("fromSortable",e),s.dropped=!1)})}}),t.ui.plugin.add("draggable","cursor",{start:function(){var e=t("body"),i=t(this).data("ui-draggable").options;e.css("cursor")&&(i._cursor=e.css("cursor")),e.css("cursor",i.cursor)},stop:function(){var e=t(this).data("ui-draggable").options;e._cursor&&t("body").css("cursor",e._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("opacity")&&(n._opacity=s.css("opacity")),s.css("opacity",n.opacity)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._opacity&&t(i.helper).css("opacity",s._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(){var e=t(this).data("ui-draggable");e.scrollParent[0]!==document&&"HTML"!==e.scrollParent[0].tagName&&(e.overflowOffset=e.scrollParent.offset())},drag:function(e){var i=t(this).data("ui-draggable"),s=i.options,n=!1;i.scrollParent[0]!==document&&"HTML"!==i.scrollParent[0].tagName?(s.axis&&"x"===s.axis||(i.overflowOffset.top+i.scrollParent[0].offsetHeight-e.pageY=0;u--)r=p.snapElements[u].left,l=r+p.snapElements[u].width,h=p.snapElements[u].top,c=h+p.snapElements[u].height,r-f>_||m>l+f||h-f>b||v>c+f||!t.contains(p.snapElements[u].item.ownerDocument,p.snapElements[u].item)?(p.snapElements[u].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=!1):("inner"!==g.snapMode&&(s=f>=Math.abs(h-b),n=f>=Math.abs(c-v),a=f>=Math.abs(r-_),o=f>=Math.abs(l-m),s&&(i.position.top=p._convertPositionTo("relative",{top:h-p.helperProportions.height,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r-p.helperProportions.width}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l}).left-p.margins.left)),d=s||n||a||o,"outer"!==g.snapMode&&(s=f>=Math.abs(h-v),n=f>=Math.abs(c-b),a=f>=Math.abs(r-m),o=f>=Math.abs(l-_),s&&(i.position.top=p._convertPositionTo("relative",{top:h,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c-p.helperProportions.height,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[u].snapping&&(s||n||a||o||d)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=s||n||a||o||d)}}),t.ui.plugin.add("draggable","stack",{start:function(){var e,i=this.data("ui-draggable").options,s=t.makeArray(t(i.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});s.length&&(e=parseInt(t(s[0]).css("zIndex"),10)||0,t(s).each(function(i){t(this).css("zIndex",e+i)}),this.css("zIndex",e+s.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("zIndex")&&(n._zIndex=s.css("zIndex")),s.css("zIndex",n.zIndex)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._zIndex&&t(i.helper).css("zIndex",s._zIndex)}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}t.widget("ui.droppable",{version:"1.10.4",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],undefined):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},t.ui.ddmanager.droppables[i.scope]=t.ui.ddmanager.droppables[i.scope]||[],t.ui.ddmanager.droppables[i.scope].push(this),i.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){for(var e=0,i=t.ui.ddmanager.droppables[this.options.scope];i.length>e;e++)i[e]===this&&i.splice(e,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(e,i){"accept"===e&&(this.accept=t.isFunction(i)?i:function(t){return t.is(i)}),t.Widget.prototype._setOption.apply(this,arguments)},_activate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var e=t.data(this,"ui-droppable");return e.options.greedy&&!e.options.disabled&&e.options.scope===s.options.scope&&e.accept.call(e.element[0],s.currentItem||s.element)&&t.ui.intersect(s,t.extend(e,{offset:e.element.offset()}),e.options.tolerance)?(n=!0,!1):undefined}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}}}),t.ui.intersect=function(t,i,s){if(!i.offset)return!1;var n,a,o=(t.positionAbs||t.position.absolute).left,r=(t.positionAbs||t.position.absolute).top,l=o+t.helperProportions.width,h=r+t.helperProportions.height,c=i.offset.left,u=i.offset.top,d=c+i.proportions().width,p=u+i.proportions().height;switch(s){case"fit":return o>=c&&d>=l&&r>=u&&p>=h;case"intersect":return o+t.helperProportions.width/2>c&&d>l-t.helperProportions.width/2&&r+t.helperProportions.height/2>u&&p>h-t.helperProportions.height/2;case"pointer":return n=(t.positionAbs||t.position.absolute).left+(t.clickOffset||t.offset.click).left,a=(t.positionAbs||t.position.absolute).top+(t.clickOffset||t.offset.click).top,e(a,u,i.proportions().height)&&e(n,c,i.proportions().width);case"touch":return(r>=u&&p>=r||h>=u&&p>=h||u>r&&h>p)&&(o>=c&&d>=o||l>=c&&d>=l||c>o&&l>d);default:return!1}},t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,a=t.ui.ddmanager.droppables[e.options.scope]||[],o=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||e&&!a[s].accept.call(a[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue t}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&t.ui.intersect(e,this,this.options.tolerance)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").bind("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=t.ui.intersect(e,this,this.options.tolerance),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return t.data(this,"ui-droppable").options.scope===n}),a.length&&(s=t.data(a[0],"ui-droppable"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").unbind("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}}})(jQuery);(function(t){function e(t){return parseInt(t,10)||0}function i(t){return!isNaN(parseInt(t,10))}t.widget("ui.resizable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var e,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(t("
      ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),e=this.handles.split(","),this.handles={},i=0;e.length>i;i++)s=t.trim(e[i]),a="ui-resizable-"+s,n=t("
      "),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(e){var i,s,n,a;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=t(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=t(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,a),this._proportionallyResize()),t(this.handles[i]).length},this._renderAxis(this.element),this._handles=t(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),t(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(t(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(t(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(i){var s,n,a,o=this.options,r=this.element.position(),h=this.element;return this.resizing=!0,/absolute/.test(h.css("position"))?h.css({position:"absolute",top:h.css("top"),left:h.css("left")}):h.is(".ui-draggable")&&h.css({position:"absolute",top:r.top,left:r.left}),this._renderProxy(),s=e(this.helper.css("left")),n=e(this.helper.css("top")),o.containment&&(s+=t(o.containment).scrollLeft()||0,n+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:s,top:n},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:h.width(),height:h.height()},this.originalSize=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalPosition={left:s,top:n},this.sizeDiff={width:h.outerWidth()-h.width(),height:h.outerHeight()-h.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,a=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===a?this.axis+"-resize":a),h.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(e){var i,s=this.helper,n={},a=this.originalMousePosition,o=this.axis,r=this.position.top,h=this.position.left,l=this.size.width,c=this.size.height,u=e.pageX-a.left||0,d=e.pageY-a.top||0,p=this._change[o];return p?(i=p.apply(this,[e,u,d]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),this.position.top!==r&&(n.top=this.position.top+"px"),this.position.left!==h&&(n.left=this.position.left+"px"),this.size.width!==l&&(n.width=this.size.width+"px"),this.size.height!==c&&(n.height=this.size.height+"px"),s.css(n),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(n)||this._trigger("resize",e,this.ui()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&t.ui.hasScroll(i[0],"left")?0:c.sizeDiff.height,a=s?0:c.sizeDiff.width,o={width:c.helper.width()-a,height:c.helper.height()-n},r=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null,h=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(o,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(t){var e,s,n,a,o,r=this.options;o={minWidth:i(r.minWidth)?r.minWidth:0,maxWidth:i(r.maxWidth)?r.maxWidth:1/0,minHeight:i(r.minHeight)?r.minHeight:0,maxHeight:i(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,n=o.minWidth/this.aspectRatio,s=o.maxHeight*this.aspectRatio,a=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),n>o.minHeight&&(o.minHeight=n),o.maxWidth>s&&(o.maxWidth=s),o.maxHeight>a&&(o.maxHeight=a)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),i(t.left)&&(this.position.left=t.left),i(t.top)&&(this.position.top=t.top),i(t.height)&&(this.size.height=t.height),i(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,s=this.size,n=this.axis;return i(t.height)?t.width=t.height*this.aspectRatio:i(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===n&&(t.left=e.left+(s.width-t.width),t.top=null),"nw"===n&&(t.top=e.top+(s.height-t.height),t.left=e.left+(s.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,s=this.axis,n=i(t.width)&&e.maxWidth&&e.maxWidtht.width,r=i(t.height)&&e.minHeight&&e.minHeight>t.height,h=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,c=/sw|nw|w/.test(s),u=/nw|ne|n/.test(s);return o&&(t.width=e.minWidth),r&&(t.height=e.minHeight),n&&(t.width=e.maxWidth),a&&(t.height=e.maxHeight),o&&c&&(t.left=h-e.minWidth),n&&c&&(t.left=h-e.maxWidth),r&&u&&(t.top=l-e.minHeight),a&&u&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var t,e,i,s,n,a=this.helper||this.element;for(t=0;this._proportionallyResizeElements.length>t;t++){if(n=this._proportionallyResizeElements[t],!this.borderDif)for(this.borderDif=[],i=[n.css("borderTopWidth"),n.css("borderRightWidth"),n.css("borderBottomWidth"),n.css("borderLeftWidth")],s=[n.css("paddingTop"),n.css("paddingRight"),n.css("paddingBottom"),n.css("paddingLeft")],e=0;i.length>e;e++)this.borderDif[e]=(parseInt(i[e],10)||0)+(parseInt(s[e],10)||0);n.css({height:a.height()-this.borderDif[0]-this.borderDif[2]||0,width:a.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("
      "),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).data("ui-resizable"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&t.ui.hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,c=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var i,s,n,a,o,r,h,l=t(this).data("ui-resizable"),c=l.options,u=l.element,d=c.containment,p=d instanceof t?d.get(0):/parent/.test(d)?u.parent().get(0):d;p&&(l.containerElement=t(p),/document/.test(d)||d===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(i=t(p),s=[],t(["Top","Right","Left","Bottom"]).each(function(t,n){s[t]=e(i.css("padding"+n))}),l.containerOffset=i.offset(),l.containerPosition=i.position(),l.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},n=l.containerOffset,a=l.containerSize.height,o=l.containerSize.width,r=t.ui.hasScroll(p,"left")?p.scrollWidth:o,h=t.ui.hasScroll(p)?p.scrollHeight:a,l.parentData={element:p,left:n.left,top:n.top,width:r,height:h}))},resize:function(e){var i,s,n,a,o=t(this).data("ui-resizable"),r=o.options,h=o.containerOffset,l=o.position,c=o._aspectRatio||e.shiftKey,u={top:0,left:0},d=o.containerElement;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-u.left),c&&(o.size.height=o.size.width/o.aspectRatio),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),c&&(o.size.width=o.size.height*o.aspectRatio),o.position.top=o._helper?h.top:0),o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top,i=Math.abs((o._helper?o.offset.left-u.left:o.offset.left-u.left)+o.sizeDiff.width),s=Math.abs((o._helper?o.offset.top-u.top:o.offset.top-h.top)+o.sizeDiff.height),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a&&(i-=Math.abs(o.parentData.left)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,c&&(o.size.height=o.size.width/o.aspectRatio)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,c&&(o.size.width=o.size.height*o.aspectRatio))},stop:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.containerOffset,n=e.containerPosition,a=e.containerElement,o=t(e.helper),r=o.offset(),h=o.outerWidth()-e.sizeDiff.width,l=o.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(a.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(a.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=function(e){t(e).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseInt(e.width(),10),height:parseInt(e.height(),10),left:parseInt(e.css("left"),10),top:parseInt(e.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):t.each(i.alsoResize,function(t){s(t)})},resize:function(e,i){var s=t(this).data("ui-resizable"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0},h=function(e,s){t(e).each(function(){var e=t(this),n=t(this).data("ui-resizable-alsoresize"),a={},o=s&&s.length?s:e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(n[e]||0)+(r[e]||0);i&&i>=0&&(a[e]=i||null)}),e.css(a)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):t.each(n.alsoResize,function(t,e){h(t,e)})},stop:function(){t(this).removeData("resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).data("ui-resizable");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).data("ui-resizable");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size,n=e.originalSize,a=e.originalPosition,o=e.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,h=r[0]||1,l=r[1]||1,c=Math.round((s.width-n.width)/h)*h,u=Math.round((s.height-n.height)/l)*l,d=n.width+c,p=n.height+u,f=i.maxWidth&&d>i.maxWidth,g=i.maxHeight&&p>i.maxHeight,m=i.minWidth&&i.minWidth>d,v=i.minHeight&&i.minHeight>p;i.grid=r,m&&(d+=h),v&&(p+=l),f&&(d-=h),g&&(p-=l),/^(se|s|e)$/.test(o)?(e.size.width=d,e.size.height=p):/^(ne)$/.test(o)?(e.size.width=d,e.size.height=p,e.position.top=a.top-u):/^(sw)$/.test(o)?(e.size.width=d,e.size.height=p,e.position.left=a.left-c):(p-l>0?(e.size.height=p,e.position.top=a.top-u):(e.size.height=l,e.position.top=a.top+n.height-l),d-h>0?(e.size.width=d,e.position.left=a.left-c):(e.size.width=h,e.position.left=a.left+n.width-h))}})})(jQuery);(function(t){t.widget("ui.selectable",t.ui.mouse,{version:"1.10.4",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e,i=this;this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e=t(i.options.filter,i.element[0]),e.addClass("ui-selectee"),e.each(function(){var e=t(this),i=e.offset();t.data(this,"selectable-item",{element:this,$element:e,left:i.left,top:i.top,right:i.left+e.outerWidth(),bottom:i.top+e.outerHeight(),startselected:!1,selected:e.hasClass("ui-selected"),selecting:e.hasClass("ui-selecting"),unselecting:e.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=e.addClass("ui-selectee"),this._mouseInit(),this.helper=t("
      ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(s.$element.removeClass("ui-selected"),s.selected=!1,s.$element.addClass("ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),n.$element.removeClass(s?"ui-unselecting":"ui-selected").addClass(s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):undefined}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,a=this.opos[0],o=this.opos[1],r=e.pageX,l=e.pageY;return a>r&&(i=r,r=a,a=i),o>l&&(i=l,l=o,o=i),this.helper.css({left:a,top:o,width:r-a,height:l-o}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),h=!1;i&&i.element!==s.element[0]&&("touch"===n.tolerance?h=!(i.left>r||a>i.right||i.top>l||o>i.bottom):"fit"===n.tolerance&&(h=i.left>a&&r>i.right&&i.top>o&&l>i.bottom),h?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");s.$element.removeClass("ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");s.$element.removeClass("ui-selecting").addClass("ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))}t.widget("ui.sortable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_setOption:function(e,i){"disabled"===e?(this.options[e]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i="x"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s="y"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&"right"===a||"down"===o?2:1:o&&("down"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&s||"left"===o&&!s:n&&("down"===n&&i||"up"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]).addClass(i||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?e.currentItem.children().each(function(){t(" ",e.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(n)}):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?"up":"down"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger("change",s,this._uiHash()),this.containers[g]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger("beforeStop",t,this._uiHash()),s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery);(function(e){var t=0,i={},a={};i.height=i.paddingTop=i.paddingBottom=i.borderTopWidth=i.borderBottomWidth="hide",a.height=a.paddingTop=a.paddingBottom=a.borderTopWidth=a.borderBottomWidth="show",e.widget("ui.accordion",{version:"1.10.4",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e(),content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),undefined):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t),undefined)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var i=e.ui.keyCode,a=this.headers.length,s=this.headers.index(t.target),n=!1;switch(t.keyCode){case i.RIGHT:case i.DOWN:n=this.headers[(s+1)%a];break;case i.LEFT:case i.UP:n=this.headers[(s-1+a)%a];break;case i.SPACE:case i.ENTER:this._eventHandler(t);break;case i.HOME:n=this.headers[0];break;case i.END:n=this.headers[a-1]}n&&(e(t.target).attr("tabIndex",-1),e(n).attr("tabIndex",0),n.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide()},_refresh:function(){var i,a=this.options,s=a.heightStyle,n=this.element.parent(),r=this.accordionId="ui-accordion-"+(this.element.attr("id")||++t);this.active=this._findActive(a.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(t){var i=e(this),a=i.attr("id"),s=i.next(),n=s.attr("id");a||(a=r+"-header-"+t,i.attr("id",a)),n||(n=r+"-panel-"+t,s.attr("id",n)),i.attr("aria-controls",n),s.attr("aria-labelledby",a)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(a.event),"fill"===s?(i=n.height(),this.element.siblings(":visible").each(function(){var t=e(this),a=t.css("position");"absolute"!==a&&"fixed"!==a&&(i-=t.outerHeight(!0))}),this.headers.each(function(){i-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===s&&(i=0,this.headers.next().each(function(){i=Math.max(i,e(this).css("height","").height())}).height(i))},_activate:function(t){var i=this._findActive(t)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var i=this.options,a=this.active,s=e(t.currentTarget),n=s[0]===a[0],r=n&&i.collapsible,o=r?e():s.next(),h=a.next(),d={oldHeader:a,oldPanel:h,newHeader:r?e():s,newPanel:o};t.preventDefault(),n&&!i.collapsible||this._trigger("beforeActivate",t,d)===!1||(i.active=r?!1:this.headers.index(s),this.active=n?e():s,this._toggle(d),a.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&a.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),n||(s.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),s.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var i=t.newPanel,a=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=a,this.options.animate?this._animate(i,a,t):(a.hide(),i.show(),this._toggleComplete(t)),a.attr({"aria-hidden":"true"}),a.prev().attr("aria-selected","false"),i.length&&a.length?a.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true",tabIndex:0,"aria-expanded":"true"})},_animate:function(e,t,s){var n,r,o,h=this,d=0,c=e.length&&(!t.length||e.index()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("
        ").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().data("ui-menu"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){if(this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type)))return this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),undefined;var s=i.item.data("ui-autocomplete-item");!1!==this._trigger("focus",t,{item:s})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(s.value):this.liveRegion.text(s.value)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertBefore(this.element),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length").append(e("").text(i.label)).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this._value(this.term),this.menu.blur(),undefined):(this.menu[e](t),undefined):(this.search(null,t),undefined)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var t;this._superApply(arguments),this.options.disabled||this.cancelSearch||(t=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.text(t))}})})(jQuery);(function(e){var t,i="ui-button ui-widget ui-state-default ui-corner-all",n="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",s=function(){var t=e(this);setTimeout(function(){t.find(":ui-button").button("refresh")},1)},a=function(t){var i=t.name,n=t.form,s=e([]);return i&&(i=i.replace(/'/g,"\\'"),s=n?e(n).find("[name='"+i+"']"):e("[name='"+i+"']",t.ownerDocument).filter(function(){return!this.form})),s};e.widget("ui.button",{version:"1.10.4",defaultElement:"").addClass(this._triggerClass).html(n?e("").attr({src:n,alt:s,title:s}):s)),t[o?"before":"after"](i.trigger),i.trigger.click(function(){return e.datepicker._datepickerShowing&&e.datepicker._lastInput===t[0]?e.datepicker._hideDatepicker():e.datepicker._datepickerShowing&&e.datepicker._lastInput!==t[0]?(e.datepicker._hideDatepicker(),e.datepicker._showDatepicker(t[0])):e.datepicker._showDatepicker(t[0]),!1}))},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t,i,a,s,n=new Date(2009,11,20),r=this._get(e,"dateFormat");r.match(/[DM]/)&&(t=function(e){for(i=0,a=0,s=0;e.length>s;s++)e[s].length>i&&(i=e[s].length,a=s);return a},n.setMonth(t(this._get(e,r.match(/MM/)?"monthNames":"monthNamesShort"))),n.setDate(t(this._get(e,r.match(/DD/)?"dayNames":"dayNamesShort"))+20-n.getDay())),e.input.attr("size",this._formatDate(e,n).length)}},_inlineDatepicker:function(t,i){var a=e(t);a.hasClass(this.markerClassName)||(a.addClass(this.markerClassName).append(i.dpDiv),e.data(t,r,i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(t),i.dpDiv.css("display","block"))},_dialogDatepicker:function(t,i,a,n,o){var u,c,h,l,d,p=this._dialogInst;return p||(this.uuid+=1,u="dp"+this.uuid,this._dialogInput=e(""),this._dialogInput.keydown(this._doKeyDown),e("body").append(this._dialogInput),p=this._dialogInst=this._newInst(this._dialogInput,!1),p.settings={},e.data(this._dialogInput[0],r,p)),s(p.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(p,i):i,this._dialogInput.val(i),this._pos=o?o.length?o:[o.pageX,o.pageY]:null,this._pos||(c=document.documentElement.clientWidth,h=document.documentElement.clientHeight,l=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[c/2-100+l,h/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),p.settings.onSelect=a,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),e.blockUI&&e.blockUI(this.dpDiv),e.data(this._dialogInput[0],r,p),this},_destroyDatepicker:function(t){var i,a=e(t),s=e.data(t,r);a.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),e.removeData(t,r),"input"===i?(s.append.remove(),s.trigger.remove(),a.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&a.removeClass(this.markerClassName).empty())},_enableDatepicker:function(t){var i,a,s=e(t),n=e.data(t,r);s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!1,n.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(a=s.children("."+this._inlineClass),a.children().removeClass("ui-state-disabled"),a.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}))},_disableDatepicker:function(t){var i,a,s=e(t),n=e.data(t,r);s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!0,n.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(a=s.children("."+this._inlineClass),a.children().addClass("ui-state-disabled"),a.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}),this._disabledInputs[this._disabledInputs.length]=t)},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;this._disabledInputs.length>t;t++)if(this._disabledInputs[t]===e)return!0;return!1},_getInst:function(t){try{return e.data(t,r)}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(i,a,n){var r,o,u,c,h=this._getInst(i);return 2===arguments.length&&"string"==typeof a?"defaults"===a?e.extend({},e.datepicker._defaults):h?"all"===a?e.extend({},h.settings):this._get(h,a):null:(r=a||{},"string"==typeof a&&(r={},r[a]=n),h&&(this._curInst===h&&this._hideDatepicker(),o=this._getDateDatepicker(i,!0),u=this._getMinMaxDate(h,"min"),c=this._getMinMaxDate(h,"max"),s(h.settings,r),null!==u&&r.dateFormat!==t&&r.minDate===t&&(h.settings.minDate=this._formatDate(h,u)),null!==c&&r.dateFormat!==t&&r.maxDate===t&&(h.settings.maxDate=this._formatDate(h,c)),"disabled"in r&&(r.disabled?this._disableDatepicker(i):this._enableDatepicker(i)),this._attachments(e(i),h),this._autoSize(h),this._setDate(h,o),this._updateAlternate(h),this._updateDatepicker(h)),t)},_changeDatepicker:function(e,t,i){this._optionDatepicker(e,t,i)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var i=this._getInst(e);i&&(this._setDate(i,t),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(e,t){var i=this._getInst(e);return i&&!i.inline&&this._setDateFromField(i,t),i?this._getDate(i):null},_doKeyDown:function(t){var i,a,s,n=e.datepicker._getInst(t.target),r=!0,o=n.dpDiv.is(".ui-datepicker-rtl");if(n._keyEvent=!0,e.datepicker._datepickerShowing)switch(t.keyCode){case 9:e.datepicker._hideDatepicker(),r=!1;break;case 13:return s=e("td."+e.datepicker._dayOverClass+":not(."+e.datepicker._currentClass+")",n.dpDiv),s[0]&&e.datepicker._selectDay(t.target,n.selectedMonth,n.selectedYear,s[0]),i=e.datepicker._get(n,"onSelect"),i?(a=e.datepicker._formatDate(n),i.apply(n.input?n.input[0]:null,[a,n])):e.datepicker._hideDatepicker(),!1;case 27:e.datepicker._hideDatepicker();break;case 33:e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(n,"stepBigMonths"):-e.datepicker._get(n,"stepMonths"),"M");break;case 34:e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(n,"stepBigMonths"):+e.datepicker._get(n,"stepMonths"),"M");break;case 35:(t.ctrlKey||t.metaKey)&&e.datepicker._clearDate(t.target),r=t.ctrlKey||t.metaKey;break;case 36:(t.ctrlKey||t.metaKey)&&e.datepicker._gotoToday(t.target),r=t.ctrlKey||t.metaKey;break;case 37:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,o?1:-1,"D"),r=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(n,"stepBigMonths"):-e.datepicker._get(n,"stepMonths"),"M");break;case 38:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,-7,"D"),r=t.ctrlKey||t.metaKey;break;case 39:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,o?-1:1,"D"),r=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(n,"stepBigMonths"):+e.datepicker._get(n,"stepMonths"),"M");break;case 40:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,7,"D"),r=t.ctrlKey||t.metaKey;break;default:r=!1}else 36===t.keyCode&&t.ctrlKey?e.datepicker._showDatepicker(this):r=!1;r&&(t.preventDefault(),t.stopPropagation())},_doKeyPress:function(i){var a,s,n=e.datepicker._getInst(i.target);return e.datepicker._get(n,"constrainInput")?(a=e.datepicker._possibleChars(e.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==i.charCode?i.keyCode:i.charCode),i.ctrlKey||i.metaKey||" ">s||!a||a.indexOf(s)>-1):t},_doKeyUp:function(t){var i,a=e.datepicker._getInst(t.target);if(a.input.val()!==a.lastVal)try{i=e.datepicker.parseDate(e.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,e.datepicker._getFormatConfig(a)),i&&(e.datepicker._setDateFromField(a),e.datepicker._updateAlternate(a),e.datepicker._updateDatepicker(a))}catch(s){}return!0},_showDatepicker:function(t){if(t=t.target||t,"input"!==t.nodeName.toLowerCase()&&(t=e("input",t.parentNode)[0]),!e.datepicker._isDisabledDatepicker(t)&&e.datepicker._lastInput!==t){var i,a,n,r,o,u,c;i=e.datepicker._getInst(t),e.datepicker._curInst&&e.datepicker._curInst!==i&&(e.datepicker._curInst.dpDiv.stop(!0,!0),i&&e.datepicker._datepickerShowing&&e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])),a=e.datepicker._get(i,"beforeShow"),n=a?a.apply(t,[t,i]):{},n!==!1&&(s(i.settings,n),i.lastVal=null,e.datepicker._lastInput=t,e.datepicker._setDateFromField(i),e.datepicker._inDialog&&(t.value=""),e.datepicker._pos||(e.datepicker._pos=e.datepicker._findPos(t),e.datepicker._pos[1]+=t.offsetHeight),r=!1,e(t).parents().each(function(){return r|="fixed"===e(this).css("position"),!r}),o={left:e.datepicker._pos[0],top:e.datepicker._pos[1]},e.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),e.datepicker._updateDatepicker(i),o=e.datepicker._checkOffset(i,o,r),i.dpDiv.css({position:e.datepicker._inDialog&&e.blockUI?"static":r?"fixed":"absolute",display:"none",left:o.left+"px",top:o.top+"px"}),i.inline||(u=e.datepicker._get(i,"showAnim"),c=e.datepicker._get(i,"duration"),i.dpDiv.zIndex(e(t).zIndex()+1),e.datepicker._datepickerShowing=!0,e.effects&&e.effects.effect[u]?i.dpDiv.show(u,e.datepicker._get(i,"showOptions"),c):i.dpDiv[u||"show"](u?c:null),e.datepicker._shouldFocusInput(i)&&i.input.focus(),e.datepicker._curInst=i))}},_updateDatepicker:function(t){this.maxRows=4,n=t,t.dpDiv.empty().append(this._generateHTML(t)),this._attachHandlers(t),t.dpDiv.find("."+this._dayOverClass+" a").mouseover();var i,a=this._getNumberOfMonths(t),s=a[1],r=17;t.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),s>1&&t.dpDiv.addClass("ui-datepicker-multi-"+s).css("width",r*s+"em"),t.dpDiv[(1!==a[0]||1!==a[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),t.dpDiv[(this._get(t,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),t===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(t)&&t.input.focus(),t.yearshtml&&(i=t.yearshtml,setTimeout(function(){i===t.yearshtml&&t.yearshtml&&t.dpDiv.find("select.ui-datepicker-year:first").replaceWith(t.yearshtml),i=t.yearshtml=null},0))},_shouldFocusInput:function(e){return e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&!e.input.is(":focus")},_checkOffset:function(t,i,a){var s=t.dpDiv.outerWidth(),n=t.dpDiv.outerHeight(),r=t.input?t.input.outerWidth():0,o=t.input?t.input.outerHeight():0,u=document.documentElement.clientWidth+(a?0:e(document).scrollLeft()),c=document.documentElement.clientHeight+(a?0:e(document).scrollTop());return i.left-=this._get(t,"isRTL")?s-r:0,i.left-=a&&i.left===t.input.offset().left?e(document).scrollLeft():0,i.top-=a&&i.top===t.input.offset().top+o?e(document).scrollTop():0,i.left-=Math.min(i.left,i.left+s>u&&u>s?Math.abs(i.left+s-u):0),i.top-=Math.min(i.top,i.top+n>c&&c>n?Math.abs(n+o):0),i},_findPos:function(t){for(var i,a=this._getInst(t),s=this._get(a,"isRTL");t&&("hidden"===t.type||1!==t.nodeType||e.expr.filters.hidden(t));)t=t[s?"previousSibling":"nextSibling"];return i=e(t).offset(),[i.left,i.top]},_hideDatepicker:function(t){var i,a,s,n,o=this._curInst;!o||t&&o!==e.data(t,r)||this._datepickerShowing&&(i=this._get(o,"showAnim"),a=this._get(o,"duration"),s=function(){e.datepicker._tidyDialog(o)},e.effects&&(e.effects.effect[i]||e.effects[i])?o.dpDiv.hide(i,e.datepicker._get(o,"showOptions"),a,s):o.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?a:null,s),i||s(),this._datepickerShowing=!1,n=this._get(o,"onClose"),n&&n.apply(o.input?o.input[0]:null,[o.input?o.input.val():"",o]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),e.blockUI&&(e.unblockUI(),e("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(t){if(e.datepicker._curInst){var i=e(t.target),a=e.datepicker._getInst(i[0]);(i[0].id!==e.datepicker._mainDivId&&0===i.parents("#"+e.datepicker._mainDivId).length&&!i.hasClass(e.datepicker.markerClassName)&&!i.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&(!e.datepicker._inDialog||!e.blockUI)||i.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==a)&&e.datepicker._hideDatepicker()}},_adjustDate:function(t,i,a){var s=e(t),n=this._getInst(s[0]);this._isDisabledDatepicker(s[0])||(this._adjustInstDate(n,i+("M"===a?this._get(n,"showCurrentAtPos"):0),a),this._updateDatepicker(n))},_gotoToday:function(t){var i,a=e(t),s=this._getInst(a[0]);this._get(s,"gotoCurrent")&&s.currentDay?(s.selectedDay=s.currentDay,s.drawMonth=s.selectedMonth=s.currentMonth,s.drawYear=s.selectedYear=s.currentYear):(i=new Date,s.selectedDay=i.getDate(),s.drawMonth=s.selectedMonth=i.getMonth(),s.drawYear=s.selectedYear=i.getFullYear()),this._notifyChange(s),this._adjustDate(a)},_selectMonthYear:function(t,i,a){var s=e(t),n=this._getInst(s[0]);n["selected"+("M"===a?"Month":"Year")]=n["draw"+("M"===a?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(n),this._adjustDate(s)},_selectDay:function(t,i,a,s){var n,r=e(t);e(s).hasClass(this._unselectableClass)||this._isDisabledDatepicker(r[0])||(n=this._getInst(r[0]),n.selectedDay=n.currentDay=e("a",s).html(),n.selectedMonth=n.currentMonth=i,n.selectedYear=n.currentYear=a,this._selectDate(t,this._formatDate(n,n.currentDay,n.currentMonth,n.currentYear)))},_clearDate:function(t){var i=e(t);this._selectDate(i,"")},_selectDate:function(t,i){var a,s=e(t),n=this._getInst(s[0]);i=null!=i?i:this._formatDate(n),n.input&&n.input.val(i),this._updateAlternate(n),a=this._get(n,"onSelect"),a?a.apply(n.input?n.input[0]:null,[i,n]):n.input&&n.input.trigger("change"),n.inline?this._updateDatepicker(n):(this._hideDatepicker(),this._lastInput=n.input[0],"object"!=typeof n.input[0]&&n.input.focus(),this._lastInput=null)},_updateAlternate:function(t){var i,a,s,n=this._get(t,"altField");n&&(i=this._get(t,"altFormat")||this._get(t,"dateFormat"),a=this._getDate(t),s=this.formatDate(i,a,this._getFormatConfig(t)),e(n).each(function(){e(this).val(s)}))},noWeekends:function(e){var t=e.getDay();return[t>0&&6>t,""]},iso8601Week:function(e){var t,i=new Date(e.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),t=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((t-i)/864e5)/7)+1},parseDate:function(i,a,s){if(null==i||null==a)throw"Invalid arguments";if(a="object"==typeof a?""+a:a+"",""===a)return null;var n,r,o,u,c=0,h=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,l="string"!=typeof h?h:(new Date).getFullYear()%100+parseInt(h,10),d=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,p=(s?s.dayNames:null)||this._defaults.dayNames,g=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,m=(s?s.monthNames:null)||this._defaults.monthNames,f=-1,_=-1,v=-1,k=-1,y=!1,b=function(e){var t=i.length>n+1&&i.charAt(n+1)===e;return t&&n++,t},D=function(e){var t=b(e),i="@"===e?14:"!"===e?20:"y"===e&&t?4:"o"===e?3:2,s=RegExp("^\\d{1,"+i+"}"),n=a.substring(c).match(s);if(!n)throw"Missing number at position "+c;return c+=n[0].length,parseInt(n[0],10)},w=function(i,s,n){var r=-1,o=e.map(b(i)?n:s,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)});if(e.each(o,function(e,i){var s=i[1];return a.substr(c,s.length).toLowerCase()===s.toLowerCase()?(r=i[0],c+=s.length,!1):t}),-1!==r)return r+1;throw"Unknown name at position "+c},M=function(){if(a.charAt(c)!==i.charAt(n))throw"Unexpected literal at position "+c;c++};for(n=0;i.length>n;n++)if(y)"'"!==i.charAt(n)||b("'")?M():y=!1;else switch(i.charAt(n)){case"d":v=D("d");break;case"D":w("D",d,p);break;case"o":k=D("o");break;case"m":_=D("m");break;case"M":_=w("M",g,m);break;case"y":f=D("y");break;case"@":u=new Date(D("@")),f=u.getFullYear(),_=u.getMonth()+1,v=u.getDate();break;case"!":u=new Date((D("!")-this._ticksTo1970)/1e4),f=u.getFullYear(),_=u.getMonth()+1,v=u.getDate();break;case"'":b("'")?M():y=!0;break;default:M()}if(a.length>c&&(o=a.substr(c),!/^\s+/.test(o)))throw"Extra/unparsed characters found in date: "+o;if(-1===f?f=(new Date).getFullYear():100>f&&(f+=(new Date).getFullYear()-(new Date).getFullYear()%100+(l>=f?0:-100)),k>-1)for(_=1,v=k;;){if(r=this._getDaysInMonth(f,_-1),r>=v)break;_++,v-=r}if(u=this._daylightSavingAdjust(new Date(f,_-1,v)),u.getFullYear()!==f||u.getMonth()+1!==_||u.getDate()!==v)throw"Invalid date";return u},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(e,t,i){if(!t)return"";var a,s=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,n=(i?i.dayNames:null)||this._defaults.dayNames,r=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,o=(i?i.monthNames:null)||this._defaults.monthNames,u=function(t){var i=e.length>a+1&&e.charAt(a+1)===t;return i&&a++,i},c=function(e,t,i){var a=""+t;if(u(e))for(;i>a.length;)a="0"+a;return a},h=function(e,t,i,a){return u(e)?a[t]:i[t]},l="",d=!1;if(t)for(a=0;e.length>a;a++)if(d)"'"!==e.charAt(a)||u("'")?l+=e.charAt(a):d=!1;else switch(e.charAt(a)){case"d":l+=c("d",t.getDate(),2);break;case"D":l+=h("D",t.getDay(),s,n);break;case"o":l+=c("o",Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()-new Date(t.getFullYear(),0,0).getTime())/864e5),3);break;case"m":l+=c("m",t.getMonth()+1,2);break;case"M":l+=h("M",t.getMonth(),r,o);break;case"y":l+=u("y")?t.getFullYear():(10>t.getYear()%100?"0":"")+t.getYear()%100;break;case"@":l+=t.getTime();break;case"!":l+=1e4*t.getTime()+this._ticksTo1970;break;case"'":u("'")?l+="'":d=!0;break;default:l+=e.charAt(a)}return l},_possibleChars:function(e){var t,i="",a=!1,s=function(i){var a=e.length>t+1&&e.charAt(t+1)===i;return a&&t++,a};for(t=0;e.length>t;t++)if(a)"'"!==e.charAt(t)||s("'")?i+=e.charAt(t):a=!1;else switch(e.charAt(t)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":s("'")?i+="'":a=!0;break;default:i+=e.charAt(t)}return i},_get:function(e,i){return e.settings[i]!==t?e.settings[i]:this._defaults[i]},_setDateFromField:function(e,t){if(e.input.val()!==e.lastVal){var i=this._get(e,"dateFormat"),a=e.lastVal=e.input?e.input.val():null,s=this._getDefaultDate(e),n=s,r=this._getFormatConfig(e);try{n=this.parseDate(i,a,r)||s}catch(o){a=t?"":a}e.selectedDay=n.getDate(),e.drawMonth=e.selectedMonth=n.getMonth(),e.drawYear=e.selectedYear=n.getFullYear(),e.currentDay=a?n.getDate():0,e.currentMonth=a?n.getMonth():0,e.currentYear=a?n.getFullYear():0,this._adjustInstDate(e)}},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(t,i,a){var s=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},n=function(i){try{return e.datepicker.parseDate(e.datepicker._get(t,"dateFormat"),i,e.datepicker._getFormatConfig(t))}catch(a){}for(var s=(i.toLowerCase().match(/^c/)?e.datepicker._getDate(t):null)||new Date,n=s.getFullYear(),r=s.getMonth(),o=s.getDate(),u=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,c=u.exec(i);c;){switch(c[2]||"d"){case"d":case"D":o+=parseInt(c[1],10);break;case"w":case"W":o+=7*parseInt(c[1],10);break;case"m":case"M":r+=parseInt(c[1],10),o=Math.min(o,e.datepicker._getDaysInMonth(n,r));break;case"y":case"Y":n+=parseInt(c[1],10),o=Math.min(o,e.datepicker._getDaysInMonth(n,r))}c=u.exec(i)}return new Date(n,r,o)},r=null==i||""===i?a:"string"==typeof i?n(i):"number"==typeof i?isNaN(i)?a:s(i):new Date(i.getTime());return r=r&&"Invalid Date"==""+r?a:r,r&&(r.setHours(0),r.setMinutes(0),r.setSeconds(0),r.setMilliseconds(0)),this._daylightSavingAdjust(r)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,i){var a=!t,s=e.selectedMonth,n=e.selectedYear,r=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=r.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=r.getMonth(),e.drawYear=e.selectedYear=e.currentYear=r.getFullYear(),s===e.selectedMonth&&n===e.selectedYear||i||this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(a?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&""===e.input.val()?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(t){var i=this._get(t,"stepMonths"),a="#"+t.id.replace(/\\\\/g,"\\");t.dpDiv.find("[data-handler]").map(function(){var t={prev:function(){e.datepicker._adjustDate(a,-i,"M")},next:function(){e.datepicker._adjustDate(a,+i,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(a)},selectDay:function(){return e.datepicker._selectDay(a,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return e.datepicker._selectMonthYear(a,this,"M"),!1},selectYear:function(){return e.datepicker._selectMonthYear(a,this,"Y"),!1}};e(this).bind(this.getAttribute("data-event"),t[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t,i,a,s,n,r,o,u,c,h,l,d,p,g,m,f,_,v,k,y,b,D,w,M,C,x,I,N,T,A,E,S,Y,F,P,O,j,K,R,H=new Date,W=this._daylightSavingAdjust(new Date(H.getFullYear(),H.getMonth(),H.getDate())),L=this._get(e,"isRTL"),U=this._get(e,"showButtonPanel"),B=this._get(e,"hideIfNoPrevNext"),z=this._get(e,"navigationAsDateFormat"),q=this._getNumberOfMonths(e),G=this._get(e,"showCurrentAtPos"),J=this._get(e,"stepMonths"),Q=1!==q[0]||1!==q[1],V=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),$=this._getMinMaxDate(e,"min"),X=this._getMinMaxDate(e,"max"),Z=e.drawMonth-G,et=e.drawYear;if(0>Z&&(Z+=12,et--),X)for(t=this._daylightSavingAdjust(new Date(X.getFullYear(),X.getMonth()-q[0]*q[1]+1,X.getDate())),t=$&&$>t?$:t;this._daylightSavingAdjust(new Date(et,Z,1))>t;)Z--,0>Z&&(Z=11,et--);for(e.drawMonth=Z,e.drawYear=et,i=this._get(e,"prevText"),i=z?this.formatDate(i,this._daylightSavingAdjust(new Date(et,Z-J,1)),this._getFormatConfig(e)):i,a=this._canAdjustMonth(e,-1,et,Z)?""+i+"":B?"":""+i+"",s=this._get(e,"nextText"),s=z?this.formatDate(s,this._daylightSavingAdjust(new Date(et,Z+J,1)),this._getFormatConfig(e)):s,n=this._canAdjustMonth(e,1,et,Z)?""+s+"":B?"":""+s+"",r=this._get(e,"currentText"),o=this._get(e,"gotoCurrent")&&e.currentDay?V:W,r=z?this.formatDate(r,o,this._getFormatConfig(e)):r,u=e.inline?"":"",c=U?"
        "+(L?u:"")+(this._isInRange(e,o)?"":"")+(L?"":u)+"
        ":"",h=parseInt(this._get(e,"firstDay"),10),h=isNaN(h)?0:h,l=this._get(e,"showWeek"),d=this._get(e,"dayNames"),p=this._get(e,"dayNamesMin"),g=this._get(e,"monthNames"),m=this._get(e,"monthNamesShort"),f=this._get(e,"beforeShowDay"),_=this._get(e,"showOtherMonths"),v=this._get(e,"selectOtherMonths"),k=this._getDefaultDate(e),y="",D=0;q[0]>D;D++){for(w="",this.maxRows=4,M=0;q[1]>M;M++){if(C=this._daylightSavingAdjust(new Date(et,Z,e.selectedDay)),x=" ui-corner-all",I="",Q){if(I+="
        "}for(I+="
        "+(/all|left/.test(x)&&0===D?L?n:a:"")+(/all|right/.test(x)&&0===D?L?a:n:"")+this._generateMonthYearHeader(e,Z,et,$,X,D>0||M>0,g,m)+"
        "+"",N=l?"":"",b=0;7>b;b++)T=(b+h)%7,N+="=5?" class='ui-datepicker-week-end'":"")+">"+""+p[T]+"";for(I+=N+"",A=this._getDaysInMonth(et,Z),et===e.selectedYear&&Z===e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,A)),E=(this._getFirstDayOfMonth(et,Z)-h+7)%7,S=Math.ceil((E+A)/7),Y=Q?this.maxRows>S?this.maxRows:S:S,this.maxRows=Y,F=this._daylightSavingAdjust(new Date(et,Z,1-E)),P=0;Y>P;P++){for(I+="",O=l?"":"",b=0;7>b;b++)j=f?f.apply(e.input?e.input[0]:null,[F]):[!0,""],K=F.getMonth()!==Z,R=K&&!v||!j[0]||$&&$>F||X&&F>X,O+="",F.setDate(F.getDate()+1),F=this._daylightSavingAdjust(F);I+=O+""}Z++,Z>11&&(Z=0,et++),I+="
        "+this._get(e,"weekHeader")+"
        "+this._get(e,"calculateWeek")(F)+""+(K&&!_?" ":R?""+F.getDate()+"":""+F.getDate()+"")+"
        "+(Q?"
        "+(q[0]>0&&M===q[1]-1?"
        ":""):""),w+=I}y+=w}return y+=c,e._keyEvent=!1,y},_generateMonthYearHeader:function(e,t,i,a,s,n,r,o){var u,c,h,l,d,p,g,m,f=this._get(e,"changeMonth"),_=this._get(e,"changeYear"),v=this._get(e,"showMonthAfterYear"),k="
        ",y="";if(n||!f)y+=""+r[t]+"";else{for(u=a&&a.getFullYear()===i,c=s&&s.getFullYear()===i,y+=""}if(v||(k+=y+(!n&&f&&_?"":" ")),!e.yearshtml)if(e.yearshtml="",n||!_)k+=""+i+"";else{for(l=this._get(e,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(e){var t=e.match(/c[+\-].*/)?i+parseInt(e.substring(1),10):e.match(/[+\-].*/)?d+parseInt(e,10):parseInt(e,10); +return isNaN(t)?d:t},g=p(l[0]),m=Math.max(g,p(l[1]||"")),g=a?Math.max(g,a.getFullYear()):g,m=s?Math.min(m,s.getFullYear()):m,e.yearshtml+="",k+=e.yearshtml,e.yearshtml=null}return k+=this._get(e,"yearSuffix"),v&&(k+=(!n&&f&&_?"":" ")+y),k+="
        "},_adjustInstDate:function(e,t,i){var a=e.drawYear+("Y"===i?t:0),s=e.drawMonth+("M"===i?t:0),n=Math.min(e.selectedDay,this._getDaysInMonth(a,s))+("D"===i?t:0),r=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(a,s,n)));e.selectedDay=r.getDate(),e.drawMonth=e.selectedMonth=r.getMonth(),e.drawYear=e.selectedYear=r.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(e)},_restrictMinMax:function(e,t){var i=this._getMinMaxDate(e,"min"),a=this._getMinMaxDate(e,"max"),s=i&&i>t?i:t;return a&&s>a?a:s},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return null==t?[1,1]:"number"==typeof t?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return new Date(e,t,1).getDay()},_canAdjustMonth:function(e,t,i,a){var s=this._getNumberOfMonths(e),n=this._daylightSavingAdjust(new Date(i,a+(0>t?t:s[0]*s[1]),1));return 0>t&&n.setDate(this._getDaysInMonth(n.getFullYear(),n.getMonth())),this._isInRange(e,n)},_isInRange:function(e,t){var i,a,s=this._getMinMaxDate(e,"min"),n=this._getMinMaxDate(e,"max"),r=null,o=null,u=this._get(e,"yearRange");return u&&(i=u.split(":"),a=(new Date).getFullYear(),r=parseInt(i[0],10),o=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(r+=a),i[1].match(/[+\-].*/)&&(o+=a)),(!s||t.getTime()>=s.getTime())&&(!n||t.getTime()<=n.getTime())&&(!r||t.getFullYear()>=r)&&(!o||o>=t.getFullYear())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t="string"!=typeof t?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,i,a){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var s=t?"object"==typeof t?t:this._daylightSavingAdjust(new Date(a,i,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),s,this._getFormatConfig(e))}}),e.fn.datepicker=function(t){if(!this.length)return this;e.datepicker.initialized||(e(document).mousedown(e.datepicker._checkExternalClick),e.datepicker.initialized=!0),0===e("#"+e.datepicker._mainDivId).length&&e("body").append(e.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof t||"isDisabled"!==t&&"getDate"!==t&&"widget"!==t?"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof t?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this].concat(i)):e.datepicker._attachDatepicker(this,t)}):e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i))},e.datepicker=new i,e.datepicker.initialized=!1,e.datepicker.uuid=(new Date).getTime(),e.datepicker.version="1.10.4"})(jQuery);(function(e){var t={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};e.widget("ui.dialog",{version:"1.10.4",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var i=e(this).css(t).offset().top;0>i&&e(this).css("top",t.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&e.fn.draggable&&this._makeDraggable(),this.options.resizable&&e.fn.resizable&&this._makeResizable(),this._isOpen=!1},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var t=this.options.appendTo;return t&&(t.jquery||t.nodeType)?e(t):this.document.find(t||"body").eq(0)},_destroy:function(){var e,t=this.originalPosition;this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},disable:e.noop,enable:e.noop,close:function(t){var i,a=this;if(this._isOpen&&this._trigger("beforeClose",t)!==!1){if(this._isOpen=!1,this._destroyOverlay(),!this.opener.filter(":focusable").focus().length)try{i=this.document[0].activeElement,i&&"body"!==i.nodeName.toLowerCase()&&e(i).blur()}catch(s){}this._hide(this.uiDialog,this.options.hide,function(){a._trigger("close",t)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,t){var i=!!this.uiDialog.nextAll(":visible").insertBefore(this.uiDialog).length;return i&&!t&&this._trigger("focus",e),i},open:function(){var t=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),undefined):(this._isOpen=!0,this.opener=e(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this._show(this.uiDialog,this.options.show,function(){t._focusTabbable(),t._trigger("focus")}),this._trigger("open"),undefined)},_focusTabbable:function(){var e=this.element.find("[autofocus]");e.length||(e=this.element.find(":tabbable")),e.length||(e=this.uiDialogButtonPane.find(":tabbable")),e.length||(e=this.uiDialogTitlebarClose.filter(":tabbable")),e.length||(e=this.uiDialog),e.eq(0).focus()},_keepFocus:function(t){function i(){var t=this.document[0].activeElement,i=this.uiDialog[0]===t||e.contains(this.uiDialog[0],t);i||this._focusTabbable()}t.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=e("
        ").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._on(this.uiDialog,{keydown:function(t){if(this.options.closeOnEscape&&!t.isDefaultPrevented()&&t.keyCode&&t.keyCode===e.ui.keyCode.ESCAPE)return t.preventDefault(),this.close(t),undefined;if(t.keyCode===e.ui.keyCode.TAB){var i=this.uiDialog.find(":tabbable"),a=i.filter(":first"),s=i.filter(":last");t.target!==s[0]&&t.target!==this.uiDialog[0]||t.shiftKey?t.target!==a[0]&&t.target!==this.uiDialog[0]||!t.shiftKey||(s.focus(1),t.preventDefault()):(a.focus(1),t.preventDefault())}},mousedown:function(e){this._moveToTop(e)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var t;this.uiDialogTitlebar=e("
        ").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog),this._on(this.uiDialogTitlebar,{mousedown:function(t){e(t.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.focus()}}),this.uiDialogTitlebarClose=e("").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(e){e.preventDefault(),this.close(e)}}),t=e("").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(t),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(e){this.options.title||e.html(" "),e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=e("
        ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=e("
        ").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var t=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),e.isEmptyObject(i)||e.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),undefined):(e.each(i,function(i,a){var s,n;a=e.isFunction(a)?{click:a,text:i}:a,a=e.extend({type:"button"},a),s=a.click,a.click=function(){s.apply(t.element[0],arguments)},n={icons:a.icons,text:a.showText},delete a.icons,delete a.showText,e("",a).button(n).appendTo(t.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),undefined)},_makeDraggable:function(){function t(e){return{position:e.position,offset:e.offset}}var i=this,a=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(a,s){e(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",a,t(s))},drag:function(e,a){i._trigger("drag",e,t(a))},stop:function(s,n){a.position=[n.position.left-i.document.scrollLeft(),n.position.top-i.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",s,t(n))}})},_makeResizable:function(){function t(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}var i=this,a=this.options,s=a.resizable,n=this.uiDialog.css("position"),r="string"==typeof s?s:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:a.maxWidth,maxHeight:a.maxHeight,minWidth:a.minWidth,minHeight:this._minHeight(),handles:r,start:function(a,s){e(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",a,t(s))},resize:function(e,a){i._trigger("resize",e,t(a))},stop:function(s,n){a.height=e(this).height(),a.width=e(this).width(),e(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",s,t(n))}}).css("position",n)},_minHeight:function(){var e=this.options;return"auto"===e.height?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");e||this.uiDialog.show(),this.uiDialog.position(this.options.position),e||this.uiDialog.hide()},_setOptions:function(a){var s=this,n=!1,r={};e.each(a,function(e,a){s._setOption(e,a),e in t&&(n=!0),e in i&&(r[e]=a)}),n&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",r)},_setOption:function(e,t){var i,a,s=this.uiDialog;"dialogClass"===e&&s.removeClass(this.options.dialogClass).addClass(t),"disabled"!==e&&(this._super(e,t),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:""+t}),"draggable"===e&&(i=s.is(":data(ui-draggable)"),i&&!t&&s.draggable("destroy"),!i&&t&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(a=s.is(":data(ui-resizable)"),a&&!t&&s.resizable("destroy"),a&&"string"==typeof t&&s.resizable("option","handles",t),a||t===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var e,t,i,a=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),a.minWidth>a.width&&(a.width=a.minWidth),e=this.uiDialog.css({height:"auto",width:a.width}).outerHeight(),t=Math.max(0,a.minHeight-e),i="number"==typeof a.maxHeight?Math.max(0,a.maxHeight-e):"none","auto"===a.height?this.element.css({minHeight:t,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,a.height-e)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=e(this);return e("
        ").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return e(t.target).closest(".ui-dialog").length?!0:!!e(t.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var t=this,i=this.widgetFullName;e.ui.dialog.overlayInstances||this._delay(function(){e.ui.dialog.overlayInstances&&this.document.bind("focusin.dialog",function(a){t._allowInteraction(a)||(a.preventDefault(),e(".ui-dialog:visible:last .ui-dialog-content").data(i)._focusTabbable())})}),this.overlay=e("
        ").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),e.ui.dialog.overlayInstances++}},_destroyOverlay:function(){this.options.modal&&this.overlay&&(e.ui.dialog.overlayInstances--,e.ui.dialog.overlayInstances||this.document.unbind("focusin.dialog"),this.overlay.remove(),this.overlay=null)}}),e.ui.dialog.overlayInstances=0,e.uiBackCompat!==!1&&e.widget("ui.dialog",e.ui.dialog,{_position:function(){var t,i=this.options.position,a=[],s=[0,0];i?(("string"==typeof i||"object"==typeof i&&"0"in i)&&(a=i.split?i.split(" "):[i[0],i[1]],1===a.length&&(a[1]=a[0]),e.each(["left","top"],function(e,t){+a[e]===a[e]&&(s[e]=a[e],a[e]=t)}),i={my:a[0]+(0>s[0]?s[0]:"+"+s[0])+" "+a[1]+(0>s[1]?s[1]:"+"+s[1]),at:a.join(" ")}),i=e.extend({},e.ui.dialog.prototype.options.position,i)):i=e.ui.dialog.prototype.options.position,t=this.uiDialog.is(":visible"),t||this.uiDialog.show(),this.uiDialog.position(i),t||this.uiDialog.hide()}})})(jQuery);(function(t){t.widget("ui.menu",{version:"1.10.4",defaultElement:"
          ",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,t.proxy(function(t){this.options.disabled&&t.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(t){t.preventDefault()},"click .ui-state-disabled > a":function(t){t.preventDefault()},"click .ui-menu-item:has(a)":function(e){var i=t(e.target).closest(".ui-menu-item");!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i=t(e.currentTarget);i.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(e,i)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.children(".ui-menu-item").eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){t(e.target).closest(".ui-menu").length||this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){function i(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var s,n,a,o,r,l=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:l=!1,n=this.previousFilter||"",a=String.fromCharCode(e.keyCode),o=!1,clearTimeout(this.filterTimer),a===n?o=!0:a=n+a,r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())}),s=o&&-1!==s.index(this.active.next())?this.active.nextAll(".ui-menu-item"):s,s.length||(a=String.fromCharCode(e.keyCode),r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())})),s.length?(this.focus(e,s),s.length>1?(this.previousFilter=a,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}l&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i=this.options.icons.submenu,s=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),s.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),s=e.prev("a"),n=t("").addClass("ui-menu-icon ui-icon "+i).data("ui-menu-submenu-carat",!0);s.attr("aria-haspopup","true").prepend(n),e.attr("aria-labelledby",s.attr("id"))}),e=s.add(this.element),e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),e.children(":not(.ui-menu-item)").each(function(){var e=t(this);/[^\-\u2014\u2013\s]/.test(e.text())||e.addClass("ui-widget-content ui-menu-divider")}),e.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),this._super(t,e)},focus:function(t,e){var i,s;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=e.height(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.children(".ui-menu-item")[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())),undefined):(this.next(e),undefined)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item").first())),undefined):(this.next(e),undefined)},_hasScroll:function(){return this.element.outerHeight()
        ").appendTo(this.element),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(t){return t===e?this.options.value:(this.options.value=this._constrainedValue(t),this._refreshValue(),e)},_constrainedValue:function(t){return t===e&&(t=this.options.value),this.indeterminate=t===!1,"number"!=typeof t&&(t=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var e=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(i.toFixed(0)+"%"),this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=t("
        ").appendTo(this.valueDiv))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":e}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),e===this.options.max&&this._trigger("complete")}})})(jQuery);(function(t){var e=5;t.widget("ui.slider",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"),this._refresh(),this._setOption("disabled",this.options.disabled),this._animateOff=!1},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var e,i,s=this.options,n=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),a="",o=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),e=n.length;i>e;e++)o.push(a);this.handles=n.add(t(o.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e)})},_createRange:function(){var e=this.options,i="";e.range?(e.range===!0&&(e.values?e.values.length&&2!==e.values.length?e.values=[e.values[0],e.values[0]]:t.isArray(e.values)&&(e.values=e.values.slice(0)):e.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?this.range.removeClass("ui-slider-range-min ui-slider-range-max").css({left:"",bottom:""}):(this.range=t("
        ").appendTo(this.element),i="ui-slider-range ui-widget-header ui-corner-all"),this.range.addClass(i+("min"===e.range||"max"===e.range?" ui-slider-range-"+e.range:""))):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){var t=this.handles.add(this.range).filter("a");this._off(t),this._on(t,this._handleEvents),this._hoverable(t),this._focusable(t)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(e){var i,s,n,a,o,r,l,h,u=this,c=this.options;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(s-u.values(e));(n>i||n===i&&(e===u._lastChangedValue||u.values(e)===c.min))&&(n=i,a=t(this),o=e)}),r=this._start(e,o),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,a.addClass("ui-state-active").focus(),l=a.offset(),h=!t(e.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=h?{left:0,top:0}:{left:e.pageX-l.left-a.width()/2,top:e.pageY-l.top-a.height()/2-(parseInt(a.css("borderTopWidth"),10)||0)-(parseInt(a.css("borderBottomWidth"),10)||0)+(parseInt(a.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,o,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,s,n,a;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/e,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),a=this._valueMin()+s*n,this._trimAlignValue(a)},_start:function(t,e){var i={handle:this.handles[e],value:this.value()};return this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("start",t,i)},_slide:function(t,e,i){var s,n,a;this.options.values&&this.options.values.length?(s=this.values(e?0:1),2===this.options.values.length&&this.options.range===!0&&(0===e&&i>s||1===e&&s>i)&&(i=s),i!==this.values(e)&&(n=this.values(),n[e]=i,a=this._trigger("slide",t,{handle:this.handles[e],value:i,values:n}),s=this.values(e?0:1),a!==!1&&this.values(e,i))):i!==this.value()&&(a=this._trigger("slide",t,{handle:this.handles[e],value:i}),a!==!1&&this.value(i))},_stop:function(t,e){var i={handle:this.handles[e],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("stop",t,i)},_change:function(t,e){if(!this._keySliding&&!this._mouseSliding){var i={handle:this.handles[e],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._lastChangedValue=e,this._trigger("change",t,i)}},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),undefined):this._value()},values:function(e,i){var s,n,a;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),undefined;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(e):this.value();for(s=this.options.values,n=arguments[0],a=0;s.length>a;a+=1)s[a]=this._trimAlignValue(n[a]),this._change(null,a);this._refreshValue()},_setOption:function(e,i){var s,n=0;switch("range"===e&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),t.isArray(this.options.values)&&(n=this.options.values.length),t.Widget.prototype._setOption.apply(this,arguments),e){case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=0;n>s;s+=1)this._change(null,s);this._animateOff=!1;break;case"min":case"max":this._animateOff=!0,this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_value:function(){var t=this.options.value;return t=this._trimAlignValue(t)},_values:function(t){var e,i,s;if(arguments.length)return e=this.options.values[t],e=this._trimAlignValue(e);if(this.options.values&&this.options.values.length){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(t){if(this._valueMin()>=t)return this._valueMin();if(t>=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,i=(t-this._valueMin())%e,s=t-i;return 2*Math.abs(i)>=e&&(s+=i>0?e:-e),parseFloat(s.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var e,i,s,n,a,o=this.options.range,r=this.options,l=this,h=this._animateOff?!1:r.animate,u={};this.options.values&&this.options.values.length?this.handles.each(function(s){i=100*((l.values(s)-l._valueMin())/(l._valueMax()-l._valueMin())),u["horizontal"===l.orientation?"left":"bottom"]=i+"%",t(this).stop(1,1)[h?"animate":"css"](u,r.animate),l.options.range===!0&&("horizontal"===l.orientation?(0===s&&l.range.stop(1,1)[h?"animate":"css"]({left:i+"%"},r.animate),1===s&&l.range[h?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:r.animate})):(0===s&&l.range.stop(1,1)[h?"animate":"css"]({bottom:i+"%"},r.animate),1===s&&l.range[h?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:r.animate}))),e=i}):(s=this.value(),n=this._valueMin(),a=this._valueMax(),i=a!==n?100*((s-n)/(a-n)):0,u["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[h?"animate":"css"](u,r.animate),"min"===o&&"horizontal"===this.orientation&&this.range.stop(1,1)[h?"animate":"css"]({width:i+"%"},r.animate),"max"===o&&"horizontal"===this.orientation&&this.range[h?"animate":"css"]({width:100-i+"%"},{queue:!1,duration:r.animate}),"min"===o&&"vertical"===this.orientation&&this.range.stop(1,1)[h?"animate":"css"]({height:i+"%"},r.animate),"max"===o&&"vertical"===this.orientation&&this.range[h?"animate":"css"]({height:100-i+"%"},{queue:!1,duration:r.animate}))},_handleEvents:{keydown:function(i){var s,n,a,o,r=t(i.target).data("ui-slider-handle-index");switch(i.keyCode){case t.ui.keyCode.HOME:case t.ui.keyCode.END:case t.ui.keyCode.PAGE_UP:case t.ui.keyCode.PAGE_DOWN:case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(i.preventDefault(),!this._keySliding&&(this._keySliding=!0,t(i.target).addClass("ui-state-active"),s=this._start(i,r),s===!1))return}switch(o=this.options.step,n=a=this.options.values&&this.options.values.length?this.values(r):this.value(),i.keyCode){case t.ui.keyCode.HOME:a=this._valueMin();break;case t.ui.keyCode.END:a=this._valueMax();break;case t.ui.keyCode.PAGE_UP:a=this._trimAlignValue(n+(this._valueMax()-this._valueMin())/e);break;case t.ui.keyCode.PAGE_DOWN:a=this._trimAlignValue(n-(this._valueMax()-this._valueMin())/e);break;case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:if(n===this._valueMax())return;a=this._trimAlignValue(n+o);break;case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(n===this._valueMin())return;a=this._trimAlignValue(n-o)}this._slide(i,r,a)},click:function(t){t.preventDefault()},keyup:function(e){var i=t(e.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(e,i),this._change(e,i),t(e.target).removeClass("ui-state-active"))}}})})(jQuery);(function(t){function e(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.widget("ui.spinner",{version:"1.10.4",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e={},i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);void 0!==n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var t=this.element[0]===this.document[0].activeElement;t||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var t=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=t.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*t.height())&&t.height()>0&&t.height(t.height()),this.options.disabled&&this.disable()},_keydown:function(e){var i=this.options,s=t.ui.keyCode;switch(e.keyCode){case s.UP:return this._repeat(null,1,e),!0;case s.DOWN:return this._repeat(null,-1,e),!0;case s.PAGE_UP:return this._repeat(null,i.page,e),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,e),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(t){return this.spinning||this._trigger("start",t)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(t,e,i){t=t||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,e,i)},t),this._spin(e*this.options.step,i)},_spin:function(t,e){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+t*this._increment(this.counter)),this.spinning&&this._trigger("spin",e,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(e){var i=this.options.incremental;return i?t.isFunction(i)?i(e):Math.floor(e*e*e/5e4-e*e/500+17*e/200+1):1},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_adjustValue:function(t){var e,i,s=this.options;return e=null!==s.min?s.min:0,i=t-e,i=Math.round(i/s.step)*s.step,t=e+i,t=parseFloat(t.toFixed(this._precision())),null!==s.max&&t>s.max?s.max:null!==s.min&&s.min>t?s.min:t},_stop:function(t){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",t))},_setOption:function(t,e){if("culture"===t||"numberFormat"===t){var i=this._parse(this.element.val());return this.options[t]=e,this.element.val(this._format(i)),void 0}("max"===t||"min"===t||"step"===t)&&"string"==typeof e&&(e=this._parse(e)),"icons"===t&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(e.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(e.down)),this._super(t,e),"disabled"===t&&(e?(this.element.prop("disabled",!0),this.buttons.button("disable")):(this.element.prop("disabled",!1),this.buttons.button("enable")))},_setOptions:e(function(t){this._super(t),this._value(this.element.val())}),_parse:function(t){return"string"==typeof t&&""!==t&&(t=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(t,10,this.options.culture):+t),""===t||isNaN(t)?null:t},_format:function(t){return""===t?"":window.Globalize&&this.options.numberFormat?Globalize.format(t,this.options.numberFormat,this.options.culture):t},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},_value:function(t,e){var i;""!==t&&(i=this._parse(t),null!==i&&(e||(i=this._adjustValue(i)),t=this._format(i))),this.element.val(t),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:e(function(t){this._stepUp(t)}),_stepUp:function(t){this._start()&&(this._spin((t||1)*this.options.step),this._stop())},stepDown:e(function(t){this._stepDown(t)}),_stepDown:function(t){this._start()&&(this._spin((t||1)*-this.options.step),this._stop())},pageUp:e(function(t){this._stepUp((t||1)*this.options.page)}),pageDown:e(function(t){this._stepDown((t||1)*this.options.page)}),value:function(t){return arguments.length?(e(this._value).call(this,t),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}})})(jQuery);(function(t,e){function i(){return++n}function s(t){return t=t.cloneNode(!1),t.hash.length>1&&decodeURIComponent(t.href.replace(a,""))===decodeURIComponent(location.href.replace(a,""))}var n=0,a=/#.*$/;t.widget("ui.tabs",{version:"1.10.4",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var e=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var i=this.options.active,s=this.options.collapsible,n=location.hash.substring(1);return null===i&&(n&&this.tabs.each(function(s,a){return t(a).attr("aria-controls")===n?(i=s,!1):e}),null===i&&(i=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===i||-1===i)&&(i=this.tabs.length?0:!1)),i!==!1&&(i=this.tabs.index(this.tabs.eq(i)),-1===i&&(i=s?!1:0)),!s&&i===!1&&this.anchors.length&&(i=0),i},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(i){var s=t(this.document[0].activeElement).closest("li"),n=this.tabs.index(s),a=!0;if(!this._handlePageNav(i)){switch(i.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:n++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:a=!1,n--;break;case t.ui.keyCode.END:n=this.anchors.length-1;break;case t.ui.keyCode.HOME:n=0;break;case t.ui.keyCode.SPACE:return i.preventDefault(),clearTimeout(this.activating),this._activate(n),e;case t.ui.keyCode.ENTER:return i.preventDefault(),clearTimeout(this.activating),this._activate(n===this.options.active?!1:n),e;default:return}i.preventDefault(),clearTimeout(this.activating),n=this._focusNextTab(n,a),i.ctrlKey||(s.attr("aria-selected","false"),this.tabs.eq(n).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",n)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.focus())},_handlePageNav:function(i){return i.altKey&&i.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):i.altKey&&i.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):e},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).focus(),t},_setOption:function(t,i){return"active"===t?(this._activate(i),e):"disabled"===t?(this._setupDisabled(i),e):(this._super(t,i),"collapsible"===t&&(this.element.toggleClass("ui-tabs-collapsible",i),i||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(i),"heightStyle"===t&&this._setupHeightStyle(i),e)},_tabId:function(t){return t.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=t(),this.anchors.each(function(i,n){var a,o,r,h=t(n).uniqueId().attr("id"),l=t(n).closest("li"),c=l.attr("aria-controls");s(n)?(a=n.hash,o=e.element.find(e._sanitizeSelector(a))):(r=e._tabId(l),a="#"+r,o=e.element.find(a),o.length||(o=e._createPanel(r),o.insertAfter(e.panels[i-1]||e.tablist)),o.attr("aria-live","polite")),o.length&&(e.panels=e.panels.add(o)),c&&l.data("ui-tabs-aria-controls",c),l.attr({"aria-controls":a.substring(1),"aria-labelledby":h}),o.attr("aria-labelledby",h)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(e){return t("
        ").attr("id",e).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(e){t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1);for(var i,s=0;i=this.tabs[s];s++)e===!0||-1!==t.inArray(s,e)?t(i).addClass("ui-state-disabled").attr("aria-disabled","true"):t(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=e},_setupEvents:function(e){var i={click:function(t){t.preventDefault()}};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?t():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():a,newPanel:h};e.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?t():a,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),e),this._toggle(e,c))},_toggle:function(e,i){function s(){a.running=!1,a._trigger("activate",e,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr({"aria-expanded":"false","aria-hidden":"true"}),i.oldTab.attr("aria-selected","false"),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr({"aria-expanded":"true","aria-hidden":"false"}),i.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(t){return"string"==typeof t&&(t=this.anchors.index(this.anchors.filter("[href$='"+t+"']"))),t},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(i){var s=this.options.disabled;s!==!1&&(i===e?s=!1:(i=this._getIndex(i),s=t.isArray(s)?t.map(s,function(t){return t!==i?t:null}):t.map(this.tabs,function(t,e){return e!==i?e:null})),this._setupDisabled(s))},disable:function(i){var s=this.options.disabled;if(s!==!0){if(i===e)s=!0;else{if(i=this._getIndex(i),-1!==t.inArray(i,s))return;s=t.isArray(s)?t.merge([i],s).sort():[i]}this._setupDisabled(s)}},load:function(e,i){e=this._getIndex(e);var n=this,a=this.tabs.eq(e),o=a.find(".ui-tabs-anchor"),r=this._getPanelForTab(a),h={tab:a,panel:r};s(o[0])||(this.xhr=t.ajax(this._ajaxSettings(o,i,h)),this.xhr&&"canceled"!==this.xhr.statusText&&(a.addClass("ui-tabs-loading"),r.attr("aria-busy","true"),this.xhr.success(function(t){setTimeout(function(){r.html(t),n._trigger("load",i,h)},1)}).complete(function(t,e){setTimeout(function(){"abort"===e&&n.panels.stop(!1,!0),a.removeClass("ui-tabs-loading"),r.removeAttr("aria-busy"),t===n.xhr&&delete n.xhr},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href"),beforeSend:function(e,a){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:a},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}})})(jQuery);(function(t){function e(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))}function i(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")}var s=0;t.widget("ui.tooltip",{version:"1.10.4",options:{content:function(){var e=t(this).attr("title")||"";return t("").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(e,i){var s=this;return"disabled"===e?(this[i?"_disable":"_enable"](),this.options[e]=i,void 0):(this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e)}),void 0)},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s[0],e.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var e=t(this);e.is("[title]")&&e.data("ui-tooltip-title",e.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))})},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s?this._open(e,t,s):(i=s.call(t[0],function(i){t.data("ui-tooltip-open")&&n._delay(function(){e&&(e.type=o),this._open(e,t,i)})}),i&&this._open(e,t,i),void 0)},_open:function(i,s,n){function o(t){l.of=t,a.is(":hidden")||a.position(l)}var a,r,h,l=t.extend({},this.options.position);if(n){if(a=this._find(s),a.length)return a.find(".ui-tooltip-content").html(n),void 0;s.is("[title]")&&(i&&"mouseover"===i.type?s.attr("title",""):s.removeAttr("title")),a=this._tooltip(s),e(s,a.attr("id")),a.find(".ui-tooltip-content").html(n),this.options.track&&i&&/^mouse/.test(i.type)?(this._on(this.document,{mousemove:o}),o(i)):a.position(t.extend({of:s},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.show&&this.options.show.delay&&(h=this.delayedShow=setInterval(function(){a.is(":visible")&&(o(l.of),clearInterval(h))},t.fx.interval)),this._trigger("open",i,{tooltip:a}),r={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var i=t.Event(e);i.currentTarget=s[0],this.close(i,!0)}},remove:function(){this._removeTooltip(a)}},i&&"mouseover"!==i.type||(r.mouseleave="close"),i&&"focusin"!==i.type||(r.focusout="close"),this._on(!0,s,r)}},close:function(e){var s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);this.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&n.attr("title",n.data("ui-tooltip-title")),i(n),o.stop(!0),this._hide(o,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e]}),this.closing=!0,this._trigger("close",e,{tooltip:o}),this.closing=!1)},_tooltip:function(e){var i="ui-tooltip-"+s++,n=t("
        ").attr({id:i,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return t("
        ").addClass("ui-tooltip-content").appendTo(n),n.appendTo(this.document[0].body),this.tooltips[i]=e,n},_find:function(e){var i=e.data("ui-tooltip-id");return i?t("#"+i):t()},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s[0],e.close(n,!0),t("#"+i).remove(),s.data("ui-tooltip-title")&&(s.attr("title",s.data("ui-tooltip-title")),s.removeData("ui-tooltip-title"))})}})})(jQuery);(function(t,e){var i="ui-effects-";t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=h(),n=s._rgba=[];return i=i.toLowerCase(),f(l,function(t,a){var o,r=a.re.exec(i),l=r&&a.parse(r),h=a.space||"rgba";return l?(o=s[h](l),s[c[h].cache]=o[c[h].cache],n=s._rgba=o._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,a.transparent),s):a[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,l=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=h.support={},p=t("

        ")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(n,o,r,l){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(o),o=e);var u=this,d=t.type(n),p=this._rgba=[];return o!==e&&(n=[n,o,r,l],d="array"),"string"===d?this.parse(s(n)||a._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof h?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var a=s.cache;f(s.props,function(t,e){if(!u[a]&&s.to){if("alpha"===t||null==n[t])return;u[a]=s.to(u._rgba)}u[a][e.idx]=i(n[t],e,!0)}),u[a]&&0>t.inArray(null,u[a].slice(0,3))&&(u[a][3]=1,s.from&&(u._rgba=s.from(u[a])))}),this):e},is:function(t){var i=h(t),s=!0,n=this;return f(c,function(t,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),n=s._space(),a=c[n],o=0===this.alpha()?h("transparent"):this,r=o[a.cache]||a.to(o._rgba),l=r.slice();return s=s[a.cache],f(a.props,function(t,n){var a=n.idx,o=r[a],h=s[a],c=u[n.type]||{};null!==h&&(null===o?l[a]=h:(c.mod&&(h-o>c.mod/2?o+=c.mod:o-h>c.mod/2&&(o-=c.mod)),l[a]=i((h-o)*e+o,n)))}),this[n](l)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,a=t[2]/255,o=t[3],r=Math.max(s,n,a),l=Math.min(s,n,a),h=r-l,c=r+l,u=.5*c;return e=l===r?0:s===r?60*(n-a)/h+360:n===r?60*(a-s)/h+120:60*(s-n)/h+240,i=0===h?0:.5>=u?h/c:h/(2-c),[Math.round(e)%360,i,u,null==o?1:o]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],a=t[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,e+1/3)),Math.round(255*n(r,o,e)),Math.round(255*n(r,o,e-1/3)),a]},f(c,function(s,n){var a=n.props,o=n.cache,l=n.to,c=n.from;h.fn[s]=function(s){if(l&&!this[o]&&(this[o]=l(this._rgba)),s===e)return this[o].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[o].slice();return f(a,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=h(c(d)),n[o]=d,n):h(d)},f(a,function(e,i){h.fn[e]||(h.fn[e]=function(n){var a,o=t.type(n),l="alpha"===e?this._hsla?"hsla":"rgba":s,h=this[l](),c=h[i.idx];return"undefined"===o?c:("function"===o&&(n=n.call(this,c),o=t.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=c+parseFloat(a[2])*("+"===a[1]?1:-1))),h[i.idx]=n,this[l](h)))})})}),h.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var a,o,r="";if("transparent"!==n&&("string"!==t.type(n)||(a=s(n)))){if(n=h(a||n),!d.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&o&&o.style;)try{r=t.css(o,"backgroundColor"),o=o.parentNode}catch(l){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(l){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook(o),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},a=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function s(e,i){var s,n,o={};for(s in i)n=i[s],e[s]!==n&&(a[s]||(t.fx.step[s]||!isNaN(parseFloat(n)))&&(o[s]=n));return o}var n=["add","remove","toggle"],a={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(jQuery.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(e,a,o,r){var l=t.speed(a,o,r);return this.queue(function(){var a,o=t(this),r=o.attr("class")||"",h=l.children?o.find("*").addBack():o;h=h.map(function(){var e=t(this);return{el:e,start:i(this)}}),a=function(){t.each(n,function(t,i){e[i]&&o[i+"Class"](e[i])})},a(),h=h.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),o.attr("class",r),h=h.map(function(){var e=this,i=t.Deferred(),s=t.extend({},l,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,h.get()).done(function(){a(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),l.complete.call(o[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,a){return s?t.effects.animateClass.call(this,{add:i},s,n,a):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,a){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,a):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(i){return function(s,n,a,o,r){return"boolean"==typeof n||n===e?a?t.effects.animateClass.call(this,n?{add:s}:{remove:s},a,o,r):i.apply(this,arguments):t.effects.animateClass.call(this,{toggle:s},n,a,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,a){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,a)}})}(),function(){function s(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function n(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}t.extend(t.effects,{version:"1.10.4",save:function(t,e){for(var s=0;e.length>s;s++)null!==e[s]&&t.data(i+e[s],t[0].style[e[s]])},restore:function(t,s){var n,a;for(a=0;s.length>a;a++)null!==s[a]&&(n=t.data(i+s[a]),n===e&&(n=""),t.css(s[a],n))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

        ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return e.wrap(s),(e[0]===a||t.contains(e[0],a))&&t(a).focus(),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).focus()),e},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var a=e.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),t.fn.extend({effect:function(){function e(e){function s(){t.isFunction(a)&&a.call(n[0]),t.isFunction(e)&&e()}var n=t(this),a=i.complete,r=i.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),s()):o.call(n[0],i,s)}var i=s.apply(this,arguments),n=i.mode,a=i.queue,o=t.effects.effect[i.effect];return t.fx.off||!o?n?this[n](i.duration,i.complete):this.each(function(){i.complete&&i.complete.call(this)}):a===!1?this.each(e):this.queue(a||"fx",e)},show:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="show",this.effect.call(this,i)}}(t.fn.show),hide:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="hide",this.effect.call(this,i)}}(t.fn.hide),toggle:function(t){return function(e){if(n(e)||"boolean"==typeof e)return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="toggle",this.effect.call(this,i)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s}})}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}()})(jQuery);(function(t){var e=/up|down|vertical/,i=/up|left|vertical|horizontal/;t.effects.effect.blind=function(s,n){var a,o,r,l=t(this),h=["position","top","bottom","left","right","height","width"],c=t.effects.setMode(l,s.mode||"hide"),u=s.direction||"up",d=e.test(u),p=d?"height":"width",f=d?"top":"left",g=i.test(u),m={},v="show"===c;l.parent().is(".ui-effects-wrapper")?t.effects.save(l.parent(),h):t.effects.save(l,h),l.show(),a=t.effects.createWrapper(l).css({overflow:"hidden"}),o=a[p](),r=parseFloat(a.css(f))||0,m[p]=v?o:0,g||(l.css(d?"bottom":"right",0).css(d?"top":"left","auto").css({position:"absolute"}),m[f]=v?r:o+r),v&&(a.css(p,0),g||a.css(f,r+o)),a.animate(m,{duration:s.duration,easing:s.easing,queue:!1,complete:function(){"hide"===c&&l.hide(),t.effects.restore(l,h),t.effects.removeWrapper(l),n()}})}})(jQuery);(function(t){t.effects.effect.bounce=function(e,i){var s,n,a,o=t(this),r=["position","top","bottom","left","right","height","width"],l=t.effects.setMode(o,e.mode||"effect"),h="hide"===l,c="show"===l,u=e.direction||"up",d=e.distance,p=e.times||5,f=2*p+(c||h?1:0),g=e.duration/f,m=e.easing,v="up"===u||"down"===u?"top":"left",_="up"===u||"left"===u,b=o.queue(),y=b.length;for((c||h)&&r.push("opacity"),t.effects.save(o,r),o.show(),t.effects.createWrapper(o),d||(d=o["top"===v?"outerHeight":"outerWidth"]()/3),c&&(a={opacity:1},a[v]=0,o.css("opacity",0).css(v,_?2*-d:2*d).animate(a,g,m)),h&&(d/=Math.pow(2,p-1)),a={},a[v]=0,s=0;p>s;s++)n={},n[v]=(_?"-=":"+=")+d,o.animate(n,g,m).animate(a,g,m),d=h?2*d:d/2;h&&(n={opacity:0},n[v]=(_?"-=":"+=")+d,o.animate(n,g,m)),o.queue(function(){h&&o.hide(),t.effects.restore(o,r),t.effects.removeWrapper(o),i()}),y>1&&b.splice.apply(b,[1,0].concat(b.splice(y,f+1))),o.dequeue()}})(jQuery);(function(t){t.effects.effect.clip=function(e,i){var s,n,a,o=t(this),r=["position","top","bottom","left","right","height","width"],l=t.effects.setMode(o,e.mode||"hide"),h="show"===l,c=e.direction||"vertical",u="vertical"===c,d=u?"height":"width",p=u?"top":"left",f={};t.effects.save(o,r),o.show(),s=t.effects.createWrapper(o).css({overflow:"hidden"}),n="IMG"===o[0].tagName?s:o,a=n[d](),h&&(n.css(d,0),n.css(p,a/2)),f[d]=h?a:0,f[p]=h?0:a/2,n.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){h||o.hide(),t.effects.restore(o,r),t.effects.removeWrapper(o),i()}})}})(jQuery);(function(t){t.effects.effect.drop=function(e,i){var s,n=t(this),a=["position","top","bottom","left","right","opacity","height","width"],o=t.effects.setMode(n,e.mode||"hide"),r="show"===o,l=e.direction||"left",h="up"===l||"down"===l?"top":"left",c="up"===l||"left"===l?"pos":"neg",u={opacity:r?1:0};t.effects.save(n,a),n.show(),t.effects.createWrapper(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(h,"pos"===c?-s:s),u[h]=(r?"pos"===c?"+=":"-=":"pos"===c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===o&&n.hide(),t.effects.restore(n,a),t.effects.removeWrapper(n),i()}})}})(jQuery);(function(t){t.effects.effect.explode=function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),g||p.hide(),i()}var a,o,r,l,h,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=t.effects.setMode(p,e.mode||"hide"),g="show"===f,m=p.show().css("visibility","hidden").offset(),v=Math.ceil(p.outerWidth()/d),_=Math.ceil(p.outerHeight()/u),b=[];for(a=0;u>a;a++)for(l=m.top+a*_,c=a-(u-1)/2,o=0;d>o;o++)r=m.left+o*v,h=o-(d-1)/2,p.clone().appendTo("body").wrap("
        ").css({position:"absolute",visibility:"visible",left:-o*v,top:-a*_}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:v,height:_,left:r+(g?h*v:0),top:l+(g?c*_:0),opacity:g?0:1}).animate({left:r+(g?0:h*v),top:l+(g?0:c*_),opacity:g?1:0},e.duration||500,e.easing,s)}})(jQuery);(function(t){t.effects.effect.fade=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}})(jQuery);(function(t){t.effects.effect.fold=function(e,i){var s,n,a=t(this),o=["position","top","bottom","left","right","height","width"],r=t.effects.setMode(a,e.mode||"hide"),l="show"===r,h="hide"===r,c=e.size||15,u=/([0-9]+)%/.exec(c),d=!!e.horizFirst,p=l!==d,f=p?["width","height"]:["height","width"],g=e.duration/2,m={},v={};t.effects.save(a,o),a.show(),s=t.effects.createWrapper(a).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],u&&(c=parseInt(u[1],10)/100*n[h?0:1]),l&&s.css(d?{height:0,width:c}:{height:c,width:0}),m[f[0]]=l?n[0]:c,v[f[1]]=l?n[1]:0,s.animate(m,g,e.easing).animate(v,g,e.easing,function(){h&&a.hide(),t.effects.restore(a,o),t.effects.removeWrapper(a),i()})}})(jQuery);(function(t){t.effects.effect.highlight=function(e,i){var s=t(this),n=["backgroundImage","backgroundColor","opacity"],a=t.effects.setMode(s,e.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),t.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(o,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&s.hide(),t.effects.restore(s,n),i()}})}})(jQuery);(function(t){t.effects.effect.pulsate=function(e,i){var s,n=t(this),a=t.effects.setMode(n,e.mode||"show"),o="show"===a,r="hide"===a,l=o||"hide"===a,h=2*(e.times||5)+(l?1:0),c=e.duration/h,u=0,d=n.queue(),p=d.length;for((o||!n.is(":visible"))&&(n.css("opacity",0).show(),u=1),s=1;h>s;s++)n.animate({opacity:u},c,e.easing),u=1-u;n.animate({opacity:u},c,e.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&d.splice.apply(d,[1,0].concat(d.splice(p,h+1))),n.dequeue()}})(jQuery);(function(t){t.effects.effect.puff=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"hide"),a="hide"===n,o=parseInt(e.percent,10)||150,r=o/100,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};t.extend(e,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:a?o:100,from:a?l:{height:l.height*r,width:l.width*r,outerHeight:l.outerHeight*r,outerWidth:l.outerWidth*r}}),s.effect(e)},t.effects.effect.scale=function(e,i){var s=t(this),n=t.extend(!0,{},e),a=t.effects.setMode(s,e.mode||"effect"),o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"hide"===a?0:100),r=e.direction||"both",l=e.origin,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},c={y:"horizontal"!==r?o/100:1,x:"vertical"!==r?o/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==a&&(n.origin=l||["middle","center"],n.restore=!0),n.from=e.from||("show"===a?{height:0,width:0,outerHeight:0,outerWidth:0}:h),n.to={height:h.height*c.y,width:h.width*c.x,outerHeight:h.outerHeight*c.y,outerWidth:h.outerWidth*c.x},n.fade&&("show"===a&&(n.from.opacity=0,n.to.opacity=1),"hide"===a&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},t.effects.effect.size=function(e,i){var s,n,a,o=t(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],l=["position","top","bottom","left","right","overflow","opacity"],h=["width","height","overflow"],c=["fontSize"],u=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],d=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=t.effects.setMode(o,e.mode||"effect"),f=e.restore||"effect"!==p,g=e.scale||"both",m=e.origin||["middle","center"],v=o.css("position"),_=f?r:l,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&o.show(),s={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},"toggle"===e.mode&&"show"===p?(o.from=e.to||b,o.to=e.from||s):(o.from=e.from||("show"===p?b:s),o.to=e.to||("hide"===p?b:s)),a={from:{y:o.from.height/s.height,x:o.from.width/s.width},to:{y:o.to.height/s.height,x:o.to.width/s.width}},("box"===g||"both"===g)&&(a.from.y!==a.to.y&&(_=_.concat(u),o.from=t.effects.setTransition(o,u,a.from.y,o.from),o.to=t.effects.setTransition(o,u,a.to.y,o.to)),a.from.x!==a.to.x&&(_=_.concat(d),o.from=t.effects.setTransition(o,d,a.from.x,o.from),o.to=t.effects.setTransition(o,d,a.to.x,o.to))),("content"===g||"both"===g)&&a.from.y!==a.to.y&&(_=_.concat(c).concat(h),o.from=t.effects.setTransition(o,c,a.from.y,o.from),o.to=t.effects.setTransition(o,c,a.to.y,o.to)),t.effects.save(o,_),o.show(),t.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(n=t.effects.getBaseline(m,s),o.from.top=(s.outerHeight-o.outerHeight())*n.y,o.from.left=(s.outerWidth-o.outerWidth())*n.x,o.to.top=(s.outerHeight-o.to.outerHeight)*n.y,o.to.left=(s.outerWidth-o.to.outerWidth)*n.x),o.css(o.from),("content"===g||"both"===g)&&(u=u.concat(["marginTop","marginBottom"]).concat(c),d=d.concat(["marginLeft","marginRight"]),h=r.concat(u).concat(d),o.find("*[width]").each(function(){var i=t(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()};f&&t.effects.save(i,h),i.from={height:s.height*a.from.y,width:s.width*a.from.x,outerHeight:s.outerHeight*a.from.y,outerWidth:s.outerWidth*a.from.x},i.to={height:s.height*a.to.y,width:s.width*a.to.x,outerHeight:s.height*a.to.y,outerWidth:s.width*a.to.x},a.from.y!==a.to.y&&(i.from=t.effects.setTransition(i,u,a.from.y,i.from),i.to=t.effects.setTransition(i,u,a.to.y,i.to)),a.from.x!==a.to.x&&(i.from=t.effects.setTransition(i,d,a.from.x,i.from),i.to=t.effects.setTransition(i,d,a.to.x,i.to)),i.css(i.from),i.animate(i.to,e.duration,e.easing,function(){f&&t.effects.restore(i,h)})})),o.animate(o.to,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){0===o.to.opacity&&o.css("opacity",o.from.opacity),"hide"===p&&o.hide(),t.effects.restore(o,_),f||("static"===v?o.css({position:"relative",top:o.to.top,left:o.to.left}):t.each(["top","left"],function(t,e){o.css(e,function(e,i){var s=parseInt(i,10),n=t?o.to.left:o.to.top;return"auto"===i?n+"px":s+n+"px"})})),t.effects.removeWrapper(o),i()}})}})(jQuery);(function(t){t.effects.effect.shake=function(e,i){var s,n=t(this),a=["position","top","bottom","left","right","height","width"],o=t.effects.setMode(n,e.mode||"effect"),r=e.direction||"left",l=e.distance||20,h=e.times||3,c=2*h+1,u=Math.round(e.duration/c),d="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},g={},m={},v=n.queue(),_=v.length;for(t.effects.save(n,a),n.show(),t.effects.createWrapper(n),f[d]=(p?"-=":"+=")+l,g[d]=(p?"+=":"-=")+2*l,m[d]=(p?"-=":"+=")+2*l,n.animate(f,u,e.easing),s=1;h>s;s++)n.animate(g,u,e.easing).animate(m,u,e.easing);n.animate(g,u,e.easing).animate(f,u/2,e.easing).queue(function(){"hide"===o&&n.hide(),t.effects.restore(n,a),t.effects.removeWrapper(n),i()}),_>1&&v.splice.apply(v,[1,0].concat(v.splice(_,c+1))),n.dequeue()}})(jQuery);(function(t){t.effects.effect.slide=function(e,i){var s,n=t(this),a=["position","top","bottom","left","right","width","height"],o=t.effects.setMode(n,e.mode||"show"),r="show"===o,l=e.direction||"left",h="up"===l||"down"===l?"top":"left",c="up"===l||"left"===l,u={};t.effects.save(n,a),n.show(),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0),t.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(h,c?isNaN(s)?"-"+s:-s:s),u[h]=(r?c?"+=":"-=":c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===o&&n.hide(),t.effects.restore(n,a),t.effects.removeWrapper(n),i()}})}})(jQuery);(function(t){t.effects.effect.transfer=function(e,i){var s=t(this),n=t(e.to),a="fixed"===n.css("position"),o=t("body"),r=a?o.scrollTop():0,l=a?o.scrollLeft():0,h=n.offset(),c={top:h.top-r,left:h.left-l,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
        ").appendTo(document.body).addClass(e.className).css({top:u.top-r,left:u.left-l,height:s.innerHeight(),width:s.innerWidth(),position:a?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),i()})}})(jQuery); \ No newline at end of file diff --git a/themes/greydragon/js/jquery.cycle.js b/themes/greydragon/js/jquery.cycle.js new file mode 100644 index 0000000..dc474ea --- /dev/null +++ b/themes/greydragon/js/jquery.cycle.js @@ -0,0 +1,1543 @@ +/*! + * jQuery Cycle Plugin (with Transition Definitions) + * Examples and documentation at: http://jquery.malsup.com/cycle/ + * Copyright (c) 2007-2013 M. Alsup + * Version: 3.0.3 (11-JUL-2013) + * Dual licensed under the MIT and GPL licenses. + * http://jquery.malsup.com/license.html + * Requires: jQuery v1.7.1 or later + */ +;(function($, undefined) { +"use strict"; + +var ver = '3.0.3'; + +function debug(s) { + if ($.fn.cycle.debug) + log(s); +} +function log() { + /*global console */ + if (window.console && console.log) + console.log('[cycle] ' + Array.prototype.join.call(arguments,' ')); +} +$.expr[':'].paused = function(el) { + return el.cyclePause; +}; + + +// the options arg can be... +// a number - indicates an immediate transition should occur to the given slide index +// a string - 'pause', 'resume', 'toggle', 'next', 'prev', 'stop', 'destroy' or the name of a transition effect (ie, 'fade', 'zoom', etc) +// an object - properties to control the slideshow +// +// the arg2 arg can be... +// the name of an fx (only used in conjunction with a numeric value for 'options') +// the value true (only used in first arg == 'resume') and indicates +// that the resume should occur immediately (not wait for next timeout) + +$.fn.cycle = function(options, arg2) { + var o = { s: this.selector, c: this.context }; + + // in 1.3+ we can fix mistakes with the ready state + if (this.length === 0 && options != 'stop') { + if (!$.isReady && o.s) { + log('DOM not ready, queuing slideshow'); + $(function() { + $(o.s,o.c).cycle(options,arg2); + }); + return this; + } + // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() + log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); + return this; + } + + // iterate the matched nodeset + return this.each(function() { + var opts = handleArguments(this, options, arg2); + if (opts === false) + return; + + opts.updateActivePagerLink = opts.updateActivePagerLink || $.fn.cycle.updateActivePagerLink; + + // stop existing slideshow for this container (if there is one) + if (this.cycleTimeout) + clearTimeout(this.cycleTimeout); + this.cycleTimeout = this.cyclePause = 0; + this.cycleStop = 0; // issue #108 + + var $cont = $(this); + var $slides = opts.slideExpr ? $(opts.slideExpr, this) : $cont.children(); + var els = $slides.get(); + + if (els.length < 2) { + log('terminating; too few slides: ' + els.length); + return; + } + + var opts2 = buildOptions($cont, $slides, els, opts, o); + if (opts2 === false) + return; + + var startTime = opts2.continuous ? 10 : getTimeout(els[opts2.currSlide], els[opts2.nextSlide], opts2, !opts2.backwards); + + // if it's an auto slideshow, kick it off + if (startTime) { + startTime += (opts2.delay || 0); + if (startTime < 10) + startTime = 10; + debug('first timeout: ' + startTime); + this.cycleTimeout = setTimeout(function(){go(els,opts2,0,!opts.backwards);}, startTime); + } + }); +}; + +function triggerPause(cont, byHover, onPager) { + var opts = $(cont).data('cycle.opts'); + if (!opts) + return; + var paused = !!cont.cyclePause; + if (paused && opts.paused) + opts.paused(cont, opts, byHover, onPager); + else if (!paused && opts.resumed) + opts.resumed(cont, opts, byHover, onPager); +} + +// process the args that were passed to the plugin fn +function handleArguments(cont, options, arg2) { + if (cont.cycleStop === undefined) + cont.cycleStop = 0; + if (options === undefined || options === null) + options = {}; + if (options.constructor == String) { + switch(options) { + case 'destroy': + case 'stop': + var opts = $(cont).data('cycle.opts'); + if (!opts) + return false; + cont.cycleStop++; // callbacks look for change + if (cont.cycleTimeout) + clearTimeout(cont.cycleTimeout); + cont.cycleTimeout = 0; + if (opts.elements) + $(opts.elements).stop(); + $(cont).removeData('cycle.opts'); + if (options == 'destroy') + destroy(cont, opts); + return false; + case 'toggle': + cont.cyclePause = (cont.cyclePause === 1) ? 0 : 1; + checkInstantResume(cont.cyclePause, arg2, cont); + triggerPause(cont); + return false; + case 'pause': + cont.cyclePause = 1; + triggerPause(cont); + return false; + case 'resume': + cont.cyclePause = 0; + checkInstantResume(false, arg2, cont); + triggerPause(cont); + return false; + case 'prev': + case 'next': + opts = $(cont).data('cycle.opts'); + if (!opts) { + log('options not found, "prev/next" ignored'); + return false; + } + if (typeof arg2 == 'string') + opts.oneTimeFx = arg2; + $.fn.cycle[options](opts); + return false; + default: + options = { fx: options }; + } + return options; + } + else if (options.constructor == Number) { + // go to the requested slide + var num = options; + options = $(cont).data('cycle.opts'); + if (!options) { + log('options not found, can not advance slide'); + return false; + } + if (num < 0 || num >= options.elements.length) { + log('invalid slide index: ' + num); + return false; + } + options.nextSlide = num; + if (cont.cycleTimeout) { + clearTimeout(cont.cycleTimeout); + cont.cycleTimeout = 0; + } + if (typeof arg2 == 'string') + options.oneTimeFx = arg2; + go(options.elements, options, 1, num >= options.currSlide); + return false; + } + return options; + + function checkInstantResume(isPaused, arg2, cont) { + if (!isPaused && arg2 === true) { // resume now! + var options = $(cont).data('cycle.opts'); + if (!options) { + log('options not found, can not resume'); + return false; + } + if (cont.cycleTimeout) { + clearTimeout(cont.cycleTimeout); + cont.cycleTimeout = 0; + } + go(options.elements, options, 1, !options.backwards); + } + } +} + +function removeFilter(el, opts) { + if (!$.support.opacity && opts.cleartype && el.style.filter) { + try { el.style.removeAttribute('filter'); } + catch(smother) {} // handle old opera versions + } +} + +// unbind event handlers +function destroy(cont, opts) { + if (opts.next) + $(opts.next).unbind(opts.prevNextEvent); + if (opts.prev) + $(opts.prev).unbind(opts.prevNextEvent); + + if (opts.pager || opts.pagerAnchorBuilder) + $.each(opts.pagerAnchors || [], function() { + this.unbind().remove(); + }); + opts.pagerAnchors = null; + $(cont).unbind('mouseenter.cycle mouseleave.cycle'); + if (opts.destroy) // callback + opts.destroy(opts); +} + +// one-time initialization +function buildOptions($cont, $slides, els, options, o) { + var startingSlideSpecified; + // support metadata plugin (v1.0 and v2.0) + var opts = $.extend({}, $.fn.cycle.defaults, options || {}, $.metadata ? $cont.metadata() : $.meta ? $cont.data() : {}); + var meta = $.isFunction($cont.data) ? $cont.data(opts.metaAttr) : null; + if (meta) + opts = $.extend(opts, meta); + if (opts.autostop) + opts.countdown = opts.autostopCount || els.length; + + var cont = $cont[0]; + $cont.data('cycle.opts', opts); + opts.$cont = $cont; + opts.stopCount = cont.cycleStop; + opts.elements = els; + opts.before = opts.before ? [opts.before] : []; + opts.after = opts.after ? [opts.after] : []; + + // push some after callbacks + if (!$.support.opacity && opts.cleartype) + opts.after.push(function() { removeFilter(this, opts); }); + if (opts.continuous) + opts.after.push(function() { go(els,opts,0,!opts.backwards); }); + + saveOriginalOpts(opts); + + // clearType corrections + if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg) + clearTypeFix($slides); + + // container requires non-static position so that slides can be position within + if ($cont.css('position') == 'static') + $cont.css('position', 'relative'); + if (opts.width) + $cont.width(opts.width); + if (opts.height && opts.height != 'auto') + $cont.height(opts.height); + + if (opts.startingSlide !== undefined) { + opts.startingSlide = parseInt(opts.startingSlide,10); + if (opts.startingSlide >= els.length || opts.startSlide < 0) + opts.startingSlide = 0; // catch bogus input + else + startingSlideSpecified = true; + } + else if (opts.backwards) + opts.startingSlide = els.length - 1; + else + opts.startingSlide = 0; + + // if random, mix up the slide array + if (opts.random) { + opts.randomMap = []; + for (var i = 0; i < els.length; i++) + opts.randomMap.push(i); + opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;}); + if (startingSlideSpecified) { + // try to find the specified starting slide and if found set start slide index in the map accordingly + for ( var cnt = 0; cnt < els.length; cnt++ ) { + if ( opts.startingSlide == opts.randomMap[cnt] ) { + opts.randomIndex = cnt; + } + } + } + else { + opts.randomIndex = 1; + opts.startingSlide = opts.randomMap[1]; + } + } + else if (opts.startingSlide >= els.length) + opts.startingSlide = 0; // catch bogus input + opts.currSlide = opts.startingSlide || 0; + var first = opts.startingSlide; + + // set position and zIndex on all the slides + $slides.css({position: 'absolute', top:0, left:0}).hide().each(function(i) { + var z; + if (opts.backwards) + z = first ? i <= first ? els.length + (i-first) : first-i : els.length-i; + else + z = first ? i >= first ? els.length - (i-first) : first-i : els.length-i; + $(this).css('z-index', z); + }); + + // make sure first slide is visible + $(els[first]).css('opacity',1).show(); // opacity bit needed to handle restart use case + removeFilter(els[first], opts); + + // stretch slides + if (opts.fit) { + if (!opts.aspect) { + if (opts.width) + $slides.width(opts.width); + if (opts.height && opts.height != 'auto') + $slides.height(opts.height); + } else { + $slides.each(function(){ + var $slide = $(this); + var ratio = (opts.aspect === true) ? $slide.width()/$slide.height() : opts.aspect; + if( opts.width && $slide.width() != opts.width ) { + $slide.width( opts.width ); + $slide.height( opts.width / ratio ); + } + + if( opts.height && $slide.height() < opts.height ) { + $slide.height( opts.height ); + $slide.width( opts.height * ratio ); + } + }); + } + } + + if (opts.center && ((!opts.fit) || opts.aspect)) { + $slides.each(function(){ + var $slide = $(this); + $slide.css({ + "margin-left": opts.width ? + ((opts.width - $slide.width()) / 2) + "px" : + 0, + "margin-top": opts.height ? + ((opts.height - $slide.height()) / 2) + "px" : + 0 + }); + }); + } + + if (opts.center && !opts.fit && !opts.slideResize) { + $slides.each(function(){ + var $slide = $(this); + $slide.css({ + "margin-left": opts.width ? ((opts.width - $slide.width()) / 2) + "px" : 0, + "margin-top": opts.height ? ((opts.height - $slide.height()) / 2) + "px" : 0 + }); + }); + } + + // stretch container + var reshape = (opts.containerResize || opts.containerResizeHeight) && $cont.innerHeight() < 1; + if (reshape) { // do this only if container has no size http://tinyurl.com/da2oa9 + var maxw = 0, maxh = 0; + for(var j=0; j < els.length; j++) { + var $e = $(els[j]), e = $e[0], w = $e.outerWidth(), h = $e.outerHeight(); + if (!w) w = e.offsetWidth || e.width || $e.attr('width'); + if (!h) h = e.offsetHeight || e.height || $e.attr('height'); + maxw = w > maxw ? w : maxw; + maxh = h > maxh ? h : maxh; + } + if (opts.containerResize && maxw > 0 && maxh > 0) + $cont.css({width:maxw+'px',height:maxh+'px'}); + if (opts.containerResizeHeight && maxh > 0) + $cont.css({height:maxh+'px'}); + } + + var pauseFlag = false; // https://github.com/malsup/cycle/issues/44 + if (opts.pause) + $cont.bind('mouseenter.cycle', function(){ + pauseFlag = true; + this.cyclePause++; + triggerPause(cont, true); + }).bind('mouseleave.cycle', function(){ + if (pauseFlag) + this.cyclePause--; + triggerPause(cont, true); + }); + + if (supportMultiTransitions(opts) === false) + return false; + + // apparently a lot of people use image slideshows without height/width attributes on the images. + // Cycle 2.50+ requires the sizing info for every slide; this block tries to deal with that. + var requeue = false; + options.requeueAttempts = options.requeueAttempts || 0; + $slides.each(function() { + // try to get height/width of each slide + var $el = $(this); + this.cycleH = (opts.fit && opts.height) ? opts.height : ($el.height() || this.offsetHeight || this.height || $el.attr('height') || 0); + this.cycleW = (opts.fit && opts.width) ? opts.width : ($el.width() || this.offsetWidth || this.width || $el.attr('width') || 0); + + if ( $el.is('img') ) { + var loading = (this.cycleH === 0 && this.cycleW === 0 && !this.complete); + // don't requeue for images that are still loading but have a valid size + if (loading) { + if (o.s && opts.requeueOnImageNotLoaded && ++options.requeueAttempts < 100) { // track retry count so we don't loop forever + log(options.requeueAttempts,' - img slide not loaded, requeuing slideshow: ', this.src, this.cycleW, this.cycleH); + setTimeout(function() {$(o.s,o.c).cycle(options);}, opts.requeueTimeout); + requeue = true; + return false; // break each loop + } + else { + log('could not determine size of image: '+this.src, this.cycleW, this.cycleH); + } + } + } + return true; + }); + + if (requeue) + return false; + + opts.cssBefore = opts.cssBefore || {}; + opts.cssAfter = opts.cssAfter || {}; + opts.cssFirst = opts.cssFirst || {}; + opts.animIn = opts.animIn || {}; + opts.animOut = opts.animOut || {}; + + $slides.not(':eq('+first+')').css(opts.cssBefore); + $($slides[first]).css(opts.cssFirst); + + if (opts.timeout) { + opts.timeout = parseInt(opts.timeout,10); + // ensure that timeout and speed settings are sane + if (opts.speed.constructor == String) + opts.speed = $.fx.speeds[opts.speed] || parseInt(opts.speed,10); + if (!opts.sync) + opts.speed = opts.speed / 2; + + var buffer = opts.fx == 'none' ? 0 : opts.fx == 'shuffle' ? 500 : 250; + while((opts.timeout - opts.speed) < buffer) // sanitize timeout + opts.timeout += opts.speed; + } + if (opts.easing) + opts.easeIn = opts.easeOut = opts.easing; + if (!opts.speedIn) + opts.speedIn = opts.speed; + if (!opts.speedOut) + opts.speedOut = opts.speed; + + opts.slideCount = els.length; + opts.currSlide = opts.lastSlide = first; + if (opts.random) { + if (++opts.randomIndex == els.length) + opts.randomIndex = 0; + opts.nextSlide = opts.randomMap[opts.randomIndex]; + } + else if (opts.backwards) + opts.nextSlide = opts.startingSlide === 0 ? (els.length-1) : opts.startingSlide-1; + else + opts.nextSlide = opts.startingSlide >= (els.length-1) ? 0 : opts.startingSlide+1; + + // run transition init fn + if (!opts.multiFx) { + var init = $.fn.cycle.transitions[opts.fx]; + if ($.isFunction(init)) + init($cont, $slides, opts); + else if (opts.fx != 'custom' && !opts.multiFx) { + log('unknown transition: ' + opts.fx,'; slideshow terminating'); + return false; + } + } + + // fire artificial events + var e0 = $slides[first]; + if (!opts.skipInitializationCallbacks) { + if (opts.before.length) + opts.before[0].apply(e0, [e0, e0, opts, true]); + if (opts.after.length) + opts.after[0].apply(e0, [e0, e0, opts, true]); + } + if (opts.next) + $(opts.next).bind(opts.prevNextEvent,function(){return advance(opts,1);}); + if (opts.prev) + $(opts.prev).bind(opts.prevNextEvent,function(){return advance(opts,0);}); + if (opts.pager || opts.pagerAnchorBuilder) + buildPager(els,opts); + + exposeAddSlide(opts, els); + + return opts; +} + +// save off original opts so we can restore after clearing state +function saveOriginalOpts(opts) { + opts.original = { before: [], after: [] }; + opts.original.cssBefore = $.extend({}, opts.cssBefore); + opts.original.cssAfter = $.extend({}, opts.cssAfter); + opts.original.animIn = $.extend({}, opts.animIn); + opts.original.animOut = $.extend({}, opts.animOut); + $.each(opts.before, function() { opts.original.before.push(this); }); + $.each(opts.after, function() { opts.original.after.push(this); }); +} + +function supportMultiTransitions(opts) { + var i, tx, txs = $.fn.cycle.transitions; + // look for multiple effects + if (opts.fx.indexOf(',') > 0) { + opts.multiFx = true; + opts.fxs = opts.fx.replace(/\s*/g,'').split(','); + // discard any bogus effect names + for (i=0; i < opts.fxs.length; i++) { + var fx = opts.fxs[i]; + tx = txs[fx]; + if (!tx || !txs.hasOwnProperty(fx) || !$.isFunction(tx)) { + log('discarding unknown transition: ',fx); + opts.fxs.splice(i,1); + i--; + } + } + // if we have an empty list then we threw everything away! + if (!opts.fxs.length) { + log('No valid transitions named; slideshow terminating.'); + return false; + } + } + else if (opts.fx == 'all') { // auto-gen the list of transitions + opts.multiFx = true; + opts.fxs = []; + for (var p in txs) { + if (txs.hasOwnProperty(p)) { + tx = txs[p]; + if (txs.hasOwnProperty(p) && $.isFunction(tx)) + opts.fxs.push(p); + } + } + } + if (opts.multiFx && opts.randomizeEffects) { + // munge the fxs array to make effect selection random + var r1 = Math.floor(Math.random() * 20) + 30; + for (i = 0; i < r1; i++) { + var r2 = Math.floor(Math.random() * opts.fxs.length); + opts.fxs.push(opts.fxs.splice(r2,1)[0]); + } + debug('randomized fx sequence: ',opts.fxs); + } + return true; +} + +// provide a mechanism for adding slides after the slideshow has started +function exposeAddSlide(opts, els) { + opts.addSlide = function(newSlide, prepend) { + var $s = $(newSlide), s = $s[0]; + if (!opts.autostopCount) + opts.countdown++; + els[prepend?'unshift':'push'](s); + if (opts.els) + opts.els[prepend?'unshift':'push'](s); // shuffle needs this + opts.slideCount = els.length; + + // add the slide to the random map and resort + if (opts.random) { + opts.randomMap.push(opts.slideCount-1); + opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;}); + } + + $s.css('position','absolute'); + $s[prepend?'prependTo':'appendTo'](opts.$cont); + + if (prepend) { + opts.currSlide++; + opts.nextSlide++; + } + + if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg) + clearTypeFix($s); + + if (opts.fit && opts.width) + $s.width(opts.width); + if (opts.fit && opts.height && opts.height != 'auto') + $s.height(opts.height); + s.cycleH = (opts.fit && opts.height) ? opts.height : $s.height(); + s.cycleW = (opts.fit && opts.width) ? opts.width : $s.width(); + + $s.css(opts.cssBefore); + + if (opts.pager || opts.pagerAnchorBuilder) + $.fn.cycle.createPagerAnchor(els.length-1, s, $(opts.pager), els, opts); + + if ($.isFunction(opts.onAddSlide)) + opts.onAddSlide($s); + else + $s.hide(); // default behavior + }; +} + +// reset internal state; we do this on every pass in order to support multiple effects +$.fn.cycle.resetState = function(opts, fx) { + fx = fx || opts.fx; + opts.before = []; opts.after = []; + opts.cssBefore = $.extend({}, opts.original.cssBefore); + opts.cssAfter = $.extend({}, opts.original.cssAfter); + opts.animIn = $.extend({}, opts.original.animIn); + opts.animOut = $.extend({}, opts.original.animOut); + opts.fxFn = null; + $.each(opts.original.before, function() { opts.before.push(this); }); + $.each(opts.original.after, function() { opts.after.push(this); }); + + // re-init + var init = $.fn.cycle.transitions[fx]; + if ($.isFunction(init)) + init(opts.$cont, $(opts.elements), opts); +}; + +// this is the main engine fn, it handles the timeouts, callbacks and slide index mgmt +function go(els, opts, manual, fwd) { + var p = opts.$cont[0], curr = els[opts.currSlide], next = els[opts.nextSlide]; + + // opts.busy is true if we're in the middle of an animation + if (manual && opts.busy && opts.manualTrump) { + // let manual transitions requests trump active ones + debug('manualTrump in go(), stopping active transition'); + $(els).stop(true,true); + opts.busy = 0; + clearTimeout(p.cycleTimeout); + } + + // don't begin another timeout-based transition if there is one active + if (opts.busy) { + debug('transition active, ignoring new tx request'); + return; + } + + + // stop cycling if we have an outstanding stop request + if (p.cycleStop != opts.stopCount || p.cycleTimeout === 0 && !manual) + return; + + // check to see if we should stop cycling based on autostop options + if (!manual && !p.cyclePause && !opts.bounce && + ((opts.autostop && (--opts.countdown <= 0)) || + (opts.nowrap && !opts.random && opts.nextSlide < opts.currSlide))) { + if (opts.end) + opts.end(opts); + return; + } + + // if slideshow is paused, only transition on a manual trigger + var changed = false; + if ((manual || !p.cyclePause) && (opts.nextSlide != opts.currSlide)) { + changed = true; + var fx = opts.fx; + // keep trying to get the slide size if we don't have it yet + curr.cycleH = curr.cycleH || $(curr).height(); + curr.cycleW = curr.cycleW || $(curr).width(); + next.cycleH = next.cycleH || $(next).height(); + next.cycleW = next.cycleW || $(next).width(); + + // support multiple transition types + if (opts.multiFx) { + if (fwd && (opts.lastFx === undefined || ++opts.lastFx >= opts.fxs.length)) + opts.lastFx = 0; + else if (!fwd && (opts.lastFx === undefined || --opts.lastFx < 0)) + opts.lastFx = opts.fxs.length - 1; + fx = opts.fxs[opts.lastFx]; + } + + // one-time fx overrides apply to: $('div').cycle(3,'zoom'); + if (opts.oneTimeFx) { + fx = opts.oneTimeFx; + opts.oneTimeFx = null; + } + + $.fn.cycle.resetState(opts, fx); + + // run the before callbacks + if (opts.before.length) + $.each(opts.before, function(i,o) { + if (p.cycleStop != opts.stopCount) return; + o.apply(next, [curr, next, opts, fwd]); + }); + + // stage the after callacks + var after = function() { + opts.busy = 0; + $.each(opts.after, function(i,o) { + if (p.cycleStop != opts.stopCount) return; + o.apply(next, [curr, next, opts, fwd]); + }); + if (!p.cycleStop) { + // queue next transition + queueNext(); + } + }; + + debug('tx firing('+fx+'); currSlide: ' + opts.currSlide + '; nextSlide: ' + opts.nextSlide); + + // get ready to perform the transition + opts.busy = 1; + if (opts.fxFn) // fx function provided? + opts.fxFn(curr, next, opts, after, fwd, manual && opts.fastOnEvent); + else if ($.isFunction($.fn.cycle[opts.fx])) // fx plugin ? + $.fn.cycle[opts.fx](curr, next, opts, after, fwd, manual && opts.fastOnEvent); + else + $.fn.cycle.custom(curr, next, opts, after, fwd, manual && opts.fastOnEvent); + } + else { + queueNext(); + } + + if (changed || opts.nextSlide == opts.currSlide) { + // calculate the next slide + var roll; + opts.lastSlide = opts.currSlide; + if (opts.random) { + opts.currSlide = opts.nextSlide; + if (++opts.randomIndex == els.length) { + opts.randomIndex = 0; + opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;}); + } + opts.nextSlide = opts.randomMap[opts.randomIndex]; + if (opts.nextSlide == opts.currSlide) + opts.nextSlide = (opts.currSlide == opts.slideCount - 1) ? 0 : opts.currSlide + 1; + } + else if (opts.backwards) { + roll = (opts.nextSlide - 1) < 0; + if (roll && opts.bounce) { + opts.backwards = !opts.backwards; + opts.nextSlide = 1; + opts.currSlide = 0; + } + else { + opts.nextSlide = roll ? (els.length-1) : opts.nextSlide-1; + opts.currSlide = roll ? 0 : opts.nextSlide+1; + } + } + else { // sequence + roll = (opts.nextSlide + 1) == els.length; + if (roll && opts.bounce) { + opts.backwards = !opts.backwards; + opts.nextSlide = els.length-2; + opts.currSlide = els.length-1; + } + else { + opts.nextSlide = roll ? 0 : opts.nextSlide+1; + opts.currSlide = roll ? els.length-1 : opts.nextSlide-1; + } + } + } + if (changed && opts.pager) + opts.updateActivePagerLink(opts.pager, opts.currSlide, opts.activePagerClass); + + function queueNext() { + // stage the next transition + var ms = 0, timeout = opts.timeout; + if (opts.timeout && !opts.continuous) { + ms = getTimeout(els[opts.currSlide], els[opts.nextSlide], opts, fwd); + if (opts.fx == 'shuffle') + ms -= opts.speedOut; + } + else if (opts.continuous && p.cyclePause) // continuous shows work off an after callback, not this timer logic + ms = 10; + if (ms > 0) + p.cycleTimeout = setTimeout(function(){ go(els, opts, 0, !opts.backwards); }, ms); + } +} + +// invoked after transition +$.fn.cycle.updateActivePagerLink = function(pager, currSlide, clsName) { + $(pager).each(function() { + $(this).children().removeClass(clsName).eq(currSlide).addClass(clsName); + }); +}; + +// calculate timeout value for current transition +function getTimeout(curr, next, opts, fwd) { + if (opts.timeoutFn) { + // call user provided calc fn + var t = opts.timeoutFn.call(curr,curr,next,opts,fwd); + while (opts.fx != 'none' && (t - opts.speed) < 250) // sanitize timeout + t += opts.speed; + debug('calculated timeout: ' + t + '; speed: ' + opts.speed); + if (t !== false) + return t; + } + return opts.timeout; +} + +// expose next/prev function, caller must pass in state +$.fn.cycle.next = function(opts) { advance(opts,1); }; +$.fn.cycle.prev = function(opts) { advance(opts,0);}; + +// advance slide forward or back +function advance(opts, moveForward) { + var val = moveForward ? 1 : -1; + var els = opts.elements; + var p = opts.$cont[0], timeout = p.cycleTimeout; + if (timeout) { + clearTimeout(timeout); + p.cycleTimeout = 0; + } + if (opts.random && val < 0) { + // move back to the previously display slide + opts.randomIndex--; + if (--opts.randomIndex == -2) + opts.randomIndex = els.length-2; + else if (opts.randomIndex == -1) + opts.randomIndex = els.length-1; + opts.nextSlide = opts.randomMap[opts.randomIndex]; + } + else if (opts.random) { + opts.nextSlide = opts.randomMap[opts.randomIndex]; + } + else { + opts.nextSlide = opts.currSlide + val; + if (opts.nextSlide < 0) { + if (opts.nowrap) return false; + opts.nextSlide = els.length - 1; + } + else if (opts.nextSlide >= els.length) { + if (opts.nowrap) return false; + opts.nextSlide = 0; + } + } + + var cb = opts.onPrevNextEvent || opts.prevNextClick; // prevNextClick is deprecated + if ($.isFunction(cb)) + cb(val > 0, opts.nextSlide, els[opts.nextSlide]); + go(els, opts, 1, moveForward); + return false; +} + +function buildPager(els, opts) { + var $p = $(opts.pager); + $.each(els, function(i,o) { + $.fn.cycle.createPagerAnchor(i,o,$p,els,opts); + }); + opts.updateActivePagerLink(opts.pager, opts.startingSlide, opts.activePagerClass); +} + +$.fn.cycle.createPagerAnchor = function(i, el, $p, els, opts) { + var a; + if ($.isFunction(opts.pagerAnchorBuilder)) { + a = opts.pagerAnchorBuilder(i,el); + debug('pagerAnchorBuilder('+i+', el) returned: ' + a); + } + else + a = '
        '+(i+1)+''; + + if (!a) + return; + var $a = $(a); + // don't reparent if anchor is in the dom + if ($a.parents('body').length === 0) { + var arr = []; + if ($p.length > 1) { + $p.each(function() { + var $clone = $a.clone(true); + $(this).append($clone); + arr.push($clone[0]); + }); + $a = $(arr); + } + else { + $a.appendTo($p); + } + } + + opts.pagerAnchors = opts.pagerAnchors || []; + opts.pagerAnchors.push($a); + + var pagerFn = function(e) { + e.preventDefault(); + opts.nextSlide = i; + var p = opts.$cont[0], timeout = p.cycleTimeout; + if (timeout) { + clearTimeout(timeout); + p.cycleTimeout = 0; + } + var cb = opts.onPagerEvent || opts.pagerClick; // pagerClick is deprecated + if ($.isFunction(cb)) + cb(opts.nextSlide, els[opts.nextSlide]); + go(els,opts,1,opts.currSlide < i); // trigger the trans +// return false; // <== allow bubble + }; + + if ( /mouseenter|mouseover/i.test(opts.pagerEvent) ) { + $a.hover(pagerFn, function(){/* no-op */} ); + } + else { + $a.bind(opts.pagerEvent, pagerFn); + } + + if ( ! /^click/.test(opts.pagerEvent) && !opts.allowPagerClickBubble) + $a.bind('click.cycle', function(){return false;}); // suppress click + + var cont = opts.$cont[0]; + var pauseFlag = false; // https://github.com/malsup/cycle/issues/44 + if (opts.pauseOnPagerHover) { + $a.hover( + function() { + pauseFlag = true; + cont.cyclePause++; + triggerPause(cont,true,true); + }, function() { + if (pauseFlag) + cont.cyclePause--; + triggerPause(cont,true,true); + } + ); + } +}; + +// helper fn to calculate the number of slides between the current and the next +$.fn.cycle.hopsFromLast = function(opts, fwd) { + var hops, l = opts.lastSlide, c = opts.currSlide; + if (fwd) + hops = c > l ? c - l : opts.slideCount - l; + else + hops = c < l ? l - c : l + opts.slideCount - c; + return hops; +}; + +// fix clearType problems in ie6 by setting an explicit bg color +// (otherwise text slides look horrible during a fade transition) +function clearTypeFix($slides) { + debug('applying clearType background-color hack'); + function hex(s) { + s = parseInt(s,10).toString(16); + return s.length < 2 ? '0'+s : s; + } + function getBg(e) { + for ( ; e && e.nodeName.toLowerCase() != 'html'; e = e.parentNode) { + var v = $.css(e,'background-color'); + if (v && v.indexOf('rgb') >= 0 ) { + var rgb = v.match(/\d+/g); + return '#'+ hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]); + } + if (v && v != 'transparent') + return v; + } + return '#ffffff'; + } + $slides.each(function() { $(this).css('background-color', getBg(this)); }); +} + +// reset common props before the next transition +$.fn.cycle.commonReset = function(curr,next,opts,w,h,rev) { + $(opts.elements).not(curr).hide(); + if (typeof opts.cssBefore.opacity == 'undefined') + opts.cssBefore.opacity = 1; + opts.cssBefore.display = 'block'; + if (opts.slideResize && w !== false && next.cycleW > 0) + opts.cssBefore.width = next.cycleW; + if (opts.slideResize && h !== false && next.cycleH > 0) + opts.cssBefore.height = next.cycleH; + opts.cssAfter = opts.cssAfter || {}; + opts.cssAfter.display = 'none'; + $(curr).css('zIndex',opts.slideCount + (rev === true ? 1 : 0)); + $(next).css('zIndex',opts.slideCount + (rev === true ? 0 : 1)); +}; + +// the actual fn for effecting a transition +$.fn.cycle.custom = function(curr, next, opts, cb, fwd, speedOverride) { + var $l = $(curr), $n = $(next); + var speedIn = opts.speedIn, speedOut = opts.speedOut, easeIn = opts.easeIn, easeOut = opts.easeOut, animInDelay = opts.animInDelay, animOutDelay = opts.animOutDelay; + $n.css(opts.cssBefore); + if (speedOverride) { + if (typeof speedOverride == 'number') + speedIn = speedOut = speedOverride; + else + speedIn = speedOut = 1; + easeIn = easeOut = null; + } + var fn = function() { + $n.delay(animInDelay).animate(opts.animIn, speedIn, easeIn, function() { + cb(); + }); + }; + $l.delay(animOutDelay).animate(opts.animOut, speedOut, easeOut, function() { + $l.css(opts.cssAfter); + if (!opts.sync) + fn(); + }); + if (opts.sync) fn(); +}; + +// transition definitions - only fade is defined here, transition pack defines the rest +$.fn.cycle.transitions = { + fade: function($cont, $slides, opts) { + $slides.not(':eq('+opts.currSlide+')').css('opacity',0); + opts.before.push(function(curr,next,opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.cssBefore.opacity = 0; + }); + opts.animIn = { opacity: 1 }; + opts.animOut = { opacity: 0 }; + opts.cssBefore = { top: 0, left: 0 }; + } +}; + +$.fn.cycle.ver = function() { return ver; }; + +// override these globally if you like (they are all optional) +$.fn.cycle.defaults = { + activePagerClass: 'activeSlide', // class name used for the active pager link + after: null, // transition callback (scope set to element that was shown): function(currSlideElement, nextSlideElement, options, forwardFlag) + allowPagerClickBubble: false, // allows or prevents click event on pager anchors from bubbling + animIn: null, // properties that define how the slide animates in + animInDelay: 0, // allows delay before next slide transitions in + animOut: null, // properties that define how the slide animates out + animOutDelay: 0, // allows delay before current slide transitions out + aspect: false, // preserve aspect ratio during fit resizing, cropping if necessary (must be used with fit option) + autostop: 0, // true to end slideshow after X transitions (where X == slide count) + autostopCount: 0, // number of transitions (optionally used with autostop to define X) + backwards: false, // true to start slideshow at last slide and move backwards through the stack + before: null, // transition callback (scope set to element to be shown): function(currSlideElement, nextSlideElement, options, forwardFlag) + center: null, // set to true to have cycle add top/left margin to each slide (use with width and height options) + cleartype: !$.support.opacity, // true if clearType corrections should be applied (for IE) + cleartypeNoBg: false, // set to true to disable extra cleartype fixing (leave false to force background color setting on slides) + containerResize: 1, // resize container to fit largest slide + containerResizeHeight: 0, // resize containers height to fit the largest slide but leave the width dynamic + continuous: 0, // true to start next transition immediately after current one completes + cssAfter: null, // properties that defined the state of the slide after transitioning out + cssBefore: null, // properties that define the initial state of the slide before transitioning in + delay: 0, // additional delay (in ms) for first transition (hint: can be negative) + easeIn: null, // easing for "in" transition + easeOut: null, // easing for "out" transition + easing: null, // easing method for both in and out transitions + end: null, // callback invoked when the slideshow terminates (use with autostop or nowrap options): function(options) + fastOnEvent: 0, // force fast transitions when triggered manually (via pager or prev/next); value == time in ms + fit: 0, // force slides to fit container + fx: 'fade', // name of transition effect (or comma separated names, ex: 'fade,scrollUp,shuffle') + fxFn: null, // function used to control the transition: function(currSlideElement, nextSlideElement, options, afterCalback, forwardFlag) + height: 'auto', // container height (if the 'fit' option is true, the slides will be set to this height as well) + manualTrump: true, // causes manual transition to stop an active transition instead of being ignored + metaAttr: 'cycle', // data- attribute that holds the option data for the slideshow + next: null, // element, jQuery object, or jQuery selector string for the element to use as event trigger for next slide + nowrap: 0, // true to prevent slideshow from wrapping + onPagerEvent: null, // callback fn for pager events: function(zeroBasedSlideIndex, slideElement) + onPrevNextEvent: null, // callback fn for prev/next events: function(isNext, zeroBasedSlideIndex, slideElement) + pager: null, // element, jQuery object, or jQuery selector string for the element to use as pager container + pagerAnchorBuilder: null, // callback fn for building anchor links: function(index, DOMelement) + pagerEvent: 'click.cycle', // name of event which drives the pager navigation + pause: 0, // true to enable "pause on hover" + pauseOnPagerHover: 0, // true to pause when hovering over pager link + prev: null, // element, jQuery object, or jQuery selector string for the element to use as event trigger for previous slide + prevNextEvent: 'click.cycle',// event which drives the manual transition to the previous or next slide + random: 0, // true for random, false for sequence (not applicable to shuffle fx) + randomizeEffects: 1, // valid when multiple effects are used; true to make the effect sequence random + requeueOnImageNotLoaded: true, // requeue the slideshow if any image slides are not yet loaded + requeueTimeout: 250, // ms delay for requeue + rev: 0, // causes animations to transition in reverse (for effects that support it such as scrollHorz/scrollVert/shuffle) + shuffle: null, // coords for shuffle animation, ex: { top:15, left: 200 } + skipInitializationCallbacks: false, // set to true to disable the first before/after callback that occurs prior to any transition + slideExpr: null, // expression for selecting slides (if something other than all children is required) + slideResize: 1, // force slide width/height to fixed size before every transition + speed: 1000, // speed of the transition (any valid fx speed value) + speedIn: null, // speed of the 'in' transition + speedOut: null, // speed of the 'out' transition + startingSlide: undefined,// zero-based index of the first slide to be displayed + sync: 1, // true if in/out transitions should occur simultaneously + timeout: 4000, // milliseconds between slide transitions (0 to disable auto advance) + timeoutFn: null, // callback for determining per-slide timeout value: function(currSlideElement, nextSlideElement, options, forwardFlag) + updateActivePagerLink: null,// callback fn invoked to update the active pager link (adds/removes activePagerClass style) + width: null // container width (if the 'fit' option is true, the slides will be set to this width as well) +}; + +})(jQuery); + + +/*! + * jQuery Cycle Plugin Transition Definitions + * This script is a plugin for the jQuery Cycle Plugin + * Examples and documentation at: http://malsup.com/jquery/cycle/ + * Copyright (c) 2007-2010 M. Alsup + * Version: 2.73 + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +(function($) { +"use strict"; + +// +// These functions define slide initialization and properties for the named +// transitions. To save file size feel free to remove any of these that you +// don't need. +// +$.fn.cycle.transitions.none = function($cont, $slides, opts) { + opts.fxFn = function(curr,next,opts,after){ + $(next).show(); + $(curr).hide(); + after(); + }; +}; + +// not a cross-fade, fadeout only fades out the top slide +$.fn.cycle.transitions.fadeout = function($cont, $slides, opts) { + $slides.not(':eq('+opts.currSlide+')').css({ display: 'block', 'opacity': 1 }); + opts.before.push(function(curr,next,opts,w,h,rev) { + $(curr).css('zIndex',opts.slideCount + (rev !== true ? 1 : 0)); + $(next).css('zIndex',opts.slideCount + (rev !== true ? 0 : 1)); + }); + opts.animIn.opacity = 1; + opts.animOut.opacity = 0; + opts.cssBefore.opacity = 1; + opts.cssBefore.display = 'block'; + opts.cssAfter.zIndex = 0; +}; + +// scrollUp/Down/Left/Right +$.fn.cycle.transitions.scrollUp = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var h = $cont.height(); + opts.cssBefore.top = h; + opts.cssBefore.left = 0; + opts.cssFirst.top = 0; + opts.animIn.top = 0; + opts.animOut.top = -h; +}; +$.fn.cycle.transitions.scrollDown = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var h = $cont.height(); + opts.cssFirst.top = 0; + opts.cssBefore.top = -h; + opts.cssBefore.left = 0; + opts.animIn.top = 0; + opts.animOut.top = h; +}; +$.fn.cycle.transitions.scrollLeft = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var w = $cont.width(); + opts.cssFirst.left = 0; + opts.cssBefore.left = w; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.left = 0-w; +}; +$.fn.cycle.transitions.scrollRight = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var w = $cont.width(); + opts.cssFirst.left = 0; + opts.cssBefore.left = -w; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.left = w; +}; +$.fn.cycle.transitions.scrollHorz = function($cont, $slides, opts) { + $cont.css('overflow','hidden').width(); + opts.before.push(function(curr, next, opts, fwd) { + if (opts.rev) + fwd = !fwd; + $.fn.cycle.commonReset(curr,next,opts); + opts.cssBefore.left = fwd ? (next.cycleW-1) : (1-next.cycleW); + opts.animOut.left = fwd ? -curr.cycleW : curr.cycleW; + }); + opts.cssFirst.left = 0; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.top = 0; +}; +$.fn.cycle.transitions.scrollVert = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push(function(curr, next, opts, fwd) { + if (opts.rev) + fwd = !fwd; + $.fn.cycle.commonReset(curr,next,opts); + opts.cssBefore.top = fwd ? (1-next.cycleH) : (next.cycleH-1); + opts.animOut.top = fwd ? curr.cycleH : -curr.cycleH; + }); + opts.cssFirst.top = 0; + opts.cssBefore.left = 0; + opts.animIn.top = 0; + opts.animOut.left = 0; +}; + +// slideX/slideY +$.fn.cycle.transitions.slideX = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $(opts.elements).not(curr).hide(); + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.animIn.width = next.cycleW; + }); + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; + opts.animIn.width = 'show'; + opts.animOut.width = 0; +}; +$.fn.cycle.transitions.slideY = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $(opts.elements).not(curr).hide(); + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.animIn.height = next.cycleH; + }); + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.cssBefore.height = 0; + opts.animIn.height = 'show'; + opts.animOut.height = 0; +}; + +// shuffle +$.fn.cycle.transitions.shuffle = function($cont, $slides, opts) { + var i, w = $cont.css('overflow', 'visible').width(); + $slides.css({left: 0, top: 0}); + opts.before.push(function(curr,next,opts) { + $.fn.cycle.commonReset(curr,next,opts,true,true,true); + }); + // only adjust speed once! + if (!opts.speedAdjusted) { + opts.speed = opts.speed / 2; // shuffle has 2 transitions + opts.speedAdjusted = true; + } + opts.random = 0; + opts.shuffle = opts.shuffle || {left:-w, top:15}; + opts.els = []; + for (i=0; i < $slides.length; i++) + opts.els.push($slides[i]); + + for (i=0; i < opts.currSlide; i++) + opts.els.push(opts.els.shift()); + + // custom transition fn (hat tip to Benjamin Sterling for this bit of sweetness!) + opts.fxFn = function(curr, next, opts, cb, fwd) { + if (opts.rev) + fwd = !fwd; + var $el = fwd ? $(curr) : $(next); + $(next).css(opts.cssBefore); + var count = opts.slideCount; + $el.animate(opts.shuffle, opts.speedIn, opts.easeIn, function() { + var hops = $.fn.cycle.hopsFromLast(opts, fwd); + for (var k=0; k < hops; k++) { + if (fwd) + opts.els.push(opts.els.shift()); + else + opts.els.unshift(opts.els.pop()); + } + if (fwd) { + for (var i=0, len=opts.els.length; i < len; i++) + $(opts.els[i]).css('z-index', len-i+count); + } + else { + var z = $(curr).css('z-index'); + $el.css('z-index', parseInt(z,10)+1+count); + } + $el.animate({left:0, top:0}, opts.speedOut, opts.easeOut, function() { + $(fwd ? this : curr).hide(); + if (cb) cb(); + }); + }); + }; + $.extend(opts.cssBefore, { display: 'block', opacity: 1, top: 0, left: 0 }); +}; + +// turnUp/Down/Left/Right +$.fn.cycle.transitions.turnUp = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.cssBefore.top = next.cycleH; + opts.animIn.height = next.cycleH; + opts.animOut.width = next.cycleW; + }); + opts.cssFirst.top = 0; + opts.cssBefore.left = 0; + opts.cssBefore.height = 0; + opts.animIn.top = 0; + opts.animOut.height = 0; +}; +$.fn.cycle.transitions.turnDown = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH; + }); + opts.cssFirst.top = 0; + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.cssBefore.height = 0; + opts.animOut.height = 0; +}; +$.fn.cycle.transitions.turnLeft = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.cssBefore.left = next.cycleW; + opts.animIn.width = next.cycleW; + }); + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; + opts.animIn.left = 0; + opts.animOut.width = 0; +}; +$.fn.cycle.transitions.turnRight = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.animIn.width = next.cycleW; + opts.animOut.left = curr.cycleW; + }); + $.extend(opts.cssBefore, { top: 0, left: 0, width: 0 }); + opts.animIn.left = 0; + opts.animOut.width = 0; +}; + +// zoom +$.fn.cycle.transitions.zoom = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,false,true); + opts.cssBefore.top = next.cycleH/2; + opts.cssBefore.left = next.cycleW/2; + $.extend(opts.animIn, { top: 0, left: 0, width: next.cycleW, height: next.cycleH }); + $.extend(opts.animOut, { width: 0, height: 0, top: curr.cycleH/2, left: curr.cycleW/2 }); + }); + opts.cssFirst.top = 0; + opts.cssFirst.left = 0; + opts.cssBefore.width = 0; + opts.cssBefore.height = 0; +}; + +// fadeZoom +$.fn.cycle.transitions.fadeZoom = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,false); + opts.cssBefore.left = next.cycleW/2; + opts.cssBefore.top = next.cycleH/2; + $.extend(opts.animIn, { top: 0, left: 0, width: next.cycleW, height: next.cycleH }); + }); + opts.cssBefore.width = 0; + opts.cssBefore.height = 0; + opts.animOut.opacity = 0; +}; + +// blindX +$.fn.cycle.transitions.blindX = function($cont, $slides, opts) { + var w = $cont.css('overflow','hidden').width(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.animIn.width = next.cycleW; + opts.animOut.left = curr.cycleW; + }); + opts.cssBefore.left = w; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.left = w; +}; +// blindY +$.fn.cycle.transitions.blindY = function($cont, $slides, opts) { + var h = $cont.css('overflow','hidden').height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH; + }); + opts.cssBefore.top = h; + opts.cssBefore.left = 0; + opts.animIn.top = 0; + opts.animOut.top = h; +}; +// blindZ +$.fn.cycle.transitions.blindZ = function($cont, $slides, opts) { + var h = $cont.css('overflow','hidden').height(); + var w = $cont.width(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH; + }); + opts.cssBefore.top = h; + opts.cssBefore.left = w; + opts.animIn.top = 0; + opts.animIn.left = 0; + opts.animOut.top = h; + opts.animOut.left = w; +}; + +// growX - grow horizontally from centered 0 width +$.fn.cycle.transitions.growX = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.cssBefore.left = this.cycleW/2; + opts.animIn.left = 0; + opts.animIn.width = this.cycleW; + opts.animOut.left = 0; + }); + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; +}; +// growY - grow vertically from centered 0 height +$.fn.cycle.transitions.growY = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.cssBefore.top = this.cycleH/2; + opts.animIn.top = 0; + opts.animIn.height = this.cycleH; + opts.animOut.top = 0; + }); + opts.cssBefore.height = 0; + opts.cssBefore.left = 0; +}; + +// curtainX - squeeze in both edges horizontally +$.fn.cycle.transitions.curtainX = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true,true); + opts.cssBefore.left = next.cycleW/2; + opts.animIn.left = 0; + opts.animIn.width = this.cycleW; + opts.animOut.left = curr.cycleW/2; + opts.animOut.width = 0; + }); + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; +}; +// curtainY - squeeze in both edges vertically +$.fn.cycle.transitions.curtainY = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false,true); + opts.cssBefore.top = next.cycleH/2; + opts.animIn.top = 0; + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH/2; + opts.animOut.height = 0; + }); + opts.cssBefore.height = 0; + opts.cssBefore.left = 0; +}; + +// cover - curr slide covered by next slide +$.fn.cycle.transitions.cover = function($cont, $slides, opts) { + var d = opts.direction || 'left'; + var w = $cont.css('overflow','hidden').width(); + var h = $cont.height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.cssAfter.display = ''; + if (d == 'right') + opts.cssBefore.left = -w; + else if (d == 'up') + opts.cssBefore.top = h; + else if (d == 'down') + opts.cssBefore.top = -h; + else + opts.cssBefore.left = w; + }); + opts.animIn.left = 0; + opts.animIn.top = 0; + opts.cssBefore.top = 0; + opts.cssBefore.left = 0; +}; + +// uncover - curr slide moves off next slide +$.fn.cycle.transitions.uncover = function($cont, $slides, opts) { + var d = opts.direction || 'left'; + var w = $cont.css('overflow','hidden').width(); + var h = $cont.height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,true,true); + if (d == 'right') + opts.animOut.left = w; + else if (d == 'up') + opts.animOut.top = -h; + else if (d == 'down') + opts.animOut.top = h; + else + opts.animOut.left = -w; + }); + opts.animIn.left = 0; + opts.animIn.top = 0; + opts.cssBefore.top = 0; + opts.cssBefore.left = 0; +}; + +// toss - move top slide and fade away +$.fn.cycle.transitions.toss = function($cont, $slides, opts) { + var w = $cont.css('overflow','visible').width(); + var h = $cont.height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,true,true); + // provide default toss settings if animOut not provided + if (!opts.animOut.left && !opts.animOut.top) + $.extend(opts.animOut, { left: w*2, top: -h/2, opacity: 0 }); + else + opts.animOut.opacity = 0; + }); + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.animIn.left = 0; +}; + +// wipe - clip animation +$.fn.cycle.transitions.wipe = function($cont, $slides, opts) { + var w = $cont.css('overflow','hidden').width(); + var h = $cont.height(); + opts.cssBefore = opts.cssBefore || {}; + var clip; + if (opts.clip) { + if (/l2r/.test(opts.clip)) + clip = 'rect(0px 0px '+h+'px 0px)'; + else if (/r2l/.test(opts.clip)) + clip = 'rect(0px '+w+'px '+h+'px '+w+'px)'; + else if (/t2b/.test(opts.clip)) + clip = 'rect(0px '+w+'px 0px 0px)'; + else if (/b2t/.test(opts.clip)) + clip = 'rect('+h+'px '+w+'px '+h+'px 0px)'; + else if (/zoom/.test(opts.clip)) { + var top = parseInt(h/2,10); + var left = parseInt(w/2,10); + clip = 'rect('+top+'px '+left+'px '+top+'px '+left+'px)'; + } + } + + opts.cssBefore.clip = opts.cssBefore.clip || clip || 'rect(0px 0px 0px 0px)'; + + var d = opts.cssBefore.clip.match(/(\d+)/g); + var t = parseInt(d[0],10), r = parseInt(d[1],10), b = parseInt(d[2],10), l = parseInt(d[3],10); + + opts.before.push(function(curr, next, opts) { + if (curr == next) return; + var $curr = $(curr), $next = $(next); + $.fn.cycle.commonReset(curr,next,opts,true,true,false); + opts.cssAfter.display = 'block'; + + var step = 1, count = parseInt((opts.speedIn / 13),10) - 1; + (function f() { + var tt = t ? t - parseInt(step * (t/count),10) : 0; + var ll = l ? l - parseInt(step * (l/count),10) : 0; + var bb = b < h ? b + parseInt(step * ((h-b)/count || 1),10) : h; + var rr = r < w ? r + parseInt(step * ((w-r)/count || 1),10) : w; + $next.css({ clip: 'rect('+tt+'px '+rr+'px '+bb+'px '+ll+'px)' }); + (step++ <= count) ? setTimeout(f, 13) : $curr.css('display', 'none'); + })(); + }); + $.extend(opts.cssBefore, { display: 'block', opacity: 1, top: 0, left: 0 }); + opts.animIn = { left: 0 }; + opts.animOut = { left: 0 }; +}; + +})(jQuery); diff --git a/themes/greydragon/js/jquery.form.custom.js b/themes/greydragon/js/jquery.form.custom.js new file mode 100644 index 0000000..796db12 --- /dev/null +++ b/themes/greydragon/js/jquery.form.custom.js @@ -0,0 +1,1076 @@ +/*! + * jQuery Form Plugin + * version: 3.09 (16-APR-2012) + * @requires jQuery v1.3.2 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Project repository: https://github.com/malsup/form + * Dual licensed under the MIT and GPL licenses: + * http://malsup.github.com/mit-license.txt + * http://malsup.github.com/gpl-license-v2.txt + */ +/*global ActiveXObject alert */ +;(function($) { +"use strict"; + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are mutually exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').on('submit', function(e) { + e.preventDefault(); // <-- important + $(this).ajaxSubmit({ + target: '#output' + }); + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + You can also use ajaxForm with delegation (requires jQuery v1.7+), so the + form does not have to exist when you invoke ajaxForm: + + $('#myForm').ajaxForm({ + delegation: true, + target: '#output' + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * Feature detection + */ +var feature = {}; +feature.fileapi = $("").get(0).files !== undefined; +feature.formdata = window.FormData !== undefined; + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + /*jshint scripturl:true */ + + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + var method, action, url, $form = this; + + if (typeof options == 'function') { + options = { success: options }; + } + + method = this.attr('method'); + action = this.attr('action'); + url = (typeof action === 'string') ? $.trim(action) : ''; + url = url || window.location.href || ''; + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + + options = $.extend(true, { + url: url, + success: $.ajaxSettings.success, + type: method || 'GET', + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var traditional = options.traditional; + if ( traditional === undefined ) { + traditional = $.ajaxSettings.traditional; + } + + var elements = []; + var qx, a = this.formToArray(options.semantic, elements); + if (options.data) { + options.extraData = options.data; + qx = $.param(options.data, traditional); + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a, traditional); + if (qx) { + q = ( q ? (q + '&' + qx) : qx ); + } + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else { + options.data = q; // data is the query string for 'post' + } + + var callbacks = []; + if (options.resetForm) { + callbacks.push(function() { $form.resetForm(); }); + } + if (options.clearForm) { + callbacks.push(function() { $form.clearForm(options.includeHidden); }); + } + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + var fn = options.replaceTarget ? 'replaceWith' : 'html'; + $(options.target)[fn](data).each(oldSuccess, arguments); + }); + } + else if (options.success) { + callbacks.push(options.success); + } + + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || options; // jQuery 1.4+ supports scope context + for (var i=0, max=callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } + }; + + // are there files to upload? + var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113) + var hasFileInputs = fileInputs.length > 0; + var mp = 'multipart/form-data'; + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + var fileAPI = feature.fileapi && feature.formdata; + log("fileAPI :" + fileAPI); + var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI; + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (options.iframe || shouldUseFrame)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, function() { + fileUploadIframe(a); + }); + } + else { + fileUploadIframe(a); + } + } + else if ((hasFileInputs || multipart) && fileAPI) { + fileUploadXhr(a); + } + else { + $.ajax(options); + } + + // clear element array + for (var k=0; k < elements.length; k++) + elements[k] = null; + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz) + function fileUploadXhr(a) { + var formdata = new FormData(); + + for (var i=0; i < a.length; i++) { + formdata.append(a[i].name, a[i].value); + } + + if (options.extraData) { + for (var p in options.extraData) + if (options.extraData.hasOwnProperty(p)) + formdata.append(p, options.extraData[p]); + } + + options.data = null; + + var s = $.extend(true, {}, $.ajaxSettings, options, { + contentType: false, + processData: false, + cache: false, + type: 'POST' + }); + + if (options.uploadProgress) { + // workaround because jqXHR does not expose upload property + s.xhr = function() { + var xhr = jQuery.ajaxSettings.xhr(); + if (xhr.upload) { + xhr.upload.onprogress = function(event) { + var percent = 0; + var position = event.loaded || event.position; /*event.position is deprecated*/ + var total = event.total; + if (event.lengthComputable) { + percent = Math.ceil(position / total * 100); + } + options.uploadProgress(event, position, total, percent); + }; + } + return xhr; + }; + } + + s.data = null; + var beforeSend = s.beforeSend; + s.beforeSend = function(xhr, o) { + o.data = formdata; + if(beforeSend) + beforeSend.call(o, xhr, options); + }; + $.ajax(s); + } + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUploadIframe(a) { + var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; + var useProp = !!$.fn.prop; + + if ($(':input[name=submit],:input[id=submit]', form).length) { + // if there is an input with a name or id of 'submit' then we won't be + // able to invoke the submit fn on the form (at least not x-browser) + alert('Error: Form elements must not have name or id of "submit".'); + return; + } + + if (a) { + // ensure that every serialized input is still enabled + for (i=0; i < elements.length; i++) { + el = $(elements[i]); + if ( useProp ) + el.prop('disabled', false); + else + el.removeAttr('disabled'); + } + } + + s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + id = 'jqFormIO' + (new Date().getTime()); + if (s.iframeTarget) { + $io = $(s.iframeTarget); + n = $io.attr('name'); + if (!n) + $io.attr('name', id); + else + id = n; + } + else { + $io = $('