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';
*
*--------------------------------------------------------------------------*/
-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) {
--- /dev/null
+<?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;
+ }
+ }
+ }
+}
--- /dev/null
+<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
-<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"> </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
background: gray;
color: #fff;
}
+#anseluploader {
+ width: auto;
+ margin-top: 180px;
+}
+.uploaderContainer {
+ border: solid 1px #999999;
+}
<?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
--- /dev/null
+// .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
--- /dev/null
+// .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
--- /dev/null
+/**\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"> </div>' +\r
+ '<div class="plupload_file_status"><span>' + _('Status') + '</span></div>' +\r
+ '<div class="plupload_file_size">' + _('Size') + '</div>' +\r
+ '<div class="plupload_clearer"> </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"> </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"> </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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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 <>"));\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
--- /dev/null
+/**\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
--- /dev/null
+/**
+ * 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(' ');
+ 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(' '))
+ .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(' ').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(' '))
+ .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
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;