Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sat, Feb 22, 12:02 AM

in-portal

This file is larger than 256 KB, so syntax highlighting was skipped.
Index: branches/5.3.x/core/kernel/db/db_event_handler.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_event_handler.php (revision 16001)
+++ branches/5.3.x/core/kernel/db/db_event_handler.php (revision 16002)
@@ -1,3636 +1,3467 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
define('EH_CUSTOM_PROCESSING_BEFORE',1);
define('EH_CUSTOM_PROCESSING_AFTER',2);
/**
* Note:
* 1. When addressing variables from submit containing
* Prefix_Special as part of their name use
* $event->getPrefixSpecial(true) instead of
* $event->getPrefixSpecial() as usual. This is due PHP
* is converting "." symbols in variable names during
* submit info "_". $event->getPrefixSpecial optional
* 1st parameter returns correct current Prefix_Special
* for variables being submitted such way (e.g. variable
* name that will be converted by PHP: "users.read_only_id"
* will be submitted as "users_read_only_id".
*
* 2. When using $this->Application-LinkVar on variables submitted
* from form which contain $Prefix_Special then note 1st item. Example:
* LinkVar($event->getPrefixSpecial(true).'_varname',$event->getPrefixSpecial().'_varname')
*
*/
/**
* EventHandler that is used to process
* any database related events
*
*/
class kDBEventHandler extends kEventHandler {
/**
* Checks permissions of user
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
$section = $event->getSection();
if ( !$this->Application->isAdmin ) {
$allow_events = Array ('OnSearch', 'OnSearchReset', 'OnNew');
if ( in_array($event->Name, $allow_events) ) {
// allow search on front
return true;
}
}
elseif ( ($event->Name == 'OnPreSaveAndChangeLanguage') && !$this->UseTempTables($event) ) {
// allow changing language in grids, when not in editing mode
return $this->Application->CheckPermission($section . '.view', 1);
}
if ( !preg_match('/^CATEGORY:(.*)/', $section) ) {
// only if not category item events
if ( (substr($event->Name, 0, 9) == 'OnPreSave') || ($event->Name == 'OnSave') ) {
if ( $this->isNewItemCreate($event) ) {
return $this->Application->CheckPermission($section . '.add', 1);
}
else {
return $this->Application->CheckPermission($section . '.add', 1) || $this->Application->CheckPermission($section . '.edit', 1);
}
}
}
if ( $event->Name == 'OnPreCreate' ) {
// save category_id before item create (for item category selector not to destroy permission checking category)
$this->Application->LinkVar('m_cat_id');
}
return parent::CheckPermission($event);
}
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array (
'OnLoad' => Array ('self' => 'view', 'subitem' => 'view'),
'OnItemBuild' => Array ('self' => 'view', 'subitem' => 'view'),
'OnSuggestValues' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnSuggestValuesJSON' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnBuild' => Array ('self' => true),
'OnNew' => Array ('self' => 'add', 'subitem' => 'add|edit'),
'OnCreate' => Array ('self' => 'add', 'subitem' => 'add|edit'),
'OnUpdate' => Array ('self' => 'edit', 'subitem' => 'add|edit'),
'OnSetPrimary' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
'OnDeleteAll' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
'OnMassDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
'OnMassClone' => Array ('self' => 'add', 'subitem' => 'add|edit'),
'OnCut' => Array ('self'=>'edit', 'subitem' => 'edit'),
'OnCopy' => Array ('self'=>'edit', 'subitem' => 'edit'),
'OnPaste' => Array ('self'=>'edit', 'subitem' => 'edit'),
'OnSelectItems' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnProcessSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnStoreSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnSelectUser' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnMassApprove' => Array ('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'),
'OnMassDecline' => Array ('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'),
'OnMassMoveUp' => Array ('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'),
'OnMassMoveDown' => Array ('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'),
'OnPreCreate' => Array ('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'),
'OnEdit' => Array ('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'),
'OnExport' => Array ('self' => 'view|advanced:export'),
'OnExportBegin' => Array ('self' => 'view|advanced:export'),
'OnExportProgress' => Array ('self' => 'view|advanced:export'),
'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true),
'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true),
// theese event do not harm, but just in case check them too :)
'OnCancelEdit' => Array ('self' => true, 'subitem' => true),
'OnCancel' => Array ('self' => true, 'subitem' => true),
'OnReset' => Array ('self' => true, 'subitem' => true),
'OnSetSorting' => Array ('self' => true, 'subitem' => true),
'OnSetSortingDirect' => Array ('self' => true, 'subitem' => true),
'OnResetSorting' => Array ('self' => true, 'subitem' => true),
'OnSetFilter' => Array ('self' => true, 'subitem' => true),
'OnApplyFilters' => Array ('self' => true, 'subitem' => true),
'OnRemoveFilters' => Array ('self' => true, 'subitem' => true),
'OnSetFilterPattern' => Array ('self' => true, 'subitem' => true),
'OnSetPerPage' => Array ('self' => true, 'subitem' => true),
'OnSetPage' => Array ('self' => true, 'subitem' => true),
'OnSearch' => Array ('self' => true, 'subitem' => true),
'OnSearchReset' => Array ('self' => true, 'subitem' => true),
'OnGoBack' => Array ('self' => true, 'subitem' => true),
// it checks permission itself since flash uploader does not send cookies
'OnUploadFile' => Array ('self' => true, 'subitem' => true),
'OnDeleteFile' => Array ('self' => true, 'subitem' => true),
'OnViewFile' => Array ('self' => true, 'subitem' => true),
'OnSaveWidths' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnValidateMInputFields' => Array ('self' => 'view'),
'OnValidateField' => Array ('self' => true, 'subitem' => true),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Define alternative event processing method names
*
* @return void
* @see kEventHandler::$eventMethods
* @access protected
*/
protected function mapEvents()
{
$events_map = Array (
'OnRemoveFilters' => 'FilterAction',
'OnApplyFilters' => 'FilterAction',
'OnMassApprove' => 'iterateItems',
'OnMassDecline' => 'iterateItems',
'OnMassMoveUp' => 'iterateItems',
'OnMassMoveDown' => 'iterateItems',
);
$this->eventMethods = array_merge($this->eventMethods, $events_map);
}
/**
* Returns ID of current item to be edited
* by checking ID passed in get/post as prefix_id
* or by looking at first from selected ids, stored.
* Returned id is also stored in Session in case
* it was explicitly passed as get/post
*
* @param kEvent $event
* @return int
* @access public
*/
public function getPassedID(kEvent $event)
{
if ( $event->getEventParam('raise_warnings') === false ) {
$event->setEventParam('raise_warnings', 1);
}
if ( $event->Special == 'previous' || $event->Special == 'next' ) {
$object = $this->Application->recallObject($event->getEventParam('item'));
/* @var $object kDBItem */
$list_helper = $this->Application->recallObject('ListHelper');
/* @var $list_helper ListHelper */
$select_clause = $object->getUnitConfig()->getNavigationSelectClause(NULL);
return $list_helper->getNavigationResource($object, $event->getEventParam('list'), $event->Special == 'next', $select_clause);
}
elseif ( $event->Special == 'filter' ) {
// temporary object, used to print filter options only
return 0;
}
if ( preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1]) ) {
// <inp2:lang.auto-phrase_Field name="DateFormat"/> - returns field DateFormat value from language (LanguageId is extracted from current phrase object)
$main_object = $this->Application->recallObject($regs[1]);
/* @var $main_object kDBItem */
return $main_object->GetDBField($event->getUnitConfig()->getIDField());
}
// 1. get id from post (used in admin)
$ret = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id');
if ( ($ret !== false) && ($ret != '') ) {
return $ret;
}
// 2. get id from env (used in front)
$ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
if ( ($ret !== false) && ($ret != '') ) {
return $ret;
}
// recall selected ids array and use the first one
$ids = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids');
if ( $ids != '' ) {
$ids = explode(',', $ids);
if ( $ids ) {
$ret = array_shift($ids);
}
}
else { // if selected ids are not yet stored
$this->StoreSelectedIDs($event);
return $this->Application->GetVar($event->getPrefixSpecial() . '_id'); // StoreSelectedIDs sets this variable
}
return $ret;
}
/**
* Prepares and stores selected_ids string
* in Session and Application Variables
* by getting all checked ids from grid plus
* id passed in get/post as prefix_id
*
* @param kEvent $event
* @param Array $direct_ids
* @return Array
* @access protected
*/
protected function StoreSelectedIDs(kEvent $event, $direct_ids = NULL)
{
$wid = $this->Application->GetTopmostWid($event->Prefix);
$session_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_');
$ids = $event->getEventParam('ids');
if ( isset($direct_ids) || ($ids !== false) ) {
// save ids directly if they given + reset array indexes
$resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false);
if ( $resulting_ids ) {
$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids));
$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true);
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]);
return $resulting_ids;
}
return Array ();
}
$ret = Array ();
// May be we don't need this part: ?
$passed = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id');
if ( $passed !== false && $passed != '' ) {
array_push($ret, $passed);
}
$ids = Array ();
// get selected ids from post & save them to session
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
$id_field = $event->getUnitConfig()->getIDField();
foreach ($items_info as $id => $field_values) {
if ( getArrayValue($field_values, $id_field) ) {
array_push($ids, $id);
}
}
//$ids = array_keys($items_info);
}
$ret = array_unique(array_merge($ret, $ids));
$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $ret));
$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', !$ret); // optional when IDs are missing
// This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true)
// this smells... needs to be refactored
$first_id = getArrayValue($ret, 0);
if ( ($first_id === false) && ($event->getEventParam('raise_warnings') == 1) ) {
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->appendTrace();
}
trigger_error('Requested ID for prefix <strong>' . $event->getPrefixSpecial() . '</strong> <span class="debug_error">not passed</span>', E_USER_NOTICE);
}
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $first_id);
return $ret;
}
/**
* Returns stored selected ids as an array
*
* @param kEvent $event
* @param bool $from_session return ids from session (written, when editing was started)
* @return Array
* @access protected
*/
protected function getSelectedIDs(kEvent $event, $from_session = false)
{
if ( $from_session ) {
$wid = $this->Application->GetTopmostWid($event->Prefix);
$var_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_');
$ret = $this->Application->RecallVar($var_name);
}
else {
$ret = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids');
}
return explode(',', $ret);
}
/**
* Stores IDs, selected in grid in session
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnStoreSelected(kEvent $event)
{
$this->StoreSelectedIDs($event);
$id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
if ( $id !== false ) {
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
}
}
/**
* Returns associative array of submitted fields for current item
* Could be used while creating/editing single item -
* meaning on any edit form, except grid edit
*
* @param kEvent $event
* @return Array
* @access protected
*/
protected function getSubmittedFields(kEvent $event)
{
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
$field_values = $items_info ? array_shift($items_info) : Array ();
return $field_values;
}
/**
* Returns fields, that are not allowed to be changed from request
*
* @param Array $hash
* @return Array
* @access protected
*/
protected function getRequestProtectedFields($hash)
{
// by default don't allow changing ID or foreign key from request
$config = $this->getUnitConfig();
$fields = Array ();
$fields[] = $config->getIDField();
$parent_prefix = $config->getParentPrefix();
if ( $parent_prefix && !$this->Application->isAdmin ) {
$fields[] = $config->getForeignKey($parent_prefix);
}
return $fields;
}
/**
* Removes any information about current/selected ids
* from Application variables and Session
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function clearSelectedIDs(kEvent $event)
{
$prefix_special = $event->getPrefixSpecial();
$ids = implode(',', $this->getSelectedIDs($event, true));
$event->setEventParam('ids', $ids);
$wid = $this->Application->GetTopmostWid($event->Prefix);
$session_name = rtrim($prefix_special . '_selected_ids_' . $wid, '_');
$this->Application->RemoveVar($session_name);
$this->Application->SetVar($prefix_special . '_selected_ids', '');
$this->Application->SetVar($prefix_special . '_id', ''); // $event->getPrefixSpecial(true) . '_id' too may be
}
/**
* Common builder part for Item & List
*
* @param kDBBase|kDBItem|kDBList $object
* @param kEvent $event
* @return void
* @access protected
*/
protected function dbBuild(&$object, kEvent $event)
{
// for permission checking inside item/list build events
$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
if ( $event->getEventParam('form_name') !== false ) {
$form_name = $event->getEventParam('form_name');
}
else {
$request_forms = $this->Application->GetVar('forms', Array ());
$form_name = (string)getArrayValue($request_forms, $object->getPrefixSpecial());
}
$object->Configure($event->getEventParam('populate_ml_fields') || $event->getUnitConfig()->getPopulateMlFields(), $form_name);
$this->PrepareObject($object, $event);
$parent_event = $event->getEventParam('parent_event');
if ( is_object($parent_event) ) {
$object->setParentEvent($parent_event);
}
// force live table if specified or is original item
$live_table = $event->getEventParam('live_table') || $event->Special == 'original';
if ( $this->UseTempTables($event) && !$live_table ) {
$object->SwitchToTemp();
}
$this->Application->setEvent($event->getPrefixSpecial(), '');
$save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate';
$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', $save_event);
}
/**
* Checks, that currently loaded item is allowed for viewing (non permission-based)
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function checkItemStatus(kEvent $event)
{
$status_field = $event->getUnitConfig()->getStatusField(true);
if ( !$status_field ) {
return true;
}
if ( $status_field == 'Status' || $status_field == 'Enabled' ) {
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->isLoaded() ) {
return true;
}
return $object->GetDBField($status_field) == STATUS_ACTIVE;
}
return true;
}
/**
* Shows not found template content
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _errorNotFound(kEvent $event)
{
if ( $event->getEventParam('raise_warnings') === 0 ) {
// when it's possible, that autoload fails do nothing
return;
}
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->appendTrace();
}
trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <strong>checkItemStatus</strong>, leading to "404 Not Found"', E_USER_NOTICE);
$this->Application->UrlManager->show404();
}
/**
* Builds item (loads if needed)
*
* Pattern: Prototype Manager
*
* @param kEvent $event
* @access protected
*/
protected function OnItemBuild(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$this->dbBuild($object, $event);
$sql = $this->ItemPrepareQuery($event);
$sql = $this->Application->ReplaceLanguageTags($sql);
$object->setSelectSQL($sql);
// 2. loads if allowed
$auto_load = $event->getUnitConfig()->getAutoLoad();
$skip_autoload = $event->getEventParam('skip_autoload');
if ( $auto_load && !$skip_autoload ) {
$perm_status = true;
$user_id = $this->Application->InitDone ? $this->Application->RecallVar('user_id') : USER_ROOT;
$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
$status_checked = false;
if ( $user_id == USER_ROOT || $this->CheckPermission($event) ) {
// don't autoload item, when user doesn't have view permission
$this->LoadItem($event);
$status_checked = true;
$editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false;
if ( $user_id != USER_ROOT && !$this->Application->isAdmin && !($editing_mode || $this->checkItemStatus($event)) ) {
// non-root user AND on front-end AND (not editing mode || incorrect status)
$perm_status = false;
}
}
else {
$perm_status = false;
}
if ( !$perm_status ) {
// when no permission to view item -> redirect to no permission template
$this->_processItemLoadingError($event, $status_checked);
}
}
$actions = $this->Application->recallObject('kActions');
/* @var $actions Params */
$actions->Set($event->getPrefixSpecial() . '_GoTab', '');
$actions->Set($event->getPrefixSpecial() . '_GoId', '');
$actions->Set('forms[' . $event->getPrefixSpecial() . ']', $object->getFormName());
}
/**
* Processes case, when item wasn't loaded because of lack of permissions
*
* @param kEvent $event
* @param bool $status_checked
* @throws kNoPermissionException
* @return void
* @access protected
*/
protected function _processItemLoadingError($event, $status_checked)
{
$current_template = $this->Application->GetVar('t');
$redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
$error_msg = 'ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <strong>' . ($status_checked ? 'checkItemStatus' : 'CheckPermission') . '</strong>';
if ( $current_template == $redirect_template ) {
// don't perform "no_permission" redirect if already on a "no_permission" template
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->appendTrace();
}
trigger_error($error_msg, E_USER_NOTICE);
return;
}
if ( MOD_REWRITE ) {
$redirect_params = Array (
'm_cat_id' => 0,
'next_template' => 'external:' . $_SERVER['REQUEST_URI'],
);
}
else {
$redirect_params = Array (
'next_template' => $current_template,
);
}
$exception = new kNoPermissionException($error_msg);
$exception->setup($redirect_template, $redirect_params);
throw $exception;
}
/**
* Build sub-tables array from configs
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnTempHandlerBuild(kEvent $event)
{
$object = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
/* @var $object kTempTablesHandler */
$parent_event = $event->getEventParam('parent_event');
/* @var $parent_event kEvent */
if ( is_object($parent_event) ) {
$object->setParentEvent($parent_event);
}
$object->BuildTables($event->Prefix, $this->getSelectedIDs($event));
}
/**
* Checks, that object used in event should use temp tables
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function UseTempTables(kEvent $event)
{
$top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual
$special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event);
return $this->Application->IsTempMode($event->Prefix, $special);
}
/**
* Load item if id is available
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function LoadItem(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$id = $this->getPassedID($event);
if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) {
// object is already loaded by same id
return ;
}
if ( $object->Load($id) ) {
$actions = $this->Application->recallObject('kActions');
/* @var $actions Params */
$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
}
else {
$object->setID( is_array($id) ? false : $id );
}
}
/**
* Builds list
*
* Pattern: Prototype Manager
*
* @param kEvent $event
* @access protected
*/
protected function OnListBuild(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBList */
/*if ( $this->Application->isDebugMode() ) {
$event_params = http_build_query($event->getEventParams());
$this->Application->Debugger->appendHTML('InitList "<strong>' . $event->getPrefixSpecial() . '</strong>" (' . $event_params . ')');
}*/
$this->dbBuild($object, $event);
if ( !$object->isMainList() && $event->getEventParam('main_list') ) {
// once list is set to main, then even "requery" parameter can't remove that
/*$passed = $this->Application->GetVar('passed');
$this->Application->SetVar('passed', $passed . ',' . $event->Prefix);*/
$object->becameMain();
}
$object->setGridName($event->getEventParam('grid'));
$sql = $this->ListPrepareQuery($event);
$sql = $this->Application->ReplaceLanguageTags($sql);
$object->setSelectSQL($sql);
$object->reset();
if ( $event->getEventParam('skip_parent_filter') === false ) {
$object->linkToParent($this->getMainSpecial($event));
}
$this->AddFilters($event);
$this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex.
$this->SetPagination($event);
$this->SetSorting($event);
$actions = $this->Application->recallObject('kActions');
/* @var $actions Params */
$actions->Set('remove_specials[' . $event->getPrefixSpecial() . ']', '0');
$actions->Set($event->getPrefixSpecial() . '_GoTab', '');
}
/**
* Returns special of main item for linking with sub-item
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function getMainSpecial(kEvent $event)
{
$main_special = $event->getEventParam('main_special');
if ( $main_special === false ) {
// main item's special not passed
if ( substr($event->Special, -5) == '-item' ) {
// temp handler added "-item" to given special -> process that here
return substr($event->Special, 0, -5);
}
// by default subitem's special is used for main item searching
return $event->Special;
}
return $main_special;
}
/**
* Apply any custom changes to list's sql query
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
}
/**
* Set's new per-page for grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetPerPage(kEvent $event)
{
$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
$event->SetRedirectParam($event->getPrefixSpecial() . '_PerPage', $per_page);
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
if ( !$this->Application->isAdminUser ) {
$list_helper = $this->Application->recallObject('ListHelper');
/* @var $list_helper ListHelper */
$this->_passListParams($event, 'per_page');
}
}
/**
* Occurs when page is changed (only for hooking)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetPage(kEvent $event)
{
$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
$event->SetRedirectParam($event->getPrefixSpecial() . '_Page', $page);
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
if ( !$this->Application->isAdminUser ) {
$this->_passListParams($event, 'page');
}
}
/**
* Passes through main list pagination and sorting
*
* @param kEvent $event
* @param string $skip_var
* @return void
* @access protected
*/
protected function _passListParams($event, $skip_var)
{
$param_names = array_diff(Array ('page', 'per_page', 'sort_by'), Array ($skip_var));
$list_helper = $this->Application->recallObject('ListHelper');
/* @var $list_helper ListHelper */
foreach ($param_names as $param_name) {
$value = $this->Application->GetVar($param_name);
switch ($param_name) {
case 'page':
if ( $value > 1 ) {
$event->SetRedirectParam('page', $value);
}
break;
case 'per_page':
if ( $value > 0 ) {
if ( $value != $list_helper->getDefaultPerPage($event->Prefix) ) {
$event->SetRedirectParam('per_page', $value);
}
}
break;
case 'sort_by':
$event->setPseudoClass('_List');
$object = $event->getObject(Array ('main_list' => 1));
/* @var $object kDBList */
if ( $list_helper->hasUserSorting($object) ) {
$event->SetRedirectParam('sort_by', $value);
}
break;
}
}
}
/**
* Set's correct page for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetPagination(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBList */
// get PerPage (forced -> session -> config -> 10)
$object->SetPerPage($this->getPerPage($event));
// main lists on Front-End have special get parameter for page
$page = $object->isMainList() ? $this->Application->GetVar('page') : false;
if ( !$page ) {
// page is given in "env" variable for given prefix
$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
}
if ( !$page && $event->Special ) {
// when not part of env, then variables like "prefix.special_Page" are
// replaced (by PHP) with "prefix_special_Page", so check for that too
$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
}
if ( !$object->isMainList() ) {
// main lists doesn't use session for page storing
$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
if ( $page ) {
// page found in request -> store in session
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
}
else {
// page not found in request -> get from session
$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
}
if ( !$event->getEventParam('skip_counting') ) {
// when stored page is larger, then maximal list page number
// (such case is also processed in kDBList::Query method)
$pages = $object->GetTotalPages();
if ( $page > $pages ) {
$page = 1;
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true);
}
}
}
$object->SetPage($page);
}
/**
* Returns current per-page setting for list
*
* @param kEvent $event
* @return int
* @access protected
*/
protected function getPerPage(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBList */
$per_page = $event->getEventParam('per_page');
if ( $per_page ) {
// per-page is passed as tag parameter to PrintList, InitList, etc.
$config_mapping = $event->getUnitConfig()->getConfigMapping();
// 2. per-page setting is stored in configuration variable
if ( $config_mapping ) {
// such pseudo per-pages are only defined in templates directly
switch ($per_page) {
case 'short_list':
$per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']);
break;
case 'default':
$per_page = $this->Application->ConfigValue($config_mapping['PerPage']);
break;
}
}
return $per_page;
}
if ( !$per_page && $object->isMainList() ) {
// main lists on Front-End have special get parameter for per-page
$per_page = $this->Application->GetVar('per_page');
}
if ( !$per_page ) {
// per-page is given in "env" variable for given prefix
$per_page = $this->Application->GetVar($event->getPrefixSpecial() . '_PerPage');
}
if ( !$per_page && $event->Special ) {
// when not part of env, then variables like "prefix.special_PerPage" are
// replaced (by PHP) with "prefix_special_PerPage", so check for that too
$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
}
if ( !$object->isMainList() ) {
// per-page given in env and not in main list
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
if ( $per_page ) {
// per-page found in request -> store in session and persistent session
$this->setListSetting($event, 'PerPage', $per_page);
}
else {
// per-page not found in request -> get from pesistent session (or session)
$per_page = $this->getListSetting($event, 'PerPage');
}
}
if ( !$per_page ) {
// per page wan't found in request/session/persistent session
$list_helper = $this->Application->recallObject('ListHelper');
/* @var $list_helper ListHelper */
// allow to override default per-page value from tag
$default_per_page = $event->getEventParam('default_per_page');
if ( !is_numeric($default_per_page) ) {
$default_per_page = $this->Application->ConfigValue('DefaultGridPerPage');
}
$per_page = $list_helper->getDefaultPerPage($event->Prefix, $default_per_page);
}
return $per_page;
}
/**
* Set's correct sorting for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetSorting(kEvent $event)
{
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
if ( $object->isMainList() ) {
$sort_by = $this->Application->GetVar('sort_by');
$cur_sort1 = $cur_sort1_dir = $cur_sort2 = $cur_sort2_dir = false;
if ( $sort_by ) {
$sortings = explode('|', $sort_by);
list ($cur_sort1, $cur_sort1_dir) = explode(',', $sortings[0]);
if ( isset($sortings[1]) ) {
list ($cur_sort2, $cur_sort2_dir) = explode(',', $sortings[1]);
}
}
}
else {
$sorting_settings = $this->getListSetting($event, 'Sortings');
$cur_sort1 = getArrayValue($sorting_settings, 'Sort1');
$cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir');
$cur_sort2 = getArrayValue($sorting_settings, 'Sort2');
$cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir');
}
$tag_sort_by = $event->getEventParam('sort_by');
if ( $tag_sort_by ) {
if ( $tag_sort_by == 'random' ) {
$object->AddOrderField('RAND()', '');
}
else {
// multiple sortings could be specified at once
$tag_sort_by = explode('|', $tag_sort_by);
foreach ($tag_sort_by as $sorting_element) {
list ($by, $dir) = explode(',', $sorting_element);
$object->AddOrderField($by, $dir);
}
}
}
$list_sortings = $this->_getDefaultSorting($event);
// use default if not specified in session
if ( !$cur_sort1 || !$cur_sort1_dir ) {
$sorting = getArrayValue($list_sortings, 'Sorting');
if ( $sorting ) {
reset($sorting);
$cur_sort1 = key($sorting);
$cur_sort1_dir = current($sorting);
if ( next($sorting) ) {
$cur_sort2 = key($sorting);
$cur_sort2_dir = current($sorting);
}
}
}
// always add forced sorting before any user sorting fields
$forced_sorting = getArrayValue($list_sortings, 'ForcedSorting');
/* @var $forced_sorting Array */
if ( $forced_sorting ) {
foreach ($forced_sorting as $field => $dir) {
$object->AddOrderField($field, $dir);
}
}
// add user sorting fields
if ( $cur_sort1 != '' && $cur_sort1_dir != '' ) {
$object->AddOrderField($cur_sort1, $cur_sort1_dir);
}
if ( $cur_sort2 != '' && $cur_sort2_dir != '' ) {
$object->AddOrderField($cur_sort2, $cur_sort2_dir);
}
}
/**
* Returns default list sortings
*
* @param kEvent $event
* @return Array
* @access protected
*/
protected function _getDefaultSorting(kEvent $event)
{
$config = $event->getUnitConfig();
$sorting_configs = $config->getConfigMapping();
$list_sortings = $config->getListSortingsBySpecial($event);
if ( $sorting_configs && array_key_exists('DefaultSorting1Field', $sorting_configs) ) {
// sorting defined in configuration variables overrides one from unit config
$list_sortings['Sorting'] = Array (
$this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']),
$this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']),
);
// TODO: lowercase configuration variable values in db, instead of here
$list_sortings['Sorting'] = array_map('strtolower', $list_sortings['Sorting']);
}
return $list_sortings ? $list_sortings : Array ();
}
/**
* Gets list setting by name (persistent or real session)
*
* @param kEvent $event
* @param string $variable_name
* @return string|Array
* @access protected
*/
protected function getListSetting(kEvent $event, $variable_name)
{
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial();
// get sorting from persistent session
$default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false;
$variable_value = $this->Application->RecallPersistentVar($storage_prefix . '_' . $variable_name . '.' . $view_name, $default_value);
/*if ( !$variable_value ) {
// get sorting from session
$variable_value = $this->Application->RecallVar($storage_prefix . '_' . $variable_name);
}*/
if ( kUtil::IsSerialized($variable_value) ) {
$variable_value = unserialize($variable_value);
}
return $variable_value;
}
/**
* Sets list setting by name (persistent and real session)
*
* @param kEvent $event
* @param string $variable_name
* @param string|Array $variable_value
* @return void
* @access protected
*/
protected function setListSetting(kEvent $event, $variable_name, $variable_value = NULL)
{
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
// $this->Application->StoreVar($event->getPrefixSpecial() . '_' . $variable_name, $variable_value, true); //true for optional
if ( isset($variable_value) ) {
if ( is_array($variable_value) ) {
$variable_value = serialize($variable_value);
}
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name, $variable_value, true); //true for optional
}
else {
$this->Application->RemovePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name);
}
}
/**
* Add filters found in session
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function AddFilters(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBList */
$edit_mark = rtrim($this->Application->GetSID() . '_' . $this->Application->GetTopmostWid($event->Prefix), '_');
// add search filter
$filter_data = $this->Application->RecallVar($event->getPrefixSpecial() . '_search_filter');
if ( $filter_data ) {
$filter_data = unserialize($filter_data);
foreach ($filter_data as $filter_field => $filter_params) {
$filter_type = ($filter_params['type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER;
$filter_value = str_replace(EDIT_MARK, $edit_mark, $filter_params['value']);
$object->addFilter($filter_field, $filter_value, $filter_type, kDBList::FLT_SEARCH);
}
}
// add custom filter
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_custom_filter.' . $view_name);
if ( $custom_filters ) {
$grid_name = $event->getEventParam('grid');
$custom_filters = unserialize($custom_filters);
if ( isset($custom_filters[$grid_name]) ) {
foreach ($custom_filters[$grid_name] as $field_name => $field_options) {
list ($filter_type, $field_options) = each($field_options);
if ( isset($field_options['value']) && $field_options['value'] ) {
$filter_type = ($field_options['sql_filter_type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER;
$filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']);
$object->addFilter($field_name, $filter_value, $filter_type, kDBList::FLT_CUSTOM);
}
}
}
}
// add view filter
$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
if ( $view_filter ) {
$view_filter = unserialize($view_filter);
$temp_filter = $this->Application->makeClass('kMultipleFilter');
/* @var $temp_filter kMultipleFilter */
$filter_menu = $event->getUnitConfig()->getFilterMenu();
$group_key = 0;
$group_count = count($filter_menu['Groups']);
while ($group_key < $group_count) {
$group_info = $filter_menu['Groups'][$group_key];
$temp_filter->setType(constant('kDBList::FLT_TYPE_' . $group_info['mode']));
$temp_filter->clearFilters();
foreach ($group_info['filters'] as $flt_id) {
$sql_key = getArrayValue($view_filter, $flt_id) ? 'on_sql' : 'off_sql';
if ( $filter_menu['Filters'][$flt_id][$sql_key] != '' ) {
$temp_filter->addFilter('view_filter_' . $flt_id, $filter_menu['Filters'][$flt_id][$sql_key]);
}
}
$object->addFilter('view_group_' . $group_key, $temp_filter, $group_info['type'], kDBList::FLT_VIEW);
$group_key++;
}
}
// add item filter
if ( $object->isMainList() ) {
$this->applyItemFilters($event);
}
}
/**
* Applies item filters
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function applyItemFilters($event)
{
$filter_values = $this->Application->GetVar('filters', Array ());
if ( !$filter_values ) {
return;
}
$object = $event->getObject();
/* @var $object kDBList */
$where_clause = Array (
'ItemPrefix = ' . $this->Conn->qstr($object->Prefix),
'FilterField IN (' . implode(',', $this->Conn->qstrArray(array_keys($filter_values))) . ')',
'Enabled = 1',
);
$sql = 'SELECT *
FROM ' . $this->Application->getUnitConfig('item-filter')->getTableName() . '
WHERE (' . implode(') AND (', $where_clause) . ')';
$filters = $this->Conn->Query($sql, 'FilterField');
foreach ($filters as $filter_field => $filter_data) {
$filter_value = $filter_values[$filter_field];
if ( "$filter_value" === '' ) {
// ListManager don't pass empty values, but check here just in case
continue;
}
$table_name = $object->isVirtualField($filter_field) ? '' : '%1$s.';
switch ($filter_data['FilterType']) {
case 'radio':
$filter_value = $table_name . '`' . $filter_field . '` = ' . $this->Conn->qstr($filter_value);
break;
case 'checkbox':
$filter_value = explode('|', substr($filter_value, 1, -1));
$filter_value = $this->Conn->qstrArray($filter_value, 'escape');
if ( $object->GetFieldOption($filter_field, 'multiple') ) {
$filter_value = $table_name . '`' . $filter_field . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $filter_field . '` LIKE "%|', $filter_value) . '|%"';
}
else {
$filter_value = $table_name . '`' . $filter_field . '` IN (' . implode(',', $filter_value) . ')';
}
break;
case 'range':
$filter_value = $this->Conn->qstrArray(explode('-', $filter_value));
$filter_value = $table_name . '`' . $filter_field . '` BETWEEN ' . $filter_value[0] . ' AND ' . $filter_value[1];
break;
}
$object->addFilter('item_filter_' . $filter_field, $filter_value, $object->isVirtualField($filter_field) ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER);
}
}
/**
* Set's new sorting for list
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetSorting(kEvent $event)
{
$sorting_settings = $this->getListSetting($event, 'Sortings');
$cur_sort1 = getArrayValue($sorting_settings, 'Sort1');
$cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir');
$use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting');
if ( $use_double_sorting ) {
$cur_sort2 = getArrayValue($sorting_settings, 'Sort2');
$cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir');
}
$passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Sort1');
if ( $cur_sort1 == $passed_sort1 ) {
$cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc';
}
else {
if ( $use_double_sorting ) {
$cur_sort2 = $cur_sort1;
$cur_sort2_dir = $cur_sort1_dir;
}
$cur_sort1 = $passed_sort1;
$cur_sort1_dir = 'asc';
}
$sorting_settings = Array ('Sort1' => $cur_sort1, 'Sort1_Dir' => $cur_sort1_dir);
if ( $use_double_sorting ) {
$sorting_settings['Sort2'] = $cur_sort2;
$sorting_settings['Sort2_Dir'] = $cur_sort2_dir;
}
$this->setListSetting($event, 'Sortings', $sorting_settings);
}
/**
* Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetSortingDirect(kEvent $event)
{
// used on Front-End in category item lists
$prefix_special = $event->getPrefixSpecial();
$combined = $this->Application->GetVar($event->getPrefixSpecial(true) . '_CombinedSorting');
if ( $combined ) {
list ($field, $dir) = explode('|', $combined);
if ( $this->Application->isAdmin || !$this->Application->GetVar('main_list') ) {
$this->setListSetting($event, 'Sortings', Array ('Sort1' => $field, 'Sort1_Dir' => $dir));
}
else {
$event->setPseudoClass('_List');
$this->Application->SetVar('sort_by', $field . ',' . $dir);
$object = $event->getObject(Array ('main_list' => 1));
/* @var $object kDBList */
$list_helper = $this->Application->recallObject('ListHelper');
/* @var $list_helper ListHelper */
$this->_passListParams($event, 'sort_by');
if ( $list_helper->hasUserSorting($object) ) {
$event->SetRedirectParam('sort_by', $field . ',' . strtolower($dir));
}
$event->SetRedirectParam('pass', 'm');
}
return;
}
// used in "View Menu -> Sort" menu in administrative console
$field_pos = $this->Application->GetVar($event->getPrefixSpecial(true) . '_SortPos');
$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos, $prefix_special . '_Sort' . $field_pos);
$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos . '_Dir', $prefix_special . '_Sort' . $field_pos . '_Dir');
}
/**
* Reset grid sorting to default (from config)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnResetSorting(kEvent $event)
{
$this->setListSetting($event, 'Sortings');
}
/**
* Sets grid refresh interval
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetAutoRefreshInterval(kEvent $event)
{
$refresh_interval = $this->Application->GetVar('refresh_interval');
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_interval);
}
/**
* Changes auto-refresh state for grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAutoRefreshToggle(kEvent $event)
{
$refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals');
if ( !$refresh_intervals ) {
return;
}
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name);
if ( $auto_refresh === false ) {
$refresh_intervals = explode(',', $refresh_intervals);
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_intervals[0]);
}
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name, $auto_refresh ? 0 : 1);
}
/**
* Creates needed sql query to load item,
* if no query is defined in config for
* special requested, then use list query
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function ItemPrepareQuery(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$sqls = $object->getFormOption('ItemSQLs', Array ());
$special = isset($sqls[$event->Special]) ? $event->Special : '';
// preferred special not found in ItemSQLs -> use analog from ListSQLs
return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event);
}
/**
* Creates needed sql query to load list,
* if no query is defined in config for
* special requested, then use default
* query
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function ListPrepareQuery(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$sqls = $object->getFormOption('ListSQLs', Array ());
return $sqls[array_key_exists($event->Special, $sqls) ? $event->Special : ''];
}
/**
* Apply custom processing to item
*
* @param kEvent $event
* @param string $type
* @return void
* @access protected
*/
protected function customProcessing(kEvent $event, $type)
{
}
/* Edit Events mostly used in Admin */
/**
* Creates new kDBItem
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCreate(kEvent $event)
{
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( !$items_info ) {
return;
}
list($id, $field_values) = each($items_info);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$event->setEventParam('form_data', $field_values);
$this->customProcessing($event, 'before');
// look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default
if ( $object->Create($event->getEventParam('ForceCreateId')) ) {
$this->customProcessing($event, 'after');
$event->SetRedirectParam('opener', 'u');
return;
}
$event->redirect = false;
$event->status = kEvent::erFAIL;
$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
$object->setID($id);
}
/**
* Updates kDBItem
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdate(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$this->_update($event);
$event->SetRedirectParam('opener', 'u');
}
/**
* Updates data in database based on request
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _update(kEvent $event)
{
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
if ( $items_info ) {
foreach ($items_info as $id => $field_values) {
$object->Load($id);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$event->setEventParam('form_data', $field_values);
$this->customProcessing($event, 'before');
if ( $object->Update($id) ) {
$this->customProcessing($event, 'after');
$event->status = kEvent::erSUCCESS;
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
}
/**
* Delete's kDBItem object
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDelete(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($this->getPassedID($event)));
}
/**
* Deletes all records from table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDeleteAll(kEvent $event)
{
$config = $event->getUnitConfig();
$sql = 'SELECT ' . $config->getIDField() . '
FROM ' . $config->getTableName();
$ids = $this->Conn->GetCol($sql);
if ( $ids ) {
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
}
}
/**
* Prepares new kDBItem object
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnNew(kEvent $event)
{
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$object->Clear(0);
$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
if ( $event->getEventParam('top_prefix') != $event->Prefix ) {
// this is subitem prefix, so use main item special
$table_info = $object->getLinkedInfo($this->getMainSpecial($event));
}
else {
$table_info = $object->getLinkedInfo();
}
$object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']);
$event->redirect = false;
}
/**
* Cancels kDBItem Editing/Creation
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCancel(kEvent $event)
{
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
$delete_ids = Array ();
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
foreach ($items_info as $id => $field_values) {
$object->Load($id);
// record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it
if ( $object->isLoaded() && !$object->Validate() && ($id <= 0) ) {
$delete_ids[] = $id;
}
}
if ( $delete_ids ) {
$temp_handler->DeleteItems($event->Prefix, $event->Special, $delete_ids);
}
}
$event->SetRedirectParam('opener', 'u');
}
/**
* Deletes all selected items.
* Automatically recurse into sub-items using temp handler, and deletes sub-items
* by calling its Delete method if sub-item has AutoDelete set to true in its config file
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassDelete(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return ;
}
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$ids = $this->StoreSelectedIDs($event);
$event->setEventParam('ids', $ids);
$this->customProcessing($event, 'before');
$ids = $event->getEventParam('ids');
if ( $ids ) {
$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
}
$this->clearSelectedIDs($event);
}
/**
* Sets window id (of first opened edit window) to temp mark in uls
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function setTempWindowID(kEvent $event)
{
$prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true));
foreach ($prefixes as $prefix) {
$mode = $this->Application->GetVar($prefix . '_mode');
if ($mode == 't') {
$wid = $this->Application->GetVar('m_wid');
$this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid);
break;
}
}
}
/**
* Prepare temp tables and populate it
* with items selected in the grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnEdit(kEvent $event)
{
$this->setTempWindowID($event);
$ids = $this->StoreSelectedIDs($event);
$object = $event->getObject(Array('skip_autoload' => true));
/* @var $object kDBItem */
$object->setPendingActions(null, true);
$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
$this->Application->RemoveVar($changes_var_name);
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$temp_handler->PrepareEdit();
$event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId());
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids));
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
$simultaneous_edit_message = $this->Application->GetVar('_simultaneous_edit_message');
if ( $simultaneous_edit_message ) {
$event->SetRedirectParam('_simultaneous_edit_message', $simultaneous_edit_message);
}
}
/**
* Saves content of temp table into live and
* redirects to event' default redirect (normally grid template)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSave(kEvent $event)
{
$event->CallSubEvent('OnPreSave');
if ( $event->status != kEvent::erSUCCESS ) {
return;
}
$skip_master = false;
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
if ( !$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$live_ids = $temp_handler->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array ());
if ( $live_ids === false ) {
// coping from table failed, because we have another coping process to same table, that wasn't finished
$event->status = kEvent::erFAIL;
return;
}
if ( $live_ids ) {
// ensure, that newly created item ids are available as if they were selected from grid
// NOTE: only works if main item has sub-items !!!
$this->StoreSelectedIDs($event, $live_ids);
}
$object = $event->getObject();
/* @var $object kDBItem */
$this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges());
}
else {
$event->status = kEvent::erFAIL;
}
$this->clearSelectedIDs($event);
$event->SetRedirectParam('opener', 'u');
$this->Application->RemoveVar($event->getPrefixSpecial() . '_modified');
// all temp tables are deleted here => all after hooks should think, that it's live mode now
$this->Application->SetVar($event->Prefix . '_mode', '');
}
/**
* Saves changes made in temporary table to log
*
* @param string $changes_var_name
* @param bool $save
* @return void
* @access public
*/
public function SaveLoggedChanges($changes_var_name, $save = true)
{
// 1. get changes, that were made
$changes = $this->Application->RecallVar($changes_var_name);
$changes = $changes ? unserialize($changes) : Array ();
$this->Application->RemoveVar($changes_var_name);
if (!$changes) {
// no changes, skip processing
return ;
}
// TODO: 2. optimize change log records (replace multiple changes to same record with one change record)
$to_increment = Array ();
// 3. collect serials to reset based on foreign keys
foreach ($changes as $index => $rec) {
if (array_key_exists('DependentFields', $rec)) {
foreach ($rec['DependentFields'] as $field_name => $field_value) {
// will be "ci|ItemResourceId:345"
$to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value;
// also reset sub-item prefix general serial
$to_increment[] = $rec['Prefix'];
}
unset($changes[$index]['DependentFields']);
}
unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']);
}
// 4. collect serials to reset based on changed ids
foreach ($changes as $change) {
$to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId'];
if ($change['MasterPrefix'] != $change['Prefix']) {
// also reset sub-item prefix general serial
$to_increment[] = $change['Prefix'];
// will be "ci|ItemResourceId"
$to_increment[] = $change['Prefix'] . '|' . $change['ItemId'];
}
}
// 5. reset serials collected before
$to_increment = array_unique($to_increment);
$this->Application->incrementCacheSerial($this->Prefix);
foreach ($to_increment as $to_increment_mixed) {
if (strpos($to_increment_mixed, '|') !== false) {
list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2);
$this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id);
}
else {
$this->Application->incrementCacheSerial($to_increment_mixed);
}
}
// save changes to database
$session_log_id = $this->Application->RecallVar('_SessionLogId_');
if (!$save || !$session_log_id) {
// saving changes to database disabled OR related session log missing
return ;
}
$add_fields = Array (
'PortalUserId' => $this->Application->RecallVar('user_id'),
'SessionLogId' => $session_log_id,
);
$change_log_table = $this->Application->getUnitConfig('change-log')->getTableName();
foreach ($changes as $rec) {
$this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table);
}
$this->Application->incrementCacheSerial('change-log');
$sql = 'UPDATE ' . $this->Application->getUnitConfig('session-log')->getTableName() . '
SET AffectedItems = AffectedItems + ' . count($changes) . '
WHERE SessionLogId = ' . $session_log_id;
$this->Conn->Query($sql);
$this->Application->incrementCacheSerial('session-log');
}
/**
* Cancels edit
* Removes all temp tables and clears selected ids
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCancelEdit(kEvent $event)
{
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$temp_handler->CancelEdit();
$this->clearSelectedIDs($event);
$this->Application->RemoveVar($event->getPrefixSpecial() . '_modified');
$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
$this->Application->RemoveVar($changes_var_name);
$event->SetRedirectParam('opener', 'u');
}
/**
* Allows to determine if we are creating new item or editing already created item
*
* @param kEvent $event
* @return bool
* @access public
*/
public function isNewItemCreate(kEvent $event)
{
$object = $event->getObject( Array ('raise_warnings' => 0) );
/* @var $object kDBItem */
return !$object->isLoaded();
}
/**
* Saves edited item into temp table
* If there is no id, new item is created in temp table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSave(kEvent $event)
{
// if there is no id - it means we need to create an item
if ( is_object($event->MasterEvent) ) {
$event->MasterEvent->setEventParam('IsNew', false);
}
if ( $this->isNewItemCreate($event) ) {
$event->CallSubEvent('OnPreSaveCreated');
if ( is_object($event->MasterEvent) ) {
$event->MasterEvent->setEventParam('IsNew', true);
}
return ;
}
// don't just call OnUpdate event here, since it maybe overwritten to Front-End specific behavior
$this->_update($event);
}
/**
* Analog of OnPreSave event for usage in AJAX request
*
* @param kEvent $event
*
* @return void
*/
protected function OnPreSaveAjax(kEvent $event)
{
$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
/* @var $ajax_form_helper AjaxFormHelper */
$ajax_form_helper->transitEvent($event, 'OnPreSave');
}
/**
* [HOOK] Saves sub-item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveSubItem(kEvent $event)
{
$not_created = $this->isNewItemCreate($event);
$event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate');
if ( $event->status == kEvent::erSUCCESS ) {
$object = $event->getObject();
/* @var $object kDBItem */
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID());
}
else {
$event->MasterEvent->status = $event->status;
}
$event->SetRedirectParam('opener', 's');
}
/**
* Saves edited item in temp table and loads
* item with passed id in current template
* Used in Prev/Next buttons
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveAndGo(kEvent $event)
{
$event->CallSubEvent('OnPreSave');
if ( $event->status == kEvent::erSUCCESS ) {
$id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId');
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
}
}
/**
* Saves edited item in temp table and goes
* to passed tabs, by redirecting to it with OnPreSave event
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveAndGoToTab(kEvent $event)
{
$event->CallSubEvent('OnPreSave');
if ( $event->status == kEvent::erSUCCESS ) {
$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
}
}
/**
* Saves editable list and goes to passed tab,
* by redirecting to it with empty event
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdateAndGoToTab(kEvent $event)
{
$event->setPseudoClass('_List');
$event->CallSubEvent('OnUpdate');
if ( $event->status == kEvent::erSUCCESS ) {
$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
}
}
/**
* Prepare temp tables for creating new item
* but does not create it. Actual create is
* done in OnPreSaveCreated
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
$this->setTempWindowID($event);
$this->clearSelectedIDs($event);
$this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId());
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$temp_handler->PrepareEdit();
$object->setID(0);
$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
$this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1);
$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
$this->Application->RemoveVar($changes_var_name);
$event->redirect = false;
}
/**
* Creates a new item in temp table and
* stores item id in App vars and Session on success
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveCreated(kEvent $event)
{
$object = $event->getObject( Array('skip_autoload' => true) );
/* @var $object kDBItem */
$field_values = $this->getSubmittedFields($event);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$event->setEventParam('form_data', $field_values);
$this->customProcessing($event, 'before');
if ( $object->Create() ) {
$this->customProcessing($event, 'after');
$event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID());
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
$object->setID(0);
}
}
/**
* Reloads form to loose all changes made during item editing
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnReset(kEvent $event)
{
//do nothing - should reset :)
if ( $this->isNewItemCreate($event) ) {
// just reset id to 0 in case it was create
$object = $event->getObject( Array ('skip_autoload' => true) );
/* @var $object kDBItem */
$object->setID(0);
$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
}
}
/**
* Apply same processing to each item being selected in grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function iterateItems(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return ;
}
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
$config = $event->getUnitConfig();
$status_field = $config->getStatusField(true);
$order_field = $config->getOrderField();
if ( !$order_field ) {
$order_field = 'Priority';
}
foreach ($ids as $id) {
$object->Load($id);
switch ( $event->Name ) {
case 'OnMassApprove':
$object->SetDBField($status_field, 1);
break;
case 'OnMassDecline':
$object->SetDBField($status_field, 0);
break;
case 'OnMassMoveUp':
$object->SetDBField($order_field, $object->GetDBField($order_field) + 1);
break;
case 'OnMassMoveDown':
$object->SetDBField($order_field, $object->GetDBField($order_field) - 1);
break;
}
if ( $object->Update() ) {
$event->status = kEvent::erSUCCESS;
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
$this->clearSelectedIDs($event);
}
/**
* Clones selected items in list
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassClone(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
$temp_handler->CloneItems($event->Prefix, $event->Special, $ids);
}
$this->clearSelectedIDs($event);
}
/**
* Checks if given value is present in given array
*
* @param Array $records
* @param string $field
* @param mixed $value
* @return bool
* @access protected
*/
protected function check_array($records, $field, $value)
{
foreach ($records as $record) {
if ($record[$field] == $value) {
return true;
}
}
return false;
}
/**
* Saves data from editing form to database without checking required fields
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSavePopup(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$this->RemoveRequiredFields($object);
$event->CallSubEvent('OnPreSave');
$event->SetRedirectParam('opener', 'u');
}
/* End of Edit events */
// III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item
/**
* Occurs before loading item, 'id' parameter
* allows to get id of item being loaded
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemLoad(kEvent $event)
{
}
/**
* Occurs after loading item, 'id' parameter
* allows to get id of item that was loaded
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemLoad(kEvent $event)
{
}
/**
* Occurs before creating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
}
/**
* Occurs after creating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemCreate(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->IsTempTable() ) {
$this->_processPendingActions($event);
}
}
/**
* Occurs before updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
}
/**
* Occurs after updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemUpdate(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->IsTempTable() ) {
$this->_processPendingActions($event);
}
}
/**
* Occurs before deleting item, id of item being
* deleted is stored as 'id' event param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemDelete(kEvent $event)
{
}
/**
* Occurs after deleting item, id of deleted item
* is stored as 'id' param of event
*
* Also deletes subscriptions to that particual item once it's deleted
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemDelete(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
// 1. delete direct subscriptions to item, that was deleted
$this->_deleteSubscriptions($event->Prefix, 'ItemId', $object->GetID());
// 2. delete this item sub-items subscriptions, that reference item, that was deleted
foreach ($event->getUnitConfig()->getSubItems(Array ()) as $sub_prefix) {
$this->_deleteSubscriptions($sub_prefix, 'ParentItemId', $object->GetID());
}
}
/**
* Deletes all subscriptions, associated with given item
*
* @param string $prefix
* @param string $field
* @param int $value
* @return void
* @access protected
*/
protected function _deleteSubscriptions($prefix, $field, $value)
{
$sql = 'SELECT TemplateId
FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
WHERE BindToSystemEvent REGEXP "' . $this->Conn->escape($prefix) . '(\\\\.[^:]*:.*|:.*)"';
$email_template_ids = $this->Conn->GetCol($sql);
if ( !$email_template_ids ) {
return;
}
// e-mail events, connected to that unit prefix are found
$sql = 'SELECT SubscriptionId
FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
WHERE ' . $field . ' = ' . $value . ' AND EmailTemplateId IN (' . implode(',', $email_template_ids) . ')';
$ids = $this->Conn->GetCol($sql);
if ( !$ids ) {
return;
}
$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler');
/* @var $temp_handler kTempTablesHandler */
$temp_handler->DeleteItems('system-event-subscription', '', $ids);
}
/**
* Occurs before validation attempt
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemValidate(kEvent $event)
{
}
/**
* Occurs after successful item validation
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemValidate(kEvent $event)
{
}
/**
* Occurs after an item has been copied to temp
* Id of copied item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterCopyToTemp(kEvent $event)
{
}
/**
* Occurs before an item is deleted from live table when copying from temp
* (temp handler deleted all items from live and then copy over all items from temp)
* Id of item being deleted is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeDeleteFromLive(kEvent $event)
{
}
/**
* Occurs before an item is copied to live table (after all foreign keys have been updated)
* Id of item being copied is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeCopyToLive(kEvent $event)
{
}
/**
* Occurs after an item has been copied to live table
* Id of copied item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterCopyToLive(kEvent $event)
{
$object = $event->getObject(array('skip_autoload' => true));
/* @var $object kDBItem */
$object->SwitchToLive();
$object->Load($event->getEventParam('id'));
$this->_processPendingActions($event);
}
/**
* Processing file pending actions (e.g. delete scheduled files)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _processPendingActions(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$update_required = false;
$temp_id = $event->getEventParam('temp_id');
$id = $temp_id !== false ? $temp_id : $object->GetID();
foreach ($object->getPendingActions($id) as $data) {
switch ( $data['action'] ) {
case 'delete':
unlink($data['file']);
break;
case 'make_live':
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$old_name = basename($data['file']);
$new_name = $file_helper->ensureUniqueFilename(dirname($data['file']), kUtil::removeTempExtension($old_name));
rename($data['file'], dirname($data['file']) . '/' . $new_name);
$db_value = $object->GetDBField($data['field']);
$object->SetDBField($data['field'], str_replace($old_name, $new_name, $db_value));
$update_required = true;
break;
default:
trigger_error('Unsupported pending action "' . $data['action'] . '" for "' . $event->getPrefixSpecial() . '" unit', E_USER_WARNING);
break;
}
}
// remove pending actions before updating to prevent recursion
$object->setPendingActions();
if ( $update_required ) {
$object->Update();
}
}
/**
* Occurs before an item has been cloned
* Id of newly created item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeClone(kEvent $event)
{
}
/**
* Occurs after an item has been cloned
* Id of newly created item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterClone(kEvent $event)
{
}
/**
* Occurs after list is queried
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterListQuery(kEvent $event)
{
}
/**
* Ensures that popup will be closed automatically
* and parent window will be refreshed with template
* passed
*
* @param kEvent $event
* @return void
* @access protected
* @deprecated
*/
protected function finalizePopup(kEvent $event)
{
$event->SetRedirectParam('opener', 'u');
}
/**
* Create search filters based on search query
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSearch(kEvent $event)
{
$event->setPseudoClass('_List');
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$search_helper->performSearch($event);
}
/**
* Clear search keywords
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSearchReset(kEvent $event)
{
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$search_helper->resetSearch($event);
}
/**
* Set's new filter value (filter_id meaning from config)
*
* @param kEvent $event
* @return void
* @access protected
* @deprecated
*/
protected function OnSetFilter(kEvent $event)
{
$filter_id = $this->Application->GetVar('filter_id');
$filter_value = $this->Application->GetVar('filter_value');
$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
$view_filter = $view_filter ? unserialize($view_filter) : Array ();
$view_filter[$filter_id] = $filter_value;
$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
}
/**
* Sets view filter based on request
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetFilterPattern(kEvent $event)
{
$filters = $this->Application->GetVar($event->getPrefixSpecial(true) . '_filters');
if ( !$filters ) {
return;
}
$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
$view_filter = $view_filter ? unserialize($view_filter) : Array ();
$filters = explode(',', $filters);
foreach ($filters as $a_filter) {
list($id, $value) = explode('=', $a_filter);
$view_filter[$id] = $value;
}
$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
$event->redirect = false;
}
/**
* Add/Remove all filters applied to list from "View" menu
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function FilterAction(kEvent $event)
{
$view_filter = Array ();
$filter_menu = $event->getUnitConfig()->getFilterMenu();
switch ($event->Name) {
case 'OnRemoveFilters':
$filter_value = 1;
break;
case 'OnApplyFilters':
$filter_value = 0;
break;
default:
$filter_value = 0;
break;
}
foreach ($filter_menu['Filters'] as $filter_key => $filter_params) {
if ( !$filter_params ) {
continue;
}
$view_filter[$filter_key] = $filter_value;
}
$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
}
/**
* Enter description here...
*
* @param kEvent $event
* @access protected
*/
protected function OnPreSaveAndOpenTranslator(kEvent $event)
{
$this->Application->SetVar('allow_translation', true);
$object = $event->getObject();
/* @var $object kDBItem */
$this->RemoveRequiredFields($object);
$event->CallSubEvent('OnPreSave');
if ( $event->status == kEvent::erSUCCESS ) {
$resource_id = $this->Application->GetVar('translator_resource_id');
if ( $resource_id ) {
$t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes'));
$cdata = $this->Application->recallObject($t_prefixes[1], NULL, Array ('skip_autoload' => true));
/* @var $cdata kDBItem */
$cdata->Load($resource_id, 'ResourceId');
if ( !$cdata->isLoaded() ) {
$cdata->SetDBField('ResourceId', $resource_id);
$cdata->Create();
}
$this->Application->SetVar($cdata->getPrefixSpecial() . '_id', $cdata->GetID());
}
$event->redirect = $this->Application->GetVar('translator_t');
$redirect_params = Array (
'pass' => 'all,trans,' . $this->Application->GetVar('translator_prefixes'),
'opener' => 's',
$event->getPrefixSpecial(true) . '_id' => $object->GetID(),
'trans_event' => 'OnLoad',
'trans_prefix' => $this->Application->GetVar('translator_prefixes'),
'trans_field' => $this->Application->GetVar('translator_field'),
'trans_multi_line' => $this->Application->GetVar('translator_multi_line'),
);
$event->setRedirectParams($redirect_params);
// 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect)
$last_template = $this->Application->RecallVar('last_template');
preg_match('/index4\.php\|' . $this->Application->GetSID() . '-(.*):/U', $last_template, $rets);
$this->Application->StoreVar('return_template', $this->Application->GetVar('t'));
}
}
/**
* Makes all fields non-required
*
* @param kDBItem $object
* @return void
* @access protected
*/
protected function RemoveRequiredFields(&$object)
{
// making all field non-required to achieve successful presave
$fields = array_keys( $object->getFields() );
foreach ($fields as $field) {
if ( $object->isRequired($field) ) {
$object->setRequired($field, false);
}
}
}
/**
* Saves selected user in needed field
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSelectUser(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$items_info = $this->Application->GetVar('u');
if ( $items_info ) {
list ($user_id, ) = each($items_info);
$this->RemoveRequiredFields($object);
$is_new = !$object->isLoaded();
$is_main = substr($this->Application->GetVar($event->Prefix . '_mode'), 0, 1) == 't';
if ( $is_new ) {
$new_event = $is_main ? 'OnPreCreate' : 'OnNew';
$event->CallSubEvent($new_event);
$event->redirect = true;
}
$object->SetDBField($this->Application->RecallVar('dst_field'), $user_id);
if ( $is_new ) {
$object->Create();
}
else {
$object->Update();
}
}
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $object->GetID());
$event->SetRedirectParam('opener', 'u');
}
/** EXPORT RELATED **/
/**
* Shows export dialog
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExport(kEvent $event)
{
$selected_ids = $this->StoreSelectedIDs($event);
if ( implode(',', $selected_ids) == '' ) {
// K4 fix when no ids found bad selected ids array is formed
$selected_ids = false;
}
$this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : '');
$this->Application->LinkVar('export_finish_t');
$this->Application->LinkVar('export_progress_t');
$this->Application->StoreVar('export_oroginal_special', $event->Special);
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/*list ($index_file, $env) = explode('|', $this->Application->RecallVar('last_template'));
$finish_url = $this->Application->BaseURL() . 'admin/' . $index_file . '?' . ENV_VAR_NAME . '=' . $env;
$this->Application->StoreVar('export_finish_url', $finish_url);*/
$redirect_params = Array (
$this->Prefix . '.export_event' => 'OnNew',
'pass' => 'all,' . $this->Prefix . '.export'
);
$event->setRedirectParams($redirect_params);
}
/**
* Apply some special processing to object being
* recalled before using it in other events that
* call prepareObject
*
* @param kDBItem|kDBList $object
* @param kEvent $event
* @return void
* @access protected
*/
protected function prepareObject(&$object, kEvent $event)
{
if ( $event->Special == 'export' || $event->Special == 'import' ) {
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$export_helper->prepareExportColumns($event);
}
}
/**
* Returns specific to each item type columns only
*
* @param kEvent $event
* @return Array
* @access public
*/
public function getCustomExportColumns(kEvent $event)
{
return Array ();
}
/**
* Export form validation & processing
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExportBegin(kEvent $event)
{
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$export_helper->OnExportBegin($event);
}
/**
* Enter description here...
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExportCancel(kEvent $event)
{
$this->OnGoBack($event);
}
/**
* Allows configuring export options
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeExportBegin(kEvent $event)
{
}
/**
* Deletes export preset
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDeleteExportPreset(kEvent $event)
{
$field_values = $this->getSubmittedFields($event);
if ( !$field_values ) {
return ;
}
$preset_key = $field_values['ExportPresets'];
$export_settings = $this->Application->RecallPersistentVar('export_settings');
if ( !$export_settings ) {
return ;
}
$export_settings = unserialize($export_settings);
if ( !isset($export_settings[$event->Prefix]) ) {
return ;
}
$to_delete = '';
foreach ($export_settings[$event->Prefix] as $key => $val) {
if ( implode('|', $val['ExportColumns']) == $preset_key ) {
$to_delete = $key;
break;
}
}
if ( $to_delete ) {
unset($export_settings[$event->Prefix][$to_delete]);
$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
}
}
/**
* Saves changes & changes language
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveAndChangeLanguage(kEvent $event)
{
if ( $this->UseTempTables($event) ) {
$event->CallSubEvent('OnPreSave');
}
if ( $event->status == kEvent::erSUCCESS ) {
$this->Application->SetVar('m_lang', $this->Application->GetVar('language'));
$data = $this->Application->GetVar('st_id');
if ( $data ) {
$event->SetRedirectParam('st_id', $data);
}
}
}
/**
- * Used to save files uploaded via swfuploader
+ * Used to save files uploaded via Plupload
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUploadFile(kEvent $event)
{
$event->status = kEvent::erSTOP;
-// define('DBG_SKIP_REPORTING', 0);
- $default_msg = "Flash requires that we output something or it won't fire the uploadSuccess event";
- if ( !$this->Application->HttpQuery->Post ) {
- // Variables {field, id, flashsid} are always submitted through POST!
- // When file size is larger, then "upload_max_filesize" (in php.ini),
- // then these variables also are not submitted -> handle such case.
- header('HTTP/1.0 413 File size exceeds allowed limit');
- echo $default_msg;
- return;
- }
-
- if ( !$this->_checkFlashUploaderPermission($event) ) {
- // 403 Forbidden
- header('HTTP/1.0 403 You don\'t have permissions to upload');
- echo $default_msg;
- return;
- }
-
- $value = $this->Application->GetVar('Filedata');
-
- if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) {
- // 413 Request Entity Too Large (file uploads disabled OR uploaded file was
- // to large for web server to accept, see "upload_max_filesize" in php.ini)
- header('HTTP/1.0 413 File size exceeds allowed limit');
- echo $default_msg;
- return;
- }
-
- if ( !$this->Application->isAdmin ) {
- $value = array_map('htmlspecialchars_decode', $value);
- }
-
- $tmp_path = WRITEABLE . '/tmp/';
- $filename = $value['name'] . '.tmp';
- $id = $this->Application->GetVar('id');
-
- if ( $id ) {
- $filename = $id . '_' . $filename;
- }
-
- if ( !is_writable($tmp_path) ) {
- // 500 Internal Server Error
- // check both temp and live upload directory
- header('HTTP/1.0 500 Write permissions not set on the server');
- echo $default_msg;
- return;
- }
-
- $file_helper = $this->Application->recallObject('FileHelper');
- /* @var $file_helper FileHelper */
-
- $filename = $file_helper->ensureUniqueFilename($tmp_path, $filename);
- $storage_format = $this->_getStorageFormat($this->Application->GetVar('field'), $event);
-
- if ( $storage_format ) {
- $image_helper = $this->Application->recallObject('ImageHelper');
- /* @var $image_helper ImageHelper */
-
- move_uploaded_file($value['tmp_name'], $value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work
- $url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format);
- $tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url);
- rename($tmp_name, $tmp_path . $filename);
- }
- else {
- move_uploaded_file($value['tmp_name'], $tmp_path . $filename);
- }
+ /** @var kUploadHelper $upload_helper */
+ $upload_helper = $this->Application->recallObject('kUploadHelper');
- echo preg_replace('/^' . preg_quote($id, '/') . '_/', '', $filename);
+ try {
+ $filename = $upload_helper->handle($event);
- $this->deleteTempFiles($tmp_path);
-
- if ( file_exists($tmp_path . 'resized/') ) {
- $this->deleteTempFiles($tmp_path . 'resized/');
- }
- }
-
- /**
- * Gets storage format for a given field
- *
- * @param string $field_name
- * @param kEvent $event
- * @return bool
- * @access protected
- */
- protected function _getStorageFormat($field_name, kEvent $event)
- {
- $config = $event->getUnitConfig();
- $field_options = $config->getFieldByName($field_name);
-
- if ( !$field_options ) {
- $field_options = $config->getVirtualFieldByName($field_name);
- }
-
- return isset($field_options['storage_format']) ? $field_options['storage_format'] : false;
- }
-
- /**
- * Delete temporary files, that won't be used for sure
- *
- * @param string $path
- * @return void
- * @access protected
- */
- protected function deleteTempFiles($path)
- {
- $files = glob($path . '*.*');
- $max_file_date = strtotime('-1 day');
-
- foreach ($files as $file) {
- if (filemtime($file) < $max_file_date) {
- unlink($file);
- }
+ $response = array(
+ 'jsonrpc' => '2.0',
+ 'status' => 'success',
+ 'result' => $filename,
+ );
}
- }
-
- /**
- * Checks, that flash uploader is allowed to perform upload
- *
- * @param kEvent $event
- * @return bool
- */
- protected function _checkFlashUploaderPermission(kEvent $event)
- {
- // Flash uploader does NOT send correct cookies, so we need to make our own check
- $cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName');
- $this->Application->HttpQuery->Cookie['cookies_on'] = 1;
- $this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid');
-
- // this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used
- $this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid');
-
- $admin_ses = $this->Application->recallObject('Session.admin');
- /* @var $admin_ses Session */
-
- if ( $admin_ses->RecallVar('user_id') == USER_ROOT ) {
- return true;
+ catch ( kUploaderException $e ) {
+ $response = array(
+ 'jsonrpc' => '2.0',
+ 'status' => 'error',
+ 'error' => array('code' => $e->getCode(), 'message' => $e->getMessage()),
+ );
}
- // copy some data from given session to current session
- $backup_user_id = $this->Application->RecallVar('user_id');
- $this->Application->StoreVar('user_id', $admin_ses->RecallVar('user_id'));
-
- $backup_user_groups = $this->Application->RecallVar('UserGroups');
- $this->Application->StoreVar('UserGroups', $admin_ses->RecallVar('UserGroups'));
-
- // check permissions using event, that have "add|edit" rule
- $check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected');
- $check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
- $allowed_to_upload = $this->CheckPermission($check_event);
-
- // restore changed data, so nothing gets saved to database
- $this->Application->StoreVar('user_id', $backup_user_id);
- $this->Application->StoreVar('UserGroups', $backup_user_groups);
-
- return $allowed_to_upload;
+ echo json_encode($response);
}
/**
* Remembers, that file should be deleted on item's save from temp table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDeleteFile(kEvent $event)
{
$event->status = kEvent::erSTOP;
- $filename = $this->_getSafeFilename();
+ $field_id = $this->Application->GetVar('field_id');
- if ( !$filename ) {
+ if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) {
return;
}
- $object = $event->getObject(Array ('skip_autoload' => true));
- /* @var $object kDBItem */
+ $field = $regs[1][1];
+ $record_id = $regs[1][0];
- $field_id = $this->Application->GetVar('field_id');
+ /** @var kUploadHelper $upload_helper */
+ $upload_helper = $this->Application->recallObject('kUploadHelper');
+ $object = $upload_helper->prepareUploadedFile($event, $field);
- if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) {
+ if ( !$object->GetDBField($field) ) {
return;
}
- $field = $regs[1][1];
- $record_id = $regs[1][0];
$pending_actions = $object->getPendingActions($record_id);
- $upload_dir = $object->GetFieldOption($field, 'upload_dir');
$pending_actions[] = Array (
- 'action' => 'delete', 'id' => $record_id, 'field' => $field, 'file' => FULL_PATH . $upload_dir . $filename
+ 'action' => 'delete',
+ 'id' => $record_id,
+ 'field' => $field,
+ 'file' => $object->GetField($field, 'full_path'),
);
$object->setPendingActions($pending_actions, $record_id);
}
/**
* Returns url for viewing uploaded file
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnViewFile(kEvent $event)
{
$event->status = kEvent::erSTOP;
- $filename = $this->_getSafeFilename();
-
- if ( !$filename ) {
- return;
- }
-
- $object = $event->getObject(Array ('skip_autoload' => true));
- /* @var $object kDBItem */
-
$field = $this->Application->GetVar('field');
- $options = $object->GetFieldOptions($field);
- // set current uploaded file
- if ( $this->Application->GetVar('tmp') ) {
- $options['upload_dir'] = WRITEBALE_BASE . '/tmp/';
- unset($options['include_path']);
- $object->SetFieldOptions($field, $options);
+ /** @var kUploadHelper $upload_helper */
+ $upload_helper = $this->Application->recallObject('kUploadHelper');
+ $object = $upload_helper->prepareUploadedFile($event, $field);
- $object->SetDBField($field, $this->Application->GetVar('id') . '_' . $filename);
- }
- else {
- $object->SetDBField($field, $filename);
+ if ( !$object->GetDBField($field) ) {
+ return;
}
// get url to uploaded file
if ( $this->Application->GetVar('thumb') ) {
- $url = $object->GetField($field, $options['thumb_format']);
+ $url = $object->GetField($field, $object->GetFieldOption($field, 'thumb_format'));
}
else {
$url = $object->GetField($field, 'raw_url');
}
+ /** @var FileHelper $file_helper */
$file_helper = $this->Application->recallObject('FileHelper');
- /* @var $file_helper FileHelper */
-
$path = $file_helper->urlToPath($url);
if ( !file_exists($path) ) {
exit;
}
header('Content-Length: ' . filesize($path));
$this->Application->setContentType(kUtil::mimeContentType($path), false);
- header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($filename) . '"');
+ header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($object->GetDBField($field)) . '"');
readfile($path);
}
/**
- * Returns safe version of filename specified in url
- *
- * @return bool|string
- * @access protected
- */
- protected function _getSafeFilename()
- {
- $filename = $this->Application->GetVar('file');
-
- if ( !$this->Application->isAdmin ) {
- $filename = htmlspecialchars_decode($filename);
- }
-
- if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) {
- // when relative paths or special chars are found template names from url, then it's hacking attempt
- return false;
- }
-
- return $filename;
- }
-
- /**
* Validates MInput control fields
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnValidateMInputFields(kEvent $event)
{
$minput_helper = $this->Application->recallObject('MInputHelper');
/* @var $minput_helper MInputHelper */
$minput_helper->OnValidateMInputFields($event);
}
/**
* Validates individual object field and returns the result
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnValidateField(kEvent $event)
{
$event->status = kEvent::erSTOP;
$field = $this->Application->GetVar('field');
if ( ($this->Application->GetVar('ajax') != 'yes') || !$field ) {
return;
}
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( !$items_info ) {
return;
}
list ($id, $field_values) = each($items_info);
$object->Load($id);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$event->setEventParam('form_data', $field_values);
$object->setID($id);
$response = Array ('status' => 'OK');
$event->CallSubEvent($object->isLoaded() ? 'OnBeforeItemUpdate' : 'OnBeforeItemCreate');
// validate all fields, since "Password_plain" field sets error to "Password" field, which is passed here
$error_field = $object->GetFieldOption($field, 'error_field', false, $field);
if ( !$object->Validate() && $object->GetErrorPseudo($error_field) ) {
$response['status'] = $object->GetErrorMsg($error_field, false);
}
$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
/* @var $ajax_form_helper AjaxFormHelper */
$response['other_errors'] = $ajax_form_helper->getErrorMessages($object);
$response['uploader_info'] = $ajax_form_helper->getUploaderInfo($object, array_keys($field_values));
$event->status = kEvent::erSTOP; // since event's OnBefore... events can change this event status
echo json_encode($response);
}
/**
* Returns auto-complete values for ajax-dropdown
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSuggestValues(kEvent $event)
{
$event->status = kEvent::erSTOP;
$this->Application->XMLHeader();
$data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('cur_value'));
echo '<suggestions>';
if ( kUtil::isAssoc($data) ) {
foreach ($data as $key => $title) {
echo '<item value="' . kUtil::escape($key, kUtil::ESCAPE_HTML) . '">' . kUtil::escape($title, kUtil::ESCAPE_HTML) . '</item>';
}
}
else {
foreach ($data as $title) {
echo '<item>' . kUtil::escape($title, kUtil::ESCAPE_HTML) . '</item>';
}
}
echo '</suggestions>';
}
/**
* Returns auto-complete values for jQueryUI.AutoComplete
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSuggestValuesJSON(kEvent $event)
{
$event->status = kEvent::erSTOP;
$data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('term'));
if ( kUtil::isAssoc($data) ) {
$transformed_data = array();
foreach ($data as $key => $title) {
$transformed_data[] = array('value' => $key, 'label' => $title);
}
$data = $transformed_data;
}
echo json_encode($data);
}
/**
* Prepares a suggestion list based on a given term.
*
* @param kEvent $event Event.
* @param string $term Term.
*
* @return Array
* @access protected
*/
protected function getAutoCompleteSuggestions(kEvent $event, $term)
{
$object = $event->getObject();
/* @var $object kDBItem */
$field = $this->Application->GetVar('field');
if ( !$field || !$term || !$object->isField($field) ) {
return array();
}
$limit = $this->Application->GetVar('limit');
if ( !$limit ) {
$limit = 20;
}
$sql = 'SELECT DISTINCT ' . $field . '
FROM ' . $event->getUnitConfig()->getTableName() . '
WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($term . '%') . '
ORDER BY ' . $field . '
LIMIT 0,' . $limit;
return $this->Conn->GetCol($sql);
}
/**
* Enter description here...
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSaveWidths(kEvent $event)
{
$event->status = kEvent::erSTOP;
// $this->Application->setContentType('text/xml');
$picker_helper = new kColumnPickerHelper(
$event->getPrefixSpecial(),
$this->Application->GetVar('grid_name')
);
$picker_helper->saveWidths($this->Application->GetVar('widths'));
echo 'OK';
}
/**
* Called from CSV import script after item fields
* are set and validated, but before actual item create/update.
* If event status is kEvent::erSUCCESS, line will be imported,
* else it will not be imported but added to skipped lines
* and displayed in the end of import.
* Event status is preset from import script.
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeCSVLineImport(kEvent $event)
{
// abstract, for hooking
}
/**
* [HOOK] Allows to add cloned subitem to given prefix
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCloneSubItem(kEvent $event)
{
$sub_item_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix);
$event->MasterEvent->getUnitConfig()->addClones(Array (
$sub_item_prefix => Array ('ParentPrefix' => $event->Prefix),
));
}
/**
* Returns constrain for priority calculations
*
* @param kEvent $event
* @return void
* @see PriorityEventHandler
* @access protected
*/
protected function OnGetConstrainInfo(kEvent $event)
{
$event->setEventParam('constrain_info', Array ('', ''));
}
}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/upload_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/upload_helper.php (nonexistent)
+++ branches/5.3.x/core/units/helpers/upload_helper.php (revision 16002)
@@ -0,0 +1,337 @@
+<?php
+/**
+* @version $Id$
+* @package In-Portal
+* @copyright Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
+* @license GNU/GPL
+* In-Portal is Open Source software.
+* This means that this software may have been modified pursuant
+* the GNU General Public License, and as distributed it includes
+* or is derivative of works licensed under the GNU General Public License
+* or other free or open source software licenses.
+* See http://www.in-portal.org/license for copyright notices and details.
+*/
+
+class kUploadHelper extends kHelper
+{
+
+ /**
+ * Creates kUploadHelper instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ // 5 minutes execution time
+ @set_time_limit(5 * 60);
+ }
+
+ /**
+ * Handles the upload.
+ *
+ * @param kEvent $event Event.
+ *
+ * @return string
+ * @throws kUploaderException When upload could not be handled properly.
+ */
+ public function handle(kEvent $event)
+ {
+ $this->disableBrowserCache();
+
+// Uncomment this one to fake upload time
+// sleep(5);
+
+ if ( !$this->Application->HttpQuery->Post ) {
+ // Variables {field, id, flashsid} are always submitted through POST!
+ // When file size is larger, then "upload_max_filesize" (in php.ini),
+ // then these variables also are not submitted.
+ throw new kUploaderException('File size exceeds allowed limit.', 413);
+ }
+
+ if ( !$this->checkPermissions($event) ) {
+ // 403 Forbidden
+ throw new kUploaderException('You don\'t have permissions to upload.', 403);
+ }
+
+ $value = $this->Application->GetVar('file');
+
+ if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) {
+ // 413 Request Entity Too Large (file uploads disabled OR uploaded file was
+ // too large for web server to accept, see "upload_max_filesize" in php.ini)
+ throw new kUploaderException('File size exceeds allowed limit.', 413);
+ }
+
+ if ( !$this->Application->isAdmin ) {
+ $value = array_map('htmlspecialchars_decode', $value);
+ }
+
+ $tmp_path = WRITEABLE . '/tmp/';
+ $filename = $this->getUploadedFilename() . '.tmp';
+ $id = $this->Application->GetVar('id');
+
+ if ( $id ) {
+ $filename = $id . '_' . $filename;
+ }
+
+ if ( !is_writable($tmp_path) ) {
+ // 500 Internal Server Error
+ // check both temp and live upload directory
+ throw new kUploaderException('Write permissions not set on the server, please contact server administrator.', 500);
+ }
+
+ /** @var FileHelper $file_helper */
+ $file_helper = $this->Application->recallObject('FileHelper');
+ $filename = $file_helper->ensureUniqueFilename($tmp_path, $filename);
+ $storage_format = $this->getStorageFormat($this->Application->GetVar('field'), $event);
+
+ if ( $storage_format ) {
+ $image_helper = $this->Application->recallObject('ImageHelper');
+ /* @var $image_helper ImageHelper */
+
+ $this->moveUploadedFile($value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work
+ $url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format);
+ $tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url);
+ rename($tmp_name, $tmp_path . $filename);
+ }
+ else {
+ $this->moveUploadedFile($tmp_path . $filename);
+ }
+
+ $this->deleteTempFiles($tmp_path);
+
+ if ( file_exists($tmp_path . 'resized/') ) {
+ $this->deleteTempFiles($tmp_path . 'resized/');
+ }
+
+ return preg_replace('/^' . preg_quote($id, '/') . '_/', '', $filename);
+ }
+
+ /**
+ * Sends headers to ensure, that response is never cached.
+ *
+ * @return void
+ */
+ protected function disableBrowserCache()
+ {
+ header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+ header('Cache-Control: no-store, no-cache, must-revalidate');
+ header('Cache-Control: post-check=0, pre-check=0', false);
+ header('Pragma: no-cache');
+ }
+
+ /**
+ * Checks, that flash uploader is allowed to perform upload
+ *
+ * @param kEvent $event
+ * @return bool
+ */
+ protected function checkPermissions(kEvent $event)
+ {
+ // Flash uploader does NOT send correct cookies, so we need to make our own check
+ $cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName');
+ $this->Application->HttpQuery->Cookie['cookies_on'] = 1;
+ $this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid');
+
+ // this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used
+ $this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid');
+
+ $admin_session = $this->Application->recallObject('Session.admin');
+ /* @var $admin_session Session */
+
+ if ( $admin_session->RecallVar('user_id') == USER_ROOT ) {
+ return true;
+ }
+
+ // copy some data from given session to current session
+ $backup_user_id = $this->Application->RecallVar('user_id');
+ $this->Application->StoreVar('user_id', $admin_session->RecallVar('user_id'));
+
+ $backup_user_groups = $this->Application->RecallVar('UserGroups');
+ $this->Application->StoreVar('UserGroups', $admin_session->RecallVar('UserGroups'));
+
+ // check permissions using event, that have "add|edit" rule
+ $check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected');
+ $check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
+
+ /** @var kEventHandler $event_handler */
+ $event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
+ $allowed_to_upload = $event_handler->CheckPermission($check_event);
+
+ // restore changed data, so nothing gets saved to database
+ $this->Application->StoreVar('user_id', $backup_user_id);
+ $this->Application->StoreVar('UserGroups', $backup_user_groups);
+
+ return $allowed_to_upload;
+ }
+
+ /**
+ * Returns uploaded filename.
+ *
+ * @return string
+ */
+ protected function getUploadedFilename()
+ {
+ if ( isset($_REQUEST['name']) ) {
+ $file_name = $_REQUEST['name'];
+ }
+ elseif ( !empty($_FILES) ) {
+ $file_name = $_FILES['file']['name'];
+ }
+ else {
+ $file_name = uniqid('file_');
+ }
+
+ return $file_name;
+ }
+
+ /**
+ * Gets storage format for a given field.
+ *
+ * @param string $field_name
+ * @param kEvent $event
+ * @return bool
+ */
+ protected function getStorageFormat($field_name, kEvent $event)
+ {
+ $config = $event->getUnitConfig();
+ $field_options = $config->getFieldByName($field_name);
+
+ if ( !$field_options ) {
+ $field_options = $config->getVirtualFieldByName($field_name);
+ }
+
+ return isset($field_options['storage_format']) ? $field_options['storage_format'] : false;
+ }
+
+ /**
+ * Moves uploaded file to given location.
+ *
+ * @param string $file_path File path.
+ *
+ * @return void
+ * @throws kUploaderException When upload could not be handled properly.
+ */
+ protected function moveUploadedFile($file_path)
+ {
+ // Chunking might be enabled
+ $chunk = (int)$this->Application->GetVar('chunk', 0);
+ $chunks = (int)$this->Application->GetVar('chunks', 0);
+
+ // Open temp file
+ if ( !$out = @fopen("{$file_path}.part", $chunks ? 'ab' : 'wb') ) {
+ throw new kUploaderException('Failed to open output stream.', 102);
+ }
+
+ if ( !empty($_FILES) ) {
+ if ( $_FILES['file']['error'] || !is_uploaded_file($_FILES['file']['tmp_name']) ) {
+ throw new kUploaderException('Failed to move uploaded file.', 103);
+ }
+
+ // Read binary input stream and append it to temp file
+ if ( !$in = @fopen($_FILES['file']['tmp_name'], 'rb') ) {
+ throw new kUploaderException('Failed to open input stream.', 101);
+ }
+ }
+ else {
+ if ( !$in = @fopen('php://input', 'rb') ) {
+ throw new kUploaderException('Failed to open input stream.', 101);
+ }
+ }
+
+ while ( $buff = fread($in, 4096) ) {
+ fwrite($out, $buff);
+ }
+
+ @fclose($out);
+ @fclose($in);
+
+ // Check if file has been uploaded
+ if ( !$chunks || $chunk == $chunks - 1 ) {
+ // Strip the temp .part suffix off
+ rename("{$file_path}.part", $file_path);
+ }
+ }
+
+ /**
+ * Delete temporary files, that won't be used for sure
+ *
+ * @param string $path
+ * @return void
+ */
+ protected function deleteTempFiles($path)
+ {
+ $files = glob($path . '*.*');
+ $max_file_date = strtotime('-1 day');
+
+ foreach ($files as $file) {
+ if (filemtime($file) < $max_file_date) {
+ unlink($file);
+ }
+ }
+ }
+
+ /**
+ * Prepares object for operations with file on given field.
+ *
+ * @param kEvent $event Event.
+ * @param string $field Field.
+ *
+ * @return kDBItem
+ */
+ public function prepareUploadedFile(kEvent $event, $field)
+ {
+ $object = $event->getObject(Array ('skip_autoload' => true));
+ /* @var $object kDBItem */
+
+ $filename = $this->getSafeFilename();
+
+ if ( !$filename ) {
+ $object->SetDBField($field, '');
+
+ return $object;
+ }
+
+ // set current uploaded file
+ if ( $this->Application->GetVar('tmp') ) {
+ $options = $object->GetFieldOptions($field);
+ $options['upload_dir'] = WRITEBALE_BASE . '/tmp/';
+ unset($options['include_path']);
+ $object->SetFieldOptions($field, $options);
+
+ $filename = $this->Application->GetVar('id') . '_' . $filename;
+ }
+
+ $object->SetDBField($field, $filename);
+
+ return $object;
+ }
+
+ /**
+ * Returns safe version of filename specified in url
+ *
+ * @return bool|string
+ * @access protected
+ */
+ protected function getSafeFilename()
+ {
+ $filename = $this->Application->GetVar('file');
+
+ if ( !$this->Application->isAdmin ) {
+ $filename = htmlspecialchars_decode($filename);
+ }
+
+ if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) {
+ // when relative paths or special chars are found template names from url, then it's hacking attempt
+ return false;
+ }
+
+ return $filename;
+ }
+}
+
+
+class kUploaderException extends Exception
+{
+
+}
Property changes on: branches/5.3.x/core/units/helpers/upload_helper.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/units/helpers/helpers_config.php
===================================================================
--- branches/5.3.x/core/units/helpers/helpers_config.php (revision 16001)
+++ branches/5.3.x/core/units/helpers/helpers_config.php (revision 16002)
@@ -1,77 +1,78 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
$config = Array (
'Prefix' => 'helpers',
'EventHandlerClass' => Array ('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'),
'RegisterClasses' => Array (
Array ('pseudo' => 'kMultiLanguageHelper', 'class' => 'kMultiLanguageHelper', 'file' => 'multilanguage_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SearchHelper', 'class' => 'kSearchHelper', 'file' => 'search_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SectionsHelper', 'class' => 'kSectionsHelper', 'file' => 'sections_helper.php', 'build_event' => ''),
Array ('pseudo' => 'PermissionsHelper', 'class' => 'kPermissionsHelper', 'file' => 'permissions_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ModulesHelper', 'class' => 'kModulesHelper', 'file' => 'modules_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CategoryItemRewrite', 'class' => 'CategoryItemRewrite', 'file' => 'mod_rewrite_helper.php', 'build_event' => ''),
Array ('pseudo' => 'RecursiveHelper', 'class' => 'kRecursiveHelper', 'file' => 'recursive_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FilenamesHelper', 'class' => 'kFilenamesHelper', 'file' => 'filenames_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ClipboardHelper', 'class' => 'kClipboardHelper', 'file' => 'clipboard_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ColumnPickerHelper', 'class' => 'kColumnPickerHelper', 'file' => 'col_picker_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ThemesHelper', 'class' => 'kThemesHelper', 'file' => 'themes_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CaptchaHelper', 'class' => 'kCaptchaHelper', 'file' => 'captcha_helper.php', 'build_event' => ''),
Array ('pseudo' => 'PriorityHelper', 'class' => 'kPriorityHelper', 'file' => 'priority_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CurlHelper', 'class' => 'kCurlHelper', 'file' => 'curl_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CountHelper', 'class' => 'kCountHelper', 'file' => 'count_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ImageHelper', 'class' => 'ImageHelper', 'file' => 'image_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FileHelper', 'class' => 'FileHelper', 'file' => 'file_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CategoryHelper', 'class' => 'CategoryHelper', 'file' => 'category_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kNavigationBar', 'class' => 'kNavigationBar', 'file' => 'navigation_bar.php', 'build_event' => ''),
Array ('pseudo' => 'CSVHelper', 'class' => 'kCSVHelper', 'file' => 'csv_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ChartHelper', 'class' => 'kChartHelper', 'file' => 'chart_helper.php', 'build_event' => ''),
Array ('pseudo' => 'RatingHelper', 'class' => 'RatingHelper', 'file' => 'rating_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FCKHelper', 'class' => 'fckFCKHelper', 'file' => 'fck_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SpamHelper', 'class' => 'SpamHelper', 'file' => 'spam_helper.php', 'build_event' => ''),
Array ('pseudo' => 'TemplateHelper', 'class' => 'TemplateHelper', 'file' => 'template_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MailingListHelper', 'class' => 'MailingListHelper', 'file' => 'mailing_list_helper.php', 'build_event' => ''),
Array ('pseudo' => 'JSONHelper', 'class' => 'JSONHelper', 'file' => 'json_helper.php', 'build_event' => ''),
Array ('pseudo' => 'LanguageImportHelper', 'class' => 'LanguageImportHelper', 'file' => 'language_import_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SkinHelper', 'class' => 'SkinHelper', 'file' => 'skin_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SiteConfigHelper', 'class' => 'SiteConfigHelper', 'file' => 'site_config_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MenuHelper', 'class' => 'MenuHelper', 'file' => 'menu_helper.php', 'build_event' => ''),
Array ('pseudo' => 'InpCustomFieldsHelper', 'class' => 'InpCustomFieldsHelper', 'file' => 'custom_fields_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CountryStatesHelper', 'class' => 'kCountryStatesHelper', 'file' => 'country_states_helper.php', 'build_event' => ''),
Array ('pseudo' => 'BracketsHelper', 'class' => 'kBracketsHelper', 'file' => 'brackets_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kXMLHelper', 'class' => 'kXMLHelper', 'file' => 'xml_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CatItemExportHelper', 'class' => 'kCatDBItemExportHelper', 'file' => 'cat_dbitem_export_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kEmailTemplateHelper', 'class' => 'kEmailTemplateHelper', 'file' => 'email_template_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ListHelper', 'class' => 'ListHelper', 'file' => 'list_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FormSubmissionHelper', 'class' => 'FormSubmissionHelper', 'file' => 'form_submission_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MailboxHelper', 'class' => 'MailboxHelper', 'file' => 'mailbox_helper.php', 'build_event' => ''),
Array ('pseudo' => 'POP3Helper', 'class' => 'POP3Helper', 'file' => 'pop3_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MimeDecodeHelper', 'class' => 'MimeDecodeHelper', 'file' => 'mime_decode_helper.php', 'build_event' => ''),
Array ('pseudo' => 'UserHelper', 'class' => 'UserHelper', 'file' => 'user_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SiteHelper', 'class' => 'SiteHelper', 'file' => 'site_helper.php', 'build_event' => ''),
Array ('pseudo' => 'DeploymentHelper', 'class' => 'DeploymentHelper', 'file' => 'deployment_helper.php', 'build_event' => ''),
Array ('pseudo' => 'PageHelper', 'class' => 'PageHelper', 'file' => 'page_helper.php', 'build_event' => ''),
Array ('pseudo' => 'BackupHelper', 'class' => 'BackupHelper', 'file' => 'backup_helper.php', 'build_event' => ''),
Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kCronHelper', 'class' => 'kCronHelper', 'file' => 'cron_helper.php', 'build_event' => ''),
+ Array ('pseudo' => 'kUploadHelper', 'class' => 'kUploadHelper', 'file' => 'upload_helper.php', 'build_event' => ''),
),
);
\ No newline at end of file
Index: branches/5.3.x/core/admin_templates/swfupload.swf
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: branches/5.3.x/core/admin_templates/swfupload.swf
___________________________________________________________________
Deleted: cvs2svn:cvs-rev
## -1 +0,0 ##
-1.2.4.2
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: branches/5.3.x/core/admin_templates/img/upload.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: branches/5.3.x/core/admin_templates/img/upload.png
___________________________________________________________________
Deleted: cvs2svn:cvs-rev
## -1 +0,0 ##
-1.1.2.1
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: branches/5.3.x/core/admin_templates/js/uploader/uploader.js
===================================================================
--- branches/5.3.x/core/admin_templates/js/uploader/uploader.js (revision 16001)
+++ branches/5.3.x/core/admin_templates/js/uploader/uploader.js (revision 16002)
@@ -1,756 +1,688 @@
-// this js class name is hardcoded in flash object :(
-var SWFUpload = function () {};
-SWFUpload.instances = {};
-
function Uploader(id, params) {
this.id = id;
+ this.instance = null;
// normalize params
if (isNaN(parseInt(params.multiple))) {
// ensure that maximal file number is greater then zero
params.multiple = 1;
}
params.allowedFilesize = this._normalizeFilesize(params.allowedFilesize);
// set params to uploader
- this._eventQueue = [];
- this.uploadCancelled = false;
- this.flashReady = false;
+ this.ready = false;
this.params = params;
this._ensureDefaultValues();
this.files = [];
this.files_count = 0;
this.deleted = [];
// because used outside this class
this.deleteURL = params.deleteURL;
this.enableUploadButton();
+ this._fixFileExtensions();
this._attachEventHandler();
var $me = this;
if ( this.params.ajax ) {
$(document).bind('FormManager.WindowManager.Ready', function ($e) {
$me.init();
});
}
else {
$(document).ready(function() {
$me.init();
});
}
}
/* ==== Private methods ==== */
+Uploader.prototype._fixFileExtensions = function() {
+ this.params.allowedFiletypes = this.params.allowedFiletypes.replace(/\*\./g, '').replace(/;/g, ',');
+};
+
Uploader.prototype._attachEventHandler = function() {
var $me = this;
$(document).bind('UploadsManager.Uploader.' + crc32(this.id), function ($e, $method) {
$me[$method].apply($me, Array.prototype.slice.call(arguments, 2));
});
};
Uploader.prototype._ensureDefaultValues = function() {
// Upload backend settings
- var $defaults = {
- baseUrl : '',
- uploadURL : '',
- deleteURL : '',
- previewURL : '',
- useQueryString : false,
- requeueOnError : false,
- httpSuccess : '',
- filePostName : 'Filedata',
- allowedFiletypes : '*.*',
- allowedFiletypesDescription : 'All Files',
- allowedFilesize : 0, // Default zero means "unlimited"
- multiple : 0,
- field : '',
- thumb_format: '',
- urls : '',
- names : '',
- sizes : '',
- fileQueueLimit : 0,
- buttonImageURL : '',
- buttonWidth : 1,
- buttonHeight : 1,
- buttonText : '',
- buttonTextTopPadding : 0,
- buttonTextLeftPadding : 0,
- buttonTextStyle : 'color: #000000; font-size: 16pt;',
- buttonAction : parseInt(this.params.multiple) == 1 ? -100 : -110, // SELECT_FILE : -100, SELECT_FILES : -110
- buttonDisabled : true, //false,
- buttonCursor : -1, // ARROW : -1, HAND : -2
- wmode : 'transparent', // "window", "transparent", "opaque"
- buttonPlaceholderId: false,
- ajax: false
- };
- for (var $param_name in $defaults) {
- if (this.params[$param_name] == null) {
-// console.log('setting default value [', $defaults[$param_name], '] for missing parameter [', $param_name, '] instead of [', this.params[$param_name], ']');
- this.params[$param_name] = $defaults[$param_name];
+ var $me = this,
+ $defaults = {
+ baseUrl : '',
+ uploadURL : '',
+ deleteURL : '',
+ previewURL : '',
+ allowedFiletypes : '*.*',
+ allowedFiletypesDescription : 'All Files',
+ allowedFilesize : 0, // Default zero means "unlimited"
+ multiple : 0,
+ field : '',
+ thumb_format: '',
+ urls : '',
+ names : '',
+ sizes : '',
+ ajax: false
+ };
+
+ $.each($defaults, function ($param_name, $param_value) {
+ if ($me.params[$param_name] == null) {
+// console.log('setting default value [', $param_value, '] for missing parameter [', $param_name, '] instead of [', $me.params[$param_name], ']');
+ $me.params[$param_name] = $param_value;
}
- }
+ });
};
Uploader.prototype._normalizeFilesize = function($file_size) {
var $normalize_size = parseInt($file_size);
if (isNaN($normalize_size)) {
return $file_size;
}
// in kilobytes (flash doesn't recognize numbers, that are longer, then 9 digits)
return $normalize_size / 1024;
};
Uploader.prototype._prepareFiles = function() {
- var ids = '';
- var names = '';
+ var ids = '',
+ names = '';
// process uploaded files
for (var f = 0; f < this.files.length; f++) {
if (isset(this.files[f].uploaded) && !isset(this.files[f].temp)) {
continue;
}
ids += this.files[f].id + '|';
names += this.files[f].name + '|';
}
- ids = ids.replace(/\|$/, '', ids);
- names = names.replace(/\|$/, '', names);
+ ids = ids.replace(/\|$/, '');
+ names = names.replace(/\|$/, '');
document.getElementById(this.id+'[tmp_ids]').value = ids;
document.getElementById(this.id+'[tmp_names]').value = names;
document.getElementById(this.id+'[tmp_deleted]').value = this.deleted.join('|');
};
Uploader.prototype._formatSize = function (bytes) {
var kb = Math.round(bytes / 1024);
if (kb < 1024) {
return kb + ' KB';
}
var mb = Math.round(kb / 1024 * 100) / 100;
return mb + ' MB';
};
-Uploader.prototype._executeNextEvent = function () {
- var f = this._eventQueue ? this._eventQueue.shift() : null;
- if (typeof(f) === 'function') {
- f.apply(this);
- }
-};
-
/* ==== Public methods ==== */
Uploader.prototype.init = function() {
+ var $me = this,
+ $uploader_options = {
+ runtimes : 'flash,html4', // html5
+ chunk_size: '1mb',
+ browse_button : this.id + '_browse_button',
+ container: this.id + '_container',
+ url : this.params.uploadURL,
+ flash_swf_url : this.params.baseUrl + '/Moxie.swf',
+ multi_selection: this.params.multiple > 1,
+ filters : {},
+ init: {}
+ };
+
+ if ( this.params.allowedFilesize > 0 ) {
+ $uploader_options.filters.max_file_size = this.params.allowedFilesize + 'kb';
+ }
+
+ if ( this.params.allowedFiletypes != '*' ) {
+ $uploader_options.filters.mime_types = [
+ {title : this.params.allowedFiletypesDescription, extensions : this.params.allowedFiletypes}
+ ];
+ }
+
this.IconPath = this.params.IconPath ? this.params.IconPath : '../admin_templates/img/browser/icons';
- // initialize flash object
- this.flash_id = UploadsManager._nextFlashId();
+ $uploader_options.init['Init'] = function(uploader) {
+ $me.onReady();
+ };
- // add callbacks for every event, because none of callbacks will work in other case (see swfupload documentation)
- SWFUpload.instances[this.flash_id] = this;
- SWFUpload.instances[this.flash_id].flashReady = function () { UploadsManager.onFlashReady(this.id); };
- SWFUpload.instances[this.flash_id].fileDialogStart = UploadsManager.onHandleEverything;
- SWFUpload.instances[this.flash_id].fileQueued = UploadsManager.onFileQueued;
- SWFUpload.instances[this.flash_id].fileQueueError = UploadsManager.onFileQueueError;
- SWFUpload.instances[this.flash_id].fileDialogComplete = UploadsManager.onHandleEverything;
-
- SWFUpload.instances[this.flash_id].uploadStart = UploadsManager.onUploadStart;
- SWFUpload.instances[this.flash_id].uploadProgress = UploadsManager.onUploadProgress;
- SWFUpload.instances[this.flash_id].uploadError = UploadsManager.onUploadError;
- SWFUpload.instances[this.flash_id].uploadSuccess = UploadsManager.onUploadSuccess;
- SWFUpload.instances[this.flash_id].uploadComplete = UploadsManager.onUploadComplete;
- SWFUpload.instances[this.flash_id].debug = UploadsManager.onDebug;
-
- this.swf = new SWFObject(this.params.baseUrl + '/swfupload.swf', this.flash_id, this.params.buttonWidth, this.params.buttonHeight, '9', '#FFFFFF');
- this.swf.setAttribute('style', '');
- this.swf.addParam('wmode', encodeURIComponent(this.params.wmode));
-
- this.swf.addVariable('movieName', encodeURIComponent(this.flash_id));
- this.swf.addVariable('fileUploadLimit', 0);
- this.swf.addVariable('fileQueueLimit', encodeURIComponent(this.params.fileQueueLimit));
- this.swf.addVariable('fileSizeLimit', encodeURIComponent(this.params.allowedFilesize)); // in kilobytes
- this.swf.addVariable('fileTypes', encodeURIComponent(this.params.allowedFiletypes));
- this.swf.addVariable('fileTypesDescription', encodeURIComponent(this.params.allowedFiletypesDescription));
- this.swf.addVariable('uploadURL', encodeURIComponent(this.params.uploadURL));
-
- // upload button appearance
- this.swf.addVariable('buttonImageURL', encodeURIComponent(this.params.buttonImageURL));
- this.swf.addVariable('buttonWidth', encodeURIComponent(this.params.buttonWidth));
- this.swf.addVariable('buttonHeight', encodeURIComponent(this.params.buttonHeight));
- this.swf.addVariable('buttonText', encodeURIComponent(this.params.buttonText));
- this.swf.addVariable('buttonTextTopPadding', encodeURIComponent(this.params.buttonTextTopPadding));
- this.swf.addVariable('buttonTextLeftPadding', encodeURIComponent(this.params.buttonTextLeftPadding));
- this.swf.addVariable('buttonTextStyle', encodeURIComponent(this.params.buttonTextStyle));
- this.swf.addVariable('buttonAction', encodeURIComponent(this.params.buttonAction));
- this.swf.addVariable('buttonDisabled', encodeURIComponent(this.params.buttonDisabled));
- this.swf.addVariable('buttonCursor', encodeURIComponent(this.params.buttonCursor));
+ $uploader_options.init['FilesAdded'] = function(uploader, files) {
+ $.each(files, function (index, file) {
+ $me.onFileQueued(file);
+ });
- if (UploadsManager._debugMode) {
- this.swf.addVariable('debugEnabled', encodeURIComponent('true')); // flash var
- }
+ $me.startUpload();
+ };
- var $me = this;
+ $uploader_options.init['FilesRemoved'] = function(uploader, files) {
+ $.each(files, function (index, file) {
+ if ( file.status != plupload.QUEUED ) {
+ uploader.stop();
+ uploader.start();
+ }
+ });
+ };
+
+ $uploader_options.init['Error'] = function(uploader, error) {
+ $me.onError(error);
+ };
+
+ $uploader_options.init['BeforeUpload'] = function(uploader, file) {
+ return $me.onUploadFileStart(uploader, file);
+ };
+
+ $uploader_options.init['UploadProgress'] = function(uploader, file) {
+ $me.onUploadProgress(file);
+ };
+
+ $uploader_options.init['FileUploaded'] = function(uploader, file, response) {
+ $me.onUploadFinished(file, response);
+ };
+
+ this.instance = new plupload.Uploader($uploader_options);
Application.setHook(
'm:OnAfterFormInit',
function () {
$me.renderBrowseButton();
}
- )
+ );
this.refreshQueue();
};
Uploader.prototype.refreshQueue = function($params) {
if ( $params !== undefined ) {
$.extend(true, this.params, $params);
document.getElementById(this.id+'[upload]').value = this.params.names;
document.getElementById(this.id+'[order]').value = this.params.names;
}
// 1. remove queue DIVs for files, that doesn't exist after upload was made
var $new_file_ids = this.getFileIds(this.params.names);
for (var $i = 0; $i < this.files.length; $i++) {
if ( !in_array(this.files[$i].id, $new_file_ids) ) {
this.updateQueueFile($i, true);
}
}
this.files = [];
this.files_count = 0;
this.deleted = [];
if (this.params.urls != '') {
var urls = this.params.urls.split('|'),
names = this.params.names.split('|'),
sizes = this.params.sizes.split('|');
for (var i = 0; i < urls.length; i++) {
var a_file = {
+ // original properties from Uploader
id : this.getUploadedFileId(names[i]),
name : names[i],
- url : urls[i],
size: sizes[i],
- uploaded : 1,
- progress: 100
+ percent: 100,
+
+ // custom properties
+ url : urls[i],
+ uploaded : 1
};
this.files_count++;
this.files.push(a_file);
}
this.updateInfo();
}
};
Uploader.prototype.getFileIds = function($file_names) {
var $ret = [];
if ( !$file_names.length ) {
return $ret;
}
if ( !$.isArray($file_names) ) {
$file_names = $file_names.split('|');
}
for (var i = 0; i < $file_names.length; i++) {
$ret.push(this.getUploadedFileId($file_names[i]))
}
return $ret;
};
Uploader.prototype.getUploadedFileId = function($file_name) {
return 'uploaded_' + crc32($file_name);
};
Uploader.prototype.enableUploadButton = function() {
- var $me = this;
-
- // enable upload button, when flash is fully loaded
- this.queueEvent(
- function() {
- setTimeout(
- function () {
- $me.callFlash('SetButtonDisabled', [false]);
- }, 0
- )
- }
- );
+ // enable upload button, when plupload runtime is fully loaded
+ $('#' + jq(this.id + '_browse_button')).prop('disabled', false).removeClass('button-disabled');
};
Uploader.prototype.renderBrowseButton = function() {
- var holder = document.getElementById(this.params.buttonPlaceholderId);
- this.swf.write(holder);
-
- this.flash = document.getElementById(this.flash_id);
+ this.instance.init();
};
Uploader.prototype.remove = function() {
- var id = this.params.buttonPlaceholderId;
-
- var obj = document.getElementById(id);
-
- if (obj/* && obj.nodeName == "OBJECT"*/) {
- var u = navigator.userAgent.toLowerCase();
- var p = navigator.platform.toLowerCase();
- var windows = p ? /win/.test(p) : /win/.test(u);
- var $me = this;
-
- if (document.all && windows) {
- obj.style.display = "none";
- (function(){
- if (obj.readyState == 4) {
- $me.removeObjectInIE(id);
- }
- else {
- setTimeout(arguments.callee, 10);
- }
- })();
- }
- else {
- obj.parentNode.removeChild(obj);
- }
- }
-};
-
-Uploader.prototype.removeObjectInIE = function(id) {
- var obj = document.getElementById(id);
- if (obj) {
- for (var i in obj) {
- if (typeof obj[i] == 'function') {
- obj[i] = null;
- }
- }
- obj.parentNode.removeChild(obj);
- }
+ this.instance.destroy();
};
Uploader.prototype.isImage = function($filename) {
this.removeTempExtension($filename).match(/\.([^.]*)$/);
var $ext = RegExp.$1.toLowerCase();
return $ext.match(/^(bmp|gif|jpg|jpeg|png)$/);
};
Uploader.prototype.getFileIcon = function($filename) {
this.removeTempExtension($filename).match(/\.([^.]*)$/);
var $ext = RegExp.$1.toLowerCase(),
$ext_overrides = {
'doc': '^(docx|dotx|docm|dotm)$',
'xls': '^(xlsx|xltx|xlsm|xltm|xlam|xlsb)$',
'ppt': '^(pptx|potx|ppsx|ppam|pptm|potm|ppsm)$'
};
$.each($ext_overrides, function ($new_ext, $expression) {
var $regexp = new RegExp($expression);
if ( $ext.match($regexp) ) {
$ext = $new_ext;
return false;
}
return true;
});
var $icon = $ext.match(/^(ai|avi|bmp|cs|dll|doc|dot|exe|fla|gif|htm|html|jpg|js|mdb|mp3|pdf|ppt|rdp|swf|swt|txt|vsd|xls|xml|zip)$/) ? $ext : 'default.icon';
return this.IconPath + '/' + $icon + '.gif';
};
Uploader.prototype.removeTempExtension = function ($file) {
return $file.replace(/(_[\d]+)?\.tmp$/, '');
};
Uploader.prototype.getQueueElement = function($file) {
- var $ret = '';
- var $icon_image = this.getFileIcon($file.name);
- var $file_label = this.removeTempExtension($file.name) + ' (' + this._formatSize($file.size) + ')';
- var $need_preview = false;
+ var $me = this,
+ $ret = '',
+ $icon_image = this.getFileIcon($file.name),
+ $file_label = this.removeTempExtension($file.name) + ' (' + this._formatSize($file.size) + ')',
+ $need_preview = false;
if (isset($file.uploaded)) {
// add deletion checkbox
$need_preview = (this.params.thumb_format.length > 0) && this.isImage($file.name);
$ret += '<div class="left delete-checkbox"><input type="checkbox" class="delete-file-btn" checked/></div>';
// add icon based on file type
$ret += '<div class="left">';
if ($need_preview) {
- $ret += '<a href="' + $file.url + '" target="_new"><img class="thumbnail-image" large_src="' + this.getUrl($file, true) + '" src="' + $icon_image + '" alt=""/></a>';
+ $ret += '<a href="' + $file.url + '" target="_new"><img class="thumbnail-image" large_src="' + this.getPreviewUrl($file, true) + '" src="' + $icon_image + '" alt=""/></a>';
}
else {
$ret += '<img src="' + $icon_image + '"/>';
}
$ret += '</div>';
// add filename + preview link
$ret += '<div class="left file-label"><a href="' + $file.url + '" target="_new">' + $file_label + '</a></div>';
}
else {
// add icon based on file type
$ret += '<div class="left"><img src="' + $icon_image + '"/></div>';
// add filename
$ret += '<div class="left file-label">' + $file_label + '</div>';
// add empty progress bar
$ret += '<div id="' + $file.id + '_progress" class="progress-container left"><div class="progress-empty"><div class="progress-full" style="width: 0%;"></div></div></div>';
// add cancel upload link
$ret += '<div class="left"><a href="#" class="cancel-upload-btn">Cancel</a></div>';
}
$ret += '<div style="clear: both;"/>';
$ret = $('<div id="' + $file.id + '_queue_row" class="file' + ($need_preview ? ' preview' : '') + '">' + $ret + '</div>');
// set click events
- var $me = this;
+ $('.delete-file-btn', $ret).click(function ($e) {
+ $(this).prop('checked', !$me.deleteFile($file));
+ });
- $('.delete-file-btn', $ret).click(
- function ($e) {
- $(this).prop('checked', !UploadsManager.DeleteFile($me.id, $file.name));
- }
- );
+ $('.cancel-upload-btn', $ret).click(function ($e) {
+ $me.removeFile($file);
- $('.cancel-upload-btn', $ret).click(
- function ($e) {
- UploadsManager.CancelFile(UploadsManager._getUploader($file).id, $file.id);
- return false;
- }
- );
+ $e.preventDefault();
+ });
// prepare auto-loading preview
var $image = $('img.thumbnail-image', $ret);
if ($image.length > 0) {
var $tmp_image = new Image();
$tmp_image.src = $image.attr('large_src');
$($tmp_image).load (
function ($e) {
$image.attr('src', $tmp_image.src).addClass('thumbnail');
}
);
}
return $ret;
};
Uploader.prototype.getSortedFiles = function($ordered_queue) {
var $me = this;
var $ret = $.map($me.files, function ($elem, $index) {
var $file_id = $ordered_queue[$index].replace(/_queue_row$/, ''),
$file_index = $me.getFileIndex({id: $file_id});
return $me.files[$file_index].name;
});
return $ret;
};
Uploader.prototype.updateQueueFile = function($file_index, $delete_file) {
var $queue_container = $( jq('#' + this.id + '_queueinfo') );
if ($delete_file !== undefined && $delete_file) {
$( jq('#' + this.files[$file_index].id + '_queue_row') ).remove();
if (this.files.length == 1) {
$queue_container.css('margin-top', '0px');
}
return ;
}
var $ret = this.getQueueElement(this.files[$file_index]),
$row = $(jq('#' + this.files[$file_index].id + '_queue_row'));
if ($row.length > 0) {
// file round -> replace
$row.replaceWith($ret);
}
else {
// file not found - add
$( jq('#' + this.id + '_queueinfo') ).append($ret);
$queue_container.css('margin-top', '8px');
}
};
Uploader.prototype.updateInfo = function($file_index, $prepare_only) {
if ($prepare_only === undefined || !$prepare_only) {
if ($file_index === undefined) {
for (var f = 0; f < this.files.length; f++) {
this.updateQueueFile(f);
}
}
else {
this.updateQueueFile($file_index);
}
}
this._prepareFiles();
};
Uploader.prototype.updateProgressOnly = function ($file_index) {
- var $progress_code = '<div class="progress-empty" title="' + this.files[$file_index].progress + '%"><div class="progress-full" style="width: ' + this.files[$file_index].progress + '%;"></div></div>';
+ var $progress_code = '<div class="progress-empty" title="' + this.files[$file_index].percent + '%"><div class="progress-full" style="width: ' + this.files[$file_index].percent + '%;"></div></div>';
$('#' + this.files[$file_index].id + '_progress').html($progress_code);
};
Uploader.prototype.removeFile = function (file) {
var count = 0,
n_files = [],
$to_delete = [];
- for (var f = 0; f < this.files.length; f++) {
- if (this.files[f].id != file.id && this.files[f].name != file.id) {
- n_files.push(this.files[f]);
- count++;
+ if (!isset(file.uploaded)) {
+ this.instance.removeFile(file);
+ }
+
+ $.each(this.files, function (f, current_file) {
+ if ( current_file.id == file.id || current_file.name == file.name ) {
+ $to_delete.push(f);
}
else {
- $to_delete.push(f);
+ n_files.push(current_file);
+ count++;
}
- }
+ });
for (var $i = 0; $i < $to_delete.length; $i++) {
this.updateQueueFile($to_delete[$i], true);
}
this.files = n_files;
this.files_count = count;
this.updateInfo(undefined, true);
};
Uploader.prototype.hasQueue = function() {
for (var f = 0; f < this.files.length; f++) {
if (isset(this.files[f].uploaded)) {
continue;
}
return true;
}
return false;
};
Uploader.prototype.startUpload = function() {
- this.uploadCancelled = false;
+ if ( this.hasQueue() ) {
+ this.instance.start();
+ }
+};
- if (!this.hasQueue()) {
- return;
+Uploader.prototype.deleteFile = function(file, confirmed) {
+ if (!confirmed && !confirm('Are you sure you want to delete "' + file.name + '" file?')) {
+ return false;
}
- this.callFlash('StartUpload');
+ var $me = this;
+
+ $.get(
+ this.getDeleteUrl(file),
+ function ($data) {
+ $me.removeFile(file);
+ $me.deleted.push(file.name);
+ $me.updateInfo(undefined, true);
+ }
+ );
+
+ return true;
};
-Uploader.prototype.cancelUpload = function() {
- this.callFlash('StopUpload');
- var $stats = this.callFlash('GetStats');
+Uploader.prototype.onUploadFileStart = function(uploader, file) {
+ var $upload_url = this.params.uploadURL,
+ $file_index = this.getFileIndex(file),
+ $extra_params = {
+ field: this.params.field,
+ id: file.id,
+ flashsid: this.params.flashsid
+ };
- while ($stats.files_queued > 0) {
- this.callFlash('CancelUpload');
- $stats = this.callFlash('GetStats');
- }
+ this.files[$file_index].percent = file.percent;
+ this.updateProgressOnly($file_index);
- this.uploadCancelled = true;
+ $upload_url += ($upload_url.indexOf('?') ? '&' : '?');
+
+ $.each($extra_params, function ($param_name, $param_value) {
+ $upload_url += $param_name + '=' + encodeURIComponent($param_value) + '&';
+ });
+
+ uploader.settings.url = $upload_url;
+
+ return true;
};
-Uploader.prototype.UploadFileStart = function(file) {
+Uploader.prototype.onUploadProgress = function(file) {
var $file_index = this.getFileIndex(file);
- this.files[$file_index].progress = 0;
+
+ this.files[$file_index].percent = file.percent;
this.updateProgressOnly($file_index);
+};
- this.callFlash('AddFileParam', [file.id, 'field', this.params.field]);
- this.callFlash('AddFileParam', [file.id, 'id', file.id]);
- this.callFlash('AddFileParam', [file.id, 'flashsid', this.params.flashsid]);
+Uploader.prototype.onFileQueued = function(file) {
+ if (this.files_count >= this.params.multiple) {
+ // new file can exceed allowed file number
+ if (this.params.multiple > 1) {
+ // it definitely exceed it
+ var $error = {
+ 'file': file, 'code': 'ERROR_1', 'message': 'Files count exceeds allowed limit.'
+ };
- // we can prevent user from adding any files here :)
- this.callFlash('ReturnUploadStart', [true]);
+ this.instance.trigger('Error', $error);
+ }
+ else {
+ // delete file added
+ this.files_count++;
+ this.files.push(file);
+
+ if (this.files[0].uploaded) {
+ this.deleteFile(this.files[0], true);
+ }
+ else {
+ this.instance.removeFile(file);
+ }
+ }
+ }
+ else {
+ // new file will not exceed allowed file number
+ this.files_count++;
+ this.files.push(file);
+ }
+
+ this.updateInfo(this.files.length - 1);
};
-Uploader.prototype.UploadProgress = function(file, bytesLoaded, bytesTotal) {
- var $file_index = this.getFileIndex(file);
- this.files[$file_index].progress = Math.round(bytesLoaded / bytesTotal * 100);
- this.updateProgressOnly($file_index);
+Uploader.prototype.onError = function(error) {
+ this.removeFile(error.file);
+
+ if ( error.code == plupload.FILE_SIZE_ERROR ) {
+ error.message = 'File size exceeds allowed limit.';
+ }
+ else if ( error.code == plupload.FILE_EXTENSION_ERROR ) {
+ error.message = 'File is not an allowed file type.';
+ }
+
+ setTimeout(function () {
+ alert('Error: ' + error.message + "\n" + 'Occurred on file ' + error.file.name);
+ }, 0);
};
-Uploader.prototype.UploadSuccess = function(file, serverData, receivedResponse) {
- if (!receivedResponse) {
+Uploader.prototype.onUploadFinished = function(file, response) {
+ var $json_response = eval('(' + response.response + ')');
+
+ if (response.status != 200) {
return ;
}
- for (var f = 0; f < this.files.length; f++) {
- if (this.files[f].id == file.id) {
- // new uploaded file name returned by OnUploadFile event
- this.files[f].name = serverData;
- }
+ if ( $json_response.status == 'error' ) {
+ var $error = {
+ 'file': file, 'code': $json_response.error.code, 'message': $json_response.error.message
+ };
+
+ this.instance.trigger('Error', $error);
+
+ return ;
}
+
+ // new uploaded file name returned by OnUploadFile event
+ file.name = $json_response.result;
+
+ this.onUploadFileComplete(file);
};
-Uploader.prototype.UploadFileComplete = function(file) {
+Uploader.prototype.onUploadFileComplete = function(file) {
// file was uploaded OR file upload was cancelled
var $file_index = this.getFileIndex(file);
if ($file_index !== false) {
// in case if file upload was cancelled, then no info here
- this.files[$file_index].uploaded = 1;
- this.files[$file_index].progress = 100;
+ this.files[$file_index].name = file.name;
+ this.files[$file_index].percent = file.percent;
+
this.files[$file_index].temp = 1;
- this.files[$file_index].url = this.getUrl(this.files[$file_index]);
+ this.files[$file_index].uploaded = 1;
+ this.files[$file_index].url = this.getPreviewUrl(this.files[$file_index]);
this.updateInfo($file_index);
}
-
- // upload next file in queue
- var $stats = this.callFlash('GetStats');
-
- if ($stats.files_queued > 0) {
- this.callFlash('StartUpload');
- }
- else {
- UploadsManager.UploadQueueComplete(this);
- }
};
-Uploader.prototype.getUrl = function($file, $preview) {
- var $url = this.params.previewURL.replace('#FILE#', encodeURIComponent($file.name)).replace('#FIELD#', this.params.field);
-
- if ( $file.temp !== undefined && $file.temp ) {
- $url += '&tmp=1&id=' + $file.id;
- }
+Uploader.prototype.getPreviewUrl = function($file, $preview) {
+ var $url = this.getUrl(this.params.previewURL, $file);
if ( $preview !== undefined && $preview === true ) {
$url += '&thumb=1';
}
return $url;
};
-Uploader.prototype.getFileIndex = function(file) {
- for (var f = 0; f < this.files.length; f++) {
- if (this.files[f].id == file.id) {
- return f;
- }
- }
-
- return false;
+Uploader.prototype.getDeleteUrl = function($file) {
+ return this.getUrl(this.params.deleteURL, $file);
};
-Uploader.prototype.queueEvent = function (function_body) {
- // Warning: Don't call this.debug inside here or you'll create an infinite loop
- var self = this;
-
- // Queue the event
- this._eventQueue.push(function_body);
-
- if (!this.flashReady) {
- // don't execute any flash-related events, while it's not completely loaded
- return ;
- }
-
- // Execute the next queued event
- setTimeout(
- function () {
- self._executeNextEvent();
- }, 0
- );
-};
-
-Uploader.prototype._executeQueuedEvents = function() {
- var $me = this;
+Uploader.prototype.getUrl = function($base_url, $file) {
+ var $replacements = {
+ '#FILE#': $file.name,
+ '#FIELD#': this.params.field,
+ '#FIELD_ID#': this.id
+ };
- setTimeout(
- function () {
- $me._executeNextEvent();
+ var $url = $base_url;
- if ($me._eventQueue.length > 0) {
- $me._executeQueuedEvents();
- }
-
- }, 0
- );
-};
+ $.each($replacements, function ($replace_from, $replace_to) {
+ $url = $url.replace($replace_from, encodeURIComponent($replace_to));
+ });
-// Private: callFlash handles function calls made to the Flash element.
-// Calls are made with a setTimeout for some functions to work around
-// bugs in the ExternalInterface library.
-Uploader.prototype.callFlash = function (functionName, argumentArray) {
- argumentArray = argumentArray || [];
-
- var returnValue;
-
- if (typeof this.flash[functionName] === 'function') {
- // We have to go through all this if/else stuff because the Flash functions don't have apply() and only accept the exact number of arguments.
- if (argumentArray.length === 0) {
- returnValue = this.flash[functionName]();
- } else if (argumentArray.length === 1) {
- returnValue = this.flash[functionName](argumentArray[0]);
- } else if (argumentArray.length === 2) {
- returnValue = this.flash[functionName](argumentArray[0], argumentArray[1]);
- } else if (argumentArray.length === 3) {
- returnValue = this.flash[functionName](argumentArray[0], argumentArray[1], argumentArray[2]);
- } else {
- throw 'Too many arguments';
- }
-
- // Unescape file post param values
- if (returnValue != undefined && typeof returnValue.post === 'object') {
- returnValue = this.unescapeFilePostParams(returnValue);
- }
-
- return returnValue;
- } else {
-// alert('invalid function name: ' + functionName);
- throw "Invalid function name: " + functionName;
+ if ( $file.temp !== undefined && $file.temp ) {
+ $url += '&tmp=1&id=' + $file.id;
}
+
+ return $url;
};
-// Private: unescapeFileParams is part of a workaround for a flash bug where objects passed through ExternalInterface cannot have
-// properties that contain characters that are not valid for JavaScript identifiers. To work around this
-// the Flash Component escapes the parameter names and we must unescape again before passing them along.
-Uploader.prototype.unescapeFilePostParams = function (file) {
- var reg = /[$]([0-9a-f]{4})/i;
- var unescapedPost = {};
- var uk;
-
- if (file != undefined) {
- for (var k in file.post) {
- if (file.post.hasOwnProperty(k)) {
- uk = k;
- var match;
- while ((match = reg.exec(uk)) !== null) {
- uk = uk.replace(match[0], String.fromCharCode(parseInt("0x" + match[1], 16)));
- }
- unescapedPost[uk] = file.post[k];
- }
+Uploader.prototype.getFileIndex = function(file) {
+ for (var f = 0; f < this.files.length; f++) {
+ if (this.files[f].id == file.id) {
+ return f;
}
-
- file.post = unescapedPost;
}
- return file;
+ return false;
};
-Uploader.prototype.onFlashReady = function() {
- var $me = this;
- this.flashReady = true;
-
- // process events, queued before flash load
- this._executeQueuedEvents();
+Uploader.prototype.onReady = function() {
+ this.ready = true;
+ UploadsManager.onReady();
};
\ No newline at end of file
Index: branches/5.3.x/core/admin_templates/js/uploader/plupload.dev.js
===================================================================
--- branches/5.3.x/core/admin_templates/js/uploader/plupload.dev.js (nonexistent)
+++ branches/5.3.x/core/admin_templates/js/uploader/plupload.dev.js (revision 16002)
@@ -0,0 +1,2254 @@
+/**
+ * Plupload - multi-runtime File Uploader
+ * v2.0.0
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ *
+ * Date: 2013-09-23
+ */
+/**
+ * Plupload.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global mOxie:true */
+
+;(function(window, o, undef) {
+
+var delay = window.setTimeout
+, fileFilters = {}
+;
+
+// convert plupload features to caps acceptable by mOxie
+function normalizeCaps(settings) {
+ var features = settings.required_features, caps = {};
+
+ function resolve(feature, value, strict) {
+ // Feature notation is deprecated, use caps (this thing here is required for backward compatibility)
+ var map = {
+ chunks: 'slice_blob',
+ jpgresize: 'send_binary_string',
+ pngresize: 'send_binary_string',
+ progress: 'report_upload_progress',
+ multi_selection: 'select_multiple',
+ max_file_size: 'access_binary',
+ dragdrop: 'drag_and_drop',
+ drop_element: 'drag_and_drop',
+ headers: 'send_custom_headers',
+ canSendBinary: 'send_binary',
+ triggerDialog: 'summon_file_dialog'
+ };
+
+ if (map[feature]) {
+ caps[map[feature]] = value;
+ } else if (!strict) {
+ caps[feature] = value;
+ }
+ }
+
+ if (typeof(features) === 'string') {
+ plupload.each(features.split(/\s*,\s*/), function(feature) {
+ resolve(feature, true);
+ });
+ } else if (typeof(features) === 'object') {
+ plupload.each(features, function(value, feature) {
+ resolve(feature, value);
+ });
+ } else if (features === true) {
+ // check settings for required features
+ if (!settings.multipart) { // special care for multipart: false
+ caps.send_binary_string = true;
+ }
+
+ if (settings.chunk_size > 0) {
+ caps.slice_blob = true;
+ }
+
+ if (settings.resize.enabled) {
+ caps.send_binary_string = true;
+ }
+
+ plupload.each(settings, function(value, feature) {
+ resolve(feature, !!value, true); // strict check
+ });
+ }
+
+ return caps;
+}
+
+/**
+ * @module plupload
+ * @static
+ */
+var plupload = {
+ /**
+ * Plupload version will be replaced on build.
+ *
+ * @property VERSION
+ * @for Plupload
+ * @static
+ * @final
+ */
+ VERSION : '2.0.0',
+
+ /**
+ * Inital state of the queue and also the state ones it's finished all it's uploads.
+ *
+ * @property STOPPED
+ * @static
+ * @final
+ */
+ STOPPED : 1,
+
+ /**
+ * Upload process is running
+ *
+ * @property STARTED
+ * @static
+ * @final
+ */
+ STARTED : 2,
+
+ /**
+ * File is queued for upload
+ *
+ * @property QUEUED
+ * @static
+ * @final
+ */
+ QUEUED : 1,
+
+ /**
+ * File is being uploaded
+ *
+ * @property UPLOADING
+ * @static
+ * @final
+ */
+ UPLOADING : 2,
+
+ /**
+ * File has failed to be uploaded
+ *
+ * @property FAILED
+ * @static
+ * @final
+ */
+ FAILED : 4,
+
+ /**
+ * File has been uploaded successfully
+ *
+ * @property DONE
+ * @static
+ * @final
+ */
+ DONE : 5,
+
+ // Error constants used by the Error event
+
+ /**
+ * Generic error for example if an exception is thrown inside Silverlight.
+ *
+ * @property GENERIC_ERROR
+ * @static
+ * @final
+ */
+ GENERIC_ERROR : -100,
+
+ /**
+ * HTTP transport error. For example if the server produces a HTTP status other than 200.
+ *
+ * @property HTTP_ERROR
+ * @static
+ * @final
+ */
+ HTTP_ERROR : -200,
+
+ /**
+ * Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.
+ *
+ * @property IO_ERROR
+ * @static
+ * @final
+ */
+ IO_ERROR : -300,
+
+ /**
+ * Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.
+ *
+ * @property SECURITY_ERROR
+ * @static
+ * @final
+ */
+ SECURITY_ERROR : -400,
+
+ /**
+ * Initialization error. Will be triggered if no runtime was initialized.
+ *
+ * @property INIT_ERROR
+ * @static
+ * @final
+ */
+ INIT_ERROR : -500,
+
+ /**
+ * File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered.
+ *
+ * @property FILE_SIZE_ERROR
+ * @static
+ * @final
+ */
+ FILE_SIZE_ERROR : -600,
+
+ /**
+ * File extension error. If the user selects a file that isn't valid according to the filters setting.
+ *
+ * @property FILE_EXTENSION_ERROR
+ * @static
+ * @final
+ */
+ FILE_EXTENSION_ERROR : -601,
+
+ /**
+ * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again.
+ *
+ * @property FILE_DUPLICATE_ERROR
+ * @static
+ * @final
+ */
+ FILE_DUPLICATE_ERROR : -602,
+
+ /**
+ * Runtime will try to detect if image is proper one. Otherwise will throw this error.
+ *
+ * @property IMAGE_FORMAT_ERROR
+ * @static
+ * @final
+ */
+ IMAGE_FORMAT_ERROR : -700,
+
+ /**
+ * While working on the image runtime will try to detect if the operation may potentially run out of memeory and will throw this error.
+ *
+ * @property IMAGE_MEMORY_ERROR
+ * @static
+ * @final
+ */
+ IMAGE_MEMORY_ERROR : -701,
+
+ /**
+ * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
+ *
+ * @property IMAGE_DIMENSIONS_ERROR
+ * @static
+ * @final
+ */
+ IMAGE_DIMENSIONS_ERROR : -702,
+
+ /**
+ * Mime type lookup table.
+ *
+ * @property mimeTypes
+ * @type Object
+ * @final
+ */
+ mimeTypes : o.mimes,
+
+ /**
+ * In some cases sniffing is the only way around :(
+ */
+ ua: o.ua,
+
+ /**
+ * Gets the true type of the built-in object (better version of typeof).
+ * @credits Angus Croll (http://javascriptweblog.wordpress.com/)
+ *
+ * @method typeOf
+ * @static
+ * @param {Object} o Object to check.
+ * @return {String} Object [[Class]]
+ */
+ typeOf: o.typeOf,
+
+ /**
+ * Extends the specified object with another object.
+ *
+ * @method extend
+ * @static
+ * @param {Object} target Object to extend.
+ * @param {Object..} obj Multiple objects to extend with.
+ * @return {Object} Same as target, the extended object.
+ */
+ extend : o.extend,
+
+ /**
+ * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
+ * The only way a user would be able to get the same ID is if the two persons at the same exact milisecond manages
+ * 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.
+ * 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
+ * to an user unique key.
+ *
+ * @method guid
+ * @static
+ * @return {String} Virtually unique id.
+ */
+ guid : o.guid,
+
+ /**
+ * Get array of DOM Elements by their ids.
+ *
+ * @method get
+ * @for Utils
+ * @param {String} id Identifier of the DOM Element
+ * @return {Array}
+ */
+ get : function get(ids) {
+ var els = [], el;
+
+ if (o.typeOf(ids) !== 'array') {
+ ids = [ids];
+ }
+
+ var i = ids.length;
+ while (i--) {
+ el = o.get(ids[i]);
+ if (el) {
+ els.push(el);
+ }
+ }
+
+ return els.length ? els : null;
+ },
+
+ /**
+ * Executes the callback function for each item in array/object. If you return false in the
+ * callback it will break the loop.
+ *
+ * @method each
+ * @static
+ * @param {Object} obj Object to iterate.
+ * @param {function} callback Callback function to execute for each item.
+ */
+ each : o.each,
+
+ /**
+ * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
+ *
+ * @method getPos
+ * @static
+ * @param {Element} node HTML element or element id to get x, y position from.
+ * @param {Element} root Optional root element to stop calculations at.
+ * @return {object} Absolute position of the specified element object with x, y fields.
+ */
+ getPos : o.getPos,
+
+ /**
+ * Returns the size of the specified node in pixels.
+ *
+ * @method getSize
+ * @static
+ * @param {Node} node Node to get the size of.
+ * @return {Object} Object with a w and h property.
+ */
+ getSize : o.getSize,
+
+ /**
+ * Encodes the specified string.
+ *
+ * @method xmlEncode
+ * @static
+ * @param {String} s String to encode.
+ * @return {String} Encoded string.
+ */
+ xmlEncode : function(str) {
+ var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g;
+
+ return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
+ return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
+ }) : str;
+ },
+
+ /**
+ * Forces anything into an array.
+ *
+ * @method toArray
+ * @static
+ * @param {Object} obj Object with length field.
+ * @return {Array} Array object containing all items.
+ */
+ toArray : o.toArray,
+
+ /**
+ * Find an element in array and return it's index if present, otherwise return -1.
+ *
+ * @method inArray
+ * @static
+ * @param {mixed} needle Element to find
+ * @param {Array} array
+ * @return {Int} Index of the element, or -1 if not found
+ */
+ inArray : o.inArray,
+
+ /**
+ * Extends the language pack object with new items.
+ *
+ * @method addI18n
+ * @static
+ * @param {Object} pack Language pack items to add.
+ * @return {Object} Extended language pack object.
+ */
+ addI18n : o.addI18n,
+
+ /**
+ * Translates the specified string by checking for the english string in the language pack lookup.
+ *
+ * @method translate
+ * @static
+ * @param {String} str String to look for.
+ * @return {String} Translated string or the input string if it wasn't found.
+ */
+ translate : o.translate,
+
+ /**
+ * Checks if object is empty.
+ *
+ * @method isEmptyObj
+ * @static
+ * @param {Object} obj Object to check.
+ * @return {Boolean}
+ */
+ isEmptyObj : o.isEmptyObj,
+
+ /**
+ * Checks if specified DOM element has specified class.
+ *
+ * @method hasClass
+ * @static
+ * @param {Object} obj DOM element like object to add handler to.
+ * @param {String} name Class name
+ */
+ hasClass : o.hasClass,
+
+ /**
+ * Adds specified className to specified DOM element.
+ *
+ * @method addClass
+ * @static
+ * @param {Object} obj DOM element like object to add handler to.
+ * @param {String} name Class name
+ */
+ addClass : o.addClass,
+
+ /**
+ * Removes specified className from specified DOM element.
+ *
+ * @method removeClass
+ * @static
+ * @param {Object} obj DOM element like object to add handler to.
+ * @param {String} name Class name
+ */
+ removeClass : o.removeClass,
+
+ /**
+ * Returns a given computed style of a DOM element.
+ *
+ * @method getStyle
+ * @static
+ * @param {Object} obj DOM element like object.
+ * @param {String} name Style you want to get from the DOM element
+ */
+ getStyle : o.getStyle,
+
+ /**
+ * Adds an event handler to the specified object and store reference to the handler
+ * in objects internal Plupload registry (@see removeEvent).
+ *
+ * @method addEvent
+ * @static
+ * @param {Object} obj DOM element like object to add handler to.
+ * @param {String} name Name to add event listener to.
+ * @param {Function} callback Function to call when event occurs.
+ * @param {String} (optional) key that might be used to add specifity to the event record.
+ */
+ addEvent : o.addEvent,
+
+ /**
+ * Remove event handler from the specified object. If third argument (callback)
+ * is not specified remove all events with the specified name.
+ *
+ * @method removeEvent
+ * @static
+ * @param {Object} obj DOM element to remove event listener(s) from.
+ * @param {String} name Name of event listener to remove.
+ * @param {Function|String} (optional) might be a callback or unique key to match.
+ */
+ removeEvent: o.removeEvent,
+
+ /**
+ * Remove all kind of events from the specified object
+ *
+ * @method removeAllEvents
+ * @static
+ * @param {Object} obj DOM element to remove event listeners from.
+ * @param {String} (optional) unique key to match, when removing events.
+ */
+ removeAllEvents: o.removeAllEvents,
+
+ /**
+ * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
+ *
+ * @method cleanName
+ * @static
+ * @param {String} s String to clean up.
+ * @return {String} Cleaned string.
+ */
+ cleanName : function(name) {
+ var i, lookup;
+
+ // Replace diacritics
+ lookup = [
+ /[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
+ /\307/g, 'C', /\347/g, 'c',
+ /[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
+ /[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
+ /\321/g, 'N', /\361/g, 'n',
+ /[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
+ /[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
+ ];
+
+ for (i = 0; i < lookup.length; i += 2) {
+ name = name.replace(lookup[i], lookup[i + 1]);
+ }
+
+ // Replace whitespace
+ name = name.replace(/\s+/g, '_');
+
+ // Remove anything else
+ name = name.replace(/[^a-z0-9_\-\.]+/gi, '');
+
+ return name;
+ },
+
+ /**
+ * Builds a full url out of a base URL and an object with items to append as query string items.
+ *
+ * @method buildUrl
+ * @static
+ * @param {String} url Base URL to append query string items to.
+ * @param {Object} items Name/value object to serialize as a querystring.
+ * @return {String} String with url + serialized query string items.
+ */
+ buildUrl : function(url, items) {
+ var query = '';
+
+ plupload.each(items, function(value, name) {
+ query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
+ });
+
+ if (query) {
+ url += (url.indexOf('?') > 0 ? '&' : '?') + query;
+ }
+
+ return url;
+ },
+
+ /**
+ * Formats the specified number as a size string for example 1024 becomes 1 KB.
+ *
+ * @method formatSize
+ * @static
+ * @param {Number} size Size to format as string.
+ * @return {String} Formatted size string.
+ */
+ formatSize : function(size) {
+ if (size === undef || /\D/.test(size)) {
+ return plupload.translate('N/A');
+ }
+
+ // TB
+ if (size > 1099511627776) {
+ return Math.round(size / 1099511627776, 1) + " " + plupload.translate('tb');
+ }
+
+ // GB
+ if (size > 1073741824) {
+ return Math.round(size / 1073741824, 1) + " " + plupload.translate('gb');
+ }
+
+ // MB
+ if (size > 1048576) {
+ return Math.round(size / 1048576, 1) + " " + plupload.translate('mb');
+ }
+
+ // KB
+ if (size > 1024) {
+ return Math.round(size / 1024, 1) + " " + plupload.translate('kb');
+ }
+
+ return size + " " + plupload.translate('b');
+ },
+
+
+ /**
+ * Parses the specified size string into a byte value. For example 10kb becomes 10240.
+ *
+ * @method parseSize
+ * @static
+ * @param {String|Number} size String to parse or number to just pass through.
+ * @return {Number} Size in bytes.
+ */
+ parseSize : o.parseSizeStr,
+
+
+ /**
+ * A way to predict what runtime will be choosen in the current environment with the
+ * specified settings.
+ *
+ * @method predictRuntime
+ * @static
+ * @param {Object|String} config Plupload settings to check
+ * @param {String} [runtimes] Comma-separated list of runtimes to check against
+ * @return {String} Type of compatible runtime
+ */
+ predictRuntime : function(config, runtimes) {
+ var up, runtime;
+ if (runtimes) {
+ config.runtimes = runtimes;
+ }
+ up = new plupload.Uploader(config);
+ runtime = up.runtime;
+ up.destroy();
+ return runtime;
+ },
+
+ /**
+ * Registers a filter that will be executed for each file added to the queue.
+ * If callback returns false, file will not be added.
+ *
+ * Callback receives two arguments: a value for the filter as it was specified in settings.filters
+ * and a file to be filtered. Callback is executed in the context of uploader instance.
+ *
+ * @method addFileFilter
+ * @static
+ * @param {String} name Name of the filter by which it can be referenced in settings.filters
+ * @param {String} cb Callback - the actual routine that every added file must pass
+ */
+ addFileFilter: function(name, cb) {
+ fileFilters[name] = cb;
+ }
+};
+
+
+plupload.addFileFilter('mime_types', function(filters, file, cb) {
+ if (filters.length && !filters.regexp.test(file.name)) {
+ this.trigger('Error', {
+ code : plupload.FILE_EXTENSION_ERROR,
+ message : plupload.translate('File extension error.'),
+ file : file
+ });
+ cb(false);
+ } else {
+ cb(true);
+ }
+});
+
+
+plupload.addFileFilter('max_file_size', function(maxSize, file, cb) {
+ var undef;
+
+ // Invalid file size
+ if (file.size !== undef && maxSize && file.size > maxSize) {
+ this.trigger('Error', {
+ code : plupload.FILE_SIZE_ERROR,
+ message : plupload.translate('File size error.'),
+ file : file
+ });
+ cb(false);
+ } else {
+ cb(true);
+ }
+});
+
+
+plupload.addFileFilter('prevent_duplicates', function(value, file, cb) {
+ if (value) {
+ var ii = this.files.length;
+ while (ii--) {
+ // Compare by name and size (size might be 0 or undefined, but still equivalent for both)
+ if (file.name === this.files[ii].name && file.size === this.files[ii].size) {
+ this.trigger('Error', {
+ code : plupload.FILE_DUPLICATE_ERROR,
+ message : plupload.translate('Duplicate file error.'),
+ file : file
+ });
+ cb(false);
+ return;
+ }
+ }
+ }
+ cb(true);
+});
+
+
+/**
+@class Uploader
+@constructor
+
+@param {Object} settings For detailed information about each option check documentation.
+ @param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger.
+ @param {String} settings.url URL of the server-side upload handler.
+ @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled.
+ @param {String} [settings.container] id of the DOM element to use as a container for uploader structures. Defaults to document.body.
+ @param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop.
+ @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
+ @param {Object} [settings.filters={}] Set of file type filters.
+ @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR`
+ @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`.
+ @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
+ @param {String} [settings.flash_swf_url] URL of the Flash swf.
+ @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
+ @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
+ @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
+ @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload.
+ @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
+ @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
+ @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
+ @param {Number} [settings.resize.width] If image is bigger, it will be resized.
+ @param {Number} [settings.resize.height] If image is bigger, it will be resized.
+ @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100).
+ @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally.
+ @param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails.
+ @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
+ @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
+*/
+plupload.Uploader = function(options) {
+ /**
+ * Fires when the current RunTime has been initialized.
+ *
+ * @event Init
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ */
+
+ /**
+ * Fires after the init event incase you need to perform actions there.
+ *
+ * @event PostInit
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ */
+
+ /**
+ * Fires when the option is changed in via uploader.setOption().
+ *
+ * @event OptionChanged
+ * @since 2.1
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {String} name Name of the option that was changed
+ * @param {Mixed} value New value for the specified option
+ * @param {Mixed} oldValue Previous value of the option
+ */
+
+ /**
+ * Fires when the silverlight/flash or other shim needs to move.
+ *
+ * @event Refresh
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ */
+
+ /**
+ * Fires when the overall state is being changed for the upload queue.
+ *
+ * @event StateChanged
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ */
+
+ /**
+ * Fires when a file is to be uploaded by the runtime.
+ *
+ * @event UploadFile
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {plupload.File} file File to be uploaded.
+ */
+
+ /**
+ * Fires when just before a file is uploaded. This event enables you to override settings
+ * on the uploader instance before the file is uploaded.
+ *
+ * @event BeforeUpload
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {plupload.File} file File to be uploaded.
+ */
+
+ /**
+ * Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.
+ *
+ * @event QueueChanged
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ */
+
+ /**
+ * Fires while a file is being uploaded. Use this event to update the current file upload progress.
+ *
+ * @event UploadProgress
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {plupload.File} file File that is currently being uploaded.
+ */
+
+ /**
+ * Fires when file is removed from the queue.
+ *
+ * @event FilesRemoved
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {Array} files Array of files that got removed.
+ */
+
+ /**
+ * Fires for every filtered file before it is added to the queue.
+ *
+ * @event FileFiltered
+ * @since 2.1
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {plupload.File} file Another file that has to be added to the queue.
+ */
+
+ /**
+ * Fires after files were filtered and added to the queue.
+ *
+ * @event FilesAdded
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {Array} files Array of file objects that were added to queue by the user.
+ */
+
+ /**
+ * Fires when a file is successfully uploaded.
+ *
+ * @event FileUploaded
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {plupload.File} file File that was uploaded.
+ * @param {Object} response Object with response properties.
+ */
+
+ /**
+ * Fires when file chunk is uploaded.
+ *
+ * @event ChunkUploaded
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {plupload.File} file File that the chunk was uploaded for.
+ * @param {Object} response Object with response properties.
+ */
+
+ /**
+ * Fires when all files in a queue are uploaded.
+ *
+ * @event UploadComplete
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {Array} files Array of file objects that was added to queue/selected by the user.
+ */
+
+ /**
+ * Fires when a error occurs.
+ *
+ * @event Error
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ * @param {Object} error Contains code, message and sometimes file and other details.
+ */
+
+ /**
+ * Fires when destroy method is called.
+ *
+ * @event Destroy
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
+ */
+ var uid = plupload.guid()
+ , settings
+ , files = []
+ , preferred_caps = {}
+ , fileInputs = []
+ , fileDrops = []
+ , startTime
+ , total
+ , disabled = false
+ , xhr
+ ;
+
+
+ // Private methods
+ function uploadNext() {
+ var file, count = 0, i;
+
+ if (this.state == plupload.STARTED) {
+ // Find first QUEUED file
+ for (i = 0; i < files.length; i++) {
+ if (!file && files[i].status == plupload.QUEUED) {
+ file = files[i];
+ if (this.trigger("BeforeUpload", file)) {
+ file.status = plupload.UPLOADING;
+ this.trigger("UploadFile", file);
+ }
+ } else {
+ count++;
+ }
+ }
+
+ // All files are DONE or FAILED
+ if (count == files.length) {
+ if (this.state !== plupload.STOPPED) {
+ this.state = plupload.STOPPED;
+ this.trigger("StateChanged");
+ }
+ this.trigger("UploadComplete", files);
+ }
+ }
+ }
+
+
+ function calcFile(file) {
+ file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;
+ calc();
+ }
+
+
+ function calc() {
+ var i, file;
+
+ // Reset stats
+ total.reset();
+
+ // Check status, size, loaded etc on all files
+ for (i = 0; i < files.length; i++) {
+ file = files[i];
+
+ if (file.size !== undef) {
+ // We calculate totals based on original file size
+ total.size += file.origSize;
+
+ // Since we cannot predict file size after resize, we do opposite and
+ // interpolate loaded amount to match magnitude of total
+ total.loaded += file.loaded * file.origSize / file.size;
+ } else {
+ total.size = undef;
+ }
+
+ if (file.status == plupload.DONE) {
+ total.uploaded++;
+ } else if (file.status == plupload.FAILED) {
+ total.failed++;
+ } else {
+ total.queued++;
+ }
+ }
+
+ // If we couldn't calculate a total file size then use the number of files to calc percent
+ if (total.size === undef) {
+ total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;
+ } else {
+ total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));
+ total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;
+ }
+ }
+
+
+ function getRUID() {
+ var ctrl = fileInputs[0] || fileDrops[0];
+ if (ctrl) {
+ return ctrl.getRuntime().uid;
+ }
+ return false;
+ }
+
+
+ function runtimeCan(file, cap) {
+ if (file.ruid) {
+ var info = o.Runtime.getInfo(file.ruid);
+ if (info) {
+ return info.can(cap);
+ }
+ }
+ return false;
+ }
+
+
+ function bindEventListeners() {
+ this.bind('FilesAdded', onFilesAdded);
+
+ this.bind('CancelUpload', onCancelUpload);
+
+ this.bind('BeforeUpload', onBeforeUpload);
+
+ this.bind('UploadFile', onUploadFile);
+
+ this.bind('UploadProgress', onUploadProgress);
+
+ this.bind('StateChanged', onStateChanged);
+
+ this.bind('QueueChanged', calc);
+
+ this.bind('Error', onError);
+
+ this.bind('FileUploaded', onFileUploaded);
+
+ this.bind('Destroy', onDestroy);
+ }
+
+
+ function initControls(settings, cb) {
+ var self = this, inited = 0, queue = [];
+
+ // common settings
+ var options = {
+ accept: settings.filters.mime_types,
+ runtime_order: settings.runtimes,
+ required_caps: settings.required_features,
+ preferred_caps: preferred_caps,
+ swf_url: settings.flash_swf_url,
+ xap_url: settings.silverlight_xap_url
+ };
+
+ // add runtime specific options if any
+ plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) {
+ if (settings[runtime]) {
+ options[runtime] = settings[runtime];
+ }
+ });
+
+ // initialize file pickers - there can be many
+ if (settings.browse_button) {
+ plupload.each(settings.browse_button, function(el) {
+ queue.push(function(cb) {
+ var fileInput = new o.FileInput(plupload.extend({}, options, {
+ name: settings.file_data_name,
+ multiple: settings.multi_selection,
+ container: settings.container,
+ browse_button: el
+ }));
+
+ fileInput.onready = function() {
+ var info = o.Runtime.getInfo(this.ruid);
+
+ // for backward compatibility
+ o.extend(self.features, {
+ chunks: info.can('slice_blob'),
+ multipart: info.can('send_multipart'),
+ multi_selection: info.can('select_multiple')
+ });
+
+ inited++;
+ fileInputs.push(this);
+ cb();
+ };
+
+ fileInput.onchange = function() {
+ self.addFile(this.files);
+ };
+
+ fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
+ if (!disabled) {
+ if (settings.browse_button_hover) {
+ if ('mouseenter' === e.type) {
+ o.addClass(el, settings.browse_button_hover);
+ } else if ('mouseleave' === e.type) {
+ o.removeClass(el, settings.browse_button_hover);
+ }
+ }
+
+ if (settings.browse_button_active) {
+ if ('mousedown' === e.type) {
+ o.addClass(el, settings.browse_button_active);
+ } else if ('mouseup' === e.type) {
+ o.removeClass(el, settings.browse_button_active);
+ }
+ }
+ }
+ });
+
+ fileInput.bind('error runtimeerror', function() {
+ fileInput = null;
+ cb();
+ });
+
+ fileInput.init();
+ });
+ });
+ }
+
+ // initialize drop zones
+ if (settings.drop_element) {
+ plupload.each(settings.drop_element, function(el) {
+ queue.push(function(cb) {
+ var fileDrop = new o.FileDrop(plupload.extend({}, options, {
+ drop_zone: el
+ }));
+
+ fileDrop.onready = function() {
+ var info = o.Runtime.getInfo(this.ruid);
+
+ self.features.dragdrop = info.can('drag_and_drop'); // for backward compatibility
+
+ inited++;
+ fileDrops.push(this);
+ cb();
+ };
+
+ fileDrop.ondrop = function() {
+ self.addFile(this.files);
+ };
+
+ fileDrop.bind('error runtimeerror', function() {
+ fileDrop = null;
+ cb();
+ });
+
+ fileDrop.init();
+ });
+ });
+ }
+
+
+ o.inSeries(queue, function() {
+ if (typeof(cb) === 'function') {
+ cb(inited);
+ }
+ });
+ }
+
+
+ function resizeImage(blob, params, cb) {
+ var img = new o.Image();
+
+ try {
+ img.onload = function() {
+ img.downsize(params.width, params.height, params.crop, params.preserve_headers);
+ };
+
+ img.onresize = function() {
+ cb(this.getAsBlob(blob.type, params.quality));
+ this.destroy();
+ };
+
+ img.onerror = function() {
+ cb(blob);
+ };
+
+ img.load(blob);
+ } catch(ex) {
+ cb(blob);
+ }
+ }
+
+
+ function setOption(option, value, init) {
+ var self = this, reinitRequired = false;
+
+ function _setOption(option, value, init) {
+ var oldValue = settings[option];
+
+ switch (option) {
+ case 'max_file_size':
+ case 'chunk_size':
+ if (value = plupload.parseSize(value)) {
+ settings[option] = value;
+ if (option === 'max_file_size') {
+ settings.max_file_size = settings.filters.max_file_size = value;
+ }
+ }
+ break;
+
+ case 'filters':
+ // for sake of backward compatibility
+ if (plupload.typeOf(value) === 'array') {
+ value = {
+ mime_types: value
+ };
+ }
+
+ if (init) {
+ plupload.extend(settings.filters, value);
+ } else {
+ settings.filters = value;
+ }
+
+ // if file format filters are being updated, regenerate the matching expressions
+ if (value.mime_types) {
+ settings.filters.mime_types.regexp = (function(filters) {
+ var extensionsRegExp = [];
+
+ plupload.each(filters, function(filter) {
+ plupload.each(filter.extensions.split(/,/), function(ext) {
+ if (/^\s*\*\s*$/.test(ext)) {
+ extensionsRegExp.push('\\.*');
+ } else {
+ extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
+ }
+ });
+ });
+
+ return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
+ }(settings.filters.mime_types));
+ }
+ break;
+
+ case 'resize':
+ if (init) {
+ plupload.extend(settings.resize, value, {
+ enabled: true
+ });
+ } else {
+ settings.resize = value;
+ }
+ break;
+
+ case 'prevent_duplicates':
+ settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value;
+ break;
+
+ case 'browse_button':
+ case 'drop_element':
+ value = plupload.get(value);
+
+ case 'container':
+ case 'runtimes':
+ case 'multi_selection':
+ case 'flash_swf_url':
+ case 'silverlight_xap_url':
+ settings[option] = value;
+ if (!init) {
+ reinitRequired = true;
+ }
+ break;
+
+ default:
+ settings[option] = value;
+ }
+
+ if (!init) {
+ self.trigger('OptionChanged', option, value, oldValue);
+ }
+ }
+
+ if (typeof(option) === 'object') {
+ plupload.each(option, function(value, option) {
+ _setOption(option, value, init);
+ });
+ } else {
+ _setOption(option, value, init);
+ }
+
+ if (init) {
+ // Normalize the list of required capabilities
+ settings.required_features = normalizeCaps(plupload.extend({}, settings));
+
+ // Come up with the list of capabilities that can affect default mode in a multi-mode runtimes
+ preferred_caps = normalizeCaps(plupload.extend({}, settings, {
+ required_features: true
+ }));
+ } else if (reinitRequired) {
+ self.trigger('Destroy');
+
+ initControls.call(self, settings, function(inited) {
+ if (inited) {
+ self.runtime = o.Runtime.getInfo(getRUID()).type;
+ self.trigger('Init', { runtime: self.runtime });
+ self.trigger('PostInit');
+ } else {
+ self.trigger('Error', {
+ code : plupload.INIT_ERROR,
+ message : plupload.translate('Init error.')
+ });
+ }
+ });
+ }
+ }
+
+
+ // Internal event handlers
+ function onFilesAdded(up, filteredFiles) {
+ // Add files to queue
+ [].push.apply(files, filteredFiles);
+
+ up.trigger('QueueChanged');
+ up.refresh();
+ }
+
+
+ function onBeforeUpload(up, file) {
+ // Generate unique target filenames
+ if (settings.unique_names) {
+ var matches = file.name.match(/\.([^.]+)$/), ext = "part";
+ if (matches) {
+ ext = matches[1];
+ }
+ file.target_name = file.id + '.' + ext;
+ }
+ }
+
+
+ function onUploadFile(up, file) {
+ var url = up.settings.url
+ , chunkSize = up.settings.chunk_size
+ , retries = up.settings.max_retries
+ , features = up.features
+ , offset = 0
+ , blob
+ ;
+
+ // make sure we start at a predictable offset
+ if (file.loaded) {
+ offset = file.loaded = chunkSize * Math.floor(file.loaded / chunkSize);
+ }
+
+ function handleError() {
+ if (retries-- > 0) {
+ delay(uploadNextChunk, 1000);
+ } else {
+ file.loaded = offset; // reset all progress
+
+ up.trigger('Error', {
+ code : plupload.HTTP_ERROR,
+ message : plupload.translate('HTTP Error.'),
+ file : file,
+ response : xhr.responseText,
+ status : xhr.status,
+ responseHeaders: xhr.getAllResponseHeaders()
+ });
+ }
+ }
+
+ function uploadNextChunk() {
+ var chunkBlob, formData, args, curChunkSize;
+
+ // File upload finished
+ if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {
+ return;
+ }
+
+ // Standard arguments
+ args = {name : file.target_name || file.name};
+
+ if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory
+ curChunkSize = Math.min(chunkSize, blob.size - offset);
+ chunkBlob = blob.slice(offset, offset + curChunkSize);
+ } else {
+ curChunkSize = blob.size;
+ chunkBlob = blob;
+ }
+
+ // If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller
+ if (chunkSize && features.chunks) {
+ // Setup query string arguments
+ if (up.settings.send_chunk_number) {
+ args.chunk = Math.ceil(offset / chunkSize);
+ args.chunks = Math.ceil(blob.size / chunkSize);
+ } else { // keep support for experimental chunk format, just in case
+ args.offset = offset;
+ args.total = blob.size;
+ }
+ }
+
+ xhr = new o.XMLHttpRequest();
+
+ // Do we have upload progress support
+ if (xhr.upload) {
+ xhr.upload.onprogress = function(e) {
+ file.loaded = Math.min(file.size, offset + e.loaded);
+ up.trigger('UploadProgress', file);
+ };
+ }
+
+ xhr.onload = function() {
+ // check if upload made itself through
+ if (xhr.status >= 400) {
+ handleError();
+ return;
+ }
+
+ retries = up.settings.max_retries; // reset the counter
+
+ // Handle chunk response
+ if (curChunkSize < blob.size) {
+ chunkBlob.destroy();
+
+ offset += curChunkSize;
+ file.loaded = Math.min(offset, blob.size);
+
+ up.trigger('ChunkUploaded', file, {
+ offset : file.loaded,
+ total : blob.size,
+ response : xhr.responseText,
+ status : xhr.status,
+ responseHeaders: xhr.getAllResponseHeaders()
+ });
+
+ // stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them
+ if (o.Env.browser === 'Android Browser') {
+ // doesn't harm in general, but is not required anywhere else
+ up.trigger('UploadProgress', file);
+ }
+ } else {
+ file.loaded = file.size;
+ }
+
+ chunkBlob = formData = null; // Free memory
+
+ // Check if file is uploaded
+ if (!offset || offset >= blob.size) {
+ // If file was modified, destory the copy
+ if (file.size != file.origSize) {
+ blob.destroy();
+ blob = null;
+ }
+
+ up.trigger('UploadProgress', file);
+
+ file.status = plupload.DONE;
+
+ up.trigger('FileUploaded', file, {
+ response : xhr.responseText,
+ status : xhr.status,
+ responseHeaders: xhr.getAllResponseHeaders()
+ });
+ } else {
+ // Still chunks left
+ delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere
+ }
+ };
+
+ xhr.onerror = function() {
+ handleError();
+ };
+
+ xhr.onloadend = function() {
+ this.destroy();
+ xhr = null;
+ };
+
+ // Build multipart request
+ if (up.settings.multipart && features.multipart) {
+
+ args.name = file.target_name || file.name;
+
+ xhr.open("post", url, true);
+
+ // Set custom headers
+ plupload.each(up.settings.headers, function(value, name) {
+ xhr.setRequestHeader(name, value);
+ });
+
+ formData = new o.FormData();
+
+ // Add multipart params
+ plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
+ formData.append(name, value);
+ });
+
+ // Add file and send it
+ formData.append(up.settings.file_data_name, chunkBlob);
+ xhr.send(formData, {
+ runtime_order: up.settings.runtimes,
+ required_caps: up.settings.required_features,
+ preferred_caps: preferred_caps,
+ swf_url: up.settings.flash_swf_url,
+ xap_url: up.settings.silverlight_xap_url
+ });
+ } else {
+ // if no multipart, send as binary stream
+ url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params));
+
+ xhr.open("post", url, true);
+
+ xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header
+
+ // Set custom headers
+ plupload.each(up.settings.headers, function(value, name) {
+ xhr.setRequestHeader(name, value);
+ });
+
+ xhr.send(chunkBlob, {
+ runtime_order: up.settings.runtimes,
+ required_caps: up.settings.required_features,
+ preferred_caps: preferred_caps,
+ swf_url: up.settings.flash_swf_url,
+ xap_url: up.settings.silverlight_xap_url
+ });
+ }
+ }
+
+ blob = file.getSource();
+
+ // Start uploading chunks
+ if (up.settings.resize.enabled && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) {
+ // Resize if required
+ resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) {
+ blob = resizedBlob;
+ file.size = resizedBlob.size;
+ uploadNextChunk();
+ });
+ } else {
+ uploadNextChunk();
+ }
+ }
+
+
+ function onUploadProgress(up, file) {
+ calcFile(file);
+ }
+
+
+ function onStateChanged(up) {
+ if (up.state == plupload.STARTED) {
+ // Get start time to calculate bps
+ startTime = (+new Date());
+ } else if (up.state == plupload.STOPPED) {
+ // Reset currently uploading files
+ for (var i = up.files.length - 1; i >= 0; i--) {
+ if (up.files[i].status == plupload.UPLOADING) {
+ up.files[i].status = plupload.QUEUED;
+ calc();
+ }
+ }
+ }
+ }
+
+
+ function onCancelUpload() {
+ if (xhr) {
+ xhr.abort();
+ }
+ }
+
+
+ function onFileUploaded(up) {
+ calc();
+
+ // Upload next file but detach it from the error event
+ // since other custom listeners might want to stop the queue
+ delay(function() {
+ uploadNext.call(up);
+ }, 1);
+ }
+
+
+ function onError(up, err) {
+ // Set failed status if an error occured on a file
+ if (err.file) {
+ err.file.status = plupload.FAILED;
+ calcFile(err.file);
+
+ // Upload next file but detach it from the error event
+ // since other custom listeners might want to stop the queue
+ if (up.state == plupload.STARTED) { // upload in progress
+ up.trigger('CancelUpload');
+ delay(function() {
+ uploadNext.call(up);
+ }, 1);
+ }
+ }
+ }
+
+
+ function onDestroy(up) {
+ up.stop();
+
+ // Purge the queue
+ plupload.each(files, function(file) {
+ file.destroy();
+ });
+ files = [];
+
+ if (fileInputs.length) {
+ plupload.each(fileInputs, function(fileInput) {
+ fileInput.destroy();
+ });
+ fileInputs = [];
+ }
+
+ if (fileDrops.length) {
+ plupload.each(fileDrops, function(fileDrop) {
+ fileDrop.destroy();
+ });
+ fileDrops = [];
+ }
+
+ preferred_caps = {};
+ disabled = false;
+ settings = startTime = xhr = null;
+ total.reset();
+ }
+
+
+ // Default settings
+ settings = {
+ runtimes: o.Runtime.order,
+ max_retries: 0,
+ chunk_size: 0,
+ multipart: true,
+ multi_selection: true,
+ file_data_name: 'file',
+ flash_swf_url: 'js/Moxie.swf',
+ silverlight_xap_url: 'js/Moxie.xap',
+ filters: {
+ mime_types: [],
+ prevent_duplicates: false,
+ max_file_size: 0
+ },
+ resize: {
+ enabled: false,
+ preserve_headers: true,
+ crop: false
+ },
+ send_chunk_number: true // whether to send chunks and chunk numbers, or total and offset bytes
+ };
+
+
+ setOption.call(this, options, null, true);
+
+ // Inital total state
+ total = new plupload.QueueProgress();
+
+ // Add public methods
+ plupload.extend(this, {
+
+ /**
+ * Unique id for the Uploader instance.
+ *
+ * @property id
+ * @type String
+ */
+ id : uid,
+ uid : uid, // mOxie uses this to differentiate between event targets
+
+ /**
+ * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
+ * These states are controlled by the stop/start methods. The default value is STOPPED.
+ *
+ * @property state
+ * @type Number
+ */
+ state : plupload.STOPPED,
+
+ /**
+ * Map of features that are available for the uploader runtime. Features will be filled
+ * before the init event is called, these features can then be used to alter the UI for the end user.
+ * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
+ *
+ * @property features
+ * @type Object
+ */
+ features : {},
+
+ /**
+ * Current runtime name.
+ *
+ * @property runtime
+ * @type String
+ */
+ runtime : null,
+
+ /**
+ * Current upload queue, an array of File instances.
+ *
+ * @property files
+ * @type Array
+ * @see plupload.File
+ */
+ files : files,
+
+ /**
+ * Object with name/value settings.
+ *
+ * @property settings
+ * @type Object
+ */
+ settings : settings,
+
+ /**
+ * Total progess information. How many files has been uploaded, total percent etc.
+ *
+ * @property total
+ * @type plupload.QueueProgress
+ */
+ total : total,
+
+
+ /**
+ * Initializes the Uploader instance and adds internal event listeners.
+ *
+ * @method init
+ */
+ init : function() {
+ var self = this;
+
+ if (typeof(settings.preinit) == "function") {
+ settings.preinit(self);
+ } else {
+ plupload.each(settings.preinit, function(func, name) {
+ self.bind(name, func);
+ });
+ }
+
+ // Check for required options
+ if (!settings.browse_button || !settings.url) {
+ this.trigger('Error', {
+ code : plupload.INIT_ERROR,
+ message : plupload.translate('Init error.')
+ });
+ return;
+ }
+
+ bindEventListeners.call(this);
+
+ initControls.call(this, settings, function(inited) {
+ if (typeof(settings.init) == "function") {
+ settings.init(self);
+ } else {
+ plupload.each(settings.init, function(func, name) {
+ self.bind(name, func);
+ });
+ }
+
+ if (inited) {
+ self.runtime = o.Runtime.getInfo(getRUID()).type;
+ self.trigger('Init', { runtime: self.runtime });
+ self.trigger('PostInit');
+ } else {
+ self.trigger('Error', {
+ code : plupload.INIT_ERROR,
+ message : plupload.translate('Init error.')
+ });
+ }
+ });
+ },
+
+ /**
+ * Set the value for the specified option(s).
+ *
+ * @method setOption
+ * @since 2.1
+ * @param {String|Object} option Name of the option to change or the set of key/value pairs
+ * @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
+ */
+ setOption: function(option, value) {
+ setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize
+ },
+
+ /**
+ * Get the value for the specified option or the whole configuration, if not specified.
+ *
+ * @method getOption
+ * @since 2.1
+ * @param {String} [option] Name of the option to get
+ * @return {Mixed} Value for the option or the whole set
+ */
+ getOption: function(option) {
+ if (!option) {
+ return settings;
+ }
+ return settings[option];
+ },
+
+ /**
+ * Refreshes the upload instance by dispatching out a refresh event to all runtimes.
+ * This would for example reposition flash/silverlight shims on the page.
+ *
+ * @method refresh
+ */
+ refresh : function() {
+ if (fileInputs.length) {
+ plupload.each(fileInputs, function(fileInput) {
+ fileInput.trigger('Refresh');
+ });
+ }
+ this.trigger('Refresh');
+ },
+
+ /**
+ * Starts uploading the queued files.
+ *
+ * @method start
+ */
+ start : function() {
+ if (this.state != plupload.STARTED) {
+ this.state = plupload.STARTED;
+ this.trigger('StateChanged');
+
+ uploadNext.call(this);
+ }
+ },
+
+ /**
+ * Stops the upload of the queued files.
+ *
+ * @method stop
+ */
+ stop : function() {
+ if (this.state != plupload.STOPPED) {
+ this.state = plupload.STOPPED;
+ this.trigger('StateChanged');
+ this.trigger('CancelUpload');
+ }
+ },
+
+
+ /**
+ * Disables/enables browse button on request.
+ *
+ * @method disableBrowse
+ * @param {Boolean} disable Whether to disable or enable (default: true)
+ */
+ disableBrowse : function() {
+ disabled = arguments[0] !== undef ? arguments[0] : true;
+
+ if (fileInputs.length) {
+ plupload.each(fileInputs, function(fileInput) {
+ fileInput.disable(disabled);
+ });
+ }
+
+ this.trigger('DisableBrowse', disabled);
+ },
+
+ /**
+ * Returns the specified file object by id.
+ *
+ * @method getFile
+ * @param {String} id File id to look for.
+ * @return {plupload.File} File object or undefined if it wasn't found;
+ */
+ getFile : function(id) {
+ var i;
+ for (i = files.length - 1; i >= 0; i--) {
+ if (files[i].id === id) {
+ return files[i];
+ }
+ }
+ },
+
+ /**
+ * Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
+ * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded,
+ * if any files were added to the queue. Otherwise nothing happens.
+ *
+ * @method addFile
+ * @since 2.0
+ * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
+ * @param {String} [fileName] If specified, will be used as a name for the file
+ */
+ addFile : function(file, fileName) {
+ var self = this
+ , queue = []
+ , files = []
+ , ruid
+ ;
+
+ function filterFile(file, cb) {
+ var queue = [];
+ o.each(self.settings.filters, function(rule, name) {
+ if (fileFilters[name]) {
+ queue.push(function(cb) {
+ fileFilters[name].call(self, rule, file, function(res) {
+ cb(!res);
+ });
+ });
+ }
+ });
+ o.inSeries(queue, cb);
+ }
+
+ /**
+ * @method resolveFile
+ * @private
+ * @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file
+ */
+ function resolveFile(file) {
+ var type = o.typeOf(file);
+
+ // o.File
+ if (file instanceof o.File) {
+ if (!file.ruid && !file.isDetached()) {
+ if (!ruid) { // weird case
+ return false;
+ }
+ file.ruid = ruid;
+ file.connectRuntime(ruid);
+ }
+ resolveFile(new plupload.File(file));
+ }
+ // o.Blob
+ else if (file instanceof o.Blob) {
+ resolveFile(file.getSource());
+ file.destroy();
+ }
+ // plupload.File - final step for other branches
+ else if (file instanceof plupload.File) {
+ if (fileName) {
+ file.name = fileName;
+ }
+
+ queue.push(function(cb) {
+ // run through the internal and user-defined filters, if any
+ filterFile(file, function(err) {
+ if (!err) {
+ files.push(file);
+ self.trigger("FileFiltered", file);
+ }
+ delay(cb, 1); // do not build up recursions or eventually we might hit the limits
+ });
+ });
+ }
+ // native File or blob
+ else if (o.inArray(type, ['file', 'blob']) !== -1) {
+ resolveFile(new o.File(null, file));
+ }
+ // input[type="file"]
+ else if (type === 'node' && o.typeOf(file.files) === 'filelist') {
+ // if we are dealing with input[type="file"]
+ o.each(file.files, resolveFile);
+ }
+ // mixed array of any supported types (see above)
+ else if (type === 'array') {
+ fileName = null; // should never happen, but unset anyway to avoid funny situations
+ o.each(file, resolveFile);
+ }
+ }
+
+ ruid = getRUID();
+
+ resolveFile(file);
+
+ if (queue.length) {
+ o.inSeries(queue, function() {
+ // if any files left after filtration, trigger FilesAdded
+ if (files.length) {
+ self.trigger("FilesAdded", files);
+ }
+ });
+ }
+ },
+
+ /**
+ * Removes a specific file.
+ *
+ * @method removeFile
+ * @param {plupload.File|String} file File to remove from queue.
+ */
+ removeFile : function(file) {
+ var id = typeof(file) === 'string' ? file : file.id;
+
+ for (var i = files.length - 1; i >= 0; i--) {
+ if (files[i].id === id) {
+ return this.splice(i, 1)[0];
+ }
+ }
+ },
+
+ /**
+ * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.
+ *
+ * @method splice
+ * @param {Number} start (Optional) Start index to remove from.
+ * @param {Number} length (Optional) Lengh of items to remove.
+ * @return {Array} Array of files that was removed.
+ */
+ splice : function(start, length) {
+ // Splice and trigger events
+ var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length);
+
+ this.trigger("FilesRemoved", removed);
+
+ // Dispose any resources allocated by those files
+ plupload.each(removed, function(file) {
+ file.destroy();
+ });
+
+ this.trigger("QueueChanged");
+ this.refresh();
+
+ return removed;
+ },
+
+ /**
+ * Dispatches the specified event name and it's arguments to all listeners.
+ *
+ *
+ * @method trigger
+ * @param {String} name Event name to fire.
+ * @param {Object..} Multiple arguments to pass along to the listener functions.
+ */
+
+ /**
+ * Check whether uploader has any listeners to the specified event.
+ *
+ * @method hasEventListener
+ * @param {String} name Event name to check for.
+ */
+
+
+ /**
+ * Adds an event listener by name.
+ *
+ * @method bind
+ * @param {String} name Event name to listen for.
+ * @param {function} func Function to call ones the event gets fired.
+ * @param {Object} scope Optional scope to execute the specified function in.
+ */
+ bind : function(name, func, scope) {
+ var self = this;
+ // adapt moxie EventTarget style to Plupload-like
+ plupload.Uploader.prototype.bind.call(this, name, function() {
+ var args = [].slice.call(arguments);
+ args.splice(0, 1, self); // replace event object with uploader instance
+ return func.apply(this, args);
+ }, 0, scope);
+ },
+
+ /**
+ * Removes the specified event listener.
+ *
+ * @method unbind
+ * @param {String} name Name of event to remove.
+ * @param {function} func Function to remove from listener.
+ */
+
+ /**
+ * Removes all event listeners.
+ *
+ * @method unbindAll
+ */
+
+
+ /**
+ * Destroys Plupload instance and cleans after itself.
+ *
+ * @method destroy
+ */
+ destroy : function() {
+ this.trigger('Destroy');
+ total = null; // purge this one exclusively
+ this.unbindAll();
+ }
+ });
+};
+
+plupload.Uploader.prototype = o.EventTarget.instance;
+
+/**
+ * Constructs a new file instance.
+ *
+ * @class File
+ * @constructor
+ *
+ * @param {Object} file Object containing file properties
+ * @param {String} file.name Name of the file.
+ * @param {Number} file.size File size.
+ */
+plupload.File = (function() {
+ var filepool = {};
+
+ function PluploadFile(file) {
+
+ plupload.extend(this, {
+
+ /**
+ * File id this is a globally unique id for the specific file.
+ *
+ * @property id
+ * @type String
+ */
+ id: plupload.guid(),
+
+ /**
+ * File name for example "myfile.gif".
+ *
+ * @property name
+ * @type String
+ */
+ name: file.name || file.fileName,
+
+ /**
+ * File type, `e.g image/jpeg`
+ *
+ * @property type
+ * @type String
+ */
+ type: file.type || '',
+
+ /**
+ * File size in bytes (may change after client-side manupilation).
+ *
+ * @property size
+ * @type Number
+ */
+ size: file.size || file.fileSize,
+
+ /**
+ * Original file size in bytes.
+ *
+ * @property origSize
+ * @type Number
+ */
+ origSize: file.size || file.fileSize,
+
+ /**
+ * Number of bytes uploaded of the files total size.
+ *
+ * @property loaded
+ * @type Number
+ */
+ loaded: 0,
+
+ /**
+ * Number of percentage uploaded of the file.
+ *
+ * @property percent
+ * @type Number
+ */
+ percent: 0,
+
+ /**
+ * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
+ *
+ * @property status
+ * @type Number
+ * @see plupload
+ */
+ status: plupload.QUEUED,
+
+ /**
+ * Date of last modification.
+ *
+ * @property lastModifiedDate
+ * @type {String}
+ */
+ lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
+
+ /**
+ * Returns native window.File object, when it's available.
+ *
+ * @method getNative
+ * @return {window.File} or null, if plupload.File is of different origin
+ */
+ getNative: function() {
+ var file = this.getSource().getSource();
+ return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null;
+ },
+
+ /**
+ * Returns mOxie.File - unified wrapper object that can be used across runtimes.
+ *
+ * @method getSource
+ * @return {mOxie.File} or null
+ */
+ getSource: function() {
+ if (!filepool[this.id]) {
+ return null;
+ }
+ return filepool[this.id];
+ },
+
+ /**
+ * Destroys plupload.File object.
+ *
+ * @method destroy
+ */
+ destroy: function() {
+ var src = this.getSource();
+ if (src) {
+ src.destroy();
+ delete filepool[this.id];
+ }
+ }
+ });
+
+ filepool[this.id] = file;
+ }
+
+ return PluploadFile;
+}());
+
+
+/**
+ * Constructs a queue progress.
+ *
+ * @class QueueProgress
+ * @constructor
+ */
+ plupload.QueueProgress = function() {
+ var self = this; // Setup alias for self to reduce code size when it's compressed
+
+ /**
+ * Total queue file size.
+ *
+ * @property size
+ * @type Number
+ */
+ self.size = 0;
+
+ /**
+ * Total bytes uploaded.
+ *
+ * @property loaded
+ * @type Number
+ */
+ self.loaded = 0;
+
+ /**
+ * Number of files uploaded.
+ *
+ * @property uploaded
+ * @type Number
+ */
+ self.uploaded = 0;
+
+ /**
+ * Number of files failed to upload.
+ *
+ * @property failed
+ * @type Number
+ */
+ self.failed = 0;
+
+ /**
+ * Number of files yet to be uploaded.
+ *
+ * @property queued
+ * @type Number
+ */
+ self.queued = 0;
+
+ /**
+ * Total percent of the uploaded bytes.
+ *
+ * @property percent
+ * @type Number
+ */
+ self.percent = 0;
+
+ /**
+ * Bytes uploaded per second.
+ *
+ * @property bytesPerSec
+ * @type Number
+ */
+ self.bytesPerSec = 0;
+
+ /**
+ * Resets the progress to it's initial values.
+ *
+ * @method reset
+ */
+ self.reset = function() {
+ self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;
+ };
+};
+
+window.plupload = plupload;
+
+}(window, mOxie));
Property changes on: branches/5.3.x/core/admin_templates/js/uploader/plupload.dev.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/admin_templates/js/uploader/upload_manager.js
===================================================================
--- branches/5.3.x/core/admin_templates/js/uploader/upload_manager.js (revision 16001)
+++ branches/5.3.x/core/admin_templates/js/uploader/upload_manager.js (revision 16002)
@@ -1,386 +1,108 @@
function UploadsManager() {
var $me = this;
$(document).bind('FormManager.Form.Ready', function ($e, $prefix) {
var $form_id = FormManager.form_param($prefix, 'form_id');
$me.Init($form_id);
});
}
UploadsManager = new UploadsManager();
/* ==== Private Attributes ==== */
-UploadsManager._nextId = 0;
+UploadsManager._uploadersCreated = 0;
UploadsManager._uploadersReady = 0;
UploadsManager._debugMode = false;
UploadsManager._Uploaders = {};
/* ==== Private methods ==== */
-UploadsManager._nextFlashId = function() {
- this._nextId++;
- return 'uploaderflash' + this._nextId;
-};
-
UploadsManager.iterate = function($method, $timeout) {
var $me = this;
var args = Array.prototype.slice.call(arguments); // convert to array
if ($timeout !== undefined) {
// 2nd parameter is given
if ((Object.prototype.toString.call($timeout) === '[object String]') && $timeout.match(/^timeout:([\d]+)$/)) {
// it's string in format "timeout:<number>"
$timeout = parseInt(RegExp.$1);
}
else {
// this is not the timeout, but 1st parameter of iteratable method
$timeout = undefined;
}
}
if ($timeout !== undefined) {
// make delayed iteration (helps with direct innerHTML assignments in IE)
args.splice(args.length - 1, 1); // remove timeout
setTimeout(function() { $me.iterate.apply($me, args); }, $timeout);
return ;
}
args.splice(0, 1); // remove method name
- for (var i in this._Uploaders) {
- this._Uploaders[i][$method].apply(this._Uploaders[i], args);
- }
+ $.each(this._Uploaders, function (index, uploader) {
+ uploader[$method].apply(uploader, args);
+ });
};
UploadsManager._hasQueue = function() {
var has_queue = false;
- for (var i in this._Uploaders) {
- var tmp = this._Uploaders[i].hasQueue();
- has_queue = has_queue || tmp;
- }
+ $.each(this._Uploaders, function (index, uploader) {
+ var tmp = uploader.hasQueue();
- return has_queue;
-};
-
-UploadsManager._getUploader = function (file) {
- var $flash_id = file.id.match(/(.*)_[\d]+/) ? RegExp.$1 : file.id;
+ has_queue = has_queue || tmp;
+ });
- for (var $uploader_index in this._Uploaders) {
- if (this._Uploaders[$uploader_index].flash_id == $flash_id) {
- return this._Uploaders[$uploader_index];
- }
- }
- return null;
+ return has_queue;
};
/* ==== Public methods ==== */
UploadsManager.Init = function ($form_id) {
var $me = this,
$submit_handler = function ($e) {
if ($me._hasQueue()) {
submitted = false;
$e.stopImmediatePropagation();
alert('File upload is in progress. Please cancel the upload or wait until it\'s completed.');
return false;
}
return true;
};
if ( $form_id === undefined ) {
$form_id = $form_name;
}
$('#' + $form_id).unbind('submit', $submit_handler).submit($submit_handler);
};
UploadsManager.AddUploader = function(id, params) {
this.Init();
this._Uploaders[id] = new Uploader(id, params);
+ this._uploadersCreated++;
};
UploadsManager.RemoveUploader = function(id) {
this._Uploaders[id].remove();
delete this._Uploaders[id];
+ this._uploadersCreated--;
};
-UploadsManager.DeleteFile = function(uploader_id, fname, confirmed) {
- if (!confirmed && !confirm('Are you sure you want to delete "' + fname + '" file?')) {
- return false;
- }
-
- var $uploader = this._Uploaders[uploader_id];
-
- $.get(
- $uploader.deleteURL.replace('#FILE#', encodeURIComponent(fname)).replace('#FIELD_ID#', $uploader.id),
- function ($data) {
- $uploader.removeFile({id:fname});
- $uploader.deleted.push(fname);
- $uploader.updateInfo(undefined, true);
- }
- );
-
- return true;
-};
-
-UploadsManager.StartUpload = function(id) {
- this._Uploaders[id].startUpload();
-};
-
-UploadsManager.CancelFile = function(id, file_id) {
- this._Uploaders[id].callFlash('CancelUpload', [file_id]);
-};
-
-UploadsManager.UploadQueueComplete = function($uploader) {
-
-};
-
-UploadsManager.CancelUpload = function(id) {
- this._Uploaders[id].cancelUpload();
-};
-
-UploadsManager.setDebugMode = function ($enabled) {
- /*for (var $uploader_index in this._Uploaders) {
- this._Uploaders[$uploader_index].clallFlash('SetDebugEnabled', [$enabled]);
- }*/
-
- this._debugMode = $enabled;
-};
-
-
/* ==== Flash event handlers ==== */
-UploadsManager.onHandleEverything = function () {
- if (UploadsManager._debugMode) {
- console.log('default swf handler');
- }
-};
-
-UploadsManager.onUploadStart = function(file) {
- var $uploader = UploadsManager._getUploader(file);
-
- $uploader.queueEvent(
- function() {
- this.UploadFileStart(file);
- }
- );
-};
-
-UploadsManager.onUploadProgress = function(file, bytesLoaded, bytesTotal) {
- var $uploader = UploadsManager._getUploader(file);
-
- $uploader.queueEvent(
- function() {
- this.UploadProgress(file, bytesLoaded, bytesTotal);
- }
- );
-};
-
-UploadsManager.onUploadComplete = function(file) {
- var $uploader = UploadsManager._getUploader(file);
-
- $uploader.queueEvent(
- function() {
- this.UploadFileComplete(file);
- }
- );
-};
-
-UploadsManager.onFileQueued = function(file) {
- var $uploader = UploadsManager._getUploader(file);
-// file = this.unescapeFilePostParams(file);
-
- $uploader.queueEvent(
- function() {
- if (this.files_count >= this.params.multiple) {
- // new file can exceed allowed file number
- if (this.params.multiple > 1) {
- // it definetly exceed it
- UploadsManager.onFileQueueError(file, -100, this.params.multiple);
- this.callFlash('CancelUpload', [file.id]);
- }
- else {
- // delete file added
- this.files_count++;
- this.files.push(file);
-
- if (this.files[0].uploaded) {
- UploadsManager.DeleteFile(UploadsManager._getUploader(file).id, this.files[0].name, true);
- }
- else {
- this.callFlash('CancelUpload', [this.files[0].id]);
- }
-
- this.startUpload();
- }
- }
- else {
- // new file will not exceed allowed file number
- this.files_count++;
- this.files.push(file);
-
- this.startUpload();
- }
-
- this.updateInfo(this.files.length - 1);
- }
- )
-};
-
-UploadsManager.onUploadSuccess = function(file, serverData, receivedResponse) {
- var $uploader = UploadsManager._getUploader(file);
-
- $uploader.queueEvent(
- function() {
- this.UploadSuccess(file, serverData, receivedResponse);
- }
- );
-};
-
-UploadsManager.onUploadError = function(file, errorCode, message) {
- var $uploader = UploadsManager._getUploader(file);
-
- $uploader.queueEvent(
- function() {
- this.removeFile(file);
-
- switch (errorCode) {
- case -200:
- // HTTP Error
- message = parseInt(message); // HTTP Error Code
- switch (message) {
- case 403:
- message = "You don't have permission to upload";
- break;
-
- case 413:
- message = 'File size exceeds allowed limit';
- break;
-
- case 500:
- message = 'Write permissions not set on the server, please contact server administrator';
- break;
- }
-
- if (isNaN(message)) {
- // message is processed
- alert('Error: ' + message + "\n" + 'Occured on file ' + file.name);
- return ;
- }
- break;
-
- case -280:
- // File Cancelled
- return ;
- break;
-
- case -290:
- // Upload Stopped
- UploadsManager.UploadQueueComplete(this);
- return ;
- break;
- }
-
- // all not processed error messages go here
- alert('Error [' + errorCode + ']: ' + message + "\n" + 'Occured on file ' + file.name);
- }
- );
-};
-
-UploadsManager.onFileQueueError = function(file, errorCode, message) {
- switch (errorCode) {
- case -100:
- // maximal allowed file count reached
- alert('Error: Files count exceeds allowed limit' + "\n" + 'Occured on file ' + file.name);
- return ;
- break;
-
- case -110:
- // maximal allowed filesize reached
- alert('Error: File size exceeds allowed limit' + "\n" + 'Occured on file ' + file.name);
- return ;
- break;
-
- case -130:
- // maximal allowed filesize reached
- alert('Error: File is not an allowed file type.' + "\n" + 'Occured on file ' + file.name);
- return ;
- break;
- }
-
- // all not processed error messages go here
- alert('Error [' + errorCode + ']: ' + message + "\n" + 'Occured on file ' + file.name);
-};
-
-UploadsManager.onFlashReady = function ($uploader_id) {
- this._Uploaders[$uploader_id].onFlashReady();
+UploadsManager.onReady = function () {
this._uploadersReady++;
- if (this._uploadersReady == this._nextId) {
+ if (this._uploadersReady == this._uploadersCreated) {
// all uploaders are ready
Application.processHooks('m:OnUploadersReady');
}
-};
-
-UploadsManager.onDebug = function (message) {
- if (!UploadsManager._debugMode) {
- return ;
- }
-
- var exceptionMessage, exceptionValues = [];
-
- // Check for an exception object and print it nicely
- if (typeof(message) === 'object' && typeof(message.name) === 'string' && typeof(message.message) === 'string') {
- for (var key in message) {
- if (message.hasOwnProperty(key)) {
- exceptionValues.push(key + ': ' + message[key]);
- }
- }
- exceptionMessage = exceptionValues.join("\n") || '';
- exceptionValues = exceptionMessage.split("\n");
- exceptionMessage = 'EXCEPTION: ' + exceptionValues.join("\nEXCEPTION: ");
-
- console.log(exceptionMessage);
- } else {
- console.log(message);
- }
-};
-
-if ( 'object' !== typeof console ) {
- // emulate FireBug Console in other browsers to see flash debug messages
- window.console = {};
- window.console.log = function (message) {
- var console, documentForm;
-
- try {
- console = document.getElementById('SWFUpload_Console');
-
- if (!console) {
- documentForm = document.createElement('form');
- document.getElementsByTagName('body')[0].appendChild(documentForm);
-
- console = document.createElement('textarea');
- console.id = 'SWFUpload_Console';
- console.style.fontFamily = 'monospace';
- console.setAttribute('wrap', 'off');
- console.wrap = 'off';
- console.style.overflow = 'auto';
- console.style.width = '700px';
- console.style.height = '350px';
- console.style.margin = '5px';
- documentForm.appendChild(console);
- }
-
- console.value += message + "\n";
-
- console.scrollTop = console.scrollHeight - console.clientHeight;
- } catch (ex) {
- alert('Exception: ' + ex.name + ' Message: ' + ex.message);
- }
- };
-}
\ No newline at end of file
+};
\ No newline at end of file
Index: branches/5.3.x/core/admin_templates/js/uploader/moxie.js
===================================================================
--- branches/5.3.x/core/admin_templates/js/uploader/moxie.js (nonexistent)
+++ branches/5.3.x/core/admin_templates/js/uploader/moxie.js (revision 16002)
@@ -0,0 +1,10211 @@
+/**
+ * mOxie - multi-runtime File API & XMLHttpRequest L2 Polyfill
+ * v1.0.0
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ *
+ * Date: 2013-09-23
+ */
+/**
+ * Compiled inline version. (Library mode)
+ */
+
+/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
+/*globals $code */
+
+(function(exports, undefined) {
+ "use strict";
+
+ var modules = {};
+
+ function require(ids, callback) {
+ var module, defs = [];
+
+ for (var i = 0; i < ids.length; ++i) {
+ module = modules[ids[i]] || resolve(ids[i]);
+ if (!module) {
+ throw 'module definition dependecy not found: ' + ids[i];
+ }
+
+ defs.push(module);
+ }
+
+ callback.apply(null, defs);
+ }
+
+ function define(id, dependencies, definition) {
+ if (typeof id !== 'string') {
+ throw 'invalid module definition, module id must be defined and be a string';
+ }
+
+ if (dependencies === undefined) {
+ throw 'invalid module definition, dependencies must be specified';
+ }
+
+ if (definition === undefined) {
+ throw 'invalid module definition, definition function must be specified';
+ }
+
+ require(dependencies, function() {
+ modules[id] = definition.apply(null, arguments);
+ });
+ }
+
+ function defined(id) {
+ return !!modules[id];
+ }
+
+ function resolve(id) {
+ var target = exports;
+ var fragments = id.split(/[.\/]/);
+
+ for (var fi = 0; fi < fragments.length; ++fi) {
+ if (!target[fragments[fi]]) {
+ return;
+ }
+
+ target = target[fragments[fi]];
+ }
+
+ return target;
+ }
+
+ function expose(ids) {
+ for (var i = 0; i < ids.length; i++) {
+ var target = exports;
+ var id = ids[i];
+ var fragments = id.split(/[.\/]/);
+
+ for (var fi = 0; fi < fragments.length - 1; ++fi) {
+ if (target[fragments[fi]] === undefined) {
+ target[fragments[fi]] = {};
+ }
+
+ target = target[fragments[fi]];
+ }
+
+ target[fragments[fragments.length - 1]] = modules[id];
+ }
+ }
+
+// Included from: src/javascript/core/utils/Basic.js
+
+/**
+ * Basic.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/utils/Basic', [], function() {
+ /**
+ Gets the true type of the built-in object (better version of typeof).
+ @author Angus Croll (http://javascriptweblog.wordpress.com/)
+
+ @method typeOf
+ @for Utils
+ @static
+ @param {Object} o Object to check.
+ @return {String} Object [[Class]]
+ */
+ var typeOf = function(o) {
+ var undef;
+
+ if (o === undef) {
+ return 'undefined';
+ } else if (o === null) {
+ return 'null';
+ } else if (o.nodeType) {
+ return 'node';
+ }
+
+ // the snippet below is awesome, however it fails to detect null, undefined and arguments types in IE lte 8
+ return ({}).toString.call(o).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
+ };
+
+ /**
+ Extends the specified object with another object.
+
+ @method extend
+ @static
+ @param {Object} target Object to extend.
+ @param {Object} [obj]* Multiple objects to extend with.
+ @return {Object} Same as target, the extended object.
+ */
+ var extend = function(target) {
+ var undef;
+
+ each(arguments, function(arg, i) {
+ if (i > 0) {
+ each(arg, function(value, key) {
+ if (value !== undef) {
+ if (typeOf(target[key]) === typeOf(value) && !!~inArray(typeOf(value), ['array', 'object'])) {
+ extend(target[key], value);
+ } else {
+ target[key] = value;
+ }
+ }
+ });
+ }
+ });
+ return target;
+ };
+
+ /**
+ Executes the callback function for each item in array/object. If you return false in the
+ callback it will break the loop.
+
+ @method each
+ @static
+ @param {Object} obj Object to iterate.
+ @param {function} callback Callback function to execute for each item.
+ */
+ var each = function(obj, callback) {
+ var length, key, i, undef;
+
+ if (obj) {
+ try {
+ length = obj.length;
+ } catch(ex) {
+ length = undef;
+ }
+
+ if (length === undef) {
+ // Loop object items
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (callback(obj[key], key) === false) {
+ return;
+ }
+ }
+ }
+ } else {
+ // Loop array items
+ for (i = 0; i < length; i++) {
+ if (callback(obj[i], i) === false) {
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ Checks if object is empty.
+
+ @method isEmptyObj
+ @static
+ @param {Object} o Object to check.
+ @return {Boolean}
+ */
+ var isEmptyObj = function(obj) {
+ var prop;
+
+ if (!obj || typeOf(obj) !== 'object') {
+ return true;
+ }
+
+ for (prop in obj) {
+ return false;
+ }
+
+ return true;
+ };
+
+ /**
+ Recieve an array of functions (usually async) to call in sequence, each function
+ receives a callback as first argument that it should call, when it completes. Finally,
+ after everything is complete, main callback is called. Passing truthy value to the
+ callback as a first argument will interrupt the sequence and invoke main callback
+ immediately.
+
+ @method inSeries
+ @static
+ @param {Array} queue Array of functions to call in sequence
+ @param {Function} cb Main callback that is called in the end, or in case of erro
+ */
+ var inSeries = function(queue, cb) {
+ var i = 0, length = queue.length;
+
+ if (typeOf(cb) !== 'function') {
+ cb = function() {};
+ }
+
+ if (!queue || !queue.length) {
+ cb();
+ }
+
+ function callNext(i) {
+ if (typeOf(queue[i]) === 'function') {
+ queue[i](function(error) {
+ /*jshint expr:true */
+ ++i < length && !error ? callNext(i) : cb(error);
+ });
+ }
+ }
+ callNext(i);
+ };
+
+
+ /**
+ Find an element in array and return it's index if present, otherwise return -1.
+
+ @method inArray
+ @static
+ @param {Mixed} needle Element to find
+ @param {Array} array
+ @return {Int} Index of the element, or -1 if not found
+ */
+ var inArray = function(needle, array) {
+ if (array) {
+ if (Array.prototype.indexOf) {
+ return Array.prototype.indexOf.call(array, needle);
+ }
+
+ for (var i = 0, length = array.length; i < length; i++) {
+ if (array[i] === needle) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ };
+
+
+ /**
+ Returns elements of first array if they are not present in second. And false - otherwise.
+
+ @private
+ @method arrayDiff
+ @param {Array} needles
+ @param {Array} array
+ @return {Array|Boolean}
+ */
+ var arrayDiff = function(needles, array) {
+ var diff = [];
+
+ if (typeOf(needles) !== 'array') {
+ needles = [needles];
+ }
+
+ if (typeOf(array) !== 'array') {
+ array = [array];
+ }
+
+ for (var i in needles) {
+ if (inArray(needles[i], array) === -1) {
+ diff.push(needles[i]);
+ }
+ }
+ return diff.length ? diff : false;
+ };
+
+
+ /**
+ Find intersection of two arrays.
+
+ @private
+ @method arrayIntersect
+ @param {Array} array1
+ @param {Array} array2
+ @return {Array} Intersection of two arrays or null if there is none
+ */
+ var arrayIntersect = function(array1, array2) {
+ var result = [];
+ each(array1, function(item) {
+ if (inArray(item, array2) !== -1) {
+ result.push(item);
+ }
+ });
+ return result.length ? result : null;
+ };
+
+
+ /**
+ Forces anything into an array.
+
+ @method toArray
+ @static
+ @param {Object} obj Object with length field.
+ @return {Array} Array object containing all items.
+ */
+ var toArray = function(obj) {
+ var i, arr = [];
+
+ for (i = 0; i < obj.length; i++) {
+ arr[i] = obj[i];
+ }
+
+ return arr;
+ };
+
+
+ /**
+ Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
+ The only way a user would be able to get the same ID is if the two persons at the same exact milisecond manages
+ 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.
+ It's more probable for the earth to be hit with an ansteriod. Y
+
+ @method guid
+ @static
+ @param {String} prefix to prepend (by default 'o' will be prepended).
+ @method guid
+ @return {String} Virtually unique id.
+ */
+ var guid = (function() {
+ var counter = 0;
+
+ return function(prefix) {
+ var guid = new Date().getTime().toString(32), i;
+
+ for (i = 0; i < 5; i++) {
+ guid += Math.floor(Math.random() * 65535).toString(32);
+ }
+
+ return (prefix || 'o_') + guid + (counter++).toString(32);
+ };
+ }());
+
+
+ /**
+ Trims white spaces around the string
+
+ @method trim
+ @static
+ @param {String} str
+ @return {String}
+ */
+ var trim = function(str) {
+ if (!str) {
+ return str;
+ }
+ return String.prototype.trim ? String.prototype.trim.call(str) : str.toString().replace(/^\s*/, '').replace(/\s*$/, '');
+ };
+
+
+ /**
+ Parses the specified size string into a byte value. For example 10kb becomes 10240.
+
+ @method parseSizeStr
+ @static
+ @param {String/Number} size String to parse or number to just pass through.
+ @return {Number} Size in bytes.
+ */
+ var parseSizeStr = function(size) {
+ if (typeof(size) !== 'string') {
+ return size;
+ }
+
+ var muls = {
+ t: 1099511627776,
+ g: 1073741824,
+ m: 1048576,
+ k: 1024
+ },
+ mul;
+
+ size = /^([0-9]+)([mgk]?)$/.exec(size.toLowerCase().replace(/[^0-9mkg]/g, ''));
+ mul = size[2];
+ size = +size[1];
+
+ if (muls.hasOwnProperty(mul)) {
+ size *= muls[mul];
+ }
+ return size;
+ };
+
+
+ return {
+ guid: guid,
+ typeOf: typeOf,
+ extend: extend,
+ each: each,
+ isEmptyObj: isEmptyObj,
+ inSeries: inSeries,
+ inArray: inArray,
+ arrayDiff: arrayDiff,
+ arrayIntersect: arrayIntersect,
+ toArray: toArray,
+ trim: trim,
+ parseSizeStr: parseSizeStr
+ };
+});
+
+// Included from: src/javascript/core/I18n.js
+
+/**
+ * I18n.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/core/I18n", [
+ "moxie/core/utils/Basic"
+], function(Basic) {
+ var i18n = {};
+
+ return {
+ /**
+ * Extends the language pack object with new items.
+ *
+ * @param {Object} pack Language pack items to add.
+ * @return {Object} Extended language pack object.
+ */
+ addI18n: function(pack) {
+ return Basic.extend(i18n, pack);
+ },
+
+ /**
+ * Translates the specified string by checking for the english string in the language pack lookup.
+ *
+ * @param {String} str String to look for.
+ * @return {String} Translated string or the input string if it wasn't found.
+ */
+ translate: function(str) {
+ return i18n[str] || str;
+ },
+
+ /**
+ * Shortcut for translate function
+ *
+ * @param {String} str String to look for.
+ * @return {String} Translated string or the input string if it wasn't found.
+ */
+ _: function(str) {
+ return this.translate(str);
+ },
+
+ /**
+ * Pseudo sprintf implementation - simple way to replace tokens with specified values.
+ *
+ * @param {String} str String with tokens
+ * @return {String} String with replaced tokens
+ */
+ sprintf: function(str) {
+ var args = [].slice.call(arguments, 1), reStr = '';
+
+ Basic.each(str.split(/%[a-z]/), function(part) {
+ reStr += part;
+ if (args.length) {
+ reStr += args.shift();
+ }
+ });
+ return reStr;
+ }
+ };
+});
+
+// Included from: src/javascript/core/utils/Mime.js
+
+/**
+ * Mime.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/core/utils/Mime", [
+ "moxie/core/utils/Basic",
+ "moxie/core/I18n"
+], function(Basic, I18n) {
+
+ var mimeData = "" +
+ "application/msword,doc dot," +
+ "application/pdf,pdf," +
+ "application/pgp-signature,pgp," +
+ "application/postscript,ps ai eps," +
+ "application/rtf,rtf," +
+ "application/vnd.ms-excel,xls xlb," +
+ "application/vnd.ms-powerpoint,ppt pps pot," +
+ "application/zip,zip," +
+ "application/x-shockwave-flash,swf swfl," +
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," +
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," +
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," +
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," +
+ "application/vnd.openxmlformats-officedocument.presentationml.template,potx," +
+ "application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," +
+ "application/x-javascript,js," +
+ "application/json,json," +
+ "audio/mpeg,mp3 mpga mpega mp2," +
+ "audio/x-wav,wav," +
+ "audio/mp4,m4a," +
+ "audio/ogg,oga ogg," +
+ "audio/aiff,aiff aif," +
+ "audio/flac,flac," +
+ "audio/aac,aac," +
+ "audio/ac3,ac3," +
+ "audio/x-ms-wma,wma," +
+ "image/bmp,bmp," +
+ "image/gif,gif," +
+ "image/jpeg,jpg jpeg jpe," +
+ "image/photoshop,psd," +
+ "image/png,png," +
+ "image/svg+xml,svg svgz," +
+ "image/tiff,tiff tif," +
+ "text/plain,asc txt text diff log," +
+ "text/html,htm html xhtml," +
+ "text/css,css," +
+ "text/csv,csv," +
+ "text/rtf,rtf," +
+ "video/mpeg,mpeg mpg mpe m2v," +
+ "video/quicktime,qt mov," +
+ "video/mp4,mp4," +
+ "video/x-m4v,m4v," +
+ "video/x-flv,flv," +
+ "video/x-ms-wmv,wmv," +
+ "video/avi,avi," +
+ "video/webm,webm," +
+ "video/3gpp,3gpp 3gp," +
+ "video/3gpp2,3g2," +
+ "video/vnd.rn-realvideo,rv," +
+ "video/ogg,ogv," +
+ "video/x-matroska,mkv," +
+ "application/vnd.oasis.opendocument.formula-template,otf," +
+ "application/octet-stream,exe";
+
+
+ var Mime = {
+
+ mimes: {},
+
+ extensions: {},
+
+ // Parses the default mime types string into a mimes and extensions lookup maps
+ addMimeType: function (mimeData) {
+ var items = mimeData.split(/,/), i, ii, ext;
+
+ for (i = 0; i < items.length; i += 2) {
+ ext = items[i + 1].split(/ /);
+
+ // extension to mime lookup
+ for (ii = 0; ii < ext.length; ii++) {
+ this.mimes[ext[ii]] = items[i];
+ }
+ // mime to extension lookup
+ this.extensions[items[i]] = ext;
+ }
+ },
+
+
+ extList2mimes: function (filters, addMissingExtensions) {
+ var self = this, ext, i, ii, type, mimes = [];
+
+ // convert extensions to mime types list
+ for (i = 0; i < filters.length; i++) {
+ ext = filters[i].extensions.split(/\s*,\s*/);
+
+ for (ii = 0; ii < ext.length; ii++) {
+
+ // if there's an asterisk in the list, then accept attribute is not required
+ if (ext[ii] === '*') {
+ return [];
+ }
+
+ type = self.mimes[ext[ii]];
+ if (!type) {
+ if (addMissingExtensions && /^\w+$/.test(ext[ii])) {
+ mimes.push('.' + ext[ii]);
+ } else {
+ return []; // accept all
+ }
+ } else if (Basic.inArray(type, mimes) === -1) {
+ mimes.push(type);
+ }
+ }
+ }
+ return mimes;
+ },
+
+
+ mimes2exts: function(mimes) {
+ var self = this, exts = [];
+
+ Basic.each(mimes, function(mime) {
+ if (mime === '*') {
+ exts = [];
+ return false;
+ }
+
+ // check if this thing looks like mime type
+ var m = mime.match(/^(\w+)\/(\*|\w+)$/);
+ if (m) {
+ if (m[2] === '*') {
+ // wildcard mime type detected
+ Basic.each(self.extensions, function(arr, mime) {
+ if ((new RegExp('^' + m[1] + '/')).test(mime)) {
+ [].push.apply(exts, self.extensions[mime]);
+ }
+ });
+ } else if (self.extensions[mime]) {
+ [].push.apply(exts, self.extensions[mime]);
+ }
+ }
+ });
+ return exts;
+ },
+
+
+ mimes2extList: function(mimes) {
+ var accept = [], exts = [];
+
+ if (Basic.typeOf(mimes) === 'string') {
+ mimes = Basic.trim(mimes).split(/\s*,\s*/);
+ }
+
+ exts = this.mimes2exts(mimes);
+
+ accept.push({
+ title: I18n.translate('Files'),
+ extensions: exts.length ? exts.join(',') : '*'
+ });
+
+ // save original mimes string
+ accept.mimes = mimes;
+
+ return accept;
+ },
+
+
+ getFileExtension: function(fileName) {
+ var matches = fileName && fileName.match(/\.([^.]+)$/);
+ if (matches) {
+ return matches[1].toLowerCase();
+ }
+ return '';
+ },
+
+ getFileMime: function(fileName) {
+ return this.mimes[this.getFileExtension(fileName)] || '';
+ }
+ };
+
+ Mime.addMimeType(mimeData);
+
+ return Mime;
+});
+
+// Included from: src/javascript/core/utils/Env.js
+
+/**
+ * Env.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/core/utils/Env", [
+ "moxie/core/utils/Basic"
+], function(Basic) {
+
+ var browser = [{
+ s1: navigator.userAgent,
+ s2: "Android",
+ id: "Android Browser", // default or Dolphin
+ sv: "Version"
+ },{
+ s1: navigator.userAgent, // string
+ s2: "Chrome", // substring
+ id: "Chrome" // identity
+ },{
+ s1: navigator.vendor,
+ s2: "Apple",
+ id: "Safari",
+ sv: "Version" // version
+ },{
+ prop: window.opera && window.opera.buildNumber,
+ id: "Opera",
+ sv: "Version"
+ },{
+ s1: navigator.vendor,
+ s2: "KDE",
+ id: "Konqueror"
+ },{
+ s1: navigator.userAgent,
+ s2: "Firefox",
+ id: "Firefox"
+ },{
+ s1: navigator.vendor,
+ s2: "Camino",
+ id: "Camino"
+ },{
+ // for newer Netscapes (6+)
+ s1: navigator.userAgent,
+ s2: "Netscape",
+ id: "Netscape"
+ },{
+ s1: navigator.userAgent,
+ s2: "MSIE",
+ id: "IE",
+ sv: "MSIE"
+ },{
+ s1: navigator.userAgent,
+ s2: "Trident",
+ id: "IE",
+ sv: "rv"
+ }, {
+ s1: navigator.userAgent,
+ s2: "Gecko",
+ id: "Mozilla",
+ sv: "rv"
+ }],
+
+ os = [{
+ s1: navigator.platform,
+ s2: "Win",
+ id: "Windows"
+ },{
+ s1: navigator.platform,
+ s2: "Mac",
+ id: "Mac"
+ },{
+ s1: navigator.userAgent,
+ s2: "iPhone",
+ id: "iOS"
+ },{
+ s1: navigator.userAgent,
+ s2: "iPad",
+ id: "iOS"
+ },{
+ s1: navigator.userAgent,
+ s2: "Android",
+ id: "Android"
+ },{
+ s1: navigator.platform,
+ s2: "Linux",
+ id: "Linux"
+ }]
+ , version;
+
+ function getStr(data) {
+ var str, prop;
+
+ for (var i = 0; i < data.length; i++) {
+ str = data[i].s1;
+ prop = data[i].prop;
+ version = data[i].sv || data[i].id;
+
+ if (str) {
+ if (str.indexOf(data[i].s2) != -1) {
+ return data[i].id;
+ }
+ } else if (prop) {
+ return data[i].id;
+ }
+ }
+ }
+
+
+ function getVer(str) {
+ var index = str.indexOf(version);
+
+ if (index == -1) {
+ return;
+ }
+
+ return parseFloat(str.substring(index + version.length + 1));
+ }
+
+ var can = (function() {
+ var caps = {
+ define_property: (function() {
+ /* // currently too much extra code required, not exactly worth it
+ try { // as of IE8, getters/setters are supported only on DOM elements
+ var obj = {};
+ if (Object.defineProperty) {
+ Object.defineProperty(obj, 'prop', {
+ enumerable: true,
+ configurable: true
+ });
+ return true;
+ }
+ } catch(ex) {}
+
+ if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) {
+ return true;
+ }*/
+ return false;
+ }()),
+
+ create_canvas: (function() {
+ // On the S60 and BB Storm, getContext exists, but always returns undefined
+ // so we actually have to call getContext() to verify
+ // github.com/Modernizr/Modernizr/issues/issue/97/
+ var el = document.createElement('canvas');
+ return !!(el.getContext && el.getContext('2d'));
+ }()),
+
+ return_response_type: function(responseType) {
+ try {
+ if (Basic.inArray(responseType, ['', 'text', 'document']) !== -1) {
+ return true;
+ } else if (window.XMLHttpRequest) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('get', '/'); // otherwise Gecko throws an exception
+ if ('responseType' in xhr) {
+ xhr.responseType = responseType;
+ // as of 23.0.1271.64, Chrome switched from throwing exception to merely logging it to the console (why? o why?)
+ if (xhr.responseType !== responseType) {
+ return false;
+ }
+ return true;
+ }
+ }
+ } catch (ex) {}
+ return false;
+ },
+
+ // ideas for this heavily come from Modernizr (http://modernizr.com/)
+ use_data_uri: (function() {
+ var du = new Image();
+
+ du.onload = function() {
+ caps.use_data_uri = (du.width === 1 && du.height === 1);
+ };
+
+ setTimeout(function() {
+ du.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
+ }, 1);
+ return false;
+ }()),
+
+ use_data_uri_over32kb: function() { // IE8
+ return caps.use_data_uri && (Env.browser !== 'IE' || Env.version >= 9);
+ },
+
+ use_data_uri_of: function(bytes) {
+ return (caps.use_data_uri && bytes < 33000 || caps.use_data_uri_over32kb());
+ },
+
+ use_fileinput: function() {
+ var el = document.createElement('input');
+ el.setAttribute('type', 'file');
+ return !el.disabled;
+ }
+ };
+
+ return function(cap) {
+ var args = [].slice.call(arguments);
+ args.shift(); // shift of cap
+ return Basic.typeOf(caps[cap]) === 'function' ? caps[cap].apply(this, args) : !!caps[cap];
+ };
+ }());
+
+ var Env = {
+ can: can,
+ browser: getStr(browser),
+ version: getVer(navigator.userAgent) || getVer(navigator.appVersion),
+ OS: getStr(os),
+ swf_url: "../flash/Moxie.swf",
+ xap_url: "../silverlight/Moxie.xap",
+ global_event_dispatcher: "moxie.core.EventTarget.instance.dispatchEvent"
+ };
+
+ return Env;
+});
+
+// Included from: src/javascript/core/utils/Dom.js
+
+/**
+ * Dom.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/utils/Dom', ['moxie/core/utils/Env'], function(Env) {
+
+ /**
+ Get DOM Element by it's id.
+
+ @method get
+ @for Utils
+ @param {String} id Identifier of the DOM Element
+ @return {DOMElement}
+ */
+ var get = function(id) {
+ if (typeof id !== 'string') {
+ return id;
+ }
+ return document.getElementById(id);
+ };
+
+ /**
+ Checks if specified DOM element has specified class.
+
+ @method hasClass
+ @static
+ @param {Object} obj DOM element like object to add handler to.
+ @param {String} name Class name
+ */
+ var hasClass = function(obj, name) {
+ if (!obj.className) {
+ return false;
+ }
+
+ var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)");
+ return regExp.test(obj.className);
+ };
+
+ /**
+ Adds specified className to specified DOM element.
+
+ @method addClass
+ @static
+ @param {Object} obj DOM element like object to add handler to.
+ @param {String} name Class name
+ */
+ var addClass = function(obj, name) {
+ if (!hasClass(obj, name)) {
+ obj.className = !obj.className ? name : obj.className.replace(/\s+$/, '') + ' ' + name;
+ }
+ };
+
+ /**
+ Removes specified className from specified DOM element.
+
+ @method removeClass
+ @static
+ @param {Object} obj DOM element like object to add handler to.
+ @param {String} name Class name
+ */
+ var removeClass = function(obj, name) {
+ if (obj.className) {
+ var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)");
+ obj.className = obj.className.replace(regExp, function($0, $1, $2) {
+ return $1 === ' ' && $2 === ' ' ? ' ' : '';
+ });
+ }
+ };
+
+ /**
+ Returns a given computed style of a DOM element.
+
+ @method getStyle
+ @static
+ @param {Object} obj DOM element like object.
+ @param {String} name Style you want to get from the DOM element
+ */
+ var getStyle = function(obj, name) {
+ if (obj.currentStyle) {
+ return obj.currentStyle[name];
+ } else if (window.getComputedStyle) {
+ return window.getComputedStyle(obj, null)[name];
+ }
+ };
+
+
+ /**
+ Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
+
+ @method getPos
+ @static
+ @param {Element} node HTML element or element id to get x, y position from.
+ @param {Element} root Optional root element to stop calculations at.
+ @return {object} Absolute position of the specified element object with x, y fields.
+ */
+ var getPos = function(node, root) {
+ var x = 0, y = 0, parent, doc = document, nodeRect, rootRect;
+
+ node = node;
+ root = root || doc.body;
+
+ // Returns the x, y cordinate for an element on IE 6 and IE 7
+ function getIEPos(node) {
+ var bodyElm, rect, x = 0, y = 0;
+
+ if (node) {
+ rect = node.getBoundingClientRect();
+ bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body;
+ x = rect.left + bodyElm.scrollLeft;
+ y = rect.top + bodyElm.scrollTop;
+ }
+
+ return {
+ x : x,
+ y : y
+ };
+ }
+
+ // Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode
+ if (node && node.getBoundingClientRect && Env.browser === 'IE' && (!doc.documentMode || doc.documentMode < 8)) {
+ nodeRect = getIEPos(node);
+ rootRect = getIEPos(root);
+
+ return {
+ x : nodeRect.x - rootRect.x,
+ y : nodeRect.y - rootRect.y
+ };
+ }
+
+ parent = node;
+ while (parent && parent != root && parent.nodeType) {
+ x += parent.offsetLeft || 0;
+ y += parent.offsetTop || 0;
+ parent = parent.offsetParent;
+ }
+
+ parent = node.parentNode;
+ while (parent && parent != root && parent.nodeType) {
+ x -= parent.scrollLeft || 0;
+ y -= parent.scrollTop || 0;
+ parent = parent.parentNode;
+ }
+
+ return {
+ x : x,
+ y : y
+ };
+ };
+
+ /**
+ Returns the size of the specified node in pixels.
+
+ @method getSize
+ @static
+ @param {Node} node Node to get the size of.
+ @return {Object} Object with a w and h property.
+ */
+ var getSize = function(node) {
+ return {
+ w : node.offsetWidth || node.clientWidth,
+ h : node.offsetHeight || node.clientHeight
+ };
+ };
+
+ return {
+ get: get,
+ hasClass: hasClass,
+ addClass: addClass,
+ removeClass: removeClass,
+ getStyle: getStyle,
+ getPos: getPos,
+ getSize: getSize
+ };
+});
+
+// Included from: src/javascript/core/Exceptions.js
+
+/**
+ * Exceptions.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/Exceptions', [
+ 'moxie/core/utils/Basic'
+], function(Basic) {
+ function _findKey(obj, value) {
+ var key;
+ for (key in obj) {
+ if (obj[key] === value) {
+ return key;
+ }
+ }
+ return null;
+ }
+
+ return {
+ RuntimeError: (function() {
+ var namecodes = {
+ NOT_INIT_ERR: 1,
+ NOT_SUPPORTED_ERR: 9,
+ JS_ERR: 4
+ };
+
+ function RuntimeError(code) {
+ this.code = code;
+ this.name = _findKey(namecodes, code);
+ this.message = this.name + ": RuntimeError " + this.code;
+ }
+
+ Basic.extend(RuntimeError, namecodes);
+ RuntimeError.prototype = Error.prototype;
+ return RuntimeError;
+ }()),
+
+ OperationNotAllowedException: (function() {
+
+ function OperationNotAllowedException(code) {
+ this.code = code;
+ this.name = 'OperationNotAllowedException';
+ }
+
+ Basic.extend(OperationNotAllowedException, {
+ NOT_ALLOWED_ERR: 1
+ });
+
+ OperationNotAllowedException.prototype = Error.prototype;
+
+ return OperationNotAllowedException;
+ }()),
+
+ ImageError: (function() {
+ var namecodes = {
+ WRONG_FORMAT: 1,
+ MAX_RESOLUTION_ERR: 2
+ };
+
+ function ImageError(code) {
+ this.code = code;
+ this.name = _findKey(namecodes, code);
+ this.message = this.name + ": ImageError " + this.code;
+ }
+
+ Basic.extend(ImageError, namecodes);
+ ImageError.prototype = Error.prototype;
+
+ return ImageError;
+ }()),
+
+ FileException: (function() {
+ var namecodes = {
+ NOT_FOUND_ERR: 1,
+ SECURITY_ERR: 2,
+ ABORT_ERR: 3,
+ NOT_READABLE_ERR: 4,
+ ENCODING_ERR: 5,
+ NO_MODIFICATION_ALLOWED_ERR: 6,
+ INVALID_STATE_ERR: 7,
+ SYNTAX_ERR: 8
+ };
+
+ function FileException(code) {
+ this.code = code;
+ this.name = _findKey(namecodes, code);
+ this.message = this.name + ": FileException " + this.code;
+ }
+
+ Basic.extend(FileException, namecodes);
+ FileException.prototype = Error.prototype;
+ return FileException;
+ }()),
+
+ DOMException: (function() {
+ var namecodes = {
+ INDEX_SIZE_ERR: 1,
+ DOMSTRING_SIZE_ERR: 2,
+ HIERARCHY_REQUEST_ERR: 3,
+ WRONG_DOCUMENT_ERR: 4,
+ INVALID_CHARACTER_ERR: 5,
+ NO_DATA_ALLOWED_ERR: 6,
+ NO_MODIFICATION_ALLOWED_ERR: 7,
+ NOT_FOUND_ERR: 8,
+ NOT_SUPPORTED_ERR: 9,
+ INUSE_ATTRIBUTE_ERR: 10,
+ INVALID_STATE_ERR: 11,
+ SYNTAX_ERR: 12,
+ INVALID_MODIFICATION_ERR: 13,
+ NAMESPACE_ERR: 14,
+ INVALID_ACCESS_ERR: 15,
+ VALIDATION_ERR: 16,
+ TYPE_MISMATCH_ERR: 17,
+ SECURITY_ERR: 18,
+ NETWORK_ERR: 19,
+ ABORT_ERR: 20,
+ URL_MISMATCH_ERR: 21,
+ QUOTA_EXCEEDED_ERR: 22,
+ TIMEOUT_ERR: 23,
+ INVALID_NODE_TYPE_ERR: 24,
+ DATA_CLONE_ERR: 25
+ };
+
+ function DOMException(code) {
+ this.code = code;
+ this.name = _findKey(namecodes, code);
+ this.message = this.name + ": DOMException " + this.code;
+ }
+
+ Basic.extend(DOMException, namecodes);
+ DOMException.prototype = Error.prototype;
+ return DOMException;
+ }()),
+
+ EventException: (function() {
+ function EventException(code) {
+ this.code = code;
+ this.name = 'EventException';
+ }
+
+ Basic.extend(EventException, {
+ UNSPECIFIED_EVENT_TYPE_ERR: 0
+ });
+
+ EventException.prototype = Error.prototype;
+
+ return EventException;
+ }())
+ };
+});
+
+// Included from: src/javascript/core/EventTarget.js
+
+/**
+ * EventTarget.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/EventTarget', [
+ 'moxie/core/Exceptions',
+ 'moxie/core/utils/Basic'
+], function(x, Basic) {
+ /**
+ Parent object for all event dispatching components and objects
+
+ @class EventTarget
+ @constructor EventTarget
+ */
+ function EventTarget() {
+ // hash of event listeners by object uid
+ var eventpool = {};
+
+ Basic.extend(this, {
+
+ /**
+ Unique id of the event dispatcher, usually overriden by children
+
+ @property uid
+ @type String
+ */
+ uid: null,
+
+ /**
+ Can be called from within a child in order to acquire uniqie id in automated manner
+
+ @method init
+ */
+ init: function() {
+ if (!this.uid) {
+ this.uid = Basic.guid('uid_');
+ }
+ },
+
+ /**
+ Register a handler to a specific event dispatched by the object
+
+ @method addEventListener
+ @param {String} type Type or basically a name of the event to subscribe to
+ @param {Function} fn Callback function that will be called when event happens
+ @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
+ @param {Object} [scope=this] A scope to invoke event handler in
+ */
+ addEventListener: function(type, fn, priority, scope) {
+ var self = this, list;
+
+ type = Basic.trim(type);
+
+ if (/\s/.test(type)) {
+ // multiple event types were passed for one handler
+ Basic.each(type.split(/\s+/), function(type) {
+ self.addEventListener(type, fn, priority, scope);
+ });
+ return;
+ }
+
+ type = type.toLowerCase();
+ priority = parseInt(priority, 10) || 0;
+
+ list = eventpool[this.uid] && eventpool[this.uid][type] || [];
+ list.push({fn : fn, priority : priority, scope : scope || this});
+
+ if (!eventpool[this.uid]) {
+ eventpool[this.uid] = {};
+ }
+ eventpool[this.uid][type] = list;
+ },
+
+ /**
+ Check if any handlers were registered to the specified event
+
+ @method hasEventListener
+ @param {String} type Type or basically a name of the event to check
+ @return {Mixed} Returns a handler if it was found and false, if - not
+ */
+ hasEventListener: function(type) {
+ return type ? !!(eventpool[this.uid] && eventpool[this.uid][type]) : !!eventpool[this.uid];
+ },
+
+ /**
+ Unregister the handler from the event, or if former was not specified - unregister all handlers
+
+ @method removeEventListener
+ @param {String} type Type or basically a name of the event
+ @param {Function} [fn] Handler to unregister
+ */
+ removeEventListener: function(type, fn) {
+ type = type.toLowerCase();
+
+ var list = eventpool[this.uid] && eventpool[this.uid][type], i;
+
+ if (list) {
+ if (fn) {
+ for (i = list.length - 1; i >= 0; i--) {
+ if (list[i].fn === fn) {
+ list.splice(i, 1);
+ break;
+ }
+ }
+ } else {
+ list = [];
+ }
+
+ // delete event list if it has become empty
+ if (!list.length) {
+ delete eventpool[this.uid][type];
+
+ // and object specific entry in a hash if it has no more listeners attached
+ if (Basic.isEmptyObj(eventpool[this.uid])) {
+ delete eventpool[this.uid];
+ }
+ }
+ }
+ },
+
+ /**
+ Remove all event handlers from the object
+
+ @method removeAllEventListeners
+ */
+ removeAllEventListeners: function() {
+ if (eventpool[this.uid]) {
+ delete eventpool[this.uid];
+ }
+ },
+
+ /**
+ Dispatch the event
+
+ @method dispatchEvent
+ @param {String/Object} Type of event or event object to dispatch
+ @param {Mixed} [...] Variable number of arguments to be passed to a handlers
+ @return {Boolean} true by default and false if any handler returned false
+ */
+ dispatchEvent: function(type) {
+ var uid, list, args, tmpEvt, evt = {}, result = true;
+
+ if (Basic.typeOf(type) !== 'string') {
+ // we can't use original object directly (because of Silverlight)
+ tmpEvt = type;
+
+ if (Basic.typeOf(tmpEvt.type) === 'string') {
+ type = tmpEvt.type;
+
+ if (tmpEvt.total && tmpEvt.loaded) { // progress event
+ evt.total = tmpEvt.total;
+ evt.loaded = tmpEvt.loaded;
+ }
+ evt.async = tmpEvt.async || false;
+ } else {
+ throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR);
+ }
+ }
+
+ // check if event is meant to be dispatched on an object having specific uid
+ if (type.indexOf('::') !== -1) {
+ (function(arr) {
+ uid = arr[0];
+ type = arr[1];
+ }(type.split('::')));
+ } else {
+ uid = this.uid;
+ }
+
+ type = type.toLowerCase();
+
+ list = eventpool[uid] && eventpool[uid][type];
+
+ if (list) {
+ // sort event list by prority
+ list.sort(function(a, b) { return b.priority - a.priority; });
+
+ args = [].slice.call(arguments);
+
+ // first argument will be pseudo-event object
+ args.shift();
+ evt.type = type;
+ args.unshift(evt);
+
+ // Dispatch event to all listeners
+ var queue = [];
+ Basic.each(list, function(handler) {
+ // explicitly set the target, otherwise events fired from shims do not get it
+ args[0].target = handler.scope;
+ // if event is marked as async, detach the handler
+ if (evt.async) {
+ queue.push(function(cb) {
+ setTimeout(function() {
+ cb(handler.fn.apply(handler.scope, args) === false);
+ }, 1);
+ });
+ } else {
+ queue.push(function(cb) {
+ cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation
+ });
+ }
+ });
+ if (queue.length) {
+ Basic.inSeries(queue, function(err) {
+ result = !err;
+ });
+ }
+ }
+ return result;
+ },
+
+ /**
+ Alias for addEventListener
+
+ @method bind
+ @protected
+ */
+ bind: function() {
+ this.addEventListener.apply(this, arguments);
+ },
+
+ /**
+ Alias for removeEventListener
+
+ @method unbind
+ @protected
+ */
+ unbind: function() {
+ this.removeEventListener.apply(this, arguments);
+ },
+
+ /**
+ Alias for removeAllEventListeners
+
+ @method unbindAll
+ @protected
+ */
+ unbindAll: function() {
+ this.removeAllEventListeners.apply(this, arguments);
+ },
+
+ /**
+ Alias for dispatchEvent
+
+ @method trigger
+ @protected
+ */
+ trigger: function() {
+ return this.dispatchEvent.apply(this, arguments);
+ },
+
+
+ /**
+ Converts properties of on[event] type to corresponding event handlers,
+ is used to avoid extra hassle around the process of calling them back
+
+ @method convertEventPropsToHandlers
+ @private
+ */
+ convertEventPropsToHandlers: function(handlers) {
+ var h;
+
+ if (Basic.typeOf(handlers) !== 'array') {
+ handlers = [handlers];
+ }
+
+ for (var i = 0; i < handlers.length; i++) {
+ h = 'on' + handlers[i];
+
+ if (Basic.typeOf(this[h]) === 'function') {
+ this.addEventListener(handlers[i], this[h]);
+ } else if (Basic.typeOf(this[h]) === 'undefined') {
+ this[h] = null; // object must have defined event properties, even if it doesn't make use of them
+ }
+ }
+ }
+
+ });
+ }
+
+ EventTarget.instance = new EventTarget();
+
+ return EventTarget;
+});
+
+// Included from: src/javascript/core/utils/Encode.js
+
+/**
+ * Encode.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/utils/Encode', [], function() {
+
+ /**
+ Encode string with UTF-8
+
+ @method utf8_encode
+ @for Utils
+ @static
+ @param {String} str String to encode
+ @return {String} UTF-8 encoded string
+ */
+ var utf8_encode = function(str) {
+ return unescape(encodeURIComponent(str));
+ };
+
+ /**
+ Decode UTF-8 encoded string
+
+ @method utf8_decode
+ @static
+ @param {String} str String to decode
+ @return {String} Decoded string
+ */
+ var utf8_decode = function(str_data) {
+ return decodeURIComponent(escape(str_data));
+ };
+
+ /**
+ Decode Base64 encoded string (uses browser's default method if available),
+ from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_decode.js
+
+ @method atob
+ @static
+ @param {String} data String to decode
+ @return {String} Decoded string
+ */
+ var atob = function(data, utf8) {
+ if (typeof(window.atob) === 'function') {
+ return utf8 ? utf8_decode(window.atob(data)) : window.atob(data);
+ }
+
+ // http://kevin.vanzonneveld.net
+ // + original by: Tyler Akins (http://rumkin.com)
+ // + improved by: Thunder.m
+ // + input by: Aman Gupta
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + bugfixed by: Onno Marsman
+ // + bugfixed by: Pellentesque Malesuada
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + input by: Brett Zamir (http://brett-zamir.me)
+ // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
+ // * returns 1: 'Kevin van Zonneveld'
+ // mozilla has this native
+ // - but breaks in 2.0.0.12!
+ //if (typeof this.window.atob == 'function') {
+ // return atob(data);
+ //}
+ var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
+ ac = 0,
+ dec = "",
+ tmp_arr = [];
+
+ if (!data) {
+ return data;
+ }
+
+ data += '';
+
+ do { // unpack four hexets into three octets using index points in b64
+ h1 = b64.indexOf(data.charAt(i++));
+ h2 = b64.indexOf(data.charAt(i++));
+ h3 = b64.indexOf(data.charAt(i++));
+ h4 = b64.indexOf(data.charAt(i++));
+
+ bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+
+ o1 = bits >> 16 & 0xff;
+ o2 = bits >> 8 & 0xff;
+ o3 = bits & 0xff;
+
+ if (h3 == 64) {
+ tmp_arr[ac++] = String.fromCharCode(o1);
+ } else if (h4 == 64) {
+ tmp_arr[ac++] = String.fromCharCode(o1, o2);
+ } else {
+ tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
+ }
+ } while (i < data.length);
+
+ dec = tmp_arr.join('');
+
+ return utf8 ? utf8_decode(dec) : dec;
+ };
+
+ /**
+ Base64 encode string (uses browser's default method if available),
+ from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_encode.js
+
+ @method btoa
+ @static
+ @param {String} data String to encode
+ @return {String} Base64 encoded string
+ */
+ var btoa = function(data, utf8) {
+ if (utf8) {
+ utf8_encode(data);
+ }
+
+ if (typeof(window.btoa) === 'function') {
+ return window.btoa(data);
+ }
+
+ // http://kevin.vanzonneveld.net
+ // + original by: Tyler Akins (http://rumkin.com)
+ // + improved by: Bayron Guevara
+ // + improved by: Thunder.m
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + bugfixed by: Pellentesque Malesuada
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + improved by: Rafał Kukawski (http://kukawski.pl)
+ // * example 1: base64_encode('Kevin van Zonneveld');
+ // * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
+ // mozilla has this native
+ // - but breaks in 2.0.0.12!
+ var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
+ ac = 0,
+ enc = "",
+ tmp_arr = [];
+
+ if (!data) {
+ return data;
+ }
+
+ do { // pack three octets into four hexets
+ o1 = data.charCodeAt(i++);
+ o2 = data.charCodeAt(i++);
+ o3 = data.charCodeAt(i++);
+
+ bits = o1 << 16 | o2 << 8 | o3;
+
+ h1 = bits >> 18 & 0x3f;
+ h2 = bits >> 12 & 0x3f;
+ h3 = bits >> 6 & 0x3f;
+ h4 = bits & 0x3f;
+
+ // use hexets to index into b64, and append result to encoded string
+ tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
+ } while (i < data.length);
+
+ enc = tmp_arr.join('');
+
+ var r = data.length % 3;
+
+ return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
+ };
+
+
+ return {
+ utf8_encode: utf8_encode,
+ utf8_decode: utf8_decode,
+ atob: atob,
+ btoa: btoa
+ };
+});
+
+// Included from: src/javascript/runtime/Runtime.js
+
+/**
+ * Runtime.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/runtime/Runtime', [
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Dom",
+ "moxie/core/EventTarget"
+], function(Basic, Dom, EventTarget) {
+ var runtimeConstructors = {}, runtimes = {};
+
+ /**
+ Common set of methods and properties for every runtime instance
+
+ @class Runtime
+
+ @param {Object} options
+ @param {String} type Sanitized name of the runtime
+ @param {Object} [caps] Set of capabilities that differentiate specified runtime
+ @param {Object} [modeCaps] Set of capabilities that do require specific operational mode
+ @param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested
+ */
+ function Runtime(options, type, caps, modeCaps, preferredMode) {
+ /**
+ Dispatched when runtime is initialized and ready.
+ Results in RuntimeInit on a connected component.
+
+ @event Init
+ */
+
+ /**
+ Dispatched when runtime fails to initialize.
+ Results in RuntimeError on a connected component.
+
+ @event Error
+ */
+
+ var self = this
+ , _shim
+ , _uid = Basic.guid(type + '_')
+ , defaultMode = preferredMode || 'browser'
+ ;
+
+ options = options || {};
+
+ // register runtime in private hash
+ runtimes[_uid] = this;
+
+ /**
+ Default set of capabilities, which can be redifined later by specific runtime
+
+ @private
+ @property caps
+ @type Object
+ */
+ caps = Basic.extend({
+ // Runtime can:
+ // provide access to raw binary data of the file
+ access_binary: false,
+ // provide access to raw binary data of the image (image extension is optional)
+ access_image_binary: false,
+ // display binary data as thumbs for example
+ display_media: false,
+ // make cross-domain requests
+ do_cors: false,
+ // accept files dragged and dropped from the desktop
+ drag_and_drop: false,
+ // filter files in selection dialog by their extensions
+ filter_by_extension: true,
+ // resize image (and manipulate it raw data of any file in general)
+ resize_image: false,
+ // periodically report how many bytes of total in the file were uploaded (loaded)
+ report_upload_progress: false,
+ // provide access to the headers of http response
+ return_response_headers: false,
+ // support response of specific type, which should be passed as an argument
+ // e.g. runtime.can('return_response_type', 'blob')
+ return_response_type: false,
+ // return http status code of the response
+ return_status_code: true,
+ // send custom http header with the request
+ send_custom_headers: false,
+ // pick up the files from a dialog
+ select_file: false,
+ // select whole folder in file browse dialog
+ select_folder: false,
+ // select multiple files at once in file browse dialog
+ select_multiple: true,
+ // send raw binary data, that is generated after image resizing or manipulation of other kind
+ send_binary_string: false,
+ // send cookies with http request and therefore retain session
+ send_browser_cookies: true,
+ // send data formatted as multipart/form-data
+ send_multipart: true,
+ // slice the file or blob to smaller parts
+ slice_blob: false,
+ // upload file without preloading it to memory, stream it out directly from disk
+ stream_upload: false,
+ // programmatically trigger file browse dialog
+ summon_file_dialog: false,
+ // upload file of specific size, size should be passed as argument
+ // e.g. runtime.can('upload_filesize', '500mb')
+ upload_filesize: true,
+ // initiate http request with specific http method, method should be passed as argument
+ // e.g. runtime.can('use_http_method', 'put')
+ use_http_method: true
+ }, caps);
+
+
+ // default to the mode that is compatible with preferred caps
+ if (options.preferred_caps) {
+ defaultMode = Runtime.getMode(modeCaps, options.preferred_caps, defaultMode);
+ }
+
+ // small extension factory here (is meant to be extended with actual extensions constructors)
+ _shim = (function() {
+ var objpool = {};
+ return {
+ exec: function(uid, comp, fn, args) {
+ if (_shim[comp]) {
+ if (!objpool[uid]) {
+ objpool[uid] = {
+ context: this,
+ instance: new _shim[comp]()
+ };
+ }
+ if (objpool[uid].instance[fn]) {
+ return objpool[uid].instance[fn].apply(this, args);
+ }
+ }
+ },
+
+ removeInstance: function(uid) {
+ delete objpool[uid];
+ },
+
+ removeAllInstances: function() {
+ var self = this;
+ Basic.each(objpool, function(obj, uid) {
+ if (Basic.typeOf(obj.instance.destroy) === 'function') {
+ obj.instance.destroy.call(obj.context);
+ }
+ self.removeInstance(uid);
+ });
+ }
+ };
+ }());
+
+
+ // public methods
+ Basic.extend(this, {
+ /**
+ Specifies whether runtime instance was initialized or not
+
+ @property initialized
+ @type {Boolean}
+ @default false
+ */
+ initialized: false, // shims require this flag to stop initialization retries
+
+ /**
+ Unique ID of the runtime
+
+ @property uid
+ @type {String}
+ */
+ uid: _uid,
+
+ /**
+ Runtime type (e.g. flash, html5, etc)
+
+ @property type
+ @type {String}
+ */
+ type: type,
+
+ /**
+ Runtime (not native one) may operate in browser or client mode.
+
+ @property mode
+ @private
+ @type {String|Boolean} current mode or false, if none possible
+ */
+ mode: Runtime.getMode(modeCaps, (options.required_caps), defaultMode),
+
+ /**
+ id of the DOM container for the runtime (if available)
+
+ @property shimid
+ @type {String}
+ */
+ shimid: _uid + '_container',
+
+ /**
+ Number of connected clients. If equal to zero, runtime can be destroyed
+
+ @property clients
+ @type {Number}
+ */
+ clients: 0,
+
+ /**
+ Runtime initialization options
+
+ @property options
+ @type {Object}
+ */
+ options: options,
+
+ /**
+ Checks if the runtime has specific capability
+
+ @method can
+ @param {String} cap Name of capability to check
+ @param {Mixed} [value] If passed, capability should somehow correlate to the value
+ @param {Object} [refCaps] Set of capabilities to check the specified cap against (defaults to internal set)
+ @return {Boolean} true if runtime has such capability and false, if - not
+ */
+ can: function(cap, value) {
+ var refCaps = arguments[2] || caps;
+
+ // if cap var is a comma-separated list of caps, convert it to object (key/value)
+ if (Basic.typeOf(cap) === 'string' && Basic.typeOf(value) === 'undefined') {
+ cap = Runtime.parseCaps(cap);
+ }
+
+ if (Basic.typeOf(cap) === 'object') {
+ for (var key in cap) {
+ if (!this.can(key, cap[key], refCaps)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // check the individual cap
+ if (Basic.typeOf(refCaps[cap]) === 'function') {
+ return refCaps[cap].call(this, value);
+ } else {
+ return (value === refCaps[cap]);
+ }
+ },
+
+ /**
+ Returns container for the runtime as DOM element
+
+ @method getShimContainer
+ @return {DOMElement}
+ */
+ getShimContainer: function() {
+ var container, shimContainer = Dom.get(this.shimid);
+
+ // if no container for shim, create one
+ if (!shimContainer) {
+ container = this.options.container ? Dom.get(this.options.container) : document.body;
+
+ // create shim container and insert it at an absolute position into the outer container
+ shimContainer = document.createElement('div');
+ shimContainer.id = this.shimid;
+ shimContainer.className = 'moxie-shim moxie-shim-' + this.type;
+
+ Basic.extend(shimContainer.style, {
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ width: '1px',
+ height: '1px',
+ overflow: 'hidden'
+ });
+
+ container.appendChild(shimContainer);
+ container = null;
+ }
+
+ return shimContainer;
+ },
+
+ /**
+ Returns runtime as DOM element (if appropriate)
+
+ @method getShim
+ @return {DOMElement}
+ */
+ getShim: function() {
+ return _shim;
+ },
+
+ /**
+ Invokes a method within the runtime itself (might differ across the runtimes)
+
+ @method shimExec
+ @param {Mixed} []
+ @protected
+ @return {Mixed} Depends on the action and component
+ */
+ shimExec: function(component, action) {
+ var args = [].slice.call(arguments, 2);
+ return self.getShim().exec.call(this, this.uid, component, action, args);
+ },
+
+ /**
+ Operaional interface that is used by components to invoke specific actions on the runtime
+ (is invoked in the scope of component)
+
+ @method exec
+ @param {Mixed} []*
+ @protected
+ @return {Mixed} Depends on the action and component
+ */
+ exec: function(component, action) { // this is called in the context of component, not runtime
+ var args = [].slice.call(arguments, 2);
+
+ if (self[component] && self[component][action]) {
+ return self[component][action].apply(this, args);
+ }
+ return self.shimExec.apply(this, arguments);
+ },
+
+ /**
+ Destroys the runtime (removes all events and deletes DOM structures)
+
+ @method destroy
+ */
+ destroy: function() {
+ if (!self) {
+ return; // obviously already destroyed
+ }
+
+ var shimContainer = Dom.get(this.shimid);
+ if (shimContainer) {
+ shimContainer.parentNode.removeChild(shimContainer);
+ }
+
+ if (_shim) {
+ _shim.removeAllInstances();
+ }
+
+ this.unbindAll();
+ delete runtimes[this.uid];
+ this.uid = null; // mark this runtime as destroyed
+ _uid = self = _shim = shimContainer = null;
+ }
+ });
+
+ // once we got the mode, test against all caps
+ if (this.mode && options.required_caps && !this.can(options.required_caps)) {
+ this.mode = false;
+ }
+ }
+
+
+ /**
+ Default order to try different runtime types
+
+ @property order
+ @type String
+ @static
+ */
+ Runtime.order = 'html5,flash,silverlight,html4';
+
+
+ /**
+ Retrieves runtime from private hash by it's uid
+
+ @method getRuntime
+ @private
+ @static
+ @param {String} uid Unique identifier of the runtime
+ @return {Runtime|Boolean} Returns runtime, if it exists and false, if - not
+ */
+ Runtime.getRuntime = function(uid) {
+ return runtimes[uid] ? runtimes[uid] : false;
+ };
+
+
+ /**
+ Register constructor for the Runtime of new (or perhaps modified) type
+
+ @method addConstructor
+ @static
+ @param {String} type Runtime type (e.g. flash, html5, etc)
+ @param {Function} construct Constructor for the Runtime type
+ */
+ Runtime.addConstructor = function(type, constructor) {
+ constructor.prototype = EventTarget.instance;
+ runtimeConstructors[type] = constructor;
+ };
+
+
+ /**
+ Get the constructor for the specified type.
+
+ method getConstructor
+ @static
+ @param {String} type Runtime type (e.g. flash, html5, etc)
+ @return {Function} Constructor for the Runtime type
+ */
+ Runtime.getConstructor = function(type) {
+ return runtimeConstructors[type] || null;
+ };
+
+
+ /**
+ Get info about the runtime (uid, type, capabilities)
+
+ @method getInfo
+ @static
+ @param {String} uid Unique identifier of the runtime
+ @return {Mixed} Info object or null if runtime doesn't exist
+ */
+ Runtime.getInfo = function(uid) {
+ var runtime = Runtime.getRuntime(uid);
+
+ if (runtime) {
+ return {
+ uid: runtime.uid,
+ type: runtime.type,
+ can: function() {
+ return runtime.can.apply(runtime, arguments);
+ }
+ };
+ }
+ return null;
+ };
+
+
+ /**
+ Convert caps represented by a comma-separated string to the object representation.
+
+ @method parseCaps
+ @static
+ @param {String} capStr Comma-separated list of capabilities
+ @return {Object}
+ */
+ Runtime.parseCaps = function(capStr) {
+ var capObj = {};
+
+ if (Basic.typeOf(capStr) !== 'string') {
+ return capStr || {};
+ }
+
+ Basic.each(capStr.split(','), function(key) {
+ capObj[key] = true; // we assume it to be - true
+ });
+
+ return capObj;
+ };
+
+ /**
+ Test the specified runtime for specific capabilities.
+
+ @method can
+ @static
+ @param {String} type Runtime type (e.g. flash, html5, etc)
+ @param {String|Object} caps Set of capabilities to check
+ @return {Boolean} Result of the test
+ */
+ Runtime.can = function(type, caps) {
+ var runtime
+ , constructor = Runtime.getConstructor(type)
+ , mode
+ ;
+ if (constructor) {
+ runtime = new constructor({
+ required_caps: caps
+ });
+ mode = runtime.mode;
+ runtime.destroy();
+ return !!mode;
+ }
+ return false;
+ };
+
+
+ /**
+ Figure out a runtime that supports specified capabilities.
+
+ @method thatCan
+ @static
+ @param {String|Object} caps Set of capabilities to check
+ @param {String} [runtimeOrder] Comma-separated list of runtimes to check against
+ @return {String} Usable runtime identifier or null
+ */
+ Runtime.thatCan = function(caps, runtimeOrder) {
+ var types = (runtimeOrder || Runtime.order).split(/\s*,\s*/);
+ for (var i in types) {
+ if (Runtime.can(types[i], caps)) {
+ return types[i];
+ }
+ }
+ return null;
+ };
+
+
+ /**
+ Figure out an operational mode for the specified set of capabilities.
+
+ @method getMode
+ @static
+ @param {Object} modeCaps Set of capabilities that depend on particular runtime mode
+ @param {Object} [requiredCaps] Supplied set of capabilities to find operational mode for
+ @param {String|Boolean} [defaultMode='browser'] Default mode to use
+ @return {String|Boolean} Compatible operational mode
+ */
+ Runtime.getMode = function(modeCaps, requiredCaps, defaultMode) {
+ var mode = null;
+
+ if (Basic.typeOf(defaultMode) === 'undefined') { // only if not specified
+ defaultMode = 'browser';
+ }
+
+ if (requiredCaps && !Basic.isEmptyObj(modeCaps)) {
+ // loop over required caps and check if they do require the same mode
+ Basic.each(requiredCaps, function(value, cap) {
+ if (modeCaps.hasOwnProperty(cap)) {
+ var capMode = modeCaps[cap](value);
+
+ // make sure we always have an array
+ if (typeof(capMode) === 'string') {
+ capMode = [capMode];
+ }
+
+ if (!mode) {
+ mode = capMode;
+ } else if (!(mode = Basic.arrayIntersect(mode, capMode))) {
+ // if cap requires conflicting mode - runtime cannot fulfill required caps
+ return (mode = false);
+ }
+ }
+ });
+
+ if (mode) {
+ return Basic.inArray(defaultMode, mode) !== -1 ? defaultMode : mode[0];
+ } else if (mode === false) {
+ return false;
+ }
+ }
+ return defaultMode;
+ };
+
+
+ /**
+ Capability check that always returns true
+
+ @private
+ @static
+ @return {True}
+ */
+ Runtime.capTrue = function() {
+ return true;
+ };
+
+ /**
+ Capability check that always returns false
+
+ @private
+ @static
+ @return {False}
+ */
+ Runtime.capFalse = function() {
+ return false;
+ };
+
+ /**
+ Evaluate the expression to boolean value and create a function that always returns it.
+
+ @private
+ @static
+ @param {Mixed} expr Expression to evaluate
+ @return {Function} Function returning the result of evaluation
+ */
+ Runtime.capTest = function(expr) {
+ return function() {
+ return !!expr;
+ };
+ };
+
+ return Runtime;
+});
+
+// Included from: src/javascript/runtime/RuntimeClient.js
+
+/**
+ * RuntimeClient.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/runtime/RuntimeClient', [
+ 'moxie/core/Exceptions',
+ 'moxie/core/utils/Basic',
+ 'moxie/runtime/Runtime'
+], function(x, Basic, Runtime) {
+ /**
+ Set of methods and properties, required by a component to acquire ability to connect to a runtime
+
+ @class RuntimeClient
+ */
+ return function RuntimeClient() {
+ var runtime;
+
+ Basic.extend(this, {
+ /**
+ Connects to the runtime specified by the options. Will either connect to existing runtime or create a new one.
+ Increments number of clients connected to the specified runtime.
+
+ @method connectRuntime
+ @param {Mixed} options Can be a runtme uid or a set of key-value pairs defining requirements and pre-requisites
+ */
+ connectRuntime: function(options) {
+ var comp = this, ruid;
+
+ function initialize(items) {
+ var type, constructor;
+
+ // if we ran out of runtimes
+ if (!items.length) {
+ comp.trigger('RuntimeError', new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
+ runtime = null;
+ return;
+ }
+
+ type = items.shift();
+ constructor = Runtime.getConstructor(type);
+ if (!constructor) {
+ initialize(items);
+ return;
+ }
+
+ // try initializing the runtime
+ runtime = new constructor(options);
+
+ runtime.bind('Init', function() {
+ // mark runtime as initialized
+ runtime.initialized = true;
+
+ // jailbreak ...
+ setTimeout(function() {
+ runtime.clients++;
+ // this will be triggered on component
+ comp.trigger('RuntimeInit', runtime);
+ }, 1);
+ });
+
+ runtime.bind('Error', function() {
+ runtime.destroy(); // runtime cannot destroy itself from inside at a right moment, thus we do it here
+ initialize(items);
+ });
+
+ /*runtime.bind('Exception', function() { });*/
+
+ // check if runtime managed to pick-up operational mode
+ if (!runtime.mode) {
+ runtime.trigger('Error');
+ return;
+ }
+
+ runtime.init();
+ }
+
+ // check if a particular runtime was requested
+ if (Basic.typeOf(options) === 'string') {
+ ruid = options;
+ } else if (Basic.typeOf(options.ruid) === 'string') {
+ ruid = options.ruid;
+ }
+
+ if (ruid) {
+ runtime = Runtime.getRuntime(ruid);
+ if (runtime) {
+ runtime.clients++;
+ return runtime;
+ } else {
+ // there should be a runtime and there's none - weird case
+ throw new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR);
+ }
+ }
+
+ // initialize a fresh one, that fits runtime list and required features best
+ initialize((options.runtime_order || Runtime.order).split(/\s*,\s*/));
+ },
+
+ /**
+ Returns the runtime to which the client is currently connected.
+
+ @method getRuntime
+ @return {Runtime} Runtime or null if client is not connected
+ */
+ getRuntime: function() {
+ if (runtime && runtime.uid) {
+ return runtime;
+ }
+ runtime = null; // make sure we do not leave zombies rambling around
+ return null;
+ },
+
+ /**
+ Disconnects from the runtime. Decrements number of clients connected to the specified runtime.
+
+ @method disconnectRuntime
+ */
+ disconnectRuntime: function() {
+ if (runtime && --runtime.clients <= 0) {
+ runtime.destroy();
+ runtime = null;
+ }
+ }
+
+ });
+ };
+
+
+});
+
+// Included from: src/javascript/file/Blob.js
+
+/**
+ * Blob.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/file/Blob', [
+ 'moxie/core/utils/Basic',
+ 'moxie/core/utils/Encode',
+ 'moxie/runtime/RuntimeClient'
+], function(Basic, Encode, RuntimeClient) {
+
+ var blobpool = {};
+
+ /**
+ @class Blob
+ @constructor
+ @param {String} ruid Unique id of the runtime, to which this blob belongs to
+ @param {Object} blob Object "Native" blob object, as it is represented in the runtime
+ */
+ function Blob(ruid, blob) {
+
+ function _sliceDetached(start, end, type) {
+ var blob, data = blobpool[this.uid];
+
+ if (Basic.typeOf(data) !== 'string' || !data.length) {
+ return null; // or throw exception
+ }
+
+ blob = new Blob(null, {
+ type: type,
+ size: end - start
+ });
+ blob.detach(data.substr(start, blob.size));
+
+ return blob;
+ }
+
+ RuntimeClient.call(this);
+
+ if (ruid) {
+ this.connectRuntime(ruid);
+ }
+
+ if (!blob) {
+ blob = {};
+ } else if (Basic.typeOf(blob) === 'string') { // dataUrl or binary string
+ blob = { data: blob };
+ }
+
+ Basic.extend(this, {
+
+ /**
+ Unique id of the component
+
+ @property uid
+ @type {String}
+ */
+ uid: blob.uid || Basic.guid('uid_'),
+
+ /**
+ Unique id of the connected runtime, if falsy, then runtime will have to be initialized
+ before this Blob can be used, modified or sent
+
+ @property ruid
+ @type {String}
+ */
+ ruid: ruid,
+
+ /**
+ Size of blob
+
+ @property size
+ @type {Number}
+ @default 0
+ */
+ size: blob.size || 0,
+
+ /**
+ Mime type of blob
+
+ @property type
+ @type {String}
+ @default ''
+ */
+ type: blob.type || '',
+
+ /**
+ @method slice
+ @param {Number} [start=0]
+ */
+ slice: function(start, end, type) {
+ if (this.isDetached()) {
+ return _sliceDetached.apply(this, arguments);
+ }
+ return this.getRuntime().exec.call(this, 'Blob', 'slice', this.getSource(), start, end, type);
+ },
+
+ /**
+ Returns "native" blob object (as it is represented in connected runtime) or null if not found
+
+ @method getSource
+ @return {Blob} Returns "native" blob object or null if not found
+ */
+ getSource: function() {
+ if (!blobpool[this.uid]) {
+ return null;
+ }
+ return blobpool[this.uid];
+ },
+
+ /**
+ Detaches blob from any runtime that it depends on and initialize with standalone value
+
+ @method detach
+ @protected
+ @param {DOMString} [data=''] Standalone value
+ */
+ detach: function(data) {
+ if (this.ruid) {
+ this.getRuntime().exec.call(this, 'Blob', 'destroy', blobpool[this.uid]);
+ this.disconnectRuntime();
+ this.ruid = null;
+ }
+
+ data = data || '';
+
+ // if dataUrl, convert to binary string
+ var matches = data.match(/^data:([^;]*);base64,/);
+ if (matches) {
+ this.type = matches[1];
+ data = Encode.atob(data.substring(data.indexOf('base64,') + 7));
+ }
+
+ this.size = data.length;
+
+ blobpool[this.uid] = data;
+ },
+
+ /**
+ Checks if blob is standalone (detached of any runtime)
+
+ @method isDetached
+ @protected
+ @return {Boolean}
+ */
+ isDetached: function() {
+ return !this.ruid && Basic.typeOf(blobpool[this.uid]) === 'string';
+ },
+
+ /**
+ Destroy Blob and free any resources it was using
+
+ @method destroy
+ */
+ destroy: function() {
+ this.detach();
+ delete blobpool[this.uid];
+ }
+ });
+
+
+ if (blob.data) {
+ this.detach(blob.data); // auto-detach if payload has been passed
+ } else {
+ blobpool[this.uid] = blob;
+ }
+ }
+
+ return Blob;
+});
+
+// Included from: src/javascript/file/File.js
+
+/**
+ * File.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/file/File', [
+ 'moxie/core/utils/Basic',
+ 'moxie/core/utils/Mime',
+ 'moxie/file/Blob'
+], function(Basic, Mime, Blob) {
+ /**
+ @class File
+ @extends Blob
+ @constructor
+ @param {String} ruid Unique id of the runtime, to which this blob belongs to
+ @param {Object} file Object "Native" file object, as it is represented in the runtime
+ */
+ function File(ruid, file) {
+ var name, type;
+
+ if (!file) { // avoid extra errors in case we overlooked something
+ file = {};
+ }
+
+ // figure out the type
+ if (file.type && file.type !== '') {
+ type = file.type;
+ } else {
+ type = Mime.getFileMime(file.name);
+ }
+
+ // sanitize file name or generate new one
+ if (file.name) {
+ name = file.name.replace(/\\/g, '/');
+ name = name.substr(name.lastIndexOf('/') + 1);
+ } else {
+ var prefix = type.split('/')[0];
+ name = Basic.guid((prefix !== '' ? prefix : 'file') + '_');
+
+ if (Mime.extensions[type]) {
+ name += '.' + Mime.extensions[type][0]; // append proper extension if possible
+ }
+ }
+
+ Blob.apply(this, arguments);
+
+ Basic.extend(this, {
+ /**
+ File mime type
+
+ @property type
+ @type {String}
+ @default ''
+ */
+ type: type || '',
+
+ /**
+ File name
+
+ @property name
+ @type {String}
+ @default UID
+ */
+ name: name || Basic.guid('file_'),
+
+ /**
+ Date of last modification
+
+ @property lastModifiedDate
+ @type {String}
+ @default now
+ */
+ lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString() // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
+ });
+ }
+
+ File.prototype = Blob.prototype;
+
+ return File;
+});
+
+// Included from: src/javascript/file/FileInput.js
+
+/**
+ * FileInput.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/file/FileInput', [
+ 'moxie/core/utils/Basic',
+ 'moxie/core/utils/Mime',
+ 'moxie/core/utils/Dom',
+ 'moxie/core/Exceptions',
+ 'moxie/core/EventTarget',
+ 'moxie/core/I18n',
+ 'moxie/file/File',
+ 'moxie/runtime/Runtime',
+ 'moxie/runtime/RuntimeClient'
+], function(Basic, Mime, Dom, x, EventTarget, I18n, File, Runtime, RuntimeClient) {
+ /**
+ Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click,
+ converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory
+ with _FileReader_ or uploaded to a server through _XMLHttpRequest_.
+
+ @class FileInput
+ @constructor
+ @extends EventTarget
+ @uses RuntimeClient
+ @param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_.
+ @param {String|DOMElement} options.browse_button DOM Element to turn into file picker.
+ @param {Array} [options.accept] Array of mime types to accept. By default accepts all.
+ @param {String} [options.file='file'] Name of the file field (not the filename).
+ @param {Boolean} [options.multiple=false] Enable selection of multiple files.
+ @param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time).
+ @param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode
+ for _browse\_button_.
+ @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support.
+
+ @example
+ <div id="container">
+ <a id="file-picker" href="javascript:;">Browse...</a>
+ </div>
+
+ <script>
+ var fileInput = new mOxie.FileInput({
+ browse_button: 'file-picker', // or document.getElementById('file-picker')
+ container: 'container',
+ accept: [
+ {title: "Image files", extensions: "jpg,gif,png"} // accept only images
+ ],
+ multiple: true // allow multiple file selection
+ });
+
+ fileInput.onchange = function(e) {
+ // do something to files array
+ console.info(e.target.files); // or this.files or fileInput.files
+ };
+
+ fileInput.init(); // initialize
+ </script>
+ */
+ var dispatches = [
+ /**
+ Dispatched when runtime is connected and file-picker is ready to be used.
+
+ @event ready
+ @param {Object} event
+ */
+ 'ready',
+
+ /**
+ Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked.
+ Check [corresponding documentation entry](#method_refresh) for more info.
+
+ @event refresh
+ @param {Object} event
+ */
+
+ /**
+ Dispatched when selection of files in the dialog is complete.
+
+ @event change
+ @param {Object} event
+ */
+ 'change',
+
+ 'cancel', // TODO: might be useful
+
+ /**
+ Dispatched when mouse cursor enters file-picker area. Can be used to style element
+ accordingly.
+
+ @event mouseenter
+ @param {Object} event
+ */
+ 'mouseenter',
+
+ /**
+ Dispatched when mouse cursor leaves file-picker area. Can be used to style element
+ accordingly.
+
+ @event mouseleave
+ @param {Object} event
+ */
+ 'mouseleave',
+
+ /**
+ Dispatched when functional mouse button is pressed on top of file-picker area.
+
+ @event mousedown
+ @param {Object} event
+ */
+ 'mousedown',
+
+ /**
+ Dispatched when functional mouse button is released on top of file-picker area.
+
+ @event mouseup
+ @param {Object} event
+ */
+ 'mouseup'
+ ];
+
+ function FileInput(options) {
+ var self = this,
+ container, browseButton, defaults;
+
+ // if flat argument passed it should be browse_button id
+ if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) {
+ options = { browse_button : options };
+ }
+
+ // this will help us to find proper default container
+ browseButton = Dom.get(options.browse_button);
+ if (!browseButton) {
+ // browse button is required
+ throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
+ }
+
+ // figure out the options
+ defaults = {
+ accept: [{
+ title: I18n.translate('All Files'),
+ extensions: '*'
+ }],
+ name: 'file',
+ multiple: false,
+ required_caps: false,
+ container: browseButton.parentNode || document.body
+ };
+
+ options = Basic.extend({}, defaults, options);
+
+ // convert to object representation
+ if (typeof(options.required_caps) === 'string') {
+ options.required_caps = Runtime.parseCaps(options.required_caps);
+ }
+
+ // normalize accept option (could be list of mime types or array of title/extensions pairs)
+ if (typeof(options.accept) === 'string') {
+ options.accept = Mime.mimes2extList(options.accept);
+ }
+
+ container = Dom.get(options.container);
+ // make sure we have container
+ if (!container) {
+ container = document.body;
+ }
+
+ // make container relative, if it's not
+ if (Dom.getStyle(container, 'position') === 'static') {
+ container.style.position = 'relative';
+ }
+
+ container = browseButton = null; // IE
+
+ RuntimeClient.call(self);
+
+ Basic.extend(self, {
+ /**
+ Unique id of the component
+
+ @property uid
+ @protected
+ @readOnly
+ @type {String}
+ @default UID
+ */
+ uid: Basic.guid('uid_'),
+
+ /**
+ Unique id of the connected runtime, if any.
+
+ @property ruid
+ @protected
+ @type {String}
+ */
+ ruid: null,
+
+ /**
+ Unique id of the runtime container. Useful to get hold of it for various manipulations.
+
+ @property shimid
+ @protected
+ @type {String}
+ */
+ shimid: null,
+
+ /**
+ Array of selected mOxie.File objects
+
+ @property files
+ @type {Array}
+ @default null
+ */
+ files: null,
+
+ /**
+ Initializes the file-picker, connects it to runtime and dispatches event ready when done.
+
+ @method init
+ */
+ init: function() {
+ self.convertEventPropsToHandlers(dispatches);
+
+ self.bind('RuntimeInit', function(e, runtime) {
+ self.ruid = runtime.uid;
+ self.shimid = runtime.shimid;
+
+ self.bind("Ready", function() {
+ self.trigger("Refresh");
+ }, 999);
+
+ self.bind("Change", function() {
+ var files = runtime.exec.call(self, 'FileInput', 'getFiles');
+
+ self.files = [];
+
+ Basic.each(files, function(file) {
+ // ignore empty files (IE10 for example hangs if you try to send them via XHR)
+ if (file.size === 0) {
+ return true;
+ }
+ self.files.push(new File(self.ruid, file));
+ });
+ }, 999);
+
+ // re-position and resize shim container
+ self.bind('Refresh', function() {
+ var pos, size, browseButton, shimContainer;
+
+ browseButton = Dom.get(options.browse_button);
+ shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist
+
+ if (browseButton) {
+ pos = Dom.getPos(browseButton, Dom.get(options.container));
+ size = Dom.getSize(browseButton);
+
+ if (shimContainer) {
+ Basic.extend(shimContainer.style, {
+ top : pos.y + 'px',
+ left : pos.x + 'px',
+ width : size.w + 'px',
+ height : size.h + 'px'
+ });
+ }
+ }
+ shimContainer = browseButton = null;
+ });
+
+ runtime.exec.call(self, 'FileInput', 'init', options);
+ });
+
+ // runtime needs: options.required_features, options.runtime_order and options.container
+ self.connectRuntime(Basic.extend({}, options, {
+ required_caps: {
+ select_file: true
+ }
+ }));
+ },
+
+ /**
+ Disables file-picker element, so that it doesn't react to mouse clicks.
+
+ @method disable
+ @param {Boolean} [state=true] Disable component if - true, enable if - false
+ */
+ disable: function(state) {
+ var runtime = this.getRuntime();
+ if (runtime) {
+ runtime.exec.call(this, 'FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state);
+ }
+ },
+
+
+ /**
+ Reposition and resize dialog trigger to match the position and size of browse_button element.
+
+ @method refresh
+ */
+ refresh: function() {
+ self.trigger("Refresh");
+ },
+
+
+ /**
+ Destroy component.
+
+ @method destroy
+ */
+ destroy: function() {
+ var runtime = this.getRuntime();
+ if (runtime) {
+ runtime.exec.call(this, 'FileInput', 'destroy');
+ this.disconnectRuntime();
+ }
+
+ if (Basic.typeOf(this.files) === 'array') {
+ // no sense in leaving associated files behind
+ Basic.each(this.files, function(file) {
+ file.destroy();
+ });
+ }
+ this.files = null;
+ }
+ });
+ }
+
+ FileInput.prototype = EventTarget.instance;
+
+ return FileInput;
+});
+
+// Included from: src/javascript/file/FileDrop.js
+
+/**
+ * FileDrop.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/file/FileDrop', [
+ 'moxie/core/I18n',
+ 'moxie/core/utils/Dom',
+ 'moxie/core/Exceptions',
+ 'moxie/core/utils/Basic',
+ 'moxie/file/File',
+ 'moxie/runtime/RuntimeClient',
+ 'moxie/core/EventTarget',
+ 'moxie/core/utils/Mime'
+], function(I18n, Dom, x, Basic, File, RuntimeClient, EventTarget, Mime) {
+ /**
+ Turn arbitrary DOM element to a drop zone accepting files. Converts selected files to _File_ objects, to be used
+ in conjunction with _Image_, preloaded in memory with _FileReader_ or uploaded to a server through
+ _XMLHttpRequest_.
+
+ @example
+ <div id="drop_zone">
+ Drop files here
+ </div>
+ <br />
+ <div id="filelist"></div>
+
+ <script type="text/javascript">
+ var fileDrop = new mOxie.FileDrop('drop_zone'), fileList = mOxie.get('filelist');
+
+ fileDrop.ondrop = function() {
+ mOxie.each(this.files, function(file) {
+ fileList.innerHTML += '<div>' + file.name + '</div>';
+ });
+ };
+
+ fileDrop.init();
+ </script>
+
+ @class FileDrop
+ @constructor
+ @extends EventTarget
+ @uses RuntimeClient
+ @param {Object|String} options If options has typeof string, argument is considered as options.drop_zone
+ @param {String|DOMElement} options.drop_zone DOM Element to turn into a drop zone
+ @param {Array} [options.accept] Array of mime types to accept. By default accepts all
+ @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support
+ */
+ var dispatches = [
+ /**
+ Dispatched when runtime is connected and drop zone is ready to accept files.
+
+ @event ready
+ @param {Object} event
+ */
+ 'ready',
+
+ /**
+ Dispatched when dragging cursor enters the drop zone.
+
+ @event dragenter
+ @param {Object} event
+ */
+ 'dragenter',
+
+ /**
+ Dispatched when dragging cursor leaves the drop zone.
+
+ @event dragleave
+ @param {Object} event
+ */
+ 'dragleave',
+
+ /**
+ Dispatched when file is dropped onto the drop zone.
+
+ @event drop
+ @param {Object} event
+ */
+ 'drop',
+
+ /**
+ Dispatched if error occurs.
+
+ @event error
+ @param {Object} event
+ */
+ 'error'
+ ];
+
+ function FileDrop(options) {
+ var self = this, defaults;
+
+ // if flat argument passed it should be drop_zone id
+ if (typeof(options) === 'string') {
+ options = { drop_zone : options };
+ }
+
+ // figure out the options
+ defaults = {
+ accept: [{
+ title: I18n.translate('All Files'),
+ extensions: '*'
+ }],
+ required_caps: {
+ drag_and_drop: true
+ }
+ };
+
+ options = typeof(options) === 'object' ? Basic.extend({}, defaults, options) : defaults;
+
+ // this will help us to find proper default container
+ options.container = Dom.get(options.drop_zone) || document.body;
+
+ // make container relative, if it is not
+ if (Dom.getStyle(options.container, 'position') === 'static') {
+ options.container.style.position = 'relative';
+ }
+
+ // normalize accept option (could be list of mime types or array of title/extensions pairs)
+ if (typeof(options.accept) === 'string') {
+ options.accept = Mime.mimes2extList(options.accept);
+ }
+
+ RuntimeClient.call(self);
+
+ Basic.extend(self, {
+ uid: Basic.guid('uid_'),
+
+ ruid: null,
+
+ files: null,
+
+ init: function() {
+
+ self.convertEventPropsToHandlers(dispatches);
+
+ self.bind('RuntimeInit', function(e, runtime) {
+ self.ruid = runtime.uid;
+
+ self.bind("Drop", function() {
+ var files = runtime.exec.call(self, 'FileDrop', 'getFiles');
+
+ self.files = [];
+
+ Basic.each(files, function(file) {
+ self.files.push(new File(self.ruid, file));
+ });
+ }, 999);
+
+ runtime.exec.call(self, 'FileDrop', 'init', options);
+
+ self.dispatchEvent('ready');
+ });
+
+ // runtime needs: options.required_features, options.runtime_order and options.container
+ self.connectRuntime(options); // throws RuntimeError
+ },
+
+ destroy: function() {
+ var runtime = this.getRuntime();
+ if (runtime) {
+ runtime.exec.call(this, 'FileDrop', 'destroy');
+ this.disconnectRuntime();
+ }
+ this.files = null;
+ }
+ });
+ }
+
+ FileDrop.prototype = EventTarget.instance;
+
+ return FileDrop;
+});
+
+// Included from: src/javascript/runtime/RuntimeTarget.js
+
+/**
+ * RuntimeTarget.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/runtime/RuntimeTarget', [
+ 'moxie/core/utils/Basic',
+ 'moxie/runtime/RuntimeClient',
+ "moxie/core/EventTarget"
+], function(Basic, RuntimeClient, EventTarget) {
+ /**
+ Instance of this class can be used as a target for the events dispatched by shims,
+ when allowing them onto components is for either reason inappropriate
+
+ @class RuntimeTarget
+ @constructor
+ @protected
+ @extends EventTarget
+ */
+ function RuntimeTarget() {
+ this.uid = Basic.guid('uid_');
+
+ RuntimeClient.call(this);
+
+ this.destroy = function() {
+ this.disconnectRuntime();
+ this.unbindAll();
+ };
+ }
+
+ RuntimeTarget.prototype = EventTarget.instance;
+
+ return RuntimeTarget;
+});
+
+// Included from: src/javascript/file/FileReader.js
+
+/**
+ * FileReader.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/file/FileReader', [
+ 'moxie/core/utils/Basic',
+ 'moxie/core/utils/Encode',
+ 'moxie/core/Exceptions',
+ 'moxie/core/EventTarget',
+ 'moxie/file/Blob',
+ 'moxie/file/File',
+ 'moxie/runtime/RuntimeTarget'
+], function(Basic, Encode, x, EventTarget, Blob, File, RuntimeTarget) {
+ /**
+ Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader)
+ interface. Where possible uses native FileReader, where - not falls back to shims.
+
+ @class FileReader
+ @constructor FileReader
+ @extends EventTarget
+ @uses RuntimeClient
+ */
+ var dispatches = [
+
+ /**
+ Dispatched when the read starts.
+
+ @event loadstart
+ @param {Object} event
+ */
+ 'loadstart',
+
+ /**
+ Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total).
+
+ @event progress
+ @param {Object} event
+ */
+ 'progress',
+
+ /**
+ Dispatched when the read has successfully completed.
+
+ @event load
+ @param {Object} event
+ */
+ 'load',
+
+ /**
+ Dispatched when the read has been aborted. For instance, by invoking the abort() method.
+
+ @event abort
+ @param {Object} event
+ */
+ 'abort',
+
+ /**
+ Dispatched when the read has failed.
+
+ @event error
+ @param {Object} event
+ */
+ 'error',
+
+ /**
+ Dispatched when the request has completed (either in success or failure).
+
+ @event loadend
+ @param {Object} event
+ */
+ 'loadend'
+ ];
+
+ function FileReader() {
+ var self = this, _fr;
+
+ Basic.extend(this, {
+ /**
+ UID of the component instance.
+
+ @property uid
+ @type {String}
+ */
+ uid: Basic.guid('uid_'),
+
+ /**
+ Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING
+ and FileReader.DONE.
+
+ @property readyState
+ @type {Number}
+ @default FileReader.EMPTY
+ */
+ readyState: FileReader.EMPTY,
+
+ /**
+ Result of the successful read operation.
+
+ @property result
+ @type {String}
+ */
+ result: null,
+
+ /**
+ Stores the error of failed asynchronous read operation.
+
+ @property error
+ @type {DOMError}
+ */
+ error: null,
+
+ /**
+ Initiates reading of File/Blob object contents to binary string.
+
+ @method readAsBinaryString
+ @param {Blob|File} blob Object to preload
+ */
+ readAsBinaryString: function(blob) {
+ _read.call(this, 'readAsBinaryString', blob);
+ },
+
+ /**
+ Initiates reading of File/Blob object contents to dataURL string.
+
+ @method readAsDataURL
+ @param {Blob|File} blob Object to preload
+ */
+ readAsDataURL: function(blob) {
+ _read.call(this, 'readAsDataURL', blob);
+ },
+
+ /**
+ Initiates reading of File/Blob object contents to string.
+
+ @method readAsText
+ @param {Blob|File} blob Object to preload
+ */
+ readAsText: function(blob) {
+ _read.call(this, 'readAsText', blob);
+ },
+
+ /**
+ Aborts preloading process.
+
+ @method abort
+ */
+ abort: function() {
+ this.result = null;
+
+ if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) {
+ return;
+ } else if (this.readyState === FileReader.LOADING) {
+ this.readyState = FileReader.DONE;
+ }
+
+ if (_fr) {
+ _fr.getRuntime().exec.call(this, 'FileReader', 'abort');
+ }
+
+ this.trigger('abort');
+ this.trigger('loadend');
+ },
+
+ /**
+ Destroy component and release resources.
+
+ @method destroy
+ */
+ destroy: function() {
+ this.abort();
+
+ if (_fr) {
+ _fr.getRuntime().exec.call(this, 'FileReader', 'destroy');
+ _fr.disconnectRuntime();
+ }
+
+ self = _fr = null;
+ }
+ });
+
+
+ function _read(op, blob) {
+ _fr = new RuntimeTarget();
+
+ function error(err) {
+ self.readyState = FileReader.DONE;
+ self.error = err;
+ self.trigger('error');
+ loadEnd();
+ }
+
+ function loadEnd() {
+ _fr.destroy();
+ _fr = null;
+ self.trigger('loadend');
+ }
+
+ function exec(runtime) {
+ _fr.bind('Error', function(e, err) {
+ error(err);
+ });
+
+ _fr.bind('Progress', function(e) {
+ self.result = runtime.exec.call(_fr, 'FileReader', 'getResult');
+ self.trigger(e);
+ });
+
+ _fr.bind('Load', function(e) {
+ self.readyState = FileReader.DONE;
+ self.result = runtime.exec.call(_fr, 'FileReader', 'getResult');
+ self.trigger(e);
+ loadEnd();
+ });
+
+ runtime.exec.call(_fr, 'FileReader', 'read', op, blob);
+ }
+
+ this.convertEventPropsToHandlers(dispatches);
+
+ if (this.readyState === FileReader.LOADING) {
+ return error(new x.DOMException(x.DOMException.INVALID_STATE_ERR));
+ }
+
+ this.readyState = FileReader.LOADING;
+ this.trigger('loadstart');
+
+ // if source is o.Blob/o.File
+ if (blob instanceof Blob) {
+ if (blob.isDetached()) {
+ var src = blob.getSource();
+ switch (op) {
+ case 'readAsText':
+ case 'readAsBinaryString':
+ this.result = src;
+ break;
+ case 'readAsDataURL':
+ this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src);
+ break;
+ }
+ this.readyState = FileReader.DONE;
+ this.trigger('load');
+ loadEnd();
+ } else {
+ exec(_fr.connectRuntime(blob.ruid));
+ }
+ } else {
+ error(new x.DOMException(x.DOMException.NOT_FOUND_ERR));
+ }
+ }
+ }
+
+ /**
+ Initial FileReader state
+
+ @property EMPTY
+ @type {Number}
+ @final
+ @static
+ @default 0
+ */
+ FileReader.EMPTY = 0;
+
+ /**
+ FileReader switches to this state when it is preloading the source
+
+ @property LOADING
+ @type {Number}
+ @final
+ @static
+ @default 1
+ */
+ FileReader.LOADING = 1;
+
+ /**
+ Preloading is complete, this is a final state
+
+ @property DONE
+ @type {Number}
+ @final
+ @static
+ @default 2
+ */
+ FileReader.DONE = 2;
+
+ FileReader.prototype = EventTarget.instance;
+
+ return FileReader;
+});
+
+// Included from: src/javascript/core/utils/Url.js
+
+/**
+ * Url.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/utils/Url', [], function() {
+ /**
+ Parse url into separate components and fill in absent parts with parts from current url,
+ based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js
+
+ @method parseUrl
+ @for Utils
+ @static
+ @param {String} url Url to parse (defaults to empty string if undefined)
+ @return {Object} Hash containing extracted uri components
+ */
+ var parseUrl = function(url, currentUrl) {
+ var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment']
+ , i = key.length
+ , ports = {
+ http: 80,
+ https: 443
+ }
+ , uri = {}
+ , regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/
+ , m = regex.exec(url || '')
+ ;
+
+ while (i--) {
+ if (m[i]) {
+ uri[key[i]] = m[i];
+ }
+ }
+
+ // when url is relative, we set the origin and the path ourselves
+ if (!uri.scheme) {
+ // come up with defaults
+ if (!currentUrl || typeof(currentUrl) === 'string') {
+ currentUrl = parseUrl(currentUrl || document.location.href);
+ }
+
+ uri.scheme = currentUrl.scheme;
+ uri.host = currentUrl.host;
+ uri.port = currentUrl.port;
+
+ var path = '';
+ // for urls without trailing slash we need to figure out the path
+ if (/^[^\/]/.test(uri.path)) {
+ path = currentUrl.path;
+ // if path ends with a filename, strip it
+ if (!/(\/|\/[^\.]+)$/.test(path)) {
+ path = path.replace(/\/[^\/]+$/, '/');
+ } else {
+ path += '/';
+ }
+ }
+ uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir
+ }
+
+ if (!uri.port) {
+ uri.port = ports[uri.scheme] || 80;
+ }
+
+ uri.port = parseInt(uri.port, 10);
+
+ if (!uri.path) {
+ uri.path = "/";
+ }
+
+ delete uri.source;
+
+ return uri;
+ };
+
+ /**
+ Resolve url - among other things will turn relative url to absolute
+
+ @method resolveUrl
+ @static
+ @param {String} url Either absolute or relative
+ @return {String} Resolved, absolute url
+ */
+ var resolveUrl = function(url) {
+ var ports = { // we ignore default ports
+ http: 80,
+ https: 443
+ }
+ , urlp = parseUrl(url)
+ ;
+
+ return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : '');
+ };
+
+ /**
+ Check if specified url has the same origin as the current document
+
+ @method hasSameOrigin
+ @param {String|Object} url
+ @return {Boolean}
+ */
+ var hasSameOrigin = function(url) {
+ function origin(url) {
+ return [url.scheme, url.host, url.port].join('/');
+ }
+
+ if (typeof url === 'string') {
+ url = parseUrl(url);
+ }
+
+ return origin(parseUrl()) === origin(url);
+ };
+
+ return {
+ parseUrl: parseUrl,
+ resolveUrl: resolveUrl,
+ hasSameOrigin: hasSameOrigin
+ };
+});
+
+// Included from: src/javascript/file/FileReaderSync.js
+
+/**
+ * FileReaderSync.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/file/FileReaderSync', [
+ 'moxie/core/utils/Basic',
+ 'moxie/runtime/RuntimeClient',
+ 'moxie/core/utils/Encode'
+], function(Basic, RuntimeClient, Encode) {
+ /**
+ Synchronous FileReader implementation. Something like this is available in WebWorkers environment, here
+ it can be used to read only preloaded blobs/files and only below certain size (not yet sure what that'd be,
+ but probably < 1mb). Not meant to be used directly by user.
+
+ @class FileReaderSync
+ @private
+ @constructor
+ */
+ return function() {
+ RuntimeClient.call(this);
+
+ Basic.extend(this, {
+ uid: Basic.guid('uid_'),
+
+ readAsBinaryString: function(blob) {
+ return _read.call(this, 'readAsBinaryString', blob);
+ },
+
+ readAsDataURL: function(blob) {
+ return _read.call(this, 'readAsDataURL', blob);
+ },
+
+ /*readAsArrayBuffer: function(blob) {
+ return _read.call(this, 'readAsArrayBuffer', blob);
+ },*/
+
+ readAsText: function(blob) {
+ return _read.call(this, 'readAsText', blob);
+ }
+ });
+
+ function _read(op, blob) {
+ if (blob.isDetached()) {
+ var src = blob.getSource();
+ switch (op) {
+ case 'readAsBinaryString':
+ return src;
+ case 'readAsDataURL':
+ return 'data:' + blob.type + ';base64,' + Encode.btoa(src);
+ case 'readAsText':
+ var txt = '';
+ for (var i = 0, length = src.length; i < length; i++) {
+ txt += String.fromCharCode(src[i]);
+ }
+ return txt;
+ }
+ } else {
+ var result = this.connectRuntime(blob.ruid).exec.call(this, 'FileReaderSync', 'read', op, blob);
+ this.disconnectRuntime();
+ return result;
+ }
+ }
+ };
+});
+
+// Included from: src/javascript/xhr/FormData.js
+
+/**
+ * FormData.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/xhr/FormData", [
+ "moxie/core/Exceptions",
+ "moxie/core/utils/Basic",
+ "moxie/file/Blob"
+], function(x, Basic, Blob) {
+ /**
+ FormData
+
+ @class FormData
+ @constructor
+ */
+ function FormData() {
+ var _blob, _fields = [];
+
+ Basic.extend(this, {
+ /**
+ Append another key-value pair to the FormData object
+
+ @method append
+ @param {String} name Name for the new field
+ @param {String|Blob|Array|Object} value Value for the field
+ */
+ append: function(name, value) {
+ var self = this, valueType = Basic.typeOf(value);
+
+ // according to specs value might be either Blob or String
+ if (value instanceof Blob) {
+ _blob = {
+ name: name,
+ value: value // unfortunately we can only send single Blob in one FormData
+ };
+ } else if ('array' === valueType) {
+ name += '[]';
+
+ Basic.each(value, function(value) {
+ self.append(name, value);
+ });
+ } else if ('object' === valueType) {
+ Basic.each(value, function(value, key) {
+ self.append(name + '[' + key + ']', value);
+ });
+ } else if ('null' === valueType || 'undefined' === valueType || 'number' === valueType && isNaN(value)) {
+ self.append(name, "false");
+ } else {
+ _fields.push({
+ name: name,
+ value: value.toString()
+ });
+ }
+ },
+
+ /**
+ Checks if FormData contains Blob.
+
+ @method hasBlob
+ @return {Boolean}
+ */
+ hasBlob: function() {
+ return !!this.getBlob();
+ },
+
+ /**
+ Retrieves blob.
+
+ @method getBlob
+ @return {Object} Either Blob if found or null
+ */
+ getBlob: function() {
+ return _blob && _blob.value || null;
+ },
+
+ /**
+ Retrieves blob field name.
+
+ @method getBlobName
+ @return {String} Either Blob field name or null
+ */
+ getBlobName: function() {
+ return _blob && _blob.name || null;
+ },
+
+ /**
+ Loop over the fields in FormData and invoke the callback for each of them.
+
+ @method each
+ @param {Function} cb Callback to call for each field
+ */
+ each: function(cb) {
+ Basic.each(_fields, function(field) {
+ cb(field.value, field.name);
+ });
+
+ if (_blob) {
+ cb(_blob.value, _blob.name);
+ }
+ },
+
+ destroy: function() {
+ _blob = null;
+ _fields = [];
+ }
+ });
+ }
+
+ return FormData;
+});
+
+// Included from: src/javascript/xhr/XMLHttpRequest.js
+
+/**
+ * XMLHttpRequest.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/xhr/XMLHttpRequest", [
+ "moxie/core/utils/Basic",
+ "moxie/core/Exceptions",
+ "moxie/core/EventTarget",
+ "moxie/core/utils/Encode",
+ "moxie/core/utils/Url",
+ "moxie/runtime/Runtime",
+ "moxie/runtime/RuntimeTarget",
+ "moxie/file/Blob",
+ "moxie/file/FileReaderSync",
+ "moxie/xhr/FormData",
+ "moxie/core/utils/Env",
+ "moxie/core/utils/Mime"
+], function(Basic, x, EventTarget, Encode, Url, Runtime, RuntimeTarget, Blob, FileReaderSync, FormData, Env, Mime) {
+
+ var httpCode = {
+ 100: 'Continue',
+ 101: 'Switching Protocols',
+ 102: 'Processing',
+
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 207: 'Multi-Status',
+ 226: 'IM Used',
+
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 306: 'Reserved',
+ 307: 'Temporary Redirect',
+
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Failed',
+ 413: 'Request Entity Too Large',
+ 414: 'Request-URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Requested Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 422: 'Unprocessable Entity',
+ 423: 'Locked',
+ 424: 'Failed Dependency',
+ 426: 'Upgrade Required',
+
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported',
+ 506: 'Variant Also Negotiates',
+ 507: 'Insufficient Storage',
+ 510: 'Not Extended'
+ };
+
+ function XMLHttpRequestUpload() {
+ this.uid = Basic.guid('uid_');
+ }
+
+ XMLHttpRequestUpload.prototype = EventTarget.instance;
+
+ /**
+ Implementation of XMLHttpRequest
+
+ @class XMLHttpRequest
+ @constructor
+ @uses RuntimeClient
+ @extends EventTarget
+ */
+ var dispatches = ['loadstart', 'progress', 'abort', 'error', 'load', 'timeout', 'loadend']; // & readystatechange (for historical reasons)
+
+ var NATIVE = 1, RUNTIME = 2;
+
+ function XMLHttpRequest() {
+ var self = this,
+ // this (together with _p() @see below) is here to gracefully upgrade to setter/getter syntax where possible
+ props = {
+ /**
+ The amount of milliseconds a request can take before being terminated. Initially zero. Zero means there is no timeout.
+
+ @property timeout
+ @type Number
+ @default 0
+ */
+ timeout: 0,
+
+ /**
+ Current state, can take following values:
+ UNSENT (numeric value 0)
+ The object has been constructed.
+
+ OPENED (numeric value 1)
+ The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the request can be made using the send() method.
+
+ HEADERS_RECEIVED (numeric value 2)
+ All redirects (if any) have been followed and all HTTP headers of the final response have been received. Several response members of the object are now available.
+
+ LOADING (numeric value 3)
+ The response entity body is being received.
+
+ DONE (numeric value 4)
+
+ @property readyState
+ @type Number
+ @default 0 (UNSENT)
+ */
+ readyState: XMLHttpRequest.UNSENT,
+
+ /**
+ True when user credentials are to be included in a cross-origin request. False when they are to be excluded
+ in a cross-origin request and when cookies are to be ignored in its response. Initially false.
+
+ @property withCredentials
+ @type Boolean
+ @default false
+ */
+ withCredentials: false,
+
+ /**
+ Returns the HTTP status code.
+
+ @property status
+ @type Number
+ @default 0
+ */
+ status: 0,
+
+ /**
+ Returns the HTTP status text.
+
+ @property statusText
+ @type String
+ */
+ statusText: "",
+
+ /**
+ Returns the response type. Can be set to change the response type. Values are:
+ the empty string (default), "arraybuffer", "blob", "document", "json", and "text".
+
+ @property responseType
+ @type String
+ */
+ responseType: "",
+
+ /**
+ Returns the document response entity body.
+
+ Throws an "InvalidStateError" exception if responseType is not the empty string or "document".
+
+ @property responseXML
+ @type Document
+ */
+ responseXML: null,
+
+ /**
+ Returns the text response entity body.
+
+ Throws an "InvalidStateError" exception if responseType is not the empty string or "text".
+
+ @property responseText
+ @type String
+ */
+ responseText: null,
+
+ /**
+ Returns the response entity body (http://www.w3.org/TR/XMLHttpRequest/#response-entity-body).
+ Can become: ArrayBuffer, Blob, Document, JSON, Text
+
+ @property response
+ @type Mixed
+ */
+ response: null
+ },
+
+ _async = true,
+ _url,
+ _method,
+ _headers = {},
+ _user,
+ _password,
+ _encoding = null,
+ _mimeType = null,
+
+ // flags
+ _sync_flag = false,
+ _send_flag = false,
+ _upload_events_flag = false,
+ _upload_complete_flag = false,
+ _error_flag = false,
+ _same_origin_flag = false,
+
+ // times
+ _start_time,
+ _timeoutset_time,
+
+ _finalMime = null,
+ _finalCharset = null,
+
+ _options = {},
+ _xhr,
+ _responseHeaders = '',
+ _responseHeadersBag
+ ;
+
+
+ Basic.extend(this, props, {
+ /**
+ Unique id of the component
+
+ @property uid
+ @type String
+ */
+ uid: Basic.guid('uid_'),
+
+ /**
+ Target for Upload events
+
+ @property upload
+ @type XMLHttpRequestUpload
+ */
+ upload: new XMLHttpRequestUpload(),
+
+
+ /**
+ Sets the request method, request URL, synchronous flag, request username, and request password.
+
+ Throws a "SyntaxError" exception if one of the following is true:
+
+ method is not a valid HTTP method.
+ url cannot be resolved.
+ url contains the "user:password" format in the userinfo production.
+ Throws a "SecurityError" exception if method is a case-insensitive match for CONNECT, TRACE or TRACK.
+
+ Throws an "InvalidAccessError" exception if one of the following is true:
+
+ Either user or password is passed as argument and the origin of url does not match the XMLHttpRequest origin.
+ There is an associated XMLHttpRequest document and either the timeout attribute is not zero,
+ the withCredentials attribute is true, or the responseType attribute is not the empty string.
+
+
+ @method open
+ @param {String} method HTTP method to use on request
+ @param {String} url URL to request
+ @param {Boolean} [async=true] If false request will be done in synchronous manner. Asynchronous by default.
+ @param {String} [user] Username to use in HTTP authentication process on server-side
+ @param {String} [password] Password to use in HTTP authentication process on server-side
+ */
+ open: function(method, url, async, user, password) {
+ var urlp;
+
+ // first two arguments are required
+ if (!method || !url) {
+ throw new x.DOMException(x.DOMException.SYNTAX_ERR);
+ }
+
+ // 2 - check if any code point in method is higher than U+00FF or after deflating method it does not match the method
+ if (/[\u0100-\uffff]/.test(method) || Encode.utf8_encode(method) !== method) {
+ throw new x.DOMException(x.DOMException.SYNTAX_ERR);
+ }
+
+ // 3
+ if (!!~Basic.inArray(method.toUpperCase(), ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'TRACE', 'TRACK'])) {
+ _method = method.toUpperCase();
+ }
+
+
+ // 4 - allowing these methods poses a security risk
+ if (!!~Basic.inArray(_method, ['CONNECT', 'TRACE', 'TRACK'])) {
+ throw new x.DOMException(x.DOMException.SECURITY_ERR);
+ }
+
+ // 5
+ url = Encode.utf8_encode(url);
+
+ // 6 - Resolve url relative to the XMLHttpRequest base URL. If the algorithm returns an error, throw a "SyntaxError".
+ urlp = Url.parseUrl(url);
+
+ _same_origin_flag = Url.hasSameOrigin(urlp);
+
+ // 7 - manually build up absolute url
+ _url = Url.resolveUrl(url);
+
+ // 9-10, 12-13
+ if ((user || password) && !_same_origin_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
+ }
+
+ _user = user || urlp.user;
+ _password = password || urlp.pass;
+
+ // 11
+ _async = async || true;
+
+ if (_async === false && (_p('timeout') || _p('withCredentials') || _p('responseType') !== "")) {
+ throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
+ }
+
+ // 14 - terminate abort()
+
+ // 15 - terminate send()
+
+ // 18
+ _sync_flag = !_async;
+ _send_flag = false;
+ _headers = {};
+ _reset.call(this);
+
+ // 19
+ _p('readyState', XMLHttpRequest.OPENED);
+
+ // 20
+ this.convertEventPropsToHandlers(['readystatechange']); // unify event handlers
+ this.dispatchEvent('readystatechange');
+ },
+
+ /**
+ Appends an header to the list of author request headers, or if header is already
+ in the list of author request headers, combines its value with value.
+
+ Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set.
+ Throws a "SyntaxError" exception if header is not a valid HTTP header field name or if value
+ is not a valid HTTP header field value.
+
+ @method setRequestHeader
+ @param {String} header
+ @param {String|Number} value
+ */
+ setRequestHeader: function(header, value) {
+ var uaHeaders = [ // these headers are controlled by the user agent
+ "accept-charset",
+ "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection",
+ "content-length",
+ "cookie",
+ "cookie2",
+ "content-transfer-encoding",
+ "date",
+ "expect",
+ "host",
+ "keep-alive",
+ "origin",
+ "referer",
+ "te",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "user-agent",
+ "via"
+ ];
+
+ // 1-2
+ if (_p('readyState') !== XMLHttpRequest.OPENED || _send_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 3
+ if (/[\u0100-\uffff]/.test(header) || Encode.utf8_encode(header) !== header) {
+ throw new x.DOMException(x.DOMException.SYNTAX_ERR);
+ }
+
+ // 4
+ /* this step is seemingly bypassed in browsers, probably to allow various unicode characters in header values
+ if (/[\u0100-\uffff]/.test(value) || Encode.utf8_encode(value) !== value) {
+ throw new x.DOMException(x.DOMException.SYNTAX_ERR);
+ }*/
+
+ header = Basic.trim(header).toLowerCase();
+
+ // setting of proxy-* and sec-* headers is prohibited by spec
+ if (!!~Basic.inArray(header, uaHeaders) || /^(proxy\-|sec\-)/.test(header)) {
+ return false;
+ }
+
+ // camelize
+ // browsers lowercase header names (at least for custom ones)
+ // header = header.replace(/\b\w/g, function($1) { return $1.toUpperCase(); });
+
+ if (!_headers[header]) {
+ _headers[header] = value;
+ } else {
+ // http://tools.ietf.org/html/rfc2616#section-4.2 (last paragraph)
+ _headers[header] += ', ' + value;
+ }
+ return true;
+ },
+
+ /**
+ Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2.
+
+ @method getAllResponseHeaders
+ @return {String} reponse headers or empty string
+ */
+ getAllResponseHeaders: function() {
+ return _responseHeaders || '';
+ },
+
+ /**
+ Returns the header field value from the response of which the field name matches header,
+ unless the field name is Set-Cookie or Set-Cookie2.
+
+ @method getResponseHeader
+ @param {String} header
+ @return {String} value(s) for the specified header or null
+ */
+ getResponseHeader: function(header) {
+ header = header.toLowerCase();
+
+ if (_error_flag || !!~Basic.inArray(header, ['set-cookie', 'set-cookie2'])) {
+ return null;
+ }
+
+ if (_responseHeaders && _responseHeaders !== '') {
+ // if we didn't parse response headers until now, do it and keep for later
+ if (!_responseHeadersBag) {
+ _responseHeadersBag = {};
+ Basic.each(_responseHeaders.split(/\r\n/), function(line) {
+ var pair = line.split(/:\s+/);
+ if (pair.length === 2) { // last line might be empty, omit
+ pair[0] = Basic.trim(pair[0]); // just in case
+ _responseHeadersBag[pair[0].toLowerCase()] = { // simply to retain header name in original form
+ header: pair[0],
+ value: Basic.trim(pair[1])
+ };
+ }
+ });
+ }
+ if (_responseHeadersBag.hasOwnProperty(header)) {
+ return _responseHeadersBag[header].header + ': ' + _responseHeadersBag[header].value;
+ }
+ }
+ return null;
+ },
+
+ /**
+ Sets the Content-Type header for the response to mime.
+ Throws an "InvalidStateError" exception if the state is LOADING or DONE.
+ Throws a "SyntaxError" exception if mime is not a valid media type.
+
+ @method overrideMimeType
+ @param String mime Mime type to set
+ */
+ overrideMimeType: function(mime) {
+ var matches, charset;
+
+ // 1
+ if (!!~Basic.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 2
+ mime = Basic.trim(mime.toLowerCase());
+
+ if (/;/.test(mime) && (matches = mime.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))) {
+ mime = matches[1];
+ if (matches[2]) {
+ charset = matches[2];
+ }
+ }
+
+ if (!Mime.mimes[mime]) {
+ throw new x.DOMException(x.DOMException.SYNTAX_ERR);
+ }
+
+ // 3-4
+ _finalMime = mime;
+ _finalCharset = charset;
+ },
+
+ /**
+ Initiates the request. The optional argument provides the request entity body.
+ The argument is ignored if request method is GET or HEAD.
+
+ Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set.
+
+ @method send
+ @param {Blob|Document|String|FormData} [data] Request entity body
+ @param {Object} [options] Set of requirements and pre-requisities for runtime initialization
+ */
+ send: function(data, options) {
+ if (Basic.typeOf(options) === 'string') {
+ _options = { ruid: options };
+ } else if (!options) {
+ _options = {};
+ } else {
+ _options = options;
+ }
+
+ this.convertEventPropsToHandlers(dispatches);
+ this.upload.convertEventPropsToHandlers(dispatches);
+
+ // 1-2
+ if (this.readyState !== XMLHttpRequest.OPENED || _send_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 3
+ // sending Blob
+ if (data instanceof Blob) {
+ _options.ruid = data.ruid;
+ _mimeType = data.type || 'application/octet-stream';
+ }
+
+ // FormData
+ else if (data instanceof FormData) {
+ if (data.hasBlob()) {
+ var blob = data.getBlob();
+ _options.ruid = blob.ruid;
+ _mimeType = blob.type || 'application/octet-stream';
+ }
+ }
+
+ // DOMString
+ else if (typeof data === 'string') {
+ _encoding = 'UTF-8';
+ _mimeType = 'text/plain;charset=UTF-8';
+
+ // data should be converted to Unicode and encoded as UTF-8
+ data = Encode.utf8_encode(data);
+ }
+
+ // if withCredentials not set, but requested, set it automatically
+ if (!this.withCredentials) {
+ this.withCredentials = (_options.required_caps && _options.required_caps.send_browser_cookies) && !_same_origin_flag;
+ }
+
+ // 4 - storage mutex
+ // 5
+ _upload_events_flag = (!_sync_flag && this.upload.hasEventListener()); // DSAP
+ // 6
+ _error_flag = false;
+ // 7
+ _upload_complete_flag = !data;
+ // 8 - Asynchronous steps
+ if (!_sync_flag) {
+ // 8.1
+ _send_flag = true;
+ // 8.2
+ // this.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
+ // 8.3
+ if (!_upload_complete_flag) {
+ // this.upload.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
+ }
+ }
+ // 8.5 - Return the send() method call, but continue running the steps in this algorithm.
+ _doXHR.call(this, data);
+ },
+
+ /**
+ Cancels any network activity.
+
+ @method abort
+ */
+ abort: function() {
+ _error_flag = true;
+ _sync_flag = false;
+
+ if (!~Basic.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED, XMLHttpRequest.DONE])) {
+ _p('readyState', XMLHttpRequest.DONE);
+ _send_flag = false;
+
+ if (_xhr) {
+ _xhr.getRuntime().exec.call(_xhr, 'XMLHttpRequest', 'abort', _upload_complete_flag);
+ } else {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ _upload_complete_flag = true;
+ } else {
+ _p('readyState', XMLHttpRequest.UNSENT);
+ }
+ },
+
+ destroy: function() {
+ if (_xhr) {
+ if (Basic.typeOf(_xhr.destroy) === 'function') {
+ _xhr.destroy();
+ }
+ _xhr = null;
+ }
+
+ this.unbindAll();
+
+ if (this.upload) {
+ this.upload.unbindAll();
+ this.upload = null;
+ }
+ }
+ });
+
+ /* this is nice, but maybe too lengthy
+
+ // if supported by JS version, set getters/setters for specific properties
+ o.defineProperty(this, 'readyState', {
+ configurable: false,
+
+ get: function() {
+ return _p('readyState');
+ }
+ });
+
+ o.defineProperty(this, 'timeout', {
+ configurable: false,
+
+ get: function() {
+ return _p('timeout');
+ },
+
+ set: function(value) {
+
+ if (_sync_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
+ }
+
+ // timeout still should be measured relative to the start time of request
+ _timeoutset_time = (new Date).getTime();
+
+ _p('timeout', value);
+ }
+ });
+
+ // the withCredentials attribute has no effect when fetching same-origin resources
+ o.defineProperty(this, 'withCredentials', {
+ configurable: false,
+
+ get: function() {
+ return _p('withCredentials');
+ },
+
+ set: function(value) {
+ // 1-2
+ if (!~o.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED]) || _send_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 3-4
+ if (_anonymous_flag || _sync_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
+ }
+
+ // 5
+ _p('withCredentials', value);
+ }
+ });
+
+ o.defineProperty(this, 'status', {
+ configurable: false,
+
+ get: function() {
+ return _p('status');
+ }
+ });
+
+ o.defineProperty(this, 'statusText', {
+ configurable: false,
+
+ get: function() {
+ return _p('statusText');
+ }
+ });
+
+ o.defineProperty(this, 'responseType', {
+ configurable: false,
+
+ get: function() {
+ return _p('responseType');
+ },
+
+ set: function(value) {
+ // 1
+ if (!!~o.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 2
+ if (_sync_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
+ }
+
+ // 3
+ _p('responseType', value.toLowerCase());
+ }
+ });
+
+ o.defineProperty(this, 'responseText', {
+ configurable: false,
+
+ get: function() {
+ // 1
+ if (!~o.inArray(_p('responseType'), ['', 'text'])) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 2-3
+ if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ return _p('responseText');
+ }
+ });
+
+ o.defineProperty(this, 'responseXML', {
+ configurable: false,
+
+ get: function() {
+ // 1
+ if (!~o.inArray(_p('responseType'), ['', 'document'])) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // 2-3
+ if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ return _p('responseXML');
+ }
+ });
+
+ o.defineProperty(this, 'response', {
+ configurable: false,
+
+ get: function() {
+ if (!!~o.inArray(_p('responseType'), ['', 'text'])) {
+ if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) {
+ return '';
+ }
+ }
+
+ if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) {
+ return null;
+ }
+
+ return _p('response');
+ }
+ });
+
+ */
+
+ function _p(prop, value) {
+ if (!props.hasOwnProperty(prop)) {
+ return;
+ }
+ if (arguments.length === 1) { // get
+ return Env.can('define_property') ? props[prop] : self[prop];
+ } else { // set
+ if (Env.can('define_property')) {
+ props[prop] = value;
+ } else {
+ self[prop] = value;
+ }
+ }
+ }
+
+ /*
+ function _toASCII(str, AllowUnassigned, UseSTD3ASCIIRules) {
+ // TODO: http://tools.ietf.org/html/rfc3490#section-4.1
+ return str.toLowerCase();
+ }
+ */
+
+
+ function _doXHR(data) {
+ var self = this;
+
+ _start_time = new Date().getTime();
+
+ _xhr = new RuntimeTarget();
+
+ function loadEnd() {
+ _xhr.destroy();
+ _xhr = null;
+ self.dispatchEvent('loadend');
+ self = null;
+ }
+
+ function exec(runtime) {
+ _xhr.bind('LoadStart', function(e) {
+ _p('readyState', XMLHttpRequest.LOADING);
+ self.dispatchEvent('readystatechange');
+
+ self.dispatchEvent(e);
+
+ if (_upload_events_flag) {
+ self.upload.dispatchEvent(e);
+ }
+ });
+
+ _xhr.bind('Progress', function(e) {
+ if (_p('readyState') !== XMLHttpRequest.LOADING) {
+ _p('readyState', XMLHttpRequest.LOADING); // LoadStart unreliable (in Flash for example)
+ self.dispatchEvent('readystatechange');
+ }
+ self.dispatchEvent(e);
+ });
+
+ _xhr.bind('UploadProgress', function(e) {
+ if (_upload_events_flag) {
+ self.upload.dispatchEvent({
+ type: 'progress',
+ lengthComputable: false,
+ total: e.total,
+ loaded: e.loaded
+ });
+ }
+ });
+
+ _xhr.bind('Load', function(e) {
+ _p('readyState', XMLHttpRequest.DONE);
+ _p('status', Number(runtime.exec.call(_xhr, 'XMLHttpRequest', 'getStatus') || 0));
+ _p('statusText', httpCode[_p('status')] || "");
+
+ _p('response', runtime.exec.call(_xhr, 'XMLHttpRequest', 'getResponse', _p('responseType')));
+
+ if (!!~Basic.inArray(_p('responseType'), ['text', ''])) {
+ _p('responseText', _p('response'));
+ } else if (_p('responseType') === 'document') {
+ _p('responseXML', _p('response'));
+ }
+
+ _responseHeaders = runtime.exec.call(_xhr, 'XMLHttpRequest', 'getAllResponseHeaders');
+
+ self.dispatchEvent('readystatechange');
+
+ if (_p('status') > 0) { // status 0 usually means that server is unreachable
+ if (_upload_events_flag) {
+ self.upload.dispatchEvent(e);
+ }
+ self.dispatchEvent(e);
+ } else {
+ _error_flag = true;
+ self.dispatchEvent('error');
+ }
+ loadEnd();
+ });
+
+ _xhr.bind('Abort', function(e) {
+ self.dispatchEvent(e);
+ loadEnd();
+ });
+
+ _xhr.bind('Error', function(e) {
+ _error_flag = true;
+ _p('readyState', XMLHttpRequest.DONE);
+ self.dispatchEvent('readystatechange');
+ _upload_complete_flag = true;
+ self.dispatchEvent(e);
+ loadEnd();
+ });
+
+ runtime.exec.call(_xhr, 'XMLHttpRequest', 'send', {
+ url: _url,
+ method: _method,
+ async: _async,
+ user: _user,
+ password: _password,
+ headers: _headers,
+ mimeType: _mimeType,
+ encoding: _encoding,
+ responseType: self.responseType,
+ withCredentials: self.withCredentials,
+ options: _options
+ }, data);
+ }
+
+ // clarify our requirements
+ if (typeof(_options.required_caps) === 'string') {
+ _options.required_caps = Runtime.parseCaps(_options.required_caps);
+ }
+
+ _options.required_caps = Basic.extend({}, _options.required_caps, {
+ return_response_type: self.responseType
+ });
+
+ if (data instanceof FormData) {
+ _options.required_caps.send_multipart = true;
+ }
+
+ if (!_same_origin_flag) {
+ _options.required_caps.do_cors = true;
+ }
+
+
+ if (_options.ruid) { // we do not need to wait if we can connect directly
+ exec(_xhr.connectRuntime(_options));
+ } else {
+ _xhr.bind('RuntimeInit', function(e, runtime) {
+ exec(runtime);
+ });
+ _xhr.bind('RuntimeError', function(e, err) {
+ self.dispatchEvent('RuntimeError', err);
+ });
+ _xhr.connectRuntime(_options);
+ }
+ }
+
+
+ function _reset() {
+ _p('responseText', "");
+ _p('responseXML', null);
+ _p('response', null);
+ _p('status', 0);
+ _p('statusText', "");
+ _start_time = _timeoutset_time = null;
+ }
+ }
+
+ XMLHttpRequest.UNSENT = 0;
+ XMLHttpRequest.OPENED = 1;
+ XMLHttpRequest.HEADERS_RECEIVED = 2;
+ XMLHttpRequest.LOADING = 3;
+ XMLHttpRequest.DONE = 4;
+
+ XMLHttpRequest.prototype = EventTarget.instance;
+
+ return XMLHttpRequest;
+});
+
+// Included from: src/javascript/runtime/Transporter.js
+
+/**
+ * Transporter.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/runtime/Transporter", [
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Encode",
+ "moxie/runtime/RuntimeClient",
+ "moxie/core/EventTarget"
+], function(Basic, Encode, RuntimeClient, EventTarget) {
+ function Transporter() {
+ var mod, _runtime, _data, _size, _pos, _chunk_size;
+
+ RuntimeClient.call(this);
+
+ Basic.extend(this, {
+ uid: Basic.guid('uid_'),
+
+ state: Transporter.IDLE,
+
+ result: null,
+
+ transport: function(data, type, options) {
+ var self = this;
+
+ options = Basic.extend({
+ chunk_size: 204798
+ }, options);
+
+ // should divide by three, base64 requires this
+ if ((mod = options.chunk_size % 3)) {
+ options.chunk_size += 3 - mod;
+ }
+
+ _chunk_size = options.chunk_size;
+
+ _reset.call(this);
+ _data = data;
+ _size = data.length;
+
+ if (Basic.typeOf(options) === 'string' || options.ruid) {
+ _run.call(self, type, this.connectRuntime(options));
+ } else {
+ // we require this to run only once
+ var cb = function(e, runtime) {
+ self.unbind("RuntimeInit", cb);
+ _run.call(self, type, runtime);
+ };
+ this.bind("RuntimeInit", cb);
+ this.connectRuntime(options);
+ }
+ },
+
+ abort: function() {
+ var self = this;
+
+ self.state = Transporter.IDLE;
+ if (_runtime) {
+ _runtime.exec.call(self, 'Transporter', 'clear');
+ self.trigger("TransportingAborted");
+ }
+
+ _reset.call(self);
+ },
+
+
+ destroy: function() {
+ this.unbindAll();
+ _runtime = null;
+ this.disconnectRuntime();
+ _reset.call(this);
+ }
+ });
+
+ function _reset() {
+ _size = _pos = 0;
+ _data = this.result = null;
+ }
+
+ function _run(type, runtime) {
+ var self = this;
+
+ _runtime = runtime;
+
+ //self.unbind("RuntimeInit");
+
+ self.bind("TransportingProgress", function(e) {
+ _pos = e.loaded;
+
+ if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) {
+ _transport.call(self);
+ }
+ }, 999);
+
+ self.bind("TransportingComplete", function() {
+ _pos = _size;
+ self.state = Transporter.DONE;
+ _data = null; // clean a bit
+ self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || '');
+ }, 999);
+
+ self.state = Transporter.BUSY;
+ self.trigger("TransportingStarted");
+ _transport.call(self);
+ }
+
+ function _transport() {
+ var self = this,
+ chunk,
+ bytesLeft = _size - _pos;
+
+ if (_chunk_size > bytesLeft) {
+ _chunk_size = bytesLeft;
+ }
+
+ chunk = Encode.btoa(_data.substr(_pos, _chunk_size));
+ _runtime.exec.call(self, 'Transporter', 'receive', chunk, _size);
+ }
+ }
+
+ Transporter.IDLE = 0;
+ Transporter.BUSY = 1;
+ Transporter.DONE = 2;
+
+ Transporter.prototype = EventTarget.instance;
+
+ return Transporter;
+});
+
+// Included from: src/javascript/image/Image.js
+
+/**
+ * Image.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define("moxie/image/Image", [
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Dom",
+ "moxie/core/Exceptions",
+ "moxie/file/FileReaderSync",
+ "moxie/xhr/XMLHttpRequest",
+ "moxie/runtime/Runtime",
+ "moxie/runtime/RuntimeClient",
+ "moxie/runtime/Transporter",
+ "moxie/core/utils/Env",
+ "moxie/core/EventTarget",
+ "moxie/file/Blob",
+ "moxie/file/File",
+ "moxie/core/utils/Encode"
+], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) {
+ /**
+ Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data.
+
+ @class Image
+ @constructor
+ @extends EventTarget
+ */
+ var dispatches = [
+ 'progress',
+
+ /**
+ Dispatched when loading is complete.
+
+ @event load
+ @param {Object} event
+ */
+ 'load',
+
+ 'error',
+
+ /**
+ Dispatched when resize operation is complete.
+
+ @event resize
+ @param {Object} event
+ */
+ 'resize',
+
+ /**
+ Dispatched when visual representation of the image is successfully embedded
+ into the corresponsing container.
+
+ @event embedded
+ @param {Object} event
+ */
+ 'embedded'
+ ];
+
+ function Image() {
+ RuntimeClient.call(this);
+
+ Basic.extend(this, {
+ /**
+ Unique id of the component
+
+ @property uid
+ @type {String}
+ */
+ uid: Basic.guid('uid_'),
+
+ /**
+ Unique id of the connected runtime, if any.
+
+ @property ruid
+ @type {String}
+ */
+ ruid: null,
+
+ /**
+ Name of the file, that was used to create an image, if available. If not equals to empty string.
+
+ @property name
+ @type {String}
+ @default ""
+ */
+ name: "",
+
+ /**
+ Size of the image in bytes. Actual value is set only after image is preloaded.
+
+ @property size
+ @type {Number}
+ @default 0
+ */
+ size: 0,
+
+ /**
+ Width of the image. Actual value is set only after image is preloaded.
+
+ @property width
+ @type {Number}
+ @default 0
+ */
+ width: 0,
+
+ /**
+ Height of the image. Actual value is set only after image is preloaded.
+
+ @property height
+ @type {Number}
+ @default 0
+ */
+ height: 0,
+
+ /**
+ Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded.
+
+ @property type
+ @type {String}
+ @default ""
+ */
+ type: "",
+
+ /**
+ Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded.
+
+ @property meta
+ @type {Object}
+ @default {}
+ */
+ meta: {},
+
+ /**
+ Alias for load method, that takes another mOxie.Image object as a source (see load).
+
+ @method clone
+ @param {Image} src Source for the image
+ @param {Boolean} [exact=false] Whether to activate in-depth clone mode
+ */
+ clone: function() {
+ this.load.apply(this, arguments);
+ },
+
+ /**
+ Loads image from various sources. Currently the source for new image can be: mOxie.Image, mOxie.Blob/mOxie.File,
+ native Blob/File, dataUrl or URL. Depending on the type of the source, arguments - differ. When source is URL,
+ Image will be downloaded from remote destination and loaded in memory.
+
+ @example
+ var img = new mOxie.Image();
+ img.onload = function() {
+ var blob = img.getAsBlob();
+
+ var formData = new mOxie.FormData();
+ formData.append('file', blob);
+
+ var xhr = new mOxie.XMLHttpRequest();
+ xhr.onload = function() {
+ // upload complete
+ };
+ xhr.open('post', 'upload.php');
+ xhr.send(formData);
+ };
+ img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg)
+
+
+ @method load
+ @param {Image|Blob|File|String} src Source for the image
+ @param {Boolean|Object} [mixed]
+ */
+ load: function() {
+ // this is here because to bind properly we need an uid first, which is created above
+ this.bind('Load Resize', function() {
+ _updateInfo.call(this);
+ }, 999);
+
+ this.convertEventPropsToHandlers(dispatches);
+
+ _load.apply(this, arguments);
+ },
+
+ /**
+ Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions.
+
+ @method downsize
+ @param {Number} width Resulting width
+ @param {Number} [height=width] Resulting height (optional, if not supplied will default to width)
+ @param {Boolean} [crop=false] Whether to crop the image to exact dimensions
+ @param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
+ */
+ downsize: function(width, height, crop, preserveHeaders) {
+ try {
+ if (!this.size) { // only preloaded image objects can be used as source
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ // no way to reliably intercept the crash due to high resolution, so we simply avoid it
+ if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
+ throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
+ }
+
+ if (!width && !height || Basic.typeOf(crop) === 'undefined') {
+ crop = false;
+ }
+
+ width = width || this.width;
+ height = height || this.height;
+
+ preserveHeaders = (Basic.typeOf(preserveHeaders) === 'undefined' ? true : !!preserveHeaders);
+
+ this.getRuntime().exec.call(this, 'Image', 'downsize', width, height, crop, preserveHeaders);
+ } catch(ex) {
+ // for now simply trigger error event
+ this.trigger('error', ex);
+ }
+ },
+
+ /**
+ Alias for downsize(width, height, true). (see downsize)
+
+ @method crop
+ @param {Number} width Resulting width
+ @param {Number} [height=width] Resulting height (optional, if not supplied will default to width)
+ @param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
+ */
+ crop: function(width, height, preserveHeaders) {
+ this.downsize(width, height, true, preserveHeaders);
+ },
+
+ getAsCanvas: function() {
+ if (!Env.can('create_canvas')) {
+ throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
+ }
+
+ var runtime = this.connectRuntime(this.ruid);
+ return runtime.exec.call(this, 'Image', 'getAsCanvas');
+ },
+
+ /**
+ Retrieves image in it's current state as mOxie.Blob object. Cannot be run on empty or image in progress (throws
+ DOMException.INVALID_STATE_ERR).
+
+ @method getAsBlob
+ @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
+ @param {Number} [quality=90] Applicable only together with mime type image/jpeg
+ @return {Blob} Image as Blob
+ */
+ getAsBlob: function(type, quality) {
+ if (!this.size) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ if (!type) {
+ type = 'image/jpeg';
+ }
+
+ if (type === 'image/jpeg' && !quality) {
+ quality = 90;
+ }
+
+ return this.getRuntime().exec.call(this, 'Image', 'getAsBlob', type, quality);
+ },
+
+ /**
+ Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws
+ DOMException.INVALID_STATE_ERR).
+
+ @method getAsDataURL
+ @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
+ @param {Number} [quality=90] Applicable only together with mime type image/jpeg
+ @return {String} Image as dataURL string
+ */
+ getAsDataURL: function(type, quality) {
+ if (!this.size) {
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+ return this.getRuntime().exec.call(this, 'Image', 'getAsDataURL', type, quality);
+ },
+
+ /**
+ Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws
+ DOMException.INVALID_STATE_ERR).
+
+ @method getAsBinaryString
+ @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
+ @param {Number} [quality=90] Applicable only together with mime type image/jpeg
+ @return {String} Image as binary string
+ */
+ getAsBinaryString: function(type, quality) {
+ var dataUrl = this.getAsDataURL(type, quality);
+ return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7));
+ },
+
+ /**
+ Embeds the image, or better to say, it's visual representation into the specified node. Depending on the runtime
+ in use, might be a canvas, or image (actual ) element or shim object (Flash or SilverLight - very rare, used for
+ legacy browsers that do not have canvas or proper dataURI support).
+
+ @method embed
+ @param {DOMElement} el DOM element to insert the image object into
+ @param {Object} options Set of key/value pairs controlling the mime type, dimensions and cropping factor of resulting
+ representation
+ */
+ embed: function(el) {
+ var self = this
+ , imgCopy
+ , type, quality, crop
+ , options = arguments[1] || {}
+ , width = this.width
+ , height = this.height
+ , runtime // this has to be outside of all the closures to contain proper runtime
+ ;
+
+ function onResize() {
+ // if possible, embed a canvas element directly
+ if (Env.can('create_canvas')) {
+ var canvas = imgCopy.getAsCanvas();
+ if (canvas) {
+ el.appendChild(canvas);
+ canvas = null;
+ imgCopy.destroy();
+ self.trigger('embedded');
+ return;
+ }
+ }
+
+ var dataUrl = imgCopy.getAsDataURL(type, quality);
+ if (!dataUrl) {
+ throw new x.ImageError(x.ImageError.WRONG_FORMAT);
+ }
+
+ if (Env.can('use_data_uri_of', dataUrl.length)) {
+ el.innerHTML = '<img src="' + dataUrl + '" width="' + imgCopy.width + '" height="' + imgCopy.height + '" />';
+ imgCopy.destroy();
+ self.trigger('embedded');
+ } else {
+ var tr = new Transporter();
+
+ tr.bind("TransportingComplete", function() {
+ runtime = self.connectRuntime(this.result.ruid);
+
+ self.bind("Embedded", function() {
+ // position and size properly
+ Basic.extend(runtime.getShimContainer().style, {
+ //position: 'relative',
+ top: '0px',
+ left: '0px',
+ width: imgCopy.width + 'px',
+ height: imgCopy.height + 'px'
+ });
+
+ // some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's
+ // position type changes (in Gecko), but since we basically need this only in IEs 6/7 and
+ // sometimes 8 and they do not have this problem, we can comment this for now
+ /*tr.bind("RuntimeInit", function(e, runtime) {
+ tr.destroy();
+ runtime.destroy();
+ onResize.call(self); // re-feed our image data
+ });*/
+
+ runtime = null;
+ }, 999);
+
+ runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height);
+ imgCopy.destroy();
+ });
+
+ tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, Basic.extend({}, options, {
+ required_caps: {
+ display_media: true
+ },
+ runtime_order: 'flash,silverlight',
+ container: el
+ }));
+ }
+ }
+
+ try {
+ if (!(el = Dom.get(el))) {
+ throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR);
+ }
+
+ if (!this.size) { // only preloaded image objects can be used as source
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+
+ if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
+ throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
+ }
+
+ type = options.type || this.type || 'image/jpeg';
+ quality = options.quality || 90;
+ crop = Basic.typeOf(options.crop) !== 'undefined' ? options.crop : false;
+
+ // figure out dimensions for the thumb
+ if (options.width) {
+ width = options.width;
+ height = options.height || width;
+ } else {
+ // if container element has > 0 dimensions, take them
+ var dimensions = Dom.getSize(el);
+ if (dimensions.w && dimensions.h) { // both should be > 0
+ width = dimensions.w;
+ height = dimensions.h;
+ }
+ }
+
+ imgCopy = new Image();
+
+ imgCopy.bind("Resize", function() {
+ onResize.call(self);
+ });
+
+ imgCopy.bind("Load", function() {
+ imgCopy.downsize(width, height, crop, false);
+ });
+
+ imgCopy.clone(this, false);
+
+ return imgCopy;
+ } catch(ex) {
+ // for now simply trigger error event
+ this.trigger('error', ex);
+ }
+ },
+
+ /**
+ Properly destroys the image and frees resources in use. If any. Recommended way to dispose mOxie.Image object.
+
+ @method destroy
+ */
+ destroy: function() {
+ if (this.ruid) {
+ this.getRuntime().exec.call(this, 'Image', 'destroy');
+ this.disconnectRuntime();
+ }
+ this.unbindAll();
+ }
+ });
+
+
+ function _updateInfo(info) {
+ if (!info) {
+ info = this.getRuntime().exec.call(this, 'Image', 'getInfo');
+ }
+
+ this.size = info.size;
+ this.width = info.width;
+ this.height = info.height;
+ this.type = info.type;
+ this.meta = info.meta;
+
+ // update file name, only if empty
+ if (this.name === '') {
+ this.name = info.name;
+ }
+ }
+
+
+ function _load(src) {
+ var srcType = Basic.typeOf(src);
+
+ try {
+ // if source is Image
+ if (src instanceof Image) {
+ if (!src.size) { // only preloaded image objects can be used as source
+ throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
+ }
+ _loadFromImage.apply(this, arguments);
+ }
+ // if source is o.Blob/o.File
+ else if (src instanceof Blob) {
+ if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) {
+ throw new x.ImageError(x.ImageError.WRONG_FORMAT);
+ }
+ _loadFromBlob.apply(this, arguments);
+ }
+ // if native blob/file
+ else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) {
+ _load.call(this, new File(null, src), arguments[1]);
+ }
+ // if String
+ else if (srcType === 'string') {
+ // if dataUrl String
+ if (/^data:[^;]*;base64,/.test(src)) {
+ _load.call(this, new Blob(null, { data: src }), arguments[1]);
+ }
+ // else assume Url, either relative or absolute
+ else {
+ _loadFromUrl.apply(this, arguments);
+ }
+ }
+ // if source seems to be an img node
+ else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') {
+ _load.call(this, src.src, arguments[1]);
+ }
+ else {
+ throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR);
+ }
+ } catch(ex) {
+ // for now simply trigger error event
+ this.trigger('error', ex);
+ }
+ }
+
+
+ function _loadFromImage(img, exact) {
+ var runtime = this.connectRuntime(img.ruid);
+ this.ruid = runtime.uid;
+ runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact));
+ }
+
+
+ function _loadFromBlob(blob, options) {
+ var self = this;
+
+ self.name = blob.name || '';
+
+ function exec(runtime) {
+ self.ruid = runtime.uid;
+ runtime.exec.call(self, 'Image', 'loadFromBlob', blob);
+ }
+
+ if (blob.isDetached()) {
+ this.bind('RuntimeInit', function(e, runtime) {
+ exec(runtime);
+ });
+
+ // convert to object representation
+ if (options && typeof(options.required_caps) === 'string') {
+ options.required_caps = Runtime.parseCaps(options.required_caps);
+ }
+
+ this.connectRuntime(Basic.extend({
+ required_caps: {
+ access_image_binary: true,
+ resize_image: true
+ }
+ }, options));
+ } else {
+ exec(this.connectRuntime(blob.ruid));
+ }
+ }
+
+
+ function _loadFromUrl(url, options) {
+ var self = this, xhr;
+
+ xhr = new XMLHttpRequest();
+
+ xhr.open('get', url);
+ xhr.responseType = 'blob';
+
+ xhr.onprogress = function(e) {
+ self.trigger(e);
+ };
+
+ xhr.onload = function() {
+ _loadFromBlob.call(self, xhr.response, true);
+ };
+
+ xhr.onerror = function(e) {
+ self.trigger(e);
+ };
+
+ xhr.onloadend = function() {
+ xhr.destroy();
+ };
+
+ xhr.bind('RuntimeError', function(e, err) {
+ self.trigger('RuntimeError', err);
+ });
+
+ xhr.send(null, options);
+ }
+ }
+
+ // virtual world will crash on you if image has a resolution higher than this:
+ Image.MAX_RESIZE_WIDTH = 6500;
+ Image.MAX_RESIZE_HEIGHT = 6500;
+
+ Image.prototype = EventTarget.instance;
+
+ return Image;
+});
+
+// Included from: src/javascript/runtime/html5/Runtime.js
+
+/**
+ * Runtime.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global File:true */
+
+/**
+Defines constructor for HTML5 runtime.
+
+@class moxie/runtime/html5/Runtime
+@private
+*/
+define("moxie/runtime/html5/Runtime", [
+ "moxie/core/utils/Basic",
+ "moxie/core/Exceptions",
+ "moxie/runtime/Runtime",
+ "moxie/core/utils/Env"
+], function(Basic, x, Runtime, Env) {
+
+ var type = "html5", extensions = {};
+
+ function Html5Runtime(options) {
+ var I = this
+ , Test = Runtime.capTest
+ , True = Runtime.capTrue
+ ;
+
+ var caps = Basic.extend({
+ access_binary: Test(window.FileReader || window.File && window.File.getAsDataURL),
+ access_image_binary: function() {
+ return I.can('access_binary') && !!extensions.Image;
+ },
+ display_media: Test(Env.can('create_canvas') || Env.can('use_data_uri_over32kb')),
+ do_cors: Test(window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()),
+ drag_and_drop: Test(function() {
+ // this comes directly from Modernizr: http://www.modernizr.com/
+ var div = document.createElement('div');
+ // IE has support for drag and drop since version 5, but doesn't support dropping files from desktop
+ return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && (Env.browser !== 'IE' || Env.version > 9);
+ }()),
+ filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
+ return (Env.browser === 'Chrome' && Env.version >= 28) || (Env.browser === 'IE' && Env.version >= 10);
+ }()),
+ return_response_headers: True,
+ return_response_type: function(responseType) {
+ if (responseType === 'json' && !!window.JSON) { // we can fake this one even if it's not supported
+ return true;
+ }
+ return Env.can('return_response_type', responseType);
+ },
+ return_status_code: True,
+ report_upload_progress: Test(window.XMLHttpRequest && new XMLHttpRequest().upload),
+ resize_image: function() {
+ return I.can('access_binary') && Env.can('create_canvas');
+ },
+ select_file: function() {
+ return Env.can('use_fileinput') && window.File;
+ },
+ select_folder: function() {
+ return I.can('select_file') && Env.browser === 'Chrome' && Env.version >= 21;
+ },
+ select_multiple: function() {
+ // it is buggy on Safari Windows and iOS
+ return I.can('select_file') && !(Env.browser === 'Safari' && Env.OS === 'Windows') && Env.OS !== 'iOS';
+ },
+ send_binary_string: Test(window.XMLHttpRequest && (new XMLHttpRequest().sendAsBinary || (window.Uint8Array && window.ArrayBuffer))),
+ send_custom_headers: Test(window.XMLHttpRequest),
+ send_multipart: function() {
+ return !!(window.XMLHttpRequest && new XMLHttpRequest().upload && window.FormData) || I.can('send_binary_string');
+ },
+ slice_blob: Test(window.File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)),
+ stream_upload: function(){
+ return I.can('slice_blob') && I.can('send_multipart');
+ },
+ summon_file_dialog: Test(function() { // yeah... some dirty sniffing here...
+ return (Env.browser === 'Firefox' && Env.version >= 4) ||
+ (Env.browser === 'Opera' && Env.version >= 12) ||
+ (Env.browser === 'IE' && Env.version >= 10) ||
+ !!~Basic.inArray(Env.browser, ['Chrome', 'Safari']);
+ }()),
+ upload_filesize: True
+ },
+ arguments[2]
+ );
+
+ Runtime.call(this, options, (arguments[1] || type), caps);
+
+
+ Basic.extend(this, {
+
+ init : function() {
+ this.trigger("Init");
+ },
+
+ destroy: (function(destroy) { // extend default destroy method
+ return function() {
+ destroy.call(I);
+ destroy = I = null;
+ };
+ }(this.destroy))
+ });
+
+ Basic.extend(this.getShim(), extensions);
+ }
+
+ Runtime.addConstructor(type, Html5Runtime);
+
+ return extensions;
+});
+
+// Included from: src/javascript/runtime/html5/file/Blob.js
+
+/**
+ * Blob.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/file/Blob
+@private
+*/
+define("moxie/runtime/html5/file/Blob", [
+ "moxie/runtime/html5/Runtime",
+ "moxie/file/Blob"
+], function(extensions, Blob) {
+
+ function HTML5Blob() {
+ function w3cBlobSlice(blob, start, end) {
+ var blobSlice;
+
+ if (window.File.prototype.slice) {
+ try {
+ blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception
+ return blob.slice(start, end);
+ } catch (e) {
+ // depricated slice method
+ return blob.slice(start, end - start);
+ }
+ // slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672
+ } else if ((blobSlice = window.File.prototype.webkitSlice || window.File.prototype.mozSlice)) {
+ return blobSlice.call(blob, start, end);
+ } else {
+ return null; // or throw some exception
+ }
+ }
+
+ this.slice = function() {
+ return new Blob(this.getRuntime().uid, w3cBlobSlice.apply(this, arguments));
+ };
+ }
+
+ return (extensions.Blob = HTML5Blob);
+});
+
+// Included from: src/javascript/core/utils/Events.js
+
+/**
+ * Events.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+define('moxie/core/utils/Events', [
+ 'moxie/core/utils/Basic'
+], function(Basic) {
+ var eventhash = {}, uid = 'moxie_' + Basic.guid();
+
+ // IE W3C like event funcs
+ function preventDefault() {
+ this.returnValue = false;
+ }
+
+ function stopPropagation() {
+ this.cancelBubble = true;
+ }
+
+ /**
+ Adds an event handler to the specified object and store reference to the handler
+ in objects internal Plupload registry (@see removeEvent).
+
+ @method addEvent
+ @for Utils
+ @static
+ @param {Object} obj DOM element like object to add handler to.
+ @param {String} name Name to add event listener to.
+ @param {Function} callback Function to call when event occurs.
+ @param {String} [key] that might be used to add specifity to the event record.
+ */
+ var addEvent = function(obj, name, callback, key) {
+ var func, events;
+
+ name = name.toLowerCase();
+
+ // Add event listener
+ if (obj.addEventListener) {
+ func = callback;
+
+ obj.addEventListener(name, func, false);
+ } else if (obj.attachEvent) {
+ func = function() {
+ var evt = window.event;
+
+ if (!evt.target) {
+ evt.target = evt.srcElement;
+ }
+
+ evt.preventDefault = preventDefault;
+ evt.stopPropagation = stopPropagation;
+
+ callback(evt);
+ };
+
+ obj.attachEvent('on' + name, func);
+ }
+
+ // Log event handler to objects internal mOxie registry
+ if (!obj[uid]) {
+ obj[uid] = Basic.guid();
+ }
+
+ if (!eventhash.hasOwnProperty(obj[uid])) {
+ eventhash[obj[uid]] = {};
+ }
+
+ events = eventhash[obj[uid]];
+
+ if (!events.hasOwnProperty(name)) {
+ events[name] = [];
+ }
+
+ events[name].push({
+ func: func,
+ orig: callback, // store original callback for IE
+ key: key
+ });
+ };
+
+
+ /**
+ Remove event handler from the specified object. If third argument (callback)
+ is not specified remove all events with the specified name.
+
+ @method removeEvent
+ @static
+ @param {Object} obj DOM element to remove event listener(s) from.
+ @param {String} name Name of event listener to remove.
+ @param {Function|String} [callback] might be a callback or unique key to match.
+ */
+ var removeEvent = function(obj, name, callback) {
+ var type, undef;
+
+ name = name.toLowerCase();
+
+ if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) {
+ type = eventhash[obj[uid]][name];
+ } else {
+ return;
+ }
+
+ for (var i = type.length - 1; i >= 0; i--) {
+ // undefined or not, key should match
+ if (type[i].orig === callback || type[i].key === callback) {
+ if (obj.removeEventListener) {
+ obj.removeEventListener(name, type[i].func, false);
+ } else if (obj.detachEvent) {
+ obj.detachEvent('on'+name, type[i].func);
+ }
+
+ type[i].orig = null;
+ type[i].func = null;
+ type.splice(i, 1);
+
+ // If callback was passed we are done here, otherwise proceed
+ if (callback !== undef) {
+ break;
+ }
+ }
+ }
+
+ // If event array got empty, remove it
+ if (!type.length) {
+ delete eventhash[obj[uid]][name];
+ }
+
+ // If mOxie registry has become empty, remove it
+ if (Basic.isEmptyObj(eventhash[obj[uid]])) {
+ delete eventhash[obj[uid]];
+
+ // IE doesn't let you remove DOM object property with - delete
+ try {
+ delete obj[uid];
+ } catch(e) {
+ obj[uid] = undef;
+ }
+ }
+ };
+
+
+ /**
+ Remove all kind of events from the specified object
+
+ @method removeAllEvents
+ @static
+ @param {Object} obj DOM element to remove event listeners from.
+ @param {String} [key] unique key to match, when removing events.
+ */
+ var removeAllEvents = function(obj, key) {
+ if (!obj || !obj[uid]) {
+ return;
+ }
+
+ Basic.each(eventhash[obj[uid]], function(events, name) {
+ removeEvent(obj, name, key);
+ });
+ };
+
+ return {
+ addEvent: addEvent,
+ removeEvent: removeEvent,
+ removeAllEvents: removeAllEvents
+ };
+});
+
+// Included from: src/javascript/runtime/html5/file/FileInput.js
+
+/**
+ * FileInput.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/file/FileInput
+@private
+*/
+define("moxie/runtime/html5/file/FileInput", [
+ "moxie/runtime/html5/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Dom",
+ "moxie/core/utils/Events",
+ "moxie/core/utils/Mime",
+ "moxie/core/utils/Env"
+], function(extensions, Basic, Dom, Events, Mime, Env) {
+
+ function FileInput() {
+ var _files = [], _options;
+
+ Basic.extend(this, {
+ init: function(options) {
+ var comp = this, I = comp.getRuntime(), input, shimContainer, mimes, browseButton, zIndex, top;
+
+ _options = options;
+ _files = [];
+
+ // figure out accept string
+ mimes = _options.accept.mimes || Mime.extList2mimes(_options.accept, I.can('filter_by_extension'));
+
+ shimContainer = I.getShimContainer();
+
+ shimContainer.innerHTML = '<input id="' + I.uid +'" type="file" style="font-size:999px;opacity:0;"' +
+ (_options.multiple && I.can('select_multiple') ? 'multiple' : '') +
+ (_options.directory && I.can('select_folder') ? 'webkitdirectory directory' : '') + // Chrome 11+
+ (mimes ? ' accept="' + mimes.join(',') + '"' : '') + ' />';
+
+ input = Dom.get(I.uid);
+
+ // prepare file input to be placed underneath the browse_button element
+ Basic.extend(input.style, {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%'
+ });
+
+
+ browseButton = Dom.get(_options.browse_button);
+
+ // Route click event to the input[type=file] element for browsers that support such behavior
+ if (I.can('summon_file_dialog')) {
+ if (Dom.getStyle(browseButton, 'position') === 'static') {
+ browseButton.style.position = 'relative';
+ }
+
+ zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
+
+ browseButton.style.zIndex = zIndex;
+ shimContainer.style.zIndex = zIndex - 1;
+
+ Events.addEvent(browseButton, 'click', function(e) {
+ var input = Dom.get(I.uid);
+ if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
+ input.click();
+ }
+ e.preventDefault();
+ }, comp.uid);
+ }
+
+ /* Since we have to place input[type=file] on top of the browse_button for some browsers,
+ browse_button loses interactivity, so we restore it here */
+ top = I.can('summon_file_dialog') ? browseButton : shimContainer;
+
+ Events.addEvent(top, 'mouseover', function() {
+ comp.trigger('mouseenter');
+ }, comp.uid);
+
+ Events.addEvent(top, 'mouseout', function() {
+ comp.trigger('mouseleave');
+ }, comp.uid);
+
+ Events.addEvent(top, 'mousedown', function() {
+ comp.trigger('mousedown');
+ }, comp.uid);
+
+ Events.addEvent(Dom.get(_options.container), 'mouseup', function() {
+ comp.trigger('mouseup');
+ }, comp.uid);
+
+
+ input.onchange = function onChange() { // there should be only one handler for this
+ _files = [];
+
+ if (_options.directory) {
+ // folders are represented by dots, filter them out (Chrome 11+)
+ Basic.each(this.files, function(file) {
+ if (file.name !== ".") { // if it doesn't looks like a folder
+ _files.push(file);
+ }
+ });
+ } else {
+ _files = [].slice.call(this.files);
+ }
+
+ // clearing the value enables the user to select the same file again if they want to
+ if (Env.browser !== 'IE') {
+ this.value = '';
+ } else {
+ // in IE input[type="file"] is read-only so the only way to reset it is to re-insert it
+ var clone = this.cloneNode(true);
+ this.parentNode.replaceChild(clone, this);
+ clone.onchange = onChange;
+ }
+ comp.trigger('change');
+ };
+
+ // ready event is perfectly asynchronous
+ comp.trigger({
+ type: 'ready',
+ async: true
+ });
+
+ shimContainer = null;
+ },
+
+ getFiles: function() {
+ return _files;
+ },
+
+ disable: function(state) {
+ var I = this.getRuntime(), input;
+
+ if ((input = Dom.get(I.uid))) {
+ input.disabled = !!state;
+ }
+ },
+
+ destroy: function() {
+ var I = this.getRuntime()
+ , shim = I.getShim()
+ , shimContainer = I.getShimContainer()
+ ;
+
+ Events.removeAllEvents(shimContainer, this.uid);
+ Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
+ Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
+
+ if (shimContainer) {
+ shimContainer.innerHTML = '';
+ }
+
+ shim.removeInstance(this.uid);
+
+ _files = _options = shimContainer = shim = null;
+ }
+ });
+ }
+
+ return (extensions.FileInput = FileInput);
+});
+
+// Included from: src/javascript/runtime/html5/file/FileDrop.js
+
+/**
+ * FileDrop.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/file/FileDrop
+@private
+*/
+define("moxie/runtime/html5/file/FileDrop", [
+ "moxie/runtime/html5/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Dom",
+ "moxie/core/utils/Events",
+ "moxie/core/utils/Mime"
+], function(extensions, Basic, Dom, Events, Mime) {
+
+ function FileDrop() {
+ var _files = [], _allowedExts = [], _options;
+
+ Basic.extend(this, {
+ init: function(options) {
+ var comp = this, dropZone;
+
+ _options = options;
+ _allowedExts = _extractExts(_options.accept);
+ dropZone = _options.container;
+
+ Events.addEvent(dropZone, 'dragover', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.dataTransfer.dropEffect = 'copy';
+ }, comp.uid);
+
+ Events.addEvent(dropZone, 'drop', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ _files = [];
+
+ // Chrome 21+ accepts folders via Drag'n'Drop
+ if (e.dataTransfer.items && e.dataTransfer.items[0].webkitGetAsEntry) {
+ var entries = [];
+ Basic.each(e.dataTransfer.items, function(item) {
+ entries.push(item.webkitGetAsEntry());
+ });
+ _readEntries(entries, function() {
+ comp.trigger("drop");
+ });
+ } else {
+ Basic.each(e.dataTransfer.files, function(file) {
+ if (_isAcceptable(file)) {
+ _files.push(file);
+ }
+ });
+ comp.trigger("drop");
+ }
+ }, comp.uid);
+
+ Events.addEvent(dropZone, 'dragenter', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ comp.trigger("dragenter");
+ }, comp.uid);
+
+ Events.addEvent(dropZone, 'dragleave', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ comp.trigger("dragleave");
+ }, comp.uid);
+ },
+
+ getFiles: function() {
+ return _files;
+ },
+
+ destroy: function() {
+ Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
+ _files = _allowedExts = _options = null;
+ }
+ });
+
+
+ function _extractExts(accept) {
+ var exts = [];
+ for (var i = 0; i < accept.length; i++) {
+ [].push.apply(exts, accept[i].extensions.split(/\s*,\s*/));
+ }
+ return Basic.inArray('*', exts) === -1 ? exts : [];
+ }
+
+
+ function _isAcceptable(file) {
+ var ext = Mime.getFileExtension(file.name);
+ return !ext || !_allowedExts.length || Basic.inArray(ext, _allowedExts) !== -1;
+ }
+
+
+ function _readEntries(entries, cb) {
+ var queue = [];
+ Basic.each(entries, function(entry) {
+ queue.push(function(cbcb) {
+ _readEntry(entry, cbcb);
+ });
+ });
+ Basic.inSeries(queue, function() {
+ cb();
+ });
+ }
+
+ function _readEntry(entry, cb) {
+ if (entry.isFile) {
+ entry.file(function(file) {
+ if (_isAcceptable(file)) {
+ _files.push(file);
+ }
+ cb();
+ }, function() {
+ // fire an error event maybe
+ cb();
+ });
+ } else if (entry.isDirectory) {
+ _readDirEntry(entry, cb);
+ } else {
+ cb(); // not file, not directory? what then?..
+ }
+ }
+
+ function _readDirEntry(dirEntry, cb) {
+ var entries = [], dirReader = dirEntry.createReader();
+
+ // keep quering recursively till no more entries
+ function getEntries(cbcb) {
+ dirReader.readEntries(function(moreEntries) {
+ if (moreEntries.length) {
+ [].push.apply(entries, moreEntries);
+ getEntries(cbcb);
+ } else {
+ cbcb();
+ }
+ }, cbcb);
+ }
+
+ // ...and you thought FileReader was crazy...
+ getEntries(function() {
+ _readEntries(entries, cb);
+ });
+ }
+ }
+
+ return (extensions.FileDrop = FileDrop);
+});
+
+// Included from: src/javascript/runtime/html5/file/FileReader.js
+
+/**
+ * FileReader.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/file/FileReader
+@private
+*/
+define("moxie/runtime/html5/file/FileReader", [
+ "moxie/runtime/html5/Runtime",
+ "moxie/core/utils/Encode",
+ "moxie/core/utils/Basic"
+], function(extensions, Encode, Basic) {
+
+ function FileReader() {
+ var _fr, _convertToBinary = false;
+
+ Basic.extend(this, {
+
+ read: function(op, blob) {
+ var target = this;
+
+ _fr = new window.FileReader();
+
+ _fr.addEventListener('progress', function(e) {
+ target.trigger(e);
+ });
+
+ _fr.addEventListener('load', function(e) {
+ target.trigger(e);
+ });
+
+ _fr.addEventListener('error', function(e) {
+ target.trigger(e, _fr.error);
+ });
+
+ _fr.addEventListener('loadend', function() {
+ _fr = null;
+ });
+
+ if (Basic.typeOf(_fr[op]) === 'function') {
+ _convertToBinary = false;
+ _fr[op](blob.getSource());
+ } else if (op === 'readAsBinaryString') { // readAsBinaryString is depricated in general and never existed in IE10+
+ _convertToBinary = true;
+ _fr.readAsDataURL(blob.getSource());
+ }
+ },
+
+ getResult: function() {
+ return _fr && _fr.result ? (_convertToBinary ? _toBinary(_fr.result) : _fr.result) : null;
+ },
+
+ abort: function() {
+ if (_fr) {
+ _fr.abort();
+ }
+ },
+
+ destroy: function() {
+ _fr = null;
+ }
+ });
+
+ function _toBinary(str) {
+ return Encode.atob(str.substring(str.indexOf('base64,') + 7));
+ }
+ }
+
+ return (extensions.FileReader = FileReader);
+});
+
+// Included from: src/javascript/runtime/html5/xhr/XMLHttpRequest.js
+
+/**
+ * XMLHttpRequest.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global ActiveXObject:true */
+
+/**
+@class moxie/runtime/html5/xhr/XMLHttpRequest
+@private
+*/
+define("moxie/runtime/html5/xhr/XMLHttpRequest", [
+ "moxie/runtime/html5/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Mime",
+ "moxie/core/utils/Url",
+ "moxie/file/File",
+ "moxie/file/Blob",
+ "moxie/xhr/FormData",
+ "moxie/core/Exceptions",
+ "moxie/core/utils/Env"
+], function(extensions, Basic, Mime, Url, File, Blob, FormData, x, Env) {
+
+ function XMLHttpRequest() {
+ var self = this
+ , _xhr
+ , _filename
+ ;
+
+ Basic.extend(this, {
+ send: function(meta, data) {
+ var target = this
+ , isGecko2_5_6 = (Env.browser === 'Mozilla' && Env.version >= 4 && Env.version < 7)
+ , isAndroidBrowser = Env.browser === 'Android Browser'
+ , mustSendAsBinary = false
+ ;
+
+ // extract file name
+ _filename = meta.url.replace(/^.+?\/([\w\-\.]+)$/, '$1').toLowerCase();
+
+ _xhr = _getNativeXHR();
+ _xhr.open(meta.method, meta.url, meta.async, meta.user, meta.password);
+
+
+ // prepare data to be sent
+ if (data instanceof Blob) {
+ if (data.isDetached()) {
+ mustSendAsBinary = true;
+ }
+ data = data.getSource();
+ } else if (data instanceof FormData) {
+
+ if (data.hasBlob()) {
+ if (data.getBlob().isDetached()) {
+ data = _prepareMultipart.call(target, data); // _xhr must be instantiated and be in OPENED state
+ mustSendAsBinary = true;
+ } else if ((isGecko2_5_6 || isAndroidBrowser) && Basic.typeOf(data.getBlob().getSource()) === 'blob' && window.FileReader) {
+ // Gecko 2/5/6 can't send blob in FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150
+ // Android browsers (default one and Dolphin) seem to have the same issue, see: #613
+ _preloadAndSend.call(target, meta, data);
+ return; // _preloadAndSend will reinvoke send() with transmutated FormData =%D
+ }
+ }
+
+ // transfer fields to real FormData
+ if (data instanceof FormData) { // if still a FormData, e.g. not mangled by _prepareMultipart()
+ var fd = new window.FormData();
+ data.each(function(value, name) {
+ if (value instanceof Blob) {
+ fd.append(name, value.getSource());
+ } else {
+ fd.append(name, value);
+ }
+ });
+ data = fd;
+ }
+ }
+
+
+ // if XHR L2
+ if (_xhr.upload) {
+ if (meta.withCredentials) {
+ _xhr.withCredentials = true;
+ }
+
+ _xhr.addEventListener('load', function(e) {
+ target.trigger(e);
+ });
+
+ _xhr.addEventListener('error', function(e) {
+ target.trigger(e);
+ });
+
+ // additionally listen to progress events
+ _xhr.addEventListener('progress', function(e) {
+ target.trigger(e);
+ });
+
+ _xhr.upload.addEventListener('progress', function(e) {
+ target.trigger({
+ type: 'UploadProgress',
+ loaded: e.loaded,
+ total: e.total
+ });
+ });
+ // ... otherwise simulate XHR L2
+ } else {
+ _xhr.onreadystatechange = function onReadyStateChange() {
+
+ // fake Level 2 events
+ switch (_xhr.readyState) {
+
+ case 1: // XMLHttpRequest.OPENED
+ // readystatechanged is fired twice for OPENED state (in IE and Mozilla) - neu
+ break;
+
+ // looks like HEADERS_RECEIVED (state 2) is not reported in Opera (or it's old versions) - neu
+ case 2: // XMLHttpRequest.HEADERS_RECEIVED
+ break;
+
+ case 3: // XMLHttpRequest.LOADING
+ // try to fire progress event for not XHR L2
+ var total, loaded;
+
+ try {
+ if (Url.hasSameOrigin(meta.url)) { // Content-Length not accessible for cross-domain on some browsers
+ total = _xhr.getResponseHeader('Content-Length') || 0; // old Safari throws an exception here
+ }
+
+ if (_xhr.responseText) { // responseText was introduced in IE7
+ loaded = _xhr.responseText.length;
+ }
+ } catch(ex) {
+ total = loaded = 0;
+ }
+
+ target.trigger({
+ type: 'progress',
+ lengthComputable: !!total,
+ total: parseInt(total, 10),
+ loaded: loaded
+ });
+ break;
+
+ case 4: // XMLHttpRequest.DONE
+ // release readystatechange handler (mostly for IE)
+ _xhr.onreadystatechange = function() {};
+
+ // usually status 0 is returned when server is unreachable, but FF also fails to status 0 for 408 timeout
+ if (_xhr.status === 0) {
+ target.trigger('error');
+ } else {
+ target.trigger('load');
+ }
+ break;
+ }
+ };
+ }
+
+
+ // set request headers
+ if (!Basic.isEmptyObj(meta.headers)) {
+ Basic.each(meta.headers, function(value, header) {
+ _xhr.setRequestHeader(header, value);
+ });
+ }
+
+ // request response type
+ if ("" !== meta.responseType && 'responseType' in _xhr) {
+ if ('json' === meta.responseType && !Env.can('return_response_type', 'json')) { // we can fake this one
+ _xhr.responseType = 'text';
+ } else {
+ _xhr.responseType = meta.responseType;
+ }
+ }
+
+ // send ...
+ if (!mustSendAsBinary) {
+ _xhr.send(data);
+ } else {
+ if (_xhr.sendAsBinary) { // Gecko
+ _xhr.sendAsBinary(data);
+ } else { // other browsers having support for typed arrays
+ (function() {
+ // mimic Gecko's sendAsBinary
+ var ui8a = new Uint8Array(data.length);
+ for (var i = 0; i < data.length; i++) {
+ ui8a[i] = (data.charCodeAt(i) & 0xff);
+ }
+ _xhr.send(ui8a.buffer);
+ }());
+ }
+ }
+
+ target.trigger('loadstart');
+ },
+
+ getStatus: function() {
+ // according to W3C spec it should return 0 for readyState < 3, but instead it throws an exception
+ try {
+ if (_xhr) {
+ return _xhr.status;
+ }
+ } catch(ex) {}
+ return 0;
+ },
+
+ getResponse: function(responseType) {
+ var I = this.getRuntime();
+
+ try {
+ switch (responseType) {
+ case 'blob':
+ var file = new File(I.uid, _xhr.response);
+
+ // try to extract file name from content-disposition if possible (might be - not, if CORS for example)
+ var disposition = _xhr.getResponseHeader('Content-Disposition');
+ if (disposition) {
+ // extract filename from response header if available
+ var match = disposition.match(/filename=([\'\"'])([^\1]+)\1/);
+ if (match) {
+ _filename = match[2];
+ }
+ }
+ file.name = _filename;
+
+ // pre-webkit Opera doesn't set type property on the blob response
+ if (!file.type) {
+ file.type = Mime.getFileMime(_filename);
+ }
+ return file;
+
+ case 'json':
+ if (!Env.can('return_response_type', 'json')) {
+ return _xhr.status === 200 && !!window.JSON ? JSON.parse(_xhr.responseText) : null;
+ }
+ return _xhr.response;
+
+ case 'document':
+ return _getDocument(_xhr);
+
+ default:
+ return _xhr.responseText !== '' ? _xhr.responseText : null; // against the specs, but for consistency across the runtimes
+ }
+ } catch(ex) {
+ return null;
+ }
+ },
+
+ getAllResponseHeaders: function() {
+ try {
+ return _xhr.getAllResponseHeaders();
+ } catch(ex) {}
+ return '';
+ },
+
+ abort: function() {
+ if (_xhr) {
+ _xhr.abort();
+ }
+ },
+
+ destroy: function() {
+ self = _filename = null;
+ }
+ });
+
+
+ // here we go... ugly fix for ugly bug
+ function _preloadAndSend(meta, data) {
+ var target = this, blob, fr;
+
+ // get original blob
+ blob = data.getBlob().getSource();
+
+ // preload blob in memory to be sent as binary string
+ fr = new window.FileReader();
+ fr.onload = function() {
+ // overwrite original blob
+ data.append(data.getBlobName(), new Blob(null, {
+ type: blob.type,
+ data: fr.result
+ }));
+ // invoke send operation again
+ self.send.call(target, meta, data);
+ };
+ fr.readAsBinaryString(blob);
+ }
+
+
+ function _getNativeXHR() {
+ if (window.XMLHttpRequest && !(Env.browser === 'IE' && Env.version < 8)) { // IE7 has native XHR but it's buggy
+ return new window.XMLHttpRequest();
+ } else {
+ return (function() {
+ var progIDs = ['Msxml2.XMLHTTP.6.0', 'Microsoft.XMLHTTP']; // if 6.0 available, use it, otherwise failback to default 3.0
+ for (var i = 0; i < progIDs.length; i++) {
+ try {
+ return new ActiveXObject(progIDs[i]);
+ } catch (ex) {}
+ }
+ })();
+ }
+ }
+
+ // @credits Sergey Ilinsky (http://www.ilinsky.com/)
+ function _getDocument(xhr) {
+ var rXML = xhr.responseXML;
+ var rText = xhr.responseText;
+
+ // Try parsing responseText (@see: http://www.ilinsky.com/articles/XMLHttpRequest/#bugs-ie-responseXML-content-type)
+ if (Env.browser === 'IE' && rText && rXML && !rXML.documentElement && /[^\/]+\/[^\+]+\+xml/.test(xhr.getResponseHeader("Content-Type"))) {
+ rXML = new window.ActiveXObject("Microsoft.XMLDOM");
+ rXML.async = false;
+ rXML.validateOnParse = false;
+ rXML.loadXML(rText);
+ }
+
+ // Check if there is no error in document
+ if (rXML) {
+ if ((Env.browser === 'IE' && rXML.parseError !== 0) || !rXML.documentElement || rXML.documentElement.tagName === "parsererror") {
+ return null;
+ }
+ }
+ return rXML;
+ }
+
+
+ function _prepareMultipart(fd) {
+ var boundary = '----moxieboundary' + new Date().getTime()
+ , dashdash = '--'
+ , crlf = '\r\n'
+ , multipart = ''
+ , I = this.getRuntime()
+ ;
+
+ if (!I.can('send_binary_string')) {
+ throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
+ }
+
+ _xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
+
+ // append multipart parameters
+ fd.each(function(value, name) {
+ // Firefox 3.6 failed to convert multibyte characters to UTF-8 in sendAsBinary(),
+ // so we try it here ourselves with: unescape(encodeURIComponent(value))
+ if (value instanceof Blob) {
+ // Build RFC2388 blob
+ multipart += dashdash + boundary + crlf +
+ 'Content-Disposition: form-data; name="' + name + '"; filename="' + unescape(encodeURIComponent(value.name || 'blob')) + '"' + crlf +
+ 'Content-Type: ' + value.type + crlf + crlf +
+ value.getSource() + crlf;
+ } else {
+ multipart += dashdash + boundary + crlf +
+ 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
+ unescape(encodeURIComponent(value)) + crlf;
+ }
+ });
+
+ multipart += dashdash + boundary + dashdash + crlf;
+
+ return multipart;
+ }
+ }
+
+ return (extensions.XMLHttpRequest = XMLHttpRequest);
+});
+
+// Included from: src/javascript/runtime/html5/utils/BinaryReader.js
+
+/**
+ * BinaryReader.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/utils/BinaryReader
+@private
+*/
+define("moxie/runtime/html5/utils/BinaryReader", [], function() {
+ return function() {
+ var II = false, bin;
+
+ // Private functions
+ function read(idx, size) {
+ var mv = II ? 0 : -8 * (size - 1), sum = 0, i;
+
+ for (i = 0; i < size; i++) {
+ sum |= (bin.charCodeAt(idx + i) << Math.abs(mv + i*8));
+ }
+
+ return sum;
+ }
+
+ function putstr(segment, idx, length) {
+ length = arguments.length === 3 ? length : bin.length - idx - 1;
+ bin = bin.substr(0, idx) + segment + bin.substr(length + idx);
+ }
+
+ function write(idx, num, size) {
+ var str = '', mv = II ? 0 : -8 * (size - 1), i;
+
+ for (i = 0; i < size; i++) {
+ str += String.fromCharCode((num >> Math.abs(mv + i*8)) & 255);
+ }
+
+ putstr(str, idx, size);
+ }
+
+ // Public functions
+ return {
+ II: function(order) {
+ if (order === undefined) {
+ return II;
+ } else {
+ II = order;
+ }
+ },
+
+ init: function(binData) {
+ II = false;
+ bin = binData;
+ },
+
+ SEGMENT: function(idx, length, segment) {
+ switch (arguments.length) {
+ case 1:
+ return bin.substr(idx, bin.length - idx - 1);
+ case 2:
+ return bin.substr(idx, length);
+ case 3:
+ putstr(segment, idx, length);
+ break;
+ default: return bin;
+ }
+ },
+
+ BYTE: function(idx) {
+ return read(idx, 1);
+ },
+
+ SHORT: function(idx) {
+ return read(idx, 2);
+ },
+
+ LONG: function(idx, num) {
+ if (num === undefined) {
+ return read(idx, 4);
+ } else {
+ write(idx, num, 4);
+ }
+ },
+
+ SLONG: function(idx) { // 2's complement notation
+ var num = read(idx, 4);
+
+ return (num > 2147483647 ? num - 4294967296 : num);
+ },
+
+ STRING: function(idx, size) {
+ var str = '';
+
+ for (size += idx; idx < size; idx++) {
+ str += String.fromCharCode(read(idx, 1));
+ }
+
+ return str;
+ }
+ };
+ };
+});
+
+// Included from: src/javascript/runtime/html5/image/JPEGHeaders.js
+
+/**
+ * JPEGHeaders.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/image/JPEGHeaders
+@private
+*/
+define("moxie/runtime/html5/image/JPEGHeaders", [
+ "moxie/runtime/html5/utils/BinaryReader"
+], function(BinaryReader) {
+
+ return function JPEGHeaders(data) {
+ var headers = [], read, idx, marker, length = 0;
+
+ read = new BinaryReader();
+ read.init(data);
+
+ // Check if data is jpeg
+ if (read.SHORT(0) !== 0xFFD8) {
+ return;
+ }
+
+ idx = 2;
+
+ while (idx <= data.length) {
+ marker = read.SHORT(idx);
+
+ // omit RST (restart) markers
+ if (marker >= 0xFFD0 && marker <= 0xFFD7) {
+ idx += 2;
+ continue;
+ }
+
+ // no headers allowed after SOS marker
+ if (marker === 0xFFDA || marker === 0xFFD9) {
+ break;
+ }
+
+ length = read.SHORT(idx + 2) + 2;
+
+ // APPn marker detected
+ if (marker >= 0xFFE1 && marker <= 0xFFEF) {
+ headers.push({
+ hex: marker,
+ name: 'APP' + (marker & 0x000F),
+ start: idx,
+ length: length,
+ segment: read.SEGMENT(idx, length)
+ });
+ }
+
+ idx += length;
+ }
+
+ read.init(null); // free memory
+
+ return {
+ headers: headers,
+
+ restore: function(data) {
+ var max, i;
+
+ read.init(data);
+
+ idx = read.SHORT(2) == 0xFFE0 ? 4 + read.SHORT(4) : 2;
+
+ for (i = 0, max = headers.length; i < max; i++) {
+ read.SEGMENT(idx, 0, headers[i].segment);
+ idx += headers[i].length;
+ }
+
+ data = read.SEGMENT();
+ read.init(null);
+ return data;
+ },
+
+ strip: function(data) {
+ var headers, jpegHeaders, i;
+
+ jpegHeaders = new JPEGHeaders(data);
+ headers = jpegHeaders.headers;
+ jpegHeaders.purge();
+
+ read.init(data);
+
+ i = headers.length;
+ while (i--) {
+ read.SEGMENT(headers[i].start, headers[i].length, '');
+ }
+
+ data = read.SEGMENT();
+ read.init(null);
+ return data;
+ },
+
+ get: function(name) {
+ var array = [];
+
+ for (var i = 0, max = headers.length; i < max; i++) {
+ if (headers[i].name === name.toUpperCase()) {
+ array.push(headers[i].segment);
+ }
+ }
+ return array;
+ },
+
+ set: function(name, segment) {
+ var array = [], i, ii, max;
+
+ if (typeof(segment) === 'string') {
+ array.push(segment);
+ } else {
+ array = segment;
+ }
+
+ for (i = ii = 0, max = headers.length; i < max; i++) {
+ if (headers[i].name === name.toUpperCase()) {
+ headers[i].segment = array[ii];
+ headers[i].length = array[ii].length;
+ ii++;
+ }
+ if (ii >= array.length) {
+ break;
+ }
+ }
+ },
+
+ purge: function() {
+ headers = [];
+ read.init(null);
+ read = null;
+ }
+ };
+ };
+});
+
+// Included from: src/javascript/runtime/html5/image/ExifParser.js
+
+/**
+ * ExifParser.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/image/ExifParser
+@private
+*/
+define("moxie/runtime/html5/image/ExifParser", [
+ "moxie/core/utils/Basic",
+ "moxie/runtime/html5/utils/BinaryReader"
+], function(Basic, BinaryReader) {
+
+ return function ExifParser() {
+ // Private ExifParser fields
+ var data, tags, Tiff, offsets = {}, tagDescs;
+
+ data = new BinaryReader();
+
+ tags = {
+ tiff : {
+ /*
+ The image orientation viewed in terms of rows and columns.
+
+ 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
+ 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
+ 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
+ 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
+ 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
+ 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
+ 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
+ 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
+ */
+ 0x0112: 'Orientation',
+ 0x010E: 'ImageDescription',
+ 0x010F: 'Make',
+ 0x0110: 'Model',
+ 0x0131: 'Software',
+ 0x8769: 'ExifIFDPointer',
+ 0x8825: 'GPSInfoIFDPointer'
+ },
+ exif : {
+ 0x9000: 'ExifVersion',
+ 0xA001: 'ColorSpace',
+ 0xA002: 'PixelXDimension',
+ 0xA003: 'PixelYDimension',
+ 0x9003: 'DateTimeOriginal',
+ 0x829A: 'ExposureTime',
+ 0x829D: 'FNumber',
+ 0x8827: 'ISOSpeedRatings',
+ 0x9201: 'ShutterSpeedValue',
+ 0x9202: 'ApertureValue' ,
+ 0x9207: 'MeteringMode',
+ 0x9208: 'LightSource',
+ 0x9209: 'Flash',
+ 0x920A: 'FocalLength',
+ 0xA402: 'ExposureMode',
+ 0xA403: 'WhiteBalance',
+ 0xA406: 'SceneCaptureType',
+ 0xA404: 'DigitalZoomRatio',
+ 0xA408: 'Contrast',
+ 0xA409: 'Saturation',
+ 0xA40A: 'Sharpness'
+ },
+ gps : {
+ 0x0000: 'GPSVersionID',
+ 0x0001: 'GPSLatitudeRef',
+ 0x0002: 'GPSLatitude',
+ 0x0003: 'GPSLongitudeRef',
+ 0x0004: 'GPSLongitude'
+ }
+ };
+
+ tagDescs = {
+ 'ColorSpace': {
+ 1: 'sRGB',
+ 0: 'Uncalibrated'
+ },
+
+ 'MeteringMode': {
+ 0: 'Unknown',
+ 1: 'Average',
+ 2: 'CenterWeightedAverage',
+ 3: 'Spot',
+ 4: 'MultiSpot',
+ 5: 'Pattern',
+ 6: 'Partial',
+ 255: 'Other'
+ },
+
+ 'LightSource': {
+ 1: 'Daylight',
+ 2: 'Fliorescent',
+ 3: 'Tungsten',
+ 4: 'Flash',
+ 9: 'Fine weather',
+ 10: 'Cloudy weather',
+ 11: 'Shade',
+ 12: 'Daylight fluorescent (D 5700 - 7100K)',
+ 13: 'Day white fluorescent (N 4600 -5400K)',
+ 14: 'Cool white fluorescent (W 3900 - 4500K)',
+ 15: 'White fluorescent (WW 3200 - 3700K)',
+ 17: 'Standard light A',
+ 18: 'Standard light B',
+ 19: 'Standard light C',
+ 20: 'D55',
+ 21: 'D65',
+ 22: 'D75',
+ 23: 'D50',
+ 24: 'ISO studio tungsten',
+ 255: 'Other'
+ },
+
+ 'Flash': {
+ 0x0000: 'Flash did not fire.',
+ 0x0001: 'Flash fired.',
+ 0x0005: 'Strobe return light not detected.',
+ 0x0007: 'Strobe return light detected.',
+ 0x0009: 'Flash fired, compulsory flash mode',
+ 0x000D: 'Flash fired, compulsory flash mode, return light not detected',
+ 0x000F: 'Flash fired, compulsory flash mode, return light detected',
+ 0x0010: 'Flash did not fire, compulsory flash mode',
+ 0x0018: 'Flash did not fire, auto mode',
+ 0x0019: 'Flash fired, auto mode',
+ 0x001D: 'Flash fired, auto mode, return light not detected',
+ 0x001F: 'Flash fired, auto mode, return light detected',
+ 0x0020: 'No flash function',
+ 0x0041: 'Flash fired, red-eye reduction mode',
+ 0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
+ 0x0047: 'Flash fired, red-eye reduction mode, return light detected',
+ 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
+ 0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
+ 0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
+ 0x0059: 'Flash fired, auto mode, red-eye reduction mode',
+ 0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
+ 0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
+ },
+
+ 'ExposureMode': {
+ 0: 'Auto exposure',
+ 1: 'Manual exposure',
+ 2: 'Auto bracket'
+ },
+
+ 'WhiteBalance': {
+ 0: 'Auto white balance',
+ 1: 'Manual white balance'
+ },
+
+ 'SceneCaptureType': {
+ 0: 'Standard',
+ 1: 'Landscape',
+ 2: 'Portrait',
+ 3: 'Night scene'
+ },
+
+ 'Contrast': {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+
+ 'Saturation': {
+ 0: 'Normal',
+ 1: 'Low saturation',
+ 2: 'High saturation'
+ },
+
+ 'Sharpness': {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+
+ // GPS related
+ 'GPSLatitudeRef': {
+ N: 'North latitude',
+ S: 'South latitude'
+ },
+
+ 'GPSLongitudeRef': {
+ E: 'East longitude',
+ W: 'West longitude'
+ }
+ };
+
+ function extractTags(IFD_offset, tags2extract) {
+ var length = data.SHORT(IFD_offset), i, ii,
+ tag, type, count, tagOffset, offset, value, values = [], hash = {};
+
+ for (i = 0; i < length; i++) {
+ // Set binary reader pointer to beginning of the next tag
+ offset = tagOffset = IFD_offset + 12 * i + 2;
+
+ tag = tags2extract[data.SHORT(offset)];
+
+ if (tag === undefined) {
+ continue; // Not the tag we requested
+ }
+
+ type = data.SHORT(offset+=2);
+ count = data.LONG(offset+=2);
+
+ offset += 4;
+ values = [];
+
+ switch (type) {
+ case 1: // BYTE
+ case 7: // UNDEFINED
+ if (count > 4) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.BYTE(offset + ii);
+ }
+
+ break;
+
+ case 2: // STRING
+ if (count > 4) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ hash[tag] = data.STRING(offset, count - 1);
+
+ continue;
+
+ case 3: // SHORT
+ if (count > 2) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.SHORT(offset + ii*2);
+ }
+
+ break;
+
+ case 4: // LONG
+ if (count > 1) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.LONG(offset + ii*4);
+ }
+
+ break;
+
+ case 5: // RATIONAL
+ offset = data.LONG(offset) + offsets.tiffHeader;
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.LONG(offset + ii*4) / data.LONG(offset + ii*4 + 4);
+ }
+
+ break;
+
+ case 9: // SLONG
+ offset = data.LONG(offset) + offsets.tiffHeader;
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.SLONG(offset + ii*4);
+ }
+
+ break;
+
+ case 10: // SRATIONAL
+ offset = data.LONG(offset) + offsets.tiffHeader;
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.SLONG(offset + ii*4) / data.SLONG(offset + ii*4 + 4);
+ }
+
+ break;
+
+ default:
+ continue;
+ }
+
+ value = (count == 1 ? values[0] : values);
+
+ if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') {
+ hash[tag] = tagDescs[tag][value];
+ } else {
+ hash[tag] = value;
+ }
+ }
+
+ return hash;
+ }
+
+ function getIFDOffsets() {
+ var idx = offsets.tiffHeader;
+
+ // Set read order of multi-byte data
+ data.II(data.SHORT(idx) == 0x4949);
+
+ // Check if always present bytes are indeed present
+ if (data.SHORT(idx+=2) !== 0x002A) {
+ return false;
+ }
+
+ offsets.IFD0 = offsets.tiffHeader + data.LONG(idx += 2);
+ Tiff = extractTags(offsets.IFD0, tags.tiff);
+
+ if ('ExifIFDPointer' in Tiff) {
+ offsets.exifIFD = offsets.tiffHeader + Tiff.ExifIFDPointer;
+ delete Tiff.ExifIFDPointer;
+ }
+
+ if ('GPSInfoIFDPointer' in Tiff) {
+ offsets.gpsIFD = offsets.tiffHeader + Tiff.GPSInfoIFDPointer;
+ delete Tiff.GPSInfoIFDPointer;
+ }
+ return true;
+ }
+
+ // At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported
+ function setTag(ifd, tag, value) {
+ var offset, length, tagOffset, valueOffset = 0;
+
+ // If tag name passed translate into hex key
+ if (typeof(tag) === 'string') {
+ var tmpTags = tags[ifd.toLowerCase()];
+ for (var hex in tmpTags) {
+ if (tmpTags[hex] === tag) {
+ tag = hex;
+ break;
+ }
+ }
+ }
+ offset = offsets[ifd.toLowerCase() + 'IFD'];
+ length = data.SHORT(offset);
+
+ for (var i = 0; i < length; i++) {
+ tagOffset = offset + 12 * i + 2;
+
+ if (data.SHORT(tagOffset) == tag) {
+ valueOffset = tagOffset + 8;
+ break;
+ }
+ }
+
+ if (!valueOffset) {
+ return false;
+ }
+
+ data.LONG(valueOffset, value);
+ return true;
+ }
+
+
+ // Public functions
+ return {
+ init: function(segment) {
+ // Reset internal data
+ offsets = {
+ tiffHeader: 10
+ };
+
+ if (segment === undefined || !segment.length) {
+ return false;
+ }
+
+ data.init(segment);
+
+ // Check if that's APP1 and that it has EXIF
+ if (data.SHORT(0) === 0xFFE1 && data.STRING(4, 5).toUpperCase() === "EXIF\0") {
+ return getIFDOffsets();
+ }
+ return false;
+ },
+
+ TIFF: function() {
+ return Tiff;
+ },
+
+ EXIF: function() {
+ var Exif;
+
+ // Populate EXIF hash
+ Exif = extractTags(offsets.exifIFD, tags.exif);
+
+ // Fix formatting of some tags
+ if (Exif.ExifVersion && Basic.typeOf(Exif.ExifVersion) === 'array') {
+ for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) {
+ exifVersion += String.fromCharCode(Exif.ExifVersion[i]);
+ }
+ Exif.ExifVersion = exifVersion;
+ }
+
+ return Exif;
+ },
+
+ GPS: function() {
+ var GPS;
+
+ GPS = extractTags(offsets.gpsIFD, tags.gps);
+
+ // iOS devices (and probably some others) do not put in GPSVersionID tag (why?..)
+ if (GPS.GPSVersionID && Basic.typeOf(GPS.GPSVersionID) === 'array') {
+ GPS.GPSVersionID = GPS.GPSVersionID.join('.');
+ }
+
+ return GPS;
+ },
+
+ setExif: function(tag, value) {
+ // Right now only setting of width/height is possible
+ if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') {return false;}
+
+ return setTag('exif', tag, value);
+ },
+
+
+ getBinary: function() {
+ return data.SEGMENT();
+ },
+
+ purge: function() {
+ data.init(null);
+ data = Tiff = null;
+ offsets = {};
+ }
+ };
+ };
+});
+
+// Included from: src/javascript/runtime/html5/image/JPEG.js
+
+/**
+ * JPEG.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/image/JPEG
+@private
+*/
+define("moxie/runtime/html5/image/JPEG", [
+ "moxie/core/utils/Basic",
+ "moxie/core/Exceptions",
+ "moxie/runtime/html5/image/JPEGHeaders",
+ "moxie/runtime/html5/utils/BinaryReader",
+ "moxie/runtime/html5/image/ExifParser"
+], function(Basic, x, JPEGHeaders, BinaryReader, ExifParser) {
+
+ function JPEG(binstr) {
+ var _binstr, _br, _hm, _ep, _info, hasExif;
+
+ function _getDimensions() {
+ var idx = 0, marker, length;
+
+ // examine all through the end, since some images might have very large APP segments
+ while (idx <= _binstr.length) {
+ marker = _br.SHORT(idx += 2);
+
+ if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn
+ idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte)
+ return {
+ height: _br.SHORT(idx),
+ width: _br.SHORT(idx += 2)
+ };
+ }
+ length = _br.SHORT(idx += 2);
+ idx += length - 2;
+ }
+ return null;
+ }
+
+ _binstr = binstr;
+
+ _br = new BinaryReader();
+ _br.init(_binstr);
+
+ // check if it is jpeg
+ if (_br.SHORT(0) !== 0xFFD8) {
+ throw new x.ImageError(x.ImageError.WRONG_FORMAT);
+ }
+
+ // backup headers
+ _hm = new JPEGHeaders(binstr);
+
+ // extract exif info
+ _ep = new ExifParser();
+ hasExif = !!_ep.init(_hm.get('app1')[0]);
+
+ // get dimensions
+ _info = _getDimensions.call(this);
+
+ Basic.extend(this, {
+ type: 'image/jpeg',
+
+ size: _binstr.length,
+
+ width: _info && _info.width || 0,
+
+ height: _info && _info.height || 0,
+
+ setExif: function(tag, value) {
+ if (!hasExif) {
+ return false; // or throw an exception
+ }
+
+ if (Basic.typeOf(tag) === 'object') {
+ Basic.each(tag, function(value, tag) {
+ _ep.setExif(tag, value);
+ });
+ } else {
+ _ep.setExif(tag, value);
+ }
+
+ // update internal headers
+ _hm.set('app1', _ep.getBinary());
+ },
+
+ writeHeaders: function() {
+ if (!arguments.length) {
+ // if no arguments passed, update headers internally
+ return (_binstr = _hm.restore(_binstr));
+ }
+ return _hm.restore(arguments[0]);
+ },
+
+ stripHeaders: function(binstr) {
+ return _hm.strip(binstr);
+ },
+
+ purge: function() {
+ _purge.call(this);
+ }
+ });
+
+ if (hasExif) {
+ this.meta = {
+ tiff: _ep.TIFF(),
+ exif: _ep.EXIF(),
+ gps: _ep.GPS()
+ };
+ }
+
+ function _purge() {
+ if (!_ep || !_hm || !_br) {
+ return; // ignore any repeating purge requests
+ }
+ _ep.purge();
+ _hm.purge();
+ _br.init(null);
+ _binstr = _info = _hm = _ep = _br = null;
+ }
+ }
+
+ return JPEG;
+});
+
+// Included from: src/javascript/runtime/html5/image/PNG.js
+
+/**
+ * PNG.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/image/PNG
+@private
+*/
+define("moxie/runtime/html5/image/PNG", [
+ "moxie/core/Exceptions",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/html5/utils/BinaryReader"
+], function(x, Basic, BinaryReader) {
+
+ function PNG(binstr) {
+ var _binstr, _br, _hm, _ep, _info;
+
+ _binstr = binstr;
+
+ _br = new BinaryReader();
+ _br.init(_binstr);
+
+ // check if it's png
+ (function() {
+ var idx = 0, i = 0
+ , signature = [0x8950, 0x4E47, 0x0D0A, 0x1A0A]
+ ;
+
+ for (i = 0; i < signature.length; i++, idx += 2) {
+ if (signature[i] != _br.SHORT(idx)) {
+ throw new x.ImageError(x.ImageError.WRONG_FORMAT);
+ }
+ }
+ }());
+
+ function _getDimensions() {
+ var chunk, idx;
+
+ chunk = _getChunkAt.call(this, 8);
+
+ if (chunk.type == 'IHDR') {
+ idx = chunk.start;
+ return {
+ width: _br.LONG(idx),
+ height: _br.LONG(idx += 4)
+ };
+ }
+ return null;
+ }
+
+ function _purge() {
+ if (!_br) {
+ return; // ignore any repeating purge requests
+ }
+ _br.init(null);
+ _binstr = _info = _hm = _ep = _br = null;
+ }
+
+ _info = _getDimensions.call(this);
+
+ Basic.extend(this, {
+ type: 'image/png',
+
+ size: _binstr.length,
+
+ width: _info.width,
+
+ height: _info.height,
+
+ purge: function() {
+ _purge.call(this);
+ }
+ });
+
+ // for PNG we can safely trigger purge automatically, as we do not keep any data for later
+ _purge.call(this);
+
+ function _getChunkAt(idx) {
+ var length, type, start, CRC;
+
+ length = _br.LONG(idx);
+ type = _br.STRING(idx += 4, 4);
+ start = idx += 4;
+ CRC = _br.LONG(idx + length);
+
+ return {
+ length: length,
+ type: type,
+ start: start,
+ CRC: CRC
+ };
+ }
+ }
+
+ return PNG;
+});
+
+// Included from: src/javascript/runtime/html5/image/ImageInfo.js
+
+/**
+ * ImageInfo.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/image/ImageInfo
+@private
+*/
+define("moxie/runtime/html5/image/ImageInfo", [
+ "moxie/core/utils/Basic",
+ "moxie/core/Exceptions",
+ "moxie/runtime/html5/image/JPEG",
+ "moxie/runtime/html5/image/PNG"
+], function(Basic, x, JPEG, PNG) {
+ /**
+ Optional image investigation tool for HTML5 runtime. Provides the following features:
+ - ability to distinguish image type (JPEG or PNG) by signature
+ - ability to extract image width/height directly from it's internals, without preloading in memory (fast)
+ - ability to extract APP headers from JPEGs (Exif, GPS, etc)
+ - ability to replace width/height tags in extracted JPEG headers
+ - ability to restore APP headers, that were for example stripped during image manipulation
+
+ @class ImageInfo
+ @constructor
+ @param {String} binstr Image source as binary string
+ */
+ return function(binstr) {
+ var _cs = [JPEG, PNG], _img;
+
+ // figure out the format, throw: ImageError.WRONG_FORMAT if not supported
+ _img = (function() {
+ for (var i = 0; i < _cs.length; i++) {
+ try {
+ return new _cs[i](binstr);
+ } catch (ex) {
+ // console.info(ex);
+ }
+ }
+ throw new x.ImageError(x.ImageError.WRONG_FORMAT);
+ }());
+
+ Basic.extend(this, {
+ /**
+ Image Mime Type extracted from it's depths
+
+ @property type
+ @type {String}
+ @default ''
+ */
+ type: '',
+
+ /**
+ Image size in bytes
+
+ @property size
+ @type {Number}
+ @default 0
+ */
+ size: 0,
+
+ /**
+ Image width extracted from image source
+
+ @property width
+ @type {Number}
+ @default 0
+ */
+ width: 0,
+
+ /**
+ Image height extracted from image source
+
+ @property height
+ @type {Number}
+ @default 0
+ */
+ height: 0,
+
+ /**
+ Sets Exif tag. Currently applicable only for width and height tags. Obviously works only with JPEGs.
+
+ @method setExif
+ @param {String} tag Tag to set
+ @param {Mixed} value Value to assign to the tag
+ */
+ setExif: function() {},
+
+ /**
+ Restores headers to the source.
+
+ @method writeHeaders
+ @param {String} data Image source as binary string
+ @return {String} Updated binary string
+ */
+ writeHeaders: function(data) {
+ return data;
+ },
+
+ /**
+ Strip all headers from the source.
+
+ @method stripHeaders
+ @param {String} data Image source as binary string
+ @return {String} Updated binary string
+ */
+ stripHeaders: function(data) {
+ return data;
+ },
+
+ /**
+ Dispose resources.
+
+ @method purge
+ */
+ purge: function() {}
+ });
+
+ Basic.extend(this, _img);
+
+ this.purge = function() {
+ _img.purge();
+ _img = null;
+ };
+ };
+});
+
+// Included from: src/javascript/runtime/html5/image/MegaPixel.js
+
+/**
+(The MIT License)
+
+Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/**
+ * Mega pixel image rendering library for iOS6 Safari
+ *
+ * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel),
+ * which causes unexpected subsampling when drawing it in canvas.
+ * By using this library, you can safely render the image with proper stretching.
+ *
+ * Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>
+ * Released under the MIT license
+ */
+
+/**
+@class moxie/runtime/html5/image/MegaPixel
+@private
+*/
+define("moxie/runtime/html5/image/MegaPixel", [], function() {
+
+ /**
+ * Rendering image element (with resizing) into the canvas element
+ */
+ function renderImageToCanvas(img, canvas, options) {
+ var iw = img.naturalWidth, ih = img.naturalHeight;
+ var width = options.width, height = options.height;
+ var x = options.x || 0, y = options.y || 0;
+ var ctx = canvas.getContext('2d');
+ if (detectSubsampling(img)) {
+ iw /= 2;
+ ih /= 2;
+ }
+ var d = 1024; // size of tiling canvas
+ var tmpCanvas = document.createElement('canvas');
+ tmpCanvas.width = tmpCanvas.height = d;
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var vertSquashRatio = detectVerticalSquash(img, iw, ih);
+ var sy = 0;
+ while (sy < ih) {
+ var sh = sy + d > ih ? ih - sy : d;
+ var sx = 0;
+ while (sx < iw) {
+ var sw = sx + d > iw ? iw - sx : d;
+ tmpCtx.clearRect(0, 0, d, d);
+ tmpCtx.drawImage(img, -sx, -sy);
+ var dx = (sx * width / iw + x) << 0;
+ var dw = Math.ceil(sw * width / iw);
+ var dy = (sy * height / ih / vertSquashRatio + y) << 0;
+ var dh = Math.ceil(sh * height / ih / vertSquashRatio);
+ ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
+ sx += d;
+ }
+ sy += d;
+ }
+ tmpCanvas = tmpCtx = null;
+ }
+
+ /**
+ * Detect subsampling in loaded image.
+ * In iOS, larger images than 2M pixels may be subsampled in rendering.
+ */
+ function detectSubsampling(img) {
+ var iw = img.naturalWidth, ih = img.naturalHeight;
+ if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
+ var canvas = document.createElement('canvas');
+ canvas.width = canvas.height = 1;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, -iw + 1, 0);
+ // subsampled image becomes half smaller in rendering size.
+ // check alpha channel value to confirm image is covering edge pixel or not.
+ // if alpha value is 0 image is not covering, hence subsampled.
+ return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Detecting vertical squash in loaded image.
+ * Fixes a bug which squash image vertically while drawing into canvas for some images.
+ */
+ function detectVerticalSquash(img, iw, ih) {
+ var canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = ih;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ var data = ctx.getImageData(0, 0, 1, ih).data;
+ // search image edge pixel position in case it is squashed vertically.
+ var sy = 0;
+ var ey = ih;
+ var py = ih;
+ while (py > sy) {
+ var alpha = data[(py - 1) * 4 + 3];
+ if (alpha === 0) {
+ ey = py;
+ } else {
+ sy = py;
+ }
+ py = (ey + sy) >> 1;
+ }
+ canvas = null;
+ var ratio = (py / ih);
+ return (ratio === 0) ? 1 : ratio;
+ }
+
+ return {
+ isSubsampled: detectSubsampling,
+ renderTo: renderImageToCanvas
+ };
+});
+
+// Included from: src/javascript/runtime/html5/image/Image.js
+
+/**
+ * Image.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html5/image/Image
+@private
+*/
+define("moxie/runtime/html5/image/Image", [
+ "moxie/runtime/html5/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/core/Exceptions",
+ "moxie/core/utils/Encode",
+ "moxie/file/Blob",
+ "moxie/runtime/html5/image/ImageInfo",
+ "moxie/runtime/html5/image/MegaPixel",
+ "moxie/core/utils/Mime",
+ "moxie/core/utils/Env"
+], function(extensions, Basic, x, Encode, Blob, ImageInfo, MegaPixel, Mime, Env) {
+
+ function HTML5Image() {
+ var me = this
+ , _img, _imgInfo, _canvas, _binStr, _blob
+ , _modified = false // is set true whenever image is modified
+ , _preserveHeaders = true
+ ;
+
+ Basic.extend(this, {
+ loadFromBlob: function(blob) {
+ var comp = this, I = comp.getRuntime()
+ , asBinary = arguments.length > 1 ? arguments[1] : true
+ ;
+
+ if (!I.can('access_binary')) {
+ throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
+ }
+
+ _blob = blob;
+
+ if (blob.isDetached()) {
+ _binStr = blob.getSource();
+ _preload.call(this, _binStr);
+ return;
+ } else {
+ _readAsDataUrl.call(this, blob.getSource(), function(dataUrl) {
+ if (asBinary) {
+ _binStr = _toBinary(dataUrl);
+ }
+ _preload.call(comp, dataUrl);
+ });
+ }
+ },
+
+ loadFromImage: function(img, exact) {
+ this.meta = img.meta;
+
+ _blob = new Blob(null, {
+ name: img.name,
+ size: img.size,
+ type: img.type
+ });
+
+ _preload.call(this, exact ? (_binStr = img.getAsBinaryString()) : img.getAsDataURL());
+ },
+
+ getInfo: function() {
+ var I = this.getRuntime(), info;
+
+ if (!_imgInfo && _binStr && I.can('access_image_binary')) {
+ _imgInfo = new ImageInfo(_binStr);
+ }
+
+ info = {
+ width: _getImg().width || 0,
+ height: _getImg().height || 0,
+ type: _blob.type || Mime.getFileMime(_blob.name),
+ size: _binStr && _binStr.length || _blob.size || 0,
+ name: _blob.name || '',
+ meta: _imgInfo && _imgInfo.meta || this.meta || {}
+ };
+
+ return info;
+ },
+
+ downsize: function() {
+ _downsize.apply(this, arguments);
+ },
+
+ getAsCanvas: function() {
+ if (_canvas) {
+ _canvas.id = this.uid + '_canvas';
+ }
+ return _canvas;
+ },
+
+ getAsBlob: function(type, quality) {
+ if (type !== this.type) {
+ // if different mime type requested prepare image for conversion
+ _downsize.call(this, this.width, this.height, false);
+ }
+ return new Blob(null, {
+ type: type,
+ data: me.getAsBinaryString.call(this, type, quality)
+ });
+ },
+
+ getAsDataURL: function(type) {
+ var quality = arguments[1] || 90;
+
+ // if image has not been modified, return the source right away
+ if (!_modified) {
+ return _img.src;
+ }
+
+ if ('image/jpeg' !== type) {
+ return _canvas.toDataURL('image/png');
+ } else {
+ try {
+ // older Geckos used to result in an exception on quality argument
+ return _canvas.toDataURL('image/jpeg', quality/100);
+ } catch (ex) {
+ return _canvas.toDataURL('image/jpeg');
+ }
+ }
+ },
+
+ getAsBinaryString: function(type, quality) {
+ // if image has not been modified, return the source right away
+ if (!_modified) {
+ // if image was not loaded from binary string
+ if (!_binStr) {
+ _binStr = _toBinary(me.getAsDataURL(type, quality));
+ }
+ return _binStr;
+ }
+
+ if ('image/jpeg' !== type) {
+ _binStr = _toBinary(me.getAsDataURL(type, quality));
+ } else {
+ var dataUrl;
+
+ // if jpeg
+ if (!quality) {
+ quality = 90;
+ }
+
+ try {
+ // older Geckos used to result in an exception on quality argument
+ dataUrl = _canvas.toDataURL('image/jpeg', quality/100);
+ } catch (ex) {
+ dataUrl = _canvas.toDataURL('image/jpeg');
+ }
+
+ _binStr = _toBinary(dataUrl);
+
+ if (_imgInfo) {
+ _binStr = _imgInfo.stripHeaders(_binStr);
+
+ if (_preserveHeaders) {
+ // update dimensions info in exif
+ if (_imgInfo.meta && _imgInfo.meta.exif) {
+ _imgInfo.setExif({
+ PixelXDimension: this.width,
+ PixelYDimension: this.height
+ });
+ }
+
+ // re-inject the headers
+ _binStr = _imgInfo.writeHeaders(_binStr);
+ }
+
+ // will be re-created from fresh on next getInfo call
+ _imgInfo.purge();
+ _imgInfo = null;
+ }
+ }
+
+ _modified = false;
+
+ return _binStr;
+ },
+
+ destroy: function() {
+ me = null;
+ _purge.call(this);
+ this.getRuntime().getShim().removeInstance(this.uid);
+ }
+ });
+
+
+ function _getImg() {
+ if (!_canvas && !_img) {
+ throw new x.ImageError(x.DOMException.INVALID_STATE_ERR);
+ }
+ return _canvas || _img;
+ }
+
+
+ function _toBinary(str) {
+ return Encode.atob(str.substring(str.indexOf('base64,') + 7));
+ }
+
+
+ function _toDataUrl(str, type) {
+ return 'data:' + (type || '') + ';base64,' + Encode.btoa(str);
+ }
+
+
+ function _preload(str) {
+ var comp = this;
+
+ _img = new Image();
+ _img.onerror = function() {
+ _purge.call(this);
+ comp.trigger('error', new x.ImageError(x.ImageError.WRONG_FORMAT));
+ };
+ _img.onload = function() {
+ comp.trigger('load');
+ };
+
+ _img.src = /^data:[^;]*;base64,/.test(str) ? str : _toDataUrl(str, _blob.type);
+ }
+
+
+ function _readAsDataUrl(file, callback) {
+ var comp = this, fr;
+
+ // use FileReader if it's available
+ if (window.FileReader) {
+ fr = new FileReader();
+ fr.onload = function() {
+ callback(this.result);
+ };
+ fr.onerror = function() {
+ comp.trigger('error', new x.FileException(x.FileException.NOT_READABLE_ERR));
+ };
+ fr.readAsDataURL(file);
+ } else {
+ return callback(file.getAsDataURL());
+ }
+ }
+
+ function _downsize(width, height, crop, preserveHeaders) {
+ var self = this
+ , scale
+ , mathFn
+ , x = 0
+ , y = 0
+ , img
+ , destWidth
+ , destHeight
+ , orientation
+ ;
+
+ _preserveHeaders = preserveHeaders; // we will need to check this on export (see getAsBinaryString())
+
+ // take into account orientation tag
+ orientation = (this.meta && this.meta.tiff && this.meta.tiff.Orientation) || 1;
+
+ if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation
+ // swap dimensions
+ var tmp = width;
+ width = height;
+ height = tmp;
+ }
+
+ img = _getImg();
+
+ // unify dimensions
+ mathFn = !crop ? Math.min : Math.max;
+ scale = mathFn(width/img.width, height/img.height);
+
+ // we only downsize here
+ if (scale > 1 && (!crop || preserveHeaders)) { // when cropping one of dimensions may still exceed max, so process it anyway
+ this.trigger('Resize');
+ return;
+ }
+
+ // prepare canvas if necessary
+ if (!_canvas) {
+ _canvas = document.createElement("canvas");
+ }
+
+ // calculate dimensions of proportionally resized image
+ destWidth = Math.round(img.width * scale);
+ destHeight = Math.round(img.height * scale);
+
+
+ // scale image and canvas
+ if (crop) {
+ _canvas.width = width;
+ _canvas.height = height;
+
+ // if dimensions of the resulting image still larger than canvas, center it
+ if (destWidth > width) {
+ x = Math.round((destWidth - width) / 2);
+ }
+
+ if (destHeight > height) {
+ y = Math.round((destHeight - height) / 2);
+ }
+ } else {
+ _canvas.width = destWidth;
+ _canvas.height = destHeight;
+ }
+
+ // rotate if required, according to orientation tag
+ if (!_preserveHeaders) {
+ _rotateToOrientaion(_canvas.width, _canvas.height, orientation);
+ }
+
+ _drawToCanvas.call(this, img, _canvas, -x, -y, destWidth, destHeight);
+
+ this.width = _canvas.width;
+ this.height = _canvas.height;
+
+ _modified = true;
+ self.trigger('Resize');
+ }
+
+
+ function _drawToCanvas(img, canvas, x, y, w, h) {
+ if (Env.OS === 'iOS') {
+ // avoid squish bug in iOS6
+ MegaPixel.renderTo(img, canvas, { width: w, height: h, x: x, y: y });
+ } else {
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, x, y, w, h);
+ }
+ }
+
+
+ /**
+ * Transform canvas coordination according to specified frame size and orientation
+ * Orientation value is from EXIF tag
+ * @author Shinichi Tomita <shinichi.tomita@gmail.com>
+ */
+ function _rotateToOrientaion(width, height, orientation) {
+ switch (orientation) {
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ _canvas.width = height;
+ _canvas.height = width;
+ break;
+ default:
+ _canvas.width = width;
+ _canvas.height = height;
+ }
+
+ /**
+ 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
+ 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
+ 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
+ 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
+ 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
+ 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
+ 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
+ 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
+ */
+
+ var ctx = _canvas.getContext('2d');
+ switch (orientation) {
+ case 2:
+ // horizontal flip
+ ctx.translate(width, 0);
+ ctx.scale(-1, 1);
+ break;
+ case 3:
+ // 180 rotate left
+ ctx.translate(width, height);
+ ctx.rotate(Math.PI);
+ break;
+ case 4:
+ // vertical flip
+ ctx.translate(0, height);
+ ctx.scale(1, -1);
+ break;
+ case 5:
+ // vertical flip + 90 rotate right
+ ctx.rotate(0.5 * Math.PI);
+ ctx.scale(1, -1);
+ break;
+ case 6:
+ // 90 rotate right
+ ctx.rotate(0.5 * Math.PI);
+ ctx.translate(0, -height);
+ break;
+ case 7:
+ // horizontal flip + 90 rotate right
+ ctx.rotate(0.5 * Math.PI);
+ ctx.translate(width, -height);
+ ctx.scale(-1, 1);
+ break;
+ case 8:
+ // 90 rotate left
+ ctx.rotate(-0.5 * Math.PI);
+ ctx.translate(-width, 0);
+ break;
+ }
+ }
+
+
+ function _purge() {
+ if (_imgInfo) {
+ _imgInfo.purge();
+ _imgInfo = null;
+ }
+ _binStr = _img = _canvas = _blob = null;
+ _modified = false;
+ }
+ }
+
+ return (extensions.Image = HTML5Image);
+});
+
+// Included from: src/javascript/runtime/flash/Runtime.js
+
+/**
+ * Runtime.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global ActiveXObject:true */
+
+/**
+Defines constructor for Flash runtime.
+
+@class moxie/runtime/flash/Runtime
+@private
+*/
+define("moxie/runtime/flash/Runtime", [
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Env",
+ "moxie/core/utils/Dom",
+ "moxie/core/Exceptions",
+ "moxie/runtime/Runtime"
+], function(Basic, Env, Dom, x, Runtime) {
+
+ var type = 'flash', extensions = {};
+
+ /**
+ Get the version of the Flash Player
+
+ @method getShimVersion
+ @private
+ @return {Number} Flash Player version
+ */
+ function getShimVersion() {
+ var version;
+
+ try {
+ version = navigator.plugins['Shockwave Flash'];
+ version = version.description;
+ } catch (e1) {
+ try {
+ version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+ } catch (e2) {
+ version = '0.0';
+ }
+ }
+ version = version.match(/\d+/g);
+ return parseFloat(version[0] + '.' + version[1]);
+ }
+
+ /**
+ Constructor for the Flash Runtime
+
+ @class FlashRuntime
+ @extends Runtime
+ */
+ function FlashRuntime(options) {
+ var I = this, initTimer;
+
+ options = Basic.extend({ swf_url: Env.swf_url }, options);
+
+ Runtime.call(this, options, type, {
+ access_binary: function(value) {
+ return value && I.mode === 'browser';
+ },
+ access_image_binary: function(value) {
+ return value && I.mode === 'browser';
+ },
+ display_media: Runtime.capTrue,
+ do_cors: Runtime.capTrue,
+ drag_and_drop: false,
+ report_upload_progress: function() {
+ return I.mode === 'client';
+ },
+ resize_image: Runtime.capTrue,
+ return_response_headers: false,
+ return_response_type: function(responseType) {
+ if (responseType === 'json' && !!window.JSON) {
+ return true;
+ }
+ return !Basic.arrayDiff(responseType, ['', 'text', 'document']) || I.mode === 'browser';
+ },
+ return_status_code: function(code) {
+ return I.mode === 'browser' || !Basic.arrayDiff(code, [200, 404]);
+ },
+ select_file: Runtime.capTrue,
+ select_multiple: Runtime.capTrue,
+ send_binary_string: function(value) {
+ return value && I.mode === 'browser';
+ },
+ send_browser_cookies: function(value) {
+ return value && I.mode === 'browser';
+ },
+ send_custom_headers: function(value) {
+ return value && I.mode === 'browser';
+ },
+ send_multipart: Runtime.capTrue,
+ slice_blob: Runtime.capTrue,
+ stream_upload: function(value) {
+ return value && I.mode === 'browser';
+ },
+ summon_file_dialog: false,
+ upload_filesize: function(size) {
+ return Basic.parseSizeStr(size) <= 2097152 || I.mode === 'client';
+ },
+ use_http_method: function(methods) {
+ return !Basic.arrayDiff(methods, ['GET', 'POST']);
+ }
+ }, {
+ // capabilities that require specific mode
+ access_binary: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ access_image_binary: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ report_upload_progress: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ return_response_type: function(responseType) {
+ return Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) ? 'browser' : ['client', 'browser'];
+ },
+ return_status_code: function(code) {
+ return Basic.arrayDiff(code, [200, 404]) ? 'browser' : ['client', 'browser'];
+ },
+ send_binary_string: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ send_browser_cookies: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ send_custom_headers: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ stream_upload: function(value) {
+ return value ? 'client' : 'browser';
+ },
+ upload_filesize: function(size) {
+ return Basic.parseSizeStr(size) >= 2097152 ? 'client' : 'browser';
+ }
+ }, 'client');
+
+
+ // minimal requirement for Flash Player version
+ if (getShimVersion() < 11.3) {
+ this.mode = false; // with falsy mode, runtime won't operable, no matter what the mode was before
+ }
+
+
+ Basic.extend(this, {
+
+ getShim: function() {
+ return Dom.get(this.uid);
+ },
+
+ shimExec: function(component, action) {
+ var args = [].slice.call(arguments, 2);
+ return I.getShim().exec(this.uid, component, action, args);
+ },
+
+ init: function() {
+ var html, el, container;
+
+ container = this.getShimContainer();
+
+ // if not the minimal height, shims are not initialized in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
+ Basic.extend(container.style, {
+ position: 'absolute',
+ top: '-8px',
+ left: '-8px',
+ width: '9px',
+ height: '9px',
+ overflow: 'hidden'
+ });
+
+ // insert flash object
+ html = '<object id="' + this.uid + '" type="application/x-shockwave-flash" data="' + options.swf_url + '" ';
+
+ if (Env.browser === 'IE') {
+ html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
+ }
+
+ html += 'width="100%" height="100%" style="outline:0">' +
+ '<param name="movie" value="' + options.swf_url + '" />' +
+ '<param name="flashvars" value="uid=' + escape(this.uid) + '&target=' + Env.global_event_dispatcher + '" />' +
+ '<param name="wmode" value="transparent" />' +
+ '<param name="allowscriptaccess" value="always" />' +
+ '</object>';
+
+ if (Env.browser === 'IE') {
+ el = document.createElement('div');
+ container.appendChild(el);
+ el.outerHTML = html;
+ el = container = null; // just in case
+ } else {
+ container.innerHTML = html;
+ }
+
+ // Init is dispatched by the shim
+ initTimer = setTimeout(function() {
+ if (I && !I.initialized) { // runtime might be already destroyed by this moment
+ I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
+ }
+ }, 5000);
+ },
+
+ destroy: (function(destroy) { // extend default destroy method
+ return function() {
+ destroy.call(I);
+ clearTimeout(initTimer); // initialization check might be still onwait
+ options = initTimer = destroy = I = null;
+ };
+ }(this.destroy))
+
+ }, extensions);
+ }
+
+ Runtime.addConstructor(type, FlashRuntime);
+
+ return extensions;
+});
+
+// Included from: src/javascript/runtime/flash/file/Blob.js
+
+/**
+ * Blob.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/file/Blob
+@private
+*/
+define("moxie/runtime/flash/file/Blob", [
+ "moxie/runtime/flash/Runtime",
+ "moxie/file/Blob"
+], function(extensions, Blob) {
+
+ var FlashBlob = {
+ slice: function(blob, start, end, type) {
+ var self = this.getRuntime();
+
+ if (start < 0) {
+ start = Math.max(blob.size + start, 0);
+ } else if (start > 0) {
+ start = Math.min(start, blob.size);
+ }
+
+ if (end < 0) {
+ end = Math.max(blob.size + end, 0);
+ } else if (end > 0) {
+ end = Math.min(end, blob.size);
+ }
+
+ blob = self.shimExec.call(this, 'Blob', 'slice', start, end, type || '');
+
+ if (blob) {
+ blob = new Blob(self.uid, blob);
+ }
+ return blob;
+ }
+ };
+
+ return (extensions.Blob = FlashBlob);
+});
+
+// Included from: src/javascript/runtime/flash/file/FileInput.js
+
+/**
+ * FileInput.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/file/FileInput
+@private
+*/
+define("moxie/runtime/flash/file/FileInput", [
+ "moxie/runtime/flash/Runtime"
+], function(extensions) {
+
+ var FileInput = {
+ init: function(options) {
+ this.getRuntime().shimExec.call(this, 'FileInput', 'init', {
+ name: options.name,
+ accept: options.accept,
+ multiple: options.multiple
+ });
+ this.trigger('ready');
+ }
+ };
+
+ return (extensions.FileInput = FileInput);
+});
+
+// Included from: src/javascript/runtime/flash/file/FileReader.js
+
+/**
+ * FileReader.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/file/FileReader
+@private
+*/
+define("moxie/runtime/flash/file/FileReader", [
+ "moxie/runtime/flash/Runtime",
+ "moxie/core/utils/Encode"
+], function(extensions, Encode) {
+
+ var _result = '';
+
+ function _formatData(data, op) {
+ switch (op) {
+ case 'readAsText':
+ return Encode.atob(data, 'utf8');
+ case 'readAsBinaryString':
+ return Encode.atob(data);
+ case 'readAsDataURL':
+ return data;
+ }
+ return null;
+ }
+
+ var FileReader = {
+ read: function(op, blob) {
+ var target = this, self = target.getRuntime();
+
+ // special prefix for DataURL read mode
+ if (op === 'readAsDataURL') {
+ _result = 'data:' + (blob.type || '') + ';base64,';
+ }
+
+ target.bind('Progress', function(e, data) {
+ if (data) {
+ _result += _formatData(data, op);
+ }
+ });
+
+ return self.shimExec.call(this, 'FileReader', 'readAsBase64', blob.uid);
+ },
+
+ getResult: function() {
+ return _result;
+ },
+
+ destroy: function() {
+ _result = null;
+ }
+ };
+
+ return (extensions.FileReader = FileReader);
+});
+
+// Included from: src/javascript/runtime/flash/file/FileReaderSync.js
+
+/**
+ * FileReaderSync.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/file/FileReaderSync
+@private
+*/
+define("moxie/runtime/flash/file/FileReaderSync", [
+ "moxie/runtime/flash/Runtime",
+ "moxie/core/utils/Encode"
+], function(extensions, Encode) {
+
+ function _formatData(data, op) {
+ switch (op) {
+ case 'readAsText':
+ return Encode.atob(data, 'utf8');
+ case 'readAsBinaryString':
+ return Encode.atob(data);
+ case 'readAsDataURL':
+ return data;
+ }
+ return null;
+ }
+
+ var FileReaderSync = {
+ read: function(op, blob) {
+ var result, self = this.getRuntime();
+
+ result = self.shimExec.call(this, 'FileReaderSync', 'readAsBase64', blob.uid);
+ if (!result) {
+ return null; // or throw ex
+ }
+
+ // special prefix for DataURL read mode
+ if (op === 'readAsDataURL') {
+ result = 'data:' + (blob.type || '') + ';base64,' + result;
+ }
+
+ return _formatData(result, op, blob.type);
+ }
+ };
+
+ return (extensions.FileReaderSync = FileReaderSync);
+});
+
+// Included from: src/javascript/runtime/flash/xhr/XMLHttpRequest.js
+
+/**
+ * XMLHttpRequest.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/xhr/XMLHttpRequest
+@private
+*/
+define("moxie/runtime/flash/xhr/XMLHttpRequest", [
+ "moxie/runtime/flash/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/file/Blob",
+ "moxie/file/File",
+ "moxie/file/FileReaderSync",
+ "moxie/xhr/FormData",
+ "moxie/runtime/Transporter"
+], function(extensions, Basic, Blob, File, FileReaderSync, FormData, Transporter) {
+
+ var XMLHttpRequest = {
+
+ send: function(meta, data) {
+ var target = this, self = target.getRuntime();
+
+ function send() {
+ meta.transport = self.mode;
+ self.shimExec.call(target, 'XMLHttpRequest', 'send', meta, data);
+ }
+
+
+ function appendBlob(name, blob) {
+ self.shimExec.call(target, 'XMLHttpRequest', 'appendBlob', name, blob.uid);
+ data = null;
+ send();
+ }
+
+
+ function attachBlob(blob, cb) {
+ var tr = new Transporter();
+
+ tr.bind("TransportingComplete", function() {
+ cb(this.result);
+ });
+
+ tr.transport(blob.getSource(), blob.type, {
+ ruid: self.uid
+ });
+ }
+
+ // copy over the headers if any
+ if (!Basic.isEmptyObj(meta.headers)) {
+ Basic.each(meta.headers, function(value, header) {
+ self.shimExec.call(target, 'XMLHttpRequest', 'setRequestHeader', header, value.toString()); // Silverlight doesn't accept integers into the arguments of type object
+ });
+ }
+
+ // transfer over multipart params and blob itself
+ if (data instanceof FormData) {
+ var blobField;
+ data.each(function(value, name) {
+ if (value instanceof Blob) {
+ blobField = name;
+ } else {
+ self.shimExec.call(target, 'XMLHttpRequest', 'append', name, value);
+ }
+ });
+
+ if (!data.hasBlob()) {
+ data = null;
+ send();
+ } else {
+ var blob = data.getBlob();
+ if (blob.isDetached()) {
+ attachBlob(blob, function(attachedBlob) {
+ blob.destroy();
+ appendBlob(blobField, attachedBlob);
+ });
+ } else {
+ appendBlob(blobField, blob);
+ }
+ }
+ } else if (data instanceof Blob) {
+ if (data.isDetached()) {
+ attachBlob(data, function(attachedBlob) {
+ data.destroy();
+ data = attachedBlob.uid;
+ send();
+ });
+ } else {
+ data = data.uid;
+ send();
+ }
+ } else {
+ send();
+ }
+ },
+
+ getResponse: function(responseType) {
+ var frs, blob, self = this.getRuntime();
+
+ blob = self.shimExec.call(this, 'XMLHttpRequest', 'getResponseAsBlob');
+
+ if (blob) {
+ blob = new File(self.uid, blob);
+
+ if ('blob' === responseType) {
+ return blob;
+ } else if (!!~Basic.inArray(responseType, ["", "text"])) {
+ frs = new FileReaderSync();
+ return frs.readAsText(blob);
+ } else if ('arraybuffer' === responseType) {
+
+ // do something
+
+ } else if ('json' === responseType && !!window.JSON) {
+ frs = new FileReaderSync();
+ try {
+ return JSON.parse(frs.readAsText(blob));
+ } catch (ex) {}
+ }
+ }
+
+ return null;
+ },
+
+ abort: function(upload_complete_flag) {
+ var self = this.getRuntime();
+
+ self.shimExec.call(this, 'XMLHttpRequest', 'abort');
+
+ this.dispatchEvent('readystatechange');
+ // this.dispatchEvent('progress');
+ this.dispatchEvent('abort');
+
+ if (!upload_complete_flag) {
+ // this.dispatchEvent('uploadprogress');
+ }
+ }
+ };
+
+ return (extensions.XMLHttpRequest = XMLHttpRequest);
+});
+
+// Included from: src/javascript/runtime/flash/runtime/Transporter.js
+
+/**
+ * Transporter.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/runtime/Transporter
+@private
+*/
+define("moxie/runtime/flash/runtime/Transporter", [
+ "moxie/runtime/flash/Runtime",
+ "moxie/file/Blob"
+], function(extensions, Blob) {
+
+ var Transporter = {
+ getAsBlob: function(type) {
+ var self = this.getRuntime()
+ , blob = self.shimExec.call(this, 'Transporter', 'getAsBlob', type)
+ ;
+ if (blob) {
+ return new Blob(self.uid, blob);
+ }
+ return null;
+ }
+ };
+
+ return (extensions.Transporter = Transporter);
+});
+
+// Included from: src/javascript/runtime/flash/image/Image.js
+
+/**
+ * Image.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/flash/image/Image
+@private
+*/
+define("moxie/runtime/flash/image/Image", [
+ "moxie/runtime/flash/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/Transporter",
+ "moxie/file/Blob",
+ "moxie/file/FileReaderSync"
+], function(extensions, Basic, Transporter, Blob, FileReaderSync) {
+
+ var Image = {
+ loadFromBlob: function(blob) {
+ var comp = this, self = comp.getRuntime();
+
+ function exec(srcBlob) {
+ self.shimExec.call(comp, 'Image', 'loadFromBlob', srcBlob.uid);
+ comp = self = null;
+ }
+
+ if (blob.isDetached()) { // binary string
+ var tr = new Transporter();
+ tr.bind("TransportingComplete", function() {
+ exec(tr.result.getSource());
+ });
+ tr.transport(blob.getSource(), blob.type, { ruid: self.uid });
+ } else {
+ exec(blob.getSource());
+ }
+ },
+
+ loadFromImage: function(img) {
+ var self = this.getRuntime();
+ return self.shimExec.call(this, 'Image', 'loadFromImage', img.uid);
+ },
+
+ getAsBlob: function(type, quality) {
+ var self = this.getRuntime()
+ , blob = self.shimExec.call(this, 'Image', 'getAsBlob', type, quality)
+ ;
+ if (blob) {
+ return new Blob(self.uid, blob);
+ }
+ return null;
+ },
+
+ getAsDataURL: function() {
+ var self = this.getRuntime()
+ , blob = self.Image.getAsBlob.apply(this, arguments)
+ , frs
+ ;
+ if (!blob) {
+ return null;
+ }
+ frs = new FileReaderSync();
+ return frs.readAsDataURL(blob);
+ }
+ };
+
+ return (extensions.Image = Image);
+});
+
+// Included from: src/javascript/runtime/silverlight/Runtime.js
+
+/**
+ * RunTime.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global ActiveXObject:true */
+
+/**
+Defines constructor for Silverlight runtime.
+
+@class moxie/runtime/silverlight/Runtime
+@private
+*/
+define("moxie/runtime/silverlight/Runtime", [
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Env",
+ "moxie/core/utils/Dom",
+ "moxie/core/Exceptions",
+ "moxie/runtime/Runtime"
+], function(Basic, Env, Dom, x, Runtime) {
+
+ var type = "silverlight", extensions = {};
+
+ function isInstalled(version) {
+ var isVersionSupported = false, control = null, actualVer,
+ actualVerArray, reqVerArray, requiredVersionPart, actualVersionPart, index = 0;
+
+ try {
+ try {
+ control = new ActiveXObject('AgControl.AgControl');
+
+ if (control.IsVersionSupported(version)) {
+ isVersionSupported = true;
+ }
+
+ control = null;
+ } catch (e) {
+ var plugin = navigator.plugins["Silverlight Plug-In"];
+
+ if (plugin) {
+ actualVer = plugin.description;
+
+ if (actualVer === "1.0.30226.2") {
+ actualVer = "2.0.30226.2";
+ }
+
+ actualVerArray = actualVer.split(".");
+
+ while (actualVerArray.length > 3) {
+ actualVerArray.pop();
+ }
+
+ while ( actualVerArray.length < 4) {
+ actualVerArray.push(0);
+ }
+
+ reqVerArray = version.split(".");
+
+ while (reqVerArray.length > 4) {
+ reqVerArray.pop();
+ }
+
+ do {
+ requiredVersionPart = parseInt(reqVerArray[index], 10);
+ actualVersionPart = parseInt(actualVerArray[index], 10);
+ index++;
+ } while (index < reqVerArray.length && requiredVersionPart === actualVersionPart);
+
+ if (requiredVersionPart <= actualVersionPart && !isNaN(requiredVersionPart)) {
+ isVersionSupported = true;
+ }
+ }
+ }
+ } catch (e2) {
+ isVersionSupported = false;
+ }
+
+ return isVersionSupported;
+ }
+
+ /**
+ Constructor for the Silverlight Runtime
+
+ @class SilverlightRuntime
+ @extends Runtime
+ */
+ function SilverlightRuntime(options) {
+ var I = this, initTimer;
+
+ options = Basic.extend({ xap_url: Env.xap_url }, options);
+
+ Runtime.call(this, options, type, {
+ access_binary: Runtime.capTrue,
+ access_image_binary: Runtime.capTrue,
+ display_media: Runtime.capTrue,
+ do_cors: Runtime.capTrue,
+ drag_and_drop: false,
+ report_upload_progress: Runtime.capTrue,
+ resize_image: Runtime.capTrue,
+ return_response_headers: function(value) {
+ return value && I.mode === 'client';
+ },
+ return_response_type: function(responseType) {
+ if (responseType !== 'json') {
+ return true;
+ } else {
+ return !!window.JSON;
+ }
+ },
+ return_status_code: function(code) {
+ return I.mode === 'client' || !Basic.arrayDiff(code, [200, 404]);
+ },
+ select_file: Runtime.capTrue,
+ select_multiple: Runtime.capTrue,
+ send_binary_string: Runtime.capTrue,
+ send_browser_cookies: function(value) {
+ return value && I.mode === 'browser';
+ },
+ send_custom_headers: function(value) {
+ return value && I.mode === 'client';
+ },
+ send_multipart: Runtime.capTrue,
+ slice_blob: Runtime.capTrue,
+ stream_upload: true,
+ summon_file_dialog: false,
+ upload_filesize: Runtime.capTrue,
+ use_http_method: function(methods) {
+ return I.mode === 'client' || !Basic.arrayDiff(methods, ['GET', 'POST']);
+ }
+ }, {
+ // capabilities that require specific mode
+ return_response_headers: function(value) {
+ return value ? 'client' : 'browser';
+ },
+ return_status_code: function(code) {
+ return Basic.arrayDiff(code, [200, 404]) ? 'client' : ['client', 'browser'];
+ },
+ send_browser_cookies: function(value) {
+ return value ? 'browser' : 'client';
+ },
+ send_custom_headers: function(value) {
+ return value ? 'client' : 'browser';
+ },
+ use_http_method: function(methods) {
+ return Basic.arrayDiff(methods, ['GET', 'POST']) ? 'client' : ['client', 'browser'];
+ }
+ });
+
+
+ // minimal requirement
+ if (!isInstalled('2.0.31005.0') || Env.browser === 'Opera') {
+ this.mode = false;
+ }
+
+
+ Basic.extend(this, {
+ getShim: function() {
+ return Dom.get(this.uid).content.Moxie;
+ },
+
+ shimExec: function(component, action) {
+ var args = [].slice.call(arguments, 2);
+ return I.getShim().exec(this.uid, component, action, args);
+ },
+
+ init : function() {
+ var container;
+
+ container = this.getShimContainer();
+
+ container.innerHTML = '<object id="' + this.uid + '" data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%" style="outline:none;">' +
+ '<param name="source" value="' + options.xap_url + '"/>' +
+ '<param name="background" value="Transparent"/>' +
+ '<param name="windowless" value="true"/>' +
+ '<param name="enablehtmlaccess" value="true"/>' +
+ '<param name="initParams" value="uid=' + this.uid + ',target=' + Env.global_event_dispatcher + '"/>' +
+ '</object>';
+
+ // Init is dispatched by the shim
+ initTimer = setTimeout(function() {
+ if (I && !I.initialized) { // runtime might be already destroyed by this moment
+ I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
+ }
+ }, Env.OS !== 'Windows'? 10000 : 5000); // give it more time to initialize in non Windows OS (like Mac)
+ },
+
+ destroy: (function(destroy) { // extend default destroy method
+ return function() {
+ destroy.call(I);
+ clearTimeout(initTimer); // initialization check might be still onwait
+ options = initTimer = destroy = I = null;
+ };
+ }(this.destroy))
+
+ }, extensions);
+ }
+
+ Runtime.addConstructor(type, SilverlightRuntime);
+
+ return extensions;
+});
+
+// Included from: src/javascript/runtime/silverlight/file/Blob.js
+
+/**
+ * Blob.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/file/Blob
+@private
+*/
+define("moxie/runtime/silverlight/file/Blob", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/flash/file/Blob"
+], function(extensions, Basic, Blob) {
+ return (extensions.Blob = Basic.extend({}, Blob));
+});
+
+// Included from: src/javascript/runtime/silverlight/file/FileInput.js
+
+/**
+ * FileInput.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/file/FileInput
+@private
+*/
+define("moxie/runtime/silverlight/file/FileInput", [
+ "moxie/runtime/silverlight/Runtime"
+], function(extensions) {
+
+ var FileInput = {
+ init: function(options) {
+
+ function toFilters(accept) {
+ var filter = '';
+ for (var i = 0; i < accept.length; i++) {
+ filter += (filter !== '' ? '|' : '') + accept[i].title + " | *." + accept[i].extensions.replace(/,/g, ';*.');
+ }
+ return filter;
+ }
+
+ this.getRuntime().shimExec.call(this, 'FileInput', 'init', toFilters(options.accept), options.name, options.multiple);
+ this.trigger('ready');
+ }
+ };
+
+ return (extensions.FileInput = FileInput);
+});
+
+// Included from: src/javascript/runtime/silverlight/file/FileDrop.js
+
+/**
+ * FileDrop.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/file/FileDrop
+@private
+*/
+define("moxie/runtime/silverlight/file/FileDrop", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Dom",
+ "moxie/core/utils/Events"
+], function(extensions, Dom, Events) {
+
+ // not exactly useful, since works only in safari (...crickets...)
+ var FileDrop = {
+ init: function() {
+ var comp = this, self = comp.getRuntime(), dropZone;
+
+ dropZone = self.getShimContainer();
+
+ Events.addEvent(dropZone, 'dragover', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.dataTransfer.dropEffect = 'copy';
+ }, comp.uid);
+
+ Events.addEvent(dropZone, 'dragenter', function(e) {
+ e.preventDefault();
+ var flag = Dom.get(self.uid).dragEnter(e);
+ // If handled, then stop propagation of event in DOM
+ if (flag) {
+ e.stopPropagation();
+ }
+ }, comp.uid);
+
+ Events.addEvent(dropZone, 'drop', function(e) {
+ e.preventDefault();
+ var flag = Dom.get(self.uid).dragDrop(e);
+ // If handled, then stop propagation of event in DOM
+ if (flag) {
+ e.stopPropagation();
+ }
+ }, comp.uid);
+
+ return self.shimExec.call(this, 'FileDrop', 'init');
+ }
+ };
+
+ return (extensions.FileDrop = FileDrop);
+});
+
+// Included from: src/javascript/runtime/silverlight/file/FileReader.js
+
+/**
+ * FileReader.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/file/FileReader
+@private
+*/
+define("moxie/runtime/silverlight/file/FileReader", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/flash/file/FileReader"
+], function(extensions, Basic, FileReader) {
+ return (extensions.FileReader = Basic.extend({}, FileReader));
+});
+
+// Included from: src/javascript/runtime/silverlight/file/FileReaderSync.js
+
+/**
+ * FileReaderSync.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/file/FileReaderSync
+@private
+*/
+define("moxie/runtime/silverlight/file/FileReaderSync", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/flash/file/FileReaderSync"
+], function(extensions, Basic, FileReaderSync) {
+ return (extensions.FileReaderSync = Basic.extend({}, FileReaderSync));
+});
+
+// Included from: src/javascript/runtime/silverlight/xhr/XMLHttpRequest.js
+
+/**
+ * XMLHttpRequest.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/xhr/XMLHttpRequest
+@private
+*/
+define("moxie/runtime/silverlight/xhr/XMLHttpRequest", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/flash/xhr/XMLHttpRequest"
+], function(extensions, Basic, XMLHttpRequest) {
+ return (extensions.XMLHttpRequest = Basic.extend({}, XMLHttpRequest));
+});
+
+// Included from: src/javascript/runtime/silverlight/runtime/Transporter.js
+
+/**
+ * Transporter.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/runtime/Transporter
+@private
+*/
+define("moxie/runtime/silverlight/runtime/Transporter", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/flash/runtime/Transporter"
+], function(extensions, Basic, Transporter) {
+ return (extensions.Transporter = Basic.extend({}, Transporter));
+});
+
+// Included from: src/javascript/runtime/silverlight/image/Image.js
+
+/**
+ * Image.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/silverlight/image/Image
+@private
+*/
+define("moxie/runtime/silverlight/image/Image", [
+ "moxie/runtime/silverlight/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/runtime/flash/image/Image"
+], function(extensions, Basic, Image) {
+ return (extensions.Image = Basic.extend({}, Image, {
+
+ getInfo: function() {
+ var self = this.getRuntime()
+ , grps = ['tiff', 'exif', 'gps']
+ , info = { meta: {} }
+ , rawInfo = self.shimExec.call(this, 'Image', 'getInfo')
+ ;
+
+ if (rawInfo.meta) {
+ Basic.each(grps, function(grp) {
+ var meta = rawInfo.meta[grp]
+ , tag
+ , i
+ , length
+ , value
+ ;
+ if (meta && meta.keys) {
+ info.meta[grp] = {};
+ for (i = 0, length = meta.keys.length; i < length; i++) {
+ tag = meta.keys[i];
+ value = meta[tag];
+ if (value) {
+ // convert numbers
+ if (/^(\d|[1-9]\d+)$/.test(value)) { // integer (make sure doesn't start with zero)
+ value = parseInt(value, 10);
+ } else if (/^\d*\.\d+$/.test(value)) { // double
+ value = parseFloat(value);
+ }
+ info.meta[grp][tag] = value;
+ }
+ }
+ }
+ });
+ }
+
+ info.width = parseInt(rawInfo.width, 10);
+ info.height = parseInt(rawInfo.height, 10);
+ info.size = parseInt(rawInfo.size, 10);
+ info.type = rawInfo.type;
+ info.name = rawInfo.name;
+
+ return info;
+ }
+ }));
+});
+
+// Included from: src/javascript/runtime/html4/Runtime.js
+
+/**
+ * Runtime.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global File:true */
+
+/**
+Defines constructor for HTML4 runtime.
+
+@class moxie/runtime/html4/Runtime
+@private
+*/
+define("moxie/runtime/html4/Runtime", [
+ "moxie/core/utils/Basic",
+ "moxie/core/Exceptions",
+ "moxie/runtime/Runtime",
+ "moxie/core/utils/Env"
+], function(Basic, x, Runtime, Env) {
+
+ var type = 'html4', extensions = {};
+
+ function Html4Runtime(options) {
+ var I = this
+ , Test = Runtime.capTest
+ , True = Runtime.capTrue
+ ;
+
+ Runtime.call(this, options, type, {
+ access_binary: Test(window.FileReader || window.File && File.getAsDataURL),
+ access_image_binary: false,
+ display_media: Test(extensions.Image && (Env.can('create_canvas') || Env.can('use_data_uri_over32kb'))),
+ do_cors: false,
+ drag_and_drop: false,
+ filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
+ return (Env.browser === 'Chrome' && Env.version >= 28) || (Env.browser === 'IE' && Env.version >= 10);
+ }()),
+ resize_image: function() {
+ return extensions.Image && I.can('access_binary') && Env.can('create_canvas');
+ },
+ report_upload_progress: false,
+ return_response_headers: false,
+ return_response_type: function(responseType) {
+ if (responseType === 'json' && !!window.JSON) {
+ return true;
+ }
+ return !!~Basic.inArray(responseType, ['text', 'document', '']);
+ },
+ return_status_code: function(code) {
+ return !Basic.arrayDiff(code, [200, 404]);
+ },
+ select_file: function() {
+ return Env.can('use_fileinput');
+ },
+ select_multiple: false,
+ send_binary_string: false,
+ send_custom_headers: false,
+ send_multipart: true,
+ slice_blob: false,
+ stream_upload: function() {
+ return I.can('select_file');
+ },
+ summon_file_dialog: Test(function() { // yeah... some dirty sniffing here...
+ return (Env.browser === 'Firefox' && Env.version >= 4) ||
+ (Env.browser === 'Opera' && Env.version >= 12) ||
+ !!~Basic.inArray(Env.browser, ['Chrome', 'Safari']);
+ }()),
+ upload_filesize: True,
+ use_http_method: function(methods) {
+ return !Basic.arrayDiff(methods, ['GET', 'POST']);
+ }
+ });
+
+
+ Basic.extend(this, {
+ init : function() {
+ this.trigger("Init");
+ },
+
+ destroy: (function(destroy) { // extend default destroy method
+ return function() {
+ destroy.call(I);
+ destroy = I = null;
+ };
+ }(this.destroy))
+ });
+
+ Basic.extend(this.getShim(), extensions);
+ }
+
+ Runtime.addConstructor(type, Html4Runtime);
+
+ return extensions;
+});
+
+// Included from: src/javascript/runtime/html4/file/FileInput.js
+
+/**
+ * FileInput.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html4/file/FileInput
+@private
+*/
+define("moxie/runtime/html4/file/FileInput", [
+ "moxie/runtime/html4/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Dom",
+ "moxie/core/utils/Events",
+ "moxie/core/utils/Mime",
+ "moxie/core/utils/Env"
+], function(extensions, Basic, Dom, Events, Mime, Env) {
+
+ function FileInput() {
+ var _uid, _files = [], _mimes = [], _options;
+
+ function addInput() {
+ var comp = this, I = comp.getRuntime(), shimContainer, browseButton, currForm, form, input, uid;
+
+ uid = Basic.guid('uid_');
+
+ shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE
+
+ if (_uid) { // move previous form out of the view
+ currForm = Dom.get(_uid + '_form');
+ if (currForm) {
+ Basic.extend(currForm.style, { top: '100%' });
+ }
+ }
+
+ // build form in DOM, since innerHTML version not able to submit file for some reason
+ form = document.createElement('form');
+ form.setAttribute('id', uid + '_form');
+ form.setAttribute('method', 'post');
+ form.setAttribute('enctype', 'multipart/form-data');
+ form.setAttribute('encoding', 'multipart/form-data');
+
+ Basic.extend(form.style, {
+ overflow: 'hidden',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%'
+ });
+
+ input = document.createElement('input');
+ input.setAttribute('id', uid);
+ input.setAttribute('type', 'file');
+ input.setAttribute('name', _options.name || 'Filedata');
+ input.setAttribute('accept', _mimes.join(','));
+
+ Basic.extend(input.style, {
+ fontSize: '999px',
+ opacity: 0
+ });
+
+ form.appendChild(input);
+ shimContainer.appendChild(form);
+
+ // prepare file input to be placed underneath the browse_button element
+ Basic.extend(input.style, {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%'
+ });
+
+ if (Env.browser === 'IE' && Env.version < 10) {
+ Basic.extend(input.style, {
+ filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)"
+ });
+ }
+
+ input.onchange = function() { // there should be only one handler for this
+ var file;
+
+ if (!this.value) {
+ return;
+ }
+
+ if (this.files) {
+ file = this.files[0];
+ } else {
+ file = {
+ name: this.value
+ };
+ }
+
+ _files = [file];
+
+ this.onchange = function() {}; // clear event handler
+ addInput.call(comp);
+
+ // after file is initialized as o.File, we need to update form and input ids
+ comp.bind('change', function onChange() {
+ var input = Dom.get(uid), form = Dom.get(uid + '_form'), file;
+
+ comp.unbind('change', onChange);
+
+ if (comp.files.length && input && form) {
+ file = comp.files[0];
+
+ input.setAttribute('id', file.uid);
+ form.setAttribute('id', file.uid + '_form');
+
+ // set upload target
+ form.setAttribute('target', file.uid + '_iframe');
+ }
+ input = form = null;
+ }, 998);
+
+ input = form = null;
+ comp.trigger('change');
+ };
+
+
+ // route click event to the input
+ if (I.can('summon_file_dialog')) {
+ browseButton = Dom.get(_options.browse_button);
+ Events.removeEvent(browseButton, 'click', comp.uid);
+ Events.addEvent(browseButton, 'click', function(e) {
+ if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
+ input.click();
+ }
+ e.preventDefault();
+ }, comp.uid);
+ }
+
+ _uid = uid;
+
+ shimContainer = currForm = browseButton = null;
+ }
+
+ Basic.extend(this, {
+ init: function(options) {
+ var comp = this, I = comp.getRuntime(), shimContainer;
+
+ // figure out accept string
+ _options = options;
+ _mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension'));
+
+ shimContainer = I.getShimContainer();
+
+ (function() {
+ var browseButton, zIndex, top;
+
+ browseButton = Dom.get(options.browse_button);
+
+ // Route click event to the input[type=file] element for browsers that support such behavior
+ if (I.can('summon_file_dialog')) {
+ if (Dom.getStyle(browseButton, 'position') === 'static') {
+ browseButton.style.position = 'relative';
+ }
+
+ zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
+
+ browseButton.style.zIndex = zIndex;
+ shimContainer.style.zIndex = zIndex - 1;
+ }
+
+ /* Since we have to place input[type=file] on top of the browse_button for some browsers,
+ browse_button loses interactivity, so we restore it here */
+ top = I.can('summon_file_dialog') ? browseButton : shimContainer;
+
+ Events.addEvent(top, 'mouseover', function() {
+ comp.trigger('mouseenter');
+ }, comp.uid);
+
+ Events.addEvent(top, 'mouseout', function() {
+ comp.trigger('mouseleave');
+ }, comp.uid);
+
+ Events.addEvent(top, 'mousedown', function() {
+ comp.trigger('mousedown');
+ }, comp.uid);
+
+ Events.addEvent(Dom.get(options.container), 'mouseup', function() {
+ comp.trigger('mouseup');
+ }, comp.uid);
+
+ browseButton = null;
+ }());
+
+ addInput.call(this);
+
+ shimContainer = null;
+
+ // trigger ready event asynchronously
+ comp.trigger({
+ type: 'ready',
+ async: true
+ });
+ },
+
+ getFiles: function() {
+ return _files;
+ },
+
+ disable: function(state) {
+ var input;
+
+ if ((input = Dom.get(_uid))) {
+ input.disabled = !!state;
+ }
+ },
+
+ destroy: function() {
+ var I = this.getRuntime()
+ , shim = I.getShim()
+ , shimContainer = I.getShimContainer()
+ ;
+
+ Events.removeAllEvents(shimContainer, this.uid);
+ Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
+ Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
+
+ if (shimContainer) {
+ shimContainer.innerHTML = '';
+ }
+
+ shim.removeInstance(this.uid);
+
+ _uid = _files = _mimes = _options = shimContainer = shim = null;
+ }
+ });
+ }
+
+ return (extensions.FileInput = FileInput);
+});
+
+// Included from: src/javascript/runtime/html4/file/FileReader.js
+
+/**
+ * FileReader.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html4/file/FileReader
+@private
+*/
+define("moxie/runtime/html4/file/FileReader", [
+ "moxie/runtime/html4/Runtime",
+ "moxie/runtime/html5/file/FileReader"
+], function(extensions, FileReader) {
+ return (extensions.FileReader = FileReader);
+});
+
+// Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js
+
+/**
+ * XMLHttpRequest.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html4/xhr/XMLHttpRequest
+@private
+*/
+define("moxie/runtime/html4/xhr/XMLHttpRequest", [
+ "moxie/runtime/html4/Runtime",
+ "moxie/core/utils/Basic",
+ "moxie/core/utils/Dom",
+ "moxie/core/utils/Url",
+ "moxie/core/Exceptions",
+ "moxie/core/utils/Events",
+ "moxie/file/Blob",
+ "moxie/xhr/FormData"
+], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) {
+
+ function XMLHttpRequest() {
+ var _status, _response, _iframe;
+
+ function cleanup(cb) {
+ var target = this, uid, form, inputs, i, hasFile = false;
+
+ if (!_iframe) {
+ return;
+ }
+
+ uid = _iframe.id.replace(/_iframe$/, '');
+
+ form = Dom.get(uid + '_form');
+ if (form) {
+ inputs = form.getElementsByTagName('input');
+ i = inputs.length;
+
+ while (i--) {
+ switch (inputs[i].getAttribute('type')) {
+ case 'hidden':
+ inputs[i].parentNode.removeChild(inputs[i]);
+ break;
+ case 'file':
+ hasFile = true; // flag the case for later
+ break;
+ }
+ }
+ inputs = [];
+
+ if (!hasFile) { // we need to keep the form for sake of possible retries
+ form.parentNode.removeChild(form);
+ }
+ form = null;
+ }
+
+ // without timeout, request is marked as canceled (in console)
+ setTimeout(function() {
+ Events.removeEvent(_iframe, 'load', target.uid);
+ if (_iframe.parentNode) { // #382
+ _iframe.parentNode.removeChild(_iframe);
+ }
+
+ // check if shim container has any other children, if - not, remove it as well
+ var shimContainer = target.getRuntime().getShimContainer();
+ if (!shimContainer.children.length) {
+ shimContainer.parentNode.removeChild(shimContainer);
+ }
+
+ shimContainer = _iframe = null;
+ cb();
+ }, 1);
+ }
+
+ Basic.extend(this, {
+ send: function(meta, data) {
+ var target = this, I = target.getRuntime(), uid, form, input, blob;
+
+ _status = _response = null;
+
+ function createIframe() {
+ var container = I.getShimContainer() || document.body
+ , temp = document.createElement('div')
+ ;
+
+ // IE 6 won't be able to set the name using setAttribute or iframe.name
+ temp.innerHTML = '<iframe id="' + uid + '_iframe" name="' + uid + '_iframe" src="javascript:&quot;&quot;" style="display:none"></iframe>';
+ _iframe = temp.firstChild;
+ container.appendChild(_iframe);
+
+ /* _iframe.onreadystatechange = function() {
+ console.info(_iframe.readyState);
+ };*/
+
+ Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8
+ var el;
+
+ try {
+ el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document;
+
+ // try to detect some standard error pages
+ if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error
+ _status = el.title.replace(/^(\d+).*$/, '$1');
+ } else {
+ _status = 200;
+ // get result
+ _response = Basic.trim(el.body.innerHTML);
+
+ // we need to fire these at least once
+ target.trigger({
+ type: 'progress',
+ loaded: _response.length,
+ total: _response.length
+ });
+
+ if (blob) { // if we were uploading a file
+ target.trigger({
+ type: 'uploadprogress',
+ loaded: blob.size || 1025,
+ total: blob.size || 1025
+ });
+ }
+ }
+ } catch (ex) {
+ if (Url.hasSameOrigin(meta.url)) {
+ // if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm
+ // which obviously results to cross domain error (wtf?)
+ _status = 404;
+ } else {
+ cleanup.call(target, function() {
+ target.trigger('error');
+ });
+ return;
+ }
+ }
+
+ cleanup.call(target, function() {
+ target.trigger('load');
+ });
+ }, target.uid);
+ } // end createIframe
+
+ // prepare data to be sent and convert if required
+ if (data instanceof FormData && data.hasBlob()) {
+ blob = data.getBlob();
+ uid = blob.uid;
+ input = Dom.get(uid);
+ form = Dom.get(uid + '_form');
+ if (!form) {
+ throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
+ }
+ } else {
+ uid = Basic.guid('uid_');
+
+ form = document.createElement('form');
+ form.setAttribute('id', uid + '_form');
+ form.setAttribute('method', meta.method);
+ form.setAttribute('enctype', 'multipart/form-data');
+ form.setAttribute('encoding', 'multipart/form-data');
+ form.setAttribute('target', uid + '_iframe');
+
+ I.getShimContainer().appendChild(form);
+ }
+
+ if (data instanceof FormData) {
+ data.each(function(value, name) {
+ if (value instanceof Blob) {
+ if (input) {
+ input.setAttribute('name', name);
+ }
+ } else {
+ var hidden = document.createElement('input');
+
+ Basic.extend(hidden, {
+ type : 'hidden',
+ name : name,
+ value : value
+ });
+
+ // make sure that input[type="file"], if it's there, comes last
+ if (input) {
+ form.insertBefore(hidden, input);
+ } else {
+ form.appendChild(hidden);
+ }
+ }
+ });
+ }
+
+ // set destination url
+ form.setAttribute("action", meta.url);
+
+ createIframe();
+ form.submit();
+ target.trigger('loadstart');
+ },
+
+ getStatus: function() {
+ return _status;
+ },
+
+ getResponse: function(responseType) {
+ if ('json' === responseType) {
+ // strip off <pre>..</pre> tags that might be enclosing the response
+ if (Basic.typeOf(_response) === 'string' && !!window.JSON) {
+ try {
+ return JSON.parse(_response.replace(/^\s*<pre[^>]*>/, '').replace(/<\/pre>\s*$/, ''));
+ } catch (ex) {
+ return null;
+ }
+ }
+ } else if ('document' === responseType) {
+
+ }
+ return _response;
+ },
+
+ abort: function() {
+ var target = this;
+
+ if (_iframe && _iframe.contentWindow) {
+ if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome
+ _iframe.contentWindow.stop();
+ } else if (_iframe.contentWindow.document.execCommand) { // IE
+ _iframe.contentWindow.document.execCommand('Stop');
+ } else {
+ _iframe.src = "about:blank";
+ }
+ }
+
+ cleanup.call(this, function() {
+ // target.dispatchEvent('readystatechange');
+ target.dispatchEvent('abort');
+ });
+ }
+ });
+ }
+
+ return (extensions.XMLHttpRequest = XMLHttpRequest);
+});
+
+// Included from: src/javascript/runtime/html4/image/Image.js
+
+/**
+ * Image.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/**
+@class moxie/runtime/html4/image/Image
+@private
+*/
+define("moxie/runtime/html4/image/Image", [
+ "moxie/runtime/html4/Runtime",
+ "moxie/runtime/html5/image/Image"
+], function(extensions, Image) {
+ return (extensions.Image = Image);
+});
+
+expose(["moxie/core/utils/Basic","moxie/core/I18n","moxie/core/utils/Mime","moxie/core/utils/Env","moxie/core/utils/Dom","moxie/core/Exceptions","moxie/core/EventTarget","moxie/core/utils/Encode","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/Blob","moxie/file/File","moxie/file/FileInput","moxie/file/FileDrop","moxie/runtime/RuntimeTarget","moxie/file/FileReader","moxie/core/utils/Url","moxie/file/FileReaderSync","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/runtime/Transporter","moxie/image/Image","moxie/core/utils/Events"]);
+})(this);/**
+ * o.js
+ *
+ * Copyright 2013, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+/*global moxie:true */
+
+/**
+Globally exposed namespace with the most frequently used public classes and handy methods.
+
+@class o
+@static
+@private
+*/
+(function() {
+ "use strict";
+
+ var o = {}, inArray = moxie.core.utils.Basic.inArray;
+
+ // directly add some public classes
+ // (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included)
+ (function addAlias(ns) {
+ var name, itemType;
+ for (name in ns) {
+ itemType = typeof(ns[name]);
+ if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) {
+ addAlias(ns[name]);
+ } else if (itemType === 'function') {
+ o[name] = ns[name];
+ }
+ }
+ })(window.moxie);
+
+ // add some manually
+ o.Env = window.moxie.core.utils.Env;
+ o.Mime = window.moxie.core.utils.Mime;
+ o.Exceptions = window.moxie.core.Exceptions;
+
+ // expose globally
+ window.mOxie = o;
+ if (!window.o) {
+ window.o = o;
+ }
+ return o;
+})();
Property changes on: branches/5.3.x/core/admin_templates/js/uploader/moxie.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/admin_templates/Moxie.swf
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: branches/5.3.x/core/admin_templates/Moxie.swf
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: branches/5.3.x/core/admin_templates/incs/form_blocks.tpl
===================================================================
--- branches/5.3.x/core/admin_templates/incs/form_blocks.tpl (revision 16001)
+++ branches/5.3.x/core/admin_templates/incs/form_blocks.tpl (revision 16002)
@@ -1,1284 +1,1279 @@
<inp2:m_Set tab_index="1"/>
<inp2:m_DefineElement name="combined_header" permission_type="view" perm_section="" perm_prefix="" perm_event="" system_permission="1" title_preset="" tab_preset="" additional_title_render_as="" additional_blue_bar_render_as="" pagination_prefix="" parent="1" grid="Default">
<inp2:m_if check="m_Param" name="perm_section" inverse="1">
<inp2:adm_SectionInfo section="$section" info="perm_section" result_to_var="perm_section"/>
</inp2:m_if>
<inp2:m_if check="m_Param" name="permission_type">
<inp2:m_RequireLogin permissions="{$perm_section}.{$permission_type}" perm_event="$perm_event" perm_prefix="$perm_prefix" system="$system_permission"/>
<inp2:m_else/>
<inp2:m_RequireLogin permissions="{$perm_section}" perm_event="$perm_event" perm_prefix="$perm_prefix" system="$system_permission"/>
</inp2:m_if>
<inp2:m_if check="m_Param" name="prefix" inverse="1"><inp2:adm_SectionInfo section="$section" info="SectionPrefix" result_to_var="prefix"/></inp2:m_if>
<inp2:m_if check="m_get" var="m_wid" inverse="1">
<inp2:m_if check="m_GetConfig" name="UseSmallHeader">
<img src="img/spacer.gif" height="8" width="1" alt=""/>
<inp2:m_else/>
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<!--## <tr<inp2:m_ifnot check="m_ModuleEnabled" module="Proj-Base"> style="background: url(<inp2:adm_SectionInfo section="$section" parent="$parent" info="module_path"/>img/logo_bg.gif) no-repeat top right; height: 55px;"</inp2:m_ifnot>> ##-->
<tr>
<td valign="top" class="admintitle" align="left" <!--##style="padding-top: 10px; padding-bottom: 10px;"##-->>
<img width="46" height="46" src="<inp2:adm_SectionInfo section='$section' parent='$parent' info='module_path'/>img/icons/icon46_<inp2:adm_SectionInfo section='$section' parent='$parent' info='icon'/>.png" align="absmiddle" title="<inp2:adm_SectionInfo section='$section' parent='$parent' info='label' no_editing='1'/>" alt=""/><inp2:adm_SectionInfo section="$section" parent="$parent" info="label"/>
</td>
<inp2:m_if check="m_Param" name="additional_title_render_as">
<inp2:m_RenderElement name="$additional_title_render_as" pass_params="1"/>
</inp2:m_if>
</tr>
</table>
</inp2:m_if>
<inp2:m_else/>
<inp2:m_if check="m_Param" name="additional_title_render_as">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<!--## <tr<inp2:m_ifnot check="m_ModuleEnabled" module="Proj-Base"> style="background: url(<inp2:adm_SectionInfo section="$section" parent="$parent" info="module_path"/>img/logo_bg.gif) no-repeat top right; height: 55px;"</inp2:m_ifnot>> ##-->
<tr>
<inp2:m_RenderElement name="$additional_title_render_as" pass_params="1"/>
</tr>
</table>
</inp2:m_if>
</inp2:m_if>
<inp2:$prefix_ModifyUnitConfig pass_params="1"/>
<inp2:m_if check="m_Param" name="tabs">
<inp2:m_include t="$tabs" pass_params="1"/>
</inp2:m_if>
<inp2:m_if check="m_Param" name="tab_preset">
<inp2:m_RenderElement name="edit_tabs" prefix="$prefix" preset_name="$tab_preset"/>
</inp2:m_if>
<table border="0" cellpadding="2" cellspacing="0" class="page-title bordered-no-bottom" width="100%" style="height: 30px;">
<tr>
<td nowrap="nowrap" style="vertical-align: middle;">
<inp2:adm_SectionInfo section="$section" info="label" result_to_var="default_title"/>
<inp2:adm_SectionInfo section="$section" parent="$parent" info="label" result_to_var="group_title"/>
<span class="tablenav_link" id="blue_bar">
<inp2:$prefix_SectionTitle title_preset="$title_preset" section="$section" title="$default_title" group_title="$group_title" cut_first="100" pass_params="true"/>
</span>
</td>
<td align="right" class="tablenav" style="vertical-align: middle;">
<inp2:m_if check="m_Param" name="additional_blue_bar_render_as">
<inp2:m_RenderElement name="$additional_blue_bar_render_as" pass_params="1"/>
<inp2:m_else/>
<inp2:m_if check="m_Param" name="pagination">
<inp2:$prefix_SelectParam possible_names="pagination_prefix,prefix" result_to_var="pagination_prefix"/>
<inp2:m_RenderElement name="grid_pagination_elem" PrefixSpecial="$pagination_prefix" pass_params="1"/>
</inp2:m_if>
</inp2:m_if>
</td>
</tr>
</table>
<script type="text/javascript">
var $visible_toolbar_buttons = <inp2:m_if check="{$prefix}_VisibleToolbarButtons" title_preset="$title_preset">[<inp2:$prefix_VisibleToolbarButtons title_preset="$title_preset"/>]<inp2:m_else/>true</inp2:m_if>;
var $allow_dbl_click = ($visible_toolbar_buttons === true) || in_array('dbl-click', $visible_toolbar_buttons);
set_window_title( $.trim( $('#blue_bar').text().replace(/\s+/g, ' ') ) + ' - <inp2:m_Phrase label="la_AdministrativeConsole" js_escape="1"/>');
setHelpLink('<inp2:lang.current_Field name="UserDocsUrl" js_escape="1"/>', '<inp2:m_Param name="title_preset" js_escape="1"/>');
</script>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_original_label">
<td><inp2:$prefix.original_Field field="$field" nl2br="1"/></td>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="subsection" prefix="" fields="" colspan="3">
<inp2:m_if check="m_Param" name="prefix" equals_to="">
<tr class="subsectiontitle">
<td colspan="<inp2:m_param name='colspan'/>"><inp2:m_phrase label="$title"/></td>
</tr>
<inp2:m_else/>
<inp2:m_if check="{$prefix}_FieldsVisible" fields="$fields">
<tr class="subsectiontitle">
<td colspan="<inp2:m_param name='colspan'/>"><inp2:m_phrase label="$title"/></td>
<inp2:m_if check="{$prefix}_DisplayOriginal" pass_params="1">
<td><inp2:m_phrase name="$original_title"/></td>
</inp2:m_if>
</tr>
</inp2:m_if>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="form_message" id="" type="warning">
<table width="100%" cellspacing="0" cellpadding="4" class="warning-table"<inp2:m_if check="m_Param" name="id"> id="<inp2:m_Param name='id'/>"</inp2:m_if>>
<tr>
<td valign="top" class="form-<inp2:m_Param name='type'/>">
<inp2:m_Param name="content"/>
</td>
</tr>
</table>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="default_field_caption_element">
<label for="<inp2:m_param name='NamePrefix'/><inp2:{$prefix}_InputName field='$field'/>">
<span class="<inp2:m_if check='{$prefix}_HasError' field='$field'>error-cell</inp2:m_if>"><inp2:m_if check="m_Param" name="title"><inp2:m_phrase label="$title"/></inp2:m_else/><inp2:m_Param name="title_text"/></inp2:m_if></span></span><inp2:m_if check="{$prefix}_IsRequired" field="$field"><span class="field-required">&nbsp;*</span></inp2:m_if>:<inp2:m_if check="{$prefix}_FieldHintLabel" title_label="$title" direct_label="$hint_label"><span>&nbsp;<img src="<inp2:m_TemplatesBase/>/img/hint_icon.png" width="12" height="13" title="<inp2:$prefix_FieldHintLabel title_label='$title' direct_label='$hint_label' html_escape='1'/>" alt="<inp2:$prefix_FieldHintLabel title_label='$title' direct_label='$hint_label' html_escape='1'/>"/></inp2:m_if>
</label>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_field_caption" title="la_fld_{$field}" title_text="" hint_label="" NamePrefix="">
<inp2:m_inc param="tab_index" by="1"/>
<td class="label-cell" onmouseover="show_form_error('<inp2:m_Param name='prefix' js_escape='1'/>', '<inp2:m_Param name='field' js_escape='1'/>')" onmouseout="hide_form_error('<inp2:m_Param name='prefix' js_escape='1'/>')">
<inp2:m_if check="m_Param" name="title">
<inp2:m_RenderElement name="$caption_render_as" pass_params="1"/>
<inp2:m_else/>
<inp2:m_if check="m_Param" name="title_text">
<inp2:m_RenderElement name="$caption_render_as" pass_params="1"/>
<inp2:m_else/>
&nbsp;
</inp2:m_if>
</inp2:m_if>
</td>
<td class="control-mid">&nbsp;</td>
<script type="text/javascript">
if (typeof(fields['<inp2:m_Param name="prefix" js_escape="1"/>']) == 'undefined') {
fields['<inp2:m_Param name="prefix" js_escape="1"/>'] = new Object();
}
fields['<inp2:m_Param name="prefix" js_escape="1"/>']['<inp2:m_Param name="field" js_escape="1"/>'] = '<inp2:m_if check="m_Param" name="title"><inp2:m_phrase label="$title" js_escape="1"/></inp2:m_else/><inp2:m_Param name="title_text" js_escape="1"/></inp2:m_if>'
</script>
</inp2:m_DefineElement>
<!--## design default parameters only avaible in design block ##-->
<inp2:m_DefineElement name="form_row" error_field_suffix="" block_name="" title="la_fld_{$field}" has_caption="1" caption_render_as="default_field_caption_element" style="" hint_label="" is_last="">
<inp2:m_if check="{$prefix}_FieldVisible" field="$field">
<tr class="<inp2:m_odd_even odd='edit-form-odd' even='edit-form-even'/>" id="<inp2:$prefix_InputName field='$field'/>_row"<inp2:m_if check="m_Param" name="row_style"> style="<inp2:m_Param name='row_style'/>"</inp2:m_if>>
<inp2:m_if check="m_Param" name="has_caption">
<inp2:m_RenderElement name="inp_edit_field_caption" field="{$field}{$error_field_suffix}" pass_params="1"/>
</inp2:m_if>
<inp2:m_Param name="content" pass_params="1"/>
<inp2:m_RenderElement name="inp_edit_error" prefix="$prefix" field="{$field}{$error_field_suffix}" block_name="$block_name"/>
<inp2:m_if check="{$prefix}_DisplayOriginal" pass_params="1">
<inp2:m_RenderElement prefix="$prefix" field="$field" name="inp_original_label"/>
</inp2:m_if>
</tr>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_label" style="" format="" db="" as_label="" currency="" no_special="" nl2br="0" with_hidden="0" after_text="">
<inp2:m_RenderElement design="form_row" block_name="inp_label" pass_params="1">
<td class="control-cell" valign="top">
<span style="<inp2:m_Param name='style'/>" id="<inp2:$prefix_InputName field='$field'/>">
<inp2:{$prefix}_Field field="$field" format="$format" as_label="$as_label" currency="$currency" nl2br="$nl2br" no_special="$no_special"/><inp2:m_Param name="after_text"/>
</span>
<inp2:m_if check="m_Param" name="with_hidden">
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' db='$db'/>">
</inp2:m_if>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_id_label">
<inp2:m_ifnot check="Field" field="$field" equals_to="|0">
<inp2:m_RenderElement name="inp_label" pass_params="true"/>
</inp2:m_ifnot>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_error" block_name="">
<script type="text/javascript">
add_form_error('<inp2:m_Param name="prefix" js_escape="1"/>', '<inp2:m_Param name="field" js_escape="1"/>', '<inp2:{$prefix}_InputName field="$field"/>', '<inp2:{$prefix}_Error field="$field" js_escape="1"/>', '<inp2:m_Param name="block_name" js_escape="1"/>');
</script>
<!--##<td class="error-cell"><inp2:{$prefix}_Error field="$field"/>&nbsp;</td>##-->
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_box" class="" format="" maxlength="" onblur="" onchange="" size="" onkeyup="" allow_html="" edit_template="popups/editor" style="width: 100%" after_text="" autocomplete="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_box" pass_params="1">
<td class="control-cell">
<input style="<inp2:m_Param name='style'/>" type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' format='$format'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" maxlength="<inp2:m_param name='maxlength'/>" class="<inp2:m_param name='class'/>" onblur="<inp2:m_Param name='onblur'/>" onkeyup="<inp2:m_Param name='onkeyup'/>" onchange="<inp2:m_Param name='onchange'/>" autocomplete="<inp2:m_Param name='autocomplete'/>"><inp2:m_Param name="after_text"/>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_timezone" onchange="" style="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_timezone" pass_params="1">
<td class="control-cell">
<script type="text/javascript">
var $timezones = {
'Africa': ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmara', 'Africa/Asmera', 'Africa/Bamako', 'Africa/Bangui', 'Africa/Banjul', 'Africa/Bissau', 'Africa/Blantyre', 'Africa/Brazzaville', 'Africa/Bujumbura', 'Africa/Cairo', 'Africa/Casablanca', 'Africa/Ceuta', 'Africa/Conakry', 'Africa/Dakar', 'Africa/Dar_es_Salaam', 'Africa/Djibouti', 'Africa/Douala', 'Africa/El_Aaiun', 'Africa/Freetown', 'Africa/Gaborone', 'Africa/Harare', 'Africa/Johannesburg', 'Africa/Kampala', 'Africa/Khartoum', 'Africa/Kigali', 'Africa/Kinshasa', 'Africa/Lagos', 'Africa/Libreville', 'Africa/Lome', 'Africa/Luanda', 'Africa/Lubumbashi', 'Africa/Lusaka', 'Africa/Malabo', 'Africa/Maputo', 'Africa/Maseru', 'Africa/Mbabane', 'Africa/Mogadishu', 'Africa/Monrovia', 'Africa/Nairobi', 'Africa/Ndjamena', 'Africa/Niamey', 'Africa/Nouakchott', 'Africa/Ouagadougou', 'Africa/Porto-Novo', 'Africa/Sao_Tome', 'Africa/Timbuktu', 'Africa/Tripoli', 'Africa/Tunis', 'Africa/Windhoek'],
'America': ['America/Adak', 'America/Anchorage', 'America/Anguilla', 'America/Antigua', 'America/Araguaina', 'America/Argentina/Buenos_Aires', 'America/Argentina/Catamarca', 'America/Argentina/ComodRivadavia', 'America/Argentina/Cordoba', 'America/Argentina/Jujuy', 'America/Argentina/La_Rioja', 'America/Argentina/Mendoza', 'America/Argentina/Rio_Gallegos', 'America/Argentina/Salta', 'America/Argentina/San_Juan', 'America/Argentina/San_Luis', 'America/Argentina/Tucuman', 'America/Argentina/Ushuaia', 'America/Aruba', 'America/Asuncion', 'America/Atikokan', 'America/Atka', 'America/Bahia', 'America/Barbados', 'America/Belem', 'America/Belize', 'America/Blanc-Sablon', 'America/Boa_Vista', 'America/Bogota', 'America/Boise', 'America/Buenos_Aires', 'America/Cambridge_Bay', 'America/Campo_Grande', 'America/Cancun', 'America/Caracas', 'America/Catamarca', 'America/Cayenne', 'America/Cayman', 'America/Chicago', 'America/Chihuahua', 'America/Coral_Harbour', 'America/Cordoba', 'America/Costa_Rica', 'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', 'America/Dawson', 'America/Dawson_Creek', 'America/Denver', 'America/Detroit', 'America/Dominica', 'America/Edmonton', 'America/Eirunepe', 'America/El_Salvador', 'America/Ensenada', 'America/Fort_Wayne', 'America/Fortaleza', 'America/Glace_Bay', 'America/Godthab', 'America/Goose_Bay', 'America/Grand_Turk', 'America/Grenada', 'America/Guadeloupe', 'America/Guatemala', 'America/Guayaquil', 'America/Guyana', 'America/Halifax', 'America/Havana', 'America/Hermosillo', 'America/Indiana/Indianapolis', 'America/Indiana/Knox', 'America/Indiana/Marengo', 'America/Indiana/Petersburg', 'America/Indiana/Tell_City', 'America/Indiana/Vevay', 'America/Indiana/Vincennes', 'America/Indiana/Winamac', 'America/Indianapolis', 'America/Inuvik', 'America/Iqaluit', 'America/Jamaica', 'America/Jujuy', 'America/Juneau', 'America/Kentucky/Louisville', 'America/Kentucky/Monticello', 'America/Knox_IN', 'America/La_Paz', 'America/Lima', 'America/Los_Angeles', 'America/Louisville', 'America/Maceio', 'America/Managua', 'America/Manaus', 'America/Marigot', 'America/Martinique', 'America/Matamoros', 'America/Mazatlan', 'America/Mendoza', 'America/Menominee', 'America/Merida', 'America/Mexico_City', 'America/Miquelon', 'America/Moncton', 'America/Monterrey', 'America/Montevideo', 'America/Montreal', 'America/Montserrat', 'America/Nassau', 'America/New_York', 'America/Nipigon', 'America/Nome', 'America/Noronha', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', 'America/Ojinaga', 'America/Panama', 'America/Pangnirtung', 'America/Paramaribo', 'America/Phoenix', 'America/Port-au-Prince', 'America/Port_of_Spain', 'America/Porto_Acre', 'America/Porto_Velho', 'America/Puerto_Rico', 'America/Rainy_River', 'America/Rankin_Inlet', 'America/Recife', 'America/Regina', 'America/Resolute', 'America/Rio_Branco', 'America/Rosario', 'America/Santa_Isabel', 'America/Santarem', 'America/Santiago', 'America/Santo_Domingo', 'America/Sao_Paulo', 'America/Scoresbysund', 'America/Shiprock', 'America/St_Barthelemy', 'America/St_Johns', 'America/St_Kitts', 'America/St_Lucia', 'America/St_Thomas', 'America/St_Vincent', 'America/Swift_Current', 'America/Tegucigalpa', 'America/Thule', 'America/Thunder_Bay', 'America/Tijuana', 'America/Toronto', 'America/Tortola', 'America/Vancouver', 'America/Virgin', 'America/Whitehorse', 'America/Winnipeg', 'America/Yakutat', 'America/Yellowknife'],
'Antarctica': ['Antarctica/Casey', 'Antarctica/Davis', 'Antarctica/DumontDUrville', 'Antarctica/Macquarie', 'Antarctica/Mawson', 'Antarctica/McMurdo', 'Antarctica/Palmer', 'Antarctica/Rothera', 'Antarctica/South_Pole', 'Antarctica/Syowa', 'Antarctica/Vostok'],
'Arctic': ['Arctic/Longyearbyen'],
'Asia': ['Asia/Aden', 'Asia/Almaty', 'Asia/Amman', 'Asia/Anadyr', 'Asia/Aqtau', 'Asia/Aqtobe', 'Asia/Ashgabat', 'Asia/Ashkhabad', 'Asia/Baghdad', 'Asia/Bahrain', 'Asia/Baku', 'Asia/Bangkok', 'Asia/Beirut', 'Asia/Bishkek', 'Asia/Brunei', 'Asia/Calcutta', 'Asia/Choibalsan', 'Asia/Chongqing', 'Asia/Chungking', 'Asia/Colombo', 'Asia/Dacca', 'Asia/Damascus', 'Asia/Dhaka', 'Asia/Dili', 'Asia/Dubai', 'Asia/Dushanbe', 'Asia/Gaza', 'Asia/Harbin', 'Asia/Ho_Chi_Minh', 'Asia/Hong_Kong', 'Asia/Hovd', 'Asia/Irkutsk', 'Asia/Istanbul', 'Asia/Jakarta', 'Asia/Jayapura', 'Asia/Jerusalem', 'Asia/Kabul', 'Asia/Kamchatka', 'Asia/Karachi', 'Asia/Kashgar', 'Asia/Kathmandu', 'Asia/Katmandu', 'Asia/Kolkata', 'Asia/Krasnoyarsk', 'Asia/Kuala_Lumpur', 'Asia/Kuching', 'Asia/Kuwait', 'Asia/Macao', 'Asia/Macau', 'Asia/Magadan', 'Asia/Makassar', 'Asia/Manila', 'Asia/Muscat', 'Asia/Nicosia', 'Asia/Novokuznetsk', 'Asia/Novosibirsk', 'Asia/Omsk', 'Asia/Oral', 'Asia/Phnom_Penh', 'Asia/Pontianak', 'Asia/Pyongyang', 'Asia/Qatar', 'Asia/Qyzylorda', 'Asia/Rangoon', 'Asia/Riyadh', 'Asia/Saigon', 'Asia/Sakhalin', 'Asia/Samarkand', 'Asia/Seoul', 'Asia/Shanghai', 'Asia/Singapore', 'Asia/Taipei', 'Asia/Tashkent', 'Asia/Tbilisi', 'Asia/Tehran', 'Asia/Tel_Aviv', 'Asia/Thimbu', 'Asia/Thimphu', 'Asia/Tokyo', 'Asia/Ujung_Pandang', 'Asia/Ulaanbaatar', 'Asia/Ulan_Bator', 'Asia/Urumqi', 'Asia/Vientiane', 'Asia/Vladivostok', 'Asia/Yakutsk', 'Asia/Yekaterinburg', 'Asia/Yerevan'],
'Atlantic': ['Atlantic/Azores', 'Atlantic/Bermuda', 'Atlantic/Canary', 'Atlantic/Cape_Verde', 'Atlantic/Faeroe', 'Atlantic/Faroe', 'Atlantic/Jan_Mayen', 'Atlantic/Madeira', 'Atlantic/Reykjavik', 'Atlantic/South_Georgia', 'Atlantic/St_Helena', 'Atlantic/Stanley'],
'Australia': ['Australia/ACT', 'Australia/Adelaide', 'Australia/Brisbane', 'Australia/Broken_Hill', 'Australia/Canberra', 'Australia/Currie', 'Australia/Darwin', 'Australia/Eucla', 'Australia/Hobart', 'Australia/LHI', 'Australia/Lindeman', 'Australia/Lord_Howe', 'Australia/Melbourne', 'Australia/North', 'Australia/NSW', 'Australia/Perth', 'Australia/Queensland', 'Australia/South', 'Australia/Sydney', 'Australia/Tasmania', 'Australia/Victoria', 'Australia/West', 'Australia/Yancowinna'],
'Europe': ['Europe/Amsterdam', 'Europe/Andorra', 'Europe/Athens', 'Europe/Belfast', 'Europe/Belgrade', 'Europe/Berlin', 'Europe/Bratislava', 'Europe/Brussels', 'Europe/Bucharest', 'Europe/Budapest', 'Europe/Chisinau', 'Europe/Copenhagen', 'Europe/Dublin', 'Europe/Gibraltar', 'Europe/Guernsey', 'Europe/Helsinki', 'Europe/Isle_of_Man', 'Europe/Istanbul', 'Europe/Jersey', 'Europe/Kaliningrad', 'Europe/Kiev', 'Europe/Lisbon', 'Europe/Ljubljana', 'Europe/London', 'Europe/Luxembourg', 'Europe/Madrid', 'Europe/Malta', 'Europe/Mariehamn', 'Europe/Minsk', 'Europe/Monaco', 'Europe/Moscow', 'Europe/Nicosia', 'Europe/Oslo', 'Europe/Paris', 'Europe/Podgorica', 'Europe/Prague', 'Europe/Riga', 'Europe/Rome', 'Europe/Samara', 'Europe/San_Marino', 'Europe/Sarajevo', 'Europe/Simferopol', 'Europe/Skopje', 'Europe/Sofia', 'Europe/Stockholm', 'Europe/Tallinn', 'Europe/Tirane', 'Europe/Tiraspol', 'Europe/Uzhgorod', 'Europe/Vaduz', 'Europe/Vatican', 'Europe/Vienna', 'Europe/Vilnius', 'Europe/Volgograd', 'Europe/Warsaw', 'Europe/Zagreb', 'Europe/Zaporozhye', 'Europe/Zurich'],
'Indian': ['Indian/Antananarivo', 'Indian/Chagos', 'Indian/Christmas', 'Indian/Cocos', 'Indian/Comoro', 'Indian/Kerguelen', 'Indian/Mahe', 'Indian/Maldives', 'Indian/Mauritius', 'Indian/Mayotte', 'Indian/Reunion'],
'Pacific': ['Pacific/Apia', 'Pacific/Auckland', 'Pacific/Chatham', 'Pacific/Easter', 'Pacific/Efate', 'Pacific/Enderbury', 'Pacific/Fakaofo', 'Pacific/Fiji', 'Pacific/Funafuti', 'Pacific/Galapagos', 'Pacific/Gambier', 'Pacific/Guadalcanal', 'Pacific/Guam', 'Pacific/Honolulu', 'Pacific/Johnston', 'Pacific/Kiritimati', 'Pacific/Kosrae', 'Pacific/Kwajalein', 'Pacific/Majuro', 'Pacific/Marquesas', 'Pacific/Midway', 'Pacific/Nauru', 'Pacific/Niue', 'Pacific/Norfolk', 'Pacific/Noumea', 'Pacific/Pago_Pago', 'Pacific/Palau', 'Pacific/Pitcairn', 'Pacific/Ponape', 'Pacific/Port_Moresby', 'Pacific/Rarotonga', 'Pacific/Saipan', 'Pacific/Samoa', 'Pacific/Tahiti', 'Pacific/Tarawa', 'Pacific/Tongatapu', 'Pacific/Truk', 'Pacific/Wake', 'Pacific/Wallis', 'Pacific/Yap'],
'Others': ['UTC']
}
function change_timezone_group($timezone_group) {
var $target = $( jq('#<inp2:InputName name="$field"/>') );
$target.hide().empty();
$target.append('<option value=""></option>');
if ($timezone_group == '') {
return ;
}
for (var $i = 0; $i < $timezones[$timezone_group].length; $i++) {
var $timezone = $timezones[$timezone_group][$i];
var $selected = $timezone == $current_timezone ? ' selected="selected"' : '';
$target.append('<option value="' + $timezone + '" ' + $selected + '>' + $timezone + '</option>');
}
$target.show();
}
var $current_timezone = '<inp2:Field name="$field"/>';
$(document).ready(
function () {
$('#timezone_group').change(
function ($e) {
change_timezone_group( $(this).val() );
}
);
var $current_timezone_group = '';
for (var $timezone_group in $timezones) {
if (in_array($current_timezone, $timezones[$timezone_group])) {
$current_timezone_group = $timezone_group;
break;
}
}
$('#timezone_group').val($current_timezone_group).change();
}
);
</script>
<select id="timezone_group" tabindex="<inp2:m_Get name='tab_index'/>">
<option value=""></option>
<option value="Africa">Africa</option>
<option value="America">America</option>
<option value="Antarctica">Antarctica</option>
<option value="Arctic">Arctic</option>
<option value="Asia">Asia</option>
<option value="Atlantic">Atlantic</option>
<option value="Australia">Australia</option>
<option value="Europe">Europe</option>
<option value="Indian">Indian</option>
<option value="Pacific">Pacific</option>
<option value="Others">Others</option>
</select>
&nbsp;&nbsp;
<select tabindex="<inp2:m_Get name='tab_index'/>" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" onchange="<inp2:m_Param name='onchange'/>" style="<inp2:m_Param name='style'/>"></select>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_password" class="" size="" style="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_password" pass_params="1">
<td class="control-cell">
<input style="<inp2:m_Param name='style'/>" autocomplete="off" type="password" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field name='{$field}_plain'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" class="<inp2:m_param name='class'/>" />
<script type="text/javascript">
$(document).ready(
function() {
<inp2:m_ifnot check="{$prefix}_Field" name="{$field}_plain">
$('#' + jq('<inp2:{$prefix}_InputName field="$field"/>')).val('');
</inp2:m_ifnot>
}
);
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_upload" class="" size="" thumbnail="" style="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_upload" pass_params="1">
<td class="control-cell">
<inp2:m_if check="m_Param" name="thumbnail">
<inp2:m_if check="{$prefix}_FieldEquals" name="$field" value="" inverse="inverse">
<img src="<inp2:{$prefix}_Field field='$field' format='resize:{$thumbnail}'/>" alt=""/><br />
<table cellpadding="0" cellspacing="0">
<tr>
<td>
<input type="hidden" id="<inp2:{$prefix}_InputName field='Delete{$field}'/>" name="<inp2:{$prefix}_InputName field='Delete{$field}'/>" value="0" />
<input type="checkbox" id="_cb_<inp2:{$prefix}_InputName field='Delete{$field}'/>" tabindex="<inp2:m_Get name='tab_index'/>" onchange="update_checkbox(this, document.getElementById('<inp2:{$prefix}_InputName field='Delete{$field}'/>'));">
</td>
<td>
<label for="_cb_<inp2:{$prefix}_InputName field='Delete{$field}'/>"><inp2:m_phrase name="la_btn_Delete"/></label>
</td>
</tr>
</table>
</inp2:m_if>
<input type="file" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" class="<inp2:m_param name='class'/>">
<inp2:m_else/>
<input type="file" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" class="<inp2:m_param name='class'/>">
<inp2:m_if check="{$prefix}_FieldEquals" name="$field" value="" inverse="inverse">
(<inp2:{$prefix}_Field field="$field"/>)
</inp2:m_if>
</inp2:m_if>
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>[upload]" id="<inp2:{$prefix}_InputName field='$field'/>[upload]" value="<inp2:{$prefix}_Field field='$field'/>">
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_box_ml">
<inp2:m_RenderElement name="inp_edit_box" format="no_default" pass_params="true"/>
<!--##
<inp2:m_RenderElement design="form_row" block_name="inp_edit_box_ml" pass_params="1">
<td class="label-cell" valign="top">
<span class="<inp2:m_if check='{$prefix}_HasError' field='$field'>error-cell</inp2:m_if>" >
<inp2:m_phrase label="$title"/><inp2:m_if check="{$prefix}_IsRequired" field="$field"><span class="field-required">&nbsp;*</span></inp2:m_if>:</span><br>
<a href="javascript:PreSaveAndOpenTranslator('<inp2:m_param name='prefix'/>', '<inp2:m_param name='field'/>', 'popups/translator');" title="<inp2:m_Phrase label='la_Translate'/>"><img src="img/icons/icon24_translate.png" style="cursor:hand" border="0"></a>
</td>
<td class="control-cell">
<input style="<inp2:m_Param name='style'/>" type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' format='no_default'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" maxlength="<inp2:m_param name='maxlength'/>" class="<inp2:m_param name='class'/>" onblur="<inp2:m_Param name='onblur'/>">
</td>
</inp2:m_RenderElement>
##-->
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_swf_upload" LinkPrefix="m" LinkTag="t" class="" style="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_swf_upload" pass_params="1">
<td class="control-cell">
- <div style="width: 63px; height: 21px;" id="<inp2:{$prefix}_InputName field='$field'/>_place_holder">
- &nbsp;
+ <inp2:m_ifnot check="m_Get" name="plupload_included">
+ <script type="text/javascript" src="<inp2:m_Compress files='js/uploader/moxie.js|js/uploader/plupload.dev.js'/>"></script>
+ <inp2:m_Set plupload_included="1"/>
+ </inp2:m_ifnot>
+
+ <div id="<inp2:{$prefix}_InputName field='$field'/>_container">
+ <button class="button button-disabled" disabled id="<inp2:{$prefix}_InputName field='$field'/>_browse_button">Browse</button>
</div>
<div id="<inp2:{$prefix}_InputName field='$field'/>_queueinfo" class="uploader-queue"></div>
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>[upload]" id="<inp2:{$prefix}_InputName field='$field'/>[upload]" value="<inp2:{$prefix}_Field field='$field' format='file_names'/>">
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>[order]" id="<inp2:{$prefix}_InputName field='$field'/>[order]" value="<inp2:{$prefix}_Field field='$field' format='file_names'/>">
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>[tmp_ids]" id="<inp2:{$prefix}_InputName field='$field'/>[tmp_ids]" value="">
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>[tmp_names]" id="<inp2:{$prefix}_InputName field='$field'/>[tmp_names]" value="">
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>[tmp_deleted]" id="<inp2:{$prefix}_InputName field='$field'/>[tmp_deleted]" value="">
<script type="text/javascript">
UploadsManager.AddUploader('<inp2:{$prefix}_InputName field="$field"/>',
{
baseUrl: '<inp2:m_TemplatesBase />',
allowedFiletypesDescription : '<inp2:{$prefix}_FieldOption field="$field" option="files_description" result_to_var="files_description"/><inp2:m_Phrase name="$files_description" js_escape="1"/>',
allowedFiletypes : '<inp2:{$prefix}_FieldOption field="$field" option="file_types"/>',
allowedFilesize : '<inp2:{$prefix}_FieldOption field="$field" option="max_size"/>',
multiple : '<inp2:{$prefix}_FieldOption field="$field" option="multiple"/>',
prefix : '<inp2:m_Param name="prefix"/>',
field : '<inp2:m_Param name="field"/>',
thumb_format: '<inp2:{$prefix}_FieldOption field="$field" option="thumb_format"/>',
urls : '<inp2:{$prefix}_Field field="$field" format="file_urls" no_special="1" js_escape="1"/>',
names : '<inp2:{$prefix}_Field field="$field" format="file_names" no_special="1" js_escape="1"/>',
sizes : '<inp2:{$prefix}_Field field="$field" format="file_sizes" no_special="1" js_escape="1"/>',
flashsid : '<inp2:m_SID/>',
uploadURL : '<inp2:{$LinkPrefix}_{$LinkTag} pass="all,$prefix" {$prefix}_event="OnUploadFile" no_amp="1" js_escape="1"/>',
deleteURL : '<inp2:{$LinkPrefix}_{$LinkTag} pass="all,$prefix" {$prefix}_event="OnDeleteFile" field_id="#FIELD_ID#" file="#FILE#" no_amp="1" js_escape="1"/>',
previewURL : '<inp2:{$LinkPrefix}_{$LinkTag} pass="all,$prefix" {$prefix}_event="OnViewFile" field="#FIELD#" file="#FILE#" no_amp="1" js_escape="1"/>',
-
- // Button settings
- buttonImageURL: 'img/upload.png', // Relative to the Flash file
- buttonWidth: 63,
- buttonHeight: 21,
- buttonText: '<span class="theFont">Browse</span>',
- buttonTextStyle: ".theFont { font-size: 12; font-family: arial, sans}",
- buttonTextTopPadding: 2,
- buttonTextLeftPadding: 9,
- buttonPlaceholderId: '<inp2:{$prefix}_InputName field="$field"/>_place_holder',
ajax: <inp2:m_if check="m_Get" name="ajax" equals_to="yes">true<inp2:m_else/>false</inp2:m_if>
}
)
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_hidden" db="">
<input type="hidden" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' db='$db'/>">
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_date" class="" error_field_suffix="_date">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_date" pass_params="1">
<td class="control-cell">
<input type="text" name="<inp2:{$prefix}_InputName field='{$field}_date'/>" id="<inp2:{$prefix}_InputName field='{$field}_date'/>" value="<inp2:{$prefix}_Field field='{$field}_date' format='_input_'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:{$prefix}_Format field='{$field}_date' input_format='1' edit_size='edit_size'/>" class="<inp2:m_param name='class'/>" datepickerIcon="<inp2:m_ProjectBase/>core/admin_templates/img/calendar_icon.gif">&nbsp;
<img src="img/calendar_icon.gif" id="cal_img_<inp2:{$prefix}_InputName field='{$field}'/>"
style="cursor: pointer; margin-right: 5px"
title="Date selector"
/>
<span class="small">(<inp2:{$prefix}_Format field="{$field}_date" input_format="1" human="true"/>)</span>
<script type="text/javascript">
Calendar.setup({
inputField : "<inp2:{$prefix}_InputName field='{$field}_date'/>",
ifFormat : Calendar.phpDateFormat("<inp2:{$prefix}_Format field='{$field}_date' input_format='1'/>"),
button : "cal_img_<inp2:{$prefix}_InputName field='{$field}'/>",
align : "br",
singleClick : true,
showsTime : true,
weekNumbers : false,
firstDay : <inp2:m_GetConfig var="FirstDayOfWeek"/>,
onUpdate : function(cal) {
runOnChange('<inp2:$prefix_InputName field='{$field}_date'/>');
}
});
</script>
<input type="hidden" name="<inp2:{$prefix}_InputName field='{$field}_time'/>" id="<inp2:{$prefix}_InputName field='{$field}_time' input_format='1'/>" value="">
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_time" class="" error_field_suffix="_time">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_time" pass_params="1">
<td class="control-cell">
<input type="text" name="<inp2:{$prefix}_InputName field='{$field}_time'/>" id="<inp2:{$prefix}_InputName field='{$field}_time'/>" value="<inp2:{$prefix}_Field field='{$field}_time' format='_input_'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:{$prefix}_Format field='{$field}_time' input_format='1' edit_size='edit_size'/>" class="<inp2:m_param name='class'/>">&nbsp;
<span class="small">(<inp2:{$prefix}_Format field="{$field}_time" input_format="1" human="true"/>)</span>
<input type="hidden" name="<inp2:{$prefix}_InputName field='{$field}_date'/>" id="<inp2:{$prefix}_InputName field='{$field}_date' input_format='1'/>" value="">
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_date_time" class="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_date_time" pass_params="1">
<td class="control-cell">
<input type="text" name="<inp2:{$prefix}_InputName field='{$field}_date'/>" id="<inp2:{$prefix}_InputName field='{$field}_date'/>" value="<inp2:{$prefix}_Field field='{$field}_date' format='_input_'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:{$prefix}_Format field='{$field}_date' input_format='1' edit_size='edit_size'/>" class="<inp2:m_param name='class'/>" datepickerIcon="<inp2:m_ProjectBase/>core/admin_templates/img/calendar_icon.gif">
<img src="img/calendar_icon.gif" id="cal_img_<inp2:{$prefix}_InputName field="{$field}"/>"
style="cursor: pointer; margin-right: 5px"
title="Date selector"
/>
<span class="small">(<inp2:{$prefix}_Format field="{$field}_date" input_format="1" human="true"/>)</span>
<input type="hidden" id="full_date_<inp2:{$prefix}_InputName field='{$field}'/>" value="<inp2:{$prefix}_Field field='{$field}' format=''/>" />
<script type="text/javascript">
Calendar.setup({
inputField : "full_date_<inp2:{$prefix}_InputName field='{$field}'/>",
ifFormat : Calendar.phpDateFormat("<inp2:{$prefix}_Format field='{$field}' input_format='1'/>"),
button : "cal_img_<inp2:{$prefix}_InputName field='{$field}'/>",
align : "br",
singleClick : true,
showsTime : true,
weekNumbers : false,
firstDay : <inp2:m_GetConfig var="FirstDayOfWeek"/>,
onUpdate : function(cal) {
document.getElementById('<inp2:{$prefix}_InputName field="{$field}_date"/>').value = cal.date.print( Calendar.phpDateFormat("<inp2:{$prefix}_Format field="{$field}_date" input_format="1"/>") );
document.getElementById('<inp2:{$prefix}_InputName field="{$field}_time"/>').value = cal.date.print( Calendar.phpDateFormat("<inp2:{$prefix}_Format field="{$field}_time" input_format="1"/>") );
}
});
</script>
&nbsp;<input type="text" name="<inp2:{$prefix}_InputName field='{$field}_time'/>" id="<inp2:{$prefix}_InputName field='{$field}_time'/>" value="<inp2:{$prefix}_Field field='{$field}_time' format='_input_'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:{$prefix}_Format field='{$field}_time' input_format='1' edit_size='edit_size'/>" class="<inp2:m_param name='class'/>"><span class="small"> (<inp2:{$prefix}_Format field="{$field}_time" input_format="1" human="true"/>)</span>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_date_time_combined" class="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_date_time_combined" pass_params="1">
<td class="control-cell">
<input type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' format='_input_'/>" tabindex="<inp2:m_get param='tab_index'/>" size="<inp2:{$prefix}_Format field='$field' input_format='1' edit_size='edit_size'/>" class="<inp2:m_param name='class'/>" datepickerIcon="<inp2:m_ProjectBase/>core/admin_templates/img/calendar_icon.gif"/>
<img src="img/calendar_icon.gif" id="cal_img_<inp2:{$prefix}_InputName field="{$field}"/>"
style="cursor: pointer; margin-right: 5px"
title="Date selector"
/>
<span class="small">(<inp2:{$prefix}_Format field="$field" input_format="1" human="true"/>)</span>
<script type="text/javascript">
Calendar.setup({
inputField : "<inp2:{$prefix}_InputName field='{$field}'/>",
ifFormat : Calendar.phpDateFormat("<inp2:{$prefix}_Format field='{$field}' input_format='1'/>"),
button : "cal_img_<inp2:{$prefix}_InputName field='{$field}'/>",
align : "br",
singleClick : true,
showsTime : true,
weekNumbers : false,
firstDay : <inp2:m_GetConfig var="FirstDayOfWeek"/>
});
</script>
<input type="hidden" name="<inp2:{$prefix}_InputName field='{$field}_combined'/>" value="1"/>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="textarea_field_caption_element">
<inp2:m_RenderElement name="default_field_caption_element" pass_params="1"/>
<inp2:m_if check="m_Param" name="allow_html">
<br>
<inp2:{$prefix}_InputName field="$field" result_to_var="input_name"/>
<a href="<inp2:m_Link template='$edit_template' TargetField='$input_name' pass_through='TargetField' pass='m,$prefix'/>" onclick="openSelector('<inp2:m_Param name='prefix' js_escape='1'/>', this.href, '', '950x600'); return false;">
<img src="img/icons/icon24_link_editor.gif" border="0">
</a>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_textarea" caption_render_as="textarea_field_caption_element" class="" format="" edit_template="popups/editor" allow_html="" style="text-align: left; width: 100%; height: 100px;" control_options="false" row_style="height: auto">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_textarea" pass_params="1">
<td class="control-cell">
<textarea style="<inp2:m_Param name='style'/>" tabindex="<inp2:m_Get name='tab_index'/>" id="<inp2:{$prefix}_InputName field='$field'/>" name="<inp2:{$prefix}_InputName field='$field'/>" ><inp2:{$prefix}_Field field="$field" format="fck_ready;{$format}"/></textarea>
<script type="text/javascript">
Form.addControl('<inp2:{$prefix}_InputName field="$field"/>', <inp2:m_param name="control_options"/>);
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_fck" class="" title="la_fld_{$field}" maxlength="" bgcolor="" body_class="" body_id="" onblur="" format="" size="" onkeyup="" style="" has_caption="0" control_options="false">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_fck" pass_params="1">
<td class="control-cell" style="padding: 0px;" colspan="3" onmouseover="show_form_error('<inp2:m_Param name='prefix' js_escape='1'/>', '<inp2:m_Param name='field' js_escape='1'/>')" onmouseout="hide_form_error('<inp2:m_Param name='prefix' js_escape='1'/>')">
<inp2:FCKEditor field="$field" width="100%" height="200" bgcolor="$bgcolor" body_class="$body_class" body_id="$body_id" format="$format" late_load="1"/>
<script type="text/javascript">
if (typeof(fields['<inp2:m_Param name="prefix" js_escape="1"/>']) == 'undefined') {
fields['<inp2:m_Param name="prefix" js_escape="1"/>'] = new Object();
}
fields['<inp2:m_Param name="prefix" js_escape="1"/>']['<inp2:m_Param name="field" js_escape="1"/>'] = '<inp2:m_phrase label="$title" js_escape="1"/>'
Form.addControl('<inp2:$prefix_InputName field="$field"/>', <inp2:m_param name="control_options"/>);
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_codepress" style="width: 100%;" language="html" has_caption="0" control_options="false">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_codepress" pass_params="1">
<td class="control-cell" colspan="3" onmouseover="show_form_error('<inp2:m_Param name='prefix' js_escape='1'/>', '<inp2:m_Param name='field' js_escape='1'/>')" onmouseout="hide_form_error('<inp2:m_Param name='prefix' js_escape='1'/>')">
<inp2:m_ifnot check="m_Get" name="codepress_included">
<script type="text/javascript" src="<inp2:m_TemplatesBase/>/themes/codepress/codepress.js"></script>
<script type="text/javascript">
CodePress.path = '<inp2:m_TemplatesBase/>/themes/codepress/'; // set path here, because script tags are not found in table cells
</script>
<inp2:m_Set codepress_included="1"/>
</inp2:m_ifnot>
<textarea id="<inp2:$prefix_InputName field='$field'/>" name="<inp2:$prefix_InputName field='$field'/>" class="codepress <inp2:m_Param name='language'/>" style="<inp2:m_Param name='style'/>"><inp2:$prefix_Field field="$field"/></textarea>
<script type="text/javascript">
Application.setHook(
new Array ('<inp2:m_Param name="prefix" js_escape="1"/>:OnPreSaveAndGoToTab', '<inp2:m_Param name="prefix" js_escape="1"/>:OnPreSaveAndGo', '<inp2:m_Param name="prefix" js_escape="1"/>:OnSave', '<inp2:m_Param name="prefix" js_escape="1"/>:OnCreate', '<inp2:m_Param name="prefix" js_escape="1"/>:OnUpdate'),
function($event) {
<inp2:m_Param name="field"/>.toggleEditor(); // enable textarea back to save data
$event.status = true;
}
);
if (typeof(fields['<inp2:m_Param name="prefix" js_escape="1"/>']) == 'undefined') {
fields['<inp2:m_Param name="prefix" js_escape="1"/>'] = new Object();
}
fields['<inp2:m_Param name="prefix" js_escape="1"/>']['<inp2:m_Param name="field" js_escape="1"/>'] = '<inp2:m_phrase label="$title" js_escape="1"/>'
Form.addControl('<inp2:$prefix_InputName field="$field"/>', <inp2:m_param name="control_options"/>);
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="code_mirror_resource">
<inp2:m_if check="m_Param" name="resource_extension" equals_to="js">
<script src="<inp2:m_TemplatesBase/>/incs/code_mirror/mode/<inp2:m_Param name='resource_file'/>"></script>
<inp2:m_elseif check="m_Param" name="resource_extension" equals_to="js"/>
<link rel="stylesheet" href="<inp2:m_TemplatesBase/>/incs/code_mirror/mode/<inp2:m_Param name='resource_file'/>"/>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_codemirror" caption_render_as="textarea_field_caption_element" class="" format="" edit_template="popups/editor" allow_html="" style="text-align: left; width: 100%; height: 100px;" control_options="false" row_style="height: auto" codemirror_options="lineNumbers: true, styleActiveLine: true">
<inp2:m_RenderElement design="form_row" pass_params="1">
<td class="control-cell">
<textarea tabindex="<inp2:m_Get name='tab_index'/>" id="<inp2:{$prefix}_InputName field='$field'/>" name="<inp2:{$prefix}_InputName field='$field'/>" ><inp2:{$prefix}_Field field="$field" format="fck_ready;{$format}"/></textarea>
<inp2:m_ifnot check="m_Get" name="codemirror_included">
<script src="<inp2:m_Compress files='incs/code_mirror/lib/codemirror.js|incs/code_mirror/addon/selection/active-line.js'/>"></script>
<link rel="stylesheet" href="<inp2:m_Compress files='incs/code_mirror/lib/codemirror.css'/>">
<inp2:m_Set codemirror_included="1"/>
</inp2:m_ifnot>
<inp2:adm_IncludeCodeMirrorFilesByLanguage language="$language" render_as="code_mirror_resource"/>
<script type="text/javascript">
$CodeMirrorEditors["<inp2:$prefix_InputName field='$field'/>"] = {mode: "<inp2:m_Param name='language'/>", tabindex: <inp2:m_Get name="tab_index"/>, indentWithTabs: true, indentUnit: 4, lineWrapping: true<inp2:m_if check="m_Param" name="codemirror_options">, <inp2:m_Param name="codemirror_options"/></inp2:m_if>};
Form.addControl('<inp2:{$prefix}_InputName field="$field"/>', <inp2:m_param name="control_options"/>);
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="textarea_ml_field_caption_element">
<inp2:m_RenderElement name="default_field_caption_element" pass_params="1"/>
<br>
<inp2:m_if check="m_ParamEquals" name="allow_html" value="allow_html">
<inp2:{$prefix}_InputName field="$field" result_to_var="input_name"/>
<a href="<inp2:m_Link template='$edit_template' TargetField='$input_name' pass_through='TargetField' pass='m,$prefix'/>" onclick="openSelector('<inp2:m_Param name='prefix' js_escape='1'/>', this.href, '', '800x575'); return false;">
<img src="img/icons/icon24_link_editor.gif" style="cursor: hand;" border="0">
</a>
</inp2:m_if>
<a href="javascript:PreSaveAndOpenTranslator('<inp2:m_param name="prefix"/>', '<inp2:m_param name="field"/>', 'popups/translator', 1);" title="<inp2:m_Phrase label='la_Translate'/>"><img src="img/icons/icon24_translate.png" border="0"></a>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="source_language_field_caption_element">
<label for="<inp2:m_param name='NamePrefix'/><inp2:{$prefix}_InputName field='$field'/>">
<span class="<inp2:m_if check='{$prefix}_HasError' field='$field'>error-cell</inp2:m_if>"><inp2:$prefix_SourceLanguageTitle label="$title"/></span><inp2:m_if check="{$prefix}_IsRequired" field="$field"><span class="field-required">&nbsp;*</span></inp2:m_if>:<inp2:m_if check="{$prefix}_FieldHintLabel" title_label="$title" direct_label="$hint_label"><span>&nbsp;<img src="<inp2:m_TemplatesBase/>/img/hint_icon.png" width="12" height="13" title="<inp2:$prefix_FieldHintLabel title_label='$title' direct_label='$hint_label' html_escape='1'/>" alt="<inp2:$prefix_FieldHintLabel title_label='$title' direct_label='$hint_label' html_escape='1'/>"/></inp2:m_if>
</label>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_textarea_ml">
<inp2:m_RenderElement name="inp_edit_textarea" format="no_default" pass_params="true"/>
<!--##<inp2:m_RenderElement design="form_row" block_name="inp_edit_textarea_ml" pass_params="1">
<td class="control-cell">
<textarea style="<inp2:m_Param name='style'/>" tabindex="<inp2:m_Get name='tab_index'/>" id="<inp2:{$prefix}_InputName field='$field'/>" name="<inp2:{$prefix}_InputName field='$field'/>" cols="<inp2:m_param name='cols'/>" rows="<inp2:m_param name='rows'/>" class="<inp2:m_param name='class'/>"><inp2:{$prefix}_Field field="$field" format="fck_ready,{$format}"/></textarea>
<script type="text/javascript">
Form.addControl('<inp2:{$prefix}_InputName field="$field"/>', <inp2:m_param name="control_options"/>);
</script>
</td>
</inp2:m_RenderElement>##-->
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_user" class="" size="" old_style="0" onkeyup="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_user" pass_params="1">
<td class="control-cell">
<input type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" class="<inp2:m_param name='class'/>" onkeyup="<inp2:m_Param name='onkeyup'/>">
<inp2:m_if check="m_ParamEquals" name="old_style" value="1">
<a href="#" onclick="return OpenUserSelector('','kernel_form','<inp2:{$prefix}_InputName field="$field"/>');">
<inp2:m_else/>
<a href="<inp2:m_t t='user_selector' pass='all,$prefix'/>" onclick="openSelector('<inp2:m_param name='prefix' js_escape='1'/>', this.href, '<inp2:m_param name='field' js_escape='1'/>'); return false;">
</inp2:m_if>
<img src="img/icons/icon24_link_user.gif" style="cursor:hand;" border="0">
</a>
<script type="text/javascript">
function processUserSelector($field, $selector) {
var $field_mask = '<inp2:$prefix_InputName name="#FIELD_NAME#"/>';
var $user_id = parseInt( $selector.Grids['u'].GetSelected() );
$( jq('#' + $field_mask.replace('#FIELD_NAME#', $field)) ).val( $selector.$user_logins[$user_id] );
}
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_category" class="" size="" old_style="0" onkeyup="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_category" pass_params="1">
<td class="control-cell">
<inp2:m_RenderElement name="inp_edit_hidden" pass_params="1"/>
<table cellpadding="0" cellspacing="0">
<tr>
<td id="<inp2:{$prefix}_InputName field='$field'/>_path"<inp2:m_ifnot check="Field" name="$field" db="db"> style="display: none;"</inp2:m_ifnot>>
<inp2:$prefix_Field field='$field' db="db" result_to_var="category_id"/>
<inp2:m_include template="categories/category_path" category_id="$category_id"/>
</td>
<td valign="middle">
<img src="img/spacer.gif" width="3" height="1" alt=""/>
<a href="<inp2:adm_SelectorLink prefix='$prefix' selection_mode='single' tab_prefixes='none'/>" onclick="openSelector('<inp2:m_param name='prefix' js_escape='1'/>', this.href, '<inp2:m_param name='field' js_escape='1'/>'); return false;">
<img src="img/icons/icon24_cat.gif" width="24" height="24" border="0"/>
</a>
<a href="#" id="<inp2:{$prefix}_InputName field='$field'/>_disable_link"<inp2:m_ifnot check="Field" name="$field" db="db"> style="display: none;"</inp2:m_ifnot>><inp2:m_Phrase name="la_Text_Disable"/></a>
<script type="text/javascript">
function processItemSelector($field, $selector) {
var $item_ids = $selector.Grids[$selector.$Catalog.ActivePrefix].GetSelected();
CategorySelector.setCategory($field, $item_ids.length ? $item_ids.shift() : '');
}
function CategorySelector() {
}
CategorySelector.getField = function ($field) {
return jq('<inp2:{$prefix}_InputName field="#FIELD_NAME#"/>'.replace('#FIELD_NAME#', $field));
}
CategorySelector.setCategory = function ($field, $category_id) {
var $field_id = this.getField($field);
$('#' + $field_id).val($category_id);
$('#' + $field_id + '_path').load(
'<inp2:m_Link template="categories/category_path" category_id="#ID#" no_amp="1" js_escape="1"/>'.replace('#ID#', $category_id),
function () {
$('#' + $field_id + '_path, #' + $field_id + '_disable_link').toggle( $category_id !== '' );
}
);
}
$(document).ready(
function() {
var $field_id = CategorySelector.getField('<inp2:m_Param name="field" js_escape="1"/>');
$('#' + $field_id + '_disable_link').click(
function ($e) {
CategorySelector.setCategory('<inp2:m_Param name="field" js_escape="1"/>', '');
return false;
}
);
}
);
</script>
</td>
</tr>
</table>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_combo_target" title="la_fld_{$field}" caption_render_as="default_field_caption_element" read_only="0" has_empty="" hint_label="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_combo_target" pass_params="1">
<td class="control-cell">
<inp2:m_if check="m_Param" name="read_only">
<inp2:$prefix_Field name="{$field}Type"/>: "<inp2:$prefix_Field name="$field"/>"
<inp2:m_else/>
<select tabindex="<inp2:m_get param='tab_index'/>" name="<inp2:{$prefix}_InputName field='{$field}Type'/>" id="<inp2:{$prefix}_InputName field='{$field}Type'/>">
<inp2:{$prefix}_PredefinedOptions field="{$field}Type" block="inp_option_phrase" selected="selected" has_empty="$has_empty"/>
</select>
<input type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field'/>" tabindex="<inp2:m_get param='tab_index'/>" style="width: 250px;"/>
</inp2:m_if>
<script type="text/javascript">
fields['<inp2:m_Param name="prefix" js_escape="1"/>']['<inp2:m_Param name="field" js_escape="1"/>Type'] = '<inp2:m_phrase label="{$title}Type" js_escape="1"/>';
$( jq('#<inp2:$prefix_InputName name="$field" js_escape="1"/>') ).autocomplete({
source: function( request, response ) {
request['type'] = $( jq('#<inp2:$prefix_InputName name="{$field}Type" js_escape="1"/>') ).val();
$.getJSON(
'<inp2:m_Link template="dummy" pass="m,$prefix" {$prefix}_event="OnSuggestAddressJSON" no_amp="1" js_escape="1"/>',
request,
response
);
}
});
$(document).ready(
function () {
$( jq('#<inp2:$prefix_InputName name="{$field}Type"/>') ).change(
function ($e) {
var $me = $(this);
if ($me.prop('disabled')) {
return ;
}
var $type_selected = !isNaN( parseInt( $me.val() ) );
$( jq('#<inp2:$prefix_InputName name="$field"/>') ).prop('disabled', !$type_selected);
}
)
.change();
}
);
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="email_recipients_js" prefix="email-template" to_readonly="auto">
<script type="text/javascript">
Recipients.formatLine = function($record_index) {
if (this.Records[$record_index]['RecipientName']) {
// name specified
if (this.Records[$record_index]['RecipientAddressType'] == 1) {
// email
this.ResultMask = '#RecipientType#: "#RecipientName#" <#RecipientAddress#>';
}
else {
// other
this.ResultMask = '#RecipientType#: "#RecipientName#" <#RecipientAddress#> - #RecipientAddressType#';
}
}
else {
// name not specified
this.ResultMask = '#RecipientType#: #RecipientAddress# - #RecipientAddressType#';
}
return MultiInputControl.prototype.formatLine.call(this, $record_index);
}
Recipients.compareRecords = function($record_a, $record_b) {
var $equals = true;
var $compare_fields = ['RecipientAddressType', 'RecipientAddress'];
for (var $i = 0; $i < $compare_fields.length; $i++) {
var $field_name = $compare_fields[$i];
if ($record_a[$field_name] !== $record_b[$field_name]) {
return false;
}
}
return $equals;
}
<inp2:m_if check="m_Param" name="to_readonly" equals_to="auto">
<inp2:$prefix_ToRecipientReadOnly result_to_var="to_readonly"/>
</inp2:m_if>
<inp2:m_if check="m_Param" name="to_readonly">
Recipients.isReadOnly = function($record_index) {
return this.Records[$record_index]['RecipientType'] == <inp2:m_GetConst name="EmailTemplate::RECIPIENT_TYPE_TO"/>;
}
<inp2:$prefix_RestoreRecipientType/>
Recipients.registerControl('RecipientType', {type: 'radio', required: true, options: <inp2:m_RenderElement name="inp_json_options" prefix="$prefix" field="RecipientType" strip_nl="2"/>, 'default': <inp2:m_GetConst name="EmailTemplate::RECIPIENT_TYPE_CC"/>});
<inp2:m_else/>
Recipients.registerControl('RecipientType', {type: 'radio', required: true, options: <inp2:m_RenderElement name="inp_json_options" prefix="$prefix" field="RecipientType" strip_nl="2"/>, 'default': <inp2:$prefix_FieldOption field="RecipientType" option="default"/>});
</inp2:m_if>
Recipients.registerControl('RecipientAddressType', {type: 'select', required: true, options: <inp2:m_RenderElement name="inp_json_options" prefix="$prefix" field="RecipientAddressType" strip_nl="2"/>, 'default': <inp2:$prefix_FieldOption field="RecipientAddressType" option="default"/>});
Recipients.registerControl('RecipientAddress', {type: 'textbox', required: true, 'default': '<inp2:$prefix_FieldOption field="RecipientAddress" option="default" js_escape="1"/>'});
Recipients.registerControl('RecipientName', {type: 'textbox', required: false, 'default': '<inp2:$prefix_FieldOption field="RecipientName" option="default" js_escape="1"/>'});
Recipients.LoadValues();
</script>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_option_item">
<option value="<inp2:m_param name='key'/>"<inp2:m_param name="selected"/>><inp2:m_param name="option"/></option>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_option_phrase">
<option value="<inp2:m_param name='key'/>"<inp2:m_param name="selected"/>><inp2:m_phrase label="$option"/></option>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_options" onchange="" has_empty="0" empty_value="" empty_label="" style="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_options" pass_params="1">
<td class="control-cell">
<select tabindex="<inp2:m_Get name='tab_index'/>" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" onchange="<inp2:m_Param name='onchange'/>" style="<inp2:m_Param name='style'/>">
<inp2:m_if check="{$prefix}_FieldOption" field="$field" option="use_phrases">
<inp2:{$prefix}_PredefinedOptions field="$field" block="inp_option_phrase" selected="selected" has_empty="$has_empty" empty_value="$empty_value" empty_label="$empty_label"/>
<inp2:m_else/>
<inp2:{$prefix}_PredefinedOptions field="$field" block="inp_option_item" selected="selected" has_empty="$has_empty" empty_value="$empty_value" empty_label="$empty_label"/>
</inp2:m_if>
</select>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_single_option">
<option value="<inp2:m_param name='value'/>"<inp2:m_if check="{$prefix}_Selected" field="$field" value="$value"> selected</inp2:m_if>><inp2:$prefix_OptionValue field="$field" value="$value"/></option>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_multioptions" has_empty="0" empty_value="" style="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_multioptions" pass_params="1">
<td class="control-cell">
<select multiple tabindex="<inp2:m_Get name='tab_index'/>" id="<inp2:{$prefix}_InputName field='$field'/>_select" onchange="update_multiple_options('<inp2:{$prefix}_InputName field='$field'/>');">
<inp2:m_if check="{$prefix}_FieldOption" field="$field" option="use_phrases">
<inp2:{$prefix}_PredefinedOptions field="$field" block="inp_option_phrase" selected="selected" has_empty="$has_empty" empty_value="$empty_value"/>
<inp2:m_else/>
<inp2:{$prefix}_PredefinedOptions field="$field" block="inp_option_item" selected="selected" has_empty="$has_empty" empty_value="$empty_value"/>
</inp2:m_if>
</select>
<input type="hidden" id="<inp2:{$prefix}_InputName field='$field'/>" name="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' db='db'/>"/>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_radio_item" onclick="" onchange="">
<input type="radio" <inp2:m_param name="checked"/> name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field="$field"/>_<inp2:m_param name="key"/>" value="<inp2:m_param name="key"/>" tabindex="<inp2:m_Get name='tab_index'/>" onclick="<inp2:m_param name="onclick"/>" onchange="<inp2:m_param name="onchange"/>"><label for="<inp2:{$prefix}_InputName field="$field"/>_<inp2:m_param name="key"/>"><inp2:m_param name="option"/></label>&nbsp;
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_radio_phrase" onclick="" onchange="">
<input type="radio" <inp2:m_param name="checked"/> name="<inp2:{$prefix}_InputName field="$field"/>" id="<inp2:{$prefix}_InputName field="$field"/>_<inp2:m_param name="key"/>" value="<inp2:m_param name="key"/>" tabindex="<inp2:m_Get name='tab_index'/>" onclick="<inp2:m_param name="onclick"/>" onchange="<inp2:m_param name="onchange"/>"><label for="<inp2:{$prefix}_InputName field="$field"/>_<inp2:m_param name="key"/>"><inp2:m_phrase label="$option"/></label>&nbsp;
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_radio" onclick="" onchange="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_radio" pass_params="1">
<td class="control-cell">
<inp2:m_if check="{$prefix}_FieldOption" field="$field" option="use_phrases">
<inp2:{$prefix}_PredefinedOptions field="$field" block="inp_radio_phrase" selected="checked" onclick="$onclick" onchange="$onchange" />
<inp2:m_else />
<inp2:{$prefix}_PredefinedOptions field="$field" block="inp_radio_item" selected="checked" onclick="$onclick" onchange="$onchange" />
</inp2:m_if>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="js_option_item">
'<inp2:m_Param name="key" js_escape="1"/>': '<inp2:m_Param name="option" js_escape="1"/>'<inp2:m_ifnot check="m_Param" name="is_last">, </inp2:m_ifnot>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="js_option_phrase">
'<inp2:m_Param name="key" js_escape="1"/>': '<inp2:m_Phrase name="$option" js_escape="1"/>'<inp2:m_ifnot check="m_Param" name="is_last">, </inp2:m_ifnot>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_json_options">
<inp2:m_if check="{$prefix}_FieldOption" field="$field" option="use_phrases">
{<inp2:{$prefix}_PredefinedOptions field="$field" block="js_option_phrase" selected="selected"/>}
<inp2:m_else />
{<inp2:{$prefix}_PredefinedOptions field="$field" block="js_option_item" selected="selected"/>}
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_checkbox" field_class="" onchange="" onclick="" NamePrefix="_cb_">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_checkbox" pass_params="1">
<td class="control-cell">
<input type="hidden" id="<inp2:{$prefix}_InputName field='$field'/>" name="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' db='db'/>"/>
<input tabindex="<inp2:m_Get name='tab_index'/>" type="checkbox" id="_cb_<inp2:{$prefix}_InputName field='$field'/>" <inp2:{$prefix}_Field field="$field" checked="checked" db="db"/> class="<inp2:m_param name='field_class'/>" onchange="update_checkbox(this, document.getElementById('<inp2:{$prefix}_InputName field='$field'/>'));<inp2:m_param name='onchange'/>" onclick="<inp2:m_param name='onclick'/>">
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_checkbox_item">
<input type="checkbox" <inp2:m_param name='checked'/> id="<inp2:{$prefix}_InputName field='$field'/>_<inp2:m_param name='key'/>" value="<inp2:m_param name='key'/>" tabindex="<inp2:m_Get name='tab_index'/>" onclick="update_checkbox_options(/^<inp2:{$prefix}_InputName field='$field' as_preg='1'/>_([0-9A-Za-z-]+)/, '<inp2:{$prefix}_InputName field='$field'/>');"><label for="<inp2:{$prefix}_InputName field='$field'/>_<inp2:m_param name='key'/>"><inp2:m_param name="option"/></label><inp2:m_if check="m_Param" name="has_br"><br/><inp2:m_else/>&nbsp;</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_checkbox_phrase">
<input type="checkbox" <inp2:m_param name='checked'/> id="<inp2:{$prefix}_InputName field='$field'/>_<inp2:m_param name='key'/>" value="<inp2:m_param name='key'/>" tabindex="<inp2:m_Get name='tab_index'/>" onclick="update_checkbox_options(/^<inp2:{$prefix}_InputName field='$field' as_preg='1'/>_([0-9A-Za-z-]+)/, '<inp2:{$prefix}_InputName field='$field'/>');"><label for="<inp2:{$prefix}_InputName field='$field'/>_<inp2:m_param name='key'/>"><inp2:m_phrase label="$option"/></label><inp2:m_if check="m_Param" name="has_br"><br/><inp2:m_else/>&nbsp;</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_checkboxes" no_empty="" has_br="0">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_checkboxes" pass_params="1">
<td class="control-cell">
<inp2:m_if check="{$prefix}_FieldOption" field="$field" option="use_phrases">
<inp2:{$prefix}_PredefinedOptions field="$field" no_empty="$no_empty" block="inp_checkbox_phrase" selected="checked" has_br="$has_br"/>
<inp2:m_else/>
<inp2:{$prefix}_PredefinedOptions field="$field" no_empty="$no_empty" block="inp_checkbox_item" selected="checked" has_br="$has_br"/>
</inp2:m_if>
<inp2:m_RenderElement prefix="$prefix" name="inp_edit_hidden" field="$field" db="db"/>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_single_checkbox">
<input type="checkbox"<inp2:m_if check="{$prefix}_Selected" field="$field" value="$value"> checked</inp2:m_if> id="<inp2:{$prefix}_InputName field='$field'/>_<inp2:m_param name='value'/>" value="<inp2:m_param name='value'/>" onclick="update_checkbox_options(/^<inp2:{$prefix}_InputName field='$field' as_preg='1'/>_([0-9A-Za-z-]+)/, '<inp2:{$prefix}_InputName field='$field'/>');"><label for="<inp2:{$prefix}_InputName field='$field'/>_<inp2:m_param name='value'/>"><inp2:$prefix_OptionValue field="$field" value="$value"/></label>&nbsp;
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_checkbox_allow_html" field_class="" onchange="" onclick="" title="la_enable_html" hint_label="la_Warning_Enable_HTML">
<inp2:m_RenderElement name="inp_edit_checkbox" pass_params="1"/>
<!--##
<inp2:m_if check="{$prefix}_FieldVisible" field="$field">
<tr class="<inp2:m_odd_even odd='edit-form-odd' even='edit-form-even'/>" id="<inp2:$prefix_InputName field='$field'/>_row">
<td class="control-cell">
<input type="hidden" id="<inp2:{$prefix}_InputName field='$field'/>" name="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field' db='db'/>">
<input tabindex="<inp2:m_Get name='tab_index'/>" type="checkbox" id="_cb_<inp2:{$prefix}_InputName field='$field'/>" name="_cb_<inp2:{$prefix}_InputName field='$field'/>" <inp2:{$prefix}_Field field="$field" checked="checked" db="db"/> class="<inp2:m_param name='field_class'/>" onchange="update_checkbox(this, document.getElementById('<inp2:{$prefix}_InputName field='$field'/>'));<inp2:m_param name='onchange'/>" onclick="<inp2:m_param name='onclick'/>">
</td>
<inp2:m_RenderElement name="inp_edit_error" pass_params="1"/>
</tr>
</inp2:m_if>
##-->
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_weight" class="" size="" maxlength="" onblur="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_weight" pass_params="1">
<td class="control-cell">
<inp2:m_if check="lang.current_FieldEquals" field="UnitSystem" value="1">
<input type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" maxlength="<inp2:m_param name='maxlength'/>" class="<inp2:m_param name='class'/>" onblur="<inp2:m_Param name='onblur'/>">
<inp2:m_phrase label="la_kg" />
</inp2:m_if>
<inp2:m_if check="lang.current_FieldEquals" field="UnitSystem" value="2">
<input type="text" name="<inp2:{$prefix}_InputName field='{$field}_a'/>" id="<inp2:{$prefix}_InputName field='{$field}_a'/>" value="<inp2:{$prefix}_Field field='{$field}_a'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" maxlength="<inp2:m_param name='maxlength'/>" class="<inp2:m_param name='class'/>" onblur="<inp2:m_Param name='onblur'/>">
<inp2:m_phrase label="la_lbs" />
<input type="text" name="<inp2:{$prefix}_InputName field='{$field}_b'/>" id="<inp2:{$prefix}_InputName field='{$field}_b'/>" value="<inp2:{$prefix}_Field field='{$field}_b'/>" tabindex="<inp2:m_Get name='tab_index'/>" size="<inp2:m_param name='size'/>" maxlength="<inp2:m_param name='maxlength'/>" class="<inp2:m_param name='class'/>" onblur="<inp2:m_Param name='onblur'/>">
<inp2:m_phrase label="la_oz" />
</inp2:m_if>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_minput" style="" format="" allow_add="1" allow_edit="1" allow_delete="1" allow_move="1" title="">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_minput" pass_params="1">
<td class="control-cell">
<table>
<tr>
<td colspan="2">
<input type="button" class="button" style="width: 70px;" value="<inp2:m_Phrase name='la_btn_Add'/>" id="<inp2:$prefix_InputName field='$field'/>_add_button"/>
<input type="button" class="button" style="width: 70px;" value="<inp2:m_Phrase name='la_btn_Cancel'/>" id="<inp2:$prefix_InputName field='$field'/>_cancel_button"/>
</td>
</tr>
<tr>
<td valign="top">
<select multiple tabindex="<inp2:m_Get name='tab_index'/>" id="<inp2:$prefix_InputName field='$field'/>_minput" style="<inp2:m_Param name='style'/>">
</select>
</td>
<td valign="top">
<inp2:m_if check="m_Param" name="allow_edit">
<input type="button" class="button" style="width: 100px;" value="<inp2:m_Phrase name='la_btn_Edit'/>" id="<inp2:$prefix_InputName field='$field'/>_edit_button"/><br />
<img src="img/spacer.gif" height="4" width="1" alt=""/><br />
</inp2:m_if>
<inp2:m_if check="m_Param" name="allow_delete">
<input type="button" class="button" style="width: 100px;" value="<inp2:m_Phrase name='la_btn_Delete'/>" id="<inp2:$prefix_InputName field='$field'/>_delete_button"/><br />
</inp2:m_if>
<inp2:m_if check="m_Param" name="allow_move">
<br /><br />
<input type="button" class="button" style="width: 100px;" value="<inp2:m_Phrase name='la_btn_MoveUp'/>" id="<inp2:$prefix_InputName field='$field'/>_moveup_button"/><br />
<img src="img/spacer.gif" height="4" width="1" alt=""/><br />
<input type="button" class="button" style="width: 100px;" value="<inp2:m_Phrase name='la_btn_MoveDown'/>" id="<inp2:$prefix_InputName field='$field'/>_movedown_button"/><br />
</inp2:m_if>
</td>
</tr>
<inp2:m_RenderElement name="inp_edit_hidden" prefix="$prefix" field="$field" db="db"/>
<script type="text/javascript">
var <inp2:m_Param name="field"/> = new MultiInputControl('<inp2:m_Param name="field"/>', '<inp2:{$prefix}_InputName field="#FIELD_NAME#"/>', fields['<inp2:m_Param name="prefix"/>'], '<inp2:m_Param name="format"/>');
<inp2:m_Param name="field"/>.ValidateURL = '<inp2:m_Link template="dummy" pass="m,$prefix" {$prefix}_event="OnValidateMInputFields" no_amp="1" js_escape="1"/>';
<inp2:m_if check="m_Param" name="allow_add">
<inp2:m_Param name="field"/>.SetPermission('add', true);
</inp2:m_if>
<inp2:m_if check="m_Param" name="allow_edit">
<inp2:m_Param name="field"/>.SetPermission('edit', true);
</inp2:m_if>
<inp2:m_if check="m_Param" name="allow_delete">
<inp2:m_Param name="field"/>.SetPermission('delete', true);
</inp2:m_if>
<inp2:m_if check="m_Param" name="allow_move">
<inp2:m_Param name="field"/>.SetPermission('move', true);
</inp2:m_if>
<inp2:m_Param name="field"/>.InitEvents();
<inp2:m_Param name="field"/>.SetMessage('required_error', '<inp2:m_Phrase name="la_err_required" escape="1"/>');
<inp2:m_Param name="field"/>.SetMessage('unique_error', '<inp2:m_Phrase name="la_error_unique" escape="1"/>');
<inp2:m_Param name="field"/>.SetMessage('delete_confirm', '<inp2:m_Phrase label="la_Delete_Confirm" escape="1"/>');
<inp2:m_Param name="field"/>.SetMessage('add_button', '<inp2:m_Phrase name="la_btn_Add" escape="1"/>');
<inp2:m_Param name="field"/>.SetMessage('save_button', '<inp2:m_Phrase name="la_btn_Save" escape="1"/>');
</script>
</table>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_picker" has_empty="0" empty_value="" style="width: 225px;" size="15">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_picker" pass_params="1">
<td class="control-cell">
<table cellpadding="0" cellspacing="0">
<tr>
<td><strong><inp2:m_Phrase label="la_SelectedItems" /></strong></td>
<td>&nbsp;</td>
<td><strong><inp2:m_Phrase label="la_AvailableItems" /></strong></td>
</tr>
<tr>
<td>
<inp2:m_DefineElement name="picker_option_block">
<option value="<inp2:Field name='$key_field' />"><inp2:Field name="$value_field" /></option>
</inp2:m_DefineElement>
<select multiple id="<inp2:$prefix_InputName name='$field' />_selected" style="<inp2:m_param name='style'/>" size="<inp2:m_param name='size'/>">
<inp2:$optprefix.selected_PrintList render_as="picker_option_block" key_field="$option_key_field" value_field="$option_value_field" per_page="-1" requery="1" link_to_prefix="$prefix" link_to_field="$field"/>
</select>
</td>
<td align="center">
<img src="img/icons/icon_left.gif" id="<inp2:$prefix_InputName name="$field" />_move_left_button"/><br />
<img src="img/icons/icon_right.gif" id="<inp2:$prefix_InputName name="$field" />_move_right_button"/>
</td>
<td>
<select multiple id="<inp2:$prefix_InputName name='$field' />_available" style="<inp2:m_param name='style'/>" size="<inp2:m_param name='size'/>">
<inp2:$optprefix.available_PrintList render_as="picker_option_block" key_field="$option_key_field" value_field="$option_value_field" requery="1" per_page="-1" link_to_prefix="$prefix" link_to_field="$field"/>
</select>
</td>
</tr>
</table>
<input type="hidden" name="<inp2:$prefix_InputName name='$field' />" id="<inp2:$prefix_InputName name='$field' />" value="<inp2:$prefix_Field field='$field' db='db'/>">
<input type="hidden" name="unselected_<inp2:$prefix_InputName name='$field' />" id="<inp2:$prefix_InputName name='$field' />_available_field" value="">
<script type="text/javascript">
<inp2:m_Param name="field"/> = new EditPickerControl('<inp2:m_Param name="field"/>', '<inp2:$prefix_InputName name="$field" />');
<inp2:m_Param name="field"/>.SetMessage('nothing_selected', '<inp2:m_Phrase label="la_error_SelectItemToMove" escape="1"/>');
</script>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_cron_box">
<inp2:m_RenderElement design="form_row" block_name="inp_edit_cron_box" pass_params="1">
<td class="control-cell">
<input style="width: 150px;" type="text" name="<inp2:{$prefix}_InputName field='$field'/>" id="<inp2:{$prefix}_InputName field='$field'/>" value="<inp2:{$prefix}_Field field='$field'/>" tabindex="<inp2:m_Get name='tab_index'/>"/>
<select tabindex="<inp2:m_Get name='tab_index'/>" name="<inp2:{$prefix}_InputName field='{$field}Hints'/>" id="<inp2:{$prefix}_InputName field='{$field}Hints'/>" style="width: 175px;">
<inp2:{$prefix}_PredefinedOptions field="{$field}Hints" block="inp_option_item" selected="selected" has_empty="1" empty_value="" empty_label="la_opt_CronCommonSettings"/>
</select>
</td>
</inp2:m_RenderElement>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="inp_edit_filler" control_options="false">
<tr class="<inp2:m_odd_even odd='edit-form-odd' even='edit-form-even'/>" style="height: auto">
<td class="label-cell-filler" ></td>
<td class="control-mid-filler" ></td>
<td class="control-cell-filler">
<input type="text" style="display: none;"/>
<div id="form_filler" style="width: 100%; height: 5px; background-color: inherit"></div>
<script type="text/javascript">
Form.addControl('form_filler', <inp2:m_param name="control_options"/>);
</script>
</td>
</tr>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="ajax_progress_bar">
<table width="100%" border="0" cellspacing="0" cellpadding="2" class="tableborder">
<tr class="<inp2:m_odd_even odd='table-color1' even='table-color2'/>">
<td colspan="2">
<img src="img/spacer.gif" height="10" width="1" alt="" /><br />
<!-- progress bar paddings: begin -->
<table width="90%" cellpadding="2" cellspacing="0" border="0" align="center">
<tr>
<td class="progress-text">0%</td>
<td width="100%">
<!-- progress bar: begin -->
<table cellspacing="0" cellpadding="0" width="100%" border="0" align="center" style="background-color: #FFFFFF; border: 1px solid #E6E6E6;">
<tr>
<td colspan="3"><img src="img/spacer.gif" height="2" width="1" alt="" /></td>
</tr>
<tr>
<td width="2"><img src="img/spacer.gif" height="13" width="3" alt="" /></td>
<td align="center" width="100%">
<table cellspacing="0" cellpadding="0" width="100%" border="0" style="background: url(img/progress_left.gif) repeat-x;">
<tr>
<td id="progress_bar[done]" style="background: url(img/progress_done.gif);" align="left"></td>
<td id="progress_bar[left]" align="right"><img src="img/spacer.gif" height="9" width="1" alt="" /></td>
</tr>
</table>
</td>
<td width="1"><img src="img/spacer.gif" height="13" width="3" alt="" /></td>
</tr>
<tr>
<td colspan="3"><img src="img/spacer.gif" height="2" width="1" alt="" /></td>
</tr>
</table>
<!-- progress bar: end -->
</td>
<td class="progress-text">100%</td>
</tr>
</table>
<!-- progress bar paddings: end -->
<img src="img/spacer.gif" height="10" width="1" alt="" /><br />
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="2" class="tableborder">
<tr class="<inp2:m_odd_even odd='table-color1' even='table-color2'/>">
<td width="50%" align="right"><inp2:m_phrase name="la_fld_PercentsCompleted"/>:</td>
<td id="progress_display[percents_completed]">n/a</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="2" class="tableborder">
<tr class="<inp2:m_odd_even odd='table-color1' even='table-color2'/>">
<td width="50%" align="right"><inp2:m_phrase name="la_fld_ElapsedTime"/>:</td>
<td id="progress_display[elapsed_time]">n/a</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="2" class="tableborder">
<tr class="<inp2:m_odd_even odd='table-color1' even='table-color2'/>">
<td width="50%" align="right"><inp2:m_phrase name="la_fld_EstimatedTime"/>:</td>
<td id="progress_display[Estimated_time]">n/a</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="2" class="tableborder">
<tr class="<inp2:m_odd_even odd='table-color1' even='table-color2'/>">
<td align="center" colspan="2">
<input type="button" class="button" onclick="<inp2:m_param name='cancel_action'/>" value="<inp2:m_phrase name='la_Cancel'/>" />
</td>
</tr>
</table>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="edit_navigation" toolbar="a_toolbar">
<inp2:m_if check="{$prefix}_IsTopmostPrefix">
<inp2:m_if check="{$prefix}_IsSingle">
<inp2:m_param name="toolbar"/>.HideButton('prev');
<inp2:m_param name="toolbar"/>.HideButton('next');
<inp2:m_else/>
<inp2:m_if check="{$prefix}_IsLast">
<inp2:m_param name="toolbar"/>.DisableButton('next');
</inp2:m_if>
<inp2:m_if check="{$prefix}_IsFirst">
<inp2:m_param name="toolbar"/>.DisableButton('prev');
</inp2:m_if>
</inp2:m_if>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="toolbar_button" icon="" title="" short_title="" toolbar="a_toolbar">
<inp2:m_Param name="toolbar"/>.AddButton(
new ToolBarButton(
'<inp2:m_Param name="icon"/>',
<inp2:m_if check="m_Param" name="short_title">
'<inp2:m_Phrase label="$title" escape="1"/>::<inp2:m_phrase label="$short_title" escape="1"/>',
<inp2:m_else/>
'<inp2:m_Phrase label="$title" escape="1"/>',
</inp2:m_if>
function() {
<inp2:m_Param name="content"/>
}
)
);
</inp2:m_DefineElement>
<inp2:m_DefineElement name="tabs_container" tabs_render_as="">
<table cellpadding="0" cellspacing="0" style="width: 100%;">
<tr>
<td style="width: 20px;">
<img src="<inp2:m_TemplatesBase/>/img/spacer.gif" width="20" height="0" alt=""/><br/>
<a href="#" class="scroll-left disabled"></a>
</td>
<td height="23" align="right">
<div id="tab-measure" style="display: none; width: 100%; height: 23px;">&nbsp;</div>
<div style="overflow: hidden; height: 23px;" class="tab-viewport">
<table class="tabs" cellpadding="0" cellspacing="0" height="23">
<tr>
<inp2:m_RenderElement name="$tabs_render_as" pass_params="1"/>
</tr>
</table>
</div>
</td>
<td class="scroll-right-container disabled">
<img src="<inp2:m_TemplatesBase/>/img/spacer.gif" width="20" height="0" alt=""/><br/>
<a href="#" class="scroll-right disabled"></a>
</td>
</tr>
</table>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="edit_tabs_element">
<inp2:m_DefineElement name="edit_tab">
<inp2:m_RenderElement name="tab" title="$title" t="$template" main_prefix="$PrefixSpecial"/>
</inp2:m_DefineElement>
<inp2:{$prefix}_PrintEditTabs render_as="edit_tab" preset_name="$preset_name"/>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="edit_tabs" preset_name="Default">
<inp2:m_if check="{$prefix}_HasEditTabs" preset_name="$preset_name">
<inp2:m_RenderElement name="tabs_container" tabs_render_as="edit_tabs_element" pass_params="1"/>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="ml_selector" prefix="">
<inp2:m_if check="lang_IsMultiLanguage">
<td align="right" style="padding-right: 5px;">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="right">
<inp2:m_phrase name="la_fld_Language"/>:
<select name="language" onchange="submit_event('<inp2:m_param name='prefix'/>', 'OnPreSaveAndChangeLanguage');">
<inp2:m_DefineElement name="lang_elem">
<option value="<inp2:Field name='LanguageId'/>" <inp2:m_if check="SelectedLanguage" type="data">selected="selected"</inp2:m_if> ><inp2:Field name="LocalName" no_special='no_special' /></option>
</inp2:m_DefineElement>
<inp2:lang_PrintList render_as="lang_elem"/>
</select>
</td>
</tr>
<tr>
<td align="right" style="vertical-align: bottom; padding: 2px 0px 5px 2px;">
<span style="color: red">*</span>&nbsp;<span class="req-note"><inp2:m_Phrase name="la_text_RequiredFields"/></span>
</td>
</tr>
</table>
</td>
<inp2:m_else/>
<td align="right" style="vertical-align: bottom; padding: 2px 5px 5px 2px;">
<span style="color: red">*</span>&nbsp;<span class="req-note"><inp2:m_Phrase name="la_text_RequiredFields"/></span>
</td>
</inp2:m_if>
</inp2:m_DefineElement>
<inp2:m_DefineElement name="form_error_warning">
<inp2:m_RenderElement design="form_message" pass_params="1">
<inp2:m_Phrase name="la_Warning_NewFormError"/><br/>
<span id="error_msg_<inp2:m_Param name='prefix'/>" style="font-weight: bold;"><br/></span>
</inp2:m_RenderElement>
</inp2:m_DefineElement>

Event Timeline