From 8a517bbf2a3f1bee8f19b8bf1de4cb270fb8a2b7 Mon Sep 17 00:00:00 2001 From: "Michael J. Rubinsky" Date: Thu, 30 Jul 2009 16:12:03 -0400 Subject: [PATCH] Move Ansel to Horde-Hatchery --- ansel/.htaccess | 18 + ansel/COPYING | 280 ++ ansel/README | 121 + ansel/browse.php | 28 + ansel/browse_edit.php | 33 + ansel/config/.cvsignore | 5 + ansel/config/conf.xml | 259 ++ ansel/config/hooks.php.dist | 96 + ansel/config/prefs.php.dist | 273 ++ ansel/config/styles.php.dist | 190 + ansel/disclamer.php | 52 + ansel/docs/CHANGES | 129 + ansel/docs/CREDITS | 36 + ansel/docs/INSTALL | 301 ++ ansel/docs/RELEASE_NOTES | 37 + ansel/docs/TODO | 17 + ansel/docs/lighttpd-ansel.conf | 36 + ansel/edit_dates.php | 99 + ansel/faces/claim.php | 78 + ansel/faces/custom.php | 87 + ansel/faces/delete.php | 38 + ansel/faces/face.php | 41 + ansel/faces/gallery.php | 61 + ansel/faces/image.php | 53 + ansel/faces/img.php | 56 + ansel/faces/name.php | 39 + ansel/faces/report.php | 81 + ansel/faces/savecustom.php | 62 + ansel/faces/search/all.php | 40 + ansel/faces/search/image.php | 81 + ansel/faces/search/image_define.php | 61 + ansel/faces/search/image_save.php | 86 + ansel/faces/search/image_search.php | 54 + ansel/faces/search/img.php | 27 + ansel/faces/search/name.php | 53 + ansel/faces/search/named.php | 39 + ansel/faces/search/owner.php | 48 + ansel/faces/search/tabs.php | 37 + ansel/gallery.php | 395 ++ ansel/gallery/captions.php | 69 + ansel/gallery/delete.php | 72 + ansel/gallery/index.php | 0 ansel/gallery/sort.php | 98 + ansel/group.php | 107 + ansel/image.php | 779 ++++ ansel/img/download.php | 42 + ansel/img/ecard.php | 118 + ansel/img/full.php | 40 + ansel/img/index.php | 39 + ansel/img/mini.php | 45 + ansel/img/prettythumb.php | 46 + ansel/img/screen.php | 45 + ansel/img/thumb.php | 45 + ansel/img/upload.php | 255 ++ ansel/img/upload_preview.php | 37 + ansel/index.php | 27 + ansel/js/src/builder.js | 20 + ansel/js/src/carousel.js | 1076 ++++++ ansel/js/src/cropper.js | 555 +++ ansel/js/src/editcaption.js | 38 + ansel/js/src/editfaces.js | 56 + ansel/js/src/embed.js | 158 + ansel/js/src/googlemap.js | 504 +++ ansel/js/src/googlemap_edit.js | 196 + ansel/js/src/imagefaces.js | 19 + ansel/js/src/lightbox.js | 460 +++ ansel/js/src/slideshow.js | 241 ++ ansel/js/src/slugcheck.js | 43 + ansel/js/src/tagactions.js | 47 + ansel/js/src/togglewidget.js | 38 + ansel/lib/Ajax/Imple/EditCaption.php | 89 + ansel/lib/Ajax/Imple/EditFaces.php | 148 + ansel/lib/Ajax/Imple/Embed.php | 50 + ansel/lib/Ajax/Imple/GallerySlugCheck.php | 47 + ansel/lib/Ajax/Imple/ImageSaveGeotag.php | 92 + ansel/lib/Ajax/Imple/LocationAutoCompleter.php | 104 + ansel/lib/Ajax/Imple/TagActions.php | 122 + ansel/lib/Ajax/Imple/ToggleGalleryActions.php | 39 + ansel/lib/Ajax/Imple/ToggleOtherGalleries.php | 39 + ansel/lib/Ansel.php | 3996 ++++++++++++++++++++ ansel/lib/Block/cloud.php | 60 + ansel/lib/Block/gallery.php | 155 + ansel/lib/Block/my_galleries.php | 90 + ansel/lib/Block/random_photo.php | 59 + ansel/lib/Block/recent_comments.php | 188 + ansel/lib/Block/recent_faces.php | 59 + ansel/lib/Block/recently_added.php | 204 + ansel/lib/Block/recently_added_geodata.php | 180 + ansel/lib/Exif.php | 1364 +++++++ ansel/lib/Exif/canon.php | 532 +++ ansel/lib/Exif/fujifilm.php | 236 ++ ansel/lib/Exif/gps.php | 271 ++ ansel/lib/Exif/nikon.php | 307 ++ ansel/lib/Exif/olympus.php | 182 + ansel/lib/Exif/panasonic.php | 403 ++ ansel/lib/Exif/sanyo.php | 153 + ansel/lib/Faces.php | 1138 ++++++ ansel/lib/Faces/facedetect.php | 81 + ansel/lib/Faces/opencv.php | 95 + ansel/lib/Forms/Ecard.php | 46 + ansel/lib/Forms/Image.php | 73 + ansel/lib/Forms/ImageDate.php | 37 + ansel/lib/Forms/Upload.php | 102 + ansel/lib/Forms/Watermark.php | 60 + ansel/lib/GalleryMode/Date.php | 1056 ++++++ ansel/lib/GalleryMode/Normal.php | 353 ++ ansel/lib/GalleryMode/RemoteVimeo.php | 259 ++ ansel/lib/ImageView.php | 170 + ansel/lib/ImageView/mini.php | 19 + ansel/lib/ImageView/plainstack.php | 52 + ansel/lib/ImageView/polaroidstack.php | 52 + ansel/lib/ImageView/polaroidthumb.php | 41 + ansel/lib/ImageView/prettythumb.php | 51 + ansel/lib/ImageView/roundedstack.php | 53 + ansel/lib/ImageView/screen.php | 19 + ansel/lib/ImageView/shadowsharpthumb.php | 47 + ansel/lib/ImageView/thumb.php | 19 + ansel/lib/ImageView/vimeothumb.php | 47 + ansel/lib/Report.php | 124 + ansel/lib/Report/letter.php | 37 + ansel/lib/Report/mail.php | 44 + ansel/lib/Report/tickets.php | 25 + ansel/lib/Search.php | 105 + ansel/lib/Search/exif.php | 46 + ansel/lib/Tags.php | 669 ++++ ansel/lib/Tile/DateGallery.php | 132 + ansel/lib/Tile/Gallery.php | 132 + ansel/lib/Tile/Image.php | 129 + ansel/lib/Views/Abstract.php | 191 + ansel/lib/Views/Embedded.php | 89 + ansel/lib/Views/EmbeddedRenderers/Carousel.php | 48 + ansel/lib/Views/EmbeddedRenderers/GalleryLink.php | 152 + ansel/lib/Views/EmbeddedRenderers/Mini.php | 183 + ansel/lib/Views/EmbeddedRenderers/Slideshow.php | 48 + ansel/lib/Views/Gallery.php | 211 ++ ansel/lib/Views/GalleryRenderer.php | 223 ++ ansel/lib/Views/GalleryRenderers/Gallery.php | 133 + .../lib/Views/GalleryRenderers/GalleryLightbox.php | 160 + ansel/lib/Views/GalleryRenderers/GalleryVimeo.php | 196 + ansel/lib/Views/Image.php | 415 ++ ansel/lib/Views/List.php | 281 ++ ansel/lib/Views/Results.php | 317 ++ ansel/lib/Views/Slideshow.php | 183 + ansel/lib/Widget.php | 157 + ansel/lib/Widget/Actions.php | 193 + ansel/lib/Widget/Base.php | 1 + ansel/lib/Widget/GalleryFaces.php | 99 + ansel/lib/Widget/Geodata.php | 245 ++ ansel/lib/Widget/ImageFaces.php | 137 + ansel/lib/Widget/Links.php | 59 + ansel/lib/Widget/OtherGalleries.php | 128 + ansel/lib/Widget/OwnerFaces.php | 69 + ansel/lib/Widget/SimilarPhotos.php | 97 + ansel/lib/Widget/Tags.php | 98 + ansel/lib/XPPublisher.php | 57 + ansel/lib/api.php | 1347 +++++++ ansel/lib/base.php | 69 + ansel/lib/prefs.php | 35 + ansel/lib/version.php | 1 + ansel/locale/de_DE/LC_MESSAGES/ansel.mo | Bin 0 -> 211281 bytes ansel/locale/en_US/help.xml | 74 + ansel/locale/es_ES/LC_MESSAGES/ansel.mo | Bin 0 -> 182303 bytes ansel/locale/es_ES/help.xml | 47 + ansel/locale/fi_FI/LC_MESSAGES/ansel.mo | Bin 0 -> 172907 bytes ansel/locale/fi_FI/help.xml | 43 + ansel/locale/it_IT/LC_MESSAGES/ansel.mo | Bin 0 -> 176515 bytes ansel/locale/ja_JP/LC_MESSAGES/ansel.mo | Bin 0 -> 161963 bytes ansel/locale/lt_LT/LC_MESSAGES/ansel.mo | Bin 0 -> 163727 bytes ansel/locale/sl_SI/LC_MESSAGES/ansel.mo | Bin 0 -> 149544 bytes ansel/locale/sv_SE/LC_MESSAGES/ansel.mo | Bin 0 -> 108729 bytes ansel/locale/tr_TR/LC_MESSAGES/ansel.mo | Bin 0 -> 182452 bytes ansel/locale/zh_TW/LC_MESSAGES/ansel.mo | Bin 0 -> 147969 bytes ansel/map_edit.php | 172 + ansel/perms.php | 275 ++ ansel/po/.cvsignore | 1 + ansel/po/README | 1 + ansel/po/ansel.pot | 2571 +++++++++++++ ansel/po/de_DE.po | 3629 ++++++++++++++++++ ansel/po/es_ES.po | 2121 +++++++++++ ansel/po/fi_FI.po | 1981 ++++++++++ ansel/po/it_IT.po | 2785 ++++++++++++++ ansel/po/ja_JP.po | 1969 ++++++++++ ansel/po/lt_LT.po | 1701 +++++++++ ansel/po/sl_SI.po | 1994 ++++++++++ ansel/po/sv_SE.po | 2007 ++++++++++ ansel/po/tr_TR.po | 2792 ++++++++++++++ ansel/po/zh_TW.po | 1576 ++++++++ ansel/preview.php | 34 + ansel/protect.php | 46 + ansel/report.php | 93 + ansel/rss.php | 297 ++ ansel/scripts/.htaccess | 1 + ansel/scripts/AnselPublish.scpt | Bin 0 -> 4188 bytes ansel/scripts/all_images_exif_to_tags.php | 123 + ansel/scripts/ansel.php | 330 ++ ansel/scripts/garbage_collection.php | 111 + ansel/scripts/recursive_import.php | 229 ++ ansel/scripts/remote_import.php | 349 ++ ansel/scripts/sql/ansel.pgsql.sql | 143 + ansel/scripts/sql/ansel.sql | 147 + ansel/scripts/upgrades/1.0_to_1.1.php | 59 + ansel/scripts/upgrades/2008-06-04-faces.sql | 25 + .../upgrades/2008-06-17_fix_varchar_lengths.sql | 4 + .../2008-09-13_add_image_original_date.sql | 8 + .../2008-09-16_add_original_date_values.php | 48 + .../scripts/upgrades/2008-09-23_fix_group_uid.sql | 2 + .../upgrades/2008-12-5_add_geolocation_tables.sql | 9 + .../scripts/upgrades/2009-01-10_fix_view_mode.sql | 5 + .../upgrades/2009-04-14_fix_view_mode.pgsql.sql | 5 + .../upgrades/2009-06-14_fix_geolocation_values.php | 50 + .../upgrades/2009-06-22_add_geolocation_fields.sql | 3 + .../2009-06-22_move_geolocation_values.php | 37 + .../2009-07-06_add_geolocation_timestamp.sql | 1 + ansel/templates/captions/captions.inc | 57 + ansel/templates/common-header.inc | 30 + ansel/templates/faces/custom.inc | 57 + ansel/templates/faces/define.inc | 69 + ansel/templates/faces/face.inc | 42 + ansel/templates/faces/faces.inc | 19 + ansel/templates/faces/gallery.inc | 71 + ansel/templates/faces/image.inc | 12 + ansel/templates/faces/index.inc | 21 + ansel/templates/faces/search.inc | 32 + ansel/templates/gallery/delete_confirmation.inc | 15 + ansel/templates/gallery/gallery.inc | 130 + ansel/templates/group/category.inc | 55 + ansel/templates/group/footer.inc | 1 + ansel/templates/group/header.inc | 14 + ansel/templates/group/owner.inc | 63 + ansel/templates/group/pager.inc | 4 + ansel/templates/image/crop_image.inc | 169 + ansel/templates/image/edit_image.inc | 41 + ansel/templates/image/preview_cropimage.inc | 32 + ansel/templates/image/preview_image.inc | 35 + ansel/templates/image/resize_image.inc | 84 + ansel/templates/image/upload.inc | 180 + ansel/templates/list/footer.inc | 2 + ansel/templates/list/header.inc | 7 + ansel/templates/list/pager.inc | 4 + ansel/templates/menu.inc | 6 + ansel/templates/prefs/default_category_select.inc | 8 + .../prefs/default_gallerystyle_select.inc | 4 + ansel/templates/rss/rss.inc | 26 + ansel/templates/rss/rss2.inc | 38 + ansel/templates/tile/dategallery.inc | 4 + ansel/templates/tile/face.inc | 38 + ansel/templates/tile/gallery.inc | 14 + ansel/templates/tile/gallerymini.inc | 4 + ansel/templates/tile/image.inc | 24 + ansel/templates/view/gallery.inc | 172 + ansel/templates/view/gallerylightbox.inc | 203 + ansel/templates/view/galleryvimeo.inc | 62 + ansel/templates/view/image.inc | 144 + ansel/templates/view/list.inc | 68 + ansel/templates/view/results.inc | 140 + ansel/templates/view/slideshow.inc | 63 + ansel/templates/xppublish/javascript.inc | 69 + ansel/templates/xppublish/list.inc | 24 + ansel/templates/xppublish/login.inc | 18 + ansel/templates/xppublish/new.inc | 29 + ansel/test.php | 125 + ansel/themes/cropper.css | 174 + ansel/themes/embed.css | 14 + ansel/themes/feed-rss.xsl | 84 + ansel/themes/graphics/add.png | Bin 0 -> 582 bytes ansel/themes/graphics/ansel.png | Bin 0 -> 552 bytes ansel/themes/graphics/arrow_switch.png | Bin 0 -> 683 bytes ansel/themes/graphics/browse.png | Bin 0 -> 582 bytes ansel/themes/graphics/down.png | Bin 0 -> 242 bytes ansel/themes/graphics/favicon.ico | Bin 0 -> 1386 bytes ansel/themes/graphics/galleries.png | Bin 0 -> 582 bytes ansel/themes/graphics/gallery-locked-mini.png | Bin 0 -> 252 bytes ansel/themes/graphics/gallery-locked.png | Bin 0 -> 548 bytes ansel/themes/graphics/image_add.png | Bin 0 -> 229 bytes ansel/themes/graphics/lightbox/bullet.gif | Bin 0 -> 49 bytes ansel/themes/graphics/lightbox/close.gif | Bin 0 -> 222 bytes ansel/themes/graphics/lightbox/closelabel.gif | Bin 0 -> 979 bytes ansel/themes/graphics/lightbox/loading.gif | Bin 0 -> 2767 bytes ansel/themes/graphics/lightbox/nextlabel.gif | Bin 0 -> 1252 bytes ansel/themes/graphics/lightbox/prevlabel.gif | Bin 0 -> 1264 bytes ansel/themes/graphics/loading.gif | Bin 0 -> 1737 bytes ansel/themes/graphics/mini-error.png | Bin 0 -> 252 bytes ansel/themes/graphics/minus.png | Bin 0 -> 203 bytes ansel/themes/graphics/mygalleries.png | Bin 0 -> 582 bytes ansel/themes/graphics/plus.png | Bin 0 -> 229 bytes ansel/themes/graphics/point.png | Bin 0 -> 279 bytes ansel/themes/graphics/prettythumb-error.png | Bin 0 -> 548 bytes ansel/themes/graphics/problem.png | Bin 0 -> 124 bytes ansel/themes/graphics/resize.png | Bin 0 -> 125 bytes ansel/themes/graphics/scaler_slider.gif | Bin 0 -> 872 bytes ansel/themes/graphics/scaler_slider_track.gif | Bin 0 -> 1053 bytes ansel/themes/graphics/screen-error.png | Bin 0 -> 548 bytes ansel/themes/graphics/slideshow_next.png | Bin 0 -> 285 bytes ansel/themes/graphics/slideshow_pause.png | Bin 0 -> 291 bytes ansel/themes/graphics/slideshow_play.png | Bin 0 -> 268 bytes ansel/themes/graphics/slideshow_prev.png | Bin 0 -> 303 bytes ansel/themes/graphics/success.png | Bin 0 -> 360 bytes ansel/themes/graphics/text.png | Bin 0 -> 104 bytes ansel/themes/graphics/thumb-error.png | Bin 0 -> 548 bytes ansel/themes/graphics/up.png | Bin 0 -> 246 bytes ansel/themes/lightbox.css | 112 + ansel/themes/screen.css | 403 ++ ansel/themes/silver/graphics/add.png | Bin 0 -> 668 bytes ansel/themes/silver/graphics/ansel.png | Bin 0 -> 661 bytes ansel/themes/silver/graphics/arrow_switch.png | Bin 0 -> 683 bytes ansel/themes/silver/graphics/browse.png | Bin 0 -> 661 bytes ansel/themes/silver/graphics/galleries.png | Bin 0 -> 704 bytes ansel/themes/silver/graphics/image_add.png | Bin 0 -> 653 bytes ansel/themes/silver/graphics/lightbox/bullet.gif | Bin 0 -> 49 bytes ansel/themes/silver/graphics/lightbox/close.gif | Bin 0 -> 222 bytes .../themes/silver/graphics/lightbox/closelabel.gif | Bin 0 -> 979 bytes ansel/themes/silver/graphics/lightbox/loading.gif | Bin 0 -> 2767 bytes .../themes/silver/graphics/lightbox/nextlabel.gif | Bin 0 -> 1252 bytes .../themes/silver/graphics/lightbox/prevlabel.gif | Bin 0 -> 1264 bytes ansel/themes/silver/graphics/mini-error.png | Bin 0 -> 252 bytes ansel/themes/silver/graphics/mygalleries.png | Bin 0 -> 704 bytes ansel/themes/silver/graphics/slideshow_next.png | Bin 0 -> 607 bytes ansel/themes/silver/graphics/slideshow_pause.png | Bin 0 -> 598 bytes ansel/themes/silver/graphics/slideshow_play.png | Bin 0 -> 592 bytes ansel/themes/silver/graphics/slideshow_prev.png | Bin 0 -> 614 bytes ansel/themes/silver/graphics/text.png | Bin 0 -> 104 bytes ansel/themes/silver/themed_graphics | 0 ansel/themes/tango-blue/graphics/add.png | Bin 0 -> 635 bytes ansel/themes/tango-blue/graphics/ansel.png | Bin 0 -> 644 bytes ansel/themes/tango-blue/graphics/browse.png | Bin 0 -> 558 bytes ansel/themes/tango-blue/graphics/down.png | Bin 0 -> 672 bytes ansel/themes/tango-blue/graphics/galleries.png | Bin 0 -> 558 bytes ansel/themes/tango-blue/graphics/image_add.png | Bin 0 -> 229 bytes .../themes/tango-blue/graphics/lightbox/bullet.gif | Bin 0 -> 49 bytes .../themes/tango-blue/graphics/lightbox/close.gif | Bin 0 -> 222 bytes .../tango-blue/graphics/lightbox/closelabel.gif | Bin 0 -> 979 bytes .../tango-blue/graphics/lightbox/loading.gif | Bin 0 -> 2767 bytes .../tango-blue/graphics/lightbox/nextlabel.gif | Bin 0 -> 1252 bytes .../tango-blue/graphics/lightbox/prevlabel.gif | Bin 0 -> 1264 bytes ansel/themes/tango-blue/graphics/mini-error.png | Bin 0 -> 252 bytes ansel/themes/tango-blue/graphics/mygalleries.png | Bin 0 -> 558 bytes .../tango-blue/graphics/prettythumb-error.png | Bin 0 -> 548 bytes .../themes/tango-blue/graphics/slideshow_next.png | Bin 0 -> 736 bytes .../themes/tango-blue/graphics/slideshow_pause.png | Bin 0 -> 721 bytes .../themes/tango-blue/graphics/slideshow_play.png | Bin 0 -> 717 bytes .../themes/tango-blue/graphics/slideshow_prev.png | Bin 0 -> 745 bytes ansel/themes/tango-blue/graphics/text.png | Bin 0 -> 104 bytes ansel/themes/tango-blue/graphics/thumb-error.png | Bin 0 -> 548 bytes ansel/themes/tango-blue/graphics/up.png | Bin 0 -> 666 bytes ansel/themes/tango-blue/screen.css | 26 + ansel/themes/tango-blue/themed_graphics | 0 ansel/view.php | 57 + ansel/xppublish.php | 213 ++ 348 files changed, 60456 insertions(+) create mode 100755 ansel/.htaccess create mode 100644 ansel/COPYING create mode 100644 ansel/README create mode 100644 ansel/browse.php create mode 100644 ansel/browse_edit.php create mode 100755 ansel/config/.cvsignore create mode 100644 ansel/config/conf.xml create mode 100644 ansel/config/hooks.php.dist create mode 100644 ansel/config/prefs.php.dist create mode 100644 ansel/config/styles.php.dist create mode 100644 ansel/disclamer.php create mode 100644 ansel/docs/CHANGES create mode 100644 ansel/docs/CREDITS create mode 100644 ansel/docs/INSTALL create mode 100644 ansel/docs/RELEASE_NOTES create mode 100755 ansel/docs/TODO create mode 100755 ansel/docs/lighttpd-ansel.conf create mode 100644 ansel/edit_dates.php create mode 100644 ansel/faces/claim.php create mode 100644 ansel/faces/custom.php create mode 100644 ansel/faces/delete.php create mode 100644 ansel/faces/face.php create mode 100644 ansel/faces/gallery.php create mode 100644 ansel/faces/image.php create mode 100644 ansel/faces/img.php create mode 100644 ansel/faces/name.php create mode 100644 ansel/faces/report.php create mode 100644 ansel/faces/savecustom.php create mode 100644 ansel/faces/search/all.php create mode 100644 ansel/faces/search/image.php create mode 100644 ansel/faces/search/image_define.php create mode 100644 ansel/faces/search/image_save.php create mode 100644 ansel/faces/search/image_search.php create mode 100644 ansel/faces/search/img.php create mode 100644 ansel/faces/search/name.php create mode 100644 ansel/faces/search/named.php create mode 100644 ansel/faces/search/owner.php create mode 100644 ansel/faces/search/tabs.php create mode 100644 ansel/gallery.php create mode 100644 ansel/gallery/captions.php create mode 100644 ansel/gallery/delete.php create mode 100755 ansel/gallery/index.php create mode 100644 ansel/gallery/sort.php create mode 100644 ansel/group.php create mode 100644 ansel/image.php create mode 100644 ansel/img/download.php create mode 100644 ansel/img/ecard.php create mode 100644 ansel/img/full.php create mode 100644 ansel/img/index.php create mode 100644 ansel/img/mini.php create mode 100644 ansel/img/prettythumb.php create mode 100644 ansel/img/screen.php create mode 100644 ansel/img/thumb.php create mode 100644 ansel/img/upload.php create mode 100644 ansel/img/upload_preview.php create mode 100644 ansel/index.php create mode 100755 ansel/js/src/builder.js create mode 100644 ansel/js/src/carousel.js create mode 100644 ansel/js/src/cropper.js create mode 100644 ansel/js/src/editcaption.js create mode 100644 ansel/js/src/editfaces.js create mode 100755 ansel/js/src/embed.js create mode 100644 ansel/js/src/googlemap.js create mode 100644 ansel/js/src/googlemap_edit.js create mode 100644 ansel/js/src/imagefaces.js create mode 100755 ansel/js/src/lightbox.js create mode 100755 ansel/js/src/slideshow.js create mode 100755 ansel/js/src/slugcheck.js create mode 100755 ansel/js/src/tagactions.js create mode 100755 ansel/js/src/togglewidget.js create mode 100644 ansel/lib/Ajax/Imple/EditCaption.php create mode 100644 ansel/lib/Ajax/Imple/EditFaces.php create mode 100644 ansel/lib/Ajax/Imple/Embed.php create mode 100644 ansel/lib/Ajax/Imple/GallerySlugCheck.php create mode 100644 ansel/lib/Ajax/Imple/ImageSaveGeotag.php create mode 100644 ansel/lib/Ajax/Imple/LocationAutoCompleter.php create mode 100644 ansel/lib/Ajax/Imple/TagActions.php create mode 100644 ansel/lib/Ajax/Imple/ToggleGalleryActions.php create mode 100644 ansel/lib/Ajax/Imple/ToggleOtherGalleries.php create mode 100644 ansel/lib/Ansel.php create mode 100644 ansel/lib/Block/cloud.php create mode 100644 ansel/lib/Block/gallery.php create mode 100644 ansel/lib/Block/my_galleries.php create mode 100644 ansel/lib/Block/random_photo.php create mode 100644 ansel/lib/Block/recent_comments.php create mode 100644 ansel/lib/Block/recent_faces.php create mode 100644 ansel/lib/Block/recently_added.php create mode 100644 ansel/lib/Block/recently_added_geodata.php create mode 100644 ansel/lib/Exif.php create mode 100644 ansel/lib/Exif/canon.php create mode 100644 ansel/lib/Exif/fujifilm.php create mode 100644 ansel/lib/Exif/gps.php create mode 100644 ansel/lib/Exif/nikon.php create mode 100644 ansel/lib/Exif/olympus.php create mode 100644 ansel/lib/Exif/panasonic.php create mode 100644 ansel/lib/Exif/sanyo.php create mode 100755 ansel/lib/Faces.php create mode 100644 ansel/lib/Faces/facedetect.php create mode 100644 ansel/lib/Faces/opencv.php create mode 100644 ansel/lib/Forms/Ecard.php create mode 100644 ansel/lib/Forms/Image.php create mode 100644 ansel/lib/Forms/ImageDate.php create mode 100644 ansel/lib/Forms/Upload.php create mode 100644 ansel/lib/Forms/Watermark.php create mode 100644 ansel/lib/GalleryMode/Date.php create mode 100644 ansel/lib/GalleryMode/Normal.php create mode 100644 ansel/lib/GalleryMode/RemoteVimeo.php create mode 100644 ansel/lib/ImageView.php create mode 100755 ansel/lib/ImageView/mini.php create mode 100644 ansel/lib/ImageView/plainstack.php create mode 100644 ansel/lib/ImageView/polaroidstack.php create mode 100644 ansel/lib/ImageView/polaroidthumb.php create mode 100644 ansel/lib/ImageView/prettythumb.php create mode 100644 ansel/lib/ImageView/roundedstack.php create mode 100755 ansel/lib/ImageView/screen.php create mode 100644 ansel/lib/ImageView/shadowsharpthumb.php create mode 100755 ansel/lib/ImageView/thumb.php create mode 100644 ansel/lib/ImageView/vimeothumb.php create mode 100644 ansel/lib/Report.php create mode 100755 ansel/lib/Report/letter.php create mode 100644 ansel/lib/Report/mail.php create mode 100755 ansel/lib/Report/tickets.php create mode 100644 ansel/lib/Search.php create mode 100644 ansel/lib/Search/exif.php create mode 100644 ansel/lib/Tags.php create mode 100644 ansel/lib/Tile/DateGallery.php create mode 100644 ansel/lib/Tile/Gallery.php create mode 100644 ansel/lib/Tile/Image.php create mode 100644 ansel/lib/Views/Abstract.php create mode 100644 ansel/lib/Views/Embedded.php create mode 100644 ansel/lib/Views/EmbeddedRenderers/Carousel.php create mode 100644 ansel/lib/Views/EmbeddedRenderers/GalleryLink.php create mode 100644 ansel/lib/Views/EmbeddedRenderers/Mini.php create mode 100644 ansel/lib/Views/EmbeddedRenderers/Slideshow.php create mode 100644 ansel/lib/Views/Gallery.php create mode 100644 ansel/lib/Views/GalleryRenderer.php create mode 100644 ansel/lib/Views/GalleryRenderers/Gallery.php create mode 100644 ansel/lib/Views/GalleryRenderers/GalleryLightbox.php create mode 100644 ansel/lib/Views/GalleryRenderers/GalleryVimeo.php create mode 100644 ansel/lib/Views/Image.php create mode 100644 ansel/lib/Views/List.php create mode 100644 ansel/lib/Views/Results.php create mode 100644 ansel/lib/Views/Slideshow.php create mode 100644 ansel/lib/Widget.php create mode 100644 ansel/lib/Widget/Actions.php create mode 100644 ansel/lib/Widget/Base.php create mode 100644 ansel/lib/Widget/GalleryFaces.php create mode 100644 ansel/lib/Widget/Geodata.php create mode 100644 ansel/lib/Widget/ImageFaces.php create mode 100644 ansel/lib/Widget/Links.php create mode 100644 ansel/lib/Widget/OtherGalleries.php create mode 100644 ansel/lib/Widget/OwnerFaces.php create mode 100755 ansel/lib/Widget/SimilarPhotos.php create mode 100644 ansel/lib/Widget/Tags.php create mode 100644 ansel/lib/XPPublisher.php create mode 100644 ansel/lib/api.php create mode 100644 ansel/lib/base.php create mode 100644 ansel/lib/prefs.php create mode 100755 ansel/lib/version.php create mode 100644 ansel/locale/de_DE/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/en_US/help.xml create mode 100755 ansel/locale/es_ES/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/es_ES/help.xml create mode 100755 ansel/locale/fi_FI/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/fi_FI/help.xml create mode 100644 ansel/locale/it_IT/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/ja_JP/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/lt_LT/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/sl_SI/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/sv_SE/LC_MESSAGES/ansel.mo create mode 100644 ansel/locale/tr_TR/LC_MESSAGES/ansel.mo create mode 100755 ansel/locale/zh_TW/LC_MESSAGES/ansel.mo create mode 100644 ansel/map_edit.php create mode 100644 ansel/perms.php create mode 100755 ansel/po/.cvsignore create mode 100755 ansel/po/README create mode 100644 ansel/po/ansel.pot create mode 100644 ansel/po/de_DE.po create mode 100644 ansel/po/es_ES.po create mode 100644 ansel/po/fi_FI.po create mode 100644 ansel/po/it_IT.po create mode 100644 ansel/po/ja_JP.po create mode 100644 ansel/po/lt_LT.po create mode 100644 ansel/po/sl_SI.po create mode 100644 ansel/po/sv_SE.po create mode 100644 ansel/po/tr_TR.po create mode 100644 ansel/po/zh_TW.po create mode 100644 ansel/preview.php create mode 100644 ansel/protect.php create mode 100644 ansel/report.php create mode 100644 ansel/rss.php create mode 100755 ansel/scripts/.htaccess create mode 100644 ansel/scripts/AnselPublish.scpt create mode 100755 ansel/scripts/all_images_exif_to_tags.php create mode 100755 ansel/scripts/ansel.php create mode 100755 ansel/scripts/garbage_collection.php create mode 100755 ansel/scripts/recursive_import.php create mode 100755 ansel/scripts/remote_import.php create mode 100644 ansel/scripts/sql/ansel.pgsql.sql create mode 100644 ansel/scripts/sql/ansel.sql create mode 100644 ansel/scripts/upgrades/1.0_to_1.1.php create mode 100755 ansel/scripts/upgrades/2008-06-04-faces.sql create mode 100755 ansel/scripts/upgrades/2008-06-17_fix_varchar_lengths.sql create mode 100644 ansel/scripts/upgrades/2008-09-13_add_image_original_date.sql create mode 100755 ansel/scripts/upgrades/2008-09-16_add_original_date_values.php create mode 100644 ansel/scripts/upgrades/2008-09-23_fix_group_uid.sql create mode 100644 ansel/scripts/upgrades/2008-12-5_add_geolocation_tables.sql create mode 100644 ansel/scripts/upgrades/2009-01-10_fix_view_mode.sql create mode 100644 ansel/scripts/upgrades/2009-04-14_fix_view_mode.pgsql.sql create mode 100755 ansel/scripts/upgrades/2009-06-14_fix_geolocation_values.php create mode 100644 ansel/scripts/upgrades/2009-06-22_add_geolocation_fields.sql create mode 100644 ansel/scripts/upgrades/2009-06-22_move_geolocation_values.php create mode 100644 ansel/scripts/upgrades/2009-07-06_add_geolocation_timestamp.sql create mode 100644 ansel/templates/captions/captions.inc create mode 100644 ansel/templates/common-header.inc create mode 100755 ansel/templates/faces/custom.inc create mode 100755 ansel/templates/faces/define.inc create mode 100755 ansel/templates/faces/face.inc create mode 100755 ansel/templates/faces/faces.inc create mode 100644 ansel/templates/faces/gallery.inc create mode 100755 ansel/templates/faces/image.inc create mode 100755 ansel/templates/faces/index.inc create mode 100755 ansel/templates/faces/search.inc create mode 100644 ansel/templates/gallery/delete_confirmation.inc create mode 100644 ansel/templates/gallery/gallery.inc create mode 100644 ansel/templates/group/category.inc create mode 100755 ansel/templates/group/footer.inc create mode 100644 ansel/templates/group/header.inc create mode 100644 ansel/templates/group/owner.inc create mode 100755 ansel/templates/group/pager.inc create mode 100644 ansel/templates/image/crop_image.inc create mode 100644 ansel/templates/image/edit_image.inc create mode 100644 ansel/templates/image/preview_cropimage.inc create mode 100644 ansel/templates/image/preview_image.inc create mode 100644 ansel/templates/image/resize_image.inc create mode 100644 ansel/templates/image/upload.inc create mode 100755 ansel/templates/list/footer.inc create mode 100644 ansel/templates/list/header.inc create mode 100755 ansel/templates/list/pager.inc create mode 100644 ansel/templates/menu.inc create mode 100644 ansel/templates/prefs/default_category_select.inc create mode 100644 ansel/templates/prefs/default_gallerystyle_select.inc create mode 100644 ansel/templates/rss/rss.inc create mode 100755 ansel/templates/rss/rss2.inc create mode 100644 ansel/templates/tile/dategallery.inc create mode 100755 ansel/templates/tile/face.inc create mode 100755 ansel/templates/tile/gallery.inc create mode 100755 ansel/templates/tile/gallerymini.inc create mode 100644 ansel/templates/tile/image.inc create mode 100644 ansel/templates/view/gallery.inc create mode 100644 ansel/templates/view/gallerylightbox.inc create mode 100644 ansel/templates/view/galleryvimeo.inc create mode 100644 ansel/templates/view/image.inc create mode 100644 ansel/templates/view/list.inc create mode 100644 ansel/templates/view/results.inc create mode 100644 ansel/templates/view/slideshow.inc create mode 100755 ansel/templates/xppublish/javascript.inc create mode 100755 ansel/templates/xppublish/list.inc create mode 100755 ansel/templates/xppublish/login.inc create mode 100755 ansel/templates/xppublish/new.inc create mode 100644 ansel/test.php create mode 100755 ansel/themes/cropper.css create mode 100755 ansel/themes/embed.css create mode 100755 ansel/themes/feed-rss.xsl create mode 100644 ansel/themes/graphics/add.png create mode 100644 ansel/themes/graphics/ansel.png create mode 100644 ansel/themes/graphics/arrow_switch.png create mode 100644 ansel/themes/graphics/browse.png create mode 100755 ansel/themes/graphics/down.png create mode 100644 ansel/themes/graphics/favicon.ico create mode 100644 ansel/themes/graphics/galleries.png create mode 100644 ansel/themes/graphics/gallery-locked-mini.png create mode 100644 ansel/themes/graphics/gallery-locked.png create mode 100755 ansel/themes/graphics/image_add.png create mode 100644 ansel/themes/graphics/lightbox/bullet.gif create mode 100644 ansel/themes/graphics/lightbox/close.gif create mode 100644 ansel/themes/graphics/lightbox/closelabel.gif create mode 100644 ansel/themes/graphics/lightbox/loading.gif create mode 100644 ansel/themes/graphics/lightbox/nextlabel.gif create mode 100644 ansel/themes/graphics/lightbox/prevlabel.gif create mode 100644 ansel/themes/graphics/loading.gif create mode 100644 ansel/themes/graphics/mini-error.png create mode 100755 ansel/themes/graphics/minus.png create mode 100644 ansel/themes/graphics/mygalleries.png create mode 100755 ansel/themes/graphics/plus.png create mode 100644 ansel/themes/graphics/point.png create mode 100755 ansel/themes/graphics/prettythumb-error.png create mode 100755 ansel/themes/graphics/problem.png create mode 100644 ansel/themes/graphics/resize.png create mode 100755 ansel/themes/graphics/scaler_slider.gif create mode 100755 ansel/themes/graphics/scaler_slider_track.gif create mode 100644 ansel/themes/graphics/screen-error.png create mode 100644 ansel/themes/graphics/slideshow_next.png create mode 100644 ansel/themes/graphics/slideshow_pause.png create mode 100644 ansel/themes/graphics/slideshow_play.png create mode 100644 ansel/themes/graphics/slideshow_prev.png create mode 100755 ansel/themes/graphics/success.png create mode 100644 ansel/themes/graphics/text.png create mode 100644 ansel/themes/graphics/thumb-error.png create mode 100755 ansel/themes/graphics/up.png create mode 100644 ansel/themes/lightbox.css create mode 100644 ansel/themes/screen.css create mode 100644 ansel/themes/silver/graphics/add.png create mode 100644 ansel/themes/silver/graphics/ansel.png create mode 100644 ansel/themes/silver/graphics/arrow_switch.png create mode 100644 ansel/themes/silver/graphics/browse.png create mode 100644 ansel/themes/silver/graphics/galleries.png create mode 100644 ansel/themes/silver/graphics/image_add.png create mode 100755 ansel/themes/silver/graphics/lightbox/bullet.gif create mode 100755 ansel/themes/silver/graphics/lightbox/close.gif create mode 100755 ansel/themes/silver/graphics/lightbox/closelabel.gif create mode 100755 ansel/themes/silver/graphics/lightbox/loading.gif create mode 100755 ansel/themes/silver/graphics/lightbox/nextlabel.gif create mode 100755 ansel/themes/silver/graphics/lightbox/prevlabel.gif create mode 100644 ansel/themes/silver/graphics/mini-error.png create mode 100644 ansel/themes/silver/graphics/mygalleries.png create mode 100755 ansel/themes/silver/graphics/slideshow_next.png create mode 100755 ansel/themes/silver/graphics/slideshow_pause.png create mode 100755 ansel/themes/silver/graphics/slideshow_play.png create mode 100755 ansel/themes/silver/graphics/slideshow_prev.png create mode 100644 ansel/themes/silver/graphics/text.png create mode 100644 ansel/themes/silver/themed_graphics create mode 100644 ansel/themes/tango-blue/graphics/add.png create mode 100644 ansel/themes/tango-blue/graphics/ansel.png create mode 100644 ansel/themes/tango-blue/graphics/browse.png create mode 100755 ansel/themes/tango-blue/graphics/down.png create mode 100644 ansel/themes/tango-blue/graphics/galleries.png create mode 100755 ansel/themes/tango-blue/graphics/image_add.png create mode 100755 ansel/themes/tango-blue/graphics/lightbox/bullet.gif create mode 100755 ansel/themes/tango-blue/graphics/lightbox/close.gif create mode 100755 ansel/themes/tango-blue/graphics/lightbox/closelabel.gif create mode 100755 ansel/themes/tango-blue/graphics/lightbox/loading.gif create mode 100755 ansel/themes/tango-blue/graphics/lightbox/nextlabel.gif create mode 100755 ansel/themes/tango-blue/graphics/lightbox/prevlabel.gif create mode 100755 ansel/themes/tango-blue/graphics/mini-error.png create mode 100644 ansel/themes/tango-blue/graphics/mygalleries.png create mode 100755 ansel/themes/tango-blue/graphics/prettythumb-error.png create mode 100755 ansel/themes/tango-blue/graphics/slideshow_next.png create mode 100755 ansel/themes/tango-blue/graphics/slideshow_pause.png create mode 100755 ansel/themes/tango-blue/graphics/slideshow_play.png create mode 100755 ansel/themes/tango-blue/graphics/slideshow_prev.png create mode 100644 ansel/themes/tango-blue/graphics/text.png create mode 100755 ansel/themes/tango-blue/graphics/thumb-error.png create mode 100755 ansel/themes/tango-blue/graphics/up.png create mode 100644 ansel/themes/tango-blue/screen.css create mode 100644 ansel/themes/tango-blue/themed_graphics create mode 100644 ansel/view.php create mode 100644 ansel/xppublish.php diff --git a/ansel/.htaccess b/ansel/.htaccess new file mode 100755 index 000000000..3a722c27f --- /dev/null +++ b/ansel/.htaccess @@ -0,0 +1,18 @@ + + RewriteEngine On + RewriteRule ^user/?$ group.php?groupby=owner [QSA,L] + RewriteRule ^category/?$ group.php?groupby=category [QSA,L] + RewriteRule ^all/?$ view.php?view=List&groupby=none [QSA,L] + RewriteRule ^user/([@a-zA-Z0-9%_+.!*',()~-]*)/rss/?$ rss.php?stream_type=user&id=$1 [L] + RewriteRule ^user/([@a-zA-Z0-9%_+.!*',()~-]*)/?$ view.php?view=List&groupby=owner&owner=$1 [QSA,L] + RewriteRule ^gallery/id/([0-9]+)/rss/?$ rss.php?stream_type=gallery&id=$1 [QSA,L] + RewriteRule ^gallery/id/([0-9]+)/([0-9]+)/? view.php?view=Image&gallery=$1&image=$2 [QSA,L] + RewriteRule ^gallery/id/([0-9]+)/?$ view.php?view=Gallery&gallery=$1 [QSA,L] + RewriteRule ^gallery/([a-zA-Z0-9_@]+)/rss/?$ rss.php?stream_type=gallery&slug=$1 [L] + RewriteRule ^gallery/([a-zA-Z0-9_@]+)/([0-9]+)/? view.php?view=Image&slug=$1&image=$2 [QSA,L] + RewriteRule ^gallery/([a-zA-Z0-9_@]+)/?$ view.php?view=Gallery&slug=$1 [QSA,L] + RewriteRule ^category/([@a-zA-Z0-9%_+.!*',()~-]*)/?$ view.php?view=List&groupby=category&category=$1 [QSA,L] + RewriteRule ^tag/?$ view.php?view=Results [QSA,L] + RewriteRule ^tag/([a-zA-Z0-9%_+.!*',()~-]*)/rss/?$ rss.php?stream_type=tag&id=$1 [QSA,L] + RewriteRule ^tag/([a-zA-Z0-9%_+.!*',()~-]*)/?$ view.php?view=Results&tag=$1 [QSA,L] + diff --git a/ansel/COPYING b/ansel/COPYING new file mode 100644 index 000000000..a6b67561a --- /dev/null +++ b/ansel/COPYING @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 diff --git a/ansel/README b/ansel/README new file mode 100644 index 000000000..95d973064 --- /dev/null +++ b/ansel/README @@ -0,0 +1,121 @@ +What is Ansel? +============== + +:Last update: $Date: 2008/10/27 19:57:04 $ +:Revision: $Revision: 1.5 $ + +.. contents:: Contents +.. section-numbering:: + +Ansel is the Horde photo management application. It makes use of the Horde frame +work and integrates with other Horde applications. Ansel is nearing 1.0 quality, +but is not yet officially released software. It can be obtained by downloading +a CVS snapshot , or by checking it out from anonymous CVS. + +Ansel is a full featured photo management application. With it, you can create +any number of galleries and subgalleries, share galleries among other Horde +users or even make them public. You can upload images either one at a time, as a +zip archive, or even upload them via Windows XP's "Publish to Web" +functionality. Once in Ansel, you can browse your images, download originals, +view galleries as a slideshow, add comments to images, send an 'ecard' to a +friend, and more. You can crop, resize and perform other image maniputlation +functions. You can even have Ansel try to recognize people in your photos +with it's support for face detection. Ansel will read and store EXIF data +associated with uploaded images and can even auto rotate your images to the +proper position if the image contains the proper EXIF properties. + +Ansel supports photo and gallery tagging - allowing you to browse your photos by +tags just like you were browsing a directory. You can also choose to have +certain EXIF tags automatically added to an image on upload. + +Mulitple gallery themes are available. You can create thumbnails that have +rounded corners with shadows, sharp corners with shadows, or even make your +thumbnails look like Polaroids! Image viewing can use a lightbox-type overlay +on the gallery page, or can be a seperate image view page that can display +image attributes, comments and other types of information. + +There are multiple ways to organize your photos within galleries. You can create +subgalleries to further organize photos and you can also have Ansel +automatically display a gallery's photos to you grouped by date. + +Ansel supports mulitiple ways to access your photos from outside of Ansel. There +are mutlitple types of RSS feeds available, including most recently added images +for a particular user, gallery or tag. The ability to embed widgets of your +photos into external websites, such as blogs, is also available. + +For developers, Ansel also offers an external api that may be accessed via +Horde's RPC server or directly through the Horde Registry. There are api +methods that make it extremely easy to embed gallery views on your own website. + +This software is OSI Certified Open Source Software. OSI Certified is a +certification mark of the `Open Source Initiative`_. + +.. _`Open Source Initiative`: http://www.opensource.org/ + + +Obtaining Ansel +--------------- + +Further information on Ansel and the latest version can be obtained at + + http://www.horde.org/ansel/ + + +Documentation +------------- + +The following documentation is available in the Ansel distribution: + +:README_: This file +:COPYING_: Copyright and license information +:`docs/CHANGES`_: Changes by release +:`docs/CREDITS`_: Project developers +:`docs/INSTALL`_: Installation instructions and notes +:`docs/TODO`_: Development TODO list + + +Installation +------------ + +Instructions for installing Ansel can be found in the file INSTALL_ in the +``docs/`` directory of the Ansel distribution. + + +Assistance +---------- + +If you encounter problems with Ansel, help is available! + +The Horde Frequently Asked Questions List (FAQ), available on the Web at + + http://www.horde.org/faq/ + +The Horde Project runs a number of mailing lists, for individual applications +and for issues relating to the project as a whole. Information, archives, and +subscription information can be found at + + http://www.horde.org/mail/ + +Lastly, Horde developers, contributors and users also make occasional +appearances on IRC, on the channel #horde on the freenode Network +(irc.freenode.net). + + +Licensing +--------- + +For licensing and copyright information, please see the file COPYING_ in the +Ansel distribution. + +Thanks, + +The Ansel team + + +.. _README: ?f=README.html +.. _COPYING: http://www.horde.org/licenses/gpl.php +.. _docs/CHANGES: ?f=CHANGES.html +.. _docs/CREDITS: ?f=CREDITS.html +.. _INSTALL: +.. _docs/INSTALL: ?f=INSTALL.html +.. _docs/TODO: ?f=TODO.html diff --git a/ansel/browse.php b/ansel/browse.php new file mode 100644 index 000000000..72dd3f85f --- /dev/null +++ b/ansel/browse.php @@ -0,0 +1,28 @@ +getValue('myansel_layout')), + Horde::applicationUrl('browse_edit.php'), + Horde::applicationUrl('browse.php', true)); + +$layout_html = $layout->toHtml(); +$title = _("Photo Galleries"); +Ansel_Tags::clearSearch(); +require ANSEL_BASE . '/templates/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +echo '
 
'; +echo $layout_html; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/browse_edit.php b/ansel/browse_edit.php new file mode 100644 index 000000000..f31ac71fe --- /dev/null +++ b/ansel/browse_edit.php @@ -0,0 +1,33 @@ +getValue('myansel_layout'))); + +// Handle requested actions. +$layout->handle(Horde_Util::getFormData('action'), + (int)Horde_Util::getFormData('row'), + (int)Horde_Util::getFormData('col'), + Horde_Util::getFormData('url')); +if ($layout->updated()) { + $prefs->setValue('myansel_layout', $layout->serialize()); +} + +$title = _("My Photos :: Add Content"); +require ANSEL_TEMPLATES . '/common-header.inc'; +echo ''; +$notification->notify(array('listeners' => 'status')); +require $registry->get('templates', 'horde') . '/portal/edit.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/config/.cvsignore b/ansel/config/.cvsignore new file mode 100755 index 000000000..cd8ce9795 --- /dev/null +++ b/ansel/config/.cvsignore @@ -0,0 +1,5 @@ +conf.php +conf.bak.php +hooks.php +prefs.php +styles.php diff --git a/ansel/config/conf.xml b/ansel/config/conf.xml new file mode 100644 index 000000000..7326e0309 --- /dev/null +++ b/ansel/config/conf.xml @@ -0,0 +1,259 @@ + + + + + + User Interface + + + + false + true + + + Menu Settings + true + + + + + + + + + + VFS Options + + + + + + + + + + + + + + Photo Generation Options + + + jpeg + png + + + + 0 + false + 5 + + + + + + + Thumbnail Settings + 30 + 150 + 150 + + + + + + Screen Image Settings + 800 + 600 + + + + + + Ecard Settings + false + + + + + + Photo Comments + never + + authenticated + never + all + + + + + + + + Cache Settings + false + + + + + + Tag Settings + false + + + + + + Content Reporting Settings + false + + + + array() + all + + all + authenticated + never + + + + + webmaster@example.com + example.com + + + all + + all + authenticated + never + + + + + + all + + all + authenticated + never + + + + + + + + + + Age Settings + + false + + + + + + Face Detection + user + + + + /usr/share/opencv/haarcascades/haarcascade_frontalface_alt.xml + + + /usr/share/opencv/haarcascades/haarcascade_frontalface_alt.xml + + + 7 + + + + + + false + + + diff --git a/ansel/config/hooks.php.dist b/ansel/config/hooks.php.dist new file mode 100644 index 000000000..761c4ef02 --- /dev/null +++ b/ansel/config/hooks.php.dist @@ -0,0 +1,96 @@ +quote(Horde_Auth::getAuth()); +// $result = $GLOBALS['ansel_db']->queryOne($query); +// if (is_a($result, 'PEAR_Error')) { +// throw new Horde_Exception($result); +// } +// +// return (int)$result; +// } + + + /** + * Example hook for sending a stream notification to facebook after a user + * uploads images to Ansel. This will likely go away, being replaced by + * by an remote image import/export functionality. + */ +// function postupload($image_ids) +// { +// $context = array('http_client' => new Horde_Http_Client(), +// 'http_request' => new Horde_Controller_Request_Http(),); +// $facebook = new Horde_Service_Facebook($GLOBALS['conf']['facebook']['key'], +// $GLOBALS['conf']['facebook']['secret'], +// $context); +// $fbp = unserialize($GLOBALS['prefs']->getValue('facebook')); +// +// // If no prefs exist just exit since there's nowhere to publish to. +// if (empty($fbp['sid'])) { +// return; +// } else { +// $facebook->auth->setUser($fbp['uid'], $fbp['sid'], 0); +// } +// // Limit of the number of images to include. This should really come +// // from a configuration parameter or at the very least, a user pref. +// // (5 is the facebook api's max) +// $limit = min(5, (count($image_ids))); +// $images = $GLOBALS['ansel_storage']->getImages(array_slice($image_ids, 0, $limit)); +// $perms = array(); +// $media = array(); +// foreach ($images as $image) { +// // Only the gallery owner should be able to publish news about the +// // gallery, and only public galleries with no passwd or age checks +// // should be considered as well since the links on facebook would +// // be useless. +// if (!isset($perms[$image->gallery])) { +// $g = $GLOBALS['ansel_storage']->getGallery($image->gallery); +// $pwd = $g->get('passwd'); +// $no_agelimit = empty($GLOBALS['conf']['ages']['limits']) || $g->get('age') == 0; +// if ($g->get('owner') == Horde_Auth::getAuth() && empty($pwd) && $no_agelimit) { +// $perms[$image->gallery] = true; +// } else { +// $perms[$image->gallery] = false; +// } +// } +// if ($perms[$image->gallery]) { +// $media[] = array('type' => 'image', +// 'href' => Ansel::getUrlFor('view',array('view' => 'Image', 'image' => $image->id, 'gallery' => $image->gallery), true, -1), +// 'src' => Ansel::getImageUrl($image->id, 'thumb', true)); +// +// } +// } +// // For this example, just use the last image's gallery title and description +// $attachment = array('name' => $g->get('name'), 'caption' => $g->get('description'), 'media' => $media); +// +// //Do it. +// try { +// $facebook->streams->publish(sprintf("just uploaded these pictures to %s", $GLOBALS['registry']->get('name')), $attachment); +// } catch (Horde_Service_Facebook_Exception $e) { +// // For now, just pass back as a pear error...needs to be cleaned up +// $GLOBALS['notification']->push('Horde_Service_Facebook: ' . $e->getMessage(), 'horde.err'); +// throw new Horde_Exception($e); +// } +// if (!empty($GLOBALS['notification'])) { +// $GLOBALS['notification']->push('Notification published to Facebook.', 'horde.success'); +// } +// } + +} diff --git a/ansel/config/prefs.php.dist b/ansel/config/prefs.php.dist new file mode 100644 index 000000000..701e6bea1 --- /dev/null +++ b/ansel/config/prefs.php.dist @@ -0,0 +1,273 @@ + _("General Options"), + 'label' => _("Display Options"), + 'desc' => _("Change display options such as which view to display by default, how many photos to display on a page, and the default gallery style to use."), + 'members' => array('grouptitle', 'defaultview', 'tilesperrow', + 'tilesperpage', 'facesperpage', 'groupby', + 'groupsperpage', + 'default_gallerystyle_select', 'default_category_select') +); + +$prefGroups['metadata'] = array( + 'column' => _("General Options"), + 'label' => _("Tags and EXIF Options"), + 'desc' => _("Change options dealing with tags and exif data."), + 'members' => array('showexif', 'exif_tags') +); + +$prefGroups['perms'] = array( + 'column' => _("General Options"), + 'label' => _("Permission Options"), + 'desc' => _("Change your user permission options such as who can download original photos, and what permissions newly created galleries should have by default."), + 'members' => array('default_download', 'default_permissions', 'group_permissions', 'guest_permissions') +); + +$prefGroups['watermark'] = array( + 'column' => _("General Options"), + 'label' => _("Watermark Options"), + 'desc' => _("Change your watermark options."), + 'members' => array('watermark_text', 'watermark_vertical', 'watermark_horizontal', + 'watermark_font', 'watermark_auto') +); + +/* Note that for the following to work, your pref backend must support + retrieving prefs for other users (such as the SQL backend) */ +$_prefs['grouptitle'] = array( + 'value' => '', + 'locked' => false, + 'shared' => false, + 'type' => 'text', + 'desc' => _("Custom text to display describing your galleries. This will be displayed in place of your username when grouping galleries by username.")); + +$_prefs['defaultview'] = array( + 'value' => 'galleries', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array('browse' => _("Browse"), + 'galleries' => _("Galleries"), + 'mygalleries' => _("My Galleries") + ), + 'desc' => _("View to display by default") +); + +$_prefs['groupby'] = array( + 'value' => 'none', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array('owner' => _("Owner"), + 'category' => _("Category"), + 'none' => _("None")), + 'desc' => _("Group galleries by") +); + +// number of photos on each row in the gallery view +$_prefs['tilesperrow'] = array( + 'value' => 3, + 'locked' => false, + 'shared' => false, + 'type' => 'number', + 'desc' => _("Number of tiles per row") +); + +$_prefs['tilesperpage'] = array( + 'value' => 9, + 'locked' => false, + 'shared' => false, + 'type' => 'number', + 'desc' => _("Number of tiles per page") +); + +$_prefs['facesperpage'] = array( + 'value' => '20', + 'locked' => !$GLOBALS['conf']['faces']['driver'], + 'shared' => false, + 'type' => 'number', + 'desc' => _("Number of faces per page") +); + +$_prefs['groupsperpage'] = array( + 'value' => 9, + 'locked' => false, + 'shared' => false, + 'type' => 'number', + 'desc' => _("Number of groups per page") +); + +$_prefs['showexif'] = array( + 'value' => false, + 'locked' => false, + 'shared' => false, + 'type' => 'checkbox', + 'desc' => _("Show EXIF data") +); + +$_prefs['watermark'] = array( + 'value' => '', + 'locked' => false, + 'shared' => false, + 'type' => 'text', + 'desc' => _("Custom watermark to use for photos") +); + +$_prefs['myansel_layout'] = array( + 'value' => 'a:1:{i:0;a:3:{i:0;a:4:{s:3:"app";s:5:"ansel";s:6:"height";i:1;s:5:"width";i:1;s:6:"params";a:2:{s:4:"type";s:5:"cloud";s:6:"params";a:1:{s:5:"count";s:2:"20";}}}i:1;a:4:{s:3:"app";s:5:"ansel";s:6:"height";i:1;s:5:"width";i:1;s:6:"params";a:2:{s:4:"type";s:12:"my_galleries";s:6:"params";a:0:{}}}i:2;a:4:{s:3:"app";s:5:"ansel";s:6:"height";i:1;s:5:"width";i:1;s:6:"params";a:2:{s:4:"type";s:14:"recently_added";s:6:"params";a:2:{s:7:"gallery";s:3:"all";s:5:"limit";s:2:"10";}}}}}', + 'locked' => false, + 'shared' => false, + 'type' => 'implicit' +); + +$_prefs['default_gallerystyle'] = array( + 'value' => 'ansel_default', + 'locked' => false, + 'shared' => false, + 'type' => 'implicit' +); +$_prefs['default_gallerystyle_select'] = array( + 'type' => 'special' +); + +// Default category +$_prefs['default_category'] = array( + 'value' => '', + 'locked' => false, + 'shared' => false, + 'type' => 'implicit' +); + +// Default category +$_prefs['default_category_select'] = array( + 'type' => 'special' +); + +$_prefs['show_actions'] = array( + 'value' => 0, + 'locked' => false, + 'shared' => false, + 'type' => 'implicit', +); + +$_prefs['show_othergalleries'] = array( + 'value' => 0, + 'locked' => false, + 'shared' => false, + 'type' => 'implicit' +); + + +$_prefs['watermark_text'] = array( + 'value' => '', + 'locked' => false, + 'shared' => false, + 'type' => 'text', + 'desc' => _("Custom watermark to use for photos") +); + +$_prefs['watermark_horizontal'] = array( + 'value' => 'left', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array('left' => _("Left"), + 'center' => _("Center"), + 'right' => _("Right")), + 'desc' => _("Horizontal Alignment") +); + +$_prefs['watermark_vertical'] = array( + 'value' => 'bottom', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array('top' => _("Top"), + 'center' => _("Center"), + 'bottom' => _("Bottom")), + 'desc' => _("Vertical Alignment") +); + +$_prefs['watermark_font'] = array( + 'value' => 'bottom', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array('tiny' => _("Tiny"), + 'small' => _("Small"), + 'medium' => _("Medium"), + 'large' => _("Large"), + 'giant' => _("Giant")), + 'desc' => _("Vertical Alignment") +); + +$_prefs['watermark_auto'] = array( + 'value' => 0, + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array(_("No"), _("Yes")), + 'desc' => _("Automatically watermark photos?") +); + +$_prefs['default_download'] = array( + 'value' => 'edit', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'enum' => array('all' => _("Anyone"), + 'edit' => _("Authenticated users"), + 'authenticated' => _("Users with edit permissions")), + 'desc' => _("Who should be allowed to download original photos") +); + +$_prefs['default_permissions'] = array( + 'value' => 'read', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'desc' => _("When a new gallery is created, what permissions should be given to authenticated users by default?"), + 'enum' => array('none' => _("None"), + 'read' => _("Read-only"), + 'edit' => _("Read and write")) +); + +$_prefs['guest_permissions'] = array( + 'value' => 'read', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'desc' => _("When a new gallery is created, what permissions should be given to guests by default?"), + 'enum' => array('none' => _("None (Owner only)"), + 'read' => _("Read-only")) +); + +$_prefs['group_permissions'] = array( + 'value' => 'none', + 'locked' => false, + 'shared' => false, + 'type' => 'enum', + 'desc' => _("When a new gallery is created, what default permissions should be given to groups that the user is a member of?"), + 'enum' => array('none' => _("None"), + 'read' => _("Read-only"), + 'edit' => _("Read and write"), + 'delete' => _("Read, write, and delete")) +); + +$_prefs['exif_tags'] = array( + 'value' => 'a:0{}', + 'locked' => false, + 'shared' => false, + 'type' => 'multienum', + 'desc' => _("Which EXIF fields should we automatically add as photo tags during upload?"), + 'enum' => array('DateTimeOriginal' => _("Date Photo Taken"), + 'ExposureTime' => _("Exposure Time")) +); \ No newline at end of file diff --git a/ansel/config/styles.php.dist b/ansel/config/styles.php.dist new file mode 100644 index 000000000..2e827cfb0 --- /dev/null +++ b/ansel/config/styles.php.dist @@ -0,0 +1,190 @@ + array(), + 'Tags' => array('view' => 'gallery'), + 'OtherGalleries' => array(), + 'Geodata' => array(), + 'Links' => array(), + 'GalleryFaces' => array(), + 'OwnerFaces' => array()); + + +$styles['ansel_default'] = array( + 'name' => 'ansel_default', + 'title' => _("Default"), + 'thumbstyle' => 'thumb', + 'background' => 'none', + 'widgets' => $widgets, +); + +$styles['ansel_prettythumbs'] = array( + 'name' => 'ansel_prettythumbs', + 'title' => _("Pretty Thumbnails (No Background)"), + 'thumbstyle' => 'prettythumb', + 'requires_png' => true, + // This really only looks good with ImageMagick, not GD. + 'default_galleryimage_type' => 'roundedstack', + 'background' => 'none', + 'widgets' => $widgets, +); + +$styles['ansel_blackonwhite'] = array( + 'name' => 'ansel_blackonwhite', + 'title' => _("Pretty Thumbnails (White Background)"), + 'default_galleryimage_type' => 'roundedstack', + 'thumbstyle' => 'prettythumb', + 'background' => 'white', + 'widgets' => $widgets, +); + +$styles['ansel_sharpshadowed'] = array( + 'name' => 'ansel_sharpshadowed', + 'title' => _("Shadowed Thumbnails (White Background)"), + 'thumbstyle' => 'shadowsharpthumb', + 'background' => 'white', + 'default_galleryimage_type' => 'plainstack', + 'widgets' => $widgets +); + +/* Polaoid style thumbnails and stacks */ +$styles['ansel_polaroid'] = array( + 'name' => 'ansel_polaroid', + 'title' => _("Polaroid Style Thumbnails (White Background)"), + 'thumbstyle' => 'polaroidthumb', + 'background' => 'white', + 'default_galleryimage_type' => 'polaroidstack', + 'widgets' => $widgets, +); + +/* Lightbox image views */ +$styles['ansel_lightbox'] = array( + 'name' => 'ansel_lightbox', + 'title' => _("A Lightbox Inspired Style (White Background)"), + 'thumbstyle' => 'thumb', + 'background' => 'white', + 'gallery_view' => 'GalleryLightbox', + 'widgets' => $widgets, +); + + +/* Lightbox image views with no background + * (requires PNG) */ +$styles['ansel_lightbox_png'] = array( + 'name' => 'ansel_lightbox_png', + 'title' => _("A Lightbox Inspired Style (No Background)"), + 'thumbstyle' => 'thumb', + 'background' => 'none', + 'requires_png' => true, + 'gallery_view' => 'GalleryLightbox', + 'widgets' => $widgets, +); + +/* Lightbox image views with no background and shadowed thumbs + * (requires PNG) */ +$styles['ansel_lightbox_shadowed_png'] = array( + 'name' => 'ansel_lightbox_shadowed_png', + 'title' => _("A Lightbox Inspired Style (Drop Shadows, No Background)"), + 'thumbstyle' => 'shadowsharpthumb', + 'background' => 'none', + 'requires_png' => true, + 'gallery_view' => 'GalleryLightbox', + 'widgets' => $widgets, + 'fallback' => 'ansel_lightbox' +); + + +/* Same as above, but with Polaroid thumbnails/stacks + * and no background (so required png support) */ +$styles['ansel_lightbox_polaroid'] = array( + 'name' => 'ansel_lightbox_polaroid', + 'title' => _("Lightbox with Polaroids (No Background)"), + 'thumbstyle' => 'polaroidthumb', + 'background' => 'none', + 'requires_png' => true, + 'default_galleryimage_type' => 'polaroidstack', + 'gallery_view' => 'GalleryLightbox', + 'widgets' => $widgets, +); + +/* Simple styles with no Ansel_Widgets useful for rendering on external sites + * via the API. Note that some require PNG support, but fallback to ansel_simple + * if no PNG support is found. You could also create your own simple style with + * no PNG support required and an appropriate background color for your site + * indicated */ +$styles['ansel_simple'] = array( + 'name' => 'ansel_simple', + 'title' => _("Simple"), + 'thumbstyle' => 'thumb', + 'background' => 'none', + 'hide' => true, +); + +/* An API friendly lightbox style */ +$styles['ansel_lightbox_simple'] = array( + 'name' => 'ansel_lightbox_simple', + 'title' => _("Simple Lightbox"), + 'thumbstyle' => 'thumb', + 'background' => 'none', + 'requires_png' => true, + 'gallery_view' => 'GalleryLightbox', + 'hide' => true, + 'fallback' => 'ansel_simple' +); + +/* Same as above, but with polaroid thumbnails */ +$styles['ansel_lightbox_simple_polaroid'] = array( + 'name' => 'ansel_lightbox_polaroid', + 'title' => _("Lightbox with Polaroids (No Background)"), + 'thumbstyle' => 'polaroidthumb', + 'background' => 'none', + 'requires_png' => true, + 'default_galleryimage_type' => 'polaroidstack', + 'gallery_view' => 'GalleryLightbox', + 'hide' => true, + 'fallback' => 'ansel_simple' +); diff --git a/ansel/disclamer.php b/ansel/disclamer.php new file mode 100644 index 000000000..0023b2be1 --- /dev/null +++ b/ansel/disclamer.php @@ -0,0 +1,52 @@ + + */ + +require_once dirname(__FILE__) . '/lib/base.php'; +require_once 'Horde/Form.php'; + +$vars = Horde_Variables::getDefaultVariables(); +$gallery = $ansel_storage->getGallery($vars->get('gallery')); +if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage()); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; +} +$url = $vars->get('url'); + + +$form = new Horde_Form($vars, _("Content Disclaimer"), 'disclamer'); +$form->addVariable($gallery->get('name'), 'name', 'description', false); +$form->addVariable($gallery->get('desc'), 'desc', 'description', false); +$form->addHidden('', 'url', 'text', true); +$form->addHidden('', 'gallery', 'int', true); +$msg = sprintf(_("Photo content may be offensive. You must be over %d to continue."), $gallery->get('age')); +$form->addVariable($msg, 'warning', 'description', false); +$form->setButtons(array(sprintf(_("Continue - I'm over %d"), $gallery->get('age')), _("Cancel"))); + +if ($form->isSubmitted()) { + if (Horde_Util::getFormData('submitbutton') == _("Cancel")) { + $notification->push("You are not authorised to view this photo.", 'horde.warning'); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; + } else { + $_SESSION['ansel']['user_age'] = (int)$gallery->get('age'); + header('Location: ' . $url, true); + exit; + } +} + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +$form->renderActive(null, null, null, 'post'); + +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/docs/CHANGES b/ansel/docs/CHANGES new file mode 100644 index 000000000..32b0dee6d --- /dev/null +++ b/ansel/docs/CHANGES @@ -0,0 +1,129 @@ +-------- +v1.1-cvs +-------- + +[mjr] Fix issue with uploading images to galleries in `Browse by Date` mode + causing the mode to be reset to `Normal`. +[mjr] Return to the previously viewed page number when manually selecting + face ranges (Bug: 8402). +[mjr] Significantly reduce the number of javascript errors due to IE issues. +[mjr] Allow the addition of geolocation data on images that do not contain any + and allow the relocating of images that do have the data. +[mjr] Fix display of EXIF date values (Bug: 8352). +[mjr] Display a Google Map with thumbnail markers for image location(s) if + location data is available. +[mjr] Add configuration switch to disallow querying the preference backend for + other user's identities and preferences (Bug: 8269). +[mjr] Remove broken implementation of the 'numlimit' configuration option + (See Bug: 8139). +[jan] Fix icon URLs if cookies are turned off. +[jan] Fix ansel_shares table definition for PostgreSQL. +[mjr] Fix issue that caused screen image creation to fail when using gd + Horde_Image driver. +[mjr] Fix issue with screen images being generated as PNGs if not using VFS + Direct serving. + +---- +v1.0 +---- + +[mjr] Force screen images to be jpegs to reduce image size. +[mjr] Add a new api method for obtaining the available gallery styles. +[mjr] Add a widget to the gallery view for showing faces that the gallery owner + has tagged. +[mjr] Allow embedding screen size images in external sites as well. +[mjr] Add configuration parameter to require a minimum number of + images to be present in a gallery before it's displayed in a + gallery list. (Duck , Request # 7949) +[mjr] Add a Link widget to the Image View. +[mjr] Correct some undefined variable warnings. (Duck ) +[mjr] Fix default parameters for the ansel tag cloud. + +-------- +v1.0-RC1 +-------- + +[jan] Add Turkish translation (Akif Dinc ). +[jan] Change group field in shares table to work with LDAP groups (Bug #6883). +[jan] Add WebDAV interface. +[jan] Add Japanese translation (Takeshi Taguchi ). +[mjr] The xppublish feature now uses the correct (not temporary) filenames. +[cjh] Use the zip extension to read uploaded ZIP files if it's available. +[mjr] Added rounded, shadowed thumbnails and a 'Polaroid-like' stack of images + to use as gallery default images. +[mjr] Add RSS feeds that can publish feeds across all images, for specific + user, specific gallery, and specific tag. +[mjr] Implement a searchTag api so other applications can list tags and perform + tag searches of Ansel resources. +[mjr] Add support for image and gallery tags. +[mjr] New blocks for listing recently added images and image comments. +[mjr] Image comments, titles, and links now correctly update during slideshow + and image navigation. +[cjh] Give Ansel its own Share implementation for now as hierarchical shares + are being removed from the main Horde_Share class. +[cjh] Merge in updates from Exifer 1.5 (final Exifer release), along with some + related code cleanup. +[cjh] Fix screen-size previews of edit operations. +[cjh] Move images from the DataTree to their own SQL table. +[jan] Add Slovenian translation (Duck ). +[mjr] Add an addGallery() method to the external api. +[cjh] Integrate slideshow and image views (Bug #3785). +[cjh] Add creation of subgalleries, setting gallery owners, and setting image + captions to the ansel.php CLI (dorm@dorm.org, Request #3986). +[cjh] Implement drag-and-drop sorting of images. +[mas] Fix undefined index warning if no galleries are defined (Bug #2172). +[jan] Add Swedish translation (Andreas Dahlén ). +[jan] Add Finnish translation (Leena Heino ). +[jan] Add Lithuanian translation (Vilius Sumskas ). +[cjh] Use new Horde_Menu CSS styled menu. +[cjh] Remove view/ directory as it wasn't being maintained. +[cjh] Add automatic rotation of images according to Exif information + (Brian Templeton ). +[cjh] Add API methods (Duck , Roel Gloudemans + ). +[cjh] Images can now have categories using the Horde-wide category system + (Duck ). +[jan] Add option to not prompt for gallery ID (Duck ). +[cjh] The default gallery "list" format now looks like the gallery image + listing view (Duck ). +[jan] Add comments on images. +[jan] Recursively delete complete directories when deleting images. +[jan] Don't convert image format when downloading the full image. +[jan] Store images with correct file extension in the VFS backend. +[mms] Allow images to be sent as an ecard. +[jan] Add Spanish translation (Manuel Perez Ayala ). +[jan] Add Traditional Chinese translation (David Chang ). +[cjh] Command line interface to Ansel (Vijay Mahrra + ). +[cjh] Store EXIF data for uploaded images into the backend on upload. + ("Heath S. Hendrickson" ). +[cjh] Allow re-ordering images in a gallery. + ("Heath S. Hendrickson" ). +[cjh] Add placeholder tokens in watermark strings + ("Heath S. Hendrickson" ). +[cjh] Allow custom watermark text + ("Heath S. Hendrickson" ). +[cjh] Link for showing exif data in a popup window + ("Heath S. Hendrickson" ). +[cjh] Preference for whether or not to show exif data in the image + view ("Heath S. Hendrickson" ). +[cjh] Support for PHP's native exif_read_data() function + ("Heath S. Hendrickson" ). +[cjh] Add batch-setting of captions. +[cjh] Add previous and next buttons for navigation within galleries + (Brian Keifer ). +[cjh] Define minimum, maximum, and default sizes for thumbnail and screen + images, and allow setting those values in the min-max range on a + per-gallery basis (Ben Chavet ). +[cjh] Authenticated users can now download a zip file containing all images in + a gallery. +[cjh] Add creation and display of subgalleries. +[cjh] Let users enter the short ids for galleries, instead of using 32 + character md5 strings. +[cjh] Add slideshows. +[cjh] Implement support for Windows XP's Publish To Web wizard. +[cjh] Add display of embedded exif information in images. Adapted from Exifer, + by Jake Olefsky . +[cjh] Show tooltips containing filename and image descriptions. +[jan] Add German translation. +[cjh] Initial Ansel code. diff --git a/ansel/docs/CREDITS b/ansel/docs/CREDITS new file mode 100644 index 000000000..a6d7bd337 --- /dev/null +++ b/ansel/docs/CREDITS @@ -0,0 +1,36 @@ +======================== + Ansel Development Team +======================== + + +Core Developers +=============== + +- Chuck Hagenbuch +- Michael J. Rubinsky + + +Features +======== + +The Exif-reading code was written by Jake Olefsky . + +The original Exifer is available from +http://www.offsky.com/software/exif/index.php, and Jake has given his +permission for the library to be integrated into Ansel. + + +Localization +============ + +====================== =============================================== +Chinese (Traditional) David Chang +Finnish Leena Heino +German Jan Schneider +Japanese Takeshi Taguchi +Lithuanian Vilius Å umskas +Slovenian Duck +Spanish Manuel Perez Ayala +Swedish Andreas Dahlén +Turkish Akif Dinc +====================== =============================================== diff --git a/ansel/docs/INSTALL b/ansel/docs/INSTALL new file mode 100644 index 000000000..8cfdaeb54 --- /dev/null +++ b/ansel/docs/INSTALL @@ -0,0 +1,301 @@ +====================== + Installing Ansel 1.0 +====================== + +:Last update: $Date: 2009/05/15 19:48:54 $ +:Revision: $Revision: 1.13 $ + +.. contents:: Contents +.. section-numbering:: + +This document contains instructions for installing the Ansel Photo Manager. + +For information on the capabilities and features of Ansel, see the file +README_ in the top-level directory of the Ansel distribution. + + +Obtaining Ansel +=============== + +Ansel can be obtained from the Horde website and FTP server, at + + http://www.horde.org/ansel/ + + ftp://ftp.horde.org/pub/ansel/ + +Or use the mirror closest to you: + + http://www.horde.org/mirrors.php + +Bleeding-edge development versions of Ansel are available via CVS; see the +file `horde/docs/HACKING`_ in the Horde distribution, or the website +http://www.horde.org/source/, for information on accessing the Horde CVS +repository. + + +Prerequisites +============= + +To function properly, Ansel **requires** the following: + +1. A working Horde installation. + + Ansel runs within the `Horde Application Framework`_, a set of common + tools for Web applications written in PHP. You must install Horde before + installing Ansel. + + .. Important:: Ansel 1.0 requires version 3.2.2 or greater of the + Horde Framework - earlier versions of Horde will **not** work. + + .. _`Horde Application Framework`: http://www.horde.org/horde/ + + The Horde Framework can be obtained from the Horde website and FTP server, + at + + http://www.horde.org/horde/ + + ftp://ftp.horde.org/pub/horde/ + + Many of Ansel's prerequisites are also Horde prerequisites. + + .. Important:: Be sure to have completed all of the steps in the + `horde/docs/INSTALL`_ file for the Horde Framework before + installing Ansel. + +2. The following PHP capabilities: + + a. _`GD` support ``--with-gd`` + + The GD extension provides functionality for image manipulation in + PHP. You can alternatively use the imagick_ extension or the + ImageMagick_ software, but one of these is **required**. + +3. A database: + + Ansel needs a database to store information about images and galleries. Any + database supported by MDB2 should work; MySQL and PostgreSQL are the most + tested. If you use MySQL, you need at least version 5.0. + +4. The following PEAR packages: + (See `horde/docs/INSTALL`_ for instructions on installing PEAR packages) + + a. MDB2 + + Ansel uses the MDB2 classes for database access. In addition, you will + need to ensure you have the proper MDB2_Driver package for your specific + database backend. + +5. The following PECL modules: + (See `horde/docs/INSTALL`_ for instructions on installing PECL modules) + + a. _`imagick` + + The imagick module provides a quicker, more efficient interface to + ImageMagick's capabilities and is used if Horde finds it available on + your system. It provides better results when doing things like + generating thumbnails with rounded corners and drop shadows. To use the + imagick module, make sure you still set the path to ``convert`` as + described below. You can alternatively use the GD_ extension or the + ImageMagick_ software, but one of these is **required**. + + b. _`OpenCV` + + The opencv module provides the ability to detect human faces in images. + If you would like to be able to have Ansel automatically detect faces in + your images, you will need to install the opencv module. You will still be + able to manually select faces from your images if you do not have opencv + installed. + + .. _ `OpenCV library`: http://sourceforge.net/projects/opencvlibrary/ + .. _ `PHP module`: http://d.hatena.ne.jp/moriyoshi/20070924/1190595577 + + + Alternatively, you may use the php-facedetect module instead of the + php-opencv module. You will still need to install the OpenCV library. + + .. _ `PHP Module`: http://www.xarg.org/project/php-facedetect/ + + c. _`libpuzzle` + + The libpuzzle module provides the ability to determine similarities + between images based on the image content. If you would like Ansel to be + able to provide recognition of similar images and similar faces, you will + need to install this module. + + .. _ `libpuzzle library`: http://libpuzzle.pureftpd.org/project/libpuzzle + +6. The _`ImageMagick` software. + + If using the `ImageMagick software`_ you need to set the path to the + ``convert`` utility in Horde's setup screen + (``Administration/Setup/Horde/Image Manipulation``). You can alternatively + use the GD_ extension or the imagick_ extension, but one of these is + **required**. + + .. _`ImageMagick software`: http://www.imagemagick.org/ + +7. _`Agora`, the Horde forums application. + + `Agora`_ provides the ability for comments to be added to photos via the + forums API. If you want your users to be able to comment on photos, an + application that provides the forums API is **required**. `Agora`_ is + currently the only such Horde application, but please be aware that currently + `Agora`_ is not officially released in a stable version, so use at your own + risk. + + .. _ 'Horde Agora': http://www.horde.org/agora + +Installing Ansel +================ + +Ansel is written in PHP, and must be installed in a web-accessible +directory. The precise location of this directory will differ from system to +system. Conventionally, Ansel is installed directly underneath Horde in the +web server's document tree. + +Since Ansel is written in PHP, there is no compilation necessary; simply +expand the distribution where you want it to reside and rename the root +directory of the distribution to whatever you wish to appear in the URL. For +example, with the Apache web server's default document root of +``/usr/local/apache/htdocs``, you would type:: + + cd /usr/local/apache/htdocs/horde + tar zxvf /path/to/ansel-h3-x.y.z.tar.gz + mv ansel-h3-x.y.z ansel + +and would then find Ansel at the URL:: + + http://your-server/horde/ansel/ + + +Configuring Ansel +================= + +1. Configuring Horde for Ansel + + a. Register the application + + In ``horde/config/registry.php``, find the ``applications['ansel']`` + stanza. The default settings here should be okay, but you can change + them if desired. If you have changed the location of Ansel relative + to Horde, either in the URL, in the filesystem or both, you must update + the ``fileroot`` and ``webroot`` settings to their correct values. + +2. Creating the database tables + + The specific steps to create Ansel's database tables depend on which + database you've chosen to use. + + First, look in ``scripts/sql/`` to see if a script already exists for your + database type. If so, you should be able to simply execute that script as + superuser in your database. (Note that executing the script as the "horde" + user will probably fail when granting privileges.) + + If such a script does not exist, you'll need to build your own, using the + file ``ansel.sql`` as a starting point. If you need assistance in creating + database tables, you may wish to let us know on the Ansel mailing list. + + You will also need to make sure that the "horde" user in your database has + table-creation privileges, so that the tables that `PEAR MDB2`_ uses to + provide portable sequences can be created. + + .. _`PEAR MDB2`: http://pear.php.net/MDB2 + +3. Configuring Ansel + + To configure Ansel, change to the ``config/`` directory of the installed + distribution, and make copies of all of the configuration ``dist`` files + without the ``dist`` suffix:: + + cd config/ + for foo in *.dist; do cp $foo `basename $foo .dist`; done + + Or on Windows:: + + copy *.dist *. + + Documentation on the format and purpose of those files can be found in each + file. You may edit these files if you wish to customize Ansel's appearance + and behavior. + + You must login to Horde as a Horde Administrator to finish the + configuration of Ansel. Use the Horde ``Administration`` menu item to get + to the administration page, and then click on the ``Configuration`` icon to + get the configuration page. Select ``Photos`` from the selection list of + applications. Fill in or change any configuration values as needed. When + done click on ``Generate Photos Configuration`` to generate the + ``conf.php`` file. If your web server doesn't have write permissions to the + Ansel configuration directory or file, it will not be able to write the + file. In this case, go back to ``Configuration`` and choose one of the + other methods to create the configuration file ``ansel/config/conf.php``. + + Note for international users: Ansel uses GNU gettext to provide local + translations of text displayed by applications; the translations are found + in the ``po/`` directory. If a translation is not yet available for your + locale (and you wish to create one), see the ``horde/po/README`` file, or + if you're having trouble using a provided translation, please see the + `horde/docs/TRANSLATIONS`_ file for instructions. + + Note for users of Lighttpd web server: The pretty url generation relies on + rewrite rules being configured on your web server. Since lighttpd does not + support htaccess files like Apache, there is also a configuration file for + lighttpd included that contains the necessary rewrite rules. This file is:: + + docs/lighttpd-ansel.conf + + Instructions for using it are included in the file. + + +4. Testing Ansel + + Once you have configured Ansel, bring up the included test page in your Web + browser to ensure that all necessary prerequisites have been met. See the + `horde/docs/INSTALL`_ document for further details on Horde test + scripts. If you installed Ansel as described above, the URL to the test + page would be:: + + http://your-server/horde/ansel/test.php + + Next, use Ansel to create galleries and photos. Test at least the + following: + + - Generate galleries + - Upload photos + - Edit photo properties + - Edit photos + - Delete photos + - Delete galleries + + +Obtaining Support +================= + +If you encounter problems with Ansel, help is available! + +The Horde Frequently Asked Questions List (FAQ), available on the Web at + + http://www.horde.org/faq/ + +The Horde Project runs a number of mailing lists, for individual applications +and for issues relating to the project as a whole. Information, archives, and +subscription information can be found at + + http://www.horde.org/mail/ + +Lastly, Horde developers, contributors and users may also be found on IRC, on +the channel #horde on the Freenode Network (irc.freenode.net). + +Please keep in mind that Ansel is free software written by volunteers. +For information on reasonable support expectations, please read + + http://www.horde.org/support.php + +Thanks for using Ansel! + +The Ansel team + + +.. _README: ?f=README.html +.. _`horde/docs/HACKING`: ../../horde/docs/?f=HACKING.html +.. _`horde/docs/INSTALL`: ../../horde/docs/?f=INSTALL.html +.. _`horde/docs/TRANSLATIONS`: ../../horde/docs/?f=TRANSLATIONS.html diff --git a/ansel/docs/RELEASE_NOTES b/ansel/docs/RELEASE_NOTES new file mode 100644 index 000000000..41fb2c0ea --- /dev/null +++ b/ansel/docs/RELEASE_NOTES @@ -0,0 +1,37 @@ +notes['fm']['focus'] = 1; + +/* Mailing list release notes. */ +$this->notes['ml']['changes'] = <<notes['fm']['changes'] = <<notes['name'] = 'Ansel'; +$this->notes['list'] = 'horde'; +$this->notes['fm']['project'] = 'horde-ansel'; +$this->notes['fm']['branch'] = 'Default'; diff --git a/ansel/docs/TODO b/ansel/docs/TODO new file mode 100755 index 000000000..2095628f2 --- /dev/null +++ b/ansel/docs/TODO @@ -0,0 +1,17 @@ +============================= + Ansel Development TODO List +============================= + +:Last update: $Date: 2008/01/16 01:30:57 $ +:Revision: $Revision: 1.43 $ +:Contact: ansel@lists.horde.org + +- Further abstract out all database access from Ansel_Gallery/Ansel_Image and + place in Ansel_Storage. Likewise, consolidate the static Ansel_Tags methods + into the Ansel_Storage class. + +- A command line script for batch importing/exporting/management of galleries + (importing is done). + +- Complete XMLRPC/SOAP API that remote programs can use to upload/edit/manage + images. diff --git a/ansel/docs/lighttpd-ansel.conf b/ansel/docs/lighttpd-ansel.conf new file mode 100755 index 000000000..016aa082f --- /dev/null +++ b/ansel/docs/lighttpd-ansel.conf @@ -0,0 +1,36 @@ +## This file should be reviewed prior to inclusion in your lighttpd +## configuration. Specifically, if you have ansel somewhere other than +## /horde/ansel you will need to edit the following rules to match your server +## configuration. + +## This file should be included in your lighttpd.conf file with the "include" +## directive. Example: +## include "path/to/lighttpd-ansel.conf" +## The exact path you use will of course depend on your specific configuration. + +url.rewrite-once += ( + ## General Groupings + "^/horde/ansel/user/?(?:\?(.*))?$" => "/horde/ansel/group.php?groupby=owner&$1", + "^/horde/ansel/category/?(?:\?(.*))?$" => "/horde/ansel/group.php?groupby=category&$1", + "^/horde/ansel/all/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=List&groupby=none&$1", + + ## This might need some work to catch all possible usernames. + "^/horde/ansel/user/([@a-zA-Z0-9%_+.!*',()~-]*)/rss/?$" => "/horde/ansel/rss.php?stream_type=user&id=$1", + "^/horde/ansel/user/([@a-zA-Z0-9%_+.!*',()~-]*)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=List&groupby=owner&owner=$1&$2", + "^/horde/ansel/category/([@a-zA-Z0-9%_+.!*',()~-]*)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=List&groupby=category&category=$1&$2", + + ## Galleries by ID + "^/horde/ansel/gallery/id/([0-9]+)/([0-9]+)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=Image&gallery=$1&image=$2&$3", + "^/horde/ansel/gallery/id/([0-9]+)/rss/?$" => "/horde/ansel/rss.php?stream_type=gallery&id=$1", + "^/horde/ansel/gallery/id/([0-9]+)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=Gallery&gallery=$1&$2", + + ## Galleries by slug + "^/horde/ansel/gallery/([a-zA-Z0-9_@]+)/([0-9]+)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=Image&slug=$1&image=$2&$3", + "^/horde/ansel/gallery/([a-zA-Z0-9_@]+)/rss/?$" => "/horde/ansel/rss.php?stream_type=gallery&slug=$1", + "^/horde/ansel/gallery/([a-zA-Z0-9_@]+)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=Gallery&slug=$1&$2", + + ## Tag browsing + "^/horde/ansel/tag/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=Results&$1", + "^/horde/ansel/tag/([a-zA-Z0-9%_+.!*',()~-]*)/rss/?$" => "/horde/ansel/rss.php?stream_type=tag&id=$1", + "^/horde/ansel/tag/([a-zA-Z0-9%_+.!*',()~-]*)/?(?:\?(.*))?$" => "/horde/ansel/view.php?view=Results&tag=$1&$2" +) diff --git a/ansel/edit_dates.php b/ansel/edit_dates.php new file mode 100644 index 000000000..f729e939e --- /dev/null +++ b/ansel/edit_dates.php @@ -0,0 +1,99 @@ + + */ + +@define('ANSEL_BASE', dirname(__FILE__)); +require_once ANSEL_BASE . '/lib/base.php'; + +$images = Horde_Util::getFormData('image', array()); +$actionID = Horde_Util::getFormData('actionID'); +$gallery_id = Horde_Util::getFormData('gallery'); +$page = Horde_Util::getFormData('page', 0); + +/* If we have a single gallery, check perms now */ +if (!empty($gallery_id)) { + $gallery = $ansel_storage->getGallery($gallery_id); + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(_("You are not allowed to edit these photos."), 'horde.error'); + Horde_Util::closeWindowJS('window.opener.location.href = window.opener.location.href; window.close();'); + exit; + } +} else { + // TODO - right now we should *always* have a gallery_id. If we get here + // from a results view, we may not, but that's not implemented yet. +} + +/* Make sure we have at least one image */ +if (!count($images)) { + echo $notification->push(_("You must select at least on photo to edit."), 'horde.error'); + Horde_Util::closeWindowJS('window.opener.location.href = window.opener.location.href; window.close();'); + exit; +} + +/* Includes */ +require_once ANSEL_BASE . '/lib/Forms/ImageDate.php'; +require_once 'Horde/Form/Renderer.php'; + +/* Set up the form */ +$vars = Horde_Variables::getDefaultVariables(); +$form = new ImageDateForm($vars, _("Edit Dates")); +/* Are we doing the edit now? */ +if ($actionID == 'edit_dates') { + $count = 0; + foreach (array_keys($images) as $image_id) { + $image = $ansel_storage->getImage($image_id); + if (!is_a($image, 'PEAR_Error')) { + if (empty($gallery_id)) { + // Images might be from different galleries + $gallery = $ansel_storage->getGallery($image->gallery); + if (is_a($gallery, 'PEAR_Error') || + !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + continue; + } + } + require_once 'Horde/Date.php'; + $newDate = new Horde_Date($vars->get('image_originalDate')); + $image->originalDate = (int)$newDate->timestamp(); + $image->save(); + ++$count; + } else { + $notification->push(sprintf(_("There was an error editing the dates: %s"), $image->getMessage()), 'horde.error'); + Horde_Util::closeWindowJS('window.opener.location.href = window.opener.location.href; window.close();'); + exit; + } + + } + + $notification->push(sprintf(_("Successfully modified the date on %d photos."), $count), 'horde.success'); + Horde_Util::closeWindowJS('window.opener.location.href = window.opener.location.href; window.close();'); + exit; +} + +$keys = array_keys($images); +$html = ''; +foreach ($keys as $key) { + $html .= '[thumbnail]'; +} +$image = $ansel_storage->getImage(array_pop($keys)); +/* Display the form */ +$vars->set('image', $images); +$vars->set('gallery', $gallery_id); +$vars->set('page', $page); +$vars->set('actionID', 'edit_dates'); +$vars->set('image_list', $html); +$vars->set('image_originalDate', $image->originalDate); +$renderer = new Horde_Form_Renderer(); +$count = count($images); +include ANSEL_TEMPLATES . '/common-header.inc'; +$form->renderActive($renderer, $vars, null, 'post'); +// Needed to ensure the body element is large enough to hold the pop up calendar +echo '


'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/faces/claim.php b/ansel/faces/claim.php new file mode 100644 index 000000000..06c820417 --- /dev/null +++ b/ansel/faces/claim.php @@ -0,0 +1,78 @@ + + */ +define('ANSEL_BASE', dirname(__FILE__) . '/../'); +require_once ANSEL_BASE . '/lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +require_once 'Horde/Form.php'; + +$faces = Ansel_Faces::factory(); + +$face_id = Horde_Util::getFormData('face'); +$face = $faces->getFaceById($face_id); + +if (is_a($face, 'PEAR_Error')) { + $notification->push($face->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/all.php')); + exit; +} + +$title = _("Tell us who is in this photo"); + +$vars = Horde_Variables::getDefaultVariables(); +$form = new Horde_Form($vars, $title); +$form->addHidden('', 'face', 'int', true); +$form->addVariable(_("Person"), 'person', 'text', true); +$form->setButtons($title); +if ($form->validate()) { + if (Horde_Util::getFormData('submitbutton') == _("Cancel")) { + $notification->push(_("Action was cancelled."), 'horde.warning'); + } else { + require ANSEL_BASE . '/lib/Report.php'; + $report = Ansel_Report::factory(); + $gallery = $ansel_storage->getGallery($face['gallery_id']); + + $face_link = Horde_Util::addParameter( + Horde::applicationUrl('faces/custom.php', true), + array('name' => $vars->get('person'), + 'face' => $face_id, + 'image' => $face['image_id']), null, false); + + $title = _("I know who is on one of your photos"); + $body = _("Gallery Name") . ': ' . $gallery->get('name') . "\n" + . _("Gallery Description") . ': ' . $gallery->get('desc') . "\n\n" + . $title . "\n" + . _("Person") . ': ' . $vars->get('person') . "\n" + . _("Face") . ': ' . $face_link; + + $report->setTitle($title); + $result = $report->report($body, $gallery->get('owner')); + if (is_a($result, 'PEAR_Error')) { + $notification->push(_("Face name was not reported.") . ' ' . + $result->getMessage(), 'horde.error'); + } else { + $notification->push(_("The owner of the photo, who will delegate the face name, was notified."), 'horde.success'); + } + } + + header('Location: ' . $faces->getLink($face)); + exit; +} + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +$form->renderActive(null, null, null, 'post'); + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/custom.php b/ansel/faces/custom.php new file mode 100644 index 000000000..8d03f6276 --- /dev/null +++ b/ansel/faces/custom.php @@ -0,0 +1,87 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; + +$image_id = (int)Horde_Util::getFormData('image'); +$face_id = (int)Horde_Util::getFormData('face'); +$page = Horde_Util::getFormData('page', 0); +$url = Horde_Util::getFormData('url'); +$urlparams = array('page' => $page); +if (!empty($url)) { + $urlparams['url'] = $url; +} +$form_post = Horde_Util::addParameter(Horde::applicationUrl('faces/savecustom.php'), $urlparams); + +$image = &$ansel_storage->getImage($image_id); +if (is_a($image, 'PEAR_Error')) { + $notification->push($image); + header('Location: ' . Horde::applicationUrl('list.php')); + exit; +} + +$gallery = $ansel_storage->getGallery($image->gallery); +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(_("Access denied editing the photo.")); + header('Location: ' . Ansel::getUrlFor('view', array('gallery' => $image->gallery))); + exit; +} + +$x1 = 0; +$y1 = 0; +$x2 = $conf['screen']['width']; +$y2 = $conf['screen']['width']; +$name = Horde_Util::getFormData('name'); + +if ($face_id) { + require_once ANSEL_BASE . '/lib/Faces.php'; + $faces = Ansel_Faces::factory(); + + if (is_a($faces, 'PEAR_Error')) { + $notification->push($faces); + header('Location: ' . $back_url); + exit; + } + + $face = $faces->getFaceById($face_id, true); + if (is_a($face, 'PEAR_Error')) { + $notification->push($face); + } else { + $x1 = $face['face_x1']; + $y1 = $face['face_y1']; + $x2 = $face['face_x2']; + $y2 = $face['face_y2']; + if (!empty($face['face_name'])) { + $name = $face['face_name']; + } + } + +} + +$height = $x2 - $x1; +$width = $y2 - $y1; + +$title = _("Create a new face"); + +Horde::addScriptFile('prototype.js', 'horde', true); +Horde::addScriptFile('builder.js'); +Horde::addScriptFile('effects.js', 'horde', true); +Horde::addScriptFile('controls.js', 'horde', true); +Horde::addScriptFile('dragdrop.js', 'horde', true); +Horde::addScriptFile('cropper.js'); +Horde::addScriptFile('stripe.js', 'horde', true); + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/faces/custom.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/faces/delete.php b/ansel/faces/delete.php new file mode 100644 index 000000000..7f6e8265d --- /dev/null +++ b/ansel/faces/delete.php @@ -0,0 +1,38 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +$image_id = (int)Horde_Util::getFormData('image'); +$face_id = (int)Horde_Util::getFormData('face'); + +$image = &$ansel_storage->getImage($image_id); +if (is_a($image, 'PEAR_Error')) { + die($image->getMessage()); +} + +$gallery = &$ansel_storage->getGallery($image->gallery); +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + die(_("Access denied editing the photo.")); +} + +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + die($faces->getMessage()); +} + +$result = $faces->delete($image, $face_id); +if (is_a($result, 'PEAR_Error')) { + die($result->getMessage()); +} diff --git a/ansel/faces/face.php b/ansel/faces/face.php new file mode 100644 index 000000000..8f159a8af --- /dev/null +++ b/ansel/faces/face.php @@ -0,0 +1,41 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + die($faces->getMessage()); +} + +$face_id = Horde_Util::getFormData('face'); +$face = $faces->getFaceById($face_id); +if (is_a($face, 'PEAR_Error')) { + $notification->push($face->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/index.php')); + exit; +} + +$title = _("Face") . ' :: ' . $face['face_name']; + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +require_once ANSEL_TEMPLATES . '/faces/face.inc'; + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/gallery.php b/ansel/faces/gallery.php new file mode 100644 index 000000000..7694cd7c9 --- /dev/null +++ b/ansel/faces/gallery.php @@ -0,0 +1,61 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; +require_once 'Horde/Serialize.php'; +require_once 'Horde/UI/Pager.php'; + +$gallery_id = (int)Horde_Util::getFormData('gallery'); +if (empty($gallery_id)) { + $notification->push(_("No gallery specified"), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('default_view', array())); + exit; +} +$gallery = $ansel_storage->getGallery($gallery_id); +if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('gallery' => $gallery_id))); + exit; +} elseif (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf(_("Access denied editing gallery \"%s\"."), $gallery->get('name')), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('gallery' => $gallery_id))); + exit; +} +$gallery->setDate(Ansel::getDateParameter()); +$page = Horde_Util::getFormData('page', 0); +$perpage = min($prefs->getValue('tilesperpage'), $conf['thumbnail']['perpage']); +$images = $gallery->getImages($page * $perpage, $perpage); + +$reloadimage = $registry->getImageDir('horde') . '/reload.png'; +$customimage = $registry->getImageDir('horde') . '/layout.png'; +$customurl = Horde_Util::addParameter(Horde::applicationUrl('faces/custom.php'), 'page', $page); +$autogenerate = Ansel_Faces::autogenerate(); + +$vars = Horde_Variables::getDefaultVariables(); +$pager = new Horde_UI_Pager( + 'page', $vars, + array('num' => $gallery->countImages(), + 'url' => 'faces/gallery.php', + 'perpage' => $perpage)); +$pager->preserve('gallery', $gallery_id); + +$title = sprintf(_("Searching for faces in %s"), Horde::link(Ansel::getUrlFor('view', array('gallery' => $gallery_id, 'view' => 'Gallery'))) . $gallery->get('name') . ''); +Horde::addScriptFile('prototype.js', 'horde', true); +Horde::addScriptFile('stripe.js', 'horde', true); +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/faces/gallery.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/faces/image.php b/ansel/faces/image.php new file mode 100644 index 000000000..a7786d9ca --- /dev/null +++ b/ansel/faces/image.php @@ -0,0 +1,53 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + die($faces->getMessage()); +} + +$name = ''; +$autocreate = true; +$image_id = (int)Horde_Util::getFormData('image'); +$reload = (int)Horde_Util::getFormData('reload'); +$result = $faces->getImageFacesData($image_id); + +// Attempt to get faces from the picture if we don't already have results, +// or if we were asked to explicitly try again. +if (($reload || empty($result))) { + $image = &$ansel_storage->getImage($image_id); + if (is_a($image, 'PEAR_Error')) { + exit; + } + + $result = $image->createView('screen'); + if (is_a($result, 'PEAR_Error')) { + exit; + } + + $result = $faces->getFromPicture($image_id, $autocreate); + if (is_a($result, 'PEAR_Error')) { + exit; + } +} + +if (!empty($result)) { + $imgdir = $registry->getImageDir('horde'); + $customurl = Horde::applicationUrl('faces/custom.php'); + require_once ANSEL_TEMPLATES . '/faces/image.inc'; +} else { + echo _("No faces found"); +} \ No newline at end of file diff --git a/ansel/faces/img.php b/ansel/faces/img.php new file mode 100644 index 000000000..23387ab62 --- /dev/null +++ b/ansel/faces/img.php @@ -0,0 +1,56 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +$face_id = Horde_Util::getFormData('face'); +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + Horde::logMessage($faces, __FILE__, __LINE__, PEAR_LOG_ERR); + exit; +} + +// Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file +// header +if ($conf['vfs']['src'] == 'sendfile') { + $face = $faces->getFaceById($face_id); + + // Make sure the view exists + if (!$faces->viewExists($face['image_id'], $face_id, true)) { + Horde::logMessage(sprintf('Unable to locate or create face_id %u.', + $face_id)); + exit; + } + + // We definitely have an image for the face. + $filename = $ansel_vfs->readFile( + $faces->getVFSPath($face['image_id']) . 'faces', + $face_id . $faces->getExtension()); + if (is_a($filename, 'PEAR_ERROR')) { + Horde::logMessage($filename, __FILE__, __LINE__, PEAR_LOG_ERR); + exit; + } + header('Content-type: image/' . $GLOBALS['conf']['image']['type']); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +// Run it through PHP +$img = $faces->getFaceImageObject($face_id); +if (is_a($img, 'PEAR_Error')) { + exit; +} +header('Content-type: image/' . $GLOBALS['conf']['image']['type']); +echo $img->raw(); diff --git a/ansel/faces/name.php b/ansel/faces/name.php new file mode 100644 index 000000000..d45184577 --- /dev/null +++ b/ansel/faces/name.php @@ -0,0 +1,39 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +$image_id = (int)Horde_Util::getFormData('image'); +$face_id = (int)Horde_Util::getFormData('face'); +$name = Horde_Util::getFormData('name'); + +$image = &$ansel_storage->getImage($image_id); +if (is_a($image, 'PEAR_Error')) { + die($image->getMessage()); +} + +$gallery = &$ansel_storage->getGallery($image->gallery); +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + die(_("Access denied editing the photo.")); +} + +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + die($faces->getMessage()); +} + +$result = $faces->setName($face_id, $name); +if (is_a($result, 'PEAR_Error')) { + die($result->getDebugInfo()); +} \ No newline at end of file diff --git a/ansel/faces/report.php b/ansel/faces/report.php new file mode 100644 index 000000000..666b2f485 --- /dev/null +++ b/ansel/faces/report.php @@ -0,0 +1,81 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +require_once 'Horde/Form.php'; + +$faces = Ansel_Faces::factory(); + +$face_id = Horde_Util::getFormData('face'); +$face = $faces->getFaceById($face_id); + +if (is_a($face, 'PEAR_Error')) { + $notification->push($face->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/all.php')); + exit; +} + +$title = _("Report face"); + +$vars = Horde_Variables::getDefaultVariables(); +$form = new Horde_Form($vars, $title); +$form->addHidden('', 'face', 'int', true); +$form->addVariable(_("Reason"), 'reason', 'longtext', true, false, _("Please describe the reasons. For example, you don't want to be mentioned etc...")); +$form->setButtons($title); + +if ($form->validate()) { + + if (Horde_Util::getFormData('submitbutton') == _("Cancel")) { + + $notification->push(_("Action was cancelled."), 'horde.warning'); + + } else { + + require ANSEL_BASE . '/lib/Report.php'; + $report = Ansel_Report::factory(); + $gallery = $ansel_storage->getGallery($face['gallery_id']); + + $face_link = Horde_Util::addParameter(Horde::applicationUrl('faces/face.php', true), + array('name' => $vars->get('person'), + 'face' => $face_id, + 'image' => $face['image_id']), null, false); + + $body = _("Gallery Name") . ': ' . $gallery->get('name') . "\n" + . _("Gallery Description") . ': ' . $gallery->get('desc') . "\n\n" + . $title . "\n" + . _("Reason") . ': ' . $vars->get('reason') . "\n" + . _("Face") . ': ' . $face_link; + + $report->setTitle($title); + $result = $report->report($body, $gallery->get('owner')); + if (is_a($result, 'PEAR_Error')) { + $notification->push(_("Face name was not reported.") . ' ' . + $result->getMessage(), 'horde.error'); + } else { + $notification->push(_("The owner of the photo was notified."), 'horde.success'); + } + + } + + header('Location: ' . $faces->getLink($face)); + exit; +} + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +$form->renderActive(null, null, null, 'post'); + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/savecustom.php b/ansel/faces/savecustom.php new file mode 100644 index 000000000..4ac701170 --- /dev/null +++ b/ansel/faces/savecustom.php @@ -0,0 +1,62 @@ + + */ +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +$image_id = (int)Horde_Util::getFormData('image_id'); +$gallery_id = (int)Horde_Util::getFormData('gallery_id'); +$face_id = (int)Horde_Util::getFormData('face_id'); +$url = Horde_Util::getFormData('url'); +$page = Horde_Util::getFormData('page', 0); + +$back_url = empty($url) ? + Horde_Util::addParameter(Horde::applicationUrl('faces/gallery.php'), + array('gallery' => $gallery_id, + 'page' => $page), null, false) : + $url; + +if (Horde_Util::getPost('submit') == _("Cancel")) { + $notification->push(_("Changes cancelled."), 'horde.warning'); + header('Location: ' . $back_url); + exit; +} + +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + $notification->push($faces); + header('Location: ' . $back_url); + exit; +} + +$result = $faces->saveCustomFace($face_id, + $image_id, + (int)Horde_Util::getFormData('x1'), + (int)Horde_Util::getFormData('y1'), + (int)Horde_Util::getFormData('x2'), + (int)Horde_Util::getFormData('y2'), + Horde_Util::getFormData('name')); + +if (is_a($result, 'PEAR_Error')) { + $notification->push($result); + $notification->push($result->getDebugInfo()); + header('Location: ' . $back_url); + exit; +} elseif ($face_id == 0) { + $notification->push(_("Face successfuly created"), 'horde.success'); +} else { + $notification->push(_("Face successfuly updated"), 'horde.success'); +} + +header('Location: ' . $back_url); +exit; diff --git a/ansel/faces/search/all.php b/ansel/faces/search/all.php new file mode 100644 index 000000000..f363ab2df --- /dev/null +++ b/ansel/faces/search/all.php @@ -0,0 +1,40 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/UI/Pager.php'; + +$title = _("All faces"); +$page = Horde_Util::getFormData('page', 0); +$perpage = $prefs->getValue('facesperpage'); + +$count = $faces->countAllFaces(); +if (is_a($count, 'PEAR_Error')) { + $notification->push($count->getDebugInfo()); + $count = 0; + $results = array(); +} else { + $results = $faces->allFaces($page * $perpage, $perpage); +} + +$vars = Horde_Variables::getDefaultVariables(); +$pager = new Horde_UI_Pager( + 'page', $vars, + array('num' => $count, + 'url' => 'faces/search/all.php', + 'perpage' => $perpage)); + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +include ANSEL_TEMPLATES . '/faces/faces.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/faces/search/image.php b/ansel/faces/search/image.php new file mode 100644 index 000000000..1a0f54cc6 --- /dev/null +++ b/ansel/faces/search/image.php @@ -0,0 +1,81 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/Form.php'; +require_once 'Horde/Image.php'; + +/* Search from */ +$form = new Horde_Form($vars); +$msg = _("Please upload photo with the face to search for. You can search only one face per time."); +$form->addVariable(_("Face to search for"), 'image', 'image', true, false, $msg, array(false)); +$form->setButtons(_("Upload")); + +if ($form->validate()) { + + $form->getInfo(null, $info); + + $tmp = Horde::getTempDir(); + $driver = empty($conf['image']['convert']) ? 'gd' : 'im'; + $img = Ansel::getImageObject(); + $result = $img->loadFile($info['image']['file']); + if (is_a($result, 'PEAR_Error')) { + $notification->push($result->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; + } + + $dimensions = $img->getDimensions(); + if ($dimensions['width'] < 50 || $dimensions['height'] < 50) { + $notification->push(_("Photo is too small. Search photo must be at least 50x50 pixels.")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; + } + + $result = $img->resize(min($conf['screen']['width'], $dimensions['width']), + min($conf['screen']['height'], $dimensions['height'])); + if (is_a($result, 'PEAR_Error')) { + $notification->push($result->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; + } + + $path = $tmp . '/search_face_' . Horde_Auth::getAuth() . Ansel_Faces::getExtension(); + if (file_put_contents($path, $img->raw())) { + header('Location: ' . Horde::applicationUrl('faces/search/image_define.php')); + } else { + $notification->push(_("Cannot store search photo")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + } + exit; + +} + +$title = _("Upload face photo"); +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +echo $tabs->render(Horde_Util::getGet('search_faces', 'image')); +$form->renderActive(null, null, null, 'post'); + +if (empty($name)) { + // Do noting +} elseif (empty($results)) { + echo _("No faces found"); +} else { + foreach ($results as $face_id => $face) { + include ANSEL_TEMPLATES . '/tile/face.inc'; + } +} + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/search/image_define.php b/ansel/faces/search/image_define.php new file mode 100644 index 000000000..c3ae5343d --- /dev/null +++ b/ansel/faces/search/image_define.php @@ -0,0 +1,61 @@ + + */ +require_once 'tabs.php'; + +/* check if image exists */ +$tmp = Horde::getTempDir(); +$path = $tmp . '/search_face_' . Horde_Auth::getAuth() . $faces->getExtension(); + +if (file_exists($path) !== true) { + $notification->push(_("You must upload the search photo first")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); +} + +$title = _("Create a new face"); + +$x1 = 0; +$y1 = 0; +$x2 = 0; +$y2 = 0; + +$faces = $faces->getFaces($path); +if (is_a($faces, 'PEAR_Error')) { + exit; +} + +if (count($faces) > 1) { + $notification->push(_("More then one face found in photo. Please note that you can search only one face at a time.")); +} elseif (empty($faces)) { + $notification->push(_("No faces found. Define you own.")); +} else { + $x1 = $faces[0]['x']; + $y1 = $faces[0]['y']; + $x2 = $faces[0]['x'] + $faces[0]['width']; + $y2 = $faces[0]['y'] + $faces[0]['height']; +} + +$height = $x2 - $x1; +$width = $y2 - $y1; + +Horde::addScriptFile('prototype.js', 'horde', true); +Horde::addScriptFile('scriptaculous.js', 'horde', true); +Horde::addScriptFile('builder.js', 'ansel', true); +Horde::addScriptFile('cropper.js', 'ansel', true); +Horde::addScriptFile('stripe.js', 'horde', true); + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/faces/define.inc'; + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/search/image_save.php b/ansel/faces/search/image_save.php new file mode 100644 index 000000000..cdc052844 --- /dev/null +++ b/ansel/faces/search/image_save.php @@ -0,0 +1,86 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/Image.php'; + +/* Check if image exists. */ +$tmp = Horde::getTempDir(); +$path = $tmp . '/search_face_' . Horde_Auth::getAuth() . $faces->getExtension(); + +if (!file_exists($path)) { + $notification->push(_("You must upload the search photo first")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); +} + +$x1 = (int)Horde_Util::getFormData('x1'); +$y1 = (int)Horde_Util::getFormData('y1'); +$x2 = (int)Horde_Util::getFormData('x2'); +$y2 = (int)Horde_Util::getFormData('y2'); + +if ($x2 - $x1 < 50 || $y2 - $y1 < 50) { + $notification->push(_("Photo is too small. Search photo must be at least 50x50 pixels.")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; +} + +/* Create Horde_Image driver. */ +$img = Ansel::getImageObject(); +$driver = empty($conf['image']['convert']) ? 'Gd' : 'Im'; +$result = $img->loadFile($path); +if (is_a($result, 'PEAR_Error')) { + $notification->push($result->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; +} + +/* Crop image. */ +$result = $img->crop($x1, $y1, $x2, $y2); +if (is_a($result, 'PEAR_Error')) { + $notification->push($result->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; +} + +/* Resize image. */ +$img->getDimensions(); +if ($img->_width >= 50) { + $img->resize(min(50, $img->_width), min(50, $img->_height), true); +} + +/* Save image. */ +$path = $tmp . '/search_face_thumb_' . Horde_Auth::getAuth() . $faces->getExtension(); +if (!file_put_contents($path, $img->raw())) { + $notification->push(_("Cannot store search photo")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; +} + +/* Get original signature. */ +$signature = $faces->getSignatureFromFile($path); +if (empty($signature)) { + $notification->push(_("Cannot read photo signature")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; +} + +/* Save signature. */ +$path = $tmp . '/search_face_' . Horde_Auth::getAuth() . '.sig'; +if (file_put_contents($path, $signature)) { + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + exit; +} + +$notification->push(_("Cannot save photo signature")); +header('Location: ' . Horde::applicationUrl('faces/search/image.php')); +exit; \ No newline at end of file diff --git a/ansel/faces/search/image_search.php b/ansel/faces/search/image_search.php new file mode 100644 index 000000000..262de19ca --- /dev/null +++ b/ansel/faces/search/image_search.php @@ -0,0 +1,54 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/UI/Pager.php'; + +$page = Horde_Util::getFormData('page', 0); +$perpage = $prefs->getValue('facesperpage'); + +if (($face_id = Horde_Util::getGet('face_id')) !== null) { + $face = $faces->getFaceById($face_id, true); + if (is_a($face, 'PEAR_Error')) { + $notification->push($face->getMessage()); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + } + $signature = $face['face_signature']; + $results = $faces->getSignatureMatches($signature, $face_id, $perpage * $page, $perpage); +} else { + $tmp = Horde::getTempDir(); + $path = $tmp . '/search_face_' . Horde_Auth::getAuth() . '.sig'; + if (file_exists($path) !== true) { + $notification->push(_("You must upload the search photo first")); + header('Location: ' . Horde::applicationUrl('faces/search/image.php')); + } + $signature = file_get_contents($path); + $results = $faces->getSignatureMatches($signature, 0, $perpage * $page, $perpage); +} +if (is_a($results, 'PEAR_Error')) { + $notification->push($results); + $results = array(); +} + +$title = _("Photo search"); +$vars = Horde_Variables::getDefaultVariables(); +$pager = new Horde_UI_Pager( + 'page', $vars, + array('num' => count($results), + 'url' => 'faces/search/image_search.php', + 'perpage' => $perpage)); + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/faces/search.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/search/img.php b/ansel/faces/search/img.php new file mode 100644 index 000000000..b3baf086d --- /dev/null +++ b/ansel/faces/search/img.php @@ -0,0 +1,27 @@ + + */ +require_once dirname(__FILE__) . '/../../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; + +/* Face search is allowd only to */ +if (!Horde_Auth::isauthenticated()) { + exit; +} + +$thumb = Horde_Util::getGet('thumb'); +$tmp = Horde::getTempDir(); +$path = $tmp . '/search_face_' . ($thumb ? 'thumb_' : '') . Horde_Auth::getAuth() . Ansel_Faces::getExtension(); + +header('Content-type: image/' . $conf['image']['type']); +readfile($path); \ No newline at end of file diff --git a/ansel/faces/search/name.php b/ansel/faces/search/name.php new file mode 100644 index 000000000..54bebd52e --- /dev/null +++ b/ansel/faces/search/name.php @@ -0,0 +1,53 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/Form.php'; +require_once 'Horde/UI/Pager.php'; + +/* Search from */ +$form = new Horde_Form($vars); +$form->addVariable(_("Face name to search"), 'face_name', 'text', true); +$form->setButtons(_("Search")); + +$page = Horde_Util::getFormData('page', 0); +$perpage = $prefs->getValue('facesperpage'); + +$name = Horde_Util::getFormData('face_name'); +if (!empty($name)) { + $page = Horde_Util::getFormData('page', 0); + $perpage = $prefs->getValue('faceperpage'); + $count = $faces->countSearchFaces($name); + if ($count) { + $results = $faces->searchFaces($name, $page * $perpage, $perpage); + } +} else { + $page = 0; + $perpage = 0; + $count = 0; +} + +$vars = Horde_Variables::getDefaultVariables(); +$pager = new Horde_UI_Pager( + 'page', $vars, + array('num' => $count, + 'url' => 'faces/search/name.php', + 'perpage' => $perpage)); + +$title = _("Search by name"); +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +include ANSEL_TEMPLATES . '/faces/faces.inc'; + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/search/named.php b/ansel/faces/search/named.php new file mode 100644 index 000000000..2085a9176 --- /dev/null +++ b/ansel/faces/search/named.php @@ -0,0 +1,39 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/UI/Pager.php'; + +$title = _("Named faces"); +$page = Horde_Util::getFormData('page', 0); +$perpage = $prefs->getValue('facesperpage'); +$results = array(); +$count = $faces->countNamedFaces(); +if (is_a($count, 'PEAR_Error')) { + $notification->push($count->getDebugInfo()); + $count = 0; +} elseif ($count > 0) { + $results = $faces->namedFaces($page * $perpage, $perpage); +} + +$vars = Horde_Variables::getDefaultVariables(); +$pager = new Horde_UI_Pager( + 'page', $vars, + array('num' => $count, + 'url' => 'faces/search/named.php', + 'perpage' => $perpage)); + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +include ANSEL_TEMPLATES . '/faces/faces.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/faces/search/owner.php b/ansel/faces/search/owner.php new file mode 100644 index 000000000..86a79c851 --- /dev/null +++ b/ansel/faces/search/owner.php @@ -0,0 +1,48 @@ + + */ +require_once 'tabs.php'; +require_once 'Horde/UI/Pager.php'; + +$page = Horde_Util::getFormData('page', 0); +$perpage = $prefs->getValue('facesperpage'); +$owner = Horde_Util::getGet('owner', Horde_Auth::getAuth()); +if ($owner == Horde_Auth::getAuth()) { + $title = _("From my galleries"); +} else { + $title = sprintf(_("From galleries of %s")); +} + +$count = $faces->countOwnerFaces($owner); +if (is_a($count, 'PEAR_Error')) { + $notification->push($count); + $results = array(); + $count = 0; +} else { + $results = $faces->ownerFaces($owner, $page * $perpage, $perpage); +} + +$vars = Horde_Variables::getDefaultVariables(); +$pager = new Horde_UI_Pager( + 'page', $vars, + array('num' => $count, + 'url' => 'faces/search/owner.php', + 'perpage' => $perpage)); +$pager->preserve('owner', $owner); + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; + +include ANSEL_TEMPLATES . '/faces/faces.inc'; + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/ansel/faces/search/tabs.php b/ansel/faces/search/tabs.php new file mode 100644 index 000000000..161841714 --- /dev/null +++ b/ansel/faces/search/tabs.php @@ -0,0 +1,37 @@ + + */ +require_once dirname(__FILE__) . '/../../lib/base.php'; +require_once ANSEL_BASE . '/lib/Faces.php'; +require_once 'Horde/UI/Tabs.php'; + +$faces = Ansel_Faces::factory(); +if (is_a($faces, 'PEAR_Error')) { + die($faces->getMessage()); +} + +/* Face search is allowed only to authenticated users */ +if (!Horde_Auth::isauthenticated()) { + Horde_Auth::authenticationFailureRedirect(); +} + +/* Show tabs */ +$vars = Horde_Variables::getDefaultVariables(); +$tabs = new Horde_UI_Tabs('search_faces', $vars); +$tabs->addTab(_("All faces"), Horde::applicationUrl('faces/search/all.php'), 'all'); +$tabs->addTab(_("From my galleries"), Horde::applicationUrl('faces/search/owner.php'), 'owner'); +$tabs->addTab(_("Named faces"), Horde::applicationUrl('faces/search/named.php'), 'named'); +$tabs->addTab(_("Search by name"), Horde::applicationUrl('faces/search/name.php'), 'name'); +if ($conf['faces']['search']) { + $tabs->addTab(_("Search by photo"), Horde::applicationUrl('faces/search/image.php'), 'image'); +} diff --git a/ansel/gallery.php b/ansel/gallery.php new file mode 100644 index 000000000..6656f7254 --- /dev/null +++ b/ansel/gallery.php @@ -0,0 +1,395 @@ + + */ + +require_once dirname(__FILE__) . '/lib/base.php'; +require_once 'Horde/Prefs/CategoryManager.php'; + +// Redirect to the gallery list if no action has been requested. +$actionID = Horde_Util::getFormData('actionID'); +if (is_null($actionID)) { + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; +} + +// Run through the action handlers. +switch ($actionID) { +case 'add': + // Set up the gallery attributes. + $gallery_name = ''; + $gallery_desc = ''; + $gallery_category = $prefs->getValue('default_category'); + $gallery_tags = ''; + $gallery_thumbstyle = ''; + $gallery_slug = ''; + $gallery_age = 0; + $gallery_download = $prefs->getValue('default_download'); + $gallery_parent = null; + $galleryId = null; + $gallery_mode = 'Normal'; + $gallery_passwd = ''; + + $notification->push('document.gallery.gallery_name.focus();', 'javascript'); + + $title = _("Adding A New Gallery"); + break; + +case 'addchild': + // Get the parent and make sure that it exists and that we have + // permissions to add to it. + $parentId = Horde_Util::getFormData('gallery'); + $parent = $ansel_storage->getGallery($parentId); + if (is_a($parent, 'PEAR_Error')) { + $notification->push($parent->getMessage(), 'horde.error'); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; + } elseif (!$parent->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push( + sprintf(_("Access denied adding a gallery to \"%s\"."), + $parent->get('name')), 'horde.error'); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; + } + + // Set up the gallery attributes. + $gallery_name = ''; + $gallery_desc = ''; + $gallery_category = $prefs->getValue('default_category'); + $gallery_tags = ''; + $gallery_slug = ''; + $gallery_age = 0; + $gallery_thumbstyle = $parent->get('style'); + $gallery_download = $prefs->getValue('default_download'); + $gallery_parent = $parentId; + $galleryId = null; + $gallery_mode = 'Normal'; + $gallery_passwd = ''; + + $notification->push('document.gallery.gallery_name.focus();', 'javascript'); + + $title = sprintf(_("Adding A Subgallery to %s"), $parent->get('name')); + break; + +case 'downloadzip': + $galleryId = Horde_Util::getFormData('gallery'); + $gallery = $ansel_storage->getGallery($galleryId); + if (!Horde_Auth::getAuth() || is_a($gallery, 'PEAR_Error') || + !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + + $name = is_a($gallery, 'PEAR_Error') + ? $galleryId + : $gallery->get('name'); + $notification->push(sprintf(_("Access denied downloading photos from \"%s\"."), $name), + 'horde.error'); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; + } + + Ansel::downloadImagesAsZip($gallery); + exit; + +case 'modify': + $galleryId = Horde_Util::getFormData('gallery'); + $gallery = $ansel_storage->getGallery($galleryId); + if (!is_a($gallery, 'PEAR_Error')) { + // Set up the gallery attributes. + $gallery_name = $gallery->get('name'); + $gallery_desc = $gallery->get('desc'); + $gallery_category = $gallery->get('category'); + $gallery_tags = implode(',', $gallery->getTags()); + $gallery_thumbstyle = $gallery->get('style'); + $gallery_slug = $gallery->get('slug'); + $gallery_age = (int)$gallery->get('age'); + $gallery_download = $gallery->get('download'); + $title = sprintf(_("Modifying: %s"), $gallery_name); + $gallery_parent = $gallery->getParent(); + if (!is_null($gallery_parent)) { + $gallery_parent = $gallery_parent->getId(); + } + $gallery_mode = $gallery->get('view_mode'); + $gallery_passwd = $gallery->get('passwd'); + } else { + $title = _("Unknown gallery"); + } + break; + +case 'save': + // Check general permissions. + if (!Horde_Auth::isAdmin() && + ($GLOBALS['perms']->exists('ansel') && + !$GLOBALS['perms']->hasPermission('ansel', Horde_Auth::getAuth(), PERMS_EDIT))) { + $notification->push(_("Access denied editing galleries."), 'horde.error'); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; + } + + // Get the form values. + $galleryId = Horde_Util::getFormData('gallery'); + $gallery_name = Horde_Util::getFormData('gallery_name'); + $gallery_desc = Horde_Util::getFormData('gallery_desc'); + $gallery_slug = Horde_Util::getFormData('gallery_slug'); + $gallery_age = (int)Horde_Util::getFormData('gallery_age', 0); + $gallery_download = Horde_Util::getFormData('gallery_download'); + $gallery_mode = Horde_Util::getFormData('view_mode', 'Normal'); + $gallery_passwd = Horde_Util::getFormData('gallery_passwd'); + if ($new_category = Horde_Util::getFormData('new_category')) { + $cManager = new Prefs_CategoryManager(); + $new_category = $cManager->add($new_category); + if ($new_category) { + $gallery_category = $new_category; + } + } else { + $gallery_category = Horde_Util::getFormData('gallery_category'); + } + + $gallery_tags = Horde_Util::getFormData('gallery_tags'); + $gallery_thumbstyle = Horde_Util::getFormData('gallery_style'); + $gallery_parent = Horde_Util::getFormData('gallery_parent'); + // Double check for an empty string instead of null + if (empty($gallery_parent)) { + $gallery_parent = null; + } + if ($galleryId && + ($exists = $ansel_storage->galleryExists($galleryId)) === true) { + + // Modifying an existing gallery. + $gallery = $ansel_storage->getGallery($galleryId); + if (is_a($gallery, 'PEAR_Error') || + !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + + $name = is_a($gallery, 'PEAR_Error') + ? $galleryId + : $gallery->get('name'); + + $notification->push(sprintf(_("Access denied saving gallery \"%s\"."), + $name), 'horde.error'); + } else { + // Don't allow the display name to be nulled out. + if ($gallery_name) { + $gallery->set('name', $gallery_name); + } + + $gallery->set('desc', $gallery_desc); + $gallery->set('category', $gallery_category); + $gallery->setTags(explode(',', $gallery_tags)); + $gallery->set('style', $gallery_thumbstyle); + $gallery->set('slug', $gallery_slug); + $gallery->set('age', $gallery_age); + $gallery->set('download', $gallery_download); + $gallery->set('view_mode', $gallery_mode); + if ($gallery->get('owner') == Horde_Auth::getAuth()) { + $gallery->set('passwd', $gallery_passwd); + } + + // Did the parent change? + $old_parent = $gallery->getParent(); + if (!is_null($old_parent)) { + $old_parent_id = $old_parent->getId(); + } else { + $old_parent_id = null; + } + if ($gallery_parent != $old_parent_id) { + if (!is_null($gallery_parent)) { + $new_parent = $ansel_storage->getGallery($gallery_parent); + if (is_a($new_parent, 'PEAR_Error')) { + return $new_parent; + } + } else { + $new_parent = null; + } + $result = $gallery->setParent($new_parent); + if (is_a($result, 'PEAR_Error')) { + $notification->push($result->getMessage(), 'horde.error'); + header('Location: ' . Horde::applicationUrl( + Ansel::getUrlFor('view', array('view' => 'List'), true))); + exit; + } + } + $result = $gallery->save(); + if (is_a($result, 'PEAR_Error')) { + $notification->push($result->getMessage(), 'horde.error'); + } else { + $notification->push(_("The gallery was saved."), + 'horde.success'); + } + } + } else { + // Is this a new subgallery? + if ($gallery_parent) { + $parent = $ansel_storage->getGallery($gallery_parent); + if (is_a($parent, 'PEAR_Error')) { + $notification->push($parent->getMessage(), 'horde.error'); + header('Location: ' . Horde::applicationUrl( + Ansel::getUrlFor('view', array('view' => 'List'), true))); + exit; + } elseif (!$parent->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf( + _("You do not have permission to add children to %s."), + $parent->get('name')), 'horde.error'); + + header('Location: ' . Horde::applicationUrl( + Ansel::getUrlFor('view', array('view' => 'List'), true))); + exit; + } + } + + // Require a display name. + if (!$gallery_name) { + $notification->push( + _("You must provide a display name for your new gallery."), + 'horde.warning'); + $actionId = 'add'; + $title = _("Adding A New Gallery"); + break; + } + + // Create the new gallery. + $perm = (!empty($parent)) ? $parent->getPermission() : null; + $parent = (!empty($gallery_parent)) ? $gallery_parent : null; + + $gallery = $ansel_storage->createGallery(array('name' => $gallery_name, + 'desc' => $gallery_desc, + 'category' => $gallery_category, + 'tags' => explode(',', $gallery_tags), + 'style' => $gallery_thumbstyle, + 'slug' => $gallery_slug, + 'age' => $gallery_age, + 'download' => $gallery_download, + 'view_mode' => $gallery_mode, + 'passwd' => $gallery_passwd, + ), $perm, $parent); + if (is_a($gallery, 'PEAR_Error')) { + $galleryId = null; + $error = sprintf(_("The gallery \"%s\" couldn't be created: %s"), + $gallery_name, $gallery->getMessage()); + Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR); + $notification->push($error, 'horde.error'); + } else { + $galleryId = $gallery->getId(); + $msg = sprintf(_("The gallery \"%s\" was created successfully."), + $gallery_name); + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $notification->push($msg, 'horde.success'); + } + } + + // Clear the OtherGalleries widget cache + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_OtherGalleries' . $gallery->get('owner')); + } + // Return to the last view. + $url = Horde_Util::getFormData('url'); + if (empty($url) && empty($exists) && !is_a($gallery, 'PEAR_Error')) { + // Redirect to the images upload page for newly creted galleries + $url = Horde_Util::addParameter(Horde::applicationUrl('img/upload.php'), + 'gallery', $galleryId); + } elseif (empty($url)) { + $url = Horde::applicationUrl('index.php', true); + } + header('Location: ' . $url); + exit; + +case 'delete': +case 'empty': + // Print the confirmation screen. + $galleryId = Horde_Util::getFormData('gallery'); + if ($galleryId) { + $gallery = $ansel_storage->getGallery($galleryId); + if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + } else { + require ANSEL_TEMPLATES . '/common-header.inc'; + require ANSEL_TEMPLATES . '/menu.inc'; + require ANSEL_TEMPLATES . '/gallery/delete_confirmation.inc'; + require $registry->get('templates', 'horde') . '/common-footer.inc'; + exit; + } + } + + // Return to the gallery list. + header('Location: ' . Horde::applicationUrl( + Ansel::getUrlFor('view', array('view' => 'List'), true))); + exit; + +case 'generateDefault': + // Re-generate the default pretty gallery image. + $galleryId = Horde_Util::getFormData('gallery'); + $gallery = $ansel_storage->getGallery($galleryId); + if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + header('Location: ' . Horde::applicationUrl('index.php', true)); + exit; + } + $gallery->clearStacks(); + $notification->push( + _("The gallery's default photo has successfully been reset."), + 'horde.success'); + $url = Horde_Util::addParameter('view.php', 'gallery', $galleryId); + header('Location: ' . Horde::applicationUrl($url, true)); + exit; + +case 'generateThumbs': + // Re-generate all of this gallery's prettythumbs. + $galleryId = Horde_Util::getFormData('gallery'); + $gallery = $ansel_storage->getGallery($galleryId); + if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + header('Location: ' . Horde::applicationUrl('index.php', true)); + exit; + } + $gallery->clearThumbs(); + $notification->push( + _("The gallery's thumbnails have successfully been reset."), + 'horde.success'); + $url = Horde_Util::addParameter('view.php', 'gallery', $galleryId); + header('Location: ' . Horde::applicationUrl($url, true)); + exit; + +case 'deleteCache': + // Delete all cached image views. This will NOT immediately regenerate + // all views that existed prior to this action. However it will remove all + // cached views (leaving the originals) which will be generated on demand + // by users browsing the site. Note that the first time each image is + // viewed there will be a delay as the view is generated and cached. + // This can be useful when changing the configured "screen" size in Ansel's + // configuration. + $galleryId = Horde_Util::getFormData('gallery'); + $gallery = $ansel_storage->getGallery($galleryId); + if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + header('Location: ' . Horde::applicationUrl('index.php', true)); + exit; + } + $gallery->clearViews(); + $notification->push( + _("The gallery's views have successfully been reset."), + 'horde.success'); + $url = Horde_Util::addParameter('view.php', 'gallery', $galleryId); + header('Location: ' . Horde::applicationUrl($url, true)); + exit; + +default: + header('Location: ' . Horde::applicationUrl( + Ansel::getUrlFor('view', array('view' => 'List'), true))); + exit; +} + +Horde::addScriptFile('stripe.js', 'horde', true); +require ANSEL_TEMPLATES . '/common-header.inc'; + +/* Attach the slug check action to the form */ +$imple = Horde_Ajax_Imple::factory(array('ansel', 'GallerySlugCheck'), + array('slug' => $gallery_slug, + 'bindTo' => 'gallery_slug')); +$imple->attach(); +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/gallery/gallery.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/gallery/captions.php b/ansel/gallery/captions.php new file mode 100644 index 000000000..585c994b8 --- /dev/null +++ b/ansel/gallery/captions.php @@ -0,0 +1,69 @@ + + */ + +@define('ANSEL_BASE', dirname(__FILE__) . '/..'); +require_once ANSEL_BASE . '/lib/base.php'; + +$galleryId = Horde_Util::getFormData('gallery'); +if (!$galleryId) { + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} + +$gallery = $ansel_storage->getGallery($galleryId); +if (is_a($gallery, 'PEAR_Error')) { + $notification->push(sprintf(_("Error accessing %s: %s"), $galleryId, $gallery->getMessage()), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} + +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf(_("Access denied setting captions for %s."), $gallery->get('name')), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} + +/* We might be browsing by date */ +$date = Ansel::getDateParameter(); +$gallery->setDate($date); + +/* Run through the action handlers. */ +$do = Horde_Util::getFormData('do'); +switch ($do) { +case 'save': + /* Save a batch of captions. */ + $images = $gallery->getImages(); + foreach ($images as $image) { + if (($caption = Horde_Util::getFormData('img' . $image->id)) !== null) { + $image->caption = $caption; + $image->save(); + } + } + + $notification->push(_("Captions Saved."), 'horde.success'); + $style = $gallery->getStyle(); + header('Location: ' . Ansel::getUrlFor('view', array_merge( + array('gallery' => $galleryId, + 'slug' => $gallery->get('slug'), + 'view' => 'Gallery'), + $date), true)); + exit; +} + +$title = _("Caption Editor"); +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/captions/captions.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/gallery/delete.php b/ansel/gallery/delete.php new file mode 100644 index 000000000..243e8b4f3 --- /dev/null +++ b/ansel/gallery/delete.php @@ -0,0 +1,72 @@ + + */ + +@define('ANSEL_BASE', dirname(__FILE__) . '/..'); +require_once ANSEL_BASE . '/lib/base.php'; + +// Delete/empty the gallery if we're provided with a valid galleryId. +$actionID = Horde_Util::getPost('action'); +$galleryId = Horde_Util::getPost('gallery'); + +if ($galleryId) { + $gallery = $ansel_storage->getGallery($galleryId); + + switch ($actionID) { + case 'delete': + if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + } elseif (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) { + $notification->push(sprintf(_("Access denied deleting gallery \"%s\"."), + $gallery->get('name')), 'horde.error'); + } else { + $result = $ansel_storage->removeGallery($gallery); + if (is_a($result, 'PEAR_Error')) { + $notification->push(sprintf( + _("There was a problem deleting %s: %s"), + $gallery->get('name'), $result->getMessage()), + 'horde.error'); + } else { + $notification->push(sprintf( + _("Successfully deleted %s."), + $gallery->get('name')), 'horde.success'); + } + } + + // Clear the OtherGalleries widget cache + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_OtherGalleries' . $gallery->get('owner')); + } + + // Return to the default view. + header('Location: ' . Ansel::getUrlFor('default_view', array())); + exit; + + case 'empty': + if (is_a($gallery, 'PEAR_Error')) { + $notification->push($gallery->getMessage(), 'horde.error'); + } elseif (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) { + $notification->push(sprintf(_("Access denied deleting gallery \"%s\"."), + $gallery->get('name')), + 'horde.error'); + } else { + $ansel_storage->emptyGallery($gallery); + $notification->push(sprintf(_("Successfully emptied \"%s\""), $gallery->get('name'))); + } + header('Location: ' + . Ansel::getUrlFor('view', + array('view' => 'Gallery', + 'gallery' => $galleryId, + 'slug' => $gallery->get('slug')), + true)); + exit; + } +} \ No newline at end of file diff --git a/ansel/gallery/index.php b/ansel/gallery/index.php new file mode 100755 index 000000000..e69de29bb diff --git a/ansel/gallery/sort.php b/ansel/gallery/sort.php new file mode 100644 index 000000000..2187c293e --- /dev/null +++ b/ansel/gallery/sort.php @@ -0,0 +1,98 @@ + + */ + +@define('ANSEL_BASE', dirname(__FILE__) . '/..'); +require_once ANSEL_BASE . '/lib/base.php'; + +/* If we aren't provided with a gallery, redirect to the gallery + * list. */ +$galleryId = Horde_Util::getFormData('gallery'); +if (!isset($galleryId)) { + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} + +$gallery = $ansel_storage->getGallery($galleryId); +if (is_a($gallery, 'PEAR_Error')) { + $notification->push(_("There was an error accessing the gallery."), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} elseif (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf(_("Access denied editing gallery \"%s\"."), $gallery->get('name')), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} +$style = $gallery->getStyle(); +$date = Ansel::getDateParameter(); +$gallery->setDate($date); + +switch (Horde_Util::getPost('action')) { +case 'Sort': + parse_str(Horde_Util::getPost('order'), $order); + $order = $order['order']; + foreach ($order as $pos => $id) { + $gallery->setImageOrder($id, $pos); + } + + $notification->push(_("Gallery sorted."), 'horde.success'); + $style = $gallery->getStyle(); + + header('Location: ' . Ansel::getUrlFor('view', array_merge( + array('view' => 'Gallery', + 'gallery' => $galleryId, + 'slug' => $gallery->get('slug')), + $date), + true)); + exit; +} + +Horde::addScriptFile('prototype.js', 'horde', true); +Horde::addScriptFile('effects.js', 'horde', true); +Horde::addScriptFile('dragdrop.js', 'horde', true); +$title = sprintf(_("%s :: Sort"), $gallery->get('name')); +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +?> +

+
+
+ + + + + + + +

+ + " /> +

+
+
+ +
+ +getImages(); +foreach ($images as $image) { + $caption = empty($image->caption) ? htmlspecialchars($image->filename, ENT_COMPAT, Horde_Nls::getCharset()) : htmlspecialchars($image->caption, ENT_COMPAT, Horde_Nls::getCharset()); + echo ''; +} +echo '
'; +$notification->push('Sortable.create(\'sortContainer\', {tag: \'div\', overlap: \'horizontal\', constraint: false })', 'javascript'); +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/group.php b/ansel/group.php new file mode 100644 index 000000000..0d13ed92e --- /dev/null +++ b/ansel/group.php @@ -0,0 +1,107 @@ + + * @author Ben Chavet + */ + +@define('ANSEL_BASE', dirname(__FILE__)); +require_once ANSEL_BASE . '/lib/base.php'; + +// check for grouping +$groupby = basename(Horde_Util::getFormData('groupby', $prefs->getValue('groupby'))); + +// check for pref update +$actionID = Horde_Util::getFormData('actionID'); +if ($actionID == 'groupby' && + ($groupby == 'owner' || $groupby == 'category' || $groupby == 'none')) { + $prefs->setValue('groupby', $groupby); +} + +// If we aren't supplied with a page number, default to page 0. +$gbpage = Horde_Util::getFormData('gbpage', 0); +$groups_perpage = $prefs->getValue('groupsperpage'); + +switch ($groupby) { +case 'category': + $num_groups = $ansel_storage->countCategories(PERMS_SHOW); + if (is_a($num_groups, 'PEAR_Error')) { + $notification->push($num_groups); + $num_groups = 0; + $groups = array(); + } elseif ($num_groups) { + $groups = $ansel_storage->listCategories(PERMS_SHOW, + $gbpage * $groups_perpage, + $groups_perpage); + } else { + $groups = array(); + } + break; + +case 'owner': + require_once 'Horde/Identity.php'; + $num_groups = $ansel_storage->shares->countOwners(PERMS_SHOW, null, + false); + if (is_a($num_groups, 'PEAR_Error')) { + $notification->push($num_groups); + $num_groups = 0; + $groups = array(); + } elseif ($num_groups) { + $groups = $ansel_storage->shares->listOwners(PERMS_SHOW, null, + false, + $gbpage * $groups_perpage, + $groups_perpage); + } else { + $groups = array(); + } + break; + +default: + header('Location: ' . Ansel::getUrlFor('view', + array('view' => 'List', + 'groupby' => $groupby), + true)); + exit; +} + +// Set up pager. +require_once 'Horde/UI/Pager.php'; +$vars = Horde_Variables::getDefaultVariables(); +$group_pager = new Horde_UI_Pager('gbpage', $vars, + array('num' => $num_groups, + 'url' => 'group.php', + 'perpage' => $groups_perpage)); + +$min = $gbpage * $groups_perpage; +$max = $min + $groups_perpage; +if ($max > $num_groups) { + $max = $num_groups - $min; +} +$start = $min + 1; +$end = min($num_groups, $min + $groups_perpage); +$count = 0; +$groupby_links = array(); +if ($groupby !== 'owner') { + $groupby_links[] = Horde::link(Ansel::getUrlFor('group', array('actionID' => 'groupby', 'groupby' => 'owner'))) . _("owner") . ''; +} elseif ($groupby !== 'category') { + $groupby_links[] = Horde::link(Ansel::getUrlFor('group', array('actionID' => 'groupby', 'groupby' => 'category'))) . _("category") . ''; +} +if ($groupby !== 'none') { + $groupby_links[] = Horde::link(Ansel::getUrlFor('group', array('actionID' => 'groupby', 'groupby' => 'none'))) . _("none") . ''; +} + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/group/header.inc'; +foreach ($groups as $group) { + require ANSEL_TEMPLATES . '/group/' . $groupby . '.inc'; +} +require ANSEL_TEMPLATES . '/group/footer.inc'; +require ANSEL_TEMPLATES . '/group/pager.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/image.php b/ansel/image.php new file mode 100644 index 000000000..0a9692da2 --- /dev/null +++ b/ansel/image.php @@ -0,0 +1,779 @@ + + * @author Michael J. Rubinsky + */ + +@define('ANSEL_BASE', dirname(__FILE__)); +require_once ANSEL_BASE . '/lib/base.php'; +require_once 'Horde/Form/Renderer.php'; + +/* Get all the form data */ +$actionID = Horde_Util::getFormData('actionID'); +$gallery_id = Horde_Util::getFormData('gallery'); +$image_id = Horde_Util::getFormData('image'); +$page = Horde_Util::getFormData('page', 0); +$watermark_font = Horde_Util::getFormData('font'); +$watermark_halign = Horde_Util::getFormData('whalign'); +$watermark_valign = Horde_Util::getFormData('wvalign'); +$watermark = Horde_Util::getFormData('watermark', $prefs->getValue('watermark')); +$date = Ansel::getDateParameter(); + +/* Are we watermarking the image? */ +if ($watermark) { + require_once 'Horde/Identity.php'; + $identity = &Identity::singleton(); + $name = $identity->getValue('fullname'); + if (empty($name)) { + $name = Horde_Auth::getAuth(); + } + + // Set up array of possible substitutions. + $watermark_array = array('%N' => $name, // User's fullname. + '%L' => Horde_Auth::getAuth()); // User login. + $watermark = str_replace(array_keys($watermark_array), + array_values($watermark_array), $watermark); + $watermark = strftime($watermark); +} + +/* See if any tags were passed in to add (when js not present) */ +$tags = Horde_Util::getFormData('addtag'); + +/* Redirect to the image list if no other action has been requested. */ +if (is_null($actionID) && is_null($tags)) { + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), + true)); + exit; +} + +/* Get the gallery object and style information */ +$gallery = $ansel_storage->getGallery($gallery_id); +if (is_a($gallery, 'PEAR_Error')) { + $notification->push(sprintf(_("Gallery %s not found."), $gallery_id), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), true)); + exit; +} + +/* Do we have tags to update? */ +if (!is_null($tags) && strlen($tags)) { + $tags = explode(',', $tags); + if (!empty($image_id)) { + $resource = &$ansel_storage->getImage($image_id); + } else { + $resource = $gallery; + } + $existingTags = $resource->getTags(); + $tags = array_merge($existingTags, $tags); + $result = $resource->setTags($tags); + // If no other action requested, redirect back to the appropriate view + if (empty($actionID)) { + if (empty($image_id)) { + $url = Ansel::getUrlFor('view', array_merge( + array('view' => 'Gallery', + 'gallery' => $gallery_id, + 'slug' => $gallery->get('slug')), + $date), + true); + + } else { + $url = Ansel::getUrlFor('view', array_merge( + array('view' => 'Image', + 'gallery' => $gallery_id, + 'image' => $image_id, + 'slug' => $gallery->get('slug')), + $date), + + true); + } + header('Location: ' . $url); + exit; + } +} + +/* Run through the action handlers. */ +switch ($actionID) { +case 'deletetags': + $tag = Horde_Util::getFormData('tag'); + if (!empty($image_id)) { + $resource = &$ansel_storage->getImage($image_id); + $page = Horde_Util::getFormData('page', 0); + $url = Ansel::getUrlFor('view', array_merge( + array('view' => 'Image', + 'gallery' => $gallery_id, + 'image' => $image_id, + 'page' => $page), + $date), + true); + } else { + $resource = $gallery; + $url = Ansel::getUrlFor('view', array_merge( + array('view' => 'Gallery', + 'gallery' => $gallery_id), + $date), + true); + } + $eTags = $resource->getTags(); + unset($eTags[$tag]); + $resource->setTags($eTags); + header('Location: ' . $url); + exit; + +case 'modify': + $image = &$ansel_storage->getImage($image_id); + $ret = Horde_Util::getFormData('ret', 'gallery'); + + if (is_a($image, 'PEAR_Error')) { + $notification->push(_("Photo not found."), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), true)); + exit; + } + + $title = sprintf(_("Edit properties :: %s"), $image->filename); + + /* Set up the form object. */ + require_once ANSEL_BASE . '/lib/Forms/Image.php'; + $vars = Horde_Variables::getDefaultVariables(); + if ($ret == 'gallery') { + $vars->set('actionID', 'saveclose'); + } else { + $vars->set('actionID', 'savecloseimage'); + } + $form = new ImageForm($vars, $title); + $renderer = new Horde_Form_Renderer(); + + /* Set up the gallery attributes. */ + $vars->set('image_default', $image->id == $gallery->get('default')); + $vars->set('image_desc', $image->caption); + $vars->set('image_tags', implode(', ', $image->getTags())); + $vars->set('image_originalDate', $image->originalDate); + $vars->set('image_uploaded', $image->uploaded); + + require ANSEL_TEMPLATES . '/common-header.inc'; + $form->renderActive($renderer, $vars, 'image.php', 'post', + 'multipart/form-data'); + + require $registry->get('templates', 'horde') . '/common-footer.inc'; + exit; + +case 'savecloseimage': +case 'saveclose': +case 'save': + $title = _("Save Photo"); + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf(_("Access denied saving photo to \"%s\"."), $gallery->get('name')), + 'horde.error'); + $imageurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'slug' => $gallery->get('slug'), + 'view' => 'Gallery', + 'page' => $page), + $date), + true); + header('Location: ' . $imageurl); + exit; + } + + /* Validate the form object. */ + require_once ANSEL_BASE . '/lib/Forms/Image.php'; + $vars = Horde_Variables::getDefaultVariables(); + $vars->set('actionID', 'save'); + $renderer = new Horde_Form_Renderer(); + $form = new ImageForm($vars, _("Edit a photo")); + + /* Update existing image. */ + if ($form->validate($vars)) { + $form->getInfo($vars, $info); + /* See if we were replacing photo */ + if (!empty($info['file0']['file']) && + !is_a(Horde_Browser::wasFileUploaded('file0'), 'PEAR_Error') && + filesize($info['file0']['file'])) { + + /* Read in the uploaded data. */ + $data = file_get_contents($info['file0']['file']); + + /* Try and make sure the image is in a recognizeable + * format. */ + if (getimagesize($info['file0']['file']) === false) { + $notification->push(_("The file you uploaded does not appear to be a valid photo."), 'horde.error'); + unset($data); + } + } + + $image = &$ansel_storage->getImage($image_id); + $image->caption = $vars->get('image_desc'); + $image->setTags(explode(',' , $vars->get('image_tags'))); + + require_once 'Horde/Date.php'; + $newDate = new Horde_Date($vars->get('image_originalDate')); + $image->originalDate = (int)$newDate->timestamp(); + + if (!empty($data)) { + $result = $image->replace($data); + if (is_a($result, 'PEAR_Error')) { + $notification->push(_("There was an error replacing the photo."), 'horde.error'); + } + } + $image->save(); + + if ($vars->get('image_default')) { + if ($gallery->get('default') != $image_id) { + // Changing default - force refresh of stack + // If we have a default-pretty already, make sure we delete it + $ids = unserialize($gallery->get('default_prettythumb')); + if (is_array($ids)) { + foreach ($ids as $imageId) { + $gallery->removeImage($imageId, true); + } + } + $gallery->set('default_prettythumb', ''); + } + $gallery->set('default', $image_id); + $gallery->set('default_type', 'manual'); + } elseif ($gallery->get('default') == $image_id) { + // Currently set as default, but we no longer wish it. + $gallery->set('default', 0); + $gallery->set('default_type', 'auto'); + // If we have a default-pretty already, make sure we delete it + $ids = unserialize($gallery->get('default_prettythumb')); + if (is_array($ids)) { + foreach ($ids as $imageId) { + $gallery->removeImage($imageId); + } + } + $gallery->set('default_prettythumb', ''); + } + + $gallery->save(); + $imageurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'image' => $image_id, + 'view' => 'Image', + 'page' => $page), + $date), + true); + if ($actionID == 'save') { + /* Return to the image view. */ + header('Location: ' . $imageurl); + } elseif ($actionID == 'saveclose') { + Horde_Util::closeWindowJS('window.opener.location.href = window.opener.location.href; window.close();'); + } else { + Horde_Util::closeWindowJS('window.opener.location.href = \'' . $imageurl . '\'; window.close();'); + } + exit; + } + break; + +case 'editimage': +case 'cropedit': +case 'resizeedit': + $imageview_url = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'image' => $image_id, + 'view' => 'Image', + 'page' => $page), + $date), + true); + $imageurl = Horde_Util::addParameter('image.php', array_merge( + array('gallery' => $gallery_id, + 'slug' => $gallery->get('slug'), + 'image' => $image_id, + 'page' => $page), + $date)); + + $galleryurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'page' => $page, + 'view' => 'Gallery', + 'slug' => $gallery->get('slug')), + $date)); + + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(_("Access denied editing the photo."), + 'horde.error'); + + /* Return to the image view. */ + header('Location: ' . $imageview_url); + exit; + } + + /* Retrieve image details. */ + $image = &$ansel_storage->getImage($image_id); + $title = sprintf(_("Edit %s :: %s"), $gallery->get('name'), + $image->filename); + + if ($actionID == 'cropedit') { + $geometry = $image->getDimensions('full'); + $x1 = 0; + $y1 = 0; + $x2 = $geometry['width']; + $y2 = $geometry['height']; + + /* js and css files */ + Horde::addScriptFile('prototype.js'); + Horde::addScriptFile('builder.js'); + Horde::addScriptFile('effects.js', 'horde', true); + Horde::addScriptFile('controls.js', 'horde', true); + Horde::addScriptFile('dragdrop.js', 'horde', true); + Horde::addScriptFile('cropper.js'); + Ansel::attachStylesheet('cropper.css'); + } elseif ($actionID == 'resizeedit') { + /* js and css files */ + // TODO: Combine these cases + $geometry = $image->getDimensions('full'); + Horde::addScriptFile('prototype.js'); + Horde::addScriptFile('builder.js'); + Horde::addScriptFile('effects.js', 'horde', true); + Horde::addScriptFile('slider.js', 'horde', true); + Horde::addScriptFile('dragdrop.js', 'horde', true); + } + + require ANSEL_TEMPLATES . '/common-header.inc'; + require ANSEL_TEMPLATES . '/menu.inc'; + + if ($actionID == 'cropedit') { + require ANSEL_TEMPLATES . '/image/crop_image.inc'; + } elseif ($actionID == 'resizeedit') { + require ANSEL_TEMPLATES . '/image/resize_image.inc'; + } else { + require ANSEL_TEMPLATES . '/image/edit_image.inc'; + } + require $registry->get('templates', 'horde') . '/common-footer.inc'; + exit; + +case 'watermark': + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf(_("Access denied saving photo to \"%s\"."), + $gallery->get('name')), + 'horde.error'); + /* Return to the image view. */ + $imageurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'image' => $image_id, + 'view' => 'Image', + 'page' => $page, + 'slug' => $gallery->get('slug')), + $date), + true); + header('Location: ' . $imageurl); + exit; + } else { + // @TODO: Writing directly to VFS here since we only watermark the + // screen images, need to refactor so that Ansel_Image can + // update image data for only a specific view. + $image = &$ansel_storage->getImage($image_id); + $image->watermark('screen', $watermark, $watermark_halign, + $watermark_valign, $watermark_font); + $image->updateData($image->raw('screen'), 'screen'); + $imageurl = Horde_Util::addParameter('image.php',array_merge( + array('gallery' => $gallery_id, + 'image' => $image_id, + 'actionID' => 'editimage', + 'page' => $page), + $date)); + + header('Location: ' . Horde::applicationUrl($imageurl, true)); + exit; + } + +case 'rotate90': +case 'rotate180': +case 'rotate270': +case 'flip': +case 'mirror': +case 'grayscale': +case 'crop': +case 'resize': + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(sprintf(_("Access denied saving photo to \"%s\"."), + $gallery->get('name')), + 'horde.error'); + } else { + $image = &$ansel_storage->getImage($image_id); + if (is_a($image, 'PEAR_Error')) { + $notification->push($image->getMessage(), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), true)); + exit; + } + + switch ($actionID) { + case 'rotate90': + case 'rotate180': + case 'rotate270': + $angle = intval(substr($actionID, 6)); + $image->rotate('full', $angle); + break; + + case 'flip': + $image->flip('full'); + break; + + case 'mirror': + $image->mirror('full'); + break; + + case 'grayscale': + $image->grayscale('full'); + break; + + case 'crop': + $image->load('full'); + $params = Horde_Util::getFormData('params'); + list($x1, $y1, $x2, $y2) = explode('.', $params); + $result = $image->crop($x1, $y1, $x2, $y2); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + $notification->push($result->getMessage(), 'horde.error'); + $error = true; + } + break; + case 'resize': + $image->load('full'); + $width = Horde_Util::getFormData('width'); + $height = Horde_Util::getFormData('height'); + $result = $image->_image->resize($width, $height, true); + break; + } + if (empty($error)) { + $image->updateData($image->_image->raw()); + } + } + + $imageurl = Horde_Util::addParameter('image.php', array_merge( + array('gallery' => $gallery_id, + 'image' => $image_id, + 'actionID' => 'editimage', + 'page' => $page), + $date)); + header('Location: ' . Horde::applicationUrl($imageurl, true)); + exit; + +case 'setwatermark': + $title = _("Watermark"); + $image = &$ansel_storage->getImage($image_id); + if (is_a($image, 'PEAR_Error')) { + $notification->push($image->getMessage(), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), true)); + exit; + } + /* Set up the form object. */ + require_once ANSEL_BASE . '/lib/Forms/Watermark.php'; + $vars = Horde_Variables::getDefaultVariables(); + $vars->set('actionID', 'previewcustomwatermark'); + $form = new WatermarkForm($vars, _("Watermark")); + $renderer = new Horde_Form_Renderer(); + + require ANSEL_TEMPLATES . '/common-header.inc'; + $form->renderActive($renderer, $vars, 'image.php', 'post'); + require $registry->get('templates', 'horde') . '/common-footer.inc'; + exit; + +case 'previewcustomwatermark': + $imageurl = Horde_Util::addParameter('image.php', array_merge( + array('gallery' => $gallery_id, + 'image' => $image_id, + 'page' => $page, + 'watermark' => $watermark, + 'font' => $watermark_font, + 'whalign' => $watermark_halign, + 'wvalign' => $watermark_valign, + 'actionID' => 'previewwatermark'), + $date)); + + $url = Horde::applicationUrl($imageurl); + $url = str_replace('&', '&', $url); + Horde_Util::closeWindowJS('window.opener.location.href = "' . $url . '";'); + exit; + +case 'previewgrayscale': +case 'previewwatermark': +case 'previewflip': +case 'previewmirror': +case 'previewrotate90': +case 'previewrotate180': +case 'previewrotate270': + $title = _("Edit Photo"); + $action = substr($actionID, 7); + + /* Retrieve image details. */ + $image = &$ansel_storage->getImage($image_id); + $title = sprintf(_("Preview changes for %s :: %s"), + $gallery->get('name'), + $image->filename); + + require ANSEL_TEMPLATES . '/common-header.inc'; + require ANSEL_TEMPLATES . '/menu.inc'; + require ANSEL_TEMPLATES . '/image/preview_image.inc'; + require $registry->get('templates', 'horde') . '/common-footer.inc'; + exit; + + break; + +case 'imagerotate90': +case 'imagerotate180': +case 'imagerotate270': + $view = Horde_Util::getFormData('view'); + $angle = intval(substr($actionID, 11)); + $image = &$ansel_storage->getImage($image_id); + $image->rotate($view, $angle); + $image->display($view); + exit; + +case 'imageflip': + $view = Horde_Util::getFormData('view'); + $image = &$ansel_storage->getImage($image_id); + $image->flip($view); + $image->display($view); + exit; + +case 'imagemirror': + $view = Horde_Util::getFormData('view'); + $image = &$ansel_storage->getImage($image_id); + $image->mirror($view); + $image->display($view); + exit; + +case 'imagegrayscale': + $view = Horde_Util::getFormData('view'); + $image = &$ansel_storage->getImage($image_id); + $image->grayscale($view); + $image->display($view); + exit; + +case 'imagewatermark': + $view = Horde_Util::getFormData('view'); + $image = &$ansel_storage->getImage($image_id); + $image->watermark($view, $watermark, $watermark_halign, $watermark_valign, + $watermark_font); + $image->display($view); + exit; + +case 'delete': + if (is_array($image_id)) { + $images = array_keys($image_id); + } else { + $images = array($image_id); + } + + /* Delete the images if we're provided with a valid image ID. */ + if (count($images)) { + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) { + $notification->push(sprintf(_("Access denied deleting photos from \"%s\"."), $gallery->get('name')), 'horde.error'); + } else { + foreach ($images as $image) { + $result = $gallery->removeImage($image); + if (is_a($result, 'PEAR_Error')) { + $notification->push( + sprintf(_("There was a problem deleting photos: %s"), + $result->getMessage()), 'horde.error'); + } else { + $notification->push(_("Deleted the photo."), + 'horde.success'); + } + } + } + } + + /* Recalculate the number of pages, since it might have changed */ + $children = $gallery->countGalleryChildren(PERMS_SHOW); + $perpage = min($prefs->getValue('tilesperpage'), + $conf['thumbnail']['perpage']); + $pages = ceil($children / $perpage); + if ($page > $pages) { + $page = $pages; + } + + /* Return to the image list. */ + $imageurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'view' => 'Gallery', + 'page' => $page, + 'slug' => $gallery->get('slug')), + $date), + true); + header('Location: ' . $imageurl); + exit; + +case 'move': + if (is_array($image_id)) { + $images = array_keys($image_id); + } else { + $images = array($image_id); + } + + /* Move the images if we're provided with at least one valid image_id. */ + $newGallery = Horde_Util::getFormData('new_gallery'); + if ($images && $newGallery) { + $newGallery = $ansel_storage->getGallery($newGallery); + if (is_a($newGallery, 'PEAR_Error')) { + $notification->push(_("Bad input."), 'horde.error'); + } else { + $result = $gallery->moveImagesTo($images, $newGallery); + if (is_a($result, 'PEAR_Error')) { + $notification->push($result, 'horde.error'); + } else { + $notification->push( + sprintf(ngettext("Moved %d photo from \"%s\" to \"%s\"", + "Moved %d photos from \"%s\" to \"%s\"", + $result), + $result, $gallery->get('name'), $newGallery->get('name')), + 'horde.success'); + } + } + } + + /* Recalculate the number of pages, since it might have changed */ + $children = $gallery->countGalleryChildren(PERMS_SHOW); + $perpage = min($prefs->getValue('tilesperpage'), + $conf['thumbnail']['perpage']); + $pages = ceil($children / $perpage); + if ($page > $pages) { + $page = $pages; + } + + /* Return to the image list. */ + $imageurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'view' => 'Gallery', + 'page' => $page, + 'slug' => $gallery->get('slug')), + $date), + true); + header('Location: ' . $imageurl); + exit; + +case 'copy': + if (is_array($image_id)) { + $images = array_keys($image_id); + } else { + $images = array($image_id); + } + + /* Move the images if we're provided with at least one valid image + * ID. */ + $newGallery = Horde_Util::getFormData('new_gallery'); + if ($images && $newGallery) { + $newGallery = $ansel_storage->getGallery($newGallery); + if (is_a($newGallery, 'PEAR_Error')) { + $notification->push(_("Bad input."), 'horde.error'); + } else { + $result = $gallery->copyImagesTo($images, $newGallery); + if (is_a($result, 'PEAR_Error')) { + $notification->push($result, 'horde.error'); + } else { + $notification->push( + sprintf(ngettext("Copied %d photo to %s", + "Copied %d photos to %s", $result), + $result, $newGallery->get('name')), + 'horde.success'); + } + } + } + + /* Return to the image list. */ + $imageurl = Ansel::getUrlFor('view', array_merge( + array('gallery' => $gallery_id, + 'view' => 'Gallery', + 'page' => $page, + 'slug' => $gallery->get('slug')), + $date), + true); + header('Location: ' . $imageurl); + exit; + +case 'downloadzip': + $galleryId = Horde_Util::getFormData('gallery'); + if ($galleryId) { + $gallery = $ansel_storage->getGallery($galleryId); + if (!Horde_Auth::getAuth() || is_a($gallery, 'PEAR_Error') || + !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ) || + $gallery->hasPasswd() || !$gallery->isOldEnough()) { + + $name = is_a($gallery, 'PEAR_Error') ? $galleryId : $gallery->get('name'); + $notification->push(sprintf(_("Access denied downloading photos from \"%s\"."), $name), + 'horde.error'); + header('Location: ' . Horde::applicationUrl('view.php?view=List', true)); + exit; + } + } + if (count($image_id)) { + Ansel::downloadImagesAsZip(null, array_keys($image_id)); + } else { + $notification->push(_("You must select images to download."), 'horde.error'); + if ($galleryId) { + $url = Ansel::getUrlFor('view', array('gallery' => $galleryId, + 'view' => 'Gallery', + 'page' => $page, + 'slug' => $gallery->get('slug'))); + } else { + $url = Ansel::getUrlFor('view', array('view' => 'List')); + } + header('Location: ' . $url); + exit; + } + exit; + break; + +case 'previewcrop': + + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $notification->push(_("Access denied editing the photo."), 'horde.error'); + $imageurl = Ansel::getUrlFor( + 'view', array('gallery' => $gallery_id, + 'image' => $image_id, + 'view' => 'Image', + 'page' => $page)); + header('Location: ' . $imageurl); + } else { + $x1 = (int)Horde_Util::getFormData('x1'); + $y1 = (int)Horde_Util::getFormData('y1'); + $x2 = (int)Horde_Util::getFormData('x2'); + $y2 = (int)Horde_Util::getFormData('y2'); + $title = _("Crop"); + $action = substr($actionID, 7); + + /* Retrieve image details. */ + $image = &$ansel_storage->getImage($image_id); + $title = sprintf(_("Preview changes for %s :: %s"), + $gallery->get('name'), + $image->filename); + + $params = $x1 . '.' . $y1 . '.' . $x2 . '.' . $y2; + + require ANSEL_TEMPLATES . '/common-header.inc'; + require ANSEL_TEMPLATES . '/menu.inc'; + require ANSEL_TEMPLATES . '/image/preview_cropimage.inc'; + require $registry->get('templates', 'horde') . '/common-footer.inc'; + } + exit; + +case 'imagecrop': + if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + $params = Horde_Util::getFormData('params'); + list($x1, $y1, $x2, $y2) = explode('.', $params); + $image = &$ansel_storage->getImage($image_id); + $image->load('full'); + $image->crop($x1, $y1, $x2, $y2); + $image->_image->display(); + } + exit; + +default: + header('Location: ' . Ansel::getUrlFor('default_view', array())); + exit; +} + +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +$form->renderActive($renderer, $vars, 'image.php', 'post', + 'multipart/form-data'); +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/img/download.php b/ansel/img/download.php new file mode 100644 index 000000000..bea925283 --- /dev/null +++ b/ansel/img/download.php @@ -0,0 +1,42 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery($image->gallery); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ) || + !$gallery->canDownload()) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +$image->downloadHeaders(); + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + $filename = $ansel_vfs->readFile($image->getVFSPath('full'), $image->getVFSName('full')); + header('Content-Type: ' . $image->getType('full')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('full'), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/ecard.php b/ansel/img/ecard.php new file mode 100644 index 000000000..b7ce91fd3 --- /dev/null +++ b/ansel/img/ecard.php @@ -0,0 +1,118 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Forms/Ecard.php'; +require_once 'Horde/Form/Renderer.php'; + +/* Abort if ecard sending is disabled. */ +if (empty($conf['ecard']['enable'])) { + exit; +} + +/* Get the gallery and the image, and abort if either fails. */ +$gallery = $ansel_storage->getGallery(Horde_Util::getFormData('gallery')); +if (is_a($gallery, 'PEAR_Error')) { + exit; +} +$image = &$gallery->getImage(Horde_Util::getFormData('image')); +if (is_a($image, 'PEAR_Error')) { + exit; +} + +/* Run through the action handlers. */ +switch (Horde_Util::getFormData('actionID')) { +case 'send': + /* Check for required elements. */ + $from = Horde_Util::getFormData('ecard_retaddr'); + if (empty($from)) { + $notification->push(_("You must enter your e-mail address."), 'horde.error'); + break; + } + $to = Horde_Util::getFormData('ecard_addr'); + if (empty($to)) { + $notification->push(_("You must enter an e-mail address to send the message to."), 'horde.error'); + break; + } + + $charset = Horde_Nls::getCharset(); + + /* Create the text part. */ + $textpart = new Horde_Mime_Part(); + $textpart->setType('text/plain'); + $textpart->setCharset($charset); + $textpart->setContents(_("You have been sent an Ecard. To view the Ecard, you must be able to view text/html messages in your mail reader. If you are viewing this message, then most likely your mail reader does not support viewing text/html messages.")); + + /* Create the multipart/related part. */ + $related = new Horde_Mime_Part(); + $related->setType('multipart/related'); + + /* Create the HTML part. */ + $htmlpart = new Horde_Mime_Part(); + $htmlpart->setType('text/html'); + $htmlpart->setCharset($charset); + + /* The image part */ + $imgpart = new Horde_Mime_Part(); + $imgpart->setType($image->getType('screen')); + $imgpart->setContents($image->raw('screen')); + $img_tag = '

'; + $comments = $htmlpart->replaceEOL(Horde_Util::getFormData('ecard_comments')); + if (!Horde_Util::getFormData('rtemode')) { + $comments = '

' . htmlspecialchars($comments, ENT_COMPAT, Horde_Nls::getCharset()) . '
'; + } + $htmlpart->setContents('' . $img_tag . $comments . ''); + $related->setContentTypeParameter('start', $htmlpart->setContentID()); + $related->addPart($htmlpart); + $related->addPart($imgpart); + + /* Create the multipart/alternative part. */ + $alternative = new Horde_Mime_Part(); + $alternative->setType('multipart/alternative'); + $alternative->addPart($textpart); + $alternative->addPart($related); + + /* Add them to the mail message */ + $alt = new Horde_Mime_Mail(_("Ecard - ") . Horde_Util::getFormData('image_desc'), null, $to, $from, $charset); + $alt->setBasePart($alternative); + + /* Send. */ + list($mail_driver, $mail_params) = Horde::getMailerConfig(); + $result = $alt->send($mail_driver, $mail_params); + if (is_a($result, 'PEAR_Error')) { + $notification->push(sprintf(_("There was an error sending your message: %s"), $result->getMessage()), 'horde.error'); + } else { + Horde_Util::closeWindowJS(); + exit; + } +} + +$title = sprintf(_("Send Ecard :: %s"), $image->filename); + +/* Set up the form object. */ +$vars = Horde_Variables::getDefaultVariables(); +$vars->set('actionID', 'send'); +$vars->set('image_desc', strlen($image->caption) ? $image->caption : $image->filename); +$form = new EcardForm($vars, $title); +$renderer = new Horde_Form_Renderer(); + +if ($browser->hasFeature('rte')) { + require_once 'Horde/Editor.php'; + $editor = Horde_Editor::factory('xinha', array('id' => 'ecard_comments')); + $vars->set('rtemode', 1); + $form->addHidden('', 'rtemode', 'text', false); +} + +require ANSEL_TEMPLATES . '/common-header.inc'; +$notification->notify(array('listeners' => 'status')); +$form->renderActive($renderer, $vars, 'ecard.php', 'post', 'multipart/form-data'); +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/img/full.php b/ansel/img/full.php new file mode 100644 index 000000000..cb60ff2f0 --- /dev/null +++ b/ansel/img/full.php @@ -0,0 +1,40 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery($image->gallery); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ) || + !$gallery->canDownload()) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + $filename = $ansel_vfs->readFile($image->getVFSPath('full'), $image->getVFSName('full')); + header('Content-Type: ' . $image->getType('full')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('full'), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/index.php b/ansel/img/index.php new file mode 100644 index 000000000..cdeceb6d0 --- /dev/null +++ b/ansel/img/index.php @@ -0,0 +1,39 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery($image->gallery); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + $filename = $ansel_vfs->readFile($image->getVFSPath('screen'), $image->getVFSName('screen')); + header('Content-Type: ' . $image->getType('screen')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('screen'), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/mini.php b/ansel/img/mini.php new file mode 100644 index 000000000..638ce0043 --- /dev/null +++ b/ansel/img/mini.php @@ -0,0 +1,45 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery(abs($image->gallery)); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + /* Need to ensure the file exists */ + $result = $image->createView('mini', 'ansel_default'); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + exit; + } + $filename = $ansel_vfs->readFile($image->getVFSPath('mini'), $image->getVFSName('mini')); + header('Content-Type: ' . $image->getType('mini')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('mini'), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/prettythumb.php b/ansel/img/prettythumb.php new file mode 100644 index 000000000..24e725835 --- /dev/null +++ b/ansel/img/prettythumb.php @@ -0,0 +1,46 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$style = Horde_Util::getFormData('style'); +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery(abs($image->gallery)); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + /* Need to ensure the file exists */ + $result = $image->createView('prettythumb', $style); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + exit; + } + $filename = $ansel_vfs->readFile($image->getVFSPath('prettythumb', $style), $image->getVFSName('prettythumb')); + header('Content-Type: ' . $image->getType('prettythumb')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('prettythumb', $style), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/screen.php b/ansel/img/screen.php new file mode 100644 index 000000000..691a00630 --- /dev/null +++ b/ansel/img/screen.php @@ -0,0 +1,45 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery($image->gallery); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + /* Need to ensure the file exists */ + $result = $image->createView('screen', 'ansel_default'); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + exit; + } + $filename = $ansel_vfs->readFile($image->getVFSPath('screen'), $image->getVFSName('screen')); + header('Content-Type: ' . $image->getType('screen')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('screen'), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/thumb.php b/ansel/img/thumb.php new file mode 100644 index 000000000..28bf48480 --- /dev/null +++ b/ansel/img/thumb.php @@ -0,0 +1,45 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$id = Horde_Util::getFormData('image'); +$image = &$ansel_storage->getImage($id); +if (is_a($image, 'PEAR_Error')) { + Horde::fatal($image, __FILE__, __LINE__); +} +$gallery = $ansel_storage->getGallery(abs($image->gallery)); +if (is_a($gallery, 'PEAR_Error')) { + Horde::fatal($gallery, __FILE__, __LINE__); +} +if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + Horde::fatal(_("Access denied viewing this photo."), __FILE__, __LINE__); +} + +/* Sendfile support. Lighttpd < 1.5 only understands the X-LIGHTTPD-send-file header */ +if ($conf['vfs']['src'] == 'sendfile') { + /* Need to ensure the file exists */ + $result = $image->createView('thumb', 'ansel_default'); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + exit; + } + $filename = $ansel_vfs->readFile($image->getVFSPath('thumb'), $image->getVFSName('thumb')); + header('Content-Type: ' . $image->getType('thumb')); + header('X-LIGHTTPD-send-file: ' . $filename); + header('X-Sendfile: ' . $filename); + exit; +} + +if (is_a($result = $image->display('thumb'), 'PEAR_Error')) { + Horde::fatal($result, __FILE__, __LINE__); +} diff --git a/ansel/img/upload.php b/ansel/img/upload.php new file mode 100644 index 000000000..480f3be89 --- /dev/null +++ b/ansel/img/upload.php @@ -0,0 +1,255 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; +require_once ANSEL_BASE . '/lib/Forms/Upload.php'; +require_once 'Horde/Form/Renderer.php'; + +$gallery_id = Horde_Util::getFormData('gallery'); +$gallery = &$ansel_storage->getGallery($gallery_id); +if (is_a($gallery, 'PEAR_Error')) { + $notification->push(sprintf(_("Gallery %s not found."), $gallery_id), 'horde.error'); + header('Location: ' . Ansel::getUrlFor('view', array('view' => 'List'), true)); + exit; +} + +$page = Horde_Util::getFormData('page', 0); +$vars = Horde_Variables::getDefaultVariables(); + +$form = new UploadForm($vars, _("Upload photos")); +if ($form->validate($vars)) { + $valid = true; + $uploaded = 0; + $form->getInfo($vars, $info); + + /* Remember the ids of the images we uploaded so we can autogen */ + $image_ids = array(); + for ($i = 0; $i <= $conf['image']['num_uploads'] + 1; ++$i) { + if (empty($info['file' . $i]['file'])) { + continue; + } + + /* Save new image. */ + if (!is_a(Horde_Browser::wasFileUploaded('file' . $i), 'PEAR_Error') && + filesize($info['file' . $i]['file'])) { + + /* Check for a compressed file. */ + require_once 'Horde/Mime/Magic.php'; + if (in_array($info['file' . $i]['type'], + array('x-extension/zip', + 'application/x-compressed', + 'application/x-zip-compressed', + 'application/zip')) || + Horde_Mime_Magic::filenameToMime($info['file' . $i]['name']) == 'application/zip') { + + /* See if we can use the zip extension for reading the file. */ + if (Horde_Util::extensionExists('zip')) { + $zip = new ZipArchive(); + if ($zip->open($info['file' . $i]['file']) !== true) { + $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $info['file' . $i]['file']), 'horde.error'); + continue; + } + + for ($z = 0; $z < $zip->numFiles; $z++) { + $zinfo = $zip->statIndex($z); + + /* Skip some known metadata files. */ + $len = strlen($zinfo['name']); + if ($zinfo['name'][$len - 1] == '/') { + continue; + } + if ($zinfo['name'] == 'Thumbs.db') { + continue; + } + if (strrpos($zinfo['name'], '.DS_Store') == ($len - 9)) { + continue; + } + if (strrpos($zinfo['name'], '.localized') == ($len - 10)) { + continue; + } + if (strpos($zinfo['name'], '__MACOSX/') !== false) { + continue; + } + + $stream = $zip->getStream($zinfo['name']); + $zdata = stream_get_contents($stream); + if (!strlen($zdata)) { + $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $zinfo['name']), 'horde.error'); + break; + } + + /* If we successfully got data, try adding the + * image to the gallery. */ + $image_id = $gallery->addImage(array( + 'image_filename' => $zinfo['name'], + 'image_caption' => '', + 'data' => $zdata, + )); + unset($zdata); + if (!is_a($image_id, 'PEAR_Error')) { + ++$uploaded; + if ($conf['image']['autogen'] > count($image_ids)) { + $image_ids[] = $image_id; + } + } else { + $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id), 'horde.error'); + } + } + + $zip->close(); + unset($zip); + } else { + /* Read in the uploaded data. */ + $data = file_get_contents($info['file' . $i]['file']); + + /* Get the list of files in the zipfile. */ + require_once 'Horde/Compress.php'; + $zip = Horde_Compress::factory('zip'); + $files = $zip->decompress($data, array('action' => Horde_Compress::ZIP_LIST)); + + if (is_a($files, 'PEAR_Error')) { + $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $files->getMessage()), 'horde.error'); + continue; + } + + foreach ($files as $key => $zinfo) { + /* Skip some known metadata files. */ + $len = strlen($zinfo['name']); + if ($zinfo['name'][$len - 1] == '/') { + continue; + } + if ($zinfo['name'] == 'Thumbs.db') { + continue; + } + if (strrpos($zinfo['name'], '.DS_Store') == ($len - 9)) { + continue; + } + if (strrpos($zinfo['name'], '.localized') == ($len - 10)) { + continue; + } + if (strpos($zinfo['name'], '__MACOSX/') !== false) { + continue; + } + + $zdata = $zip->decompress($data, array('action' => Horde_Compress::ZIP_DATA, + 'info' => $files, + 'key' => $key)); + if (is_a($zdata, 'PEAR_Error')) { + $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $zdata->getMessage()), 'horde.error'); + break; + } + + /* If we successfully got data, try adding the + * image to the gallery. */ + $image_id = $gallery->addImage(array( + 'image_filename' => $zinfo['name'], + 'image_caption' => '', + 'data' => $zdata, + )); + unset($zdata); + if (!is_a($image_id, 'PEAR_Error')) { + ++$uploaded; + if ($conf['image']['autogen'] > count($image_ids)) { + $image_ids[] = $image_id; + } + } else { + $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id), 'horde.error'); + } + } + + unset($zip); + unset($data); + } + } else { + /* Read in the uploaded data. */ + $data = file_get_contents($info['file' . $i]['file']); + + /* Try and make sure the image is in a recognizeable + * format. */ + if (getimagesize($info['file' . $i]['file']) === false) { + $notification->push(_("The file you uploaded does not appear to be a valid photo."), 'horde.error'); + continue; + } + + /* Add the image to the gallery */ + $image_data = array('image_filename' => $info['file' . $i]['name'], + 'image_caption' => $vars->get('image' . $i . '_desc'), + 'image_type' => $info['file' . $i]['type'], + 'data' => $data, + 'tags' => (isset($info['image' . $i . '_tags']) ? explode(',', $info['image' . $i . '_tags']) : array())); + $image_id = $gallery->addImage($image_data, (bool)$vars->get('image' . $i . '_default')); + unset($data); + if (is_a($image_id, 'PEAR_Error')) { + $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id->getMessage()), 'horde.error'); + $valid = false; + } else { + ++$uploaded; + $image_ids[] = $image_id; + } + } + } else { + if (!empty($info['file' . $i]['error'])) { + $notification->push(sprintf(_("There was a problem uploading the photo: %s"), $info['file' . $i]['error']), 'horde.error'); + } elseif (!filesize($info['file' . $i]['file'])) { + $notification->push(_("The uploaded file appears to be empty. It may not exist on your computer."), 'horde.error'); + } + $valid = false; + } + } + + /* Try to autogenerate some views and tell the user what happened. */ + if ($uploaded) { + $cnt = count($image_ids); + for ($i = 0; $i < $conf['image']['autogen'] && $cnt > $i; $i++) { + $image_id = $image_ids[$i]; + $image = &$GLOBALS['ansel_storage']->getImage($image_id); + $image->createView('screen'); + $image->createView('thumb'); + $image->createView('mini'); + unset($image); + } + + // postupload hook if needed + try { + Horde::callHook('postupload', array($image_ids)); + } catch (Horde_Exception $e) { + // Error from within the hook. + } catch (Horde_Exception_HookNotSet $e) {} + $notification->push(sprintf(ngettext("%d photo was uploaded.", "%d photos were uploaded.", $uploaded), $uploaded), 'horde.success'); + } elseif ($vars->get('submitbutton') != _("Cancel")) { + $notification->push(_("You did not select any photos to upload."), 'horde.error'); + } + + if ($valid) { + /* Return to the gallery view. */ + $imageurl = Ansel::getUrlFor('view', + array('gallery' => $gallery_id, + 'slug' => $gallery->get('slug'), + 'view' => 'Gallery', + 'page' => $page), + true); + + header('Location: ' . $imageurl); + exit; + } +} +///* Preview existing images */ +if ($gallery->countImages() && $browser->hasFeature('javascript')) { + $haveImages = true; +} + +$breadcrumbs = Ansel::getBreadCrumbs(' » ', $gallery); +$title = _("Add Photo"); +require ANSEL_TEMPLATES . '/common-header.inc'; +require ANSEL_TEMPLATES . '/menu.inc'; +require ANSEL_TEMPLATES . '/image/upload.inc'; +require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/ansel/img/upload_preview.php b/ansel/img/upload_preview.php new file mode 100644 index 000000000..d8fe9ebf6 --- /dev/null +++ b/ansel/img/upload_preview.php @@ -0,0 +1,37 @@ + + */ + +require_once dirname(__FILE__) . '/../lib/base.php'; + +$gallery_id = (int)Horde_Util::getFormData('gallery'); +$gallery = $ansel_storage->getGallery($gallery_id); +if (is_a($gallery, 'PEAR_Error') || + !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + die(sprintf(_("Gallery %s not found."), $gallery_id)); +} + +$from = (int)Horde_Util::getFormData('from'); +$to = (int)Horde_Util::getFormData('to'); +$count = $to - $from + 1; + +$images = $gallery->getImages($from, $count); +if (is_a($images, 'PEAR_Error')) { + die($images->getError()); +} + +foreach ($images as $image) { + echo '
  • '; + echo '
    '; + $alt = htmlspecialchars($image->filename); + echo '' . $alt . ''; + echo '
  • ' . "\n"; +} diff --git a/ansel/index.php b/ansel/index.php new file mode 100644 index 000000000..1cdffd00d --- /dev/null +++ b/ansel/index.php @@ -0,0 +1,27 @@ + + */ + +define('ANSEL_BASE', dirname(__FILE__)); +$ansel_configured = (is_readable(ANSEL_BASE . '/config/conf.php') && + is_readable(ANSEL_BASE . '/config/prefs.php') && + is_readable(ANSEL_BASE . '/config/styles.php')); + +if (!$ansel_configured) { + require ANSEL_BASE . '/../lib/Test.php'; + Horde_Test::configFilesMissing('Ansel', ANSEL_BASE, + array('conf.php', 'prefs.php'), + array('styles.php' => 'This file controls the available gallery styles for Ansel.')); +} + +require_once ANSEL_BASE . '/lib/base.php'; +header('Location: ' . Ansel::getUrlFor('default_view', array())); +exit; diff --git a/ansel/js/src/builder.js b/ansel/js/src/builder.js new file mode 100755 index 000000000..f25e996cf --- /dev/null +++ b/ansel/js/src/builder.js @@ -0,0 +1,20 @@ + +var Builder={NODEMAP:{AREA:'map',CAPTION:'table',COL:'table',COLGROUP:'table',LEGEND:'fieldset',OPTGROUP:'select',OPTION:'select',PARAM:'object',TBODY:'table',TD:'table',TFOOT:'table',TH:'table',THEAD:'table',TR:'table'},node:function(elementName){elementName=elementName.toUpperCase();var parentTag=this.NODEMAP[elementName]||'div';var parentElement=document.createElement(parentTag);try{parentElement.innerHTML="<"+elementName+">";}catch(e){} +var element=parentElement.firstChild||null;if(element&&(element.tagName.toUpperCase()!=elementName)) +element=element.getElementsByTagName(elementName)[0];if(!element)element=document.createElement(elementName);if(!element)return;if(arguments[1]) +if(this._isStringOrNumber(arguments[1])||(arguments[1]instanceof Array)||arguments[1].tagName){this._children(element,arguments[1]);}else{var attrs=this._attributes(arguments[1]);if(attrs.length){try{parentElement.innerHTML="<"+elementName+" "+ +attrs+">";}catch(e){} +element=parentElement.firstChild||null;if(!element){element=document.createElement(elementName);for(attr in arguments[1]) +element[attr=='class'?'className':attr]=arguments[1][attr];} +if(element.tagName.toUpperCase()!=elementName) +element=parentElement.getElementsByTagName(elementName)[0];}} +if(arguments[2]) +this._children(element,arguments[2]);return element;},_text:function(text){return document.createTextNode(text);},ATTR_MAP:{'className':'class','htmlFor':'for'},_attributes:function(attributes){var attrs=[];for(attribute in attributes) +attrs.push((attribute in this.ATTR_MAP?this.ATTR_MAP[attribute]:attribute)+'="'+attributes[attribute].toString().escapeHTML().gsub(/"/,'"')+'"');return attrs.join(" ");},_children:function(element,children){if(children.tagName){element.appendChild(children);return;} +if(typeof children=='object'){children.flatten().each(function(e){if(typeof e=='object') +element.appendChild(e) +else +if(Builder._isStringOrNumber(e)) +element.appendChild(Builder._text(e));});}else +if(Builder._isStringOrNumber(children)) +element.appendChild(Builder._text(children));},_isStringOrNumber:function(param){return(typeof param=='string'||typeof param=='number');},build:function(html){var element=this.node('div');$(element).update(html.strip());return element.down();},dump:function(scope){if(typeof scope!='object'&&typeof scope!='function')scope=window;var tags=("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY "+"BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET "+"FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+"KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+"PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+"TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);tags.each(function(tag){scope[tag]=function(){return Builder.node.apply(Builder,[tag].concat($A(arguments)));}});}} \ No newline at end of file diff --git a/ansel/js/src/carousel.js b/ansel/js/src/carousel.js new file mode 100644 index 000000000..b966dbbdf --- /dev/null +++ b/ansel/js/src/carousel.js @@ -0,0 +1,1076 @@ +/* Prototype-UI, version trunk + * + * Prototype-UI is freely distributable under the terms of an MIT-style license. + * For details, see the PrototypeUI web site: http://www.prototype-ui.com/ + * + *--------------------------------------------------------------------------*/ + +if(typeof Prototype == 'undefined' || !Prototype.Version.match("1.6")) + throw("Prototype-UI library require Prototype library >= 1.6.0"); + +if (Prototype.Browser.WebKit) { + Prototype.Browser.WebKitVersion = parseFloat(navigator.userAgent.match(/AppleWebKit\/([\d\.\+]*)/)[1]); + Prototype.Browser.Safari2 = (Prototype.Browser.WebKitVersion < 420); +} + +if (Prototype.Browser.IE) { + Prototype.Browser.IEVersion = parseFloat(navigator.appVersion.split(';')[1].strip().split(' ')[1]); + Prototype.Browser.IE6 = Prototype.Browser.IEVersion == 6; + Prototype.Browser.IE7 = Prototype.Browser.IEVersion == 7; +} + +Prototype.falseFunction = function() { return false }; +Prototype.trueFunction = function() { return true }; + +/* +Namespace: UI + + Introduction: + Prototype-UI is a library of user interface components based on the Prototype framework. + Its aim is to easilly improve user experience in web applications. + + It also provides utilities to help developers. + + Guideline: + - Prototype conventions are followed + - Everything should be unobstrusive + - All components are themable with CSS stylesheets, various themes are provided + + Warning: + Prototype-UI is still under deep development, this release is targeted to developers only. + All interfaces are subjects to changes, suggestions are welcome. + + DO NOT use it in production for now. + + Authors: + - Sébastien Gruhier, + - Samuel Lebeau, +*/ + +var UI = { + Abstract: { }, + Ajax: { } +}; +Object.extend(Class.Methods, { + extend: Object.extend.methodize(), + + addMethods: Class.Methods.addMethods.wrap(function(proceed, source) { + // ensure we are not trying to add null or undefined + if (!source) return this; + + // no callback, vanilla way + if (!source.hasOwnProperty('methodsAdded')) + return proceed(source); + + var callback = source.methodsAdded; + delete source.methodsAdded; + proceed(source); + callback.call(source, this); + source.methodsAdded = callback; + + return this; + }), + + addMethod: function(name, lambda) { + var methods = {}; + methods[name] = lambda; + return this.addMethods(methods); + }, + + method: function(name) { + return this.prototype[name].valueOf(); + }, + + classMethod: function() { + $A(arguments).flatten().each(function(method) { + this[method] = (function() { + return this[method].apply(this, arguments); + }).bind(this.prototype); + }, this); + return this; + }, + + // prevent any call to this method + undefMethod: function(name) { + this.prototype[name] = undefined; + return this; + }, + + // remove the class' own implementation of this method + removeMethod: function(name) { + delete this.prototype[name]; + return this; + }, + + aliasMethod: function(newName, name) { + this.prototype[newName] = this.prototype[name]; + return this; + }, + + aliasMethodChain: function(target, feature) { + feature = feature.camelcase(); + + this.aliasMethod(target+"Without"+feature, target); + this.aliasMethod(target, target+"With"+feature); + + return this; + } +}); +Object.extend(Number.prototype, { + // Snap a number to a grid + snap: function(round) { + return parseInt(round == 1 ? this : (this / round).floor() * round); + } +}); +/* +Interface: String + +*/ + +Object.extend(String.prototype, { + camelcase: function() { + var string = this.dasherize().camelize(); + return string.charAt(0).toUpperCase() + string.slice(1); + }, + + /* + Method: makeElement + toElement is unfortunately already taken :/ + + Transforms html string into an extended element or null (when failed) + + > '
  • some text
  • '.makeElement(); // => LI href# + > ''.makeElement(); // => IMG#foo (first one) + + Returns: + Extended element + + */ + makeElement: function() { + var wrapper = new Element('div'); wrapper.innerHTML = this; + return wrapper.down(); + } +}); +Object.extend(Array.prototype, { + empty: function() { + return !this.length; + }, + + extractOptions: function() { + return this.last().constructor === Object ? this.pop() : { }; + }, + + removeAt: function(index) { + var object = this[index]; + this.splice(index, 1); + return object; + }, + + remove: function(object) { + var index; + while ((index = this.indexOf(object)) != -1) + this.removeAt(index); + return object; + }, + + insert: function(index) { + var args = $A(arguments); + args.shift(); + this.splice.apply(this, [ index, 0 ].concat(args)); + return this; + } +}); +Element.addMethods({ + getScrollDimensions: function(element) { + return { + width: element.scrollWidth, + height: element.scrollHeight + } + }, + + getScrollOffset: function(element) { + return Element._returnOffset(element.scrollLeft, element.scrollTop); + }, + + setScrollOffset: function(element, offset) { + element = $(element); + if (arguments.length == 3) + offset = { left: offset, top: arguments[2] }; + element.scrollLeft = offset.left; + element.scrollTop = offset.top; + return element; + }, + + // returns "clean" numerical style (without "px") or null if style can not be resolved + // or is not numeric + getNumStyle: function(element, style) { + var value = parseFloat($(element).getStyle(style)); + return isNaN(value) ? null : value; + }, + + // by Tobie Langel (http://tobielangel.com/2007/5/22/prototype-quick-tip) + appendText: function(element, text) { + element = $(element); + text = String.interpret(text); + element.appendChild(document.createTextNode(text)); + return element; + } +}); + +document.whenReady = function(callback) { + if (document.loaded) + callback.call(document); + else + document.observe('dom:loaded', callback); +}; + +Object.extend(document.viewport, { + // Alias this method for consistency + getScrollOffset: document.viewport.getScrollOffsets, + + setScrollOffset: function(offset) { + Element.setScrollOffset(Prototype.Browser.WebKit ? document.body : document.documentElement, offset); + }, + + getScrollDimensions: function() { + return Element.getScrollDimensions(Prototype.Browser.WebKit ? document.body : document.documentElement); + } +}); +/* +Interface: UI.Options + Mixin to handle *options* argument in initializer pattern. + + TODO: find a better example than Circle that use an imaginary Point function, + this example should be used in tests too. + + It assumes class defines a property called *options*, containing + default options values. + + Instances hold their own *options* property after a first call to . + + Example: + > var Circle = Class.create(UI.Options, { + > + > // default options + > options: { + > radius: 1, + > origin: Point(0, 0) + > }, + > + > // common usage is to call setOptions in initializer + > initialize: function(options) { + > this.setOptions(options); + > } + > }); + > + > var circle = new Circle({ origin: Point(1, 4) }); + > + > circle.options + > // => { radius: 1, origin: Point(1,4) } + + Accessors: + There are builtin methods to automatically write options accessors. All those + methods can take either an array of option names nor option names as arguments. + Notice that those methods won't override an accessor method if already present. + + * creates getters + * creates setters + * creates both getters and setters + + Common usage is to invoke them on a class to create accessors for all instances + of this class. + Invoking those methods on a class has the same effect as invoking them on the class prototype. + See for more details. + + Example: + > // Creates getter and setter for the "radius" options of circles + > Circle.optionsAccessor('radius'); + > + > circle.setRadius(4); + > // 4 + > + > circle.getRadius(); + > // => 4 (circle.options.radius) + + Inheritance support: + Subclasses can refine default *options* values, after a first instance call on setOptions, + *options* attribute will hold all default options values coming from the inheritance hierarchy. +*/ + +(function() { + UI.Options = { + methodsAdded: function(klass) { + klass.classMethod($w(' setOptions allOptions optionsGetter optionsSetter optionsAccessor ')); + }, + + // Group: Methods + + /* + Method: setOptions + Extends object's *options* property with the given object + */ + setOptions: function(options) { + if (!this.hasOwnProperty('options')) + this.options = this.allOptions(); + + this.options = Object.extend(this.options, options || {}); + }, + + /* + Method: allOptions + Computes the complete default options hash made by reverse extending all superclasses + default options. + + > Widget.prototype.allOptions(); + */ + allOptions: function() { + var superclass = this.constructor.superclass, ancestor = superclass && superclass.prototype; + return (ancestor && ancestor.allOptions) ? + Object.extend(ancestor.allOptions(), this.options) : + Object.clone(this.options); + }, + + /* + Method: optionsGetter + Creates default getters for option names given as arguments. + With no argument, creates getters for all option names. + */ + optionsGetter: function() { + addOptionsAccessors(this, arguments, false); + }, + + /* + Method: optionsSetter + Creates default setters for option names given as arguments. + With no argument, creates setters for all option names. + */ + optionsSetter: function() { + addOptionsAccessors(this, arguments, true); + }, + + /* + Method: optionsAccessor + Creates default getters/setters for option names given as arguments. + With no argument, creates accessors for all option names. + */ + optionsAccessor: function() { + this.optionsGetter.apply(this, arguments); + this.optionsSetter.apply(this, arguments); + } + }; + + // Internal + function addOptionsAccessors(receiver, names, areSetters) { + names = $A(names).flatten(); + + if (names.empty()) + names = Object.keys(receiver.allOptions()); + + names.each(function(name) { + var accessorName = (areSetters ? 'set' : 'get') + name.camelcase(); + + receiver[accessorName] = receiver[accessorName] || (areSetters ? + // Setter + function(value) { return this.options[name] = value } : + // Getter + function() { return this.options[name] }); + }); + } +})(); +/* + Class: UI.Carousel + + Main class to handle a carousel of elements in a page. A carousel : + * could be vertical or horizontal + * works with liquid layout + * is designed by CSS + + Assumptions: + * Elements should be from the same size + + Example: + > ... + > + > + > ... +*/ +UI.Carousel = Class.create(UI.Options, { + // Group: Options + options: { + // Property: direction + // Can be horizontal or vertical, horizontal by default + direction : "horizontal", + + // Property: previousButton + // Selector of previous button inside carousel element, ".previous_button" by default, + // set it to false to ignore previous button + previousButton : ".previous_button", + + // Property: nextButton + // Selector of next button inside carousel element, ".next_button" by default, + // set it to false to ignore next button + nextButton : ".next_button", + + // Property: container + // Selector of carousel container inside carousel element, ".container" by default, + container : ".container", + + // Property: scrollInc + // Define the maximum number of elements that gonna scroll each time, auto by default + scrollInc : "auto", + + // Property: disabledButtonSuffix + // Define the suffix classanme used when a button get disabled, to '_disabled' by default + // Previous button classname will be previous_button_disabled + disabledButtonSuffix : '_disabled', + + // Property: overButtonSuffix + // Define the suffix classanme used when a button has a rollover status, '_over' by default + // Previous button classname will be previous_button_over + overButtonSuffix : '_over' + }, + + /* + Group: Attributes + + Property: element + DOM element containing the carousel + + Property: id + DOM id of the carousel's element + + Property: container + DOM element containing the carousel's elements + + Property: elements + Array containing the carousel's elements as DOM elements + + Property: previousButton + DOM id of the previous button + + Property: nextButton + DOM id of the next button + + Property: posAttribute + Define if the positions are from left or top + + Property: dimAttribute + Define if the dimensions are horizontal or vertical + + Property: elementSize + Size of each element, it's an integer + + Property: nbVisible + Number of visible elements, it's a float + + Property: animating + Define whether the carousel is in animation or not + */ + + /* + Group: Events + List of events fired by a carousel + + Notice: Carousel custom events are automatically namespaced in "carousel:" (see Prototype custom events). + + Examples: + This example will observe all carousels + > document.observe('carousel:scroll:ended', function(event) { + > alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled"); + > }); + + This example will observe only this carousel + > new UI.Carousel('horizontal_carousel').observe('scroll:ended', function(event) { + > alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled"); + > }); + + Property: previousButton:enabled + Fired when the previous button has just been enabled + + Property: previousButton:disabled + Fired when the previous button has just been disabled + + Property: nextButton:enabled + Fired when the next button has just been enabled + + Property: nextButton:disabled + Fired when the next button has just been disabled + + Property: scroll:started + Fired when a scroll has just started + + Property: scroll:ended + Fired when a scroll has been done, + memo.shift = number of elements scrolled, it's a float + + Property: sizeUpdated + Fired when the carousel size has just been updated. + Tips: memo.carousel.currentSize() = the new carousel size + */ + + // Group: Constructor + + /* + Method: initialize + Constructor function, should not be called directly + + Parameters: + element - DOM element + options - (Hash) list of optional parameters + + Returns: + this + */ + initialize: function(element, options) { + this.setOptions(options); + this.element = $(element); + this.id = this.element.id; + this.container = this.element.down(this.options.container).firstDescendant(); + this.elements = this.container.childElements(); + this.previousButton = this.options.previousButton == false ? null : this.element.down(this.options.previousButton); + this.nextButton = this.options.nextButton == false ? null : this.element.down(this.options.nextButton); + + this.posAttribute = (this.options.direction == "horizontal" ? "left" : "top"); + this.dimAttribute = (this.options.direction == "horizontal" ? "width" : "height"); + + this.elementSize = this.computeElementSize(); + this.nbVisible = this.currentSize() / this.elementSize; + + var scrollInc = this.options.scrollInc; + if (scrollInc == "auto") + scrollInc = Math.floor(this.nbVisible); + [ this.previousButton, this.nextButton ].each(function(button) { + if (!button) return; + var className = (button == this.nextButton ? "next_button" : "previous_button") + this.options.overButtonSuffix; + button.clickHandler = this.scroll.bind(this, (button == this.nextButton ? -1 : 1) * scrollInc * this.elementSize); + button.observe("click", button.clickHandler) + .observe("mouseover", function() {button.addClassName(className)}.bind(this)) + .observe("mouseout", function() {button.removeClassName(className)}.bind(this)); + }, this); + this.updateButtons(); + }, + + // Group: Destructor + + /* + Method: destroy + Cleans up DOM and memory + */ + destroy: function($super) { + [ this.previousButton, this.nextButton ].each(function(button) { + if (!button) return; + button.stopObserving("click", button.clickHandler); + }, this); + this.element.remove(); + this.fire('destroyed'); + }, + + // Group: Event handling + + /* + Method: fire + Fires a carousel custom event automatically namespaced in "carousel:" (see Prototype custom events). + The memo object contains a "carousel" property referring to the carousel. + + Example: + > document.observe('carousel:scroll:ended', function(event) { + > alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled"); + > }); + + Parameters: + eventName - an event name + memo - a memo object + + Returns: + fired event + */ + fire: function(eventName, memo) { + memo = memo || { }; + memo.carousel = this; + return this.element.fire('carousel:' + eventName, memo); + }, + + /* + Method: observe + Observe a carousel event with a handler function automatically bound to the carousel + + Parameters: + eventName - an event name + handler - a handler function + + Returns: + this + */ + observe: function(eventName, handler) { + this.element.observe('carousel:' + eventName, handler.bind(this)); + return this; + }, + + /* + Method: stopObserving + Unregisters a carousel event, it must take the same parameters as this.observe (see Prototype stopObserving). + + Parameters: + eventName - an event name + handler - a handler function + + Returns: + this + */ + stopObserving: function(eventName, handler) { + this.element.stopObserving('carousel:' + eventName, handler); + return this; + }, + + // Group: Actions + + /* + Method: checkScroll + Check scroll position to avoid unused space at right or bottom + + Parameters: + position - position to check + updatePosition - should the container position be updated ? true/false + + Returns: + position + */ + checkScroll: function(position, updatePosition) { + if (position > 0) + position = 0; + else { + var limit = this.elements.last().positionedOffset()[this.posAttribute] + this.elementSize; + var carouselSize = this.currentSize(); + + if (position + limit < carouselSize) + position += carouselSize - (position + limit); + position = Math.min(position, 0); + } + if (updatePosition) + this.container.style[this.posAttribute] = position + "px"; + + return position; + }, + + /* + Method: scroll + Scrolls carousel from maximum deltaPixel + + Parameters: + deltaPixel - a float + + Returns: + this + */ + scroll: function(deltaPixel) { + if (this.animating) + return this; + + // Compute new position + var position = this.currentPosition() + deltaPixel; + + // Check bounds + position = this.checkScroll(position, false); + + // Compute shift to apply + deltaPixel = position - this.currentPosition(); + if (deltaPixel != 0) { + this.animating = true; + this.fire("scroll:started"); + + var that = this; + // Move effects + this.container.morph("opacity:0.5", {duration: 0.2, afterFinish: function() { + that.container.morph(that.posAttribute + ": " + position + "px", { + duration: 0.4, + delay: 0.2, + afterFinish: function() { + that.container.morph("opacity:1", { + duration: 0.2, + afterFinish: function() { + that.animating = false; + that.updateButtons() + .fire("scroll:ended", { shift: deltaPixel / that.currentSize() }); + } + }); + } + }); + }}); + } + return this; + }, + + /* + Method: scrollTo + Scrolls carousel, so that element with specified index is the left-most. + This method is convenient when using carousel in a tabbed navigation. + Clicking on first tab should scroll first container into view, clicking on a fifth - fifth one, etc. + Indexing starts with 0. + + Parameters: + Index of an element which will be a left-most visible in the carousel + + Returns: + this + */ + scrollTo: function(index) { + if (this.animating || index < 0 || index > this.elements.length || index == this.currentIndex() || isNaN(parseInt(index))) + return this; + return this.scroll((this.currentIndex() - index) * this.elementSize); + }, + + /* + Method: updateButtons + Update buttons status to enabled or disabled + Them status is defined by classNames and fired as carousel's custom events + + Returns: + this + */ + updateButtons: function() { + this.updatePreviousButton(); + this.updateNextButton(); + return this; + }, + + updatePreviousButton: function() { + var position = this.currentPosition(); + var previousClassName = "previous_button" + this.options.disabledButtonSuffix; + + if (this.previousButton.hasClassName(previousClassName) && position != 0) { + this.previousButton.removeClassName(previousClassName); + this.fire('previousButton:enabled'); + } + if (!this.previousButton.hasClassName(previousClassName) && position == 0) { + this.previousButton.addClassName(previousClassName); + this.fire('previousButton:disabled'); + } + }, + + updateNextButton: function() { + var lastPosition = this.currentLastPosition(); + var size = this.currentSize(); + var nextClassName = "next_button" + this.options.disabledButtonSuffix; + + if (this.nextButton.hasClassName(nextClassName) && lastPosition != size) { + this.nextButton.removeClassName(nextClassName); + this.fire('nextButton:enabled'); + } + if (!this.nextButton.hasClassName(nextClassName) && lastPosition == size) { + this.nextButton.addClassName(nextClassName); + this.fire('nextButton:disabled'); + } + }, + + // Group: Size and Position + + /* + Method: computeElementSize + Return elements size in pixel, height or width depends on carousel orientation. + + Returns: + an integer value + */ + computeElementSize: function() { + return this.elements.first().getDimensions()[this.dimAttribute]; + }, + + /* + Method: currentIndex + Returns current visible index of a carousel. + For example, a horizontal carousel with image #3 on left will return 3 and with half of image #3 will return 3.5 + Don't forget that the first image have an index 0 + + Returns: + a float value + */ + currentIndex: function() { + return - this.currentPosition() / this.elementSize; + }, + + /* + Method: currentLastPosition + Returns the current position from the end of the last element. This value is in pixel. + + Returns: + an integer value, if no images a present it will return 0 + */ + currentLastPosition: function() { + if (this.container.childElements().empty()) + return 0; + return this.currentPosition() + + this.elements.last().positionedOffset()[this.posAttribute] + + this.elementSize; + }, + + /* + Method: currentPosition + Returns the current position in pixel. + Tips: To get the position in elements use currentIndex() + + Returns: + an integer value + */ + currentPosition: function() { + return this.container.getNumStyle(this.posAttribute); + }, + + /* + Method: currentSize + Returns the current size of the carousel in pixel + + Returns: + Carousel's size in pixel + */ + currentSize: function() { + return this.container.parentNode.getDimensions()[this.dimAttribute]; + }, + + /* + Method: updateSize + Should be called if carousel size has been changed (usually called with a liquid layout) + + Returns: + this + */ + updateSize: function() { + this.nbVisible = this.currentSize() / this.elementSize; + var scrollInc = this.options.scrollInc; + if (scrollInc == "auto") + scrollInc = Math.floor(this.nbVisible); + + [ this.previousButton, this.nextButton ].each(function(button) { + if (!button) return; + button.stopObserving("click", button.clickHandler); + button.clickHandler = this.scroll.bind(this, (button == this.nextButton ? -1 : 1) * scrollInc * this.elementSize); + button.observe("click", button.clickHandler); + }, this); + + this.checkScroll(this.currentPosition(), true); + this.updateButtons().fire('sizeUpdated'); + return this; + } +}); +/* + Class: UI.Ajax.Carousel + + Gives the AJAX power to carousels. An AJAX carousel : + * Use AJAX to add new elements on the fly + + Example: + > new UI.Ajax.Carousel("horizontal_carousel", + > {url: "get-more-elements", elementSize: 250}); +*/ +UI.Ajax.Carousel = Class.create(UI.Carousel, { + // Group: Options + // + // Notice: + // It also include of all carousel's options + options: { + // Property: elementSize + // Required, it define the size of all elements + elementSize : -1, + + // Property: url + // Required, it define the URL used by AJAX carousel to request new elements details + url : null + }, + + /* + Group: Attributes + + Notice: + It also include of all carousel's attributes + + Property: elementSize + Size of each elements, it's an integer + + Property: endIndex + Index of the last loaded element + + Property: hasMore + Flag to define if there's still more elements to load + + Property: requestRunning + Define whether a request is processing or not + + Property: updateHandler + Callback to update carousel, usually used after request success + + Property: url + URL used to request additional elements + */ + + /* + Group: Events + List of events fired by an AJAX carousel, it also include of all carousel's custom events + + Property: request:started + Fired when the request has just started + + Property: request:ended + Fired when the request has succeed + */ + + // Group: Constructor + + /* + Method: initialize + Constructor function, should not be called directly + + Parameters: + element - DOM element + options - (Hash) list of optional parameters + + Returns: + this + */ + initialize: function($super, element, options) { + if (!options.url) + throw("url option is required for UI.Ajax.Carousel"); + if (!options.elementSize) + throw("elementSize option is required for UI.Ajax.Carousel"); + + $super(element, options); + + this.endIndex = 0; + this.hasMore = true; + + // Cache handlers + this.updateHandler = this.update.bind(this); + this.updateAndScrollHandler = function(nbElements, transport, json) { + this.update(transport, json); + this.scroll(nbElements); + }.bind(this); + + // Run first ajax request to fill the carousel + this.runRequest.bind(this).defer({parameters: {from: 0, to: Math.floor(this.nbVisible)}, onSuccess: this.updateHandler}); + }, + + // Group: Actions + + /* + Method: runRequest + Request the new elements details + + Parameters: + options - (Hash) list of optional parameters + + Returns: + this + */ + runRequest: function(options) { + this.requestRunning = true; + //alert("Asking for: " + options.parameters.from + " - " + options.parameters.to); + new Ajax.Request(this.options.url, Object.extend({method: "GET"}, options)); + this.fire("request:started"); + return this; + }, + + /* + Method: scroll + Scrolls carousel from maximum deltaPixel + + Parameters: + deltaPixel - a float + + Returns: + this + */ + scroll: function($super, deltaPixel) { + if (this.animating || this.requestRunning) + return this; + + var nbElements = (-deltaPixel) / this.elementSize; + // Check if there is not enough + if (this.hasMore && nbElements > 0 && this.currentIndex() + this.nbVisible + nbElements - 1 > this.endIndex) { + var from = this.endIndex + 1; + var to = Math.floor(from + this.nbVisible - 1); + this.runRequest({parameters: {from: from, to: to}, onSuccess: this.updateAndScrollHandler.curry(deltaPixel).bind(this)}); + return this; + } + else + $super(deltaPixel); + }, + + /* + Method: update + Update the carousel + + Parameters: + transport - XMLHttpRequest object + json - JSON object + + Returns: + this + */ + update: function(transport, json) { + this.requestRunning = false; + this.fire("request:ended"); + if (!json) + json = transport.responseJSON; + this.hasMore = json.more; + + this.endIndex = Math.max(this.endIndex, json.to); + this.elements = this.container.insert({bottom: json.html}).childElements(); + return this.updateButtons(); + }, + + // Group: Size and Position + + /* + Method: computeElementSize + Return elements size in pixel + + Returns: + an integer value + */ + computeElementSize: function() { + return this.options.elementSize; + }, + + /* + Method: updateSize + Should be called if carousel size has been changed (usually called with a liquid layout) + + Returns: + this + */ + updateSize: function($super) { + var nbVisible = this.nbVisible; + $super(); + // If we have enough space for at least a new element + if (Math.floor(this.nbVisible) - Math.floor(nbVisible) >= 1 && this.hasMore) { + if (this.currentIndex() + Math.floor(this.nbVisible) >= this.endIndex) { + var nbNew = Math.floor(this.currentIndex() + Math.floor(this.nbVisible) - this.endIndex); + this.runRequest({parameters: {from: this.endIndex + 1, to: this.endIndex + nbNew}, onSuccess: this.updateHandler}); + } + } + return this; + }, + + updateNextButton: function($super) { + var lastPosition = this.currentLastPosition(); + var size = this.currentSize(); + var nextClassName = "next_button" + this.options.disabledButtonSuffix; + + if (this.nextButton.hasClassName(nextClassName) && lastPosition != size) { + this.nextButton.removeClassName(nextClassName); + this.fire('nextButton:enabled'); + } + if (!this.nextButton.hasClassName(nextClassName) && lastPosition == size && !this.hasMore) { + this.nextButton.addClassName(nextClassName); + this.fire('nextButton:disabled'); + } + } +}); diff --git a/ansel/js/src/cropper.js b/ansel/js/src/cropper.js new file mode 100644 index 000000000..08519bb5c --- /dev/null +++ b/ansel/js/src/cropper.js @@ -0,0 +1,555 @@ +/** + * Copyright 2006, David Spurr (http://www.defusion.org.uk/) + * 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 David Spurr 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. + * + * http://www.opensource.org/licenses/bsd-license.php + * + * See scriptaculous.js for full scriptaculous licence + */ + +var CropDraggable=Class.create(); +Object.extend(Object.extend(CropDraggable.prototype,Draggable.prototype),{initialize:function(_1){ +this.options=Object.extend({drawMethod:function(){ +}},arguments[1]||{}); +this.element=$(_1); +this.handle=this.element; +this.delta=this.currentDelta(); +this.dragging=false; +this.eventMouseDown=this.initDrag.bindAsEventListener(this); +Event.observe(this.handle,"mousedown",this.eventMouseDown); +Draggables.register(this); +},draw:function(_2){ +var _3=Position.cumulativeOffset(this.element); +var d=this.currentDelta(); +_3[0]-=d[0]; +_3[1]-=d[1]; +var p=[0,1].map(function(i){ +return (_2[i]-_3[i]-this.offset[i]); +}.bind(this)); +this.options.drawMethod(p); +}}); +var Cropper={}; +Cropper.Img=Class.create(); +Cropper.Img.prototype={initialize:function(_7,_8){ +this.options=Object.extend({ratioDim:{x:0,y:0},minWidth:0,minHeight:0,displayOnInit:false,onEndCrop:Prototype.emptyFunction,captureKeys:true,onloadCoords:null,maxWidth:0,maxHeight:0},_8||{}); +this.img=$(_7); +this.clickCoords={x:0,y:0}; +this.dragging=false; +this.resizing=false; +this.isWebKit=/Konqueror|Safari|KHTML/.test(navigator.userAgent); +this.isIE=/MSIE/.test(navigator.userAgent); +this.isOpera8=/Opera\s[1-8]/.test(navigator.userAgent); +this.ratioX=0; +this.ratioY=0; +this.attached=false; +this.fixedWidth=(this.options.maxWidth>0&&(this.options.minWidth>=this.options.maxWidth)); +this.fixedHeight=(this.options.maxHeight>0&&(this.options.minHeight>=this.options.maxHeight)); +if(typeof this.img=="undefined"){ +return; +} +if(this.options.ratioDim.x>0&&this.options.ratioDim.y>0){ +var _c=this.getGCD(this.options.ratioDim.x,this.options.ratioDim.y); +this.ratioX=this.options.ratioDim.x/_c; +this.ratioY=this.options.ratioDim.y/_c; +} +this.subInitialize(); +if(this.img.complete||this.isWebKit){ +this.onLoad(); +}else{ +Event.observe(this.img,"load",this.onLoad.bindAsEventListener(this)); +} +},getGCD:function(a,b){ +if(b==0){ +return a; +} +return this.getGCD(b,a%b); +},onLoad:function(){ +var _f="imgCrop_"; +var _10=this.img.parentNode; +var _11=""; +if(this.isOpera8){ +_11=" opera8"; +} +this.imgWrap=Builder.node("div",{"class":_f+"wrap"+_11}); +this.north=Builder.node("div",{"class":_f+"overlay "+_f+"north"},[Builder.node("span")]); +this.east=Builder.node("div",{"class":_f+"overlay "+_f+"east"},[Builder.node("span")]); +this.south=Builder.node("div",{"class":_f+"overlay "+_f+"south"},[Builder.node("span")]); +this.west=Builder.node("div",{"class":_f+"overlay "+_f+"west"},[Builder.node("span")]); +var _12=[this.north,this.east,this.south,this.west]; +this.dragArea=Builder.node("div",{"class":_f+"dragArea"},_12); +this.handleN=Builder.node("div",{"class":_f+"handle "+_f+"handleN"}); +this.handleNE=Builder.node("div",{"class":_f+"handle "+_f+"handleNE"}); +this.handleE=Builder.node("div",{"class":_f+"handle "+_f+"handleE"}); +this.handleSE=Builder.node("div",{"class":_f+"handle "+_f+"handleSE"}); +this.handleS=Builder.node("div",{"class":_f+"handle "+_f+"handleS"}); +this.handleSW=Builder.node("div",{"class":_f+"handle "+_f+"handleSW"}); +this.handleW=Builder.node("div",{"class":_f+"handle "+_f+"handleW"}); +this.handleNW=Builder.node("div",{"class":_f+"handle "+_f+"handleNW"}); +this.selArea=Builder.node("div",{"class":_f+"selArea"},[Builder.node("div",{"class":_f+"marqueeHoriz "+_f+"marqueeNorth"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeVert "+_f+"marqueeEast"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeHoriz "+_f+"marqueeSouth"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeVert "+_f+"marqueeWest"},[Builder.node("span")]),this.handleN,this.handleNE,this.handleE,this.handleSE,this.handleS,this.handleSW,this.handleW,this.handleNW,Builder.node("div",{"class":_f+"clickArea"})]); +this.imgWrap.appendChild(this.img); +this.imgWrap.appendChild(this.dragArea); +this.dragArea.appendChild(this.selArea); +this.dragArea.appendChild(Builder.node("div",{"class":_f+"clickArea"})); +_10.appendChild(this.imgWrap); +this.startDragBind=this.startDrag.bindAsEventListener(this); +Event.observe(this.dragArea,"mousedown",this.startDragBind); +this.onDragBind=this.onDrag.bindAsEventListener(this); +Event.observe(document,"mousemove",this.onDragBind); +this.endCropBind=this.endCrop.bindAsEventListener(this); +Event.observe(document,"mouseup",this.endCropBind); +this.resizeBind=this.startResize.bindAsEventListener(this); +this.handles=[this.handleN,this.handleNE,this.handleE,this.handleSE,this.handleS,this.handleSW,this.handleW,this.handleNW]; +this.registerHandles(true); +if(this.options.captureKeys){ +this.keysBind=this.handleKeys.bindAsEventListener(this); +Event.observe(document,"keypress",this.keysBind); +} +new CropDraggable(this.selArea,{drawMethod:this.moveArea.bindAsEventListener(this)}); +this.setParams(); +},registerHandles:function(_13){ +for(var i=0;i0&&this.options.ratioDim.y>0){ +_1a.x1=Math.ceil((this.imgW-this.options.ratioDim.x)/2); +_1a.y1=Math.ceil((this.imgH-this.options.ratioDim.y)/2); +_1a.x2=_1a.x1+this.options.ratioDim.x; +_1a.y2=_1a.y1+this.options.ratioDim.y; +_1b=true; +} +} +this.setAreaCoords(_1a,false,false,1); +if(this.options.displayOnInit&&_1b){ +this.selArea.show(); +this.drawArea(); +this.endCrop(); +} +this.attached=true; +},remove:function(){ +if(this.attached){ +this.attached=false; +this.imgWrap.parentNode.insertBefore(this.img,this.imgWrap); +this.imgWrap.parentNode.removeChild(this.imgWrap); +Event.stopObserving(this.dragArea,"mousedown",this.startDragBind); +Event.stopObserving(document,"mousemove",this.onDragBind); +Event.stopObserving(document,"mouseup",this.endCropBind); +this.registerHandles(false); +if(this.options.captureKeys){ +Event.stopObserving(document,"keypress",this.keysBind); +} +} +},reset:function(){ +if(!this.attached){ +this.onLoad(); +}else{ +this.setParams(); +} +this.endCrop(); +},handleKeys:function(e){ +var dir={x:0,y:0}; +if(!this.dragging){ +switch(e.keyCode){ +case (37): +dir.x=-1; +break; +case (38): +dir.y=-1; +break; +case (39): +dir.x=1; +break; +case (40): +dir.y=1; +break; +} +if(dir.x!=0||dir.y!=0){ +if(e.shiftKey){ +dir.x*=10; +dir.y*=10; +} +this.moveArea([this.areaCoords.x1+dir.x,this.areaCoords.y1+dir.y]); +Event.stop(e); +} +} +},calcW:function(){ +return (this.areaCoords.x2-this.areaCoords.x1); +},calcH:function(){ +return (this.areaCoords.y2-this.areaCoords.y1); +},moveArea:function(_1e){ +this.setAreaCoords({x1:_1e[0],y1:_1e[1],x2:_1e[0]+this.calcW(),y2:_1e[1]+this.calcH()},true,false); +this.drawArea(); +},cloneCoords:function(_1f){ +return {x1:_1f.x1,y1:_1f.y1,x2:_1f.x2,y2:_1f.y2}; +},setAreaCoords:function(_20,_21,_22,_23,_24){ +if(_21){ +var _25=_20.x2-_20.x1; +var _26=_20.y2-_20.y1; +if(_20.x1<0){ +_20.x1=0; +_20.x2=_25; +} +if(_20.y1<0){ +_20.y1=0; +_20.y2=_26; +} +if(_20.x2>this.imgW){ +_20.x2=this.imgW; +_20.x1=this.imgW-_25; +} +if(_20.y2>this.imgH){ +_20.y2=this.imgH; +_20.y1=this.imgH-_26; +} +}else{ +if(_20.x1<0){ +_20.x1=0; +} +if(_20.y1<0){ +_20.y1=0; +} +if(_20.x2>this.imgW){ +_20.x2=this.imgW; +} +if(_20.y2>this.imgH){ +_20.y2=this.imgH; +} +if(_23!=null){ +if(this.ratioX>0){ +this.applyRatio(_20,{x:this.ratioX,y:this.ratioY},_23,_24); +}else{ +if(_22){ +this.applyRatio(_20,{x:1,y:1},_23,_24); +} +} +var _27=[this.options.minWidth,this.options.minHeight]; +var _28=[this.options.maxWidth,this.options.maxHeight]; +if(_27[0]>0||_27[1]>0||_28[0]>0||_28[1]>0){ +var _29={a1:_20.x1,a2:_20.x2}; +var _2a={a1:_20.y1,a2:_20.y2}; +var _2b={min:0,max:this.imgW}; +var _2c={min:0,max:this.imgH}; +if((_27[0]!=0||_27[1]!=0)&&_22){ +if(_27[0]>0){ +_27[1]=_27[0]; +}else{ +if(_27[1]>0){ +_27[0]=_27[1]; +} +} +} +if((_28[0]!=0||_28[0]!=0)&&_22){ +if(_28[0]>0&&_28[0]<=_28[1]){ +_28[1]=_28[0]; +}else{ +if(_28[1]>0&&_28[1]<=_28[0]){ +_28[0]=_28[1]; +} +} +} +if(_27[0]>0){ +this.applyDimRestriction(_29,_27[0],_23.x,_2b,"min"); +} +if(_27[1]>1){ +this.applyDimRestriction(_2a,_27[1],_23.y,_2c,"min"); +} +if(_28[0]>0){ +this.applyDimRestriction(_29,_28[0],_23.x,_2b,"max"); +} +if(_28[1]>1){ +this.applyDimRestriction(_2a,_28[1],_23.y,_2c,"max"); +} +_20={x1:_29.a1,y1:_2a.a1,x2:_29.a2,y2:_2a.a2}; +} +} +} +this.areaCoords=_20; +},applyDimRestriction:function(_2d,val,_2f,_30,_31){ +var _32; +if(_31=="min"){ +_32=((_2d.a2-_2d.a1)val); +} +if(_32){ +if(_2f==1){ +_2d.a2=_2d.a1+val; +}else{ +_2d.a1=_2d.a2-val; +} +if(_2d.a1<_30.min){ +_2d.a1=_30.min; +_2d.a2=val; +}else{ +if(_2d.a2>_30.max){ +_2d.a1=_30.max-val; +_2d.a2=_30.max; +} +} +} +},applyRatio:function(_33,_34,_35,_36){ +var _37; +if(_36=="N"||_36=="S"){ +_37=this.applyRatioToAxis({a1:_33.y1,b1:_33.x1,a2:_33.y2,b2:_33.x2},{a:_34.y,b:_34.x},{a:_35.y,b:_35.x},{min:0,max:this.imgW}); +_33.x1=_37.b1; +_33.y1=_37.a1; +_33.x2=_37.b2; +_33.y2=_37.a2; +}else{ +_37=this.applyRatioToAxis({a1:_33.x1,b1:_33.y1,a2:_33.x2,b2:_33.y2},{a:_34.x,b:_34.y},{a:_35.x,b:_35.y},{min:0,max:this.imgH}); +_33.x1=_37.a1; +_33.y1=_37.b1; +_33.x2=_37.a2; +_33.y2=_37.b2; +} +},applyRatioToAxis:function(_38,_39,_3a,_3b){ +var _3c=Object.extend(_38,{}); +var _3d=_3c.a2-_3c.a1; +var _3e=Math.floor(_3d*_39.b/_39.a); +var _3f; +var _40; +var _41=null; +if(_3a.b==1){ +_3f=_3c.b1+_3e; +if(_3f>_3b.max){ +_3f=_3b.max; +_41=_3f-_3c.b1; +} +_3c.b2=_3f; +}else{ +_3f=_3c.b2-_3e; +if(_3f<_3b.min){ +_3f=_3b.min; +_41=_3f+_3c.b2; +} +_3c.b1=_3f; +} +if(_41!=null){ +_40=Math.floor(_41*_39.a/_39.b); +if(_3a.a==1){ +_3c.a2=_3c.a1+_40; +}else{ +_3c.a1=_3c.a1=_3c.a2-_40; +} +} +return _3c; +},drawArea:function(){ +var _42=this.calcW(); +var _43=this.calcH(); +var px="px"; +var _45=[this.areaCoords.x1+px,this.areaCoords.y1+px,_42+px,_43+px,this.areaCoords.x2+px,this.areaCoords.y2+px,(this.img.width-this.areaCoords.x2)+px,(this.img.height-this.areaCoords.y2)+px]; +var _46=this.selArea.style; +_46.left=_45[0]; +_46.top=_45[1]; +_46.width=_45[2]; +_46.height=_45[3]; +var _47=Math.ceil((_42-6)/2)+px; +var _48=Math.ceil((_43-6)/2)+px; +this.handleN.style.left=_47; +this.handleE.style.top=_48; +this.handleS.style.left=_47; +this.handleW.style.top=_48; +this.north.style.height=_45[1]; +var _49=this.east.style; +_49.top=_45[1]; +_49.height=_45[3]; +_49.left=_45[4]; +_49.width=_45[6]; +var _4a=this.south.style; +_4a.top=_45[5]; +_4a.height=_45[7]; +var _4b=this.west.style; +_4b.top=_45[1]; +_4b.height=_45[3]; +_4b.width=_45[0]; +this.subDrawArea(); +this.forceReRender(); +},forceReRender:function(){ +if(this.isIE||this.isWebKit){ +var n=document.createTextNode(" "); +var d,el,fixEL,i; +if(this.isIE){ +fixEl=this.selArea; +}else{ +if(this.isWebKit){ +fixEl=document.getElementsByClassName("imgCrop_marqueeSouth",this.imgWrap)[0]; +d=Builder.node("div",""); +d.style.visibility="hidden"; +var _4e=["SE","S","SW"]; +for(i=0;i<_4e.length;i++){ +el=document.getElementsByClassName("imgCrop_handle"+_4e[i],this.selArea)[0]; +if(el.childNodes.length){ +el.removeChild(el.childNodes[0]); +} +el.appendChild(d); +} +} +} +fixEl.appendChild(n); +fixEl.removeChild(n); +} +},startResize:function(e){ +this.startCoords=this.cloneCoords(this.areaCoords); +this.resizing=true; +this.resizeHandle=Event.element(e).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/,""); +Event.stop(e); +},startDrag:function(e){ +this.selArea.show(); +this.clickCoords=this.getCurPos(e); +this.setAreaCoords({x1:this.clickCoords.x,y1:this.clickCoords.y,x2:this.clickCoords.x,y2:this.clickCoords.y},false,false,null); +this.dragging=true; +this.onDrag(e); +Event.stop(e); +},getCurPos:function(e){ +var el=this.imgWrap,wrapOffsets=Position.cumulativeOffset(el); +while(el.nodeName!="BODY"){ +wrapOffsets[1]-=el.scrollTop||0; +wrapOffsets[0]-=el.scrollLeft||0; +el=el.parentNode; +} +return curPos={x:Event.pointerX(e)-wrapOffsets[0],y:Event.pointerY(e)-wrapOffsets[1]}; +},onDrag:function(e){ +if(this.dragging||this.resizing){ +var _54=null; +var _55=this.getCurPos(e); +var _56=this.cloneCoords(this.areaCoords); +var _57={x:1,y:1}; +if(this.dragging){ +if(_55.x_59){ +_5c.reverse(); +} +_5a[_5b+"1"]=_5c[0]; +_5a[_5b+"2"]=_5c[1]; +},endCrop:function(){ +this.dragging=false; +this.resizing=false; +this.options.onEndCrop(this.areaCoords,{width:this.calcW(),height:this.calcH()}); +},subInitialize:function(){ +},subDrawArea:function(){ +}}; +Cropper.ImgWithPreview=Class.create(); +Object.extend(Object.extend(Cropper.ImgWithPreview.prototype,Cropper.Img.prototype),{subInitialize:function(){ +this.hasPreviewImg=false; +if(typeof (this.options.previewWrap)!="undefined"&&this.options.minWidth>0&&this.options.minHeight>0){ +this.previewWrap=$(this.options.previewWrap); +this.previewImg=this.img.cloneNode(false); +this.previewImg.id="imgCrop_"+this.previewImg.id; +this.options.displayOnInit=true; +this.hasPreviewImg=true; +this.previewWrap.addClassName("imgCrop_previewWrap"); +this.previewWrap.setStyle({width:this.options.minWidth+"px",height:this.options.minHeight+"px"}); +this.previewWrap.appendChild(this.previewImg); +} +},subDrawArea:function(){ +if(this.hasPreviewImg){ +var _5d=this.calcW(); +var _5e=this.calcH(); +var _5f={x:this.imgW/_5d,y:this.imgH/_5e}; +var _60={x:_5d/this.options.minWidth,y:_5e/this.options.minHeight}; +var _61={w:Math.ceil(this.options.minWidth*_5f.x)+"px",h:Math.ceil(this.options.minHeight*_5f.y)+"px",x:"-"+Math.ceil(this.areaCoords.x1/_60.x)+"px",y:"-"+Math.ceil(this.areaCoords.y1/_60.y)+"px"}; +var _62=this.previewImg.style; +_62.width=_61.w; +_62.height=_61.h; +_62.left=_61.x; +_62.top=_61.y; +} +}}); + diff --git a/ansel/js/src/editcaption.js b/ansel/js/src/editcaption.js new file mode 100644 index 000000000..3b49ac2d4 --- /dev/null +++ b/ansel/js/src/editcaption.js @@ -0,0 +1,38 @@ +// InPlaceEditor extension based somewhat on an example given in the +// scriptaculous wiki +Ajax.InPlaceEditor.prototype.__initialize = Ajax.InPlaceEditor.prototype.initialize; +Ajax.InPlaceEditor.prototype.__getText = Ajax.InPlaceEditor.prototype.getText; +Object.extend(Ajax.InPlaceEditor.prototype, { + initialize: function(element, url, options) { + this.__initialize(element, url, options); + this.setOptions(options); + // Remove this line to stop from auto-showing the + // empty caption text on page load. + this.checkEmpty(); + }, + + setOptions: function(options) { + this.options = Object.extend(Object.extend(this.options, { + emptyClassName: 'inplaceeditor-empty' + }),options||{}); + }, + + checkEmpty: function() { + if (this.element.innerHTML.length == 0) { + emptyNode = new Element('span', {className: this.options.emptyClassName}).update(this.options.emptyText); + this.element.appendChild(emptyNode); + } + }, + + getText: function() { + $(this.element).select('.' + this.options.emptyClassName).each(function(child) { + this.element.removeChild(child); + }.bind(this)); + return this.__getText(); + } +}); + +function tileExit(ipe, e) +{ + ipe.checkEmpty(); +} diff --git a/ansel/js/src/editfaces.js b/ansel/js/src/editfaces.js new file mode 100644 index 000000000..4657ac155 --- /dev/null +++ b/ansel/js/src/editfaces.js @@ -0,0 +1,56 @@ +document.observe('dom:loaded', function() { + Ansel.deleteFace = function(image_id, face_id) + { + new Ajax.Request(Ansel.ajax.editFaces.url, + { + method: 'post', + parameters: { + action: 'delete', + image: image_id, + face: face_id + } + }); + $('face' + face_id).remove(); + }; + + Ansel.setFaceName = function(image_id, face_id) + { + new Ajax.Request(Ansel.ajax.editFaces.url, + { + method: 'post', + parameters: + { + action: 'setname', + face: face_id, + image: image_id, + facename: encodeURIComponent($F('facename' + face_id)) + }, + onComplete: function(r) { + if (r.responseJSON.response == 1) { + $('faces_widget_content').update(r.responseJSON.message); + } + } + } + ); + }; + + Ansel.doFaceEdit = function(image_id) + { + $('faces_widget_content').update(Ansel.ajax.editFaces.text.loading); + new Ajax.Request(Ansel.ajax.editFaces.url, + { + method: 'post', + parameters: + { + action: 'process', + image: image_id + }, + onComplete: function(r) { + if (r.responseJSON.response == 1) { + $('faces_widget_content').update(r.responseJSON.message); + } + } + } + ); + }; +}); \ No newline at end of file diff --git a/ansel/js/src/embed.js b/ansel/js/src/embed.js new file mode 100755 index 000000000..f7f49e6f8 --- /dev/null +++ b/ansel/js/src/embed.js @@ -0,0 +1,158 @@ +// 0) { + (function() { + var jx = j; + + var nextLink = new Element('a',{href: '#', title: 'Next Image', className: 'anselNext', style: 'text-decoration:none;width:40%;float:right;'}); + nextLink.update('>>'); + var arg1 = {node: jx, page: 1}; + nextLink.observe('click', function(e) {displayPage(e, arg1)}); + + var prevLink = new Element('a',{href: '#', title: 'Previous Image', className: 'anselPrev', style: 'text-decoration:none;width:40%;float:right;'}); + prevLink.update('<<'); + var arg2 = {node: jx, page: -1}; + prevLink.observe('click', function(e) {displayPage(e, arg2)}); + $(jx).appendChild(nextLink); + $(jx).appendChild(prevLink); + Horde_ToolTips.attachBehavior(jx); + Event.observe(window, 'unload', Horde_ToolTips.out.bind(Horde_ToolTips)); + + })(); + } else { + (function () { + var jx = j; + Horde_ToolTips.attachBehavior(jx); + })(); + } + } + if (lightboxData.length) { + lbOptions['gallery_json'] = lightboxData; + ansel_lb = new Lightbox(lbOptions); + } + + Event.observe(window, 'unload', Horde_ToolTips.out.bind(Horde_ToolTips)); + }); + +/** + * Display the images from the requested page for the requested node. + * + * @param string $node The DOM id of the embedded widget. + * @param integer $page The requested page number. + */ +function displayPage(event, args) { + var node = args.node; + var page = args.page; + var perpage = anseljson[node]['perpage']; + var imgcount = anseljson[node]['data'].size(); + var pages = Math.ceil(imgcount / perpage) - 1; + var oldPage = anseljson[node]['page']; + + page = oldPage + page; + + /* Rollover? */ + if (page > pages) { + page = 0; + } + if (page < 0) { + page = pages; + } + + var mainNode = $(node); + mainNode.update(); + var start = page * perpage; + var end = Math.min(imgcount - 1, start + perpage - 1); + for (var i = start; i <= end; i++) { + var imgContainer = mainNode.appendChild(new Element('span', {className: 'anselGalleryWidget'})); + var imgLink = imgContainer.appendChild(new Element('a', + { + href: anseljson[node]['data'][i][5], + alt: anseljson[node]['data'][i][2], + title: anseljson[node]['data'][i][2] + })); + imgLink.appendChild(new Element('img', {src: anseljson[node]['data'][i][0]})); + } + + var nextLink = new Element('a',{href: '', title: 'Next Image', style: 'text-decoration:none;width:40%;float:right;'}); + nextLink.update('>>'); + + var args = {node: node, page: ++oldPage}; + nextLink.observe('click', function(e) {displayPage(e, args);}.bind()); + + var prevLink = new Element('a',{href: '', title: 'Previous Image', style: 'text-decoration:none;width:40%;float:right;'}); + prevLink.update('<<'); + + var args = {node: node, page: --oldPage}; + prevLink.observe('click', function(e) {displayPage(e, args);}.bind()); + + mainNode.appendChild(nextLink); + mainNode.appendChild(prevLink); + + Horde_ToolTips.attachBehavior(node); + anseljson[node]['page'] = page; + event.stop(); +} +//] diff --git a/ansel/js/src/googlemap.js b/ansel/js/src/googlemap.js new file mode 100644 index 000000000..fb7689d7a --- /dev/null +++ b/ansel/js/src/googlemap.js @@ -0,0 +1,504 @@ +/** + * Google maps implementation for Ansel + * + * Copyright 2009 The Horde Project (http://www.horde.org/) + * + * $Horde: ansel/js/src/googlemap.js,v 1.36 2009/07/30 18:02:13 mrubinsk Exp $ + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @author Michael J. Rubinsky + */ +var Ansel_GMap = Class.create(); + +Ansel_GMap.prototype = { + // Main google map handle + mainMap: undefined, + + // Smaller overview map handle + smallMap: undefined, + + // Tinymarker icons... + tI: undefined, + tIO: undefined, + + // GLatLngBounds obejct for calculating proper center and zoom + bounds: undefined, + + // Geocoder + geocoder: undefined, + + // MarkerManager, if we are browsing the map. + // Note we need
    '; + } + +} + +/** + * Class to encapsulate a single gallery. Implemented as an extension of + * the Horde_Share_Object class. + * + * @author Michael J. Rubinsky + * @package Ansel + */ +class Ansel_Gallery extends Horde_Share_Object_sql_hierarchical { + + /** + * Cache the Gallery Id - to match the Ansel_Image interface + */ + var $id; + + /** + * The gallery mode helper + * + * @var Ansel_Gallery_Mode object + */ + var $_modeHelper; + + /** + * + */ + function __sleep() + { + $properties = get_object_vars($this); + unset($properties['_shareOb']); + unset($properties['_modeHelper']); + $properties = array_keys($properties); + return $properties; + } + + function __wakeup() + { + $this->setShareOb($GLOBALS['ansel_storage']->shares); + $mode = $this->get('view_mode'); + $this->_setModeHelper($mode); + } + + /** + * The Ansel_Gallery constructor. + * + * @param string $name The name of the gallery + */ + function Ansel_Gallery($attributes = array()) + { + /* Existing gallery? */ + if (!empty($attributes['share_id'])) { + $this->id = (int)$attributes['share_id']; + } + + /* Pass on up the chain */ + parent::Horde_Share_Object_sql_hierarchical($attributes); + $this->setShareOb($GLOBALS['ansel_storage']->shares); + $mode = isset($attributes['attribute_view_mode']) ? $attributes['attribute_view_mode'] : 'Normal'; + $this->_setModeHelper($mode); + } + + /** + * Check for special capabilities of this gallery. + * + */ + function hasFeature($feature) + { + + // First check for purely Ansel_Gallery features + // Currently we have none of these. + + // Delegate to the modeHelper + return $this->_modeHelper->hasFeature($feature); + + } + + /** + * Simple factory to retrieve the proper mode object. + * + * @param string $type The mode to use + * + * @return Ansel_Gallery_Mode object + */ + function _setModeHelper($type = 'Normal') + { + $type = basename($type); + $class = 'Ansel_GalleryMode_' . $type; + require_once dirname(__FILE__) . '/GalleryMode/' . $type . '.php'; + $this->_modeHelper = new $class($this); + $this->_modeHelper->init(); + } + + /** + * Checks if the user can download the full photo + * + * @return boolean Whether or not user can download full photos + */ + function canDownload() + { + if (Horde_Auth::getAuth() == $this->data['share_owner'] || Horde_Auth::isAdmin('ansel:admin')) { + return true; + } + + switch ($this->data['attribute_download']) { + case 'all': + return true; + + case 'authenticated': + return Horde_Auth::isAuthenticated(); + + case 'edit': + return $this->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT); + + case 'hook': + return Horde::callHook('_ansel_hook_can_download', array($this->id)); + + default: + return false; + } + } + + /** + * Saves any changes to this object to the backend permanently. + * + * @return mixed true || PEAR_Error on failure. + */ + function _save() + { + // Check for invalid characters in the slug. + if (!empty($this->data['attribute_slug']) && + preg_match('/[^a-zA-Z0-9_@]/', $this->data['attribute_slug'])) { + + return PEAR::raiseError( + sprintf(_("Could not save gallery, the slug, \"%s\", contains invalid characters."), + $this->data['attribute_slug'])); + } + + // Check for slug uniqueness + $slugGalleryId = $GLOBALS['ansel_storage']->slugExists($this->data['attribute_slug']); + if ($slugGalleryId > 0 && $slugGalleryId <> $this->id) { + return PEAR::raiseError(sprintf(_("Could not save gallery, the slug, \"%s\", already exists."), + $this->data['attribute_slug'])); + } + + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_Gallery' . $this->id); + } + return parent::_save(); + } + + /** + * Update the gallery image count. + * + * @param integer $images Number of images in action + * @param boolean $add Action to take (add or remove) + * @param integer $gallery_id Gallery id to update images for + */ + function _updateImageCount($images, $add = true, $gallery_id = null) + { + // We do the query directly here to avoid having to instantiate a + // gallery object just to increment/decrement one value in the table. + $sql = 'UPDATE ' . $this->_shareOb->_table + . ' SET attribute_images = attribute_images ' + . ($add ? ' + ' : ' - ') . $images . ' WHERE share_id = ' + . ($gallery_id ? $gallery_id : $this->id); + + // Make sure to update the local value as well, so it doesn't get + // overwritten by any other updates from ->set() calls. + if (is_null($gallery_id) || $gallery_id === $this->id) { + if ($add) { + $this->data['attribute_images'] += $images; + } else { + $this->data['attribute_images'] -= $images; + } + } + + /* Need to expire the cache for the gallery that was changed */ + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $id = (is_null($gallery_id) ? $this->id : $gallery_id); + $GLOBALS['cache']->expire('Ansel_Gallery' . $id); + } + + return $this->_shareOb->_write_db->exec($sql); + + } + + /** + * Add an image to this gallery. + * + * @param array $image_data The image to add. Required keys include + * 'image_caption', and 'data'. Optional keys + * include 'image_filename' and 'image_type' + * + * @param boolean $default Make this image the new default tile image. + * + * @return integer The id of the new image. + */ + function addImage($image_data, $default = false) + { + global $conf; + + /* Normal is the only view mode that can accurately update gallery counts */ + $vMode = $this->get('view_mode'); + if ($vMode != 'Normal') { + $this->_setModeHelper('Normal'); + } + + $resetStack = false; + if (!isset($image_data['image_filename'])) { + $image_data['image_filename'] = 'Untitled'; + } + $image_data['gallery_id'] = $this->id; + $image_data['image_sort'] = $this->countImages(); + + /* Create the image object */ + $image = new Ansel_Image($image_data); + $result = $image->save(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (empty($image_data['image_id'])) { + $this->_updateImageCount(1); + if ($this->countImages() < 5) { + $resetStack = true; + } + } + + /* Should this be the default image? */ + if (!$default && $this->data['attribute_default_type'] == 'auto') { + $this->data['attribute_default'] = $image->id; + $resetStack = true; + } elseif ($default) { + $this->data['attribute_default'] = $image->id; + $this->data['default_type'] = 'manual'; + } + + /* Reset the gallery default image stacks if needed. */ + if ($resetStack) { + $this->clearStacks(); + } + + /* Update the modified flag and save gallery changes */ + $this->data['attribute_last_modified'] = time(); + + /* Save all changes to the gallery */ + $this->save(); + + /* Return to the proper view mode */ + if ($vMode != 'Normal') { + $this->_setModeHelper($vMode); + } + + /* Return the ID of the new image. */ + return $image->id; + } + + /** + * Clear all of this gallery's default image stacks from the VFS and the + * gallery's data store. + * + */ + function clearStacks() + { + $ids = @unserialize($this->data['attribute_default_prettythumb']); + if (is_array($ids)) { + foreach ($ids as $imageId) { + $this->removeImage($imageId, true); + } + } + + // Using the set function here so we can efficently update the db + $this->set('default_prettythumb', '', true); + } + + /** + * Removes all generated and cached 'prettythumb' thumbnails for this + * gallery + * + */ + function clearThumbs() + { + $images = $this->listImages(); + foreach ($images as $id) { + $image = $this->getImage($id); + $image->deleteCache('prettythumb'); + } + } + + /** + * Removes all generated and cached views for this gallery + * + */ + function clearViews() + { + $images = $this->listImages(); + foreach ($images as $id) { + $image = $this->getImage($id); + $image->deleteCache('all'); + } + } + + /** + * Move images from this gallery to a new gallery. + * + * @param array $images An array of image ids. + * @param Ansel_Gallery $gallery The gallery to move the images to. + * + * @return integer | PEAR_Error The number of images moved, or an error message. + */ + function moveImagesTo($images, $gallery) + { + return $this->_modeHelper->moveImagesTo($images, $gallery); + } + + /** + * Copy image and related data to specified gallery. + * + * @param array $images An array of image ids. + * @param Ansel_Gallery $gallery The gallery to copy images to. + * + * @return integer | PEAR_Error The number of images copied or error message + */ + function copyImagesTo($images, $gallery) + { + if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + return PEAR::raiseError( + sprintf(_("Access denied copying photos to \"%s\"."), + $gallery->get('name'))); + } + + $db = $this->_shareOb->_write_db; + $imgCnt = 0; + foreach ($images as $imageId) { + $img = &$this->getImage($imageId); + // Note that we don't pass the tags when adding the image..see below + $newId = $gallery->addImage(array( + 'image_caption' => $img->caption, + 'data' => $img->raw(), + 'image_filename' => $img->filename, + 'image_type' => $img->getType(), + 'image_uploaded_date' => $img->uploaded)); + if (is_a($newId, 'PEAR_Error')) { + return $newId; + } + /* Copy any tags */ + // Since we know that the tags already exist, no need to + // go through Ansel_Tags::writeTags() - this saves us a SELECT query + // for each tag - just write the data into the DB ourselves. + $tags = $img->getTags(); + $query = $this->_shareOb->_write_db->prepare('INSERT INTO ansel_images_tags (image_id, tag_id) VALUES(' . $newId . ',?);'); + if (is_a($query, 'PEAR_Error')) { + return $query; + } + foreach ($tags as $tag_id => $tag_name) { + $result = $query->execute($tag_id); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + $query->free(); + + /* exif data */ + // First check to see if the exif data was present in the raw data. + $count = $db->queryOne('SELECT COUNT(image_id) FROM ansel_image_attributes WHERE image_id = ' . (int) $newId . ';'); + if ($count == 0) { + $exif = $db->queryAll('SELECT attr_name, attr_value FROM ansel_image_attributes WHERE image_id = ' . (int) $imageId . ';',null, MDB2_FETCHMODE_ASSOC); + if (is_array($exif) && count($exif) > 0) { + $insert = $db->prepare('INSERT INTO ansel_image_attributes (image_id, attr_name, attr_value) VALUES (?, ?, ?)'); + if (is_a($insert, 'PEAR_Error')) { + return $insert; + } + foreach ($exif as $attr){ + $result = $insert->execute(array($newId, $attr['attr_name'], $attr['attr_value'])); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + $insert->free(); + } + } + ++$imgCnt; + } + + return $imgCnt; + } + + /** + * Set the order of an image in this gallery. + * + * @param integer $imageId The image to sort. + * @param integer $pos The sort position of the image. + */ + function setImageOrder($imageId, $pos) + { + return $this->_shareOb->_write_db->exec('UPDATE ansel_images SET image_sort = ' . (int)$pos . ' WHERE image_id = ' . (int)$imageId); + } + + /** + * Remove the given image from this gallery. + * + * @param mixed $image Image to delete. Can be an Ansel_Image + * or an image ID. + * + * @return boolean True on success, false on failure. + */ + function removeImage($image, $isStack = false) + { + return $this->_modeHelper->removeImage($image, $isStack); + } + + /** + * Returns this share's owner's Identity object. + * + * @return Identity object for the owner of this gallery. + */ + function getOwner() + { + require_once 'Horde/Identity.php'; + $identity = &Identity::singleton('none', $this->data['share_owner']); + return $identity; + } + + /** + * Output the HTML for this gallery's tile. + * + * @param Ansel_Gallery $parent The parent Ansel_Gallery object + * @param string $style A named gallery style to use. + * @param boolean $mini Force the use of a mini thumbnail? + * @param array $params Any additional parameters the Ansel_Tile + * object may need. + */ + function getTile($parent = null, $style = null, $mini = false, + $params = array()) + { + require_once ANSEL_BASE . '/lib/Tile/Gallery.php'; + + if (!is_null($parent) && is_null($style)) { + $style = $parent->getStyle(); + } else { + $style = Ansel::getStyleDefinition($style); + } + + if (!empty($view_url)) { + $view_url = str_replace('%g', $this->id, $view_url); + } + + return Ansel_Tile_Gallery::getTile($this, $style, $mini, $params); + } + + /** + * Get the children of this gallery. + * + * @param integer $perm The permissions to limit to. + * @param integer $from The child to start at. + * @param integer $to The child to end with. + * @param boolean $noauto Prevent auto + * + * @return A mixed array of Ansel_Gallery and Ansel_Image objects that are + * children of this gallery. + */ + function getGalleryChildren($perm = PERMS_SHOW, $from = 0, $to = 0, $noauto = true) + { + return $this->_modeHelper->getGalleryChildren($perm, $from, $to, $noauto); + } + + + /** + * Return the count of this gallery's children + * + * @param integer $perm The permissions to require. + * @param boolean $galleries_only Only include galleries, no images. + * + * @return integer The count of this gallery's children. + */ + function countGalleryChildren($perm = PERMS_SHOW, $galleries_only = false, $noauto = true) + { + return $this->_modeHelper->countGalleryChildren($perm, $galleries_only, $noauto); + } + + /** + * Lists a slice of the image ids in this gallery. + * + * @param integer $from The image to start listing. + * @param integer $count The numer of images to list. + * + * @return mixed An array of image_ids | PEAR_Error + */ + function listImages($from = 0, $count = 0) + { + return $this->_modeHelper->listImages($from, $count); + } + + /** + * Gets a slice of the images in this gallery. + * + * @param integer $from The image to start fetching. + * @param integer $count The numer of images to return. + * + * @param mixed An array of Ansel_Image objects | PEAR_Error + */ + function getImages($from = 0, $count = 0) + { + return $this->_modeHelper->getImages($from, $count); + } + + /** + * Return the most recently added images in this gallery. + * + * @param integer $limit The maximum number of images to return. + * + * @return mixed An array of Ansel_Image objects | PEAR_Error + */ + function getRecentImages($limit = 10) + { + return $GLOBALS['ansel_storage']->getRecentImages(array($this->id), + $limit); + } + + /** + * Returns the image in this gallery corresponding to the given id. + * + * @param integer $id The ID of the image to retrieve. + * + * @return Ansel_Image The image object corresponding to the given id. + */ + function &getImage($id) + { + return $GLOBALS['ansel_storage']->getImage($id); + } + + /** + * Checks if the gallery has any subgallery + */ + function hasSubGalleries() + { + return $this->_modeHelper->hasSubGalleries(); + } + + /** + * Returns the number of images in this gallery and, optionally, all + * sub-galleries. + * + * @param boolean $subgalleries Determines whether subgalleries should + * be counted or not. + * + * @return integer number of images in this gallery + */ + function countImages($subgalleries = false) + { + return $this->_modeHelper->countImages($subgalleries); + } + + /** + * Returns the default image for this gallery. + * + * @param string $style Force the use of this style, if it's available + * otherwise use whatever style is choosen for this + * gallery. If prettythumbs are not available then + * we always use ansel_default style. + * + * @return mixed The image_id of the default image or false. + */ + function getDefaultImage($style = null) + { + // Check for explicitly requested style + if (!is_null($style)) { + $gal_style = Ansel::getStyleDefinition($style); + } else { + // Use gallery's default. + $gal_style = $this->getStyle(); + if (!isset($GLOBALS['ansel_styles'][$gal_style['name']])) { + $gal_style = $GLOBALS['ansel_styles']['ansel_default']; + } + } + Horde::logMessage(sprintf("using gallery style: %s in Ansel::getDefaultImage()", $gal_style['name']), __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (!empty($gal_style['default_galleryimage_type']) && + $gal_style['default_galleryimage_type'] != 'plain') { + + $thumbstyle = $gal_style['default_galleryimage_type']; + $styleHash = $this->_getViewHash($thumbstyle, $style); + + // First check for the existence of a default image in the style + // we are looking for. + if (!empty($this->data['attribute_default_prettythumb'])) { + $thumbs = @unserialize($this->data['attribute_default_prettythumb']); + } + if (!isset($thumbs) || !is_array($thumbs)) { + $thumbs = array(); + } + + if (!empty($thumbs[$styleHash])) { + return $thumbs[$styleHash]; + } + + // Don't already have one, must generate it. + require_once dirname(__FILE__) . '/ImageView.php'; + $params = array('gallery' => $this, 'style' => $gal_style); + $iview = Ansel_ImageView::factory( + $gal_style['default_galleryimage_type'], $params); + + if (!is_a($iview, 'PEAR_Error')) { + $img = $iview->create(); + if (!is_a($img, 'PEAR_Error')) { + // Note the gallery_id is negative for generated stacks + $iparams = array('image_filename' => $this->get('name'), + 'image_caption' => $this->get('name'), + 'data' => $img->raw(), + 'image_sort' => 0, + 'gallery_id' => -$this->id); + $newImg = new Ansel_Image($iparams); + $newImg->save(); + $prettyData = serialize( + array_merge($thumbs, + array($styleHash => $newImg->id))); + + $this->set('default_prettythumb', $prettyData, true); + return $newImg->id; + } else { + Horde::logMessage($img, __FILE__, __LINE__, PEAR_LOG_ERR); + } + } else { + // Might not support the requested style...try ansel_default + // but protect against infinite recursion. + Horde::logMessage($iview, __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($style != 'ansel_default') { + return $this->getDefaultImage('ansel_default'); + } + Horde::logMessage($iview, __FILE__, __LINE__, PEAR_LOG_ERR); + } + } else { + // We are just using an image thumbnail for the gallery default. + if ($this->countImages()) { + if (!empty($this->data['attribute_default']) && + $this->data['attribute_default'] > 0) { + + return $this->data['attribute_default']; + } + $keys = $this->listImages(); + if (is_a($keys, 'PEAR_Error')) { + return $keys; + } + $this->data['attribute_default'] = $keys[count($keys) - 1]; + $this->data['attribute_default_type'] = 'auto'; + $this->save(); + return $keys[count($keys) - 1]; + } + + if ($this->hasSubGalleries()) { + // Fall through to a default image of a sub gallery. + $galleries = $GLOBALS['ansel_storage']->listGalleries( + PERMS_SHOW, null, $this, false); + if ($galleries && !is_a($galleries, 'PEAR_Error')) { + foreach ($galleries as $galleryId => $gallery) { + if ($default_img = $gallery->getDefaultImage($style)) { + return $default_img; + } + } + } + } + } + return false; + } + + /** + * Returns this gallery's tags. + */ + function getTags() { + require_once ANSEL_BASE . '/lib/Tags.php'; + if ($this->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + return Ansel_Tags::readTags($this->id, 'gallery'); + } else { + return PEAR::raiseError(_("Access denied viewing this gallery.")); + } + } + + /** + * Set/replace this gallery's tags. + * + * @param array $tags AN array of tag names to associate with this image. + */ + function setTags($tags) + { + require_once ANSEL_BASE . '/lib/Tags.php'; + if ($this->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + return Ansel_Tags::writeTags($this->id, $tags, 'gallery'); + } else { + return PEAR::raiseError(_("Access denied adding tags to this gallery.")); + } + } + + /** + * Return the style definition for this gallery. Returns the first available + * style in this order: Explicitly configured style if available, if + * configured style is not available, use ansel_default. If nothing has + * been configured, the user's selected default is attempted. + * + * @return array The style definition array. + */ + function getStyle() + { + if (empty($this->data['attribute_style'])) { + $style = $GLOBALS['prefs']->getValue('default_gallerystyle'); + } else { + $style = $this->data['attribute_style']; + } + return Ansel::getStyleDefinition($style); + + } + + /** + * Return a hash key for the given view and style. + * + * @param string $view The view (thumb, prettythumb etc...) + * @param string $style The named style. + * + * @return string A md5 hash suitable for use as a key. + */ + function _getViewHash($view, $style = null) + { + if (is_null($style)) { + $style = $this->getStyle(); + } else { + $style = Ansel::getStyleDefinition($style); + } + if ($view != 'screen' && $view != 'thumb' && $view != 'mini' && + $view != 'full') { + + $view = md5($style['thumbstyle'] . '.' . $style['background']); + } + return $view; + } + /** + * Checks to see if a user has a given permission. + * + * @param string $userid The userid of the user. + * @param integer $permission A PERMS_* constant to test for. + * @param string $creator The creator of the event. + * + * @return boolean Whether or not $userid has $permission. + */ + function hasPermission($userid, $permission, $creator = null) + { + if ($userid == $this->data['share_owner'] || + Horde_Auth::isAdmin('ansel:admin')) { + + return true; + } + + + return $GLOBALS['perms']->hasPermission($this->getPermission(), + $userid, $permission, $creator); + } + + /** + * Check user age limtation + * + * @return boolean + */ + function isOldEnough() + { + if ($this->data['share_owner'] == Horde_Auth::getAuth() || + empty($GLOBALS['conf']['ages']['limits']) || + empty($this->data['attribute_age'])) { + + return true; + } + + // Do we have the user age already cheked? + if (!isset($_SESSION['ansel']['user_age'])) { + $_SESSION['ansel']['user_age'] = 0; + } elseif ($_SESSION['ansel']['user_age'] >= $this->data['attribute_age']) { + return true; + } + + // Can we hook user's age? + if ($GLOBALS['conf']['ages']['hook'] && Horde_Auth::isAuthenticated()) { + $result = Horde::callHook('_ansel_hook_user_age'); + if (is_int($result)) { + $_SESSION['ansel']['user_age'] = $result; + } + } + + return ($_SESSION['ansel']['user_age'] >= $this->data['attribute_age']); + } + + /** + * Determine if we need to unlock a password protected gallery + * + * @return boolean + */ + function hasPasswd() + { + if (Horde_Auth::getAuth() == $this->get('owner') || Horde_Auth::isAdmin('ansel:admin')) { + return false; + } + + $passwd = $this->get('passwd'); + if (empty($passwd) || + (!empty($_SESSION['ansel']['passwd'][$this->id]) + && $_SESSION['ansel']['passwd'][$this->id] = md5($this->get('passwd')))) { + return false; + } + + return true; + } + + /** + * Sets this gallery's parent gallery. + * + * @TODO: Check how this interacts with date galleries - shouldn't be able + * to remove a subgallery from a date gallery anyway, but just incase + * @param mixed $parent An Ansel_Gallery or a gallery_id. + * + * @return mixed Ture || PEAR_Error + */ + function setParent($parent) + { + /* Make sure we have a gallery object */ + if (!is_null($parent) && !is_a($parent, 'Ansel_Gallery')) { + $parent = $GLOBALS['ansel_storage']->getGallery($parent); + if (is_a($parent, 'PEAR_Error')) { + return $parent; + } + } + + /* Check this now since we don't know if we are updating the DB or not */ + $old = $this->getParent(); + $reset_has_subgalleries = false; + if (!is_null($old)) { + $cnt = $old->countGalleryChildren(PERMS_READ, true); + if ($cnt == 1) { + /* Count is 1, and we are about to delete it */ + $reset_has_subgalleries = true; + } + } + + /* Call the parent class method */ + $result = parent::setParent($parent); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + /* Tell the parent the good news */ + if (!is_null($parent) && !$parent->get('has_subgalleries')) { + return $parent->set('has_subgalleries', '1', true); + } + Horde::logMessage('Ansel_Gallery parent successfully set', __FILE__, + __LINE__, PEAR_LOG_DEBUG); + + /* Gallery parent changed, safe to change the parent's attributes */ + if ($reset_has_subgalleries) { + $old->set('has_subgalleries', 0, true); + } + + return true; + } + + /** + * Sets an attribute value in this object. + * + * @param string $attribute The attribute to set. + * @param mixed $value The value for $attribute. + * @param boolean $update Commit only this change to storage. + * + * @return mixed True if setting the attribute did succeed, a PEAR_Error + * otherwise. + */ + function set($attribute, $value, $update = false) + { + /* Translate the keys */ + if ($attribute == 'owner') { + $driver_key = 'share_owner'; + } else { + $driver_key = 'attribute_' . $attribute; + } + + if ($driver_key == 'attribute_view_mode' && + !empty($this->data[$driver_key]) && + $value != $this->data[$driver_key]) { + + $mode = isset($attributes['attribute_view_mode']) ? $attributes['attribute_view_mode'] : 'Normal'; + $this->_setModeHelper($mode); + } + + $this->data[$driver_key] = $value; + + /* Update the backend, but only this current change */ + if ($update) { + $db = $this->_shareOb->_write_db; + // Manually convert the charset since we're not going through save() + $data = $this->_shareOb->_toDriverCharset(array($driver_key => $value)); + $query = $db->prepare('UPDATE ' . $this->_shareOb->_table . ' SET ' . $driver_key . ' = ? WHERE share_id = ?', null, MDB2_PREPARE_MANIP); + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_Gallery' . $this->id); + } + $result = $query->execute(array($data[$driver_key], $this->id)); + $query->free(); + + return $result; + } + + return true; + } + + function setDate($date) + { + $this->_modeHelper->setDate($date); + } + + function getDate() + { + return $this->_modeHelper->getDate(); + } + + /** + * Get an array describing where this gallery is in a breadcrumb trail. + * + * @return An array of 'title' and 'navdata' hashes with the [0] element + * being the deepest part. + */ + function getGalleryCrumbData() + { + return $this->_modeHelper->getGalleryCrumbData(); + } + +} + +/** + * Class to describe a single Ansel image. + * + * @author Chuck Hagenbuch + * @author Michael J. Rubinsky + * @package Ansel + */ +class Ansel_Image { + + /** + * @var integer The gallery id of this image's parent gallery + */ + var $gallery; + + /** + * @var Horde_Image Horde_Image object for this image. + */ + var $_image; + + var $id = null; + var $filename = 'Untitled'; + var $caption = ''; + var $type = 'image/jpeg'; + + /** + * timestamp of uploaded date + * + * @var integer + */ + var $uploaded; + + var $sort; + var $commentCount; + var $facesCount; + var $lat; + var $lng; + var $location; + var $geotag_timestamp; + + var $_dirty; + + + /** + * Timestamp of original date. + * + * @var integer + */ + var $originalDate; + + /** + * Holds an array of tags for this image + * @var array + */ + var $_tags = array(); + + var $_loaded = array(); + var $_data = array(); + + /** + * Cache the raw EXIF data locally + * + * @var array + */ + var $_exif = array(); + + /** + * TODO: refactor Ansel_Image to use a ::get() method like Ansel_Gallery + * instead of direct instance variable access and all the nonsense below. + * + * @param unknown_type $image + * @return Ansel_Image + */ + function Ansel_Image($image = array()) + { + if ($image) { + $this->filename = $image['image_filename']; + $this->caption = $image['image_caption']; + $this->sort = $image['image_sort']; + $this->gallery = $image['gallery_id']; + + // New image? + if (!empty($image['image_id'])) { + $this->id = $image['image_id']; + } + + if (!empty($image['data'])) { + $this->_data['full'] = $image['data']; + } + + if (!empty($image['image_uploaded_date'])) { + $this->uploaded = $image['image_uploaded_date']; + } else { + $this->uploaded = time(); + } + + if (!empty($image['image_type'])) { + $this->type = $image['image_type']; + } + + if (!empty($image['tags'])) { + $this->_tags = $image['tags']; + } + + if (!empty($image['image_faces'])) { + $this->facesCount = $image['image_faces']; + } + + $this->location = !empty($image['image_location']) ? $image['image_location'] : ''; + + // The following may have to be rewritten by EXIF. + // EXIF requires both an image id and a stream, so we can't + // get EXIF data before we save the image to the VFS. + if (!empty($image['image_original_date'])) { + $this->originalDate = $image['image_original_date']; + } else { + $this->originalDate = $this->uploaded; + } + $this->lat = !empty($image['image_latitude']) ? $image['image_latitude'] : ''; + $this->lng = !empty($image['image_longitude']) ? $image['image_longitude'] : ''; + $this->geotag_timestamp = !empty($image['image_geotag_date']) ? $image['image_geotag_date'] : '0'; + } + + $this->_image = Ansel::getImageObject(); + $this->_image->reset(); + } + + /** + * Return the vfs path for this image. + * + * @param string $view The view we want. + * @param string $style A named gallery style. + * + * @return string The vfs path for this image. + */ + function getVFSPath($view = 'full', $style = null) + { + $view = $this->_getViewHash($view, $style); + return '.horde/ansel/' + . substr(str_pad($this->id, 2, 0, STR_PAD_LEFT), -2) + . '/' . $view; + } + + /** + * Returns the file name of this image as used in the VFS backend. + * + * @return string This image's VFS file name. + */ + function getVFSName($view) + { + $vfsname = $this->id; + + if ($view == 'full' && $this->type) { + $type = strpos($this->type, '/') === false ? 'image/' . $this->type : $this->type; + require_once 'Horde/Mime/Magic.php'; + if ($ext = Horde_Mime_Magic::mimeToExt($type)) { + $vfsname .= '.' . $ext; + } + } elseif (($GLOBALS['conf']['image']['type'] == 'jpeg') || $view == 'screen') { + $vfsname .= '.jpg'; + } else { + $vfsname .= '.png'; + } + + return $vfsname; + } + + /** + * Loads the given view into memory. + * + * @param string $view Which view to load. + * @param string $style The named gallery style. + * + * @return mixed True || PEAR_Error + */ + function load($view = 'full', $style = null) + { + // If this is a new image that hasn't been saved yet, we will + // already have the full data loaded. If we auto-rotate the image + // then there is no need to save it just to load it again. + if ($view == 'full' && !empty($this->_data['full'])) { + $this->_image->loadString('original', $this->_data['full']); + $this->_loaded['full'] = true; + return true; + } + + $viewHash = $this->_getViewHash($view, $style); + /* If we've already loaded the data, just return now. */ + if (!empty($this->_loaded[$viewHash])) { + return true; + } + + $result = $this->createView($view, $style); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + /* If createView() had to resize the full image, we've already + * loaded the data, so return now. */ + if (!empty($this->_loaded[$viewHash])) { + return; + } + + /* We've definitely successfully loaded the image now. */ + $this->_loaded[$viewHash] = true; + + /* Get the VFS info. */ + $vfspath = $this->getVFSPath($view, $style); + if (is_a($vfspath, 'PEAR_Error')) { + return $vfspath; + } + + /* Read in the requested view. */ + $data = $GLOBALS['ansel_vfs']->read($vfspath, $this->getVFSName($view)); + if (is_a($data, 'PEAR_Error')) { + Horde::logMessage($date, __FILE__, __LINE__, PEAR_LOG_ERR); + return $data; + } + + $this->_data[$viewHash] = $data; + $this->_image->loadString($vfspath . '/' . $this->id, $data); + return true; + } + + /** + * Check if an image view exists and returns the vfs name complete with + * the hash directory name prepended if appropriate. + * + * @param integer $id Image id to check + * @param string $view Which view to check for + * @param string $style A named gallery style + * + * @return mixed False if image does not exists | string vfs name + * + * @static + */ + function viewExists($id, $view, $style) + { + /* We cannot check empty styles since we cannot get the hash */ + if (empty($style)) { + return false; + } + + /* Get the VFS path. */ + $view = Ansel_Gallery::_getViewHash($view, $style); + + /* Can't call the various vfs methods here, since this method needs + to be called statically */ + $vfspath = '.horde/ansel/' . substr(str_pad($id, 2, 0, STR_PAD_LEFT), -2) . '/' . $view; + + /* Get VFS name */ + $vfsname = $id . '.'; + if ($GLOBALS['conf']['image']['type'] == 'jpeg' || $view == 'screen') { + $vfsname .= 'jpg'; + } else { + $vfsname .= 'png'; + } + + if ($GLOBALS['ansel_vfs']->exists($vfspath, $vfsname)) { + return $view . '/' . $vfsname; + } else { + return false; + } + } + + /** + * Creates and caches the given view. + * + * @param string $view Which view to create. + * @param string $style A named gallery style + */ + function createView($view, $style = null) + { + // HACK: Need to replace the image object with a JPG typed image if + // we are generating a screen image. Need to do the replacement + // and do it *here* for BC reasons with Horde_Image...and this + // needs to be done FIRST, since the view might already be cached + // in the VFS. + if ($view == 'screen' && $GLOBALS['conf']['image']['type'] != 'jpeg') { + $this->_image = Ansel::getImageObject(array('type' => 'jpeg')); + $this->_image->reset(); + } + + /* Get the VFS info. */ + $vfspath = $this->getVFSPath($view, $style); + if ($GLOBALS['ansel_vfs']->exists($vfspath, $this->getVFSName($view))) { + return true; + } + + $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'), + $this->getVFSName('full')); + if (is_a($data, 'PEAR_Error')) { + Horde::logMessage($data, __FILE__, __LINE__, PEAR_LOG_ERR); + return $data; + } + + if (is_a($result = $this->_image->loadString($this->getVFSPath('full') . '/' . $this->id, $data), 'PEAR_Error')) { + return $result; + } + $styleDef = Ansel::getStyleDefinition($style); + + require_once dirname(__FILE__) . '/ImageView.php'; + if ($view == 'prettythumb') { + $viewType = $styleDef['thumbstyle']; + } else { + $viewType = $view; + } + $iview = Ansel_ImageView::factory($viewType, array('image' => $this, + 'style' => $style)); + + if (is_a($iview, 'PEAR_Error')) { + // It could be we don't support the requested effect, try + // ansel_default before giving up. + if ($view == 'prettythumb') { + $iview = Ansel_ImageView::factory( + 'thumb', array('image' => $this, + 'style' => 'ansel_default')); + + if (is_a($iview, 'PEAR_Error')) { + return $iview; + } + } + } + + $res = $iview->create(); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + + $view = $this->_getViewHash($view, $style); + + $this->_data[$view] = $this->_image->raw(); + $this->_image->loadString($vfspath . '/' . $this->id, + $this->_data[$view]); + $this->_loaded[$view] = true; + $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view), + $this->_data[$view], true); + + // Autowatermark the screen view + if ($view == 'screen' && + $GLOBALS['prefs']->getValue('watermark_auto') && + $GLOBALS['prefs']->getValue('watermark_text') != '') { + + $this->watermark('screen'); + $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view), + $this->_image->_data); + } + + return true; + } + + /** + * Writes the current data to vfs, used when creating a new image + */ + function _writeData() + { + $this->_dirty = false; + return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath('full'), + $this->getVFSName('full'), + $this->_data['full'], true); + } + + /** + * Change the image data. Deletes old cache and writes the new + * data to the VFS. Used when updating an image + * + * @param string $data The new data for this image. + * @param string $view If specified, the $data represents only this + * particular view. Cache will not be deleted. + */ + function updateData($data, $view = 'full') + { + if (is_a($data, 'PEAR_Error')) { + return $data; + } + + /* Delete old cached data if we are replacing the full image */ + if ($view == 'full') { + $this->deleteCache(); + } + + return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath($view), + $this->getVFSName($view), + $data, true); + } + + /** + * Update the geotag data + */ + function geotag($lat, $lng, $location = '') + { + $this->lat = $lat; + $this->lng = $lng; + $this->location = $location; + $this->geotag_timestamp = time(); + $this->save(); + } + + /** + * Save basic image details + * + * @TODO: Move all SQL queries to Ansel_Storage::? + */ + function save() + { + /* If we have an id, then it's an existing image.*/ + if ($this->id) { + $update = $GLOBALS['ansel_db']->prepare('UPDATE ansel_images SET image_filename = ?, image_type = ?, image_caption = ?, image_sort = ?, image_original_date = ?, image_latitude = ?, image_longitude = ?, image_location = ?, image_geotag_date = ? WHERE image_id = ?'); + if (is_a($update, 'PEAR_Error')) { + Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR); + return $update; + } + $result = $update->execute(array(Horde_String::convertCharset($this->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']), + $this->type, + Horde_String::convertCharset($this->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']), + $this->sort, + $this->originalDate, + $this->lat, + $this->lng, + $this->location, + $this->geotag_timestamp, + $this->id)); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR); + } else { + $update->free(); + } + return $result; + } + + /* Saving a new Image */ + if (!$this->gallery || !strlen($this->filename) || !$this->type) { + $error = PEAR::raiseError(_("Incomplete photo")); + Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR); + } + + /* Get the next image_id */ + $image_id = $GLOBALS['ansel_db']->nextId('ansel_images'); + if (is_a($image_id, 'PEAR_Error')) { + return $image_id; + } + + /* Prepare the SQL statement */ + $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_images (image_id, gallery_id, image_filename, image_type, image_caption, image_uploaded_date, image_sort, image_original_date, image_latitude, image_longitude, image_location, image_geotag_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + if (is_a($insert, 'PEAR_Error')) { + Horde::logMessage($insert, __FILE__, __LINE__, PEAR_LOG_ERR); + return $insert; + } + + /* Perform the INSERT */ + $result = $insert->execute(array($image_id, + $this->gallery, + Horde_String::convertCharset($this->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']), + $this->type, + Horde_String::convertCharset($this->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']), + $this->uploaded, + $this->sort, + $this->originalDate, + $this->lat, + $this->lng, + $this->location, + (empty($this->lat) ? 0 : $this->uploaded))); + $insert->free(); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + /* Keep the image_id */ + $this->id = $image_id; + + /* The EXIF functions require a stream, so we need to save before we read */ + $this->_writeData(); + + /* Get the EXIF data now that we have an id and a file in the VFS */ + $needUpdate = $this->_getEXIF(); + + /* Create tags from exif data if desired */ + $fields = @unserialize($GLOBALS['prefs']->getValue('exif_tags')); + if ($fields) { + $this->_exifToTags($fields); + } + + /* Save the tags */ + if (count($this->_tags)) { + $result = $this->setTags($this->_tags); + if (is_a($result, 'PEAR_Error')) { + // Since we got this far, the image has been added, so + // just log the tag failure. + Horde::logMessage($result, __LINE__, __FILE__, PEAR_LOG_ERR); + } + } + + /* Save again if EXIF changed any values */ + if ($needUpdate) { + $this->save(); + } + + return $this->id; + } + + /** + * Replace this image's image data. + * + */ + function replace($imageData) + { + /* Reset the data array and remove all cached images */ + $this->_data = array(); + $this->reset(); + + /* Remove attributes */ + $result = $GLOBALS['ansel_db']->exec('DELETE FROM ansel_image_attributes WHERE image_id = ' . (int)$this->id); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERROR); + return $result; + } + /* Load the new image data */ + $this->_getEXIF(); + $this->updateData($imageData); + + return true; + } + + /** + * Adds specified EXIF fields to this image's tags. Called during image + * upload/creation. + * + * @param array $fields An array of EXIF fields to import as a tag. + * + */ + function _exifToTags($fields = array()) + { + require_once 'Horde/Date.php'; + + $tags = array(); + foreach ($fields as $field) { + if (!empty($this->_exif[$field])) { + if (substr($field, 0, 8) == 'DateTime') { + $d = new Horde_Date(strtotime($this->_exif[$field])); + $tags[] = $d->format("Y-m-d"); + } else { + $tags[] = $this->_exif[$field]; + } + } + } + + $this->_tags = array_merge($this->_tags, $tags); + } + + /** + * Reads the EXIF data from the image and stores in _exif array() as well + * also populates any local properties that come from the EXIF data. + * + * @return mixed true if any local properties were modified, false otherwise, PEAR_Error on failure + */ + function _getEXIF() + { + require_once ANSEL_BASE . '/lib/Exif.php'; + + /* Clear the local copy */ + $this->_exif = array(); + + /* Get the data */ + $exif_fields = Ansel_ImageData::getExifData($this); + + /* Flag to determine if we need to resave the image data */ + $needUpdate = false; + + /* Populate any local properties that come from EXIF */ + if (!is_a($exif_fields, 'PEAR_Error')) { + /* Save any geo data to a seperate table as well */ + if (!empty($exif_fields['GPSLatitude'])) { + $this->lat = $exif_fields['GPSLatitude']; + $this->lng = $exif_fields['GPSLongitude']; + $this->geotag_timestamp = time(); + $needUpdate = true; + } + + if (!empty($exif_fields['DateTimeOriginal'])) { + $this->originalDate = $exif_fields['DateTimeOriginal']; + $needUpdate = true; + } + + /* Attempt to autorotate based on Orientation field */ + $this->_autoRotate(); + + /* Save attributes. */ + $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_image_attributes (image_id, attr_name, attr_value) VALUES (?, ?, ?)'); + foreach ($exif_fields as $name => $value) { + $result = $insert->execute(array($this->id, $name, Horde_String::convertCharset($value, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']))); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + /* Cache it locally */ + $this->_exif[$name] = Ansel_ImageData::getHumanReadable($name, $value); + } + $insert->free(); + } + + return $needUpdate; + } + + /** + * Autorotate based on EXIF orientation field. Updates the data in memory + * only. + * + */ + function _autoRotate() + { + if (isset($this->_exif['Orientation']) && $this->_exif['Orientation'] != 1) { + switch ($this->_exif['Orientation']) { + case 2: + $this->mirror(); + break; + + case 3: + $this->rotate('full', 180); + break; + + case 4: + $this->mirror(); + $this->rotate('full', 180); + break; + + case 5: + $this->flip(); + $this->rotate('full', 90); + break; + + case 6: + $this->rotate('full', 90); + break; + + case 7: + $this->mirror(); + $this->rotate('full', 90); + break; + + case 8: + $this->rotate('full', 270); + break; + } + + if ($this->_dirty) { + $this->_exif['Orientation'] = 1; + $this->data['full'] = $this->raw(); + $this->_writeData(); + } + } + } + + /** + * Reset the image, removing all loaded views. + */ + function reset() + { + $this->_image->reset(); + $this->_loaded = array(); + } + + /** + * Deletes the specified cache file. + * + * If none is specified, deletes all of the cache files. + * + * @param string $view Which cache file to delete. + */ + function deleteCache($view = 'all') + { + /* Delete cached screen image. */ + if ($view == 'all' || $view == 'screen') { + $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('screen'), + $this->getVFSName('screen')); + } + + /* Delete cached thumbnail. */ + if ($view == 'all' || $view == 'thumb') { + $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('thumb'), + $this->getVFSName('thumb')); + } + + /* Delete cached mini image. */ + if ($view == 'all' || $view == 'mini') { + $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('mini'), + $this->getVFSName('mini')); + } + + if ($view == 'all' || $view == 'prettythumb') { + + /* No need to try to delete a hash we already removed */ + $deleted = array(); + + /* Need to generate hashes for each possible style */ + $styles = Horde::loadConfiguration('styles.php', 'styles', 'ansel'); + foreach ($styles as $style) { + $hash = md5($style['thumbstyle'] . '.' . $style['background']); + if (empty($deleted[$hash])) { + $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath($hash), + $this->getVFSName($hash)); + $deleted[$hash] = true; + } + } + } + } + + /** + * Returns the raw data for the given view. + * + * @param string $view Which view to return. + */ + function raw($view = 'full') + { + if ($this->_dirty) { + return $this->_image->raw(); + } else { + $this->load($view); + return $this->_data[$view]; + } + } + + /** + * Sends the correct HTTP headers to the browser to download this image. + * + * @param string $view The view to download. + */ + function downloadHeaders($view = 'full') + { + global $browser, $conf; + + $filename = $this->filename; + if ($view != 'full') { + require_once 'Horde/Mime/Magic.php'; + if ($ext = Horde_Mime_Magic::mimeToExt('image/' . $conf['image']['type'])) { + $filename .= '.' . $ext; + } + } + + $browser->downloadHeaders($filename); + } + + /** + * Display the requested view. + * + * @param string $view Which view to display. + * @param string $style Force use of this gallery style. + */ + function display($view = 'full', $style = null) + { + if ($view == 'full' && !$this->_dirty) { + + // Check full photo permissions + $gallery = $GLOBALS['ansel_storage']->getGallery($this->gallery); + if (is_a($gallery, 'PEAR_Error')) { + return $gallery; + } + if (!$gallery->canDownload()) { + return PEAR::RaiseError(sprintf(_("Access denied downloading photos from \"%s\"."), $gallery->get('name'))); + } + + $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'), + $this->getVFSName('full')); + + if (is_a($data, 'PEAR_Error')) { + return $data; + } + echo $data; + return; + } + + if (is_a($result = $this->load($view, $style), 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + return $this->_image->display(); + } + + /** + * Wraps the given view into a file. + * + * @param string $view Which view to wrap up. + */ + function toFile($view = 'full') + { + if (is_a(($result = $this->load($view)), 'PEAR_Error')) { + return $result; + } + return $this->_image->toFile($this->_dirty ? false : $this->_data[$view]); + } + + /** + * Returns the dimensions of the given view. + * + * @param string $view The view (size) to check dimensions for. + */ + function getDimensions($view = 'full') + { + if (is_a(($result = $this->load($view)), 'PEAR_Error')) { + return $result; + } + return $this->_image->getDimensions(); + } + + /** + * Rotates the image. + * + * @param string $view The view (size) to work with. + * @param integer $angle What angle to rotate the image by. + */ + function rotate($view = 'full', $angle) + { + $this->load($view); + $this->_dirty = true; + return $this->_image->rotate($angle); + } + + function crop($x1, $y1, $x2, $y2) + { + $this->_dirty = true; + return $this->_image->crop($x1, $y1, $x2, $y2); + } + + /** + * Converts the image to grayscale. + * + * @param string $view The view (size) to work with. + */ + function grayscale($view = 'full') + { + $this->load($view); + $this->_dirty = true; + return $this->_image->grayscale(); + } + + /** + * Watermarks the image. + * + * @param string $view The view (size) to work with. + * @param string $watermark String to use as the watermark. + */ + function watermark($view = 'full', $watermark = null, $halign = null, + $valign = null, $font = null) + { + if (empty($watermark)) { + $watermark = $GLOBALS['prefs']->getValue('watermark_text'); + } + + if (empty($halign)) { + $halign = $GLOBALS['prefs']->getValue('watermark_horizontal'); + } + + if (empty($valign)) { + $valign = $GLOBALS['prefs']->getValue('watermark_vertical'); + } + + if (empty($font)) { + $font = $GLOBALS['prefs']->getValue('watermark_font'); + } + + if (empty($watermark)) { + require_once 'Horde/Identity.php'; + $identity = &Identity::singleton(); + $name = $identity->getValue('fullname'); + if (empty($name)) { + $name = Horde_Auth::getAuth(); + } + $watermark = sprintf(_("(c) %s %s"), date('Y'), $name); + } + + $this->load($view); + $this->_dirty = true; + $params = array('text' => $watermark, + 'halign' => $halign, + 'valign' => $valign, + 'fontsize' => $font); + if (!empty($GLOBALS['conf']['image']['font'])) { + $params['font'] = $GLOBALS['conf']['image']['font']; + } + $this->_image->addEffect('TextWatermark', $params); + + return true; + } + + /** + * Flips the image. + * + * @param string $view The view (size) to work with. + */ + function flip($view = 'full') + { + $this->load($view); + $this->_dirty = true; + return $this->_image->flip(); + } + + /** + * Mirrors the image. + * + * @param string $view The view (size) to work with. + */ + function mirror($view = 'full') + { + $this->load($view); + $this->_dirty = true; + return $this->_image->mirror(); + } + + /** + * Returns this image's tags. + * + * @return mixed An array of tags | PEAR_Error + * @see Ansel_Tags::readTags() + */ + function getTags() + { + global $ansel_storage; + + if (count($this->_tags)) { + return $this->_tags; + } + require_once ANSEL_BASE . '/lib/Tags.php'; + $gallery = $ansel_storage->getGallery($this->gallery); + if (is_a($gallery, 'PEAR_Error')) { + return $gallery; + } + if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) { + $res = Ansel_Tags::readTags($this->id); + if (!is_a($res, 'PEAR_Error')) { + $this->_tags = $res; + return $this->_tags; + } else { + return $res; + } + } else { + return PEAR::raiseError(_("Access denied viewing this photo.")); + } + } + + /** + * Set/replace this image's tags. + * + * @param array $tags An array of tag names to associate with this image. + */ + function setTags($tags) + { + global $ansel_storage; + + require_once ANSEL_BASE . '/lib/Tags.php'; + + $gallery = $ansel_storage->getGallery(abs($this->gallery)); + if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) { + // Clear the local cache. + $this->_tags = array(); + return Ansel_Tags::writeTags($this->id, $tags); + } else { + return PEAR::raiseError(_("Access denied adding tags to this photo.")); + } + } + + /** + * Get the Ansel_View_Image_Thumb object + * + * @param Ansel_Gallery $parent The parent Ansel_Gallery object. + * @param string $style A named gallery style to use. + * @param boolean $mini Force the use of a mini thumbnail? + * @param array $params Any additional parameters the Ansel_Tile + * object may need. + * + */ + function getTile($parent = null, $style = null, $mini = false, + $params = array()) + { + require_once ANSEL_BASE . '/lib/Tile/Image.php'; + + if (!is_null($parent) && is_null($style)) { + $style = $parent->getStyle(); + } else { + $style = Ansel::getStyleDefinition($style); + } + + return Ansel_Tile_Image::getTile($this, $style, $mini, $params); + } + + /** + * Get the image type for the requested view. + */ + function getType($view = 'full') + { + if ($view == 'full') { + return $this->type; + } elseif ($view == 'screen') { + return 'image/jpg'; + } else { + return 'image/' . $GLOBALS['conf']['image']['type']; + } + } + + /** + * Return a hash key for the given view and style. + * + * @param string $view The view (thumb, prettythumb etc...) + * @param string $style The named style. + * + * @return string A md5 hash suitable for use as a key. + */ + function _getViewHash($view, $style = null) + { + global $ansel_storage; + + // These views do not care about style...just return the $view value. + if ($view == 'screen' || $view == 'thumb' || $view == 'mini' || + $view == 'full') { + + return $view; + } + if (is_null($style)) { + $gallery = $ansel_storage->getGallery(abs($this->gallery)); + if (is_a($gallery, 'PEAR_Error')) { + return $gallery; + } + $style = $gallery->getStyle(); + } else { + $style = Ansel::getStyleDefinition($style); + } + + $view = md5($style['thumbstyle'] . '.' . $style['background']); + return $view; + } + +} + +/** + * Class for interfacing with back end data storage. + * + * @author Michael J. Rubinsky + * + * @package Ansel + */ +class Ansel_Storage { + + var $_scope = 'ansel'; + var $_db = null; + var $galleries = array(); + + /** + * The Horde_Shares object to use for this scope. + * + * @var Horde_Share + */ + var $shares = null; + + /* Local cache of retrieved images */ + var $images = array(); + + function Ansel_Storage($scope = null) + { + /* Check for a scope other than the default Ansel scope.*/ + if (!is_null($scope)) { + $this->_scope = $scope; + } + + /* This is the only supported share backend for Ansel */ + $this->shares = Horde_Share::singleton($this->_scope, + 'sql_hierarchical'); + + /* Ansel_Gallery is just a subclass of Horde_Share_Object */ + $this->shares->_shareObject = 'Ansel_Gallery'; + + /* Database handle */ + $this->_db = $GLOBALS['ansel_db']; + } + + /** + * Create and initialise a new gallery object. + * + * @param array $attributes The gallery attributes + * @param object Perms $perm The permissions for the gallery if the + * defaults are not desirable. + * @param mixed $parent The gallery id of the parent (if any) + * + * @return Ansel_Gallery A new gallery object or PEAR_Error. + */ + function createGallery($attributes = array(), $perm = null, $parent = null) + { + /* Required values. */ + if (empty($attributes['owner'])) { + $attributes['owner'] = Horde_Auth::getAuth(); + } + if (empty($attributes['name'])) { + $attributes['name'] = _("Unnamed"); + } + if (empty($attributes['desc'])) { + $attributes['desc'] = ''; + } + + /* Default values */ + $attributes['default_type'] = isset($attributes['default_type']) ? $attributes['default_type'] : 'auto'; + $attributes['default'] = isset($attributes['default']) ? (int)$attributes['default'] : 0; + $attributes['default_prettythumb'] = isset($attributes['default_prettythumb']) ? $attributes['default_prettythumb'] : ''; + $attributes['style'] = isset($attributes['style']) ? $attributes['style'] : $GLOBALS['prefs']->getValue('default_gallerystyle'); + $attributes['category'] = isset($attributes['category']) ? $attributes['category'] : $GLOBALS['prefs']->getValue('default_category'); + $attributes['date_created'] = time(); + $attributes['last_modified'] = $attributes['date_created']; + $attributes['images'] = isset($attributes['images']) ? (int)$attributes['images'] : 0; + $attributes['slug'] = isset($attributes['slug']) ? $attributes['slug'] : ''; + $attributes['age'] = isset($attributes['age']) ? (int)$attributes['age'] : 0; + $attributes['download'] = isset($attributes['download']) ? $attributes['download'] : $GLOBALS['prefs']->getValue('default_download'); + $attributes['view_mode'] = isset($attributes['view_mode']) ? $attributes['view_mode'] : 'Normal'; + $attributes['passwd'] = isset($attributes['passwd']) ? $attributes['passwd'] : ''; + + /* Don't pass tags to the share creation method */ + if (isset($attributes['tags'])) { + $tags = $attributes['tags']; + unset($attributes['tags']); + } else { + $tags = array(); + } + + /* Check for slug uniqueness */ + if (!empty($attributes['slug']) && + $this->slugExists($attributes['slug'])) { + return PEAR::raiseError(sprintf(_("The slug \"%s\" already exists."), + $attributes['slug'])); + } + + /* Create the gallery */ + $gallery = $this->shares->newShare(''); + if (is_a($gallery, 'PEAR_Error')) { + Horde::logMessage($gallery, __FILE__, __LINE__, PEAR_LOG_ERR); + return $gallery; + } + Horde::logMessage('New Ansel_Gallery object instantiated', __FILE__, __LINE__, PEAR_LOG_DEBUG); + + /* Set the gallery's parent if needed */ + if (!is_null($parent)) { + $result = $gallery->setParent($parent); + + /* Clear the parent from the cache */ + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_Gallery' . $parent); + } + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + } + + /* Fill up the new gallery */ + // TODO: New private method to bulk load these (it's done this way + // since the data is stored in the Share_Object class keyed by the + // DB specific fields and set() translates them. + foreach ($attributes as $key => $value) { + $gallery->set($key, $value); + } + + /* Save it to storage */ + $result = $this->shares->addShare($gallery); + if (is_a($result, 'PEAR_Error')) { + $error = sprintf(_("The gallery \"%s\" could not be created: %s"), + $attributes['name'], $result->getMessage()); + Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR); + return PEAR::raiseError($error); + } + + /* Convenience */ + $gallery->id = $gallery->getId(); + + /* Add default permissions. */ + if (empty($perm)) { + $perm = $gallery->getPermission(); + + /* Default permissions for logged in users */ + switch ($GLOBALS['prefs']->getValue('default_permissions')) { + case 'read': + $perms = PERMS_SHOW | PERMS_READ; + break; + case 'edit': + $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT; + break; + case 'none': + $perms = 0; + break; + } + $perm->addDefaultPermission($perms, false); + + /* Default guest permissions */ + switch ($GLOBALS['prefs']->getValue('guest_permissions')) { + case 'read': + $perms = PERMS_SHOW | PERMS_READ; + break; + case 'none': + default: + $perms = 0; + break; + } + $perm->addGuestPermission($perms, false); + + /* Default user groups permissions */ + switch ($GLOBALS['prefs']->getValue('group_permissions')) { + case 'read': + $perms = PERMS_SHOW | PERMS_READ; + break; + case 'edit': + $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT; + break; + case 'delete': + $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT | PERMS_DELETE; + break; + case 'none': + default: + $perms = 0; + break; + } + + if ($perms) { + require_once 'Horde/Group.php'; + $groups = &Group::singleton(); + $group_list = $groups->getGroupMemberships(Horde_Auth::getAuth()); + if (!is_a($group_list, 'PEAR_Error') && count($group_list)) { + foreach ($group_list as $group_id => $group_name) { + $perm->addGroupPermission($group_id, $perms, false); + } + } + } + } + $gallery->setPermission($perm, true); + + /* Initial tags */ + if (count($tags)) { + $gallery->setTags($tags); + } + + return $gallery; + } + + /** + * Check that a slug exists. + * + * @param string $slug The slug name + * + * @return integer The share_id the slug represents, or 0 if not found. + */ + function slugExists($slug) + { + // An empty slug should never match. + if (!strlen($slug)) { + return 0; + } + + $stmt = $this->_db->prepare('SELECT share_id FROM ' + . $this->shares->_table . ' WHERE attribute_slug = ?'); + + if (is_a($stmt, 'PEAR_Error')) { + Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR); + return 0; + } + + $result = $stmt->execute($slug); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + } + if (!$result->numRows()) { + return 0; + } + + $slug = $result->fetchRow(); + + $result->free(); + $stmt->free(); + + return $slug[0]; + } + + /** + * Retrieve an Ansel_Gallery given the gallery's slug + * + * @param string $slug The gallery slug + * @param array $overrides An array of attributes that should be overridden + * when the gallery is returned. + * + * @return mixed Ansel_Gallery object | PEAR_Error + */ + function &getGalleryBySlug($slug, $overrides = array()) + { + $id = $this->slugExists($slug); + if ($id) { + return $this->getGallery($id, $overrides); + } else { + return PEAR::raiseError(sprintf(_("Gallery %s not found."), $slug)); + } + } + + /** + * Retrieve an Ansel_Gallery given the share id + * + * @param integer $gallery_id The share_id to fetch + * @param array $overrides An array of attributes that should be + * overridden when the gallery is returned. + * + * @return mixed Ansel_Gallery | PEAR_Error + */ + function &getGallery($gallery_id, $overrides = array()) + { + // avoid cache server hits + if (isset($this->galleries[$gallery_id]) && !count($overrides)) { + return $this->galleries[$gallery_id]; + } + + if (!count($overrides) && $GLOBALS['conf']['ansel_cache']['usecache'] && + ($gallery = $GLOBALS['cache']->get('Ansel_Gallery' . $gallery_id, $GLOBALS['conf']['cache']['default_lifetime'])) !== false) { + + $this->galleries[$gallery_id] = unserialize($gallery); + + return $this->galleries[$gallery_id]; + } + + $result = &$this->shares->getShareById($gallery_id); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $this->galleries[$gallery_id] = &$result; + + // Don't cache if we have overridden anything + if (!count($overrides)) { + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->set('Ansel_Gallery' . $gallery_id, serialize($result)); + } + } else { + foreach ($overrides as $key => $value) { + $this->galleries[$gallery_id]->set($key, $value, false); + } + } + return $this->galleries[$gallery_id]; + } + + /** + * Retrieve an array of Ansel_Gallery objects for the given slugs. + * + * @param array $slugs The gallery slugs + * + * @return mixed Array of Ansel_Gallery objects | PEAR_Error + */ + function getGalleriesBySlugs($slugs) + { + $sql = 'SELECT share_id FROM ' . $this->shares->_table + . ' WHERE attribute_slug IN (' . str_repeat('?, ', count($slugs) - 1) . '?)'; + + $stmt = $this->shares->_db->prepare($sql); + if (is_a($stmt, 'PEAR_Error')) { + return $stmt; + } + $result = $stmt->execute($slugs); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $ids = array_values($result->fetchCol()); + $shares = $this->shares->getShares($ids); + + $stmt->free(); + $result->free(); + + return $shares; + } + + /** + * Retrieve an array of Ansel_Gallery objects for the requested ids + */ + function getGalleries($ids) + { + return $this->shares->getShares($ids); + } + + /** + * Empties a gallery of all images. + * + * @param Ansel_Gallery $gallery The ansel gallery to empty. + */ + function emptyGallery($gallery) + { + $images = $gallery->listImages(); + foreach ($images as $image) { + // Pretend we are a stack so we don't update the images count + // for every image deletion, since we know the end result will + // be zero. + $gallery->removeImage($image, true); + } + $gallery->set('images', 0, true); + + // Clear the OtherGalleries widget cache + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_OtherGalleries' . $gallery->get('owner')); + } + + } + + /** + * Removes an Ansel_Gallery. + * + * @param Ansel_Gallery $gallery The gallery to delete + * + * @return mixed True || PEAR_Error + */ + function removeGallery($gallery) + { + /* Get any children and empty them */ + $children = $gallery->getChildren(null, true); + if (is_a($children, 'PEAR_Error')) { + return $children; + } + foreach ($children as $child) { + $this->emptyGallery($child); + $child->setTags(array()); + } + + /* Now empty the selected gallery of images */ + $this->emptyGallery($gallery); + + /* Clear all the tags. */ + $gallery->setTags(array()); + + /* Get the parent, if it exists, before we delete the gallery. */ + $parent = $gallery->getParent(); + $id = $gallery->id; + + /* Delete the gallery from storage */ + $result = $this->shares->removeShare($gallery); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + /* Expire the cache */ + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_Gallery' . $id); + } + unset($this->galleries[$id]); + + /* See if we need to clear the has_subgalleries field */ + if (is_a($parent, 'Ansel_Gallery')) { + if (!$parent->countChildren(PERMS_SHOW, false)) { + $parent->set('has_subgalleries', 0, true); + + if ($GLOBALS['conf']['ansel_cache']['usecache']) { + $GLOBALS['cache']->expire('Ansel_Gallery' . $parent->id); + } + unset($this->galleries[$id]); + } + } + + return true; + } + + /** + * Returns the image corresponding to the given id. + * + * @param integer $id The ID of the image to retrieve. + * + * @return Ansel_Image The image object corresponding to the given name. + */ + function &getImage($id) + { + if (isset($this->images[$id])) { + return $this->images[$id]; + } + + $q = $this->_db->prepare('SELECT ' . $this->_getImageFields() . ' FROM ansel_images WHERE image_id = ?'); + if (is_a($q, 'PEAR_Error')) { + Horde::logMessage($q, __FILE__, __LINE__, PEAR_LOG_ERR); + return $q; + } + $result = $q->execute((int)$id); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + $image = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + $q->free(); + $result->free(); + if (is_null($image)) { + return PEAR::raiseError(_("Photo not found")); + } elseif (is_a($image, 'PEAR_Error')) { + Horde::logMessage($image, __FILE__, __LINE__, PEAR_LOG_ERR); + return $image; + } else { + $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']); + $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']); + $this->images[$id] = new Ansel_Image($image); + + return $this->images[$id]; + } + } + + /** + * Returns the images corresponding to the given ids. + * + * @param array $ids An array of image ids. + * + * @return array of Ansel_Image objects. + */ + function getImages($ids, $preserve_order = false) + { + if (is_array($ids) && count($ids) > 0) { + $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images WHERE image_id IN ('; + $i = 1; + $cnt = count($ids); + foreach ($ids as $id) { + $sql .= (int)$id . (($i++ < $cnt) ? ',' : ');'); + } + + $images = $this->_db->query($sql); + if (is_a($images, 'PEAR_Error')) { + return $images; + } elseif ($images->numRows() == 0) { + $images->free(); + return PEAR::raiseError(_("Photos not found")); + } + + $return = array(); + while ($image = $images->fetchRow(MDB2_FETCHMODE_ASSOC)) { + $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']); + $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']); + $return[$image['image_id']] = new Ansel_Image($image); + $this->images[(int)$image['image_id']] = &$return[$image['image_id']]; + } + $images->free(); + + /* Need to get comment counts if comments are enabled */ + $ccounts = $this->_getImageCommentCounts(array_keys($return)); + if (!is_a($ccounts, 'PEAR_Error') && count($ccounts)) { + foreach ($return as $key => $image) { + $return[$key]->commentCount = (!empty($ccounts[$key]) ? $ccounts[$key] : 0); + } + } + + /* Preserve the order the ids were passed in) */ + if ($preserve_order) { + foreach ($ids as $id) { + $ordered[$id] = $return[$id]; + } + return $ordered; + } + return $return; + } else { + return array(); + } + } + + function _getImageCommentCounts($ids) + { + global $conf, $registry; + + /* Need to get comment counts if comments are enabled */ + if (($conf['comments']['allow'] == 'all' || ($conf['comments']['allow'] == 'authenticated' && Horde_Auth::getAuth())) && + $registry->hasMethod('forums/numMessagesBatch')) { + + return $registry->call('forums/numMessagesBatch', + array($ids, 'ansel')); + } + + return array(); + } + + /** + * Return a list of image ids of the most recently added images. + * + * @param array $galleries An array of gallery ids to search in. If + * left empty, will search all galleries + * with PERMS_SHOW. + * @param integer $limit The maximum number of images to return + * @param string $slugs An array of gallery slugs. + * @param string $where Additional where clause + * + * @return array An array of Ansel_Image objects + */ + function getRecentImages($galleries = array(), $limit = 10, $slugs = array()) + { + $results = array(); + + if (!count($galleries) && !count($slugs)) { + $sql = 'SELECT DISTINCT ' . $this->_getImageFields('i') . ' FROM ansel_images i, ' + . str_replace('WHERE' , ' WHERE i.gallery_id = s.share_id AND (', substr($this->shares->_getShareCriteria(Horde_Auth::getAuth()), 5)) . ')'; + } elseif (!count($slugs) && count($galleries)) { + // Searching by gallery_id + $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images ' + . 'WHERE gallery_id IN (' + . str_repeat('?, ', count($galleries) - 1) . '?) '; + } elseif (count($slugs)) { + // Searching by gallery_slug so we need to join the share table + $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images LEFT JOIN ' + . $this->shares->_table . ' ON ansel_images.gallery_id = ' + . $this->shares->_table . '.share_id ' . 'WHERE attribute_slug IN (' + . str_repeat('?, ', count($slugs) - 1) . '?) '; + } else { + return array(); + } + + $sql .= ' ORDER BY image_uploaded_date DESC LIMIT ' . (int)$limit; + $query = $this->_db->prepare($sql); + if (is_a($query, 'PEAR_Error')) { + return $query; + } + + if (count($slugs)) { + $images = $query->execute($slugs); + } else { + $images = $query->execute($galleries); + } + $query->free(); + if (is_a($images, 'PEAR_Error')) { + return $images; + } elseif ($images->numRows() == 0) { + return array(); + } + + while ($image = $images->fetchRow(MDB2_FETCHMODE_ASSOC)) { + $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']); + $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']); + $results[] = new Ansel_Image($image); + } + + $images->free(); + return $results; + } + + /** + * Check if a gallery exists. Need to do this here instead of Horde_Share + * since Horde_Share::exists() takes a share_name, not a share_id plus we + * might also be checking by gallery_slug and this is more efficient than + * a listShares() call for one gallery. + * + * @param integer $gallery_id The gallery id + * @param string $slug The gallery slug + * + * @return mixed true | false | PEAR_Error + */ + function galleryExists($gallery_id, $slug = null) + { + if (empty($slug)) { + return (bool)$this->_db->queryOne( + 'SELECT COUNT(share_id) FROM ' . $this->shares->_table + . ' WHERE share_id = ' . (int)$gallery_id); + } else { + return (bool)$this->slugExists($slug); + } + } + + /** + * Return a list of categories containing galleries with the given + * permissions for the current user. + * + * @param integer $perm The level of permissions required. + * @param integer $from The gallery to start listing at. + * @param integer $count The number of galleries to return. + * + * @return mixed List of categories | PEAR_Error + */ + function listCategories($perm = PERMS_SHOW, $from = 0, $count = 0) + { + require_once 'Horde/Array.php'; + + $sql = 'SELECT DISTINCT attribute_category FROM ' + . $this->shares->_table; + $results = $this->shares->_db->query($sql); + if (is_a($results, 'PEAR_Error')) { + return $results; + } + $all_categories = $results->fetchCol('attribute_category'); + $results->free(); + if (count($all_categories) < $from) { + return array(); + } else { + $categories = array(); + foreach ($all_categories as $category) { + $categories[] = Horde_String::convertCharset( + $category, $GLOBALS['conf']['sql']['charset']); + } + if ($count > 0) { + return array_slice($categories, $from, $count); + } else { + return array_slice($categories, $from); + } + } + } + + function countCategories($perms = PERMS_SHOW) + { + return count($this->listCategories($perms)); + } + + /** + * Return the count of galleries that the user has specified permissions to + * and that match any of the requested attributes. + * + * @param string $userid The user to check access for. + * @param integer $perm The level of permissions to require for a + * gallery to return it. + * @param mixed $attributes Restrict the galleries counted to those + * matching $attributes. An array of + * attribute/values pairs or a gallery owner + * username. + * @param string $parent The parent share to start counting at. + * @param boolean $allLevels Return all levels, or just the direct + * children of $parent? Defaults to all levels. + */ + function countGalleries($userid, $perm = PERMS_SHOW, $attributes = null, + $parent = null, $allLevels = true) + { + static $counts; + + if (is_a($parent, 'Ansel_Gallery')) { + $parent_id = $parent->getId(); + } else { + $parent_id = $parent; + } + + $key = "$userid,$perm,$parent_id,$allLevels" + . serialize($attributes); + if (isset($counts[$key])) { + return $counts[$key]; + } + + $count = $this->shares->countShares($userid, $perm, $attributes, + $parent, $allLevels); + + $counts[$key] = $count; + + return $count; + } + + /** + * Retrieves the current user's gallery list from storage. + * + * @param integer $perm The level of permissions to require for a + * gallery to return it. + * @param mixed $attributes Restrict the galleries counted to those + * matching $attributes. An array of + * attribute/values pairs or a gallery owner + * username. + * @param mixed $parent The parent gallery to start listing at. + * (Ansel_Gallery, gallery id or null) + * @param boolean $allLevels Return all levels, or just the direct + * children of $parent? + * @param integer $from The gallery to start listing at. + * @param integer $count The number of galleries to return. + * @param string $sort_by The field to order the results by. + * @param integer $direction Sort direction: + * 0 - ascending + * 1 - descending + * + * @return mixed An array of Ansel_Gallery objects | PEAR_Error + */ + function listGalleries($perm = PERMS_SHOW, + $attributes = null, + $parent = null, + $allLevels = true, + $from = 0, + $count = 0, + $sort_by = null, + $direction = 0) + { + return $this->shares->listShares(Horde_Auth::getAuth(), $perm, $attributes, + $from, $count, $sort_by, $direction, + $parent, $allLevels); + } + + /** + * Retrieve json data for an arbitrary list of image ids, not necessarily + * from the same gallery. + * + * @param array $images An array of image ids + * @param string $style A named gallery style to force if requesting + * pretty thumbs. + * @param boolean $full Generate full urls + * @param string $image_view Which image view to use? screen, thumb etc.. + * @param boolean $view_links Include links to the image view + * + * @return string The json data || PEAR_Error + */ + function getImageJson($images, $style = null, $full = false, + $image_view = 'mini', $view_links = false) + { + $galleries = array(); + if (is_null($style)) { + $style = 'ansel_default'; + } + + $json = array(); + + foreach ($images as $id) { + $image = $this->getImage($id); + if (!is_a($image, 'PEAR_Error')) { + $gallery_id = abs($image->gallery); + + if (empty($galleries[$gallery_id])) { + $galleries[$gallery_id]['gallery'] = $GLOBALS['ansel_storage']->getGallery($gallery_id); + if (is_a($galleries[$gallery_id]['gallery'], 'PEAR_Error')) { + return $galleries[$gallery_id]; + } + } + + // Any authentication that needs to take place for any of the + // images included here MUST have already taken place or the + // image will not be incldued in the output. + if (!isset($galleries[$gallery_id]['perm'])) { + $galleries[$gallery_id]['perm'] = + ($galleries[$gallery_id]['gallery']->hasPermission(Horde_Auth::getAuth(), PERMS_READ) && + $galleries[$gallery_id]['gallery']->isOldEnough() && + !$galleries[$gallery_id]['gallery']->hasPasswd()); + } + + if ($galleries[$gallery_id]['perm']) { + $data = array(Ansel::getImageUrl($image->id, $image_view, $full, $style), + htmlspecialchars($image->filename, ENT_COMPAT, Horde_Nls::getCharset()), + Horde_Text_Filter::filter($image->caption, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL)), + $image->id, + 0); + + if ($view_links) { + $data[] = Ansel::getUrlFor('view', + array('gallery' => $image->gallery, + 'image' => $image->id, + 'view' => 'Image', + 'slug' => $galleries[$gallery_id]['gallery']->get('slug')), + $full); + + $data[] = Ansel::getUrlFor('view', + array('gallery' => $image->gallery, + 'slug' => $galleries[$gallery_id]['gallery']->get('slug'), + 'view' => 'Gallery'), + $full); + } + + $json[] = $data; + } + } + } + + if (count($json)) { + require_once 'Horde/Serialize.php'; + return Horde_Serialize::serialize($json, Horde_Serialize::JSON, Horde_Nls::getCharset()); + } else { + return ''; + } + } + + /** + * Returns a random Ansel_Gallery from a list fitting the search criteria. + * + * @see Ansel_Storage::listGalleries() + */ + function getRandomGallery($perm = PERMS_SHOW, $attributes = null, + $parent = null, $allLevels = true) + { + $num_galleries = $this->countGalleries(Horde_Auth::getAuth(), $perm, + $attributes, $parent, + $allLevels); + if (!$num_galleries) { + return $num_galleries; + } + + $galleries = $this->listGalleries($perm, $attributes, $parent, + $allLevels, + rand(0, $num_galleries - 1), + 1); + $gallery = array_pop($galleries); + return $gallery; + } + + /** + * Lists a slice of the image ids in the given gallery. + * + * @param integer $gallery_id The gallery to list from. + * @param integer $from The image to start listing. + * @param integer $count The numer of images to list. + * @param mixed $fields The fields to return (either an array of + * fileds or a single string). + * @param string $where A SQL where clause ($gallery_id will be + * ignored if this is non-empty). + * @param mixed $sort The field(s) to sort by. + * + * @return mixed An array of image_ids | PEAR_Error + */ + function listImages($gallery_id, $from = 0, $count = 0, + $fields = 'image_id', $where = '', $sort = 'image_sort') + { + if (is_array($fields)) { + $field_count = count($fields); + $fields = implode(', ', $fields); + } elseif ($fields == '*') { + // The count is not important, as long as it's > 1 + $field_count = 2; + } else { + $field_count = substr_count($fields, ',') + 1; + } + + if (is_array($sort)) { + $sort = implode(', ', $sort); + } + + if (!empty($where)) { + $query_where = 'WHERE ' . $where; + } else { + $query_where = 'WHERE gallery_id = ' . $gallery_id; + } + $this->_db->setLimit($count, $from); + $sql = 'SELECT ' . $fields . ' FROM ansel_images ' . $query_where . ' ORDER BY ' . $sort; + Horde::logMessage('Query by Ansel_Storage::listImages: ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $results = $this->_db->query('SELECT ' . $fields . ' FROM ansel_images ' + . $query_where . ' ORDER BY ' . $sort); + if (is_a($results, 'PEAR_Error')) { + return $results; + } + if ($field_count > 1) { + return $results->fetchAll(MDB2_FETCHMODE_ASSOC, true, true, false); + } else { + return $results->fetchCol(); + } + } + + /** + * Return images' geolocation data. + * + * @param array $image_ids An array of image_ids to look up. + * @param integer $gallery A gallery id. If this is provided, will return + * all images in the gallery that have geolocation + * data ($image_ids would be ignored). + * + * @return mixed An array of geodata || PEAR_Error + */ + function getImagesGeodata($image_ids = array(), $gallery = null) + { + if ((!is_array($image_ids) || count($image_ids) == 0) && empty($gallery)) { + return array(); + } + + if (!empty($gallery)) { + $where = 'gallery_id = ' . (int)$gallery . ' AND LENGTH(image_latitude) > 0'; + } elseif (count($image_ids) > 0) { + $where = 'image_id IN(' . implode(',', $image_ids) . ') AND LENGTH(image_latitude) > 0'; + } else { + return array(); + } + + return $this->listImages(0, 0, 0, array('image_id as id', 'image_id', 'image_latitude', 'image_longitude', 'image_location'), $where); + } + + /** + * Like getRecentImages, but returns geotag data for the most recently added + * images from the current user. Useful for providing images to help locate + * images at the same place. + */ + function getRecentImagesGeodata($user = null, $start = 0, $count = 8) + { + $galleries = $this->listGalleries('PERMS_EDIT', $user); + $where = 'gallery_id IN(' . implode(',', array_keys($galleries)) . ') AND LENGTH(image_latitude) > 0 GROUP BY image_latitude, image_longitude'; + return $this->listImages(0, $start, $count, array('image_id as id', 'image_id', 'gallery_id', 'image_latitude', 'image_longitude', 'image_location'), $where, 'image_geotag_date DESC'); + } + + function searchLocations($search = '') + { + $sql = 'SELECT DISTINCT image_location, image_latitude, image_longitude' + . ' FROM ansel_images WHERE image_location LIKE "' . $search . '%"'; + $results = $this->_db->query($sql); + if (is_a($results, 'PEAR_Error')) { + return $results; + } + + return $results->fetchAll(MDB2_FETCHMODE_ASSOC, true, true, false); + } + + /** + * Helper function to get a string of field names + * + * @return string + */ + function _getImageFields($alias = '') + { + $fields = array('image_id', 'gallery_id', 'image_filename', 'image_type', + 'image_caption', 'image_uploaded_date', 'image_sort', + 'image_faces', 'image_original_date', 'image_latitude', + 'image_longitude', 'image_location', 'image_geotag_date'); + if (!empty($alias)) { + foreach ($fields as $field) { + $new[] = $alias . '.' . $field; + } + return implode(', ', $new); + } + + return implode(', ', $fields); + } + +} diff --git a/ansel/lib/Block/cloud.php b/ansel/lib/Block/cloud.php new file mode 100644 index 000000000..afa4b653f --- /dev/null +++ b/ansel/lib/Block/cloud.php @@ -0,0 +1,60 @@ + + * @package Horde_Block + */ +class Horde_Block_ansel_cloud extends Horde_Block { + + var $_app = 'ansel'; + + function _params() + { + $params = array('count' => array( + 'name' => _("Number of tags to display"), + 'type' => 'int', + 'default' => 20)); + return $params; + } + + function _title() + { + return _("Tag Cloud"); + } + + function _content() + { + require_once 'Horde/UI/TagCloud.php'; + require_once dirname(__FILE__) . '/../base.php'; + require_once ANSEL_BASE . '/lib/Tags.php'; + + global $registry; + + /* Get the tags */ + $tags = Ansel_Tags::listTagInfo(null, $this->_params['count']); + if (count($tags)) { + $cloud = new Horde_UI_TagCloud(); + foreach ($tags as $id => $tag) { + $link = Ansel::getUrlFor('view', array('view' => 'Results', + 'tag' => $tag['tag_name'])); + $cloud->addElement($tag['tag_name'], $link, $tag['total']); + } + $html = $cloud->buildHTML(); + } else { + $html = ''; + } + return $html; + } + +} diff --git a/ansel/lib/Block/gallery.php b/ansel/lib/Block/gallery.php new file mode 100644 index 000000000..c59d68296 --- /dev/null +++ b/ansel/lib/Block/gallery.php @@ -0,0 +1,155 @@ + + * @author Marcus Ryan + * @package Horde_Block + */ +class Horde_Block_ansel_gallery extends Horde_Block { + + var $_app = 'ansel'; + var $_gallery = null; + + function _params() + { + require_once dirname(__FILE__) . '/../base.php'; + + $params = array('gallery' => array( + 'name' => _("Gallery"), + 'type' => 'enum', + 'default' => '__random', + 'values' => array('__random' => _("Random gallery"))), + 'perpage' => array( + 'name' => _("Maximum number of photos to display (0 means unlimited)"), + 'type' => 'int', + 'default' => 20), + 'use_lightbox' => array( + 'name' => _("Use a lightbox to view photos"), + 'type' => 'checkbox', + 'default' => true)); + + if ($GLOBALS['ansel_storage']->countGalleries(Horde_Auth::getAuth(), PERMS_READ) < $GLOBALS['conf']['gallery']['listlimit']) { + foreach ($GLOBALS['ansel_storage']->listGalleries() as $gal) { + $params['gallery']['values'][$gal->id] = $gal->get('name'); + } + } + + return $params; + } + + function _title() + { + $gallery = $this->_getGallery(); + if (is_a($gallery, 'PEAR_Error')) { + return Horde::link(Ansel::getUrlFor('view', array('view' => 'List'), + true)) . _("Gallery") . ''; + } + + // Build the gallery name. + if (isset($this->_params['gallery']) && + $this->_params['gallery'] == '__random') { + $name = _("Random Gallery") . ': ' . $gallery->get('name'); + } else { + $name = $gallery->get('name'); + } + + $style = $gallery->getStyle(); + $viewurl = Ansel::getUrlFor('view', + array('view' => 'Gallery', + 'gallery' => $gallery->id, + 'slug' => $gallery->get('slug')), + true); + return Horde::link($viewurl) + . @htmlspecialchars($name, ENT_COMPAT, Horde_Nls::getCharset()) + . ''; + + } + function _content() + { + $gallery = $this->_getGallery(); + if (is_a($gallery, 'PEAR_Error')) { + return $gallery->getMessage(); + } + + $params = array('gallery_id' => $gallery->id, + 'count' => $this->_params['perpage']); + if (!empty($this->_params['use_lightbox'])) { + $params['lightbox'] = true; + } + + $html = Ansel::embedCode($params); + + // Be nice to people with