Initial work for new ansel image uploader.
authorMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 9 Sep 2010 21:09:42 +0000 (17:09 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 9 Sep 2010 21:09:42 +0000 (17:09 -0400)
This is a custom uploader using pluploader library. Very basic UI
to start, but is mostly functional. Currently falls back to the old
uploader if browser is noscript. Currently attempt to use the following
to upload in this order: html5, flash, silverlight, browserplus. HTML5 supports
drag and drop images right onto the uploader box. Images can be added
pretty much at anytime during the lifecycle.

Still need to refactor the upload view into a single Horde-4ish view
object.

Request: 8862

21 files changed:
ansel/img/upload.php
ansel/js/carousel.js
ansel/lib/View/Upload.php [new file with mode: 0644]
ansel/templates/image/plupload.inc [new file with mode: 0644]
ansel/templates/image/upload.inc
ansel/themes/screen.css
ansel/view.php
horde/js/plupload/i18n/da.js [new file with mode: 0644]
horde/js/plupload/i18n/sv.js [new file with mode: 0644]
horde/js/plupload/jquery.plupload.queue.js [new file with mode: 0644]
horde/js/plupload/plupload.browserplus.js [new file with mode: 0644]
horde/js/plupload/plupload.flash.js [new file with mode: 0644]
horde/js/plupload/plupload.flash.swf [new file with mode: 0644]
horde/js/plupload/plupload.gears.js [new file with mode: 0644]
horde/js/plupload/plupload.html4.js [new file with mode: 0644]
horde/js/plupload/plupload.html5.js [new file with mode: 0644]
horde/js/plupload/plupload.js [new file with mode: 0644]
horde/js/plupload/plupload.silverlight.js [new file with mode: 0644]
horde/js/plupload/plupload.silverlight.xap [new file with mode: 0644]
horde/js/plupload/uploader.js [new file with mode: 0644]
horde/themes/screen.css

index ebb871c..0722651 100644 (file)
@@ -19,232 +19,30 @@ try {
     Ansel::getUrlFor('view', array('view' => 'List'), true)->redirect();
     exit;
 }
+if (!$gallery->hasPermission($registry->getAuth(), Horde_Perms::EDIT)) {
+    $notification->push(_("You are not authorized to upload photos to this gallery."), 'horde.error');
+    Ansel::getUrlFor('view', array('view' => 'List'), true)->redirect();
+}
 
 $page = Horde_Util::getFormData('page', 0);
-$vars = Horde_Variables::getDefaultVariables();
-
-$form = new Ansel_Form_Upload($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. */
-        try {
-            $GLOBALS['browser']->wasFileUploaded('file' . $i);
-        } catch (Horde_Browser_Exception $e) {
-            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;
-            continue;
-        }
-
-
-        /* Check for a compressed file. */
-        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 */
-                    try {
-                        $image_id = $gallery->addImage(
-                            array(
-                                'image_filename' => $zinfo['name'],
-                                'image_caption' => '',
-                                'data' => $zdata)
-                        );
-                        ++$uploaded;
-                        if ($conf['image']['autogen'] > count($image_ids)) {
-                            $image_ids[] = $image_id;
-                        }
-                    } catch (Ansel_Exception $e) {
-                        $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id), 'horde.error');
-                    }
-                    unset($zdata);
-                }
-
-                $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. */
-                try {
-                    $zip = Horde_Compress::factory('zip');
-                    $files = $zip->decompress($data, array('action' => Horde_Compress_Zip::ZIP_LIST));
-                } catch (Horde_Exception $e) {
-                    $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $e->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;
-                    }
-
-                    try {
-                        $zdata = $zip->decompress($data, array('action' => Horde_Compress_Zip::ZIP_DATA,
-                                                               'info' => $files,
-                                                               'key' => $key));
-                    } catch (Horde_Exception $e) {
-                        $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $e->getMessage()), 'horde.error');
-                        break;
-                    }
-
-                    /* If we successfully got data, try adding the image */
-                    try {
-                        $image_id = $gallery->addImage(
-                            array(
-                                'image_filename' => $zinfo['name'],
-                                'image_caption' => '',
-                                'data' => $zdata)
-                        );
-                        ++$uploaded;
-                        if ($conf['image']['autogen'] > count($image_ids)) {
-                            $image_ids[] = $image_id;
-                        }
-                    } catch (Ansel_Exception $e) {
-                        $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id), 'horde.error');
-                    }
-                    unset($zdata);
-                }
-                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()));
-            try {
-                $image_id = $gallery->addImage($image_data, (bool)$vars->get('image' . $i . '_default'));
-                ++$uploaded;
-                $image_ids[] = $image_id;
-            } catch (Ansel_Exception $e) {
-                $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id->getMessage()), 'horde.error');
-                $valid = false;
-            }
-            unset($data);
-        }
-    }
-
-    /* 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['injector']->getInstance('Ansel_Storage')->getScope()->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_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. */
-        Ansel::getUrlFor('view',
-                         array('gallery' => $gallery_id,
-                               'slug' => $gallery->get('slug'),
-                               'view' => 'Gallery',
-                               'page' => $page),
-                         true)->redirect();
-        exit;
-    }
-}
-///* Preview existing images */
-if ($gallery->countImages() && $browser->hasFeature('javascript')) {
-    $haveImages = true;
-}
+$return_url = Ansel::getUrlFor('view',
+                               array('gallery' => $gallery_id,
+                                     'slug' => $gallery->get('slug'),
+                                     'view' => 'Gallery',
+                                     'page' => $page),
+                               true);
+$view = new Ansel_View_Upload(array('browse_button' => 'pickfiles',
+                                    'target' => Horde::selfUrl(),
+                                    'drop_target' => 'filelist',
+                                    'upload_button' => 'uploadfiles',
+                                    'gallery' => $gallery,
+                                    'return_target' => $return_url->toString()));
+$view->run();
+$nojs = $view->handleNoJs();
 
-$breadcrumbs = Ansel::getBreadCrumbs($gallery);
 $title = _("Add Photo");
 require ANSEL_TEMPLATES . '/common-header.inc';
-echo Horde::menu();
-$notification->notify(array('listeners' => 'status'));
-require ANSEL_TEMPLATES . '/image/upload.inc';
+require ANSEL_TEMPLATES . '/menu.inc';
+echo '<div class="header" id="galleryHeader"><span class="breadcrumbs">' . Ansel::getBreadCrumbs($gallery) . '</span></div>';
+require ANSEL_TEMPLATES . '/image/plupload.inc';
 require $registry->get('templates', 'horde') . '/common-footer.inc';
index b966dbb..71d197f 100644 (file)
@@ -5,7 +5,7 @@
  *
  *--------------------------------------------------------------------------*/
 
-if(typeof Prototype == 'undefined' || !Prototype.Version.match("1.6"))
+if(typeof Prototype == 'undefined' )
   throw("Prototype-UI library require Prototype library >= 1.6.0");
 
 if (Prototype.Browser.WebKit) {
diff --git a/ansel/lib/View/Upload.php b/ansel/lib/View/Upload.php
new file mode 100644 (file)
index 0000000..6fcc624
--- /dev/null
@@ -0,0 +1,388 @@
+<?php
+/**
+ * The Ansel_View_Upload:: class provides a view for handling image uploads.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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 <mrubinsk@horde.org>
+ * @package Ansel
+ */
+
+class Ansel_View_Upload
+{
+    protected $_params;
+
+    /**
+     * Initialize the view. Needs the following parameters:
+     * <pre>
+     *   'browse_button' - Dom id of button to open file system browser.
+     *   'target'        - Url of the target page to upload images to.
+     *   'drop_target'   - Dom id of the element to receive drag and drop images
+     *                     (If runtime supports it).
+     *   'gallery'
+     * </pre>
+     * @param <type> $params
+     */
+    public function __construct($params)
+    {
+        $this->_params = $params;
+    }
+
+    public function run()
+    {
+        /* Check for file upload */
+        $this->_handleFileUpload();
+
+        // TODO: Configure which runtimes to allow?
+        Horde::addScriptFile('plupload/plupload.js', 'horde');
+        Horde::addScriptFile('plupload/plupload.flash.js', 'horde');
+        Horde::addScriptFile('plupload/plupload.silverlight.js', 'horde');
+        Horde::addScriptFile('plupload/plupload.html5.js', 'horde');
+        Horde::addScriptFile('plupload/plupload.browserplus.js', 'horde');
+        Horde::addScriptFile('plupload/uploader.js', 'horde');
+        Horde::addScriptFile('effects.js', 'horde', true);
+        Horde::addScriptFile('carousel.js', 'ansel', true);
+
+        $startText = _("Upload");
+        $addText = _("Add Image");
+        $header = _("Upload to gallery");
+        $returnText =_("View Gallery");
+
+        $this->_params['target']->add('gallery', $this->_params['gallery']->id);
+
+        $jsuri = $GLOBALS['registry']->get('jsuri', 'horde');
+        $js = <<< EOT
+        var uploader = new Horde_Uploader({
+            'target': "{$this->_params['target']}",
+            drop_target: "{$this->_params['drop_target']}",
+            swf_path: '{$jsuri}/plupload/plupload.flash.swf',
+            xap_path: '{$jsuri}/plupload/plupload.silverlight.xap',
+            container: 'anseluploader',
+            text: { start: '{$startText}',
+                    add: '{$addText}',
+                    header: '{$header}',
+                    returnButton: '{$returnText}'
+            },
+            header_class: 'control header hordeUploaderHeader',
+            container_class: 'uploaderContainer',
+            return_target: '{$this->_params['return_target']}'
+        });
+        uploader.init();
+EOT;
+
+        Horde::addInlineScript($js, 'dom');
+    }
+
+    /**
+     * Handle uploads from non-js browsers
+     */
+    public function handleNoJs()
+    {
+        global $conf, $notification, $browser;
+
+        $vars = Horde_Variables::getDefaultVariables();
+        $form = new Ansel_Form_Upload($vars, _("Upload photos"));
+        $gallery = $this->_params['gallery'];
+        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. */
+                try {
+                    $GLOBALS['browser']->wasFileUploaded('file' . $i);
+                } catch (Horde_Browser_Exception $e) {
+                    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;
+                    continue;
+                }
+
+                /* Check for a compressed file. */
+                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 */
+                            try {
+                                $image_id = $gallery->addImage(
+                                    array('image_filename' => $zinfo['name'],
+                                          'image_caption' => '',
+                                          'data' => $zdata));
+                                ++$uploaded;
+                                if ($conf['image']['autogen'] > count($image_ids)) {
+                                    $image_ids[] = $image_id;
+                                }
+                            } catch (Ansel_Exception $e) {
+                                $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id), 'horde.error');
+                            }
+                            unset($zdata);
+                        }
+                        $zip->close();
+                        unset($zip);
+                    } else {
+                        /* Receiving zip data, but extension not loaded */
+                        /* Read in the uploaded data. */
+                        $data = file_get_contents($info['file' . $i]['file']);
+
+                        /* Get the list of files in the zipfile. */
+                        try {
+                            $zip = Horde_Compress::factory('zip');
+                            $files = $zip->decompress($data, array('action' => Horde_Compress_Zip::ZIP_LIST));
+                        } catch (Horde_Exception $e) {
+                            $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $e->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;
+                            }
+
+                            try {
+                                $zdata = $zip->decompress($data, array('action' => Horde_Compress_Zip::ZIP_DATA,
+                                                                       'info' => $files,
+                                                                       'key' => $key));
+                            } catch (Horde_Exception $e) {
+                                $notification->push(sprintf(_("There was an error processing the uploaded archive: %s"), $e->getMessage()), 'horde.error');
+                                break;
+                            }
+
+                            /* If we successfully got data, try adding the image */
+                            try {
+                                $image_id = $gallery->addImage(
+                                    array(
+                                        'image_filename' => $zinfo['name'],
+                                        'image_caption' => '',
+                                        'data' => $zdata)
+                                );
+                                ++$uploaded;
+                                if ($conf['image']['autogen'] > count($image_ids)) {
+                                    $image_ids[] = $image_id;
+                                }
+                            } catch (Ansel_Exception $e) {
+                                $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id), 'horde.error');
+                            }
+                            unset($zdata);
+                        }
+                        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()));
+                    try {
+                        $image_id = $gallery->addImage($image_data, (bool)$vars->get('image' . $i . '_default'));
+                        ++$uploaded;
+                        $image_ids[] = $image_id;
+                    } catch (Ansel_Exception $e) {
+                        $notification->push(sprintf(_("There was a problem saving the photo: %s"), $image_id->getMessage()), 'horde.error');
+                        $valid = false;
+                    }
+                    unset($data);
+                }
+            }
+
+            /* 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['injector']->getInstance('Ansel_Storage')->getScope()->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_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. */
+                Ansel::getUrlFor('view',
+                                 array('gallery' => $gallery_id,
+                                       'slug' => $gallery->get('slug'),
+                                       'view' => 'Gallery',
+                                       'page' => $page),
+                                 true)->redirect();
+                exit;
+            }
+        }
+
+        Horde::startBuffer();
+        include ANSEL_TEMPLATES . '/image/upload.inc';
+        return '<noscript>' . Horde::endBuffer() . '</noscript>';
+    }
+
+    /**
+     * Checks for a file uploaded via the pluploader. If one is found, handle
+     * it, send the server json response and exit.
+     */
+    protected function _handleFileUpload()
+    {
+        if ($filename = Horde_Util::getFormData('name')) {
+            /* First, figure out the content type */
+            if (isset($_SERVER["HTTP_CONTENT_TYPE"])) {
+                $type = $_SERVER["HTTP_CONTENT_TYPE"];
+            } elseif (isset($_SERVER["CONTENT_TYPE"])) {
+                $type = $_SERVER["CONTENT_TYPE"];
+            }
+
+            if (empty($type) || $type == 'application/octet-stream') {
+                $temp = Horde_Util::getTempFile('', true);
+                $out = fopen($temp, 'wb');
+                if ($out) {
+                    // Read binary input stream and append it to temp file
+                    $in = fopen("php://input", "rb");
+                    if ($in) {
+                        while ($buff = fread($in, 4096)) {
+                            fwrite($out, $buff);
+                        }
+                    } else {
+                        fclose($out);
+                        header('Content-Type: application/json');
+                        echo('{"status" : "500", "file": "' . $temp. '", error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
+                        exit;
+                    }
+                }
+                // Don't know type. Try to deduce it.
+                if (!($type = Horde_Mime_Magic::analyzeFile($temp, isset($GLOBALS['conf']['mime']['magic_db']) ? $GLOBALS['conf']['mime']['magic_db'] : null))) {
+                    $type = Horde_Mime_Magic::filenameToMime($filename);
+                }
+            } elseif (strpos($type, "multipart") !== false) {
+                // TODO:
+            }
+
+            /* Figure out what to do with the file */
+            if (in_array($type,
+                         array('x-extension/zip',
+                               'application/x-compressed',
+                               'application/x-zip-compressed',
+                               'application/zip'))) {
+
+                /* handle zip files */
+                header('Content-Type: application/json');
+                echo('{ "status" : "501", "error" : { "message": "Not implemented" }, "id" : "id" }');
+                exit;
+            } else {
+                /* Try and make sure the image is in a recognizeable
+                 * format. */
+                $data = file_get_contents($temp);
+                if (getimagesize($temp) === false) {
+                    header('Content-Type: application/json');
+                    echo('{ "status" : "400", "error" : { "message": "Not a valid, supported image file." }, "id" : "id" }');
+                    exit;
+                }
+
+                /* Add the image to the gallery */
+                $image_data = array('image_filename' => $filename,
+                                    //'image_caption' => $vars->get('image' . $i . '_desc'),
+                                    'image_type' => $type,
+                                    'data' => $data,
+                                    );
+
+                try {
+                    $gallery = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery(Horde_Util::getFormData('gallery'));
+                    $image_id = $gallery->addImage($image_data);
+                    $image_ids[] = $image_id;
+                } catch (Ansel_Exception $e) {
+                    header('Content-Type: application/json');
+                    echo('{ "status" : "400", "error" : { "message": "Not a valid, supported image file." }, "id" : "id" }');
+                    exit;
+                }
+                
+                unset($data);
+                header('Content-Type: application/json');
+                echo('{ "status" : "200", "error" : {}, "id" : "id" }');
+                exit;
+            }
+        }
+    }
+}
diff --git a/ansel/templates/image/plupload.inc b/ansel/templates/image/plupload.inc
new file mode 100644 (file)
index 0000000..3771cb0
--- /dev/null
@@ -0,0 +1,156 @@
+<div class="anselWidgets">
+    <div id="horizontal_carousel">
+     <div class="anselWidget" style="height:150px;">
+         <h2 class="header tagTitle"><?php echo _("Photos in this gallery") . ' (' . $gallery->countImages() . ')' ?></h2>
+      <br />
+      <div class="previous_button"></div>
+      <div class="container">
+       <ul></ul>
+       <div id="spinner" style="display: none;"><?php echo _("Loading ...") ?></div>
+      </div>
+      <div class="next_button"></div>
+     </div>
+    </div>
+</div>
+<div id="anseluploader"></div>
+<?php echo $nojs; ?>
+<script type="text/javascript">
+// <![CDATA[
+// Delay response
+Ajax.Request.prototype.originalInitialize = Ajax.Request.prototype.initialize;
+Ajax.Request.prototype.initialize = function(url, options) {
+    options.onSuccess = options.onSuccess.wrap(function(proceed, request, json) {
+        proceed.curry(request, json).delay(1);
+    });
+    this.originalInitialize(url, options);
+}
+
+// Mock ajax response
+Ajax.Response.prototype._getHeaderJSON = function() {
+    var nbElements = <?php echo $gallery->countImages() ?>;
+    var from = this.request.parameters.from;
+    var to   = Math.min(nbElements, this.request.parameters.to);
+    return {html: this.responseText, from: from, to: to, more: to != nbElements};
+}
+
+var carousel = null;
+
+function runTest() {
+    updateCarouselSize();
+    carousel = new UI.Ajax.Carousel("horizontal_carousel", {url: "<?php echo Horde::url('img/upload_preview.php')->add('gallery', $gallery->id) ?>", elementSize: 90})
+        .observe("request:started", function() {
+            $('spinner').show().morph("opacity:0.8", {duration:0.5});
+        })
+        .observe("request:ended", function() {
+            $('spinner').morph("opacity:0", {duration:0.5, afterFinish: function(obj) { obj.element.hide(); }});
+        });
+}
+
+function resized() {
+    updateCarouselSize();
+    if (carousel)
+        carousel.updateSize();
+}
+
+function updateCarouselSize() {
+    // Get the width of the viewport, then match the percentage of the
+    // table cell the carousel appears in. We can't do this by referencing the
+    // carousel's parent because that won't be sized correctly until the
+    // carousel is sized correctly. Classic chicken and egg....
+    var dim = document.viewport.getDimensions();
+    var w = dim.width - 150;
+
+    // size the carousel
+    $("horizontal_carousel").style.width = (Math.floor(w/90) * 90) + "px";
+    $$("#horizontal_carousel .container").first().style.width =  (Math.floor((w - 100) / 90)) * 90 +  "px";
+}
+Event.observe(window, "load", runTest);
+Event.observe(window, "resize", resized);
+// ]]>
+</script>
+
+<style type="text/css">
+/* Horizontal Carousel */
+#horizontal_carousel {
+  float: left;
+  width: 100%;
+  height: 50px;
+  padding: 3px;
+  position: relative;
+}
+
+#horizontal_carousel .container {
+  float: left;
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+}
+
+#horizontal_carousel ul {
+  margin: 0;
+  padding: 0;
+  width: 100000px;
+  position: relative;
+  top: 0;
+  left: 0;
+}
+
+#horizontal_carousel ul li {
+  width:90px;
+  text-align: center;
+  list-style: none;
+  float: left;
+}
+
+#horizontal_carousel .previous_button {
+  float:left;
+  margin: 5px;
+  padding: 2px;
+  width: 16px;
+  height: 16px;
+  background: url(<?php echo $graphic_dir ?>/slideshow_prev.png);
+  background-repeat: no-repeat;
+  z-index: 100;
+  cursor: pointer;
+}
+
+#horizontal_carousel .previous_button_over {
+  background-color: #e9e9e9;
+}
+
+#horizontal_carousel .previous_button_disabled {
+  cursor: crosshair;
+}
+
+#horizontal_carousel .next_button {
+  float:left;
+  margin: 5px;
+  padding: 2px;
+  width: 16px;
+  height: 16px;
+  background: url(<?php echo $graphic_dir ?>/slideshow_next.png);
+  background-repeat: no-repeat;
+  z-index: 100;
+  cursor: pointer;
+}
+
+#horizontal_carousel .next_button_over {
+  background-color: #e9e9e9;
+}
+
+#horizontal_carousel .next_button_disabled {
+  cursor: crosshair;
+}
+
+#spinner {
+  position: absolute;
+  top: 0px;
+  left: 50px;
+  width: 100%;
+  background: #FFF;
+  z-index: 10;
+  text-align: center;
+  font-size: 150%;
+  opacity: 0;
+}
+</style>
\ No newline at end of file
index 0ddf477..8a2d33c 100644 (file)
@@ -1,179 +1,7 @@
-<div class="header" id="galleryHeader"><span class="breadcrumbs"><?php echo $breadcrumbs ?></span></div>
-<table width="100%" cellspacing="0">
-<tr><td style="vertical-align:top;padding:0;">
 <?php
 $form->renderActive(null, null, 'upload.php', 'post', 'multipart/form-data');
 if ($browser->getPlatform() == 'win' && Horde_Menu::showService('help')) {
     echo '<div class="box" style="margin:8px; padding:8px"><h3>' . Horde_Help::link('ansel', 'xppublish') . ' ' . _("Learn how to publish photos directly from Windows.") . '</h3></div>';
 }
-
-if (!empty($haveImages)) {
-    Horde::addScriptFile('effects.js', 'horde');
-    Horde::addScriptFile('carousel.js', 'ansel');
-}
-
 $graphic_dir = Horde_Themes::img();
-$lbcssurl = Horde::url($GLOBALS['registry']->get('themesuri', 'ansel') . '/lightbox.css', true);
-?>
-</td>
-<td style="vertical-align:top;width:40%;padding:0;">
-  <div class="header">&nbsp;</div>
-  <div class="anselWidgets">
-    <div id="horizontal_carousel">
-     <div class="anselWidget" style="height:150px;">
-      <h2 class="header tagTitle"><?php echo _("Photos in this gallery") . ' (' . $gallery->countImages() . ')' ?></h2>
-      <br />
-      <div class="previous_button"></div>
-      <div class="container">
-       <ul></ul>
-       <div id="spinner" style="display: none;"><?php echo _("Loading ...") ?></div>
-      </div>
-      <div class="next_button"></div>
-     </div>
-    </div>
-  </div>
-</td>
-</tr></table>
-<?php if (!empty($haveImages)):?>
-<script type="text/javascript">
-// <![CDATA[
-// Delay response
-Ajax.Request.prototype.originalInitialize = Ajax.Request.prototype.initialize;
-Ajax.Request.prototype.initialize = function(url, options) {
-    options.onSuccess = options.onSuccess.wrap(function(proceed, request, json) {
-        proceed.curry(request, json).delay(1);
-    });
-    this.originalInitialize(url, options);
-}
-
-// Mock ajax response
-Ajax.Response.prototype._getHeaderJSON = function() {
-    var nbElements = <?php echo $gallery->countImages() ?>;
-    var from = this.request.parameters.from;
-    var to   = Math.min(nbElements, this.request.parameters.to);
-    return {html: this.responseText, from: from, to: to, more: to != nbElements};
-}
-
-var carousel = null;
-
-function runTest() {
-    updateCarouselSize();
-    carousel = new UI.Ajax.Carousel("horizontal_carousel", {url: "<?php echo Horde::url('img/upload_preview.php')->add('gallery', $gallery->id) ?>", elementSize: 90})
-        .observe("request:started", function() {
-            $('spinner').show().morph("opacity:0.8", {duration:0.5});
-        })
-        .observe("request:ended", function() {
-            $('spinner').morph("opacity:0", {duration:0.5, afterFinish: function(obj) { obj.element.hide(); }});
-        });
-}
-
-function resized() {
-    updateCarouselSize();
-    if (carousel)
-        carousel.updateSize();
-}
-
-function updateCarouselSize() {
-    // Get the width of the viewport, then match the percentage of the
-    // table cell the carousel appears in. We can't do this by referencing the
-    // carousel's parent because that won't be sized correctly until the
-    // carousel is sized correctly. Classic chicken and egg....
-    var dim = document.viewport.getDimensions();
-    dim.width *= .4;
-
-    // size the carousel
-    $("horizontal_carousel").style.width = (Math.floor(dim.width/90) * 90) + "px";
-    $$("#horizontal_carousel .container").first().style.width =  (Math.floor((dim.width - 100) / 90)) * 90 +  "px";
-}
-Event.observe(window, "load", runTest);
-Event.observe(window, "resize", resized);
-// ]]>
-</script>
-
-<style type="text/css">
-/* Horizontal Carousel */
-#horizontal_carousel {
-  float: left;
-  width: 100%;
-  height: 50px;
-  padding: 3px;
-  position: relative;
-}
-
-#horizontal_carousel .container {
-  float: left;
-  width: 100%;
-  position: relative;
-  overflow: hidden;
-}
-
-#horizontal_carousel ul {
-  margin: 0;
-  padding: 0;
-  width: 100000px;
-  position: relative;
-  top: 0;
-  left: 0;
-}
-
-#horizontal_carousel ul li {
-  width:90px;
-  text-align: center;
-  list-style: none;
-  float: left;
-}
-
-#horizontal_carousel .previous_button {
-  float:left;
-  margin: 5px;
-  padding: 2px;
-  width: 16px;
-  height: 16px;
-  background: url(<?php echo $graphic_dir ?>/slideshow_prev.png);
-  background-repeat: no-repeat;
-  z-index: 100;
-  cursor: pointer;
-}
-
-#horizontal_carousel .previous_button_over {
-  background-color: #e9e9e9;
-}
-
-#horizontal_carousel .previous_button_disabled {
-  cursor: crosshair;
-}
-
-#horizontal_carousel .next_button {
-  float:left;
-  margin: 5px;
-  padding: 2px;
-  width: 16px;
-  height: 16px;
-  background: url(<?php echo $graphic_dir ?>/slideshow_next.png);
-  background-repeat: no-repeat;
-  z-index: 100;
-  cursor: pointer;
-}
-
-#horizontal_carousel .next_button_over {
-  background-color: #e9e9e9;
-}
-
-#horizontal_carousel .next_button_disabled {
-  cursor: crosshair;
-}
-
-#spinner {
-  position: absolute;
-  top: 0px;
-  left: 50px;
-  width: 100%;
-  background: #FFF;
-  z-index: 10;
-  text-align: center;
-  font-size: 150%;
-  opacity: 0;
-}
-
-</style>
-<?php endif;?>
+$lbcssurl = Horde::url($GLOBALS['registry']->get('themesuri', 'ansel') . '/lightbox.css', true);
\ No newline at end of file
index d1b5a5c..d1c68ee 100644 (file)
@@ -374,3 +374,10 @@ a.latest:link, a.latest:visited, a.latest:hover, a.latest:active {
     background: gray;
     color: #fff;
 }
+#anseluploader {
+    width: auto;
+    margin-top: 180px;
+}
+.uploaderContainer {
+    border: solid 1px #999999;
+}
index 3f35d98..79adfa1 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 /**
+ * Delegates to the correct view.
+ *
  * Copyright 2001-2010 The Horde Project (http://www.horde.org/)
  *
  * See the enclosed file COPYING for license information (GPL). If you
diff --git a/horde/js/plupload/i18n/da.js b/horde/js/plupload/i18n/da.js
new file mode 100644 (file)
index 0000000..fc95896
--- /dev/null
@@ -0,0 +1,12 @@
+// .po file like language pack
+plupload.addI18n({
+       'Select files' : 'Vælg filer',
+       'Add files to the upload queue and click the start button.' : 'Tilføj filer til køen, og tryk pÃ¥ start.',
+       'Filename' : 'Filnavn',
+       'Status' : 'Status',
+       'Size' : 'Størrelse',
+       'Add files' : 'Tilføj filer',
+       'Stop current upload' : 'Stop upload',
+       'Start uploading queue' : 'Start upload',
+       'Drag files here.' : 'Træk filer her.'
+});
\ No newline at end of file
diff --git a/horde/js/plupload/i18n/sv.js b/horde/js/plupload/i18n/sv.js
new file mode 100644 (file)
index 0000000..11c7524
--- /dev/null
@@ -0,0 +1,12 @@
+// .po file like language pack\r
+plupload.addI18n({\r
+       'Select files' : 'Välj filer',\r
+       'Add files to the upload queue and click the start button.' : 'Lägg till filer till kön och tryck pÃ¥ start.',\r
+       'Filename' : 'Filnamn',\r
+       'Status' : 'Status',\r
+       'Size' : 'Storlek',\r
+       'Add files' : 'Lägg till filer',\r
+       'Stop current upload' : 'Stoppa uppladdningen',\r
+       'Start uploading queue' : 'Starta uppladdningen',\r
+       'Drag files here.' : 'Dra filer hit'\r
+});
\ No newline at end of file
diff --git a/horde/js/plupload/jquery.plupload.queue.js b/horde/js/plupload/jquery.plupload.queue.js
new file mode 100644 (file)
index 0000000..c4edd9b
--- /dev/null
@@ -0,0 +1,326 @@
+/**\r
+ * jquery.plupload.queue.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, jQuery:false */\r
+\r
+(function($) {\r
+       var uploaders = {};\r
+\r
+       function _(str) {\r
+               return plupload.translate(str) || str;\r
+       }\r
+\r
+       function renderUI(id, target) {\r
+               // Remove all existing non plupload items\r
+               target.contents().each(function(i, node) {\r
+                       node = $(node);\r
+\r
+                       if (!node.is('.plupload')) {\r
+                               node.remove();\r
+                       }\r
+               });\r
+\r
+               target.prepend(\r
+                       '<div class="plupload_wrapper plupload_scroll">' +\r
+                               '<div id="' + id + '_container" class="plupload_container">' +\r
+                                       '<div class="plupload">' +\r
+                                               '<div class="plupload_header">' +\r
+                                                       '<div class="plupload_header_content">' +\r
+                                                               '<div class="plupload_header_title">' + _('Select files') + '</div>' +\r
+                                                               '<div class="plupload_header_text">' + _('Add files to the upload queue and click the start button.') + '</div>' +\r
+                                                       '</div>' +\r
+                                               '</div>' +\r
+\r
+                                               '<div class="plupload_content">' +\r
+                                                       '<div class="plupload_filelist_header">' +\r
+                                                               '<div class="plupload_file_name">' + _('Filename') + '</div>' +\r
+                                                               '<div class="plupload_file_action">&nbsp;</div>' +\r
+                                                               '<div class="plupload_file_status"><span>' + _('Status') + '</span></div>' +\r
+                                                               '<div class="plupload_file_size">' + _('Size') + '</div>' +\r
+                                                               '<div class="plupload_clearer">&nbsp;</div>' +\r
+                                                       '</div>' +\r
+\r
+                                                       '<ul id="' + id + '_filelist" class="plupload_filelist"></ul>' +\r
+\r
+                                                       '<div class="plupload_filelist_footer">' +\r
+                                                               '<div class="plupload_file_name">' +\r
+                                                                       '<div class="plupload_buttons">' +\r
+                                                                               '<a href="#" class="plupload_button plupload_add">' + _('Add files') + '</a>' +\r
+                                                                               '<a href="#" class="plupload_button plupload_start">' + _('Start upload') + '</a>' +\r
+                                                                       '</div>' +\r
+                                                                       '<span class="plupload_upload_status"></span>' +\r
+                                                               '</div>' +\r
+                                                               '<div class="plupload_file_action"></div>' +\r
+                                                               '<div class="plupload_file_status"><span class="plupload_total_status">0%</span></div>' +\r
+                                                               '<div class="plupload_file_size"><span class="plupload_total_file_size">0 b</span></div>' +\r
+                                                               '<div class="plupload_progress">' +\r
+                                                                       '<div class="plupload_progress_container">' +\r
+                                                                               '<div class="plupload_progress_bar"></div>' +\r
+                                                                       '</div>' +\r
+                                                               '</div>' +\r
+                                                               '<div class="plupload_clearer">&nbsp;</div>' +\r
+                                                       '</div>' +\r
+                                               '</div>' +\r
+                                       '</div>' +\r
+                               '</div>' +\r
+                               '<input type="hidden" id="' + id + '_count" name="' + id + '_count" value="0" />' +\r
+                       '</div>'\r
+               );\r
+       }\r
+\r
+       $.fn.pluploadQueue = function(settings) {\r
+               if (settings) {\r
+                       this.each(function() {\r
+                               var uploader, target, id;\r
+\r
+                               target = $(this);\r
+                               id = target.attr('id');\r
+\r
+                               if (!id) {\r
+                                       id = plupload.guid();\r
+                                       target.attr('id', id);\r
+                               }\r
+\r
+                               uploader = new plupload.Uploader($.extend({\r
+                                       dragdrop : true,\r
+                                       container : id\r
+                               }, settings));\r
+\r
+                               // Call preinit function\r
+                               if (settings.preinit) {\r
+                                       settings.preinit(uploader);\r
+                               }\r
+\r
+                               uploaders[id] = uploader;\r
+\r
+                               function handleStatus(file) {\r
+                                       var actionClass;\r
+\r
+                                       if (file.status == plupload.DONE) {\r
+                                               actionClass = 'plupload_done';\r
+                                       }\r
+\r
+                                       if (file.status == plupload.FAILED) {\r
+                                               actionClass = 'plupload_failed';\r
+                                       }\r
+\r
+                                       if (file.status == plupload.QUEUED) {\r
+                                               actionClass = 'plupload_delete';\r
+                                       }\r
+\r
+                                       if (file.status == plupload.UPLOADING) {\r
+                                               actionClass = 'plupload_uploading';\r
+                                       }\r
+\r
+                                       $('#' + file.id).attr('class', actionClass).find('a').css('display', 'block');\r
+                               }\r
+\r
+                               function updateTotalProgress() {\r
+                                       $('span.plupload_total_status', target).html(uploader.total.percent + '%');\r
+                                       $('div.plupload_progress_bar', target).css('width', uploader.total.percent + '%');\r
+                                       $('span.plupload_upload_status', target).text('Uploaded ' + uploader.total.uploaded + '/' + uploader.files.length + ' files');\r
+\r
+                                       // All files are uploaded\r
+                                       if (uploader.total.uploaded == uploader.files.length) {\r
+                                               uploader.stop();\r
+                                       }\r
+                               }\r
+\r
+                               function updateList() {\r
+                                       var fileList = $('ul.plupload_filelist', target).html(''), inputCount = 0, inputHTML;\r
+\r
+                                       $.each(uploader.files, function(i, file) {\r
+                                               inputHTML = '';\r
+\r
+                                               if (file.status == plupload.DONE) {\r
+                                                       if (file.target_name) {\r
+                                                               inputHTML += '<input type="hidden" name="' + id + '_' + inputCount + '_tmpname" value="' + plupload.xmlEncode(file.target_name) + '" />';\r
+                                                       }\r
+\r
+                                                       inputHTML += '<input type="hidden" name="' + id + '_' + inputCount + '_name" value="' + plupload.xmlEncode(file.name) + '" />';\r
+                                                       inputHTML += '<input type="hidden" name="' + id + '_' + inputCount + '_status" value="' + (file.status == plupload.DONE ? 'done' : 'failed') + '" />';\r
+       \r
+                                                       inputCount++;\r
+\r
+                                                       $('#' + id + '_count').val(inputCount);\r
+                                               }\r
+\r
+                                               fileList.append(\r
+                                                       '<li id="' + file.id + '">' +\r
+                                                               '<div class="plupload_file_name"><span>' + file.name + '</span></div>' +\r
+                                                               '<div class="plupload_file_action"><a href="#"></a></div>' +\r
+                                                               '<div class="plupload_file_status">' + file.percent + '%</div>' +\r
+                                                               '<div class="plupload_file_size">' + plupload.formatSize(file.size) + '</div>' +\r
+                                                               '<div class="plupload_clearer">&nbsp;</div>' +\r
+                                                               inputHTML +\r
+                                                       '</li>'\r
+                                               );\r
+\r
+                                               handleStatus(file);\r
+\r
+                                               $('#' + file.id + '.plupload_delete a').click(function(e) {\r
+                                                       $('#' + file.id).remove();\r
+                                                       uploader.removeFile(file);\r
+\r
+                                                       e.preventDefault();\r
+                                               });\r
+                                       });\r
+\r
+                                       $('span.plupload_total_file_size', target).html(plupload.formatSize(uploader.total.size));\r
+\r
+                                       if (uploader.total.queued === 0) {\r
+                                               $('span.plupload_add_text', target).text(_('Add files.'));\r
+                                       } else {\r
+                                               $('span.plupload_add_text', target).text(uploader.total.queued + ' files queued.');\r
+                                       }\r
+\r
+                                       $('a.plupload_start', target).toggleClass('plupload_disabled', uploader.files.length === 0);\r
+\r
+                                       // Scroll to end of file list\r
+                                       fileList[0].scrollTop = fileList[0].scrollHeight;\r
+\r
+                                       updateTotalProgress();\r
+\r
+                                       // Re-add drag message if there is no files\r
+                                       if (!uploader.files.length && uploader.features.dragdrop && uploader.settings.dragdrop) {\r
+                                               $('#' + id + '_filelist').append('<li class="plupload_droptext">' + _("Drag files here.") + '</li>');\r
+                                       }\r
+                               }\r
+\r
+                               uploader.bind("UploadFile", function(up, file) {\r
+                                       $('#' + file.id).addClass('plupload_current_file');\r
+                               });\r
+\r
+                               uploader.bind('Init', function(up, res) {\r
+                                       renderUI(id, target);\r
+\r
+                                       // Enable rename support\r
+                                       if (!settings.unique_names && settings.rename) {\r
+                                               $('#' + id + '_filelist div.plupload_file_name span', target).live('click', function(e) {\r
+                                                       var targetSpan = $(e.target), file, parts, name, ext = "";\r
+\r
+                                                       // Get file name and split out name and extension\r
+                                                       file = up.getFile(targetSpan.parents('li')[0].id);\r
+                                                       name = file.name;\r
+                                                       parts = /^(.+)(\.[^.]+)$/.exec(name);\r
+                                                       if (parts) {\r
+                                                               name = parts[1];\r
+                                                               ext = parts[2];\r
+                                                       }\r
+\r
+                                                       // Display input element\r
+                                                       targetSpan.hide().after('<input type="text" />');\r
+                                                       targetSpan.next().val(name).focus().blur(function() {\r
+                                                               targetSpan.show().next().remove();\r
+                                                       }).keydown(function(e) {\r
+                                                               var targetInput = $(this);\r
+\r
+                                                               if (e.keyCode == 13) {\r
+                                                                       e.preventDefault();\r
+\r
+                                                                       // Rename file and glue extension back on\r
+                                                                       file.name = targetInput.val() + ext;\r
+                                                                       targetSpan.text(file.name);\r
+                                                                       targetInput.blur();\r
+                                                               }\r
+                                                       });\r
+                                               });\r
+                                       }\r
+\r
+                                       $('a.plupload_add', target).attr('id', id + '_browse');\r
+\r
+                                       up.settings.browse_button = id + '_browse';\r
+\r
+                                       // Enable drag/drop\r
+                                       if (up.features.dragdrop && up.settings.dragdrop) {\r
+                                               up.settings.drop_element = id + '_filelist';\r
+                                               $('#' + id + '_filelist').append('<li class="plupload_droptext">' + _("Drag files here.") + '</li>');\r
+                                       }\r
+\r
+                                       $('#' + id + '_container').attr('title', 'Using runtime: ' + res.runtime);\r
+\r
+                                       $('a.plupload_start', target).click(function(e) {\r
+                                               if (!$(this).hasClass('plupload_disabled')) {\r
+                                                       uploader.start();\r
+                                               }\r
+\r
+                                               e.preventDefault();\r
+                                       });\r
+\r
+                                       $('a.plupload_stop', target).click(function(e) {\r
+                                               uploader.stop();\r
+\r
+                                               e.preventDefault();\r
+                                       });\r
+\r
+                                       $('a.plupload_start', target).addClass('plupload_disabled');\r
+                               });\r
+\r
+                               uploader.init();\r
+\r
+                               // Call setup function\r
+                               if (settings.setup) {\r
+                                       settings.setup(uploader);\r
+                               }\r
+\r
+                               uploader.bind("Error", function(up, err) {\r
+                                       var file = err.file, message;\r
+\r
+                                       if (file) {\r
+                                               message = err.message;\r
+\r
+                                               if (err.details) {\r
+                                                       message += " (" + err.details + ")";\r
+                                               }\r
+\r
+                                               $('#' + file.id).attr('class', 'plupload_failed').find('a').css('display', 'block').attr('title', message);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind('StateChanged', function() {\r
+                                       if (uploader.state === plupload.STARTED) {\r
+                                               $('li.plupload_delete a,div.plupload_buttons', target).hide();\r
+                                               $('span.plupload_upload_status,div.plupload_progress,a.plupload_stop', target).css('display', 'block');\r
+                                               $('span.plupload_upload_status', target).text('Uploaded 0/' + uploader.files.length + ' files');\r
+                                       } else {\r
+                                               $('a.plupload_stop,div.plupload_progress', target).hide();\r
+                                               $('a.plupload_delete', target).css('display', 'block');\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind('QueueChanged', updateList);\r
+\r
+                               uploader.bind('StateChanged', function(up) {\r
+                                       if (up.state == plupload.STOPPED) {\r
+                                               updateList();\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind('FileUploaded', function(up, file) {\r
+                                       handleStatus(file);\r
+                               });\r
+\r
+                               uploader.bind("UploadProgress", function(up, file) {\r
+                                       // Set file specific progress\r
+                                       $('#' + file.id + ' div.plupload_file_status', target).html(file.percent + '%');\r
+\r
+                                       handleStatus(file);\r
+                                       updateTotalProgress();\r
+                               });\r
+                       });\r
+\r
+                       return this;\r
+               } else {\r
+                       // Get uploader instance for specified element\r
+                       return uploaders[$(this[0]).attr('id')];\r
+               }\r
+       };\r
+})(jQuery);\r
diff --git a/horde/js/plupload/plupload.browserplus.js b/horde/js/plupload/plupload.browserplus.js
new file mode 100644 (file)
index 0000000..1107966
--- /dev/null
@@ -0,0 +1,337 @@
+/**\r
+ * plupload.browserplus.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, BrowserPlus:false, window:false */\r
+\r
+(function(plupload) {\r
+       /**\r
+        * Yahoo BrowserPlus implementation. This runtime supports these features: dragdrop, jpgresize, pngresize.\r
+        *\r
+        * @static\r
+        * @class plupload.runtimes.BrowserPlus\r
+        * @extends plupload.Runtime\r
+        */\r
+       plupload.runtimes.BrowserPlus = plupload.addRuntime("browserplus", {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               getFeatures : function() {\r
+                       return {\r
+                               dragdrop : true,\r
+                               jpgresize : true,\r
+                               pngresize : true,\r
+                               chunks : true,\r
+                               progress: true,\r
+                               multipart: true\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Initializes the browserplus runtime.\r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               init : function(uploader, callback) {\r
+                       var browserPlus = window.BrowserPlus, browserPlusFiles = {}, settings = uploader.settings, resize = settings.resize;\r
+\r
+                       function addSelectedFiles(native_files) {\r
+                               var files, i, selectedFiles = [], file, id;\r
+\r
+                               // Add the native files and setup plupload files\r
+                               for (i = 0; i < native_files.length; i++) {\r
+                                       file = native_files[i];\r
+                                       id = plupload.guid();\r
+                                       browserPlusFiles[id] = file;\r
+\r
+                                       selectedFiles.push(new plupload.File(id, file.name, file.size));\r
+                               }\r
+\r
+                               // Any files selected fire event\r
+                               if (i) {\r
+                                       uploader.trigger("FilesAdded", selectedFiles);\r
+                               }\r
+                       }\r
+\r
+                       // Setup event listeners if browserplus was initialized\r
+                       function setup() {\r
+                               // Add drop handler\r
+                               uploader.bind("PostInit", function() {\r
+                                       var dropTargetElm, dropElmId = settings.drop_element,\r
+                                               dropTargetId = uploader.id + '_droptarget',\r
+                                               dropElm = document.getElementById(dropElmId),\r
+                                               lastState;\r
+\r
+                                       // Enable/disable drop support for the drop target\r
+                                       // this is needed to resolve IE bubbeling issues and make it possible to drag/drop\r
+                                       // files into gears runtimes on the same page\r
+                                       function addDropHandler(id, end_callback) {\r
+                                               // Add drop target and listener\r
+                                               browserPlus.DragAndDrop.AddDropTarget({id : id}, function(res) {\r
+                                                       browserPlus.DragAndDrop.AttachCallbacks({\r
+                                                               id : id,\r
+                                                               hover : function(res) {\r
+                                                                       if (!res && end_callback) {\r
+                                                                               end_callback();\r
+                                                                       }\r
+                                                               },\r
+                                                               drop : function(res) {\r
+                                                                       if (end_callback) {\r
+                                                                               end_callback();\r
+                                                                       }\r
+\r
+                                                                       addSelectedFiles(res);\r
+                                                               }\r
+                                                       }, function() {\r
+                                                       });\r
+                                               });\r
+                                       }\r
+\r
+                                       function hide() {\r
+                                               document.getElementById(dropTargetId).style.top = '-1000px';\r
+                                       }\r
+\r
+                                       if (dropElm) {\r
+                                               // Since IE has issues with bubbeling when it comes to the drop of files\r
+                                               // we need to do this hack where we show a drop target div element while dropping\r
+                                               if (document.attachEvent && (/MSIE/gi).test(navigator.userAgent)) {\r
+                                                       // Create drop target\r
+                                                       dropTargetElm = document.createElement('div');\r
+                                                       dropTargetElm.setAttribute('id', dropTargetId);\r
+                                                       plupload.extend(dropTargetElm.style, {\r
+                                                               position : 'absolute',\r
+                                                               top : '-1000px',\r
+                                                               background : 'red',\r
+                                                               filter : 'alpha(opacity=0)',\r
+                                                               opacity : 0\r
+                                                       });\r
+\r
+                                                       document.body.appendChild(dropTargetElm);\r
+\r
+                                                       plupload.addEvent(dropElm, 'dragenter', function(e) {\r
+                                                               var dropElm, dropElmPos;\r
+\r
+                                                               dropElm = document.getElementById(dropElmId);\r
+                                                               dropElmPos = plupload.getPos(dropElm);\r
+\r
+                                                               plupload.extend(document.getElementById(dropTargetId).style, {\r
+                                                                       top : dropElmPos.y + 'px',\r
+                                                                       left : dropElmPos.x + 'px',\r
+                                                                       width : dropElm.offsetWidth + 'px',\r
+                                                                       height : dropElm.offsetHeight + 'px'\r
+                                                               });\r
+                                                       });\r
+\r
+                                                       addDropHandler(dropTargetId, hide);\r
+                                               } else {\r
+                                                       addDropHandler(dropElmId);\r
+                                               }\r
+                                       }\r
+\r
+                                       plupload.addEvent(document.getElementById(settings.browse_button), 'click', function(e) {\r
+                                               var mimeTypes = [], i, a, filters = settings.filters, ext;\r
+\r
+                                               e.preventDefault();\r
+\r
+                                               // Convert extensions to mimetypes\r
+                                               for (i = 0; i < filters.length; i++) {\r
+                                                       ext = filters[i].extensions.split(',');\r
+\r
+                                                       for (a = 0; a < ext.length; a++) {\r
+                                                               mimeTypes.push(plupload.mimeTypes[ext[a]]);\r
+                                                       }\r
+                                               }\r
+\r
+                                               browserPlus.FileBrowse.OpenBrowseDialog({\r
+                                                       mimeTypes : mimeTypes\r
+                                               }, function(res) {\r
+                                                       if (res.success) {\r
+                                                               addSelectedFiles(res.value);\r
+                                                       }\r
+                                               });\r
+                                       });\r
+\r
+                                       // Prevent IE leaks\r
+                                       dropElm = dropTargetElm = null;\r
+                               });\r
+\r
+                               uploader.bind("UploadFile", function(up, file) {\r
+                                       var nativeFile = browserPlusFiles[file.id], urlParams = {},\r
+                                           chunkSize = up.settings.chunk_size, loadProgress, chunkStack = [];\r
+\r
+                                       function uploadFile(chunk, chunks) {\r
+                                               var chunkFile;\r
+\r
+                                               // Stop upload if file is maked as failed\r
+                                               if (file.status == plupload.FAILED) {\r
+                                                       return;\r
+                                               }\r
+\r
+                                               urlParams.name = file.target_name || file.name;\r
+\r
+                                               // Only send chunk parameters if chunk size is defined\r
+                                               if (chunkSize) {\r
+                                                       urlParams.chunk = chunk;\r
+                                                       urlParams.chunks = chunks;\r
+                                               }\r
+\r
+                                           chunkFile = chunkStack.shift();\r
+\r
+                                               browserPlus.Uploader.upload({\r
+                                                       url : plupload.buildUrl(up.settings.url, urlParams),\r
+                                                       files : {file : chunkFile},\r
+                                                       cookies : document.cookies,\r
+                                                       postvars : up.settings.multipart_params,\r
+                                                       progressCallback : function(res) {\r
+                                                               var i, loaded = 0;\r
+\r
+                                                               // since more than 1 chunk can be sent at a time, keep track of how many bytes\r
+                                                               // of each chunk was sent\r
+                                                               loadProgress[chunk] = parseInt(res.filePercent * chunkFile.size / 100, 10);\r
+                                                               for (i = 0; i < loadProgress.length; i++) {\r
+                                                                       loaded += loadProgress[i];\r
+                                                               }\r
+\r
+                                                               file.loaded = loaded;\r
+                                                               up.trigger('UploadProgress', file);\r
+                                                       }\r
+                                               }, function(res) {\r
+                                                       var httpStatus, chunkArgs;\r
+\r
+                                                       if (res.success) {\r
+                                                               httpStatus = res.value.statusCode;\r
+\r
+                                                               if (chunkSize) {\r
+                                                                       up.trigger('ChunkUploaded', file, {\r
+                                                                               chunk : chunk,\r
+                                                                               chunks : chunks,\r
+                                                                               response : res.value.body,\r
+                                                                               status : httpStatus\r
+                                                                       });\r
+                                                               }\r
+\r
+                                                           if (chunkStack.length > 0) {\r
+                                                                       // More chunks to be uploaded\r
+                                                                       uploadFile(++chunk, chunks);\r
+                                                           } else {\r
+                                                                       file.status = plupload.DONE;\r
+\r
+                                                                       up.trigger('FileUploaded', file, {\r
+                                                                               response : res.value.body,\r
+                                                                               status : httpStatus\r
+                                                                       });\r
+\r
+                                                                       // Is error status\r
+                                                                       if (httpStatus >= 400) {\r
+                                                                               up.trigger('Error', {\r
+                                                                                       code : plupload.HTTP_ERROR,\r
+                                                                                       message : 'HTTP Error.',\r
+                                                                                       file : file,\r
+                                                                                       status : httpStatus\r
+                                                                               });\r
+                                                                       }\r
+                                                           }\r
+                                                       } else {\r
+                                                               up.trigger('Error', {\r
+                                                                       code : plupload.GENERIC_ERROR,\r
+                                                                       message : 'Generic Error.',\r
+                                                                       file : file,\r
+                                                                       details : res.error\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                       }\r
+\r
+                                       function chunkAndUploadFile(native_file) {\r
+                                               file.size = native_file.size;\r
+                                               if (chunkSize) {\r
+                                                       browserPlus.FileAccess.chunk({file : native_file, chunkSize : chunkSize}, function(cr) {\r
+                                                               if (cr.success) {\r
+                                                                       var chunks = cr.value, len = chunks.length;\r
+\r
+                                                                       loadProgress = Array(len);\r
+\r
+                                                                       for (var i = 0; i < len; i++) {\r
+                                                                               loadProgress[i] = 0;\r
+                                                                               chunkStack.push(chunks[i]);\r
+                                                                       }\r
+\r
+                                                                       uploadFile(0, len);\r
+                                                               }\r
+                                                       });\r
+                                               } else {\r
+                                                       loadProgress = Array(1);\r
+                                                       chunkStack.push(native_file);\r
+                                                       uploadFile(0, 1);\r
+                                               }\r
+                                       }\r
+\r
+                                       // Resize image if it's a supported format and resize is enabled\r
+                                       if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) {\r
+                                               BrowserPlus.ImageAlter.transform({\r
+                                                       file : nativeFile,\r
+                                                       quality : resize.quality || 90,\r
+                                                       actions : [{\r
+                                                               scale : {\r
+                                                                       maxwidth : resize.width,\r
+                                                                       maxheight : resize.height\r
+                                                               }\r
+                                                       }]\r
+                                               }, function(res) {\r
+                                                       if (res.success) {\r
+                                                               chunkAndUploadFile(res.value.file);\r
+                                                       }\r
+                                               });\r
+                                       } else {\r
+                                               chunkAndUploadFile(nativeFile);\r
+                                       }\r
+                               });\r
+\r
+                               callback({success : true});\r
+                       }\r
+\r
+                       // Check for browserplus object\r
+                       if (browserPlus) {\r
+                               browserPlus.init(function(res) {\r
+                                       var services = [\r
+                                               {service: "Uploader", version: "3"},\r
+                                               {service: "DragAndDrop", version: "1"},\r
+                                               {service: "FileBrowse", version: "1"},\r
+                                               {service: "FileAccess", version: "2"}\r
+                                       ];\r
+\r
+                                       if (resize) {\r
+                                               services.push({service : 'ImageAlter', version : "4"});\r
+                                       }\r
+\r
+                                       if (res.success) {\r
+                                               browserPlus.require({\r
+                                                       services : services\r
+                                               }, function(sres) {\r
+                                                       if (sres.success) {\r
+                                                               setup();\r
+                                                       } else {\r
+                                                               callback();\r
+                                                       }\r
+                                               });\r
+                                       } else {\r
+                                               callback();\r
+                                       }\r
+                               });\r
+                       } else {\r
+                               callback();\r
+                       }\r
+               }\r
+       });\r
+})(plupload);\r
diff --git a/horde/js/plupload/plupload.flash.js b/horde/js/plupload/plupload.flash.js
new file mode 100644 (file)
index 0000000..ebedc03
--- /dev/null
@@ -0,0 +1,297 @@
+/**\r
+ * plupload.flash.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, ActiveXObject:false, escape:false */\r
+\r
+(function(plupload) {\r
+       var uploadInstances = {};\r
+\r
+       function getFlashVersion() {\r
+               var version;\r
+\r
+               try {\r
+                       version = navigator.plugins['Shockwave Flash'];\r
+                       version = version.description;\r
+               } catch (e1) {\r
+                       try {\r
+                               version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');\r
+                       } catch (e2) {\r
+                               version = '0.0';\r
+                       }\r
+               }\r
+\r
+               version = version.match(/\d+/g);\r
+\r
+               return parseFloat(version[0] + '.' + version[1]);\r
+       }\r
+\r
+       plupload.flash = {\r
+               /**\r
+                * Will be executed by the Flash runtime when it sends out events.\r
+                *\r
+                * @param {String} id If for the upload instance.\r
+                * @param {String} name Event name to trigger.\r
+                * @param {Object} obj Parameters to be passed with event.\r
+                */\r
+               trigger : function(id, name, obj) {\r
+                       // Detach the call so that error handling in the browser is presented correctly\r
+                       setTimeout(function() {\r
+                               var uploader = uploadInstances[id], i, args;\r
+\r
+                               if (uploader) {\r
+                                       uploader.trigger('Flash:' + name, obj);\r
+                               }\r
+                       }, 0);\r
+               }\r
+       };\r
+\r
+       /**\r
+        * FlashRuntime implementation. This runtime supports these features: jpgresize, pngresize, chunks.\r
+        *\r
+        * @static\r
+        * @class plupload.runtimes.Flash\r
+        * @extends plupload.Runtime\r
+        */\r
+       plupload.runtimes.Flash = plupload.addRuntime("flash", {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               getFeatures : function() {\r
+                       return {\r
+                               jpgresize: true,\r
+                               pngresize: true,\r
+                               chunks: true,\r
+                               progress: true,\r
+                               multipart: true\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Initializes the upload runtime. This method should add necessary items to the DOM and register events needed for operation. \r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               init : function(uploader, callback) {\r
+                       var browseButton, flashContainer, flashVars, initialized, waitCount = 0, container = document.body;\r
+\r
+                       if (getFlashVersion() < 10) {\r
+                               callback({success : false});\r
+                               return;\r
+                       }\r
+\r
+                       uploadInstances[uploader.id] = uploader;\r
+\r
+                       // Find browse button and set to to be relative\r
+                       browseButton = document.getElementById(uploader.settings.browse_button);\r
+\r
+                       // Create flash container and insert it at an absolute position within the browse button\r
+                       flashContainer = document.createElement('div');\r
+                       flashContainer.id = uploader.id + '_flash_container';\r
+\r
+                       plupload.extend(flashContainer.style, {\r
+                               position : 'absolute',\r
+                               top : '0px',\r
+                               background : uploader.settings.shim_bgcolor || 'transparent',\r
+                               zIndex : 99999,\r
+                               width : '100%',\r
+                               height : '100%'\r
+                       });\r
+\r
+                       flashContainer.className = 'plupload flash';\r
+\r
+                       if (uploader.settings.container) {\r
+                               container = document.getElementById(uploader.settings.container);\r
+                               container.style.position = 'relative';\r
+                       }\r
+\r
+                       container.appendChild(flashContainer);\r
+\r
+                       flashVars = 'id=' + escape(uploader.id);\r
+\r
+                       // Insert the Flash inide the flash container\r
+                       flashContainer.innerHTML = '<object id="' + uploader.id + '_flash" width="100%" height="100%" style="outline:0" type="application/x-shockwave-flash" data="' + uploader.settings.flash_swf_url + '">' +\r
+                               '<param name="movie" value="' + uploader.settings.flash_swf_url + '" />' +\r
+                               '<param name="flashvars" value="' + flashVars + '" />' +\r
+                               '<param name="wmode" value="transparent" />' +\r
+                               '<param name="allowscriptaccess" value="always" /></object>';\r
+\r
+                       function getFlashObj() {\r
+                               return document.getElementById(uploader.id + '_flash');\r
+                       }\r
+\r
+                       function waitLoad() {\r
+                               // Wait for 5 sec\r
+                               if (waitCount++ > 5000) {\r
+                                       callback({success : false});\r
+                                       return;\r
+                               }\r
+\r
+                               if (!initialized) {\r
+                                       setTimeout(waitLoad, 1);\r
+                               }\r
+                       }\r
+\r
+                       waitLoad();\r
+\r
+                       // Fix IE memory leaks\r
+                       browseButton = flashContainer = null;\r
+\r
+                       // Wait for Flash to send init event\r
+                       uploader.bind("Flash:Init", function() {\r
+                               var lookup = {}, i, resize = uploader.settings.resize || {};\r
+\r
+                               initialized = true;\r
+                               getFlashObj().setFileFilters(uploader.settings.filters, uploader.settings.multi_selection);\r
+\r
+                               uploader.bind("UploadFile", function(up, file) {\r
+                                       var settings = up.settings;\r
+\r
+                                       getFlashObj().uploadFile(lookup[file.id], plupload.buildUrl(settings.url, {name : file.target_name || file.name}), {\r
+                                               chunk_size : settings.chunk_size,\r
+                                               width : resize.width,\r
+                                               height : resize.height,\r
+                                               quality : resize.quality || 90,\r
+                                               multipart : settings.multipart,\r
+                                               multipart_params : settings.multipart_params,\r
+                                               file_data_name : settings.file_data_name,\r
+                                               format : /\.(jpg|jpeg)$/i.test(file.name) ? 'jpg' : 'png',\r
+                                               headers : settings.headers\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("Flash:UploadProcess", function(up, flash_file) {\r
+                                       var file = up.getFile(lookup[flash_file.id]);\r
+\r
+                                       if (file.status != plupload.FAILED) {\r
+                                               file.loaded = flash_file.loaded;\r
+                                               file.size = flash_file.size;\r
+\r
+                                               up.trigger('UploadProgress', file);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("Flash:UploadChunkComplete", function(up, info) {\r
+                                       var chunkArgs, file = up.getFile(lookup[info.id]);\r
+\r
+                                       chunkArgs = {\r
+                                               chunk : info.chunk,\r
+                                               chunks : info.chunks,\r
+                                               response : info.text\r
+                                       };\r
+\r
+                                       up.trigger('ChunkUploaded', file, chunkArgs);\r
+\r
+                                       // Stop upload if file is maked as failed\r
+                                       if (file.status != plupload.FAILED) {\r
+                                               getFlashObj().uploadNextChunk();\r
+                                       }\r
+\r
+                                       // Last chunk then dispatch FileUploaded event\r
+                                       if (info.chunk == info.chunks - 1) {\r
+                                               file.status = plupload.DONE;\r
+\r
+                                               up.trigger('FileUploaded', file, {\r
+                                                       response : info.text\r
+                                               });\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("Flash:SelectFiles", function(up, selected_files) {\r
+                                       var file, i, files = [], id;\r
+\r
+                                       // Add the selected files to the file queue\r
+                                       for (i = 0; i < selected_files.length; i++) {\r
+                                               file = selected_files[i];\r
+\r
+                                               // Store away flash ref internally\r
+                                               id = plupload.guid();\r
+                                               lookup[id] = file.id;\r
+                                               lookup[file.id] = id;\r
+\r
+                                               files.push(new plupload.File(id, file.name, file.size));\r
+                                       }\r
+\r
+                                       // Trigger FilesAdded event if we added any\r
+                                       if (files.length) {\r
+                                               uploader.trigger("FilesAdded", files);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("Flash:SecurityError", function(up, err) {\r
+                                       uploader.trigger('Error', {\r
+                                               code : plupload.SECURITY_ERROR,\r
+                                               message : 'Security error.',\r
+                                               details : err.message,\r
+                                               file : uploader.getFile(lookup[err.id])\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("Flash:GenericError", function(up, err) {\r
+                                       uploader.trigger('Error', {\r
+                                               code : plupload.GENERIC_ERROR,\r
+                                               message : 'Generic error.',\r
+                                               details : err.message,\r
+                                               file : uploader.getFile(lookup[err.id])\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("Flash:IOError", function(up, err) {\r
+                                       uploader.trigger('Error', {\r
+                                               code : plupload.IO_ERROR,\r
+                                               message : 'IO error.',\r
+                                               details : err.message,\r
+                                               file : uploader.getFile(lookup[err.id])\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("QueueChanged", function(up) {\r
+                                       uploader.refresh();\r
+                               });\r
+\r
+                               uploader.bind("FilesRemoved", function(up, files) {\r
+                                       var i;\r
+\r
+                                       for (i = 0; i < files.length; i++) {\r
+                                               getFlashObj().removeFile(lookup[files[i].id]);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("StateChanged", function(up) {\r
+                                       uploader.refresh();\r
+                               });\r
+\r
+                               uploader.bind("Refresh", function(up) {\r
+                                       var browseButton, browsePos, browseSize;\r
+\r
+                                       // Set file filters incase it has been changed dynamically\r
+                                       getFlashObj().setFileFilters(uploader.settings.filters, uploader.settings.multi_selection);\r
+\r
+                                       browseButton = document.getElementById(up.settings.browse_button);\r
+                                       browsePos = plupload.getPos(browseButton, document.getElementById(up.settings.container));\r
+                                       browseSize = plupload.getSize(browseButton);\r
+\r
+                                       plupload.extend(document.getElementById(up.id + '_flash_container').style, {\r
+                                               top : browsePos.y + 'px',\r
+                                               left : browsePos.x + 'px',\r
+                                               width : browseSize.w + 'px',\r
+                                               height : browseSize.h + 'px'\r
+                                       });\r
+                               });\r
+\r
+                               callback({success : true});\r
+                       });\r
+               }\r
+       });\r
+})(plupload);\r
diff --git a/horde/js/plupload/plupload.flash.swf b/horde/js/plupload/plupload.flash.swf
new file mode 100644 (file)
index 0000000..62eef87
Binary files /dev/null and b/horde/js/plupload/plupload.flash.swf differ
diff --git a/horde/js/plupload/plupload.gears.js b/horde/js/plupload/plupload.gears.js
new file mode 100644 (file)
index 0000000..47cc140
--- /dev/null
@@ -0,0 +1,298 @@
+/**\r
+ * plupload.gears.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, google:false, window:false */\r
+\r
+(function(plupload) {\r
+       var blobs = {};\r
+\r
+       function scaleImage(image_blob, width, height, quality, mime) {\r
+               var percentage, canvas, context, scale;\r
+\r
+               // Setup canvas and scale\r
+               canvas = google.gears.factory.create('beta.canvas');\r
+               canvas.decode(image_blob);\r
+               scale = Math.min(width / canvas.width, height / canvas.height);\r
+\r
+               if (scale < 1) {\r
+                       width = Math.round(canvas.width * scale);\r
+                       height = Math.round(canvas.height * scale);\r
+               } else {\r
+                       width = canvas.width;\r
+                       height = canvas.height;\r
+               }\r
+\r
+               canvas.resize(width, height);\r
+\r
+               return canvas.encode(mime, {quality : quality / 100});\r
+       }\r
+\r
+       /**\r
+        * Gears implementation. This runtime supports these features: dragdrop, jpgresize, pngresize, chunks.\r
+        *\r
+        * @static\r
+        * @class plupload.runtimes.Gears\r
+        * @extends plupload.Runtime\r
+        */\r
+       plupload.runtimes.Gears = plupload.addRuntime("gears", {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               getFeatures : function() {\r
+                       return {\r
+                               dragdrop: true,\r
+                               jpgresize: true,\r
+                               pngresize: true,\r
+                               chunks: true,\r
+                               progress: true,\r
+                               multipart: true\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Initializes the upload runtime.\r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               init : function(uploader, callback) {\r
+                       var desktop;\r
+\r
+                       // Check for gears support\r
+                       if (!window.google || !google.gears) {\r
+                               return callback({success : false});\r
+                       }\r
+\r
+                       try {\r
+                               desktop = google.gears.factory.create('beta.desktop');\r
+                       } catch (e) {\r
+                               // Might fail on the latest Gecko build for some odd reason\r
+                               return callback({success : false});\r
+                       }\r
+\r
+                       function addSelectedFiles(selected_files) {\r
+                               var file, i, files = [], id;\r
+\r
+                               // Add the selected files to the file queue\r
+                               for (i = 0; i < selected_files.length; i++) {\r
+                                       file = selected_files[i];\r
+\r
+                                       // Store away gears blob internally\r
+                                       id = plupload.guid();\r
+                                       blobs[id] = file.blob;\r
+\r
+                                       files.push(new plupload.File(id, file.name, file.blob.length));\r
+                               }\r
+\r
+                               // Fire FilesAdded event\r
+                               uploader.trigger("FilesAdded", files);\r
+                       }\r
+\r
+                       // Add drop handler\r
+                       uploader.bind("PostInit", function() {\r
+                               var settings = uploader.settings, dropElm = document.getElementById(settings.drop_element);\r
+\r
+                               if (dropElm) {\r
+                                       // Block browser default drag over\r
+                                       plupload.addEvent(dropElm, 'dragover', function(e) {\r
+                                               desktop.setDropEffect(e, 'copy');\r
+                                               e.preventDefault();\r
+                                       });\r
+\r
+                                       // Attach drop handler and grab files from Gears\r
+                                       plupload.addEvent(dropElm, 'drop', function(e) {\r
+                                               var dragData = desktop.getDragData(e, 'application/x-gears-files');\r
+\r
+                                               if (dragData) {\r
+                                                       addSelectedFiles(dragData.files);\r
+                                               }\r
+\r
+                                               e.preventDefault();\r
+                                       });\r
+\r
+                                       // Prevent IE leak\r
+                                       dropElm = 0;\r
+                               }\r
+\r
+                               // Add browse button\r
+                               plupload.addEvent(document.getElementById(settings.browse_button), 'click', function(e) {\r
+                                       var filters = [], i, a, ext;\r
+\r
+                                       e.preventDefault();\r
+\r
+                                       for (i = 0; i < settings.filters.length; i++) {\r
+                                               ext = settings.filters[i].extensions.split(',');\r
+\r
+                                               for (a = 0; a < ext.length; a++) {\r
+                                                       filters.push('.' + ext[a]);\r
+                                               }\r
+                                       }\r
+\r
+                                       desktop.openFiles(addSelectedFiles, {singleFile : !settings.multi_selection, filter : filters});\r
+                               });\r
+                       });\r
+\r
+                       uploader.bind("UploadFile", function(up, file) {\r
+                               var chunk = 0, chunks, chunkSize, loaded = 0, resize = up.settings.resize, chunking;\r
+\r
+                               chunkSize = up.settings.chunk_size;\r
+                               chunking = chunkSize > 0;\r
+                               chunks = Math.ceil(file.size / chunkSize);\r
+\r
+                               // If chunking is disabled then upload the whole file in one huge chunk\r
+                               if (!chunking) {\r
+                                       chunkSize = file.size;\r
+                                       chunks = 1;\r
+                               }\r
+\r
+                               // If file is png or jpeg and resize is configured then resize it\r
+                               if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) {\r
+                                       blobs[file.id] = scaleImage(blobs[file.id], resize.width, resize.height, resize.quality || 90, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg');\r
+                               }\r
+\r
+                               file.size = blobs[file.id].length;\r
+\r
+                               function uploadNextChunk() {\r
+                                       var req, curChunkSize, multipart = up.settings.multipart, multipartLength = 0, reqArgs = {name : file.target_name || file.name};\r
+\r
+                                       // Sends the binary blob multipart encoded or raw depending on config\r
+                                       function sendBinaryBlob(blob) {\r
+                                               var builder, boundary = '----pluploadboundary' + plupload.guid(), dashdash = '--', crlf = '\r\n', multipartBlob;\r
+\r
+                                               // Build multipart request\r
+                                               if (multipart) {\r
+                                                       req.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);\r
+                                                       builder = google.gears.factory.create('beta.blobbuilder');\r
+\r
+                                                       // Append mutlipart parameters\r
+                                                       plupload.each(up.settings.multipart_params, function(value, name) {\r
+                                                               builder.append(\r
+                                                                       dashdash + boundary + crlf +\r
+                                                                       'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf\r
+                                                               );\r
+\r
+                                                               builder.append(value + crlf);\r
+                                                       });\r
+\r
+                                                       // Add file header\r
+                                                       builder.append(\r
+                                                               dashdash + boundary + crlf +\r
+                                                               'Content-Disposition: form-data; name="' + up.settings.file_data_name + '"; filename="' + file.name + '"' + crlf +\r
+                                                               'Content-Type: application/octet-stream' + crlf + crlf\r
+                                                       );\r
+\r
+                                                       // Add file data\r
+                                                       builder.append(blob);\r
+\r
+                                                       // Add footer\r
+                                                       builder.append(crlf + dashdash + boundary + dashdash + crlf);\r
+                                                       multipartBlob = builder.getAsBlob();\r
+                                                       multipartLength = multipartBlob.length - blob.length;\r
+                                                       blob = multipartBlob;\r
+                                               }\r
+\r
+                                               // Send blob or multipart blob depending on config\r
+                                               req.send(blob);\r
+                                       }\r
+\r
+                                       // File upload finished\r
+                                       if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {\r
+                                               return;\r
+                                       }\r
+\r
+                                       // Only add chunking args if needed\r
+                                       if (chunking) {\r
+                                               reqArgs.chunk = chunk;\r
+                                               reqArgs.chunks = chunks;\r
+                                       }\r
+\r
+                                       // Setup current chunk size\r
+                                       curChunkSize = Math.min(chunkSize, file.size - (chunk  * chunkSize));\r
+\r
+                                       req = google.gears.factory.create('beta.httprequest');\r
+                                       req.open('POST', plupload.buildUrl(up.settings.url, reqArgs));\r
+\r
+                                       // Add disposition and type if multipart is disabled\r
+                                       if (!multipart) {\r
+                                               req.setRequestHeader('Content-Disposition', 'attachment; filename="' + file.name + '"');\r
+                                               req.setRequestHeader('Content-Type', 'application/octet-stream');\r
+                                       }\r
+\r
+                                       // Set custom headers\r
+                                       plupload.each(up.settings.headers, function(value, name) {\r
+                                               req.setRequestHeader(name, value);\r
+                                       });\r
+\r
+                                       req.upload.onprogress = function(progress) {\r
+                                               file.loaded = loaded + progress.loaded - multipartLength;\r
+                                               up.trigger('UploadProgress', file);\r
+                                       };\r
+\r
+                                       req.onreadystatechange = function() {\r
+                                               var chunkArgs;\r
+\r
+                                               if (req.readyState == 4) {\r
+                                                       if (req.status == 200) {\r
+                                                               chunkArgs = {\r
+                                                                       chunk : chunk,\r
+                                                                       chunks : chunks,\r
+                                                                       response : req.responseText,\r
+                                                                       status : req.status\r
+                                                               };\r
+\r
+                                                               up.trigger('ChunkUploaded', file, chunkArgs);\r
+\r
+                                                               // Stop upload\r
+                                                               if (chunkArgs.cancelled) {\r
+                                                                       file.status = plupload.FAILED;\r
+                                                                       return;\r
+                                                               }\r
+\r
+                                                               loaded += curChunkSize;\r
+\r
+                                                               if (++chunk >= chunks) {\r
+                                                                       file.status = plupload.DONE;\r
+                                                                       up.trigger('FileUploaded', file, {\r
+                                                                               response : req.responseText,\r
+                                                                               status : req.status\r
+                                                                       });\r
+                                                               } else {\r
+                                                                       uploadNextChunk();\r
+                                                               }\r
+                                                       } else {\r
+                                                               up.trigger('Error', {\r
+                                                                       code : plupload.HTTP_ERROR,\r
+                                                                       message : 'HTTP Error.',\r
+                                                                       file : file,\r
+                                                                       chunk : chunk,\r
+                                                                       chunks : chunks,\r
+                                                                       status : req.status\r
+                                                               });\r
+                                                       }\r
+                                               }\r
+                                       };\r
+\r
+                                       if (chunk < chunks) {\r
+                                               sendBinaryBlob(blobs[file.id].slice(chunk * chunkSize, curChunkSize));\r
+                                       }\r
+                               }\r
+\r
+                               // Start uploading chunks\r
+                               uploadNextChunk();\r
+                       });\r
+\r
+                       callback({success : true});\r
+               }\r
+       });\r
+})(plupload);\r
diff --git a/horde/js/plupload/plupload.html4.js b/horde/js/plupload/plupload.html4.js
new file mode 100644 (file)
index 0000000..1106d2a
--- /dev/null
@@ -0,0 +1,334 @@
+/**\r
+ * plupload.html4.js\r
+ *\r
+ * Copyright 2010, Ryan Demmer\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, window:false */\r
+\r
+(function(plupload) {\r
+       /**\r
+        * HTML4 implementation. This runtime has no special features it uses an form that posts files into an hidden iframe.\r
+        *\r
+        * @static\r
+        * @class plupload.runtimes.Html4\r
+        * @extends plupload.Runtime\r
+        */\r
+       plupload.runtimes.Html4 = plupload.addRuntime("html4", {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               getFeatures : function() {\r
+                       // Only multipart feature\r
+                       return {\r
+                               multipart: true\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Initializes the upload runtime.\r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               init : function(uploader, callback) {\r
+                       var iframefiles = {}, form, iframe;\r
+\r
+                       function addSelectedFiles(element) {\r
+                               var file, i, files = [], id, name;\r
+\r
+                               name = element.value.replace(/\\/g, '/');\r
+                               name = name.substring(name.length, name.lastIndexOf('/')+1);\r
+\r
+                               // Store away gears blob internally\r
+                               id = plupload.guid();\r
+\r
+                               // Expose id, name and size\r
+                               file = new plupload.File(id, name);\r
+\r
+                               iframefiles[id] = file;\r
+\r
+                               file.input = element;\r
+                               files.push(file);\r
+\r
+                               // Fire FilesAdded event\r
+                               if (files.length) {\r
+                                       uploader.trigger("FilesAdded", files);\r
+                               }\r
+                       }\r
+\r
+                       uploader.bind("Init", function(up) {\r
+                               var forms, inputContainer, input, mimes = [], i, y,\r
+                                       filters = up.settings.filters, ext, type, IE = /MSIE/.test(navigator.userAgent),\r
+                                       url = "javascript", bgcolor, container = document.body, node;\r
+\r
+                               if (uploader.settings.container) {\r
+                                       container = document.getElementById(uploader.settings.container);\r
+                                       container.style.position = 'relative';\r
+                               }\r
+\r
+                               // Find existing form\r
+                               form = (typeof up.settings.form == 'string') ? document.getElementById(up.settings.form) : up.settings.form;\r
+                               if (!form) {\r
+                                       node = document.getElementById(uploader.settings.browse_button);\r
+                                       for (; node; node = node.parentNode) {\r
+                                               if (node.nodeName == 'FORM') {\r
+                                                       form = node;\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                               // If no form set, create a new one\r
+                               if (!form) {\r
+                                       // Create a form and set it as inline so it doesn't mess up any layout\r
+                                       form = document.createElement("form");\r
+                                       form.style.display = 'inline';\r
+\r
+                                       // Wrap browse button in empty form\r
+                                       node = document.getElementById(uploader.settings.container);\r
+                                       node.parentNode.insertBefore(form, node);\r
+                                       form.appendChild(node);\r
+                               }\r
+\r
+                               // Force the form into post and multipart\r
+                               form.setAttribute('method', 'post');\r
+                               form.setAttribute('enctype', 'multipart/form-data');\r
+\r
+                               // Append mutlipart parameters\r
+                               plupload.each(up.settings.multipart_params, function(value, name) {\r
+                                       var input = document.createElement('input');\r
+\r
+                                       plupload.extend(input, {\r
+                                               type : 'hidden',\r
+                                               name : name,\r
+                                               value : value\r
+                                       });\r
+\r
+                                       form.appendChild(input);\r
+                               });\r
+\r
+                               iframe = document.createElement('iframe');\r
+                               iframe.setAttribute('src', url + ':""'); // javascript:"" for HTTPS issue on IE6, uses a variable to make an ignore for jslint\r
+                               iframe.setAttribute('name', up.id + '_iframe');\r
+                               iframe.setAttribute('id', up.id + '_iframe');\r
+                               iframe.style.display = 'none';\r
+\r
+                               // Add IFrame onload event\r
+                               plupload.addEvent(iframe, 'load', function(e){\r
+                                       var n = e.target, file = uploader.currentfile, el;\r
+\r
+                                       try {\r
+                                               el = n.contentWindow.document || n.contentDocument || window.frames[n.id].document;\r
+                                       } catch (ex) {\r
+                                               // Probably a permission denied error\r
+                                               up.trigger('Error', {\r
+                                                       code : plupload.SECURITY_ERROR,\r
+                                                       message : 'Security error.',\r
+                                                       file : file\r
+                                               });\r
+\r
+                                               return;\r
+                                       }\r
+\r
+                                       // Return on first load\r
+                                       if (el.location.href == 'about:blank' || !file) {\r
+                                               return;\r
+                                       }\r
+\r
+                                       // Get result\r
+                                       var result = el.documentElement.innerText || el.documentElement.textContent;\r
+\r
+                                       // Assume no error\r
+                                       if (result != '') {\r
+                                               file.status = plupload.DONE;\r
+                                               file.loaded = 1025;\r
+                                               file.percent = 100;\r
+\r
+                                               // Remove input element\r
+                                               if (file.input) {\r
+                                                       file.input.removeAttribute('name');\r
+                                               }\r
+\r
+                                               up.trigger('UploadProgress', file);\r
+                                               up.trigger('FileUploaded', file, {\r
+                                                       response : result\r
+                                               });\r
+\r
+                                               // Reset action and target\r
+                                               if (form.tmpAction) {\r
+                                                       form.setAttribute("action", form.tmpAction);\r
+                                               }\r
+\r
+                                               if (form.tmpTarget) {\r
+                                                       form.setAttribute("target", form.tmpTarget);\r
+                                               }\r
+                                       }\r
+                               });\r
+\r
+                               // append iframe to form\r
+                               form.appendChild(iframe);\r
+\r
+                               // Change iframe name\r
+                               if (IE) {\r
+                                       window.frames[iframe.id].name = iframe.name;\r
+                               }\r
+\r
+                               // Create container for iframe\r
+                               inputContainer = document.createElement('div');\r
+                               inputContainer.id = up.id + '_iframe_container';\r
+\r
+                               // Convert extensions to mime types list\r
+                               for (i = 0; i < filters.length; i++) {\r
+                                       ext = filters[i].extensions.split(/,/);\r
+\r
+                                       for (y = 0; y < ext.length; y++) {\r
+                                               type = plupload.mimeTypes[ext[y]];\r
+\r
+                                               if (type) {\r
+                                                       mimes.push(type);\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                               // Set container styles\r
+                               plupload.extend(inputContainer.style, {\r
+                                       position : 'absolute',\r
+                                       background : 'transparent',\r
+                                       width : '100px',\r
+                                       height : '100px',\r
+                                       overflow : 'hidden',\r
+                                       zIndex : 99999,\r
+                                       opacity : 0\r
+                               });\r
+\r
+                               // Show the container if shim_bgcolor is specified\r
+                               bgcolor = uploader.settings.shim_bgcolor;\r
+                               if (bgcolor) {\r
+                                       plupload.extend(inputContainer.style, {\r
+                                               background : bgcolor,\r
+                                               opacity : 1\r
+                                       });\r
+                               }\r
+\r
+                               // set container class\r
+                               inputContainer.className = 'plupload_iframe';\r
+\r
+                               // Append to form\r
+                               container.appendChild(inputContainer);\r
+\r
+                               // Create an input element\r
+                               function createInput() {\r
+                                       // Create element and set attributes\r
+                                       input = document.createElement('input');\r
+                                       input.setAttribute('type', 'file');\r
+                                       input.setAttribute('accept', mimes.join(','));\r
+                                       input.setAttribute('size', 1);\r
+\r
+                                       // set input styles\r
+                                       plupload.extend(input.style, {\r
+                                               width : '100%',\r
+                                               height : '100%',\r
+                                               opacity : 0\r
+                                       });\r
+\r
+                                       // no opacity in IE\r
+                                       if (IE) {\r
+                                               plupload.extend(input.style, {\r
+                                                       filter : "alpha(opacity=0)"\r
+                                               });\r
+                                       }\r
+\r
+                                       // add change event\r
+                                       plupload.addEvent(input, 'change', function(e) {\r
+                                               var n = e.target;\r
+\r
+                                               if (n.value) {\r
+                                                       // Create next input\r
+                                                       createInput();\r
+                                                       n.style.display = 'none';\r
+                                                       addSelectedFiles(n);\r
+                                               }\r
+                                       });\r
+\r
+                                       // append to container\r
+                                       inputContainer.appendChild(input);\r
+                                       return true;\r
+                               }\r
+\r
+                               // Create input element\r
+                               createInput();\r
+                       });\r
+\r
+                       // Refresh button\r
+                       uploader.bind("Refresh", function(up) {\r
+                               var browseButton, browsePos, browseSize;\r
+\r
+                               browseButton = document.getElementById(uploader.settings.browse_button);\r
+                               browsePos = plupload.getPos(browseButton, document.getElementById(up.settings.container));\r
+                               browseSize = plupload.getSize(browseButton);\r
+\r
+                               plupload.extend(document.getElementById(uploader.id + '_iframe_container').style, {\r
+                                       top : browsePos.y + 'px',\r
+                                       left : browsePos.x + 'px',\r
+                                       width : browseSize.w + 'px',\r
+                                       height : browseSize.h + 'px'\r
+                               });\r
+                       });\r
+\r
+                       // Upload file\r
+                       uploader.bind("UploadFile", function(up, file) {\r
+                               // File upload finished\r
+                               if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {\r
+                                       return;\r
+                               }\r
+\r
+                               // No input element so set error\r
+                               if (!file.input) {\r
+                                       file.status = plupload.ERROR;\r
+                                       return;\r
+                               }\r
+\r
+                               // Set input element name attribute which allows it to be submitted\r
+                               file.input.setAttribute('name', up.settings.file_data_name);\r
+\r
+                               // Store action\r
+                               form.tmpAction = form.getAttribute("action");\r
+                               form.setAttribute("action", plupload.buildUrl(up.settings.url, {name : file.target_name || file.name}));\r
+\r
+                               // Store Target\r
+                               form.tmpTarget = form.getAttribute("target");\r
+                               form.setAttribute("target", iframe.name);\r
+\r
+                               // set current file\r
+                               this.currentfile = file;\r
+\r
+                               form.submit();\r
+                       });\r
+\r
+                       // Remove files\r
+                       uploader.bind("FilesRemoved", function(up, files) {\r
+                               var i, n;\r
+\r
+                               for (i = 0; i < files.length; i++) {\r
+                                       n = files[i].input;\r
+\r
+                                       // Remove input element\r
+                                       if (n) {\r
+                                               n.parentNode.removeChild(n);\r
+                                       }\r
+                               }\r
+                       });\r
+\r
+                       callback({success : true});\r
+               }\r
+       });\r
+})(plupload);\r
diff --git a/horde/js/plupload/plupload.html5.js b/horde/js/plupload/plupload.html5.js
new file mode 100644 (file)
index 0000000..91706f2
--- /dev/null
@@ -0,0 +1,342 @@
+/**\r
+ * plupload.html5.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, File:false, window:false, atob:false */\r
+\r
+(function(plupload) {\r
+       function scaleImage(image_data_url, max_width, max_height, mime, callback) {\r
+               var canvas, context, img, data, scale;\r
+\r
+               // Setup canvas and context\r
+               canvas = document.createElement("canvas");\r
+               canvas.style.display = 'none';\r
+               document.body.appendChild(canvas);\r
+               context = canvas.getContext('2d');\r
+\r
+               // Load image\r
+               img = new Image();\r
+               img.onload = function() {\r
+                       var width, height, percentage;\r
+\r
+                       scale = Math.min(max_width / img.width, max_height / img.height);\r
+\r
+                       if (scale < 1) {\r
+                               width = Math.round(img.width * scale);\r
+                               height = Math.round(img.height * scale);\r
+                       } else {\r
+                               width = img.width;\r
+                               height = img.height;\r
+                       }\r
+\r
+                       // Scale image and canvas\r
+                       canvas.width = width;\r
+                       canvas.height = height;\r
+                       context.drawImage(img, 0, 0, width, height);\r
+\r
+                       // Remove data prefix information and grab the base64 encoded data and decode it\r
+                       data = canvas.toDataURL(mime);\r
+                       data = data.substring(data.indexOf('base64,') + 7);\r
+                       data = atob(data);\r
+\r
+                       // Remove canvas and execute callback with decoded image data\r
+                       canvas.parentNode.removeChild(canvas);\r
+                       callback({success : true, data : data});\r
+               };\r
+\r
+               img.src = image_data_url;\r
+       }\r
+\r
+       /**\r
+        * HMTL5 implementation. This runtime supports these features: dragdrop, jpgresize, pngresize.\r
+        *\r
+        * @static\r
+        * @class plupload.runtimes.Html5\r
+        * @extends plupload.Runtime\r
+        */\r
+       plupload.runtimes.Html5 = plupload.addRuntime("html5", {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               getFeatures : function() {\r
+                       var xhr, hasXhrSupport, hasProgress, dataAccessSupport, sliceSupport;\r
+\r
+                       hasXhrSupport = hasProgress = dataAccessSupport = sliceSupport = false;\r
+\r
+                       if (window.XMLHttpRequest) {\r
+                               xhr = new XMLHttpRequest();\r
+                               hasProgress = !!xhr.upload;\r
+                               hasXhrSupport = !!(xhr.sendAsBinary || xhr.upload);\r
+                       }\r
+\r
+                       // Check for support for various features\r
+                       if (hasXhrSupport) {\r
+                               dataAccessSupport = !!(File && File.prototype.getAsDataURL);\r
+                               sliceSupport = !!(File && File.prototype.slice);\r
+                       }\r
+\r
+                       return {\r
+                               // Detect drag/drop file support by sniffing, will try to find a better way\r
+                               html5: hasXhrSupport, // This is a special one that we check inside the init call\r
+                               dragdrop: window.mozInnerScreenX !== undefined || sliceSupport,\r
+                               jpgresize: dataAccessSupport,\r
+                               pngresize: dataAccessSupport,\r
+                               multipart: dataAccessSupport,\r
+                               progress: hasProgress\r
+                               // todo: Implement chunking support\r
+                               // chunking: sliceSupport || dataAccessSupport\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Initializes the upload runtime.\r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               init : function(uploader, callback) {\r
+                       var html5files = {};\r
+\r
+                       function addSelectedFiles(native_files) {\r
+                               var file, i, files = [], id;\r
+\r
+                               // Add the selected files to the file queue\r
+                               for (i = 0; i < native_files.length; i++) {\r
+                                       file = native_files[i];\r
+\r
+                                       // Store away gears blob internally\r
+                                       id = plupload.guid();\r
+                                       html5files[id] = file;\r
+\r
+                                       // Expose id, name and size\r
+                                       files.push(new plupload.File(id, file.fileName, file.fileSize));\r
+                               }\r
+\r
+                               // Trigger FilesAdded event if we added any\r
+                               if (files.length) {\r
+                                       uploader.trigger("FilesAdded", files);\r
+                               }\r
+                       }\r
+\r
+                       // No HTML5 upload support\r
+                       if (!this.getFeatures().html5) {\r
+                               callback({success : false});\r
+                               return;\r
+                       }\r
+\r
+                       uploader.bind("Init", function(up) {\r
+                               var inputContainer, mimes = [], i, y, filters = up.settings.filters, ext, type, container = document.body;\r
+\r
+                               // Create input container and insert it at an absolute position within the browse button\r
+                               inputContainer = document.createElement('div');\r
+                               inputContainer.id = up.id + '_html5_container';\r
+\r
+                               // Convert extensions to mime types list\r
+                               for (i = 0; i < filters.length; i++) {\r
+                                       ext = filters[i].extensions.split(/,/);\r
+\r
+                                       for (y = 0; y < ext.length; y++) {\r
+                                               type = plupload.mimeTypes[ext[y]];\r
+\r
+                                               if (type) {\r
+                                                       mimes.push(type);\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                               plupload.extend(inputContainer.style, {\r
+                                       position : 'absolute',\r
+                                       background : uploader.settings.shim_bgcolor || 'transparent',\r
+                                       width : '100px',\r
+                                       height : '100px',\r
+                                       overflow : 'hidden',\r
+                                       zIndex : 99999,\r
+                                       opacity : uploader.settings.shim_bgcolor ? '' : 0 // Force transparent if bgcolor is undefined\r
+                               });\r
+\r
+                               inputContainer.className = 'plupload html5';\r
+\r
+                               if (uploader.settings.container) {\r
+                                       container = document.getElementById(uploader.settings.container);\r
+                                       container.style.position = 'relative';\r
+                               }\r
+\r
+                               container.appendChild(inputContainer);\r
+\r
+                               // Insert the input inide the input container\r
+                               inputContainer.innerHTML = '<input id="' + uploader.id + '_html5" ' +\r
+                                                                                       'style="width:100%;" type="file" accept="' + mimes.join(',') + '" ' +\r
+                                                                                       (uploader.settings.multi_selection ? 'multiple="multiple"' : '') + ' />';\r
+\r
+                               document.getElementById(uploader.id + '_html5').onchange = function() {\r
+                                       // Add the selected files from file input\r
+                                       addSelectedFiles(this.files);\r
+\r
+                                       // Clearing the value enables the user to select the same file again if they want to\r
+                                       this.value = '';\r
+                               };\r
+                       });\r
+\r
+                       // Add drop handler\r
+                       uploader.bind("PostInit", function() {\r
+                               var dropElm = document.getElementById(uploader.settings.drop_element);\r
+\r
+                               if (dropElm) {\r
+                                       // Block browser default drag over\r
+                                       plupload.addEvent(dropElm, 'dragover', function(e) {\r
+                                               e.preventDefault();\r
+                                       });\r
+\r
+                                       // Attach drop handler and grab files\r
+                                       plupload.addEvent(dropElm, 'drop', function(e) {\r
+                                               var dataTransfer = e.dataTransfer;\r
+\r
+                                               // Add dropped files\r
+                                               if (dataTransfer && dataTransfer.files) {\r
+                                                       addSelectedFiles(dataTransfer.files);\r
+                                               }\r
+\r
+                                               e.preventDefault();\r
+                                       });\r
+                               }\r
+                       });\r
+\r
+                       uploader.bind("Refresh", function(up) {\r
+                               var browseButton, browsePos, browseSize;\r
+\r
+                               browseButton = document.getElementById(uploader.settings.browse_button);\r
+                               browsePos = plupload.getPos(browseButton, document.getElementById(up.settings.container));\r
+                               browseSize = plupload.getSize(browseButton);\r
+\r
+                               plupload.extend(document.getElementById(uploader.id + '_html5_container').style, {\r
+                                       top : browsePos.y + 'px',\r
+                                       left : browsePos.x + 'px',\r
+                                       width : browseSize.w + 'px',\r
+                                       height : browseSize.h + 'px'\r
+                               });\r
+                       });\r
+\r
+                       uploader.bind("UploadFile", function(up, file) {\r
+                               var xhr = new XMLHttpRequest(), upload = xhr.upload, resize = up.settings.resize, nativeFile, multipartSize = 0;\r
+\r
+                               // Sends the binary blob to server and multipart encodes it if needed this code will\r
+                               // only be executed on Gecko since it's currently the only browser that supports direct file access\r
+                               function sendBinaryBlob(blob) {\r
+                                       var boundary = '----pluploadboundary' + plupload.guid(), dashdash = '--', crlf = '\r\n', multipartBlob = '';\r
+\r
+                                       // Build multipart request\r
+                                       if (up.settings.multipart) {\r
+                                               xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);\r
+\r
+                                               // Append mutlipart parameters\r
+                                               plupload.each(up.settings.multipart_params, function(value, name) {\r
+                                                       multipartBlob += dashdash + boundary + crlf +\r
+                                                               'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf;\r
+\r
+                                                       multipartBlob += value + crlf;\r
+                                               });\r
+\r
+                                               // Build RFC2388 blob\r
+                                               multipartBlob += dashdash + boundary + crlf +\r
+                                                       'Content-Disposition: form-data; name="' + up.settings.file_data_name + '"; filename="' + file.name + '"' + crlf +\r
+                                                       'Content-Type: application/octet-stream' + crlf + crlf +\r
+                                                       blob + crlf +\r
+                                                       dashdash + boundary + dashdash + crlf;\r
+\r
+                                               multipartSize = multipartBlob.length - blob.length;\r
+                                               blob = multipartBlob;\r
+                                       }\r
+\r
+                                       // Send blob or multipart blob depending on config\r
+                                       xhr.sendAsBinary(blob);\r
+                               }\r
+\r
+                               // File upload finished\r
+                               if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {\r
+                                       return;\r
+                               }\r
+\r
+                               // Do we have upload progress support\r
+                               if (upload) {\r
+                                       upload.onprogress = function(e) {\r
+                                               file.loaded = e.loaded - multipartSize;\r
+                                               up.trigger('UploadProgress', file);\r
+                                       };\r
+                               }\r
+\r
+                               xhr.onreadystatechange = function() {\r
+                                       var httpStatus;\r
+\r
+                                       if (xhr.readyState == 4) {\r
+                                               // Getting the HTTP status might fail on some Gecko versions\r
+                                               try {\r
+                                                       httpStatus = xhr.status;\r
+                                               } catch (ex) {\r
+                                                       httpStatus = 0;\r
+                                               }\r
+\r
+                                               file.status = plupload.DONE;\r
+                                               file.loaded = file.size;\r
+                                               up.trigger('UploadProgress', file);\r
+                                               up.trigger('FileUploaded', file, {\r
+                                                       response : xhr.responseText,\r
+                                                       status : httpStatus\r
+                                               });\r
+\r
+                                               // Is error status\r
+                                               if (httpStatus >= 400) {\r
+                                                       up.trigger('Error', {\r
+                                                               code : plupload.HTTP_ERROR,\r
+                                                               message : 'HTTP Error.',\r
+                                                               file : file,\r
+                                                               status : httpStatus\r
+                                                       });\r
+                                               }\r
+                                       }\r
+                               };\r
+\r
+                               xhr.open("post", plupload.buildUrl(up.settings.url, {name : file.target_name || file.name}), true);\r
+                               xhr.setRequestHeader('Content-Type', 'application/octet-stream');\r
+\r
+                               // Set custom headers\r
+                               plupload.each(up.settings.headers, function(value, name) {\r
+                                       xhr.setRequestHeader(name, value);\r
+                               });\r
+\r
+                               nativeFile = html5files[file.id]; \r
+\r
+                               if (xhr.sendAsBinary) {\r
+                                       // Resize image if it's a supported format and resize is enabled\r
+                                       if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) {\r
+                                               scaleImage(nativeFile.getAsDataURL(), resize.width, resize.height, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg', function(res) {\r
+                                                       // If it was scaled send the scaled image if it failed then\r
+                                                       // send the raw image and let the server do the scaling\r
+                                                       if (res.success) {\r
+                                                               file.size = res.data.length;\r
+                                                               sendBinaryBlob(res.data);\r
+                                                       } else {\r
+                                                               sendBinaryBlob(nativeFile.getAsBinary());\r
+                                                       }\r
+                                               });\r
+                                       } else {\r
+                                               sendBinaryBlob(nativeFile.getAsBinary());\r
+                                       }\r
+                               } else {\r
+                                       xhr.send(nativeFile);\r
+                               }\r
+                       });\r
+\r
+                       callback({success : true});\r
+               }\r
+       });\r
+})(plupload);\r
diff --git a/horde/js/plupload/plupload.js b/horde/js/plupload/plupload.js
new file mode 100644 (file)
index 0000000..7b7379c
--- /dev/null
@@ -0,0 +1,1319 @@
+/**\r
+ * plupload.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global window:false, escape:false */\r
+\r
+(function() {\r
+       var count = 0, runtimes = [], i18n = {}, mimes = {},\r
+               xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'},\r
+               xmlEncodeRegExp = /[<>&\"\']/g, undef;\r
+\r
+       // IE W3C like event funcs\r
+       function preventDefault() {\r
+               this.returnValue = false;\r
+       }\r
+\r
+       function stopPropagation() {\r
+               this.cancelBubble = true;\r
+       }\r
+\r
+       // Parses the default mime types string into a mimes lookup map\r
+       (function(mime_data) {\r
+               var items = mime_data.split(/,/), i, y, ext;\r
+\r
+               for (i = 0; i < items.length; i += 2) {\r
+                       ext = items[i + 1].split(/ /);\r
+\r
+                       for (y = 0; y < ext.length; y++) {\r
+                               mimes[ext[y]] = items[i];\r
+                       }\r
+               }\r
+       })(\r
+               "application/msword,doc dot," +\r
+               "application/pdf,pdf," +\r
+               "application/pgp-signature,pgp," +\r
+               "application/postscript,ps ai eps," +\r
+               "application/rtf,rtf," +\r
+               "application/vnd.ms-excel,xls xlb," +\r
+               "application/vnd.ms-powerpoint,ppt pps pot," +\r
+               "application/zip,zip," +\r
+               "application/x-shockwave-flash,swf swfl," +\r
+               "application/vnd.openxmlformats,docx pptx xlsx," +\r
+               "audio/mpeg,mpga mpega mp2 mp3," +\r
+               "audio/x-wav,wav," +\r
+               "image/bmp,bmp," +\r
+               "image/gif,gif," +\r
+               "image/jpeg,jpeg jpg jpe," +\r
+               "image/png,png," +\r
+               "image/svg+xml,svg svgz," +\r
+               "image/tiff,tiff tif," +\r
+               "text/html,htm html xhtml," +\r
+               "text/rtf,rtf," +\r
+               "video/mpeg,mpeg mpg mpe," +\r
+               "video/quicktime,qt mov," +\r
+               "video/x-flv,flv," +\r
+               "video/vnd.rn-realvideo,rv," +\r
+               "text/plain,asc txt text diff log," +\r
+               "application/octet-stream,exe"\r
+       );\r
+\r
+       /**\r
+        * Plupload class with some global constants and functions.\r
+        *\r
+        * @example\r
+        * // Encode entities\r
+        * console.log(plupload.xmlEncode("My string &lt;&gt;"));\r
+        *\r
+        * // Generate unique id\r
+        * console.log(plupload.guid());\r
+        *\r
+        * @static\r
+        * @class plupload\r
+        */\r
+       var plupload = {\r
+               /**\r
+                * Inital state of the queue and also the state ones it's finished all it's uploads.\r
+                *\r
+                * @property STOPPED\r
+                * @final\r
+                */\r
+               STOPPED : 1,\r
+\r
+               /**\r
+                * Upload process is running\r
+                *\r
+                * @property STARTED\r
+                * @final\r
+                */\r
+               STARTED : 2,\r
+\r
+               /**\r
+                * File is queued for upload\r
+                *\r
+                * @property QUEUED\r
+                * @final\r
+                */\r
+               QUEUED : 1,\r
+\r
+               /**\r
+                * File is being uploaded\r
+                *\r
+                * @property UPLOADING\r
+                * @final\r
+                */\r
+               UPLOADING : 2,\r
+\r
+               /**\r
+                * File has failed to be uploaded\r
+                *\r
+                * @property FAILED\r
+                * @final\r
+                */\r
+               FAILED : 4,\r
+\r
+               /**\r
+                * File has been uploaded successfully\r
+                *\r
+                * @property DONE\r
+                * @final\r
+                */\r
+               DONE : 5,\r
+\r
+               // Error constants used by the Error event\r
+\r
+               /**\r
+                * Generic error for example if an exception is thrown inside Silverlight.\r
+                *\r
+                * @property GENERIC_ERROR\r
+                * @final\r
+                */\r
+               GENERIC_ERROR : -100,\r
+\r
+               /**\r
+                * HTTP transport error. For example if the server produces a HTTP status other than 200.\r
+                *\r
+                * @property HTTP_ERROR\r
+                * @final\r
+                */\r
+               HTTP_ERROR : -200,\r
+\r
+               /**\r
+                * Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.\r
+                *\r
+                * @property IO_ERROR\r
+                * @final\r
+                */\r
+               IO_ERROR : -300,\r
+\r
+               /**\r
+                * Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.\r
+                *\r
+                * @property SECURITY_ERROR\r
+                * @final\r
+                */\r
+               SECURITY_ERROR : -400,\r
+\r
+               /**\r
+                * Initialization error. Will be triggered if no runtime was initialized.\r
+                *\r
+                * @property INIT_ERROR\r
+                * @final\r
+                */\r
+               INIT_ERROR : -500,\r
+\r
+               /**\r
+                * File size error. If the user selects a file that is to large it will be blocked and an error of this type will be triggered.\r
+                *\r
+                * @property FILE_SIZE_ERROR\r
+                * @final\r
+                */\r
+               FILE_SIZE_ERROR : -600,\r
+\r
+               /**\r
+                * File extension error. If the user selects a file that isn't valid according to the filters setting.\r
+                *\r
+                * @property FILE_EXTENSION_ERROR\r
+                * @final\r
+                */\r
+               FILE_EXTENSION_ERROR : -700,\r
+\r
+               /**\r
+                * Mime type lookup table.\r
+                *\r
+                * @property mimeTypes\r
+                * @type Object\r
+                * @final\r
+                */\r
+               mimeTypes : mimes,\r
+\r
+               /**\r
+                * Extends the specified object with another object.\r
+                *\r
+                * @method extend\r
+                * @param {Object} target Object to extend.\r
+                * @param {Object..} obj Multiple objects to extend with.\r
+                * @return {Object} Same as target, the extended object.\r
+                */\r
+               extend : function(target) {\r
+                       plupload.each(arguments, function(arg, i) {\r
+                               if (i > 0) {\r
+                                       plupload.each(arg, function(value, key) {\r
+                                               target[key] = value;\r
+                                       });\r
+                               }\r
+                       });\r
+\r
+                       return target;\r
+               },\r
+\r
+               /**\r
+                * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.\r
+                *\r
+                * @method cleanName\r
+                * @param {String} s String to clean up.\r
+                * @return {String} Cleaned string.\r
+                */\r
+               cleanName : function(name) {\r
+                       var i, lookup;\r
+\r
+                       // Replace diacritics\r
+                       lookup = [\r
+                               /[\300-\306]/g, 'A', /[\340-\346]/g, 'a',\r
+                               /\307/g, 'C', /\347/g, 'c',\r
+                               /[\310-\313]/g, 'E', /[\350-\353]/g, 'e',\r
+                               /[\314-\317]/g, 'I', /[\354-\357]/g, 'i',\r
+                               /\321/g, 'N', /\361/g, 'n',\r
+                               /[\322-\330]/g, 'O', /[\362-\370]/g, 'o',\r
+                               /[\331-\334]/g, 'U', /[\371-\374]/g, 'u'\r
+                       ];\r
+\r
+                       for (i = 0; i < lookup.length; i += 2) {\r
+                               name = name.replace(lookup[i], lookup[i + 1]);\r
+                       }\r
+\r
+                       // Replace whitespace\r
+                       name = name.replace(/\s+/g, '_');\r
+\r
+                       // Remove anything else\r
+                       name = name.replace(/[^a-z0-9_\-\.]+/gi, '');\r
+\r
+                       return name;\r
+               },\r
+\r
+               /**\r
+                * Adds a specific upload runtime like for example flash or gears.\r
+                *\r
+                * @method addRuntime\r
+                * @param {String} name Runtime name for example flash.\r
+                * @param {Object} obj Object containing init/destroy method.\r
+                */\r
+               addRuntime : function(name, runtime) {\r
+                       runtime.name = name;\r
+                       runtimes[name] = runtime;\r
+                       runtimes.push(runtime);\r
+\r
+                       return runtime;\r
+               },\r
+\r
+               /**\r
+                * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.\r
+                * The only way a user would be able to get the same ID is if the two persons at the same exact milisecond manages\r
+                * to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique.\r
+                * It's more probable for the earth to be hit with an ansteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property\r
+                * to an user unique key.\r
+                *\r
+                * @method guid\r
+                * @return {String} Virtually unique id.\r
+                */\r
+               guid : function() {\r
+                       var guid = new Date().getTime().toString(32), i;\r
+\r
+                       for (i = 0; i < 5; i++) {\r
+                               guid += Math.floor(Math.random() * 65535).toString(32);\r
+                       }\r
+\r
+                       return (plupload.guidPrefix || 'p') + guid + (count++).toString(32);\r
+               },\r
+\r
+               /**\r
+                * Builds a full url out of a base URL and an object with items to append as query string items.\r
+                *\r
+                * @param {String} url Base URL to append query string items to.\r
+                * @param {Object} items Name/value object to serialize as a querystring.\r
+                * @return {String} String with url + serialized query string items.\r
+                */\r
+               buildUrl : function(url, items) {\r
+                       var query = '';\r
+\r
+                       plupload.each(items, function(value, name) {\r
+                               query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);\r
+                       });\r
+\r
+                       if (query) {\r
+                               url += (url.indexOf('?') > 0 ? '&' : '?') + query;\r
+                       }\r
+\r
+                       return url;\r
+               },\r
+\r
+               /**\r
+                * Executes the callback function for each item in array/object. If you return false in the\r
+                * callback it will break the loop.\r
+                *\r
+                * @param {Object} obj Object to iterate.\r
+                * @param {function} callback Callback function to execute for each item.\r
+                */\r
+               each : function(obj, callback) {\r
+                       var length, key, i;\r
+\r
+                       if (obj) {\r
+                               length = obj.length;\r
+\r
+                               if (length === undef) {\r
+                                       // Loop object items\r
+                                       for (key in obj) {\r
+                                               if (obj.hasOwnProperty(key)) {\r
+                                                       if (callback(obj[key], key) === false) {\r
+                                                               return;\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                               } else {\r
+                                       // Loop array items\r
+                                       for (i = 0; i < length; i++) {\r
+                                               if (callback(obj[i], i) === false) {\r
+                                                       return;\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+               },\r
+\r
+               /**\r
+                * Formats the specified number as a size string for example 1024 becomes 1 KB.\r
+                *\r
+                * @method formatSize\r
+                * @param {Number} size Size to format as string.\r
+                * @return {String} Formatted size string.\r
+                */\r
+               formatSize : function(size) {\r
+                       if (size === undef) {\r
+                               return plupload.translate('N/A');\r
+                       }\r
+\r
+                       // MB\r
+                       if (size > 1048576) {\r
+                               return Math.round(size / 1048576, 1) + " MB";\r
+                       }\r
+\r
+                       // KB\r
+                       if (size > 1024) {\r
+                               return Math.round(size / 1024, 1) + " KB";\r
+                       }\r
+\r
+                       return size + " b";\r
+               },\r
+\r
+               /**\r
+                * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.\r
+                *\r
+                * @method getPos\r
+                * @param {Element} node HTML element or element id to get x, y position from.\r
+                * @param {Element} root Optional root element to stop calculations at.\r
+                * @return {object} Absolute position of the specified element object with x, y fields.\r
+                */\r
+                getPos : function(node, root) {\r
+                       var x = 0, y = 0, parent, doc = document, nodeRect, rootRect;\r
+\r
+                       node = node;\r
+                       root = root || doc.body;\r
+\r
+                       // Returns the x, y cordinate for an element on IE 6 and IE 7\r
+                       function getIEPos(node) {\r
+                               var bodyElm, rect, x = 0, y = 0;\r
+\r
+                               if (node) {\r
+                                       rect = node.getBoundingClientRect();\r
+                                       bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body;\r
+                                       x = rect.left + bodyElm.scrollLeft;\r
+                                       y = rect.top + bodyElm.scrollTop;\r
+                               }\r
+\r
+                               return {\r
+                                       x : x,\r
+                                       y : y\r
+                               };\r
+                       }\r
+\r
+                       // Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode\r
+                       if (node.getBoundingClientRect && (navigator.userAgent.indexOf('MSIE') > 0 && doc.documentMode !== 8)) {\r
+                               nodeRect = getIEPos(node);\r
+                               rootRect = getIEPos(root);\r
+\r
+                               return {\r
+                                       x : nodeRect.x - rootRect.x,\r
+                                       y : nodeRect.y - rootRect.y\r
+                               };\r
+                       }\r
+\r
+                       parent = node;\r
+                       while (parent && parent != root && parent.nodeType) {\r
+                               x += parent.offsetLeft || 0;\r
+                               y += parent.offsetTop || 0;\r
+                               parent = parent.offsetParent;\r
+                       }\r
+\r
+                       parent = node.parentNode;\r
+                       while (parent && parent != root && parent.nodeType) {\r
+                               x -= parent.scrollLeft || 0;\r
+                               y -= parent.scrollTop || 0;\r
+                               parent = parent.parentNode;\r
+                       }\r
+\r
+                       return {\r
+                               x : x,\r
+                               y : y\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Returns the size of the specified node in pixels.\r
+                *\r
+                * @param {Node} node Node to get the size of.\r
+                * @return {Object} Object with a w and h property.\r
+                */\r
+               getSize : function(node) {\r
+                       return {\r
+                               w : node.clientWidth || node.offsetWidth,\r
+                               h : node.clientHeight || node.offsetHeight\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Parses the specified size string into a byte value. For example 10kb becomes 10240.\r
+                *\r
+                * @method parseSize\r
+                * @param {String/Number} size String to parse or number to just pass through.\r
+                * @return {Number} Size in bytes.\r
+                */\r
+               parseSize : function(size) {\r
+                       var mul;\r
+\r
+                       if (typeof(size) == 'string') {\r
+                               size = /^([0-9]+)([mgk]+)$/.exec(size.toLowerCase().replace(/[^0-9mkg]/g, ''));\r
+                               mul = size[2];\r
+                               size = +size[1];\r
+\r
+                               if (mul == 'g') {\r
+                                       size *= 1073741824;\r
+                               }\r
+\r
+                               if (mul == 'm') {\r
+                                       size *= 1048576;\r
+                               }\r
+\r
+                               if (mul == 'k') {\r
+                                       size *= 1024;\r
+                               }\r
+                       }\r
+\r
+                       return size;\r
+               },\r
+\r
+               /**\r
+                * Encodes the specified string.\r
+                *\r
+                * @method xmlEncode\r
+                * @param {String} s String to encode.\r
+                * @return {String} Encoded string.\r
+                */\r
+               xmlEncode : function(str) {\r
+                       return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {\r
+                               return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;\r
+                       }) : str;\r
+               },\r
+\r
+               /**\r
+                * Forces anything into an array.\r
+                *\r
+                * @method toArray\r
+                * @param {Object} obj Object with length field.\r
+                * @return {Array} Array object containing all items.\r
+                */\r
+               toArray : function(obj) {\r
+                       var i, arr = [];\r
+\r
+                       for (i = 0; i < obj.length; i++) {\r
+                               arr[i] = obj[i];\r
+                       }\r
+\r
+                       return arr;\r
+               },\r
+\r
+               /**\r
+                * Extends the language pack object with new items.\r
+                *\r
+                * @param {Object} pack Language pack items to add.\r
+                * @return {Object} Extended language pack object.\r
+                */\r
+               addI18n : function(pack) {\r
+                       return plupload.extend(i18n, pack);\r
+               },\r
+\r
+               /**\r
+                * Translates the specified string by checking for the english string in the language pack lookup.\r
+                *\r
+                * @param {String} str String to look for.\r
+                * @reutrn {String} Translated string or the input string if it wasn't found.\r
+                */\r
+               translate : function(str) {\r
+                       return i18n[str] || str;\r
+               },\r
+\r
+               /**\r
+                * Adds an event handler to the specified object.\r
+                *\r
+                * @param {Object} obj DOM element like object to add handler to.\r
+                * @param {String} name Name to add event listener to.\r
+                * @param {function} callback Function to call when event occurs.\r
+                */\r
+               addEvent : function(obj, name, callback) {\r
+                       if (obj.attachEvent) {\r
+                               obj.attachEvent('on' + name, function() {\r
+                                       var evt = window.event;\r
+\r
+                                       if (!evt.target) {\r
+                                               evt.target = evt.srcElement;\r
+                                       }\r
+\r
+                                       evt.preventDefault = preventDefault;\r
+                                       evt.stopPropagation = stopPropagation;\r
+\r
+                                       callback(evt);\r
+                               });\r
+                       } else if (obj.addEventListener) {\r
+                               obj.addEventListener(name, callback, false);\r
+                       }\r
+               }\r
+       };\r
+\r
+       /**\r
+        * Uploader class, an instance of this class will be created for each upload field.\r
+        *\r
+        * @example\r
+        * var uploader = new plupload.Uploader({\r
+        *     runtimes : 'gears,html5,flash',\r
+        *     browse_button : 'button_id'\r
+        * });\r
+        *\r
+        * uploader.bind('Init', function(up) {\r
+        *     alert('Supports drag/drop: ' + (!!up.features.dragdrop));\r
+        * });\r
+        *\r
+        * uploader.bind('FilesAdded', function(up, files) {\r
+        *     alert('Selected files: ' + files.length);\r
+        * });\r
+        *\r
+        * uploader.bind('QueueChanged', function(up) {\r
+        *     alert('Queued files: ' + uploader.files.length);\r
+        * });\r
+        *\r
+        * uploader.init();\r
+        *\r
+        * @class plupload.Uploader\r
+        */\r
+\r
+       /**\r
+        * Constructs a new uploader instance.\r
+        *\r
+        * @constructor\r
+        * @method Uploader\r
+        * @param {Object} settings Initialization settings, to be used by the uploader instance and runtimes.\r
+        */\r
+       plupload.Uploader = function(settings) {\r
+               var events = {}, total, files = [], fileIndex, startTime;\r
+\r
+               // Inital total state\r
+               total = new plupload.QueueProgress();\r
+\r
+               // Default settings\r
+               settings = plupload.extend({\r
+                       chunk_size : 0,\r
+                       max_file_size : '1gb',\r
+                       multi_selection : true,\r
+                       file_data_name : 'file',\r
+                       filters : []\r
+               }, settings);\r
+\r
+               // Private methods\r
+               function uploadNext() {\r
+                       var file;\r
+\r
+                       if (this.state == plupload.STARTED && fileIndex < files.length) {\r
+                               file = files[fileIndex++];\r
+\r
+                               if (file.status == plupload.QUEUED) {\r
+                                       this.trigger("UploadFile", file);\r
+                               } else {\r
+                                       uploadNext.call(this);\r
+                               }\r
+                       } else {\r
+                               this.stop();\r
+                       }\r
+               }\r
+\r
+               function calc() {\r
+                       var i, file;\r
+\r
+                       // Reset stats\r
+                       total.reset();\r
+\r
+                       // Check status, size, loaded etc on all files\r
+                       for (i = 0; i < files.length; i++) {\r
+                               file = files[i];\r
+\r
+                               if (file.size !== undef) {\r
+                                       total.size += file.size;\r
+                                       total.loaded += file.loaded;\r
+                               } else {\r
+                                       total.size = undef;\r
+                               }\r
+\r
+                               if (file.status == plupload.DONE) {\r
+                                       total.uploaded++;\r
+                               } else if (file.status == plupload.FAILED) {\r
+                                       total.failed++;\r
+                               } else {\r
+                                       total.queued++;\r
+                               }\r
+                       }\r
+\r
+                       // If we couldn't calculate a total file size then use the number of files to calc percent\r
+                       if (total.size === undef) {\r
+                               total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;\r
+                       } else {\r
+                               total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));\r
+                               total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;\r
+                       }\r
+               }\r
+\r
+               // Add public methods\r
+               plupload.extend(this, {\r
+                       /**\r
+                        * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.\r
+                        * These states are controlled by the stop/start methods. The default value is STOPPED.\r
+                        *\r
+                        * @property state\r
+                        * @type Number\r
+                        */\r
+                       state : plupload.STOPPED,\r
+\r
+                       /**\r
+                        * Map of features that are available for the uploader runtime. Features will be filled\r
+                        * before the init event is called, these features can then be used to alter the UI for the end user.\r
+                        * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.\r
+                        *\r
+                        * @property features\r
+                        * @type Object\r
+                        */\r
+                       features : {},\r
+\r
+                       /**\r
+                        * Current upload queue, an array of File instances.\r
+                        *\r
+                        * @property files\r
+                        * @type Array\r
+                        * @see plupload.File\r
+                        */\r
+                       files : files,\r
+\r
+                       /**\r
+                        * Object with name/value settings.\r
+                        *\r
+                        * @property settings\r
+                        * @type Object\r
+                        */\r
+                       settings : settings,\r
+\r
+                       /**\r
+                        * Total progess information. How many files has been uploaded, total percent etc.\r
+                        *\r
+                        * @property total\r
+                        * @type plupload.QueueProgress\r
+                        */\r
+                       total : total,\r
+\r
+                       /**\r
+                        * Unique id for the Uploader instance.\r
+                        *\r
+                        * @property id\r
+                        * @type String\r
+                        */\r
+                       id : plupload.guid(),\r
+\r
+                       /**\r
+                        * Initializes the Uploader instance and adds internal event listeners.\r
+                        *\r
+                        * @method init\r
+                        */\r
+                       init : function() {\r
+                               var self = this, i, runtimeList, a, runTimeIndex = 0, items;\r
+\r
+                               settings.page_url = settings.page_url || document.location.pathname.replace(/\/[^\/]+$/g, '/');\r
+\r
+                               // If url is relative force it absolute to the current page\r
+                               if (!/^(\w+:\/\/|\/)/.test(settings.url)) {\r
+                                       settings.url = settings.page_url + settings.url;\r
+                               }\r
+\r
+                               // Convert settings\r
+                               settings.chunk_size = plupload.parseSize(settings.chunk_size);\r
+                               settings.max_file_size = plupload.parseSize(settings.max_file_size);\r
+\r
+                               // Call preinit function\r
+                               if (settings.preinit) {\r
+                                       settings.preinit(this);\r
+                               }\r
+                \r
+                               // Add files to queue\r
+                               self.bind('FilesAdded', function(up, selected_files) {\r
+                                       var i, file, count = 0, extensionsMap, filters = settings.filters;\r
+\r
+                                       // Convert extensions to map\r
+                                       if (filters && filters.length) {\r
+                                               extensionsMap = {};\r
+\r
+                                               plupload.each(filters, function(filter) {\r
+                                                       plupload.each(filter.extensions.split(/,/), function(ext) {\r
+                                                               extensionsMap[ext.toLowerCase()] = true;\r
+                                                       });\r
+                                               });\r
+                                       }\r
+\r
+                                       for (i = 0; i < selected_files.length; i++) {\r
+                                               file = selected_files[i];\r
+                                               file.loaded = 0;\r
+                                               file.percent = 0;\r
+                                               file.status = plupload.QUEUED;\r
+\r
+                                               // Invalid file extension\r
+                                               if (extensionsMap && !extensionsMap[file.name.toLowerCase().split('.').slice(-1)]) {\r
+                                                       up.trigger('Error', {\r
+                                                               code : plupload.FILE_EXTENSION_ERROR,\r
+                                                               message : 'File extension error.',\r
+                                                               file : file\r
+                                                       });\r
+\r
+                                                       continue;\r
+                                               }\r
+\r
+                                               // Invalid file size\r
+                                               if (file.size !== undef && file.size > settings.max_file_size) {\r
+                                                       up.trigger('Error', {\r
+                                                               code : plupload.FILE_SIZE_ERROR,\r
+                                                               message : 'File size error.',\r
+                                                               file : file\r
+                                                       });\r
+\r
+                                                       continue;\r
+                                               }\r
+\r
+                                               // Add valid file to list\r
+                                               files.push(file);\r
+                                               count++;\r
+                                       }\r
+\r
+                                       // Only trigger QueueChanged event if any files where added\r
+                                       if (count) {\r
+                                               self.trigger("QueueChanged");\r
+                                               self.refresh();\r
+                                       }\r
+                               });\r
+\r
+                               // Generate unique target filenames\r
+                               if (settings.unique_names) {\r
+                                       self.bind("UploadFile", function(up, file) {\r
+                                               file.target_name = file.id + '.tmp';\r
+                                       });\r
+                               }\r
+\r
+                               self.bind('UploadProgress', function(up, file) {\r
+                                       if (file.status == plupload.QUEUED) {\r
+                                               file.status = plupload.UPLOADING;\r
+                                       }\r
+\r
+                                       file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;\r
+                                       calc();\r
+                               });\r
+\r
+                               self.bind('StateChanged', function(up) {\r
+                                       if (up.state == plupload.STARTED) {\r
+                                               // Get start time to calculate bps\r
+                                               startTime = (+new Date());\r
+                                       }\r
+                               });\r
+\r
+                               self.bind('QueueChanged', calc);\r
+\r
+                               self.bind("Error", function(up, err) {\r
+                                       // Set failed status if an error occured on a file\r
+                                       if (err.file) {\r
+                                               err.file.status = plupload.FAILED;\r
+                                               calc();\r
+\r
+                                               // Upload next file but detach it from the error event\r
+                                               // since other custom listeners might want to stop the queue\r
+                                               window.setTimeout(function() {\r
+                                                       uploadNext.call(self);\r
+                                               });\r
+                                       }\r
+                               });\r
+\r
+                               self.bind("FileUploaded", function(up, file) {\r
+                                       file.status = plupload.DONE;\r
+                                       up.trigger('UploadProgress', file);\r
+                                       uploadNext.call(self);\r
+                               });\r
+\r
+                               // Setup runtimeList\r
+                               if (settings.runtimes) {\r
+                                       runtimeList = [];\r
+                                       items = settings.runtimes.split(/\s?,\s?/);\r
+\r
+                                       for (i = 0; i < items.length; i++) {\r
+                                               if (runtimes[items[i]]) {\r
+                                                       runtimeList.push(runtimes[items[i]]);\r
+                                               }\r
+                                       }\r
+                               } else {\r
+                                       runtimeList = runtimes;\r
+                               }\r
+\r
+                               // Call init on each runtime in sequence\r
+                               function callNextInit() {\r
+                                       var runtime = runtimeList[runTimeIndex++], features, requiredFeatures, i;\r
+\r
+                                       if (runtime) {\r
+                                               features = runtime.getFeatures();\r
+\r
+                                               // Check if runtime supports required features\r
+                                               requiredFeatures = self.settings.required_features;\r
+                                               if (requiredFeatures) {\r
+                                                       requiredFeatures = requiredFeatures.split(',');\r
+\r
+                                                       for (i = 0; i < requiredFeatures.length; i++) {\r
+                                                               // Specified feature doesn't exist\r
+                                                               if (!features[requiredFeatures[i]]) {\r
+                                                                       callNextInit();\r
+                                                                       return;\r
+                                                               }\r
+                                                       }\r
+                                               }\r
+\r
+                                               // Try initializing the runtime\r
+                                               runtime.init(self, function(res) {\r
+                                                       if (res && res.success) {\r
+                                                               // Successful initialization\r
+                                                               self.features = features;\r
+                                                               self.trigger('Init', {runtime : runtime.name});\r
+                                                               self.trigger('PostInit');\r
+                                                               self.refresh();\r
+                                                       } else {\r
+                                                               callNextInit();\r
+                                                       }\r
+                                               });\r
+                                       } else {\r
+                                               // Trigger an init error if we run out of runtimes\r
+                                               self.trigger('Error', {\r
+                                                       code : plupload.INIT_ERROR,\r
+                                                       message : 'Init error.'\r
+                                               });\r
+                                       }\r
+                               }\r
+\r
+                               callNextInit();\r
+                       },\r
+\r
+                       /**\r
+                        * Refreshes the upload instance by dispatching out a refresh event to all runtimes.\r
+                        * This would for example reposition flash/silverlight shims on the page.\r
+                        *\r
+                        * @method refresh\r
+                        */\r
+                       refresh : function() {\r
+                               this.trigger("Refresh");\r
+                       },\r
+\r
+                       /**\r
+                        * Starts uploading the queued files.\r
+                        *\r
+                        * @method start\r
+                        */\r
+                       start : function() {\r
+                               if (this.state != plupload.STARTED) {\r
+                                       fileIndex = 0;\r
+\r
+                                       this.state = plupload.STARTED;\r
+                                       this.trigger("StateChanged");\r
+\r
+                                       uploadNext.call(this);\r
+                               }\r
+                       },\r
+\r
+                       /**\r
+                        * Stops the upload of the queued files.\r
+                        *\r
+                        * @method stop\r
+                        */\r
+                       stop : function() {\r
+                               if (this.state != plupload.STOPPED) {\r
+                                       this.state = plupload.STOPPED;\r
+                                       this.trigger("StateChanged");\r
+                               }\r
+                       },\r
+\r
+                       /**\r
+                        * Returns the specified file object by id.\r
+                        *\r
+                        * @method getFile\r
+                        * @param {String} id File id to look for.\r
+                        * @return {plupload.File} File object or undefined if it wasn't found;\r
+                        */\r
+                       getFile : function(id) {\r
+                               var i;\r
+\r
+                               for (i = files.length - 1; i >= 0; i--) {\r
+                                       if (files[i].id === id) {\r
+                                               return files[i];\r
+                                       }\r
+                               }\r
+                       },\r
+\r
+                       /**\r
+                        * Removes a specific file.\r
+                        *\r
+                        * @method removeFile\r
+                        * @param {plupload.File} file File to remove from queue.\r
+                        */\r
+                       removeFile : function(file) {\r
+                               var i;\r
+\r
+                               for (i = files.length - 1; i >= 0; i--) {\r
+                                       if (files[i].id === file.id) {\r
+                                               return this.splice(i, 1)[0];\r
+                                       }\r
+                               }\r
+                       },\r
+\r
+                       /**\r
+                        * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.\r
+                        *\r
+                        * @method splice\r
+                        * @param {Number} start (Optional) Start index to remove from.\r
+                        * @param {Number} length (Optional) Lengh of items to remove.\r
+                        * @return {Array} Array of files that was removed.\r
+                        */\r
+                       splice : function(start, length) {\r
+                               var removed;\r
+       \r
+                               // Splice and trigger events\r
+                               removed = files.splice(start, length);\r
+\r
+                               this.trigger("FilesRemoved", removed);\r
+                               this.trigger("QueueChanged");\r
+\r
+                               return removed;\r
+                       },\r
+\r
+                       /**\r
+                        * Dispatches the specified event name and it's arguments to all listeners.\r
+                        *\r
+                        *\r
+                        * @method trigger\r
+                        * @param {String} name Event name to fire.\r
+                        * @param {Object..} Multiple arguments to pass along to the listener functions.\r
+                        */\r
+                       trigger : function(name) {\r
+                               var list = events[name.toLowerCase()], i, args;\r
+\r
+                               // console.log(name, arguments);\r
+\r
+                               if (list) {\r
+                                       // Replace name with sender in args\r
+                                       args = Array.prototype.slice.call(arguments);\r
+                                       args[0] = this;\r
+\r
+                                       // Dispatch event to all listeners\r
+                                       for (i = 0; i < list.length; i++) {\r
+                                               // Fire event, break chain if false is returned\r
+                                               if (list[i].func.apply(list[i].scope, args) === false) {\r
+                                                       return false;\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                               return true;\r
+                       },\r
+\r
+                       /**\r
+                        * Adds an event listener by name.\r
+                        *\r
+                        * @method bind\r
+                        * @param {String} name Event name to listen for.\r
+                        * @param {function} func Function to call ones the event gets fired.\r
+                        * @param {Object} scope Optional scope to execute the specified function in.\r
+                        */\r
+                       bind : function(name, func, scope) {\r
+                               var list;\r
+\r
+                               name = name.toLowerCase();\r
+                               list = events[name] || [];\r
+                               list.push({func : func, scope : scope || this});\r
+                               events[name] = list;\r
+                       },\r
+\r
+                       /**\r
+                        * Removes the specified event listener.\r
+                        *\r
+                        * @method unbind\r
+                        * @param {String} name Name of event to remove.\r
+                        * @param {function} func Function to remove from listener.\r
+                        */\r
+                       unbind : function(name, func) {\r
+                               var list = events[name.toLowerCase()], i;\r
+\r
+                               if (list) {\r
+                                       for (i = list.length - 1; i >= 0; i--) {\r
+                                               if (list[i].func === func) {\r
+                                                       list.splice(i, 1);\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       /**\r
+                        * Fires when the current RunTime has been initialized.\r
+                        *\r
+                        * @event Init\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires after the init event incase you need to perform actions there.\r
+                        *\r
+                        * @event PostInit\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires when the silverlight/flash or other shim needs to move.\r
+                        *\r
+                        * @event Refresh\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        */\r
+       \r
+                       /**\r
+                        * Fires when the overall state is being changed for the upload queue.\r
+                        *\r
+                        * @event StateChanged\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires when a file is to be uploaded by the runtime.\r
+                        *\r
+                        * @event UploadFile\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {plupload.File} file File to be uploaded.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.\r
+                        *\r
+                        * @event QueueChanged\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        */\r
+       \r
+                       /**\r
+                        * Fires while a file is being uploaded. Use this event to update the current file upload progress.\r
+                        *\r
+                        * @event UploadProgress\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {plupload.File} file File that is currently being uploaded.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires while a file was removed from queue.\r
+                        *\r
+                        * @event FilesRemoved\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {Array} files Array of files that got removed.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires while when the user selects files to upload.\r
+                        *\r
+                        * @event FilesAdded\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {Array} files Array of file objects that was added to queue/selected by the user.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires when a file is successfully uploaded.\r
+                        *\r
+                        * @event FileUploaded\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {plupload.File} file File that was uploaded.\r
+                        * @param {Object} response Object with response properties.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires when file chunk is uploaded.\r
+                        *\r
+                        * @event ChunkUploaded\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {plupload.File} file File that the chunk was uploaded for.\r
+                        * @param {Object} response Object with response properties.\r
+                        */\r
+\r
+                       /**\r
+                        * Fires when a error occurs.\r
+                        *\r
+                        * @event Error\r
+                        * @param {plupload.Uploader} uploader Uploader instance sending the event.\r
+                        * @param {Object} error Contains code, message and sometimes file and other details.\r
+                        */\r
+               });\r
+       };\r
+\r
+       /**\r
+        * File instance.\r
+        *\r
+        * @class plupload.File\r
+        * @param {String} name Name of the file.\r
+        * @param {Number} size File size.\r
+        */\r
+\r
+       /**\r
+        * Constructs a new file instance.\r
+        *\r
+        * @constructor\r
+        * @method File\r
+        * @param {String} id Unique file id.\r
+        * @param {String} name File name.\r
+        * @param {Number} size File size in bytes.\r
+        */\r
+       plupload.File = function(id, name, size) {\r
+               var self = this; // Setup alias for self to reduce code size when it's compressed\r
+\r
+               /**\r
+                * File id this is a globally unique id for the specific file.\r
+                *\r
+                * @property id\r
+                * @type String\r
+                */\r
+               self.id = id;\r
+\r
+               /**\r
+                * File name for example "myfile.gif".\r
+                *\r
+                * @property name\r
+                * @type String\r
+                */\r
+               self.name = name;\r
+\r
+               /**\r
+                * File size in bytes.\r
+                *\r
+                * @property size\r
+                * @type Number\r
+                */\r
+               self.size = size;\r
+\r
+               /**\r
+                * Number of bytes uploaded of the files total size.\r
+                *\r
+                * @property loaded\r
+                * @type Number\r
+                */\r
+               self.loaded = 0;\r
+\r
+               /**\r
+                * Number of percentage uploaded of the file.\r
+                *\r
+                * @property percent\r
+                * @type Number\r
+                */\r
+               self.percent = 0;\r
+\r
+               /**\r
+                * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.\r
+                *\r
+                * @property status\r
+                * @type Number\r
+                * @see plupload\r
+                */\r
+               self.status = 0;\r
+       };\r
+\r
+       /**\r
+        * Runtime class gets implemented by each upload runtime.\r
+        *\r
+        * @class plupload.Runtime\r
+        * @static\r
+        */\r
+       plupload.Runtime = function() {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               this.getFeatures = function() {\r
+               };\r
+\r
+               /**\r
+                * Initializes the upload runtime. This method should add necessary items to the DOM and register events needed for operation. \r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback function to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               this.init = function(uploader, callback) {\r
+               };\r
+       };\r
+\r
+       /**\r
+        * Runtime class gets implemented by each upload runtime.\r
+        *\r
+        * @class plupload.QueueProgress\r
+        */\r
+\r
+       /**\r
+        * Constructs a queue progress.\r
+        *\r
+        * @constructor\r
+        * @method QueueProgress\r
+        */\r
+        plupload.QueueProgress = function() {\r
+               var self = this; // Setup alias for self to reduce code size when it's compressed\r
+\r
+               /**\r
+                * Total queue file size.\r
+                *\r
+                * @property size\r
+                * @type Number\r
+                */\r
+               self.size = 0;\r
+\r
+               /**\r
+                * Total bytes uploaded.\r
+                *\r
+                * @property loaded\r
+                * @type Number\r
+                */\r
+               self.loaded = 0;\r
+\r
+               /**\r
+                * Number of files uploaded.\r
+                *\r
+                * @property uploaded\r
+                * @type Number\r
+                */\r
+               self.uploaded = 0;\r
+\r
+               /**\r
+                * Number of files failed to upload.\r
+                *\r
+                * @property failed\r
+                * @type Number\r
+                */\r
+               self.failed = 0;\r
+\r
+               /**\r
+                * Number of files yet to be uploaded.\r
+                *\r
+                * @property queued\r
+                * @type Number\r
+                */\r
+               self.queued = 0;\r
+\r
+               /**\r
+                * Total percent of the uploaded bytes.\r
+                *\r
+                * @property percent\r
+                * @type Number\r
+                */\r
+               self.percent = 0;\r
+\r
+               /**\r
+                * Bytes uploaded per second.\r
+                *\r
+                * @property bytesPerSec\r
+                * @type Number\r
+                */\r
+               self.bytesPerSec = 0;\r
+\r
+               /**\r
+                * Resets the progress to it's initial values.\r
+                *\r
+                * @method reset\r
+                */\r
+               self.reset = function() {\r
+                       self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;\r
+               };\r
+       };\r
+\r
+       // Create runtimes namespace\r
+       plupload.runtimes = {};\r
+\r
+       // Expose plupload namespace\r
+       window.plupload = plupload;\r
+})();\r
diff --git a/horde/js/plupload/plupload.silverlight.js b/horde/js/plupload/plupload.silverlight.js
new file mode 100644 (file)
index 0000000..d387a80
--- /dev/null
@@ -0,0 +1,359 @@
+/**\r
+ * plupload.silverlight.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under GPL License.\r
+ *\r
+ * License: http://www.plupload.com/license\r
+ * Contributing: http://www.plupload.com/contributing\r
+ */\r
+\r
+// JSLint defined globals\r
+/*global plupload:false, ActiveXObject:false, window:false */\r
+\r
+(function(plupload) {\r
+       var uploadInstances = {};\r
+\r
+       function jsonSerialize(obj) {\r
+               var value, type = typeof obj, undef, isArray, i, key;\r
+\r
+               // Encode strings\r
+               if (type === 'string') {\r
+                       value = '\bb\tt\nn\ff\rr\""\'\'\\\\';\r
+\r
+                       return '"' + obj.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {\r
+                               var idx = value.indexOf(b);\r
+\r
+                               if (idx + 1) {\r
+                                       return '\\' + value.charAt(idx + 1);\r
+                               }\r
+\r
+                               a = b.charCodeAt().toString(16);\r
+\r
+                               return '\\u' + '0000'.substring(a.length) + a;\r
+                       }) + '"';\r
+               }\r
+\r
+               // Loop objects/arrays\r
+               if (type == 'object') {\r
+                       isArray = obj.length !== undef;\r
+                       value = '';\r
+\r
+                       if (isArray) {\r
+                               for (i = 0; i < obj.length; i++) {\r
+                                       if (value) {\r
+                                               value += ',';\r
+                                       }\r
+\r
+                                       value += jsonSerialize(obj[i]);\r
+                               }\r
+\r
+                               value = '[' + value + ']';\r
+                       } else {\r
+                               for (key in obj) {\r
+                                       if (obj.hasOwnProperty(key)) {\r
+                                               if (value) {\r
+                                                       value += ',';\r
+                                               }\r
+\r
+                                               value += jsonSerialize(key) + ':' + jsonSerialize(obj[key]);\r
+                                       }\r
+                               }\r
+\r
+                               value = '{' + value + '}';\r
+                       }\r
+\r
+                       return value;\r
+               }\r
+\r
+               // Treat undefined as null\r
+               if (obj === undef) {\r
+                       return 'null';\r
+               }\r
+\r
+               // Convert all other types to string\r
+               return '' + obj;\r
+       }\r
+\r
+       function isInstalled(version) {\r
+               var isVersionSupported = false, container = null, control = null, actualVer,\r
+                       actualVerArray, reqVerArray, requiredVersionPart, actualVersionPart, index = 0;\r
+\r
+               try {\r
+                       try {\r
+                               control = new ActiveXObject('AgControl.AgControl');\r
+\r
+                               if (control.IsVersionSupported(version)) {\r
+                                       isVersionSupported = true;\r
+                               }\r
+\r
+                               control = null;\r
+                       } catch (e) {\r
+                               var plugin = navigator.plugins["Silverlight Plug-In"];\r
+\r
+                               if (plugin) {\r
+                                       actualVer = plugin.description;\r
+\r
+                                       if (actualVer === "1.0.30226.2") {\r
+                                               actualVer = "2.0.30226.2";\r
+                                       }\r
+\r
+                                       actualVerArray = actualVer.split(".");\r
+\r
+                                       while (actualVerArray.length > 3) {\r
+                                               actualVerArray.pop();\r
+                                       }\r
+\r
+                                       while ( actualVerArray.length < 4) {\r
+                                               actualVerArray.push(0);\r
+                                       }\r
+\r
+                                       reqVerArray = version.split(".");\r
+\r
+                                       while (reqVerArray.length > 4) {\r
+                                               reqVerArray.pop();\r
+                                       }\r
+\r
+                                       do {\r
+                                               requiredVersionPart = parseInt(reqVerArray[index], 10);\r
+                                               actualVersionPart = parseInt(actualVerArray[index], 10);\r
+                                               index++;\r
+                                       } while (index < reqVerArray.length && requiredVersionPart === actualVersionPart);\r
+\r
+                                       if (requiredVersionPart <= actualVersionPart && !isNaN(requiredVersionPart)) {\r
+                                               isVersionSupported = true;\r
+                                       }\r
+                               }\r
+                       }\r
+               } catch (e2) {\r
+                       isVersionSupported = false;\r
+               }\r
+\r
+               return isVersionSupported;\r
+       }\r
+\r
+       plupload.silverlight = {\r
+               trigger : function(id, name) {\r
+                       var uploader = uploadInstances[id], i, args;\r
+\r
+                       if (uploader) {\r
+                               args = plupload.toArray(arguments).slice(1);\r
+                               args[0] = 'Silverlight:' + name;\r
+\r
+                               // Detach the call so that error handling in the browser is presented correctly\r
+                               setTimeout(function() {\r
+                                       uploader.trigger.apply(uploader, args);\r
+                               }, 0);\r
+                       }\r
+               }\r
+       };\r
+\r
+       /**\r
+        * Silverlight implementation. This runtime supports these features: jpgresize, pngresize, chunks.\r
+        *\r
+        * @static\r
+        * @class plupload.runtimes.Silverlight\r
+        * @extends plupload.Runtime\r
+        */\r
+       plupload.runtimes.Silverlight = plupload.addRuntime("silverlight", {\r
+               /**\r
+                * Returns a list of supported features for the runtime.\r
+                *\r
+                * @return {Object} Name/value object with supported features.\r
+                */\r
+               getFeatures : function() {\r
+                       return {\r
+                               jpgresize: true,\r
+                               pngresize: true,\r
+                               chunks: true,\r
+                               progress: true,\r
+                               multipart: true\r
+                       };\r
+               },\r
+\r
+               /**\r
+                * Initializes the upload runtime. This runtime supports these features: jpgresize, pngresize, chunks.\r
+                *\r
+                * @method init\r
+                * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.\r
+                * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.\r
+                */\r
+               init : function(uploader, callback) {\r
+                       var silverlightContainer, filter = '', filters = uploader.settings.filters, i, container = document.body;\r
+\r
+                       // Check if Silverlight is installed, Silverlight windowless parameter doesn't work correctly on Opera so we disable it for now\r
+                       if (!isInstalled('2.0.31005.0') || (window.opera && window.opera.buildNumber)) {\r
+                               callback({success : false});\r
+                               return;\r
+                       }\r
+\r
+                       uploadInstances[uploader.id] = uploader;\r
+\r
+                       // Create silverlight container and insert it at an absolute position within the browse button\r
+                       silverlightContainer = document.createElement('div');\r
+                       silverlightContainer.id = uploader.id + '_silverlight_container';\r
+\r
+                       plupload.extend(silverlightContainer.style, {\r
+                               position : 'absolute',\r
+                               top : '0px',\r
+                               background : uploader.settings.shim_bgcolor || 'transparent',\r
+                               zIndex : 99999,\r
+                               width : '100px',\r
+                               height : '100px',\r
+                               overflow : 'hidden',\r
+                               opacity : uploader.settings.shim_bgcolor ? '' : 0.01 // Force transparent if bgcolor is undefined\r
+                       });\r
+\r
+                       silverlightContainer.className = 'plupload silverlight';\r
+\r
+                       if (uploader.settings.container) {\r
+                               container = document.getElementById(uploader.settings.container);\r
+                               container.style.position = 'relative';\r
+                       }\r
+\r
+                       container.appendChild(silverlightContainer);\r
+\r
+                       for (i = 0; i < filters.length; i++) {\r
+                               filter += (filter != '' ? '|' : '') + filters[i].title + " | *." + filters[i].extensions.replace(/,/g, ';*.');\r
+                       }\r
+\r
+                       // Insert the Silverlight object inide the Silverlight container\r
+                       silverlightContainer.innerHTML = '<object id="' + uploader.id + '_silverlight" data="data:application/x-silverlight," type="application/x-silverlight-2" style="outline:none;" width="1024" height="1024">' +\r
+                               '<param name="source" value="' + uploader.settings.silverlight_xap_url + '"/>' +\r
+                               '<param name="background" value="Transparent"/>' +\r
+                               '<param name="windowless" value="true"/>' +\r
+                               '<param name="initParams" value="id=' + uploader.id + ',filter=' + filter + '"/>' +\r
+                               '</object>';\r
+\r
+                       function getSilverlightObj() {\r
+                               return document.getElementById(uploader.id + '_silverlight').content.Upload;\r
+                       }\r
+\r
+                       uploader.bind("Silverlight:Init", function() {\r
+                               var selectedFiles, lookup = {};\r
+\r
+                               uploader.bind("Silverlight:StartSelectFiles", function(up) {\r
+                                       selectedFiles = [];\r
+                               });\r
+\r
+                               uploader.bind("Silverlight:SelectFile", function(up, sl_id, name, size) {\r
+                                       var id;\r
+\r
+                                       // Store away silverlight ids\r
+                                       id = plupload.guid();\r
+                                       lookup[id] = sl_id;\r
+                                       lookup[sl_id] = id;\r
+\r
+                                       // Expose id, name and size\r
+                                       selectedFiles.push(new plupload.File(id, name, size));\r
+                               });\r
+\r
+                               uploader.bind("Silverlight:SelectSuccessful", function() {\r
+                                       // Trigger FilesAdded event if we added any\r
+                                       if (selectedFiles.length) {\r
+                                               uploader.trigger("FilesAdded", selectedFiles);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("Silverlight:UploadChunkError", function(up, file_id, chunk, chunks, message) {\r
+                                       uploader.trigger("Error", {\r
+                                               code : plupload.IO_ERROR,\r
+                                               message : 'IO Error.',\r
+                                               details : message,\r
+                                               file : up.getFile(lookup[file_id])\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("Silverlight:UploadFileProgress", function(up, sl_id, loaded, total) {\r
+                                       var file = up.getFile(lookup[sl_id]);\r
+\r
+                                       if (file.status != plupload.FAILED) {\r
+                                               file.size = total;\r
+                                               file.loaded = loaded;\r
+\r
+                                               up.trigger('UploadProgress', file);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("Refresh", function(up) {\r
+                                       var browseButton, browsePos, browseSize;\r
+\r
+                                       browseButton = document.getElementById(up.settings.browse_button);\r
+                                       browsePos = plupload.getPos(browseButton, document.getElementById(up.settings.container));\r
+                                       browseSize = plupload.getSize(browseButton);\r
+\r
+                                       plupload.extend(document.getElementById(up.id + '_silverlight_container').style, {\r
+                                               top : browsePos.y + 'px',\r
+                                               left : browsePos.x + 'px',\r
+                                               width : browseSize.w + 'px',\r
+                                               height : browseSize.h + 'px'\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("Silverlight:UploadChunkSuccessful", function(up, sl_id, chunk, chunks, text) {\r
+                                       var chunkArgs, file = up.getFile(lookup[sl_id]);\r
+\r
+                                       chunkArgs = {\r
+                                               chunk : chunk,\r
+                                               chunks : chunks,\r
+                                               response : text\r
+                                       };\r
+\r
+                                       up.trigger('ChunkUploaded', file, chunkArgs);\r
+\r
+                                       // Stop upload if file is maked as failed\r
+                                       if (file.status != plupload.FAILED) {\r
+                                               getSilverlightObj().UploadNextChunk();\r
+                                       }\r
+\r
+                                       // Last chunk then dispatch FileUploaded event\r
+                                       if (chunk == chunks - 1) {\r
+                                               file.status = plupload.DONE;\r
+\r
+                                               up.trigger('FileUploaded', file, {\r
+                                                       response : text\r
+                                               });\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("Silverlight:UploadSuccessful", function(up, sl_id, response) {\r
+                                       var file = up.getFile(lookup[sl_id]);\r
+\r
+                                       file.status = plupload.DONE;\r
+\r
+                                       up.trigger('FileUploaded', file, {\r
+                                               response : response\r
+                                       });\r
+                               });\r
+\r
+                               uploader.bind("FilesRemoved", function(up, files) {\r
+                                       var i;\r
+\r
+                                       for (i = 0; i < files.length; i++) {\r
+                                               getSilverlightObj().RemoveFile(lookup[files[i].id]);\r
+                                       }\r
+                               });\r
+\r
+                               uploader.bind("UploadFile", function(up, file) {\r
+                                       var settings = up.settings, resize = settings.resize || {};\r
+\r
+                                       getSilverlightObj().UploadFile(\r
+                                               lookup[file.id],\r
+                                               plupload.buildUrl(up.settings.url, {name : file.target_name || file.name}),\r
+                                               jsonSerialize({\r
+                                                       chunk_size : settings.chunk_size,\r
+                                                       image_width : resize.width,\r
+                                                       image_height : resize.height,\r
+                                                       image_quality : resize.quality || 90,\r
+                                                       multipart : !!settings.multipart,\r
+                                                       multipart_params : settings.multipart_params || {},\r
+                                                       headers : settings.headers\r
+                                               })\r
+                                       );\r
+                               });\r
+\r
+                               callback({success : true});\r
+                       });\r
+               }\r
+       });\r
+})(plupload);\r
diff --git a/horde/js/plupload/plupload.silverlight.xap b/horde/js/plupload/plupload.silverlight.xap
new file mode 100644 (file)
index 0000000..a58a80b
Binary files /dev/null and b/horde/js/plupload/plupload.silverlight.xap differ
diff --git a/horde/js/plupload/uploader.js b/horde/js/plupload/uploader.js
new file mode 100644 (file)
index 0000000..6bdfcea
--- /dev/null
@@ -0,0 +1,208 @@
+/**
+ * File upload widget based on puploader.
+ * See: http://www.plupload.com
+ *
+ * Uses the pluploader lower level API to sort-of duplicate the idea behind the
+ * jquery widget.
+ *
+ * Requires: puploader.js (v1.2.3+) as well as the runtime files for the desired
+ *           runtimes, e.g. puploader.html5.js.
+ *
+ * Usage:
+ * ======
+ * var uploader = new Horde_Uploader({
+ *      'container' - (string) Dom id of the parent container to place the
+ *                             widget it.
+ *      'target' - (string) Url of target page
+ *      'drop_target' (string) Dom id of element to hold file list
+ *      'swf_path' - (string) (optional) Path to flash file
+ *      'xap_path' - (string) (optional) Path to silverlight file
+ *      'browsebutton_class' |
+ *      'uploadbutton_class' | - CSS class names for various elements.
+ *      'filelist_class'     |
+ *      'browse_button'  dom id to use for the file browser button.
+ *      'upload_button'  dom id to use for the upload start butotn.
+ *      'text' - hash of various strings used in the interface.,
+ *      'return_target'
+ * });
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ */
+var Horde_Uploader = Class.create({
+
+    handlers: {
+
+        filesAdded: function(up, files)
+        {
+            $(this._params['return_button']).hide();
+            files.each(function(file) {
+                var remove = new Element('div', {'class': 'hordeUploaderRemove'}).update('&nbsp;');
+                var newdiv = new Element('li',
+                    { 'class': this._params.filelistitem_class,
+                      'id': file.id
+                    })
+                    .insert(new Element('div', { 'class': 'hordeUploaderFilename' }).update(file.name))
+                    .insert(new Element('div', { 'class': 'hordeUploaderFileaction' }).update(remove))
+                    .insert(new Element('div', { 'class': 'hordeUploaderFilestatus' }).update('&nbsp'))
+                    .insert(new Element('div', { 'class': 'hordeUploaderFilesize' }).update(plupload.formatSize(file.size)));
+                remove.observe('click', function() { var f = up.getFile(newdiv.id); up.removeFile(f); $(newdiv.id).remove(); });
+                $(this._params['drop_target']).select('.hordeUploaderFileUl').each(function(p) { p.insert(newdiv) });
+            }.bind(this));
+        },
+
+        /**
+         * TODO: tweak the selector usage... implement a progress bar.
+         */
+        progress: function(up, file)
+        {
+            // Refresh the uploader when done. Might be a better way to do this
+            // but the documentation is pretty lacking.
+            if (up.total.percent == 100) {
+                up.files = [];
+                up.refresh();
+                $(this._params['return_button']).show();
+            }
+            $(file.id).select('.hordeUploaderFilestatus').each(function(p) { p.update(file.percent + '%') });
+        },
+
+        /**
+         *
+         */
+        init: function(up, params)
+        {
+        },
+
+        /**
+         * Receives the server response in response and is responsible for
+         * updating the UI accordingly
+         */
+        fileuploaded: function(up, file, response) {
+            try {
+                var result = response.response.evalJSON();
+                if (result.status != 200) {
+                    up.unbind('UploadProgress', this.handlers.progress);
+                    $(file.id).select('.hordeUploaderFilestatus').each(function(p) { $(p).update(result.error.message); });
+                    $(file.id).setStyle({'fontWeight': 'bold', 'color': 'red'});
+                    $(file.id).select('.hordeUploaderFileaction').each(function(p) { $(p).addClassName(this._params['error_class']) }.bind(this));
+                } else {
+                    $(file.id).setStyle({'fontWeight': 'bold', 'color': 'green'});
+                    $(file.id).select('.hordeUploaderFileaction').each(function(p) {
+                        $(p).select('.hordeUploaderRemove').each(function(r) { r.remove(); });
+                        $(p).update('&nbsp;').addClassName(this._params['success_class']);
+                    }.bind(this));
+                }
+            } catch (Exception) {
+                    up.unbind('UploadProgress', this.handlers.progress);
+                    $(file.id).select('.hordeUploaderFilestatus').each(function(p) { $(p).update(Exception); });
+            }
+        }
+    },
+
+    /**
+     * Params required: container, target.
+     * Params not required, but have no defaults: swf_path, xap_path
+     * Parms with sensible defaults: browsebutton_class, uploadbutton_class,
+     *                               filelist_class, browse_button, drop_target,
+     *                               upload_button, upload_button
+     */
+    initialize: function(params)
+    {
+        this._params = Object.extend({
+            browsebutton_class: 'button',
+            uploadbutton_class: 'button',
+            header_class: 'hordeUploaderHeader',
+            subheader_class: 'hordeUploaderSubHeader',
+            container_class: 'hordeUploaderContainer',
+            filelist_class: 'hordeUploaderFilelist',
+            filelistitem_class: 'hordeUploaderFilelistItem',
+            browse_button: 'browseimages',
+            drop_target: 'filelist',
+            upload_button: 'uploadimages',
+            return_button: 'return',
+            returnbutton_class: 'button',
+            success_class: 'hordeUploaderSuccess',
+            error_class: 'hordeUploaderError'
+        }, params);
+
+        this._build();
+
+        this._puploader = new plupload.Uploader({
+            runtimes: 'html5, flash, silverlight, browserplus',
+            browse_button: this._params['browse_button'],
+            url: this._params['target'],
+            drop_element: this._params['drop_target'],
+            flash_swf_url: this._params['swf_path'],
+            silverlight_xap_url: this._params['xap_path']
+        });
+
+        this._puploader.bind('UploadProgress', this.handlers.progress, this);
+        this._puploader.bind('Init', this.handlers.init, this);
+        this._puploader.bind('FilesAdded', this.handlers.filesAdded, this);
+        this._puploader.bind('FileUploaded', this.handlers.fileuploaded, this);
+
+    },
+
+    init: function()
+    {
+        this._puploader.init();
+        $(this._params['upload_button']).observe('click', function(e) {
+            this._puploader.start();
+            e.stop();
+        }.bindAsEventListener(this));
+        this.setReturnTarget(this._params['return_target']);
+        $(this._params['return_button']).hide();
+    },
+
+    setReturnTarget: function(path)
+    {
+        $(this._params['return_button']).href = path;
+    },
+
+    /**
+     * Draw the upload widget.
+     *
+     * TODO: eh, make pretty :)
+     *
+     */
+    _build: function()
+    {
+        /* Filelist, with embedded UL */
+        var filelist = new Element('div',
+            { 'id': this._params['drop_target'],
+              'class': this._params['filelist_class'] }).insert(new Element('ul', { 'class': 'hordeUploaderFileUl' }));
+
+        /* Browse button */
+        var browse = new Element('a', { 'id': this._params['browse_button'], 'class': this._params['browsebutton_class'] }).update(this._params.text.add);
+
+        /* Upload button */
+        var upload = new Element('a', { 'id': this._params['upload_button'], 'class': this._params['uploadbutton_class'] }).update(this._params.text.start);
+
+        /* Return button (Activated when uploader is DONE). */
+        var returnButton = new Element('a', { 'id': this._params['return_button'], 'class': this._params['returnbutton_class'] }).update(this._params.text.returnButton);
+
+        /* Header section */
+        var header = new Element('div', { 'class': this._params['header_class'] }).update(this._params.text.header);
+        var subheader = new Element('div', { 'class': this._params['subheader_class'] }).update(this._params.text.subheader);
+
+        /* filelist header rows */
+        var fileheader = new Element('div')
+            .insert(new Element('div', { 'class': 'hordeUploaderFilename' }).update('Filename'))
+            .insert(new Element('div', { 'class': 'hordeUploaderFileaction'}).update('&nbsp;'))
+            .insert(new Element('div', { 'class': 'hordeUploaderFilestatus'}).update('Status'))
+            .insert(new Element('div', { 'class': 'hordeUploaderFilesize' }).update('Size'));
+
+
+        /* Build full widget, Insert into the DOM */
+        $(this._params['container']).insert(
+            new Element('div', {'class': this._params['container_class'] })
+                .insert(header)
+                .insert(subheader)
+                .insert(fileheader)
+                .insert(new Element('div', { 'class': 'clear' }))
+                .insert(filelist)
+                .insert(browse)
+                .insert(upload)
+                .insert(returnButton));
+    }
+
+});
\ No newline at end of file
index 5090517..2af2df3 100644 (file)
@@ -1306,6 +1306,49 @@ div.GrowlerNoticeExit:hover {
     padding-top: 5px;
 }
 
+/* Uploader */
+.hordeUploaderHeader {
+}
+.hordeUploaderSubHeader {
+    border-bottom: thin solid #000;
+}
+.hordeUploaderFilelist {
+    height: 350px;
+    overflow: auto;
+}
+.hordeUploaderFilename {
+    float: left;
+    width: 200px;
+}
+.hordeUploaderFilestatus,
+.hordeUploaderFilesize,
+.hordeUploaderFileaction
+{
+   float: right;
+   text-align: right;
+}
+.hordeUploaderFilestatus,
+.hordeUploaderFilesize
+{
+    width: 80px;
+}
+.hordeUploaderFileaction
+{
+    width: 16px;
+}
+.hordeUploaderSuccess
+{
+    background-image: url("graphics/alerts/success.png");
+}
+.hordeUploaderError
+{
+    background-image: url("graphics/alerts/error.png");
+}
+.hordeUploaderRemove
+{
+    background-image: url("graphics/delete.png");
+}
+
 /* For in place editing */
 form.inplaceeditor-form {
     background: none;