Overview

Packages

  • Ansel
  • None

Classes

  • Ansel
  • Ansel_Ajax_Application
  • Ansel_Ajax_Imple_EditCaption
  • Ansel_Ajax_Imple_EditFaces
  • Ansel_Ajax_Imple_EditGalleryFaces
  • Ansel_Ajax_Imple_Embed
  • Ansel_Ajax_Imple_GallerySlugCheck
  • Ansel_Ajax_Imple_ImageSaveGeotag
  • Ansel_Ajax_Imple_LocationAutoCompleter
  • Ansel_Ajax_Imple_MapLayerSelect
  • Ansel_Ajax_Imple_TagActions
  • Ansel_Ajax_Imple_ToggleGalleryActions
  • Ansel_Ajax_Imple_ToggleOtherGalleries
  • Ansel_Ajax_Imple_UploadNotification
  • Ansel_Api
  • Ansel_Exception
  • Ansel_Faces
  • Ansel_Faces_Base
  • Ansel_Faces_Facedetect
  • Ansel_Faces_User
  • Ansel_Factory_Faces
  • Ansel_Factory_Storage
  • Ansel_Factory_Styles
  • Ansel_Form_Ecard
  • Ansel_Form_Image
  • Ansel_Form_ImageDate
  • Ansel_Form_Upload
  • Ansel_Gallery
  • Ansel_Gallery_Decorator_Date
  • Ansel_GalleryMode_Base
  • Ansel_GalleryMode_Date
  • Ansel_GalleryMode_Normal
  • Ansel_Image
  • Ansel_ImageGenerator
  • Ansel_ImageGenerator_Mini
  • Ansel_ImageGenerator_PolaroidThumb
  • Ansel_ImageGenerator_PolaroidThumbStack
  • Ansel_ImageGenerator_RoundedThumb
  • Ansel_ImageGenerator_RoundedThumbStack
  • Ansel_ImageGenerator_Screen
  • Ansel_ImageGenerator_ShadowThumb
  • Ansel_ImageGenerator_ShadowThumbStack
  • Ansel_ImageGenerator_SquareThumb
  • Ansel_ImageGenerator_Thumb
  • Ansel_LoginTasks_SystemTask_Upgrade
  • Ansel_Report
  • Ansel_Report_letter
  • Ansel_Report_mail
  • Ansel_Report_tickets
  • Ansel_Search
  • Ansel_Search_exif
  • Ansel_Search_Tag
  • Ansel_Storage
  • Ansel_Style
  • Ansel_Tagger
  • Ansel_Test
  • Ansel_Tile_DateGallery
  • Ansel_Tile_Gallery
  • Ansel_Tile_Image
  • Ansel_View_Ansel
  • Ansel_View_Base
  • Ansel_View_EmbeddedRenderer_GalleryLink
  • Ansel_View_EmbeddedRenderer_Mini
  • Ansel_View_EmbeddedRenderer_Slideshow
  • Ansel_View_Gallery
  • Ansel_View_GalleryProperties
  • Ansel_View_GalleryRenderer_Base
  • Ansel_View_GalleryRenderer_Gallery
  • Ansel_View_GalleryRenderer_GalleryLightbox
  • Ansel_View_Image
  • Ansel_View_List
  • Ansel_View_Results
  • Ansel_View_Slideshow
  • Ansel_View_Upload
  • Ansel_Widget
  • Ansel_Widget_Actions
  • Ansel_Widget_Base
  • Ansel_Widget_GalleryFaces
  • Ansel_Widget_Geotag
  • Ansel_Widget_ImageFaces
  • Ansel_Widget_Links
  • Ansel_Widget_OtherGalleries
  • Ansel_Widget_OwnerFaces
  • Ansel_Widget_SimilarPhotos
  • Ansel_Widget_Tags
  • Ansel_XPPublisher
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Ansel_View_Upload:: class provides a view for handling image uploads.
  4:  *
  5:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (GPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/gpl.
  9:  *
 10:  * @author  Michael J. Rubinsky <mrubinsk@horde.org>
 11:  * @package Ansel
 12:  */
 13: 
 14: class Ansel_View_Upload
 15: {
 16:     /**
 17:      *
 18:      * @var array
 19:      */
 20:     protected $_params;
 21: 
 22:     /**
 23:      *
 24:      * @var Ansel_Gallery
 25:      */
 26:     protected $_gallery;
 27: 
 28:     /**
 29:      * Force the older, non-javascript uploader view.
 30:      *
 31:      * @var Boolean
 32:      */
 33:     protected $_forceNoScript = false;
 34: 
 35:     /**
 36:      * Flag for when we already output the carousel code.
 37:      *
 38:      * @var boolean
 39:      */
 40:     protected $_haveCarousel = false;
 41: 
 42:     /**
 43:      * Initialize the view. Needs the following parameters:
 44:      * <pre>
 45:      *   'browse_button' - Dom id of button to open file system browser.
 46:      *   'target'        - Url of the target page to upload images to.
 47:      *   'drop_target'   - Dom id of the element to receive drag and drop images
 48:      *                     (If runtime supports it).
 49:      *   'gallery'
 50:      * </pre>
 51:      * @param <type> $params
 52:      */
 53:     public function __construct($params)
 54:     {
 55:         $this->_params = $params;
 56:         $this->_gallery = $this->_params['gallery'];
 57:         if (!empty($params['forceNoScript'])) {
 58:             $this->_forceNoScript = true;
 59:         }
 60: 
 61:         Ansel::initJSVariables();
 62:         Horde::addScriptFile('effects.js', 'horde', true);
 63:         Horde::addScriptFile('carousel.js', 'ansel', true);
 64:         Horde::addScriptFile('upload.js', 'ansel');
 65:     }
 66: 
 67:     public function run()
 68:     {
 69:         /* Check for file upload */
 70:         $this->_handleFileUpload();
 71: 
 72:         // TODO: Configure which runtimes to allow?
 73:         Horde::addScriptFile('plupload/plupload.js', 'horde');
 74:         Horde::addScriptFile('plupload/plupload.flash.js', 'horde');
 75:         Horde::addScriptFile('plupload/plupload.silverlight.js', 'horde');
 76:         Horde::addScriptFile('plupload/plupload.html5.js', 'horde');
 77:         Horde::addScriptFile('plupload/plupload.browserplus.js', 'horde');
 78:         Horde::addScriptFile('plupload/uploader.js', 'horde');
 79: 
 80:         $startText = _("Upload");
 81:         $addText = _("Add Images");
 82:         $header = _("Upload to gallery");
 83:         $returnText =_("View Gallery");
 84:         $subText = _("Add files to the upload queue and click the start button.");
 85:         $sizeError = _("File size error.");
 86:         $typeError = _("File type error.");
 87: 
 88:         $imple = $GLOBALS['injector']
 89:             ->getInstance('Horde_Core_Factory_Imple')
 90:             ->create(array('ansel', 'UploadNotification'));
 91:         $notificationUrl = (string)$imple->getUrl();
 92:         $this->_params['target']->add('gallery', $this->_params['gallery']->id);
 93:         $jsuri = $GLOBALS['registry']->get('jsuri', 'horde');
 94:         // workaround for older mozilla browsers that incorrectly enocde as utf8
 95:         if ($GLOBALS['browser']->getBrowser() == 'mozilla' && $GLOBALS['browser']->getMajor() <= 4) {
 96:             $multipart = 'true';
 97:         } else {
 98:             $multipart = 'false';
 99:         }
100:         $js = <<< EOT
101:         Ansel.ajax.uploadNotificationUrl = '{$notificationUrl}';
102:         var uploader = new Horde_Uploader({
103:             'target': "{$this->_params['target']}",
104:             drop_target: "{$this->_params['drop_target']}",
105:             swf_path: '{$jsuri}/plupload/plupload.flash.swf',
106:             xap_path: '{$jsuri}/plupload/plupload.silverlight.xap',
107:             container: 'anseluploader',
108:             text: { start: '{$startText}',
109:                     add: '{$addText}',
110:                     header: '{$header}',
111:                     returnButton: '{$returnText}',
112:                     subheader: '{$subText}',
113:                     size: '{$sizeError}',
114:                     type: '{$typeError}'
115:             },
116:             header_class: 'hordeUploaderHeader',
117:             container_class: 'uploaderContainer',
118:             return_target: '{$this->_params['return_target']}',
119:             multipart: {$multipart}
120:         },
121:         {
122:             'uploadcomplete': function(up, files) {
123:                 Ansel.uploadedImages = files;
124:                 if (Ansel.conf.havetwitter) {
125:                     $('twitter').toggleClassName('hidden');
126:                 }
127:             }
128:         });
129:         uploader.init();
130:         $('twitter').observe('click', function() {
131:             AnselUpload.doUploadNotification('twitter', '{$this->_gallery->id}');
132:         });
133: EOT;
134: 
135:         $js .= $this->_doCarouselSetup();
136:         Horde::addInlineScript($js, 'load');
137:     }
138: 
139:     /**
140:      * Handle uploads from non-js browsers
141:      */
142:     public function handleLegacy()
143:     {
144:         global $conf, $notification, $browser;
145: 
146:         $vars = Horde_Variables::getDefaultVariables();
147:         $form = new Ansel_Form_Upload($vars, _("Upload photos"));
148: 
149:         // Output the carousel JS in case we are here because the user
150:         // explicitly selected the old uploader.
151:         $js = $this->_doCarouselSetup();
152:         if (!empty($js)) {
153:             Horde::addInlineScript($js, 'load');
154:         }
155: 
156:         if ($form->validate($vars)) {
157:             $valid = true;
158:             $uploaded = 0;
159:             $form->getInfo($vars, $info);
160: 
161:             // Remember the ids of the images we uploaded so we can autogen
162:             $image_ids = array();
163:             for ($i = 0; $i <= $conf['image']['num_uploads'] + 1; ++$i) {
164:                 if (empty($info['file' . $i]['file'])) {
165:                     continue;
166:                 }
167: 
168:                 try {
169:                     $GLOBALS['browser']->wasFileUploaded('file' . $i);
170:                 } catch (Horde_Browser_Exception $e) {
171:                     if (!empty($info['file' . $i]['error'])) {
172:                         $notification->push(
173:                             sprintf(_("There was a problem uploading the photo: %s"), $info['file' . $i]['error']), 'horde.error');
174:                     } elseif (!filesize($info['file' . $i]['file'])) {
175:                         $notification->push(
176:                             _("The uploaded file appears to be empty. It may not exist on your computer."), 'horde.error');
177:                     }
178:                     $valid = false;
179:                     continue;
180:                 }
181: 
182:                 // Check for a compressed file.
183:                 if (in_array(
184:                     $info['file' . $i]['type'],
185:                     array(
186:                         'x-extension/zip',
187:                         'application/x-compressed',
188:                         'application/x-zip-compressed',
189:                         'application/zip')
190:                     ) ||
191:                     Horde_Mime_Magic::filenameToMime($info['file' . $i]['name']) == 'application/zip') {
192: 
193:                     $this->_handleZip($info['file' . $i]['name']);
194: 
195:                 } else {
196: 
197:                     // Read in the uploaded data.
198:                     $data = file_get_contents($info['file' . $i]['file']);
199: 
200:                     // Try and make sure the image is in a recognizeable
201:                     // format.
202:                     if (getimagesize($info['file' . $i]['file']) === false) {
203:                         $notification->push(
204:                             _("The file you uploaded does not appear to be a valid photo."),
205:                             'horde.error');
206:                         continue;
207:                     }
208: 
209:                     // Add the image to the gallery
210:                     $image_data = array(
211:                         'image_filename' => $info['file' . $i]['name'],
212:                         'image_caption' => $vars->get('image' . $i . '_desc'),
213:                         'image_type' => $info['file' . $i]['type'],
214:                         'data' => $data,
215:                         'tags' => (isset($info['image' . $i . '_tags']) ? explode(',', $info['image' . $i . '_tags']) : array()));
216:                     try {
217:                         $image_ids[] = $this->_gallery->addImage(
218:                             $image_data, (bool)$vars->get('image' . $i . '_default'));
219:                         ++$uploaded;
220:                     } catch (Ansel_Exception $e) {
221:                         $notification->push(
222:                             sprintf(_("There was a problem saving the photo: %s"), $e->getMessage()),
223:                             'horde.error');
224:                         $valid = false;
225:                     }
226:                     unset($data);
227:                 }
228:             }
229: 
230:             // Try to autogenerate some views and tell the user what happened.
231:             if ($uploaded) {
232:                 $cnt = count($image_ids);
233:                 for ($i = 0; $i < $conf['image']['autogen'] && $cnt > $i; $i++) {
234:                     $image_id = $image_ids[$i];
235:                     $image = $GLOBALS['injector']
236:                         ->getInstance('Ansel_Storage')
237:                         ->getImage($image_id);
238:                     $image->createView('screen');
239:                     $image->createView('thumb');
240:                     $image->createView('mini');
241:                     unset($image);
242:                 }
243: 
244:                 // postupload hook if needed
245:                 try {
246:                     Horde::callHook('postupload', array($image_ids), 'ansel');
247:                 } catch (Horde_Exception_HookNotSet $e) {}
248:                 $notification->push(sprintf(ngettext("%d photo was uploaded.", "%d photos were uploaded.", $uploaded), $uploaded), 'horde.success');
249:             } elseif ($vars->get('submitbutton') != _("Cancel")) {
250:                 $notification->push(_("You did not select any photos to upload."), 'horde.error');
251:             }
252: 
253:             if ($valid) {
254:                 // Return to the gallery view.
255:                 Ansel::getUrlFor(
256:                     'view',
257:                     array(
258:                         'gallery' => $this->_gallery->id,
259:                         'slug' => $this->_gallery->get('slug'),
260:                         'view' => 'Gallery',
261:                         'page' => $page),
262:                     true)->redirect();
263:                 exit;
264:             }
265:         }
266: 
267:         Horde::startBuffer();
268:         include ANSEL_TEMPLATES . '/image/upload.inc';
269: 
270:         return ($this->_forceNoScript ? '' : '<noscript>') . Horde::endBuffer() . ($this->_forceNoScript ? '' : '</noscript>');
271:     }
272: 
273:     /**
274:      * Return javascript needed to initialize the carousel.
275:      *
276:      * @return string  The javascript code.
277:      */
278:     protected function _doCarouselSetup()
279:     {
280:         if ($this->_haveCarousel) {
281:             return '';
282:         }
283: 
284:         $this->_haveCarousel = true;
285:         $previewUrl = Horde::url('img/upload_preview.php')
286:             ->add('gallery', $this->_gallery->id);
287: 
288:         $js = <<<EOT
289:            Ajax.Response.prototype._getHeaderJSON = function() {
290:             var nbElements = {$this->_gallery->countImages()};
291:             var from = this.request.parameters.from;
292:             var to   = Math.min(nbElements, this.request.parameters.to);
293:             return {html: this.responseText, from: from, to: to, more: to != nbElements};
294:         }
295: 
296:         function runCarousel() {
297:             updateCarouselSize();
298:             carousel = new UI.Ajax.Carousel("horizontal_carousel", { url: "{$previewUrl->toString(true)}", elementSize: 115 })
299:                 .observe("request:started", function() {
300:                     $('spinner').show().morph("opacity:0.8", {duration:0.5});
301:                 })
302:                 .observe("request:ended", function() {
303:                     $('spinner').morph("opacity:0", {duration:0.5, afterFinish: function(obj) { obj.element.hide(); }});
304:                 });
305:         }
306:         function resized() {
307:             updateCarouselSize();
308:             if (carousel) {
309:                 carousel.updateSize();
310:             }
311:         }
312:         function updateCarouselSize() {
313:             var dim = $('anseluploader').getDimensions();
314:             $("horizontal_carousel").style.width = dim.width + "px";
315:             $$("#horizontal_carousel .container").first().style.width =  (dim.width - 50) + "px";
316:         }
317: 
318:         Event.observe(window, 'resize', resized);
319:         carousel = null;
320:         runCarousel();
321: EOT;
322: 
323:         return $js;
324:     }
325: 
326:     /**
327:      * Checks for a file uploaded via the pluploader. If one is found, handle
328:      * it, send the server json response and exit.
329:      */
330:     protected function _handleFileUpload()
331:     {
332:         if ($filename = Horde_Util::getFormData('name')) {
333:             if (isset($_SERVER["HTTP_CONTENT_TYPE"])) {
334:                 $type = $_SERVER["HTTP_CONTENT_TYPE"];
335:             } elseif (isset($_SERVER["CONTENT_TYPE"])) {
336:                 $type = $_SERVER["CONTENT_TYPE"];
337:             }
338: 
339:             if (empty($type) || $type == 'application/octet-stream') {
340:                 $temp = Horde_Util::getTempFile('', true);
341:                 $out = fopen($temp, 'wb');
342:                 if ($out) {
343:                     // Read binary input stream and append it to temp file
344:                     $in = fopen("php://input", "rb");
345:                     if ($in) {
346:                         while ($buff = fread($in, 4096)) {
347:                             fwrite($out, $buff);
348:                         }
349:                     } else {
350:                         fclose($out);
351:                         header('Content-Type: application/json');
352:                         echo('{ "status" : "500", "file": "' . $temp. '", error" : { "message": "Failed to open input stream." } }');
353:                         exit;
354:                     }
355:                 } else {
356:                     header('Content-Type: application/json');
357:                     echo('{ "status" : "500", "file": "' . $temp. '", error" : { "message": "Failed to open output stream." } }');
358:                     exit;
359:                 }
360: 
361:                 // Don't know type. Try to deduce it.
362:                 if (!($type = Horde_Mime_Magic::analyzeFile($temp, isset($GLOBALS['conf']['mime']['magic_db']) ? $GLOBALS['conf']['mime']['magic_db'] : null))) {
363:                     $type = Horde_Mime_Magic::filenameToMime($filename);
364:                 }
365:             } elseif (strpos($type, "multipart") !== false) {
366:                 // Handle mulitpart uploads
367:                 $temp = Horde_Util::getTempFile('', true);
368:                 $out = fopen($temp, 'wb');
369:                 if ($out) {
370:                     $in = fopen($_FILES['file']['tmp_name'], 'rb');
371:                     if ($in) {
372:                         while ($buff = fread($in, 4096)) {
373:                             fwrite($out, $buff);
374:                         }
375:                     } else {
376:                         fclose($out);
377:                         header('Content-Type: application/json');
378:                         echo('{ "status" : "500", "file": "' . $temp. '", error" : { "message": "Failed to open input stream." } }');
379:                         exit;
380:                     }
381:                 } else {
382:                     header('Content-Type: application/json');
383:                     echo('{ "status" : "500", "file": "' . $temp. '", error" : { "message": "Failed to open output stream." } }');
384:                     exit;
385:                 }
386:             }
387: 
388:             // Figure out what to do with the file
389:             if (in_array(
390:                     $type,
391:                     array(
392:                         'x-extension/zip',
393:                         'application/x-compressed',
394:                         'application/x-zip-compressed',
395:                         'application/zip')) ||
396:                 Horde_Mime_Magic::filenameToMime($temp) == 'application/zip') {
397: 
398:                 // ZIP file
399:                 try {
400:                     $image_ids = $this->_handleZip($temp);
401:                 } catch (Ansel_Exception $e) {
402:                     $notification->push(
403:                         sprintf(_("There was an error processing the uploaded archive: %s"), $e->getMessage()), 'horde.error');
404:                 }
405: 
406:                 header('Content-Type: application/json');
407:                 echo('{ "status" : "200", "error" : {} }');
408:                 exit;
409:             } else {
410:                 // Try and make sure the image is in a recognizeable format.
411:                 $data = file_get_contents($temp);
412:                 if (getimagesize($temp) === false) {
413:                     header('Content-Type: application/json');
414:                     echo('{ "status" : "400", "error" : { "message": "Not a valid, supported image file." }, "id" : "id" }');
415:                     exit;
416:                 }
417: 
418:                 // Add the image to the gallery
419:                 $image_data = array(
420:                     'image_filename' => $filename,
421:                     'image_type' => $type,
422:                     'data' => $data);
423: 
424:                 try {
425:                     $image_id = $this->_gallery->addImage($image_data);
426:                     $image_ids[] = $image_id;
427:                 } catch (Ansel_Exception $e) {
428:                     header('Content-Type: application/json');
429:                     echo('{ "status" : "400", "error" : { "message": "Not a valid, supported image file." }, "id" : "id" }');
430:                     exit;
431:                 }
432: 
433:                 unset($data);
434:                 header('Content-Type: application/json');
435:                 echo('{ "status" : "200", "error" : {} }');
436:                 exit;
437:             }
438:         }
439:     }
440: 
441:     /**
442:      * Indicates if the specified filename is a known meta file type.
443:      *
444:      * @param string $filename
445:      *
446:      * @return boolean
447:      */
448:     protected function _isMetaFile($filename)
449:     {
450:         /* Skip some known metadata files. */
451:         $len = strlen($filename);
452:         if ($filename[$len - 1] == '/' ||
453:             $filename == 'Thumbs.db' ||
454:             strrpos($filename, '.DS_Store') == ($len - 9) ||
455:             strrpos($filename, '.localized') == ($len - 10) ||
456:             strpos($filename, '__MACOSX/') !== false) {
457: 
458:             return true;
459:         }
460: 
461:         return false;
462:     }
463: 
464:     /**
465:      * Handle extracting images from uploaded zip files.
466:      *
467:      * @param string $filename  The local path to the zip file.
468:      *
469:      * @return array  An array of the resulting image_ids.
470:      * @throws Ansel_Exception
471:      */
472:     private function _handleZip($filename)
473:     {
474:         $image_ids = array();
475: 
476:         /* See if we can use the zip extension for reading the file. */
477:         if (Horde_Util::extensionExists('zip')) {
478:             $zip = new ZipArchive();
479:             if ($zip->open($filename) !== true) {
480:                 throw new Ansel_Exception(_("Could not open zip archive."));
481:             }
482: 
483:             /* Iterate the archive */
484:             for ($z = 0; $z < $zip->numFiles; $z++) {
485:                 $zinfo = $zip->statIndex($z);
486:                 if ($this->_isMetaFile($zinfo['name'])) {
487:                     continue;
488:                 }
489: 
490:                 /* Extract the image */
491:                 $stream = $zip->getStream($zinfo['name']);
492:                 $zdata = stream_get_contents($stream);
493:                 if (!strlen($zdata)) {
494:                     throw new Ansel_Exception(_("Could not extract image data from zip archive."));
495:                 }
496: 
497:                 /* Save the image */
498:                 $image_id = $this->_gallery->addImage(array('image_filename' => $zinfo['name'],
499:                                                      'image_caption' => '',
500:                                                      'data' => $zdata));
501:                 $image_ids[] = $image_id;
502:                 unset($zdata);
503:             }
504:             $zip->close();
505:             unset($zip);
506:         } else {
507:             /* Receiving zip data, but extension not loaded */
508:             $data = file_get_contents($filename);
509: 
510:             /* Get list of images */
511:             try {
512:                 $zip = Horde_Compress::factory('zip');
513:                 $files = $zip->decompress($data, array('action' => Horde_Compress_Zip::ZIP_LIST));
514:             } catch (Horde_Exception $e) {
515:                 throw new Ansel_Exception($e);
516:                 continue;
517:             }
518: 
519:             /* Iterate the archive */
520:             foreach ($files as $key => $zinfo) {
521:                 if ($this->_isMetaFile($zinfo['name'])) {
522:                     continue;
523:                 }
524: 
525:                 /* Extract the image */
526:                 try {
527:                     $zdata = $zip->decompress($data,
528:                                               array('action' => Horde_Compress_Zip::ZIP_DATA,
529:                                                     'info' => $files,
530:                                                     'key' => $key));
531:                 } catch (Horde_Exception $e) {
532:                     throw new Ansel_Exception($e);
533:                 }
534: 
535:                 /* Add the image */
536:                 $image_id = $this->_gallery->addImage(array('image_filename' => $zinfo['name'],
537:                                                      'image_caption' => '',
538:                                                      'data' => $zdata));
539:                 $image_ids[] = $image_id;
540:                 unset($zdata);
541:             }
542:             unset($zip);
543:             unset($data);
544:         }
545: 
546:         return $image_ids;
547:     }
548: 
549: }
550: 
API documentation generated by ApiGen