diff --git b/AdminInformax.gif a/AdminInformax.gif new file mode 100644 index 0000000..7e4c010 --- /dev/null +++ a/AdminInformax.gif diff --git b/ComunesImaxStockAvelonEmptyingModule.php a/ComunesImaxStockAvelonEmptyingModule.php new file mode 100644 index 0000000..909951f --- /dev/null +++ a/ComunesImaxStockAvelonEmptyingModule.php @@ -0,0 +1,552 @@ +context->controller->addCss($this->_path.'css/'.$css, 'all'); + } + + return; + } + + public function addJS($js) { + $tab = Tools::getValue('tab', 0); + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) { + $this->context->controller->addJs($this->_path.'js/'.$js); + } + + return; + } + + public function addJqueryUI($plugin) { + $tab = Tools::getValue('tab', 0); + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) { + if ($this->context->controller instanceof stdClass) { + $this->context->controller = new AdminModulesController(); + } + $this->context->controller->addJqueryUI($plugin); + } + return; + } + + public function createHelpHeader() { + $html = '
'; + $html .= $this->getDatosPubli('header'); + $html .= '
'; + + $html .= '
'; + $html .= '
'; + $html .= '

' . $this->displayName . '' . (Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER') ? " " . Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER') . " " : ' by Informax ' ) . '

'; + $html .= '

' . $this->l('Version: ') . $this->version . '

'; + $html .= '
'; + + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + + return $html; + } + + public function getModuleFooter() { + $url = Configuration::getGlobalValue(self::prefijo.'URL_FALDON'); + $html = '
'; + $html .= '
+ + '; + $html .= '

'.$this->l('Si deseas enterarte de todos los cambios en nuestros modulos, nuevos Modulos, como funciona Prestashop, apuntate a nuestras news').'

'; + $html .= '

'; + $html .= ' '; + $html .= ''; + $html .= ''; + $html .= '

'; + $html .= '

'; + $html .= '
'; + $html .= ''; + return $html; + } + + public function getDatosPubli($tipo) { + $datosCompletos = Configuration::getGlobalValue(self::prefijo.'TXT_FILE'); + + $html = ''; + if ($datosCompletos) { + $aperturaA = ''; + $cierreA = ''; + $datosCompletos = @unserialize($datosCompletos); + if (!is_array($datosCompletos)) { + return ''; + } + shuffle($datosCompletos); + foreach ($datosCompletos AS $datoElemento) { + if ($datoElemento[1] == $tipo) { + if (trim($datoElemento[2]) != '') { + $aperturaA .= ''; + $cierreA = ''; + } + if (trim($datoElemento[0]) != '') { + $html = $aperturaA.''.$cierreA; + } + return $html; + } + } + } + return $html; + } + + public function getTxtFiles() { + if (Configuration::getGlobalValue(self::prefijo.'DESCARGA_ARCHIVO') < 10000) { + Configuration::updateGlobalValue(self::prefijo.'DESCARGA_ARCHIVO', Configuration::getGlobalValue(self::prefijo.'DESCARGA_ARCHIVO') + 1); + return false; + } + $url = Configuration::getGlobalValue(self::prefijo.'URL_TXT').'/'.Configuration::getGlobalValue(self::prefijo.'TIPO').'.txt'; + $basedir_active = ini_get('open_basedir'); + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($ch, CURLOPT_BINARYTRANSFER, TRUE); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + if (!$basedir_active) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE); + } + $datos = curl_exec($ch); + $infoCurl = curl_getinfo($ch); + if (!isset($infoCurl['http_code']) || $infoCurl['http_code'] != 200) { + + return false; + } + Configuration::updateGlobalValue(self::prefijo.'DESCARGA_ARCHIVO', 0); + return Configuration::updateGlobalValue(self::prefijo.'TXT_FILE', $this->readCsv($datos)); + } + + private function readCsv($string) { + $string = trim($string); + $temp = explode("\n", $string); + $respuesta = array(); + foreach ($temp AS $elemento) { + $elementoInterno = array(); + $elementoInterno = explode(";", $elemento); + $respuesta[] = $elementoInterno; + } + return serialize($respuesta); + } + + public function l($msg, $modulo = '', $locale = null) { + if ($modulo == '') { + $modulo = 'traducciones' . strtolower($this->name); + } + return parent::l($msg, $modulo, $locale); + } + + /** + * Crea un nuevo tab. + * @param string $clase + * @param string $nombre + * @param string $padre + * @return boolean + */ + private function crearTab($clase, $nombre, $padre = '') { + if (!Tab::getIdFromClassName($clase)) { + $tab = new Tab(); + $tab->active = 1; + $tab->class_name = $clase; + $tab->name = array(); + foreach (Language::getLanguages(true) as $lang) { + $tab->name[$lang['id_lang']] = $nombre; + } + if ($padre == '') { + $posicion = 0; + } + else { + $posicion = Tab::getIdFromClassName($padre); + } + $tab->id_parent = intval($posicion); + $tab->module = $this->name; + try { + if (!$tab->add()) { + return false; + } + } + catch (Exception $exc) { + return false; + } + } + + return true; + } + + /** + * Borra un tab. + * @param string $clase + * @return boolean + */ + private function borrarTab($clase) { + $id_tab = (int)Tab::getIdFromClassName($clase); + if ($id_tab) { + $tab = new Tab($id_tab); + try { + if (!$tab->delete()) { + return false; + } + } + catch (Exception $exc) { + return false; + } + } + + return true; + } + + /** + * Instala los tabs del módulo. + * @return boolean + */ + private function installTab() { + include(dirname(__FILE__).'/configuration.php'); + + //Instalamos el root + if (isset($moduleTabRoot) && $moduleTabRoot) { + $this->crearTab($moduleTabRoot['clase'], $moduleTabRoot['name']); + } + + //Instalamos el resto de tabs + if (isset($moduleTabs) && $moduleTabs) { + foreach ($moduleTabs AS $moduleTab) { + $this->borrarTab($moduleTab['clase']); + if (!$this->crearTab($moduleTab['clase'], $moduleTab['name'], $moduleTab['padre'])) { + return false; + } + } + } + + return true; + } + + /** + * Desinstala los tabs del módulo. + * @return boolean + */ + private function uninstallTab() { + include(dirname(__FILE__).'/configuration.php'); + + //Desinstalamos las tabs de este módulo + if (isset($moduleTabs) && $moduleTabs) { + foreach ($moduleTabs AS $moduleTab) { + if (!$this->borrarTab($moduleTab['clase'])) { + return false; + } + } + } + + //Desinstalamos el root si está vacío + if (isset($moduleTabRoot) && $moduleTabRoot) { + $id_tab = (int)Tab::getIdFromClassName($moduleTabRoot['clase']); + if ($id_tab && Tab::getNbTabs($id_tab) == 0) { + if (!$this->borrarTab($moduleTabRoot['clase'])) { + return false; + } + } + } + + return true; + } + + public function checkLicencia($force = 0) { + return true; + // $data = array(); + // $f = Configuration::getGlobalValue(self::prefijo.'F'); + // $check = Configuration::getGlobalValue(self::prefijo.'F_CHECK'); + // $url = Configuration::getGlobalValue(self::prefijo.'F_SERVER'); + // $data['server'] = $this->getDomain(); + // $data['modulo'] = $this->name; + // $data['action'] = 'checkLicencia'; + // $data['licencia'] = Configuration::getGlobalValue(self::prefijo.'LICENCIA'); + // if ($check >= 100 || $force == 1) { + // $ch = curl_init($url); + // curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + // curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + // curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + // $datos = curl_exec($ch); + // $datos = json_decode($datos); + // $info = curl_getinfo($ch); + // if (!$datos || $datos->codError != 0) { + // Configuration::updateGlobalValue(self::prefijo.'F', ''); + // Configuration::updateGlobalValue(self::prefijo.'F_CHECK', (int)$check + 1); + // return false; + // } + // else { + // Configuration::updateGlobalValue(self::prefijo.'F', $datos->msgError); + // Configuration::updateGlobalValue(self::prefijo.'F_CHECK', 0); + // return true; + // } + // } + // else { + // Configuration::updateGlobalValue(self::prefijo.'F_CHECK', (int)$check + 1); + // return true; + // } + } + + private function getFunction() { + $function = Configuration::getGlobalValue($this->sufijo.'F'); + return $function; + } + + private static function getUrlAdmin() { + $temp = explode('/', $_SERVER['PHP_SELF']); + $dummy = array_pop(($temp)); + $urlAdmin = implode('/', $temp); + if (Configuration::get('PS_SSL_ENABLED') == 1) { + $url = 'https://'; + } + else { + $url = 'http://'; + } + $url .= $_SERVER['SERVER_NAME'].$urlAdmin.'/'; + return $url; + } + + public function getDomain() { + $idShopDefault = Configuration::getGlobalValue('PS_SHOP_DEFAULT'); + $tienda = new Shop($idShopDefault); + if (Validate::isLoadedObject($tienda)) { + if (isset($tienda->domain)) { + return $tienda->domain; + } + } return false; + } + + public function displayWarning($error) { + if ($this->versionPS == 15) { + $output = ' +
+ '.$error.' +
'; + } + else { + $output = '
' + .'

' + .$error. + '

'; + } + return $output; + } + + private function _checkVersion() { + return false; + } + + /** + * Version comun de displayForm. + * @param string[] $tabsArray [$funcion => $nombreTab] + * @param string $extra Para meter incrustado algun js o css. + */ + private function displayFormTrait($tabsArray, $extra = '') { + if (Configuration::getGlobalValue(self::prefijo.'LICENCIA') == '') { + $this->_html .= '
'; + $this->_html .= '
'; + $this->_html .= '

'.$this->l('ATENCION: SU LICENCIA ESTA VACIA').'

'; + $this->_html .= '

'.$this->l('Debe introducir un numero de licencia valido para continuar').'

'; + $this->_html .= '
'; + $this->_html .= '
'; + $this->_html .= $this->_mostrarLicencia(); + $this->forceCheck = 0; + } + elseif (!$this->checkLicencia($this->forceCheck)) { + $this->_html .= '
'; + $this->_html .= '
'; + $this->_html .= '

'.$this->l('ATENCION: SU LICENCIA NO ES VALIDA').'

'; + $this->_html .= '

'.$this->l('Si ejecuta el modulo sin licencia los resultados no seran validos').'

'; + $this->_html .= '

'.$this->l('Haga esto bajo su responsabilidad, Informax no dara soporte ni aceptar quejas o peticiones derivadas del uso sin licencia de nuestros productos').'

'; + $this->_html .= '

'.$this->l('El uso de nuestro software sin licencia constituye una infraccion de las leyes de propiedad intelectual, y sera puesta en conocimiento de las autoridades pertinentes').'

'; + $this->_html .= '

'.$this->l('Si cree que este mensaje es un error, por favor, pongase en contacto con nosotros, enviandonos codigo de licencia, nombre del modulo, dominio de la tienda y copia de la factura de pago').'

'; + $this->_html .= '
'; + $this->_html .= '
'; + $this->_html .= $this->_mostrarLicencia(); + $this->forceCheck = 0; + } + else { + if ($this->_checkVersion()) { + $this->_html .= $this->displayConfirmation($this->_checkVersion()); + } + if ($this->idTab == '' || empty($this->idTab)) { + $this->idTab = 1; + } + $urlTienda = self::getUrlAdmin(); + $token = Tools::getAdminTokenLite('AdminModules'); + $moduleLink = $urlTienda.'index.php?controller=AdminModules&token='.$token.'&configure='.$this->name.'&tab_module=administration&module_name='.$this->name; + + $this->_html .= '' + .$extra; + $this->_html .= ''; + $this->_html .= '
'; + $i = 1; + foreach ($tabsArray as $funcion => $nombreTab) { + $this->_html .= ''; + $i++; + } + $this->_html .= '
'; + $this->_html .= ' + '; + } + } + + private function mostrarLicenciaTrait($idTab) { + include_once(dirname(__FILE__).'/functionsForm.php'); + include_once(dirname(__FILE__).'/imaxAcordeon.php'); + $licenseDomain = $this->getDomain(); + $html = ''; + $form = new imaxForm($this, $this->_path); + $acordeon = new imaxAcordeon($this->_path); + if (!$licenseDomain) { + $form->createFormInfomationText($this->l('La configuracion de su tienda no es correcta. No se puede determinar el dominio')); + $html .= $acordeon->renderAcordeon($this->l('Gestion Licencia'), $form->renderForm()); + Configuration::updateGlobalValue(self::prefijo.'LICENCIA', ''); + Configuration::updateGlobalValue(self::prefijo.'F', ''); + Configuration::updateGlobalValue(self::prefijo.'F_CHECK', 1001); + } + else { + $licencia = Configuration::getGlobalValue(self::prefijo.'LICENCIA'); + $form->createHidden("accion", "gestionLicencia"); + $form->createHidden("idTab", $idTab); + $form->createFormInfomationText($this->l('ATENCION: La licencia es obligatoria para el correcto funcionamiento del modulo')); + $form->createFormInfomationText($this->l('La licencia se deberia generar para el dominio: ').$licenseDomain, 'notice'); + $form->createFormTextGroup('licencia', $licencia, $this->l('Numero de Licencia')); + $form->createSubmitButton('submitLicencia', $this->l('Guardar')); + $html .= $acordeon->renderAcordeon($this->l('Gestion Licencia'), $form->renderForm()); + } + + $urlPubli = Configuration::getGlobalValue($this->sufijo . 'URL_TXT'); + $tipoPubli = Configuration::getGlobalValue($this->sufijo . 'TIPO'); + $nombre = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER'); + $descripcionModulo = Configuration::getGlobalValue($this->sufijo . 'DESCRIPCION_MODULO'); + $nombreModulo = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_MODULO'); + $urlEmpresa = Configuration::getGlobalValue($this->sufijo . 'URL_DEVELOPER'); + $urlManual = Configuration::getGlobalValue($this->sufijo . 'URL_MANUAL'); + $urlSoporte = Configuration::getGlobalValue($this->sufijo . 'URL_TICKETS'); + $sampleFile = "Ejemplo file con el nombre de Fichero:
cabecera.jpg;header;https://www.informax.es
+ footer_a.jpg;footera;https://tienda.informax.es
+ footer_b.jpg;footerb;https://www.informax.es
"; + + $form = new imaxForm($this, $this->_path); + $form->createHidden("accion", "gestionPubli"); + $form->createHidden("idTab", $idTab); + $form->createFormTextGroup('urlPubli', $urlPubli, $this->l('Url Publicidad: ')); + $form->createFormTextGroup('nameDeveloper', $nombre, $this->l('Nombre Desarrollador: ')); + $form->createFormTextGroup('nombreModulo', $nombreModulo, $this->l('Nombre Modulo: ')); + $form->createFormTextGroup('descripcionModulo', $descripcionModulo, $this->l('Descripcion Modulo: ')); + $form->createFormTextGroup('namePubli', $tipoPubli, $this->l('Tipo o nombre del Fichero')); + $form->createFormTextGroup('urlEmpresa', $urlEmpresa, $this->l('Url Empresa')); + $form->createFormTextGroup('urlManual', $urlManual, $this->l('Url Manual')); + $form->createFormTextGroup('urlSoporte', $urlSoporte, $this->l('Url Tickets o Soporte')); + $form->createFormInfomationText($sampleFile); + $form->createSubmitButton('submitLicencia', $this->l('Guardar')); + $html .= $acordeon->renderAcordeon($this->l('Gestion Publicidad'), $form->renderForm(), false, 'acordeonPubli', 'imaxHidden'); + + return $html; + } + + /** + * Instala los tabs del módulo. + * @return boolean + */ + private function installTabNewData() { + $this->uninstallTab(); + + $moduleTabRoot = array(); + $moduleTabRoot['name'] = 'Informax'; + $moduleTabRoot['clase'] = 'AdminInformax'; + $moduleTabRoot['padre'] = ''; + $moduleTabRoot['imagen'] = 'informax.gif'; + + + $nombreModulo = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_MODULO'); + $moduleTabs = array(); + $moduleTabs[0] = array(); + $moduleTabs[0]['name'] = $nombreModulo; + $moduleTabs[0]['clase'] = 'AdminImaxStockAvelonEmptyingModule'; + $moduleTabs[0]['padre'] = 'AdminInformax'; + $moduleTabs[0]['imagen'] = 'informax.gif'; + + + //Instalamos el root + if (isset($moduleTabRoot) && $moduleTabRoot) { + $this->crearTab($moduleTabRoot['clase'], $moduleTabRoot['name']); + } + + //Instalamos el resto de tabs + if (isset($moduleTabs) && $moduleTabs) { + foreach ($moduleTabs AS $moduleTab) { + $this->borrarTab($moduleTab['clase']); + if (!$this->crearTab($moduleTab['clase'], $moduleTab['name'], $moduleTab['padre'])) { + return false; + } + } + } + + return true; + } +} diff --git b/FuncionesImaxStockAvelonEmptyingModule.php a/FuncionesImaxStockAvelonEmptyingModule.php new file mode 100644 index 0000000..9195f1c --- /dev/null +++ a/FuncionesImaxStockAvelonEmptyingModule.php @@ -0,0 +1,22 @@ +modulo = $modulo; + } + + public function vaciarTablaStockAvelon(){ + $module = Module::getInstanceByName('imaxavelonimportcloud'); + if ($module->id) { + if(Db::getInstance()->execute('TRUNCATE TABLE `' . _DB_PREFIX_ . 'imaxStockAvelon`')){ + return 'La tabla ' . _DB_PREFIX_ . 'imaxStockAvelon se vació correctamente'; + }else{ + return 'Ocurrió un error al vaciar la tabla ' . _DB_PREFIX_ . 'imaxStockAvelon.'; + } + } else { + return 'El módulo imaxavelonimportcloud no existe o no está instalado.'; + } + } +} diff --git b/Thumbs.db a/Thumbs.db new file mode 100644 index 0000000..5338598 --- /dev/null +++ a/Thumbs.db diff --git b/acordeon/acordeon.css a/acordeon/acordeon.css new file mode 100644 index 0000000..22ddfc5 --- /dev/null +++ a/acordeon/acordeon.css @@ -0,0 +1,52 @@ +.acordeon, .acordeonInterno{ + font-family: sans-serif; + margin: 0; + position:relative; +} +.acordeon > dl{ margin: 60px auto; } +.acordeon dt, .acordeon dd, .acordeonInterno dt, .acordeonInterno dd{ padding: 10px; } +//.acordeon dt{ background: #333333; color: white; border-bottom: 1px solid #141414; border-top: 1px solid #4E4E4E; cursor: pointer; } +//.acordeon dd{ background: #F5F5F5; line-height: 1.6em; } + +.acordeon dt, +.acordeonInterno dt{ + background: #efefef; + color: #000; + border-bottom: 1px solid #cccccc; + border-top: 1px solid #cccccc; + cursor: pointer; +} +.acordeon dd +, .acordeonInterno dd +{line-height: 1.6em;margin-left:0 } + +.acordeon dt.activo, .acordeonInterno dt.activo, dt:hover{ background: #fff6d3; } +//.acordeon dt:before{ content: url("../css/right.png"); margin-right: 10px; } +//.acordeon dt.activo:before{ content: url("../css/down.png"); } + +.acordeon dt +, .acordeonInterno dt{ + background-image: url("img/right.png"); + background-position: 10px 5px; + background-repeat: no-repeat; + padding-left: 35px; +} +.acordeon dt.activo +, .acordeonInterno dt.activo{ + background-image: url("img/down.png"); + background-position: 14px 9px; + background-repeat: no-repeat; + padding-left: 35px; + +} +.acordeon input[type="submit"] { + position: absolute; + right: 5px; + top: 5px; + border-radius: 5px; + padding: 3px; +} +.tr_1 td, .tr_2 td, .tr_3 td, .tr_4 td, +.tr_5 td, .tr_6 td, .tr_7 td, .tr_8 td { + padding-right: 15px; +} diff --git b/acordeon/acordeon.js a/acordeon/acordeon.js new file mode 100644 index 0000000..75baafb --- /dev/null +++ a/acordeon/acordeon.js @@ -0,0 +1,26 @@ +$(function() { + $('dl.acordeonInterno > dd').not('dt.activo + dd').hide(); + $('dl.acordeonInterno > dt').click(function() { + if ($(this).hasClass('activo')) { + $(this).removeClass('activo'); + $(this).next().slideUp(); + } else { + $('dl.acordeonInterno dt').removeClass('activo'); + $(this).addClass('activo'); + $('dl.acordeonInterno dd').slideUp(); + $(this).next().slideDown(); + } + }); + $('dl.acordeon > dd').not('dt.activo + dd').hide(); + $('dl.acordeon > dt').click(function() { + if ($(this).hasClass('activo')) { + $(this).removeClass('activo'); + $(this).next().slideUp(); + } else { + $('dl dt').removeClass('activo'); + $(this).addClass('activo'); + $('dl dd').slideUp(); + $(this).next().slideDown(); + } + }); +}); diff --git b/acordeon/img/down.png a/acordeon/img/down.png new file mode 100644 index 0000000..a5f0d7a --- /dev/null +++ a/acordeon/img/down.png diff --git b/acordeon/img/right.png a/acordeon/img/right.png new file mode 100644 index 0000000..619819b --- /dev/null +++ a/acordeon/img/right.png diff --git b/config.xml a/config.xml new file mode 100644 index 0000000..8c6a298 --- /dev/null +++ a/config.xml @@ -0,0 +1,14 @@ + + + imaxstockavelonemptyingmodule + + + + + + 1 + 0 + 1 + 1 + + diff --git b/configuration.php a/configuration.php new file mode 100644 index 0000000..3cbc102 --- /dev/null +++ a/configuration.php @@ -0,0 +1,49 @@ +sufijo . 'MODO_SOLAPADO'] = 0; +$configuracion[$this->sufijo . 'PROCESO_ACTIVO'] = 0; +$configuracion[$this->sufijo . 'ULTIMO_PROCESO'] = ''; +$configuracion[$this->sufijo . 'TOKEN'] = md5(uniqid()); + +$configuracion[$this->sufijo . 'LICENCIA'] = ''; +$configuracion[$this->sufijo . 'F_SERVER'] = 'http://licencia.informax.es/gestionarLicencias.php'; +$configuracion[$this->sufijo . 'F'] = ''; +$configuracion[$this->sufijo . 'F_CHECK'] = 1000; + + +$hooks = array(); + + + +/* + CAMPOS NECESARIOS PARA LA PUBLICIDAD + */ +$configuracion[$this->sufijo . 'URL_TXT'] = 'http://publi.informax.es'; +$configuracion[$this->sufijo . 'TIPO'] = 'general'; +$configuracion[$this->sufijo . 'DESCARGA_ARCHIVO'] = 10001; +$configuracion[$this->sufijo . 'TXT_FILE'] = ''; + + +$configuracion[$this->sufijo . 'F'] = ''; +$configuracion[$this->sufijo . 'LICENCIA'] = ''; +$configuracion[$this->sufijo . 'F_CHECK'] = 1000; +$configuracion[$this->sufijo . 'F_SERVER'] = 'http://licencia.informax.es/gestionarLicencias.php'; + + + + +//ARCHIVO CONFIGURATION.PHP + +$moduleTabRoot= array(); +$moduleTabRoot['name'] = 'Informax'; +$moduleTabRoot['clase'] = 'AdminInformax'; +$moduleTabRoot['padre'] = ''; +$moduleTabRoot['imagen'] = 'informax.gif'; + +$moduleTabs = array(); +$moduleTabs[0] = array(); + +$moduleTabs[0]['name'] = 'Stock Avelon Emptying'; +$moduleTabs[0]['clase'] = 'AdminImaxStockAvelonEmptyingModule'; +$moduleTabs[0]['padre'] = 'AdminInformax'; +$moduleTabs[0]['imagen'] = 'informax.gif'; diff --git b/controllers/admin/Adminimaxstockavelonemptyingmodule.php a/controllers/admin/Adminimaxstockavelonemptyingmodule.php new file mode 100644 index 0000000..2f58ae1 --- /dev/null +++ a/controllers/admin/Adminimaxstockavelonemptyingmodule.php @@ -0,0 +1,16 @@ +id_employee)); + header('Location: index.php?configure=imaxstockavelonemptyingmodule&tab_module=administration&module_name=imaxstockavelonemptyingmodule&controller=AdminModules&token=' . $token); + exit; + + } + public function initContent() { + global $cookie; + $token = md5(pSQL(_COOKIE_KEY_ . 'AdminModules' . (int) Tab::getIdFromClassName('AdminModules') . (int) $cookie->id_employee)); + header('Location: index.php?configure=imaxstockavelonemptyingmodule&tab_module=administration&module_name=imaxstockavelonemptyingmodule&controller=AdminModules&token=' . $token); + exit; + } +} diff --git b/css/Thumbs.db a/css/Thumbs.db new file mode 100644 index 0000000..f8ea036 --- /dev/null +++ a/css/Thumbs.db diff --git b/css/clock.gif a/css/clock.gif new file mode 100644 index 0000000..d72702c --- /dev/null +++ a/css/clock.gif diff --git b/css/css.css a/css/css.css new file mode 100644 index 0000000..f307769 --- /dev/null +++ a/css/css.css @@ -0,0 +1,44 @@ +#imax_destinatarios { + text-align: center; + margin-bottom: 10px; +} + +#imax_destinatarios p { + font-weight: bold; + margin-top: 5px; +} + +.imax_destinatarios { + margin: auto; +} + +.imax_destinatarios th { + font-weight: bold; +} + +.imax_destinatarios td, .imax_destinatarios th { + padding: 3px; + border: 1px solid black; +} + +#imax_mensajeError { + color: red; +} + +.centrado { + text-align: center; +} + +@media print { + .noImprimir { + display: none; + } +} + +#order_sortable li, .table img { + cursor: pointer; +} + +.imaxHidden { + display: none; +} diff --git b/css/custom.css a/css/custom.css new file mode 100644 index 0000000..6582118 --- /dev/null +++ a/css/custom.css @@ -0,0 +1,7 @@ +/* + Document : custom + Created on : 12-ago-2013, 11:51:14 + Author : daniel + Description: + Para que el usuario modifique la apariencia a su gusto. +*/ diff --git b/css/down.png a/css/down.png new file mode 100644 index 0000000..a5f0d7a --- /dev/null +++ a/css/down.png diff --git b/css/espera.gif a/css/espera.gif new file mode 100644 index 0000000..f11dcaf --- /dev/null +++ a/css/espera.gif diff --git b/css/estadoLicencia_ausente.png a/css/estadoLicencia_ausente.png new file mode 100644 index 0000000..72271e9 --- /dev/null +++ a/css/estadoLicencia_ausente.png diff --git b/css/estadoLicencia_correcta.png a/css/estadoLicencia_correcta.png new file mode 100644 index 0000000..b06eb6a --- /dev/null +++ a/css/estadoLicencia_correcta.png diff --git b/css/estadoLicencia_incorrecta.png a/css/estadoLicencia_incorrecta.png new file mode 100644 index 0000000..18789a4 --- /dev/null +++ a/css/estadoLicencia_incorrecta.png diff --git b/css/index.php a/css/index.php new file mode 100644 index 0000000..d897a02 --- /dev/null +++ a/css/index.php @@ -0,0 +1,9 @@ + diff --git b/css/publi.css a/css/publi.css new file mode 100644 index 0000000..21bca18 --- /dev/null +++ a/css/publi.css @@ -0,0 +1,105 @@ +.module-header { + /*background-color: #8bc954; + border-bottom: 4px solid #71b238; + */ + background-color: #ff5757; + border-bottom: 4px solid #c43d3d; + box-shadow: 0 5px 0 rgba(0, 0, 0, 0.15); + height: 70px; + left: 0; + margin: 0 0 20px 0; + padding: 0; + width: 100%; + font-family:"Open Sans","Helvetica","Arial","sans-serif"; + color:#fff; +} +.module-title-container { + width:58%; + float:left; + margin-left:10px; +} +.module-header h2 { + margin-bottom:0; +} +.module-header h3 { + margin-top:0; +} +.module-toolbar { + float:right; + text-align: right; + width: 40%; +} + +.module-toolbar li { + float: right; + list-style: outside none none; + text-align: center; + width: 31%; +} +.module-nav li a { + text-decoration:none; + color:#fff; +} +.module-nav li a:hover { + text-decoration:none; + color:#EEE; +} + +.module-preheader { + width: 100%; +} + +.module-preheader img{ + width: 100%; +} + +.module-footer{ + margin-top: 30px; + width: 100%; +} +.module-footer img{ + width: 100%; +} +.module-footer-left{ + width: 63.2%; + float: left; +} +.module-footer-right{ + width: 36.65%; + float: left; + +} +.module-newsletter{ + /* background-color: #8bc954; */ + background-color: #ff5757; + border-top: 4px solid #c43d3d; + box-shadow: 0 -5px 0 rgba(0, 0, 0, 0.15); + color: #fff; + font-family: "Open Sans","Helvetica","Arial","sans-serif"; + height: 70px; + left: 0; + margin: 0 0 20px; + padding: 0; + width: 100%; +} + +.module-newsletter p { + padding-left: 10px !important; + color:#FFF; +} +.module-newsletter form p label { + color: #fff; + font-weight: normal; + text-shadow: none; +} +.module-newsletter ul li{ + list-style: none; +} + +.module-newsletter p.uppercase { + text-transform: uppercase; +} + +.nobootstrap { + min-width: 1024px !important; +} diff --git b/css/right.png a/css/right.png new file mode 100644 index 0000000..619819b --- /dev/null +++ a/css/right.png diff --git b/forms/forms.15.css a/forms/forms.15.css new file mode 100644 index 0000000..86fde24 --- /dev/null +++ a/forms/forms.15.css @@ -0,0 +1,9 @@ +.selectIdioma, .selectIdioma option { + padding-left: 20px; + background-position: 1px center; + background-repeat: no-repeat; +} + +.form-group { + margin-top: 5px; +} diff --git b/forms/forms.css a/forms/forms.css new file mode 100644 index 0000000..1391578 --- /dev/null +++ a/forms/forms.css @@ -0,0 +1,22 @@ +.selectIdioma, .selectIdioma option { + padding-left: 20px; + background-position: 1px center; + background-repeat: no-repeat; +} + +.imaxTable .imaxTableRow .form-group > label { + display: none; +} + +.imaxTable .imaxTableRow .form-group > div { + width: auto; +} + +.imaxTable input[name="imaxTableNewRow"], .imaxTable input[name="imaxTableDeleteRow"] { + padding: 3px 7px; + font-weight: bold; +} + +.imaxTable input[name="imaxTableNewRow"] { + font-size: 1.3em; +} diff --git b/forms/forms.js a/forms/forms.js new file mode 100644 index 0000000..6b0227c --- /dev/null +++ a/forms/forms.js @@ -0,0 +1,30 @@ +$(function () { + /** + * Aplica el evento de eliminacion de fila. + * @param {jQuery} elementos + */ + function aplicarEventoEliminarFilaObjectModel(elementos) { + elementos.click(function() { + $(this).parents('.imaxTableRow').remove(); + }); + } + + $('.selectIdioma').change(function () { + var idLang = $(this).val(); + $(this).css('background-image', 'url("' + rutaImagenes + idLang + '.jpg")'); + }); + $('.selectIdioma').change(); + + //Eliminar fila de la tabla de ObjectModel + aplicarEventoEliminarFilaObjectModel($('input[name="imaxTableDeleteRow"]')); + + //Eliminar fila de la tabla de ObjectModel + $('input[name="imaxTableNewRow"]').click(function() { + var objectModelName = $(this).data('object_model'); + var filaString = window['emptyRow_' + objectModelName]; + var uniq = Math.random().toString(36).substr(2, 9); + var fila = $(filaString.replace(/\[newRow\]/g, '[newRow_' + uniq + ']')); + aplicarEventoEliminarFilaObjectModel(fila.find('input[name="imaxTableDeleteRow"]')); + $(this).parents('.imaxTable').children('tbody').append(fila); + }); +}); diff --git b/forms/forms_1.css a/forms/forms_1.css new file mode 100644 index 0000000..582b59f --- /dev/null +++ a/forms/forms_1.css @@ -0,0 +1,5 @@ +.selectIdioma, .selectIdioma option { + padding-left: 20px; + background-position: 1px center; + background-repeat: no-repeat; +} diff --git b/forms/forms_1.js a/forms/forms_1.js new file mode 100644 index 0000000..d5eaaa2 --- /dev/null +++ a/forms/forms_1.js @@ -0,0 +1,7 @@ +$(function() { + $('.selectIdioma').change(function() { + var idLang = $(this).val(); + $(this).css('background-image', 'url("' + rutaImagenes + idLang + '.jpg")'); + }); + $('.selectIdioma').change(); +}) diff --git b/forms/jquery.fileupload-image.js a/forms/jquery.fileupload-image.js new file mode 100644 index 0000000..d8ccd3f --- /dev/null +++ a/forms/jquery.fileupload-image.js @@ -0,0 +1,294 @@ +/* + * jQuery File Upload Image Preview & Resize Plugin 1.3.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, DataView, Blob, Uint8Array */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'load-image-meta', + 'load-image-exif', + 'load-image-ios', + 'canvas-to-blob', + './jquery.fileupload-process' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadImageMetaData', + disableImageHead: '@', + disableExif: '@', + disableExifThumbnail: '@', + disableExifSub: '@', + disableExifGps: '@', + disabled: '@disableImageMetaDataLoad' + }, + { + action: 'loadImage', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + noRevoke: '@', + disabled: '@disableImageLoad' + }, + { + action: 'resizeImage', + // Use "image" as prefix for the "@" options: + prefix: 'image', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + disabled: '@disableImageResize' + }, + { + action: 'saveImage', + disabled: '@disableImageResize' + }, + { + action: 'saveImageMetaData', + disabled: '@disableImageMetaDataSave' + }, + { + action: 'resizeImage', + // Use "preview" as prefix for the "@" options: + prefix: 'preview', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + thumbnail: '@', + canvas: '@', + disabled: '@disableImagePreview' + }, + { + action: 'setImage', + name: '@imagePreviewName', + disabled: '@disableImagePreview' + } + ); + + // The File Upload Resize plugin extends the fileupload widget + // with image resize functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of images to load: + // matched against the file type: + loadImageFileTypes: /^image\/(gif|jpeg|png)$/, + // The maximum file size of images to load: + loadImageMaxFileSize: 10000000, // 10MB + // The maximum width of resized images: + imageMaxWidth: 1920, + // The maximum height of resized images: + imageMaxHeight: 1080, + // Defines the image orientation (1-8) or takes the orientation + // value from Exif data if set to true: + imageOrientation: false, + // Define if resized images should be cropped or only scaled: + imageCrop: false, + // Disable the resize image functionality by default: + disableImageResize: true, + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // Defines the preview orientation (1-8) or takes the orientation + // value from Exif data if set to true: + previewOrientation: true, + // Create the preview using the Exif data thumbnail: + previewThumbnail: true, + // Define if preview images should be cropped or only scaled: + previewCrop: false, + // Define if preview images should be resized as canvas elements: + previewCanvas: true + }, + + processActions: { + + // Loads the image given via data.files and data.index + // as img element, if the browser supports the File API. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadImage: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (($.type(options.maxFileSize) === 'number' && + file.size > options.maxFileSize) || + (options.fileTypes && + !options.fileTypes.test(file.type)) || + !loadImage( + file, + function (img) { + if (img.src) { + data.img = img; + } + dfd.resolveWith(that, [data]); + }, + options + )) { + return data; + } + return dfd.promise(); + }, + + // Resizes the image given as data.canvas or data.img + // and updates data.canvas or data.img with the resized image. + // Also stores the resized image as preview property. + // Accepts the options maxWidth, maxHeight, minWidth, + // minHeight, canvas and crop: + resizeImage: function (data, options) { + if (options.disabled || !(data.canvas || data.img)) { + return data; + } + options = $.extend({canvas: true}, options); + var that = this, + dfd = $.Deferred(), + img = (options.canvas && data.canvas) || data.img, + resolve = function (newImg) { + if (newImg && (newImg.width !== img.width || + newImg.height !== img.height)) { + data[newImg.getContext ? 'canvas' : 'img'] = newImg; + } + data.preview = newImg; + dfd.resolveWith(that, [data]); + }, + thumbnail; + if (data.exif) { + if (options.orientation === true) { + options.orientation = data.exif.get('Orientation'); + } + if (options.thumbnail) { + thumbnail = data.exif.get('Thumbnail'); + if (thumbnail) { + loadImage(thumbnail, resolve, options); + return dfd.promise(); + } + } + } + if (img) { + resolve(loadImage.scale(img, options)); + return dfd.promise(); + } + return data; + }, + + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + saveImage: function (data, options) { + if (!data.canvas || options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + name = file.name, + dfd = $.Deferred(), + callback = function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\..+$/, + '.' + blob.type.substr(6) + ); + } + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }; + // Use canvas.mozGetAsFile directly, to retain the filename, as + // Gecko doesn't support the filename option for FormData.append: + if (data.canvas.mozGetAsFile) { + callback(data.canvas.mozGetAsFile( + (/^image\/(jpeg|png)$/.test(file.type) && name) || + ((name && name.replace(/\..+$/, '')) || + 'blob') + '.png', + file.type + )); + } else if (data.canvas.toBlob) { + data.canvas.toBlob(callback, file.type); + } else { + return data; + } + return dfd.promise(); + }, + + loadImageMetaData: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + dfd = $.Deferred(); + loadImage.parseMetaData(data.files[data.index], function (result) { + $.extend(data, result); + dfd.resolveWith(that, [data]); + }, options); + return dfd.promise(); + }, + + saveImageMetaData: function (data, options) { + if (!(data.imageHead && data.canvas && + data.canvas.toBlob && !options.disabled)) { + return data; + } + var file = data.files[data.index], + blob = new Blob([ + data.imageHead, + // Resized images always have a head size of 20 bytes, + // including the JPEG marker and a minimal JFIF header: + this._blobSlice.call(file, 20) + ], {type: file.type}); + blob.name = file.name; + data.files[data.index] = blob; + return data; + }, + + // Sets the resized version of the image as a property of the + // file object, must be called after "saveImage": + setImage: function (data, options) { + if (data.preview && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.preview; + } + return data; + } + + } + + }); + +})); diff --git b/forms/jquery.fileupload-image_1.js a/forms/jquery.fileupload-image_1.js new file mode 100644 index 0000000..d8ccd3f --- /dev/null +++ a/forms/jquery.fileupload-image_1.js @@ -0,0 +1,294 @@ +/* + * jQuery File Upload Image Preview & Resize Plugin 1.3.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, DataView, Blob, Uint8Array */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'load-image-meta', + 'load-image-exif', + 'load-image-ios', + 'canvas-to-blob', + './jquery.fileupload-process' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadImageMetaData', + disableImageHead: '@', + disableExif: '@', + disableExifThumbnail: '@', + disableExifSub: '@', + disableExifGps: '@', + disabled: '@disableImageMetaDataLoad' + }, + { + action: 'loadImage', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + noRevoke: '@', + disabled: '@disableImageLoad' + }, + { + action: 'resizeImage', + // Use "image" as prefix for the "@" options: + prefix: 'image', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + disabled: '@disableImageResize' + }, + { + action: 'saveImage', + disabled: '@disableImageResize' + }, + { + action: 'saveImageMetaData', + disabled: '@disableImageMetaDataSave' + }, + { + action: 'resizeImage', + // Use "preview" as prefix for the "@" options: + prefix: 'preview', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + thumbnail: '@', + canvas: '@', + disabled: '@disableImagePreview' + }, + { + action: 'setImage', + name: '@imagePreviewName', + disabled: '@disableImagePreview' + } + ); + + // The File Upload Resize plugin extends the fileupload widget + // with image resize functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of images to load: + // matched against the file type: + loadImageFileTypes: /^image\/(gif|jpeg|png)$/, + // The maximum file size of images to load: + loadImageMaxFileSize: 10000000, // 10MB + // The maximum width of resized images: + imageMaxWidth: 1920, + // The maximum height of resized images: + imageMaxHeight: 1080, + // Defines the image orientation (1-8) or takes the orientation + // value from Exif data if set to true: + imageOrientation: false, + // Define if resized images should be cropped or only scaled: + imageCrop: false, + // Disable the resize image functionality by default: + disableImageResize: true, + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // Defines the preview orientation (1-8) or takes the orientation + // value from Exif data if set to true: + previewOrientation: true, + // Create the preview using the Exif data thumbnail: + previewThumbnail: true, + // Define if preview images should be cropped or only scaled: + previewCrop: false, + // Define if preview images should be resized as canvas elements: + previewCanvas: true + }, + + processActions: { + + // Loads the image given via data.files and data.index + // as img element, if the browser supports the File API. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadImage: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (($.type(options.maxFileSize) === 'number' && + file.size > options.maxFileSize) || + (options.fileTypes && + !options.fileTypes.test(file.type)) || + !loadImage( + file, + function (img) { + if (img.src) { + data.img = img; + } + dfd.resolveWith(that, [data]); + }, + options + )) { + return data; + } + return dfd.promise(); + }, + + // Resizes the image given as data.canvas or data.img + // and updates data.canvas or data.img with the resized image. + // Also stores the resized image as preview property. + // Accepts the options maxWidth, maxHeight, minWidth, + // minHeight, canvas and crop: + resizeImage: function (data, options) { + if (options.disabled || !(data.canvas || data.img)) { + return data; + } + options = $.extend({canvas: true}, options); + var that = this, + dfd = $.Deferred(), + img = (options.canvas && data.canvas) || data.img, + resolve = function (newImg) { + if (newImg && (newImg.width !== img.width || + newImg.height !== img.height)) { + data[newImg.getContext ? 'canvas' : 'img'] = newImg; + } + data.preview = newImg; + dfd.resolveWith(that, [data]); + }, + thumbnail; + if (data.exif) { + if (options.orientation === true) { + options.orientation = data.exif.get('Orientation'); + } + if (options.thumbnail) { + thumbnail = data.exif.get('Thumbnail'); + if (thumbnail) { + loadImage(thumbnail, resolve, options); + return dfd.promise(); + } + } + } + if (img) { + resolve(loadImage.scale(img, options)); + return dfd.promise(); + } + return data; + }, + + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + saveImage: function (data, options) { + if (!data.canvas || options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + name = file.name, + dfd = $.Deferred(), + callback = function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\..+$/, + '.' + blob.type.substr(6) + ); + } + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }; + // Use canvas.mozGetAsFile directly, to retain the filename, as + // Gecko doesn't support the filename option for FormData.append: + if (data.canvas.mozGetAsFile) { + callback(data.canvas.mozGetAsFile( + (/^image\/(jpeg|png)$/.test(file.type) && name) || + ((name && name.replace(/\..+$/, '')) || + 'blob') + '.png', + file.type + )); + } else if (data.canvas.toBlob) { + data.canvas.toBlob(callback, file.type); + } else { + return data; + } + return dfd.promise(); + }, + + loadImageMetaData: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + dfd = $.Deferred(); + loadImage.parseMetaData(data.files[data.index], function (result) { + $.extend(data, result); + dfd.resolveWith(that, [data]); + }, options); + return dfd.promise(); + }, + + saveImageMetaData: function (data, options) { + if (!(data.imageHead && data.canvas && + data.canvas.toBlob && !options.disabled)) { + return data; + } + var file = data.files[data.index], + blob = new Blob([ + data.imageHead, + // Resized images always have a head size of 20 bytes, + // including the JPEG marker and a minimal JFIF header: + this._blobSlice.call(file, 20) + ], {type: file.type}); + blob.name = file.name; + data.files[data.index] = blob; + return data; + }, + + // Sets the resized version of the image as a property of the + // file object, must be called after "saveImage": + setImage: function (data, options) { + if (data.preview && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.preview; + } + return data; + } + + } + + }); + +})); diff --git b/forms/jquery.fileupload-process.js a/forms/jquery.fileupload-process.js new file mode 100644 index 0000000..87042c3 --- /dev/null +++ a/forms/jquery.fileupload-process.js @@ -0,0 +1,164 @@ +/* + * jQuery File Upload Processing Plugin 1.2.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true */ +/*global define, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + var originalAdd = $.blueimp.fileupload.prototype.options.add; + + // The File Upload Processing plugin extends the fileupload widget + // with file processing functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The list of processing actions: + processQueue: [ + /* + { + action: 'log', + type: 'debug' + } + */ + ], + add: function (e, data) { + var $this = $(this); + data.process(function () { + return $this.fileupload('process', data); + }); + originalAdd.call(this, e, data); + } + }, + + processActions: { + /* + log: function (data, options) { + console[options.type]( + 'Processing "' + data.files[data.index].name + '"' + ); + } + */ + }, + + _processFile: function (data) { + var that = this, + dfd = $.Deferred().resolveWith(that, [data]), + chain = dfd.promise(); + this._trigger('process', null, data); + $.each(data.processQueue, function (i, settings) { + var func = function (data) { + return that.processActions[settings.action].call( + that, + data, + settings + ); + }; + chain = chain.pipe(func, settings.always && func); + }); + chain + .done(function () { + that._trigger('processdone', null, data); + that._trigger('processalways', null, data); + }) + .fail(function () { + that._trigger('processfail', null, data); + that._trigger('processalways', null, data); + }); + return chain; + }, + + // Replaces the settings of each processQueue item that + // are strings starting with an "@", using the remaining + // substring as key for the option map, + // e.g. "@autoUpload" is replaced with options.autoUpload: + _transformProcessQueue: function (options) { + var processQueue = []; + $.each(options.processQueue, function () { + var settings = {}, + action = this.action, + prefix = this.prefix === true ? action : this.prefix; + $.each(this, function (key, value) { + if ($.type(value) === 'string' && + value.charAt(0) === '@') { + settings[key] = options[ + value.slice(1) || (prefix ? prefix + + key.charAt(0).toUpperCase() + key.slice(1) : key) + ]; + } else { + settings[key] = value; + } + + }); + processQueue.push(settings); + }); + options.processQueue = processQueue; + }, + + // Returns the number of files currently in the processsing queue: + processing: function () { + return this._processing; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows to bind callbacks: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.processQueue && options.processQueue.length) { + this._transformProcessQueue(options); + if (this._processing === 0) { + this._trigger('processstart'); + } + $.each(data.files, function (index) { + var opts = index ? $.extend({}, options) : options, + func = function () { + return that._processFile(opts); + }; + opts.index = index; + that._processing += 1; + that._processingQueue = that._processingQueue.pipe(func, func) + .always(function () { + that._processing -= 1; + if (that._processing === 0) { + that._trigger('processstop'); + } + }); + }); + } + return this._processingQueue; + }, + + _create: function () { + this._super(); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); diff --git b/forms/jquery.fileupload-process_1.js a/forms/jquery.fileupload-process_1.js new file mode 100644 index 0000000..87042c3 --- /dev/null +++ a/forms/jquery.fileupload-process_1.js @@ -0,0 +1,164 @@ +/* + * jQuery File Upload Processing Plugin 1.2.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true */ +/*global define, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + var originalAdd = $.blueimp.fileupload.prototype.options.add; + + // The File Upload Processing plugin extends the fileupload widget + // with file processing functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The list of processing actions: + processQueue: [ + /* + { + action: 'log', + type: 'debug' + } + */ + ], + add: function (e, data) { + var $this = $(this); + data.process(function () { + return $this.fileupload('process', data); + }); + originalAdd.call(this, e, data); + } + }, + + processActions: { + /* + log: function (data, options) { + console[options.type]( + 'Processing "' + data.files[data.index].name + '"' + ); + } + */ + }, + + _processFile: function (data) { + var that = this, + dfd = $.Deferred().resolveWith(that, [data]), + chain = dfd.promise(); + this._trigger('process', null, data); + $.each(data.processQueue, function (i, settings) { + var func = function (data) { + return that.processActions[settings.action].call( + that, + data, + settings + ); + }; + chain = chain.pipe(func, settings.always && func); + }); + chain + .done(function () { + that._trigger('processdone', null, data); + that._trigger('processalways', null, data); + }) + .fail(function () { + that._trigger('processfail', null, data); + that._trigger('processalways', null, data); + }); + return chain; + }, + + // Replaces the settings of each processQueue item that + // are strings starting with an "@", using the remaining + // substring as key for the option map, + // e.g. "@autoUpload" is replaced with options.autoUpload: + _transformProcessQueue: function (options) { + var processQueue = []; + $.each(options.processQueue, function () { + var settings = {}, + action = this.action, + prefix = this.prefix === true ? action : this.prefix; + $.each(this, function (key, value) { + if ($.type(value) === 'string' && + value.charAt(0) === '@') { + settings[key] = options[ + value.slice(1) || (prefix ? prefix + + key.charAt(0).toUpperCase() + key.slice(1) : key) + ]; + } else { + settings[key] = value; + } + + }); + processQueue.push(settings); + }); + options.processQueue = processQueue; + }, + + // Returns the number of files currently in the processsing queue: + processing: function () { + return this._processing; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows to bind callbacks: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.processQueue && options.processQueue.length) { + this._transformProcessQueue(options); + if (this._processing === 0) { + this._trigger('processstart'); + } + $.each(data.files, function (index) { + var opts = index ? $.extend({}, options) : options, + func = function () { + return that._processFile(opts); + }; + opts.index = index; + that._processing += 1; + that._processingQueue = that._processingQueue.pipe(func, func) + .always(function () { + that._processing -= 1; + if (that._processing === 0) { + that._trigger('processstop'); + } + }); + }); + } + return this._processingQueue; + }, + + _create: function () { + this._super(); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); diff --git b/forms/jquery.fileupload-validate.js a/forms/jquery.fileupload-validate.js new file mode 100644 index 0000000..ee1c2f2 --- /dev/null +++ a/forms/jquery.fileupload-validate.js @@ -0,0 +1,117 @@ +/* + * jQuery File Upload Validation Plugin 1.1.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload-process' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + // Append to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.push( + { + action: 'validate', + // Always trigger this action, + // even if the previous action was rejected: + always: true, + // Options taken from the global options map: + acceptFileTypes: '@', + maxFileSize: '@', + minFileSize: '@', + maxNumberOfFiles: '@', + disabled: '@disableValidation' + } + ); + + // The File Upload Validation plugin extends the fileupload widget + // with file validation functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + /* + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + // The maximum allowed file size in bytes: + maxFileSize: 10000000, // 10 MB + // The minimum allowed file size in bytes: + minFileSize: undefined, // No minimal file size + // The limit of files to be uploaded: + maxNumberOfFiles: 10, + */ + + // Function returning the current number of files, + // has to be overriden for maxNumberOfFiles validation: + getNumberOfFiles: $.noop, + + // Error and info messages: + messages: { + maxNumberOfFiles: 'Maximum number of files exceeded', + acceptFileTypes: 'File type not allowed', + maxFileSize: 'File is too large', + minFileSize: 'File is too small' + } + }, + + processActions: { + + validate: function (data, options) { + if (options.disabled) { + return data; + } + var dfd = $.Deferred(), + settings = this.options, + file = data.files[data.index]; + if ($.type(options.maxNumberOfFiles) === 'number' && + (settings.getNumberOfFiles() || 0) + data.files.length > + options.maxNumberOfFiles) { + file.error = settings.i18n('maxNumberOfFiles'); + } else if (options.acceptFileTypes && + !(options.acceptFileTypes.test(file.type) || + options.acceptFileTypes.test(file.name))) { + file.error = settings.i18n('acceptFileTypes'); + } else if (options.maxFileSize && file.size > + options.maxFileSize) { + file.error = settings.i18n('maxFileSize'); + } else if ($.type(file.size) === 'number' && + file.size < options.minFileSize) { + file.error = settings.i18n('minFileSize'); + } else { + delete file.error; + } + if (file.error || data.files.error) { + data.files.error = true; + dfd.rejectWith(this, [data]); + } else { + dfd.resolveWith(this, [data]); + } + return dfd.promise(); + } + + } + + }); + +})); diff --git b/forms/jquery.fileupload-validate_1.js a/forms/jquery.fileupload-validate_1.js new file mode 100644 index 0000000..ee1c2f2 --- /dev/null +++ a/forms/jquery.fileupload-validate_1.js @@ -0,0 +1,117 @@ +/* + * jQuery File Upload Validation Plugin 1.1.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload-process' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + // Append to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.push( + { + action: 'validate', + // Always trigger this action, + // even if the previous action was rejected: + always: true, + // Options taken from the global options map: + acceptFileTypes: '@', + maxFileSize: '@', + minFileSize: '@', + maxNumberOfFiles: '@', + disabled: '@disableValidation' + } + ); + + // The File Upload Validation plugin extends the fileupload widget + // with file validation functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + /* + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + // The maximum allowed file size in bytes: + maxFileSize: 10000000, // 10 MB + // The minimum allowed file size in bytes: + minFileSize: undefined, // No minimal file size + // The limit of files to be uploaded: + maxNumberOfFiles: 10, + */ + + // Function returning the current number of files, + // has to be overriden for maxNumberOfFiles validation: + getNumberOfFiles: $.noop, + + // Error and info messages: + messages: { + maxNumberOfFiles: 'Maximum number of files exceeded', + acceptFileTypes: 'File type not allowed', + maxFileSize: 'File is too large', + minFileSize: 'File is too small' + } + }, + + processActions: { + + validate: function (data, options) { + if (options.disabled) { + return data; + } + var dfd = $.Deferred(), + settings = this.options, + file = data.files[data.index]; + if ($.type(options.maxNumberOfFiles) === 'number' && + (settings.getNumberOfFiles() || 0) + data.files.length > + options.maxNumberOfFiles) { + file.error = settings.i18n('maxNumberOfFiles'); + } else if (options.acceptFileTypes && + !(options.acceptFileTypes.test(file.type) || + options.acceptFileTypes.test(file.name))) { + file.error = settings.i18n('acceptFileTypes'); + } else if (options.maxFileSize && file.size > + options.maxFileSize) { + file.error = settings.i18n('maxFileSize'); + } else if ($.type(file.size) === 'number' && + file.size < options.minFileSize) { + file.error = settings.i18n('minFileSize'); + } else { + delete file.error; + } + if (file.error || data.files.error) { + data.files.error = true; + dfd.rejectWith(this, [data]); + } else { + dfd.resolveWith(this, [data]); + } + return dfd.promise(); + } + + } + + }); + +})); diff --git b/forms/jquery.fileupload.js a/forms/jquery.fileupload.js new file mode 100644 index 0000000..a39958a --- /dev/null +++ a/forms/jquery.fileupload.js @@ -0,0 +1,1368 @@ +/* + * jQuery File Upload Plugin 5.34.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, location, File, Blob, FormData */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Detect file input support, based on + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/ + $.support.fileInput = !(new RegExp( + // Handle devices which give false positives for the feature detection: + '(Android (1\\.[0156]|2\\.[01]))' + + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + + '|(w(eb)?OSBrowser)|(webOS)' + + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' + ).test(window.navigator.userAgent) || + // Feature detection for all other devices: + $('').prop('disabled')); + + // The FileReader API is not actually used, but works as feature detection, + // as e.g. Safari supports XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads: + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // Detect support for Blob slicing (required for chunked uploads): + $.support.blobSlice = window.Blob && (Blob.prototype.slice || + Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: + dropZone: $(document), + // The paste target element(s), by the default the complete document. + // Set to null to disable paste support: + pasteZone: $(document), + // The file input field(s), that are listened to for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + // By default, uploads are started automatically when adding files: + autoUpload: true, + + // Error and info messages: + messages: { + uploadedBytes: 'Uploaded bytes exceed file size' + }, + + // Translation function, gets the message key to be translated + // and an object with context specific data as arguments: + i18n: function (message, context) { + message = this.messages[message] || message.toString(); + if (context) { + $.each(context, function (key, value) { + message = message.replace('{' + key + '}', value); + }); + } + return message; + }, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows you to override plugin options as well as define ajax settings. + // + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + if (data.autoUpload || (data.autoUpload !== false && + $(this).fileupload('option', 'autoUpload'))) { + data.process().done(function () { + data.submit(); + }); + } + }, + + // Other callbacks: + + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + + // Callback for change events of the fileInput(s): + // change: function (e, data) {}, // .bind('fileuploadchange', func); + + // Callback for paste events to the pasteZone(s): + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + + // Callback for drop events of the dropZone(s): + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + + // Callback for dragover events of the dropZone(s): + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // Callback for the start of each chunk upload request: + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); + + // Callback for successful chunk uploads: + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); + + // Callback for failed (abort or error) chunk uploads: + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); + + // Callback for completed (success, abort or error) chunk upload requests: + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false + }, + + // A list of options that require reinitializing event listeners and/or + // special initialization code: + _specialOptions: [ + 'fileInput', + 'dropZone', + 'pasteZone', + 'multipart', + 'forceIframeTransport' + ], + + _blobSlice: $.support.blobSlice && function () { + var slice = this.slice || this.webkitSlice || this.mozSlice; + return slice.apply(this, arguments); + }, + + _BitrateTimer: function () { + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if (typeof options.formData === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if ($.type(options.formData) === 'object') { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _initProgressObject: function (obj) { + var progress = { + loaded: 0, + total: 0, + bitrate: 0 + }; + if (obj._progress) { + $.extend(obj._progress, progress); + } else { + obj._progress = progress; + } + }, + + _initResponseObject: function (obj) { + var prop; + if (obj._response) { + for (prop in obj._response) { + if (obj._response.hasOwnProperty(prop)) { + delete obj._response[prop]; + } + } + } else { + obj._response = {}; + } + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = ((Date.now) ? Date.now() : (new Date()).getTime()), + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + loaded = Math.floor( + e.loaded / e.total * (data.chunkSize || data._progress.total) + ) + (data.uploadedBytes || 0); + // Add the difference from the previously loaded state + // to the global loaded counter: + this._progress.loaded += (loaded - data._progress.loaded); + this._progress.bitrate = this._bitrateTimer.getBitrate( + now, + this._progress.loaded, + data.bitrateInterval + ); + data._progress.loaded = data.loaded = loaded; + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger( + 'progress', + $.Event('progress', {delegatedEvent: e}), + data + ); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger( + 'progressall', + $.Event('progressall', {delegatedEvent: e}), + this._progress + ); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _isInstanceOf: function (type, obj) { + // Cross-frame instanceof check + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }, + + _initXHRData: function (options) { + var that = this, + formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = options.paramName[0]; + options.headers = $.extend({}, options.headers); + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; + } + if (!multipart || options.blob || !this._isInstanceOf('File', file)) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; + } + if (!multipart) { + options.contentType = file.type; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: options.paramName[index] || paramName, + value: file + }); + }); + } + } else { + if (that._isInstanceOf('FormData', options.formData)) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // This check allows the tests to run with + // dummy objects: + if (that._isInstanceOf('File', file) || + that._isInstanceOf('Blob', file)) { + formData.append( + options.paramName[index] || paramName, + file, + file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + var targetHost = $('').prop('href', options.url).prop('host'); + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && targetHost && targetHost !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || + ($.type(options.form.prop('method')) === 'string' && + options.form.prop('method')) || '' + ).toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { + options.type = 'POST'; + } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + options.url = options.url+'&rand=' + new Date().getTime(); + return options; + }, + + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected() and .isResolved(): + _getDeferredState: function (deferred) { + if (deferred.state) { + return deferred.state(); + } + if (deferred.isResolved()) { + return 'resolved'; + } + if (deferred.isRejected()) { + return 'rejected'; + } + return 'pending'; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Adds convenience methods to the data callback argument: + _addConvenienceMethods: function (e, data) { + var that = this, + getPromise = function (data) { + return $.Deferred().resolveWith(that, [data]).promise(); + }; + data.process = function (resolveFunc, rejectFunc) { + if (resolveFunc || rejectFunc) { + data._processQueue = this._processQueue = + (this._processQueue || getPromise(this)) + .pipe(resolveFunc, rejectFunc); + } + return this._processQueue || getPromise(this); + }; + data.submit = function () { + if (this.state() !== 'pending') { + data.jqXHR = this.jqXHR = + (that._trigger( + 'submit', + $.Event('submit', {delegatedEvent: e}), + this + ) !== false) && that._onSend(e, this); + } + return this.jqXHR || that._getXHRPromise(); + }; + data.abort = function () { + if (this.jqXHR) { + return this.jqXHR.abort(); + } + return that._getXHRPromise(); + }; + data.state = function () { + if (this.jqXHR) { + return that._getDeferredState(this.jqXHR); + } + if (this._processQueue) { + return that._getDeferredState(this._processQueue); + } + }; + data.progress = function () { + return this._progress; + }; + data.response = function () { + return this._response; + }; + }, + + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + options.uploadedBytes = options.uploadedBytes || 0; + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes, + mcs = options.maxChunkSize || fs, + slice = this._blobSlice, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + upload; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = options.i18n('uploadedBytes'); + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // The chunk upload method: + upload = function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options), + currentLoaded = o._progress.loaded; + o.blob = slice.call( + file, + ub, + ub + mcs, + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || + that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if no final progress event + // with loaded equaling total has been triggered + // for this chunk: + if (currentLoaded + o.chunkSize - o._progress.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + o.result = result; + o.textStatus = textStatus; + o.jqXHR = jqXHR; + that._trigger('chunkdone', null, o); + that._trigger('chunkalways', null, o); + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + o.jqXHR = jqXHR; + o.textStatus = textStatus; + o.errorThrown = errorThrown; + that._trigger('chunkfail', null, o); + that._trigger('chunkalways', null, o); + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }); + }; + this._enhancePromise(promise); + promise.abort = function () { + return jqXHR.abort(); + }; + upload(); + return promise; + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + // Reset the global progress values: + this._progress.loaded = this._progress.total = 0; + this._progress.bitrate = 0; + } + // Make sure the container objects for the .response() and + // .progress() methods on the data object are available + // and reset to their initial state: + this._initResponseObject(data); + this._initProgressObject(data); + data._progress.loaded = data.loaded = data.uploadedBytes || 0; + data._progress.total = data.total = this._getTotal(data.files) || 1; + data._progress.bitrate = data.bitrate = 0; + this._active += 1; + // Initialize the global progress values: + this._progress.loaded += data.loaded; + this._progress.total += data.total; + }, + + _onDone: function (result, textStatus, jqXHR, options) { + var total = options._progress.total, + response = options._response; + if (options._progress.loaded < total) { + // Create a progress event if no final progress event + // with loaded equaling total has been triggered: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: total, + total: total + }), options); + } + response.result = options.result = result; + response.textStatus = options.textStatus = textStatus; + response.jqXHR = options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + var response = options._response; + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._progress.loaded -= options._progress.loaded; + this._progress.total -= options._progress.total; + } + response.jqXHR = options.jqXHR = jqXHR; + response.textStatus = options.textStatus = textStatus; + response.errorThrown = options.errorThrown = errorThrown; + this._trigger('fail', null, options); + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + // jqXHRorResult, textStatus and jqXHRorError are added to the + // options object via done and fail callbacks + this._trigger('always', null, options); + }, + + _onSend: function (e, data) { + if (!data.submit) { + this._addConvenienceMethods(e, data); + } + var that = this, + jqXHR, + aborted, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function () { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + ((aborted || that._trigger( + 'send', + $.Event('send', {delegatedEvent: e}), + options + ) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + that._sending -= 1; + that._active -= 1; + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (that._getDeferredState(nextSlot) === 'pending') { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + if (that._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + that._trigger('stop'); + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + this._sequence = this._sequence.pipe(send, send); + pipe = this._sequence; + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + aborted = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(options.context, aborted); + } + return send(); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + limit = options.limitMultiFileUploads, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i; + if (!(options.singleFileUploads || limit) || + !this._isXHRUpload(options)) { + fileSet = [data.files]; + paramNameSet = [paramName]; + } else if (!options.singleFileUploads && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < data.files.length; i += limit) { + fileSet.push(data.files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else { + paramNameSet = paramName; + } + data.originalFiles = data.files; + $.each(fileSet || data.files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + that._initResponseObject(newData); + that._initProgressObject(newData); + that._addConvenienceMethods(e, newData); + result = that._trigger( + 'add', + $.Event('add', {delegatedEvent: e}), + newData + ); + return result; + }); + return result; + }, + + _replaceFileInput: function (input) { + var inputClone = input.clone(true); + $('
').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // elements set with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + dirReader; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + dirReader.readEntries(function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, errorHandler); + } else { + // Return an empy list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); + } + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); + } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger( + 'change', + $.Event('change', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + }, + + _onPaste: function (e) { + var items = e.originalEvent && e.originalEvent.clipboardData && + e.originalEvent.clipboardData.items, + data = {files: []}; + if (items && items.length) { + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (this._trigger( + 'paste', + $.Event('paste', {delegatedEvent: e}), + data + ) !== false) { + this._onAdd(e, data); + } + } + }, + + _onDrop: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var that = this, + dataTransfer = e.dataTransfer, + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger( + 'drop', + $.Event('drop', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + } + }, + + _onDragOver: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && + this._trigger( + 'dragover', + $.Event('dragover', {delegatedEvent: e}) + ) !== false) { + e.preventDefault(); + dataTransfer.dropEffect = 'copy'; + } + }, + + _initEventHandlers: function () { + if (this._isXHRUpload(this.options)) { + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); + } + if ($.support.fileInput) { + this._on(this.options.fileInput, { + change: this._onChange + }); + } + }, + + _destroyEventHandlers: function () { + this._off(this.options.dropZone, 'dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); + }, + + _setOption: function (key, value) { + var reinit = $.inArray(key, this._specialOptions) !== -1; + if (reinit) { + this._destroyEventHandlers(); + } + this._super(key, value); + if (reinit) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } + }, + + _getRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _isRegExpOption: function (key, value) { + return key !== 'url' && $.type(value) === 'string' && + /^\/.*\/[igm]{0,3}$/.test(value); + }, + + _initDataAttributes: function () { + var that = this, + options = this.options; + // Initialize options set via HTML5 data-attributes: + $.each( + $(this.element[0].cloneNode(false)).data(), + function (key, value) { + if (that._isRegExpOption(key, value)) { + value = that._getRegExp(value); + } + options[key] = value; + } + ); + }, + + _create: function () { + this._initDataAttributes(); + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = 0; + this._initProgressObject(this); + this._initEventHandlers(); + }, + + // This method is exposed to the widget API and allows to query + // the number of active uploads: + active: function () { + return this._active; + }, + + // This method is exposed to the widget API and allows to query + // the widget upload progress. + // It returns an object with loaded, total and bitrate properties + // for the running uploads: + progress: function () { + return this._progress; + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + var that = this; + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files or fileInput property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + if (!files.length) { + dfd.reject(); + return; + } + data.files = files; + jqXHR = that._onSend(null, data).then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff --git b/forms/jquery.fileupload_1.js a/forms/jquery.fileupload_1.js new file mode 100644 index 0000000..a39958a --- /dev/null +++ a/forms/jquery.fileupload_1.js @@ -0,0 +1,1368 @@ +/* + * jQuery File Upload Plugin 5.34.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, location, File, Blob, FormData */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Detect file input support, based on + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/ + $.support.fileInput = !(new RegExp( + // Handle devices which give false positives for the feature detection: + '(Android (1\\.[0156]|2\\.[01]))' + + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + + '|(w(eb)?OSBrowser)|(webOS)' + + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' + ).test(window.navigator.userAgent) || + // Feature detection for all other devices: + $('').prop('disabled')); + + // The FileReader API is not actually used, but works as feature detection, + // as e.g. Safari supports XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads: + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // Detect support for Blob slicing (required for chunked uploads): + $.support.blobSlice = window.Blob && (Blob.prototype.slice || + Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: + dropZone: $(document), + // The paste target element(s), by the default the complete document. + // Set to null to disable paste support: + pasteZone: $(document), + // The file input field(s), that are listened to for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + // By default, uploads are started automatically when adding files: + autoUpload: true, + + // Error and info messages: + messages: { + uploadedBytes: 'Uploaded bytes exceed file size' + }, + + // Translation function, gets the message key to be translated + // and an object with context specific data as arguments: + i18n: function (message, context) { + message = this.messages[message] || message.toString(); + if (context) { + $.each(context, function (key, value) { + message = message.replace('{' + key + '}', value); + }); + } + return message; + }, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows you to override plugin options as well as define ajax settings. + // + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + if (data.autoUpload || (data.autoUpload !== false && + $(this).fileupload('option', 'autoUpload'))) { + data.process().done(function () { + data.submit(); + }); + } + }, + + // Other callbacks: + + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + + // Callback for change events of the fileInput(s): + // change: function (e, data) {}, // .bind('fileuploadchange', func); + + // Callback for paste events to the pasteZone(s): + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + + // Callback for drop events of the dropZone(s): + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + + // Callback for dragover events of the dropZone(s): + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // Callback for the start of each chunk upload request: + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); + + // Callback for successful chunk uploads: + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); + + // Callback for failed (abort or error) chunk uploads: + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); + + // Callback for completed (success, abort or error) chunk upload requests: + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false + }, + + // A list of options that require reinitializing event listeners and/or + // special initialization code: + _specialOptions: [ + 'fileInput', + 'dropZone', + 'pasteZone', + 'multipart', + 'forceIframeTransport' + ], + + _blobSlice: $.support.blobSlice && function () { + var slice = this.slice || this.webkitSlice || this.mozSlice; + return slice.apply(this, arguments); + }, + + _BitrateTimer: function () { + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if (typeof options.formData === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if ($.type(options.formData) === 'object') { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _initProgressObject: function (obj) { + var progress = { + loaded: 0, + total: 0, + bitrate: 0 + }; + if (obj._progress) { + $.extend(obj._progress, progress); + } else { + obj._progress = progress; + } + }, + + _initResponseObject: function (obj) { + var prop; + if (obj._response) { + for (prop in obj._response) { + if (obj._response.hasOwnProperty(prop)) { + delete obj._response[prop]; + } + } + } else { + obj._response = {}; + } + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = ((Date.now) ? Date.now() : (new Date()).getTime()), + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + loaded = Math.floor( + e.loaded / e.total * (data.chunkSize || data._progress.total) + ) + (data.uploadedBytes || 0); + // Add the difference from the previously loaded state + // to the global loaded counter: + this._progress.loaded += (loaded - data._progress.loaded); + this._progress.bitrate = this._bitrateTimer.getBitrate( + now, + this._progress.loaded, + data.bitrateInterval + ); + data._progress.loaded = data.loaded = loaded; + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger( + 'progress', + $.Event('progress', {delegatedEvent: e}), + data + ); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger( + 'progressall', + $.Event('progressall', {delegatedEvent: e}), + this._progress + ); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _isInstanceOf: function (type, obj) { + // Cross-frame instanceof check + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }, + + _initXHRData: function (options) { + var that = this, + formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = options.paramName[0]; + options.headers = $.extend({}, options.headers); + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; + } + if (!multipart || options.blob || !this._isInstanceOf('File', file)) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; + } + if (!multipart) { + options.contentType = file.type; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: options.paramName[index] || paramName, + value: file + }); + }); + } + } else { + if (that._isInstanceOf('FormData', options.formData)) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // This check allows the tests to run with + // dummy objects: + if (that._isInstanceOf('File', file) || + that._isInstanceOf('Blob', file)) { + formData.append( + options.paramName[index] || paramName, + file, + file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + var targetHost = $('').prop('href', options.url).prop('host'); + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && targetHost && targetHost !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || + ($.type(options.form.prop('method')) === 'string' && + options.form.prop('method')) || '' + ).toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { + options.type = 'POST'; + } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + options.url = options.url+'&rand=' + new Date().getTime(); + return options; + }, + + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected() and .isResolved(): + _getDeferredState: function (deferred) { + if (deferred.state) { + return deferred.state(); + } + if (deferred.isResolved()) { + return 'resolved'; + } + if (deferred.isRejected()) { + return 'rejected'; + } + return 'pending'; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Adds convenience methods to the data callback argument: + _addConvenienceMethods: function (e, data) { + var that = this, + getPromise = function (data) { + return $.Deferred().resolveWith(that, [data]).promise(); + }; + data.process = function (resolveFunc, rejectFunc) { + if (resolveFunc || rejectFunc) { + data._processQueue = this._processQueue = + (this._processQueue || getPromise(this)) + .pipe(resolveFunc, rejectFunc); + } + return this._processQueue || getPromise(this); + }; + data.submit = function () { + if (this.state() !== 'pending') { + data.jqXHR = this.jqXHR = + (that._trigger( + 'submit', + $.Event('submit', {delegatedEvent: e}), + this + ) !== false) && that._onSend(e, this); + } + return this.jqXHR || that._getXHRPromise(); + }; + data.abort = function () { + if (this.jqXHR) { + return this.jqXHR.abort(); + } + return that._getXHRPromise(); + }; + data.state = function () { + if (this.jqXHR) { + return that._getDeferredState(this.jqXHR); + } + if (this._processQueue) { + return that._getDeferredState(this._processQueue); + } + }; + data.progress = function () { + return this._progress; + }; + data.response = function () { + return this._response; + }; + }, + + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + options.uploadedBytes = options.uploadedBytes || 0; + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes, + mcs = options.maxChunkSize || fs, + slice = this._blobSlice, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + upload; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = options.i18n('uploadedBytes'); + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // The chunk upload method: + upload = function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options), + currentLoaded = o._progress.loaded; + o.blob = slice.call( + file, + ub, + ub + mcs, + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || + that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if no final progress event + // with loaded equaling total has been triggered + // for this chunk: + if (currentLoaded + o.chunkSize - o._progress.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + o.result = result; + o.textStatus = textStatus; + o.jqXHR = jqXHR; + that._trigger('chunkdone', null, o); + that._trigger('chunkalways', null, o); + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + o.jqXHR = jqXHR; + o.textStatus = textStatus; + o.errorThrown = errorThrown; + that._trigger('chunkfail', null, o); + that._trigger('chunkalways', null, o); + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }); + }; + this._enhancePromise(promise); + promise.abort = function () { + return jqXHR.abort(); + }; + upload(); + return promise; + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + // Reset the global progress values: + this._progress.loaded = this._progress.total = 0; + this._progress.bitrate = 0; + } + // Make sure the container objects for the .response() and + // .progress() methods on the data object are available + // and reset to their initial state: + this._initResponseObject(data); + this._initProgressObject(data); + data._progress.loaded = data.loaded = data.uploadedBytes || 0; + data._progress.total = data.total = this._getTotal(data.files) || 1; + data._progress.bitrate = data.bitrate = 0; + this._active += 1; + // Initialize the global progress values: + this._progress.loaded += data.loaded; + this._progress.total += data.total; + }, + + _onDone: function (result, textStatus, jqXHR, options) { + var total = options._progress.total, + response = options._response; + if (options._progress.loaded < total) { + // Create a progress event if no final progress event + // with loaded equaling total has been triggered: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: total, + total: total + }), options); + } + response.result = options.result = result; + response.textStatus = options.textStatus = textStatus; + response.jqXHR = options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + var response = options._response; + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._progress.loaded -= options._progress.loaded; + this._progress.total -= options._progress.total; + } + response.jqXHR = options.jqXHR = jqXHR; + response.textStatus = options.textStatus = textStatus; + response.errorThrown = options.errorThrown = errorThrown; + this._trigger('fail', null, options); + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + // jqXHRorResult, textStatus and jqXHRorError are added to the + // options object via done and fail callbacks + this._trigger('always', null, options); + }, + + _onSend: function (e, data) { + if (!data.submit) { + this._addConvenienceMethods(e, data); + } + var that = this, + jqXHR, + aborted, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function () { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + ((aborted || that._trigger( + 'send', + $.Event('send', {delegatedEvent: e}), + options + ) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + that._sending -= 1; + that._active -= 1; + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (that._getDeferredState(nextSlot) === 'pending') { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + if (that._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + that._trigger('stop'); + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + this._sequence = this._sequence.pipe(send, send); + pipe = this._sequence; + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + aborted = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(options.context, aborted); + } + return send(); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + limit = options.limitMultiFileUploads, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i; + if (!(options.singleFileUploads || limit) || + !this._isXHRUpload(options)) { + fileSet = [data.files]; + paramNameSet = [paramName]; + } else if (!options.singleFileUploads && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < data.files.length; i += limit) { + fileSet.push(data.files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else { + paramNameSet = paramName; + } + data.originalFiles = data.files; + $.each(fileSet || data.files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + that._initResponseObject(newData); + that._initProgressObject(newData); + that._addConvenienceMethods(e, newData); + result = that._trigger( + 'add', + $.Event('add', {delegatedEvent: e}), + newData + ); + return result; + }); + return result; + }, + + _replaceFileInput: function (input) { + var inputClone = input.clone(true); + $('
').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // elements set with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + dirReader; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + dirReader.readEntries(function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, errorHandler); + } else { + // Return an empy list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); + } + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); + } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger( + 'change', + $.Event('change', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + }, + + _onPaste: function (e) { + var items = e.originalEvent && e.originalEvent.clipboardData && + e.originalEvent.clipboardData.items, + data = {files: []}; + if (items && items.length) { + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (this._trigger( + 'paste', + $.Event('paste', {delegatedEvent: e}), + data + ) !== false) { + this._onAdd(e, data); + } + } + }, + + _onDrop: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var that = this, + dataTransfer = e.dataTransfer, + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger( + 'drop', + $.Event('drop', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + } + }, + + _onDragOver: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && + this._trigger( + 'dragover', + $.Event('dragover', {delegatedEvent: e}) + ) !== false) { + e.preventDefault(); + dataTransfer.dropEffect = 'copy'; + } + }, + + _initEventHandlers: function () { + if (this._isXHRUpload(this.options)) { + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); + } + if ($.support.fileInput) { + this._on(this.options.fileInput, { + change: this._onChange + }); + } + }, + + _destroyEventHandlers: function () { + this._off(this.options.dropZone, 'dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); + }, + + _setOption: function (key, value) { + var reinit = $.inArray(key, this._specialOptions) !== -1; + if (reinit) { + this._destroyEventHandlers(); + } + this._super(key, value); + if (reinit) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } + }, + + _getRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _isRegExpOption: function (key, value) { + return key !== 'url' && $.type(value) === 'string' && + /^\/.*\/[igm]{0,3}$/.test(value); + }, + + _initDataAttributes: function () { + var that = this, + options = this.options; + // Initialize options set via HTML5 data-attributes: + $.each( + $(this.element[0].cloneNode(false)).data(), + function (key, value) { + if (that._isRegExpOption(key, value)) { + value = that._getRegExp(value); + } + options[key] = value; + } + ); + }, + + _create: function () { + this._initDataAttributes(); + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = 0; + this._initProgressObject(this); + this._initEventHandlers(); + }, + + // This method is exposed to the widget API and allows to query + // the number of active uploads: + active: function () { + return this._active; + }, + + // This method is exposed to the widget API and allows to query + // the widget upload progress. + // It returns an object with loaded, total and bitrate properties + // for the running uploads: + progress: function () { + return this._progress; + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + var that = this; + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files or fileInput property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + if (!files.length) { + dfd.reject(); + return; + } + data.files = files; + jqXHR = that._onSend(null, data).then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff --git b/forms/jquery.iframe-transport.js a/forms/jquery.iframe-transport.js new file mode 100644 index 0000000..33e2795 --- /dev/null +++ a/forms/jquery.iframe-transport.js @@ -0,0 +1,208 @@ +/* + * jQuery Iframe Transport Plugin 1.8.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts four additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + // options.initialIframeSrc: the URL of the initial iframe src, + // by default set to "javascript:false;" + $.ajaxTransport('iframe', function (options) { + if (options.async) { + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6: + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', + form, + iframe, + addParamChar; + return { + send: function (_, completeCallback) { + form = $('
'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + counter += 1; + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + $(input).prop('name', clone.prop('name')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', initialIframeSrc); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return iframe && $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return iframe && $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); + }, + 'iframe script': function (iframe) { + return iframe && $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff --git b/forms/jquery.iframe-transport_1.js a/forms/jquery.iframe-transport_1.js new file mode 100644 index 0000000..33e2795 --- /dev/null +++ a/forms/jquery.iframe-transport_1.js @@ -0,0 +1,208 @@ +/* + * jQuery Iframe Transport Plugin 1.8.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts four additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + // options.initialIframeSrc: the URL of the initial iframe src, + // by default set to "javascript:false;" + $.ajaxTransport('iframe', function (options) { + if (options.async) { + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6: + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', + form, + iframe, + addParamChar; + return { + send: function (_, completeCallback) { + form = $('
'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + counter += 1; + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + $(input).prop('name', clone.prop('name')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', initialIframeSrc); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return iframe && $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return iframe && $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); + }, + 'iframe script': function (iframe) { + return iframe && $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff --git b/forms/jquery.tagify.js a/forms/jquery.tagify.js new file mode 100644 index 0000000..a758fea --- /dev/null +++ a/forms/jquery.tagify.js @@ -0,0 +1,139 @@ +/* Author: Alicia Liu */ + +(function ($) { + + $.widget("ui.tagify", { + options: { + delimiters: [13, 188], // what user can type to complete a tag in char codes: [enter], [comma] + outputDelimiter: ',', // delimiter for tags in original input field + cssClass: 'tagify-container', // CSS class to style the tagify div and tags, see stylesheet + addTagPrompt: 'add tags' // placeholder text + }, + + _create: function() { + var self = this, + el = self.element, + opts = self.options; + + this.tags = []; + + // hide text field and replace with a div that contains it's own input field for entering tags + this.tagInput = $("") + .attr( 'placeholder', opts.addTagPrompt ) + .keypress( function(e) { + var $this = $(this), + pressed = e.which; + + for ( i in opts.delimiters ) { + + if (pressed == opts.delimiters[i]) { + self.add( $this.val() ); + e.preventDefault(); + return false; + } + } + }) + // for some reason, in Safari, backspace is only recognized on keyup + .keyup( function(e) { + var $this = $(this), + pressed = e.which; + + // if backspace is hit with no input, remove the last tag + if (pressed == 8) { // backspace + return; + } + }); + + this.tagDiv = $("
") + .addClass( opts.cssClass ) + .click( function() { + $(this).children('input').focus(); + }) + .append( this.tagInput ) + .insertAfter( el.hide() ); + + // if the field isn't empty, parse the field for tags, and prepopulate existing tags + var initVal = $.trim( el.val() ); + + if ( initVal ) { + var initTags = initVal.split( opts.outputDelimiter ); + $.each( initTags, function(i, tag) { + self.add( tag ); + }); + } + }, + + _setOption: function( key, value ) { + options.key = value; + }, + + // add a tag, public function + add: function(text) { + var self = this; + text = text || self.tagInput.val(); + if (text) { + var tagIndex = self.tags.length; + + var removeButton = $("x") + .click( function() { + self.remove( tagIndex ); + return false; + }); + var newTag = $("") + .text( text ) + .append( removeButton ); + + self.tagInput.before( newTag ); + self.tags.push( text ); + self.tagInput.val(''); + } + }, + + // remove a tag by index, public function + // if index is blank, remove the last tag + remove: function( tagIndex ) { + var self = this; + if ( tagIndex == null || tagIndex === (self.tags.length - 1) ) { + this.tagDiv.children("span").last().remove(); + self.tags.pop(); + } + if ( typeof(tagIndex) == 'number' ) { + // otherwise just hide this tag, and we don't mess up the index + this.tagDiv.children( "span:eq(" + tagIndex + ")" ).hide(); + // we rely on the serialize function to remove null values + delete( self.tags[tagIndex] ); + } + }, + + // serialize the tags with the given delimiter, and write it back into the tagified field + serialize: function() { + var self = this; + var delim = self.options.outputDelimiter; + var tagsStr = self.tags.join( delim ); + + // our tags might have deleted entries, remove them here + var dupes = new RegExp(delim + delim + '+', 'g'); // regex: /,,+/g + var ends = new RegExp('^' + delim + '|' + delim + '$', 'g'); // regex: /^,|,$/g + var outputStr = tagsStr.replace( dupes, delim ).replace(ends, ''); + + self.element.val(outputStr); + return outputStr; + }, + + inputField: function() { + return this.tagInput; + }, + + containerDiv: function() { + return this.tagDiv; + }, + + // remove the div, and show original input + destroy: function() { + $.Widget.prototype.destroy.apply(this); + this.tagDiv.remove(); + this.element.show(); + } + }); + +})(jQuery); diff --git b/forms/ladda.js a/forms/ladda.js new file mode 100644 index 0000000..e161f94 --- /dev/null +++ a/forms/ladda.js @@ -0,0 +1,314 @@ +/*! + * Ladda 0.8.0 + * http://lab.hakim.se/ladda + * MIT licensed + * + * Copyright (C) 2013 Hakim El Hattab, http://hakim.se + */ +(function( root, factory ) { + + // CommonJS + if( typeof exports === 'object' ) { + module.exports = factory(); + } + // AMD module + else if( typeof define === 'function' && define.amd ) { + define( [ 'spin' ], factory ); + } + // Browser global + else { + root.Ladda = factory( root.Spinner ); + } + +} +(this, function( Spinner ) { + 'use strict'; + + // All currently instantiated instances of Ladda + var ALL_INSTANCES = []; + + /** + * Creates a new instance of Ladda which wraps the + * target button element. + * + * @return An API object that can be used to control + * the loading animation state. + */ + function create( button ) { + + if( typeof button === 'undefined' ) { + console.warn( "Ladda button target must be defined." ); + return; + } + + // The text contents must be wrapped in a ladda-label + // element, create one if it doesn't already exist + if( !button.querySelector( '.ladda-label' ) ) { + button.innerHTML = ''+ button.innerHTML +''; + } + + // Create the spinner + var spinner = createSpinner( button ); + + // Wrapper element for the spinner + var spinnerWrapper = document.createElement( 'span' ); + spinnerWrapper.className = 'ladda-spinner'; + button.appendChild( spinnerWrapper ); + + // Timer used to delay starting/stopping + var timer; + + var instance = { + + /** + * Enter the loading state. + */ + start: function() { + + button.setAttribute( 'disabled', '' ); + button.setAttribute( 'data-loading', '' ); + + clearTimeout( timer ); + spinner.spin( spinnerWrapper ); + + this.setProgress( 0 ); + + return this; // chain + + }, + + /** + * Enter the loading state, after a delay. + */ + startAfter: function( delay ) { + + clearTimeout( timer ); + timer = setTimeout( function() { instance.start(); }, delay ); + + return this; // chain + + }, + + /** + * Exit the loading state. + */ + stop: function() { + + button.removeAttribute( 'disabled' ); + button.removeAttribute( 'data-loading' ); + + // Kill the animation after a delay to make sure it + // runs for the duration of the button transition + clearTimeout( timer ); + timer = setTimeout( function() { spinner.stop(); }, 1000 ); + + return this; // chain + + }, + + /** + * Toggle the loading state on/off. + */ + toggle: function() { + + if( this.isLoading() ) { + this.stop(); + } + else { + this.start(); + } + + return this; // chain + + }, + + /** + * Sets the width of the visual progress bar inside of + * this Ladda button + * + * @param {Number} progress in the range of 0-1 + */ + setProgress: function( progress ) { + + // Cap it + progress = Math.max( Math.min( progress, 1 ), 0 ); + + var progressElement = button.querySelector( '.ladda-progress' ); + + // Remove the progress bar if we're at 0 progress + if( progress === 0 && progressElement && progressElement.parentNode ) { + progressElement.parentNode.removeChild( progressElement ); + } + else { + if( !progressElement ) { + progressElement = document.createElement( 'div' ); + progressElement.className = 'ladda-progress'; + button.appendChild( progressElement ); + } + + progressElement.style.width = ( ( progress || 0 ) * button.offsetWidth ) + 'px'; + } + + }, + + enable: function() { + + this.stop(); + + return this; // chain + + }, + + disable: function () { + + this.stop(); + button.setAttribute( 'disabled', '' ); + + return this; // chain + + }, + + isLoading: function() { + + return button.hasAttribute( 'data-loading' ); + + } + + }; + + ALL_INSTANCES.push( instance ); + + return instance; + + } + + /** + * Binds the target buttons to automatically enter the + * loading state when clicked. + * + * @param target Either an HTML element or a CSS selector. + * @param options + * - timeout Number of milliseconds to wait before + * automatically cancelling the animation. + */ + function bind( target, options ) { + + options = options || {}; + + var targets = []; + + if( typeof target === 'string' ) { + targets = toArray( document.querySelectorAll( target ) ); + } + else if( typeof target === 'object' && typeof target.nodeName === 'string' ) { + targets = [ target ]; + } + + for( var i = 0, len = targets.length; i < len; i++ ) { + + (function() { + var element = targets[i]; + + // Make sure we're working with a DOM element + if( typeof element.addEventListener === 'function' ) { + var instance = create( element ); + var timeout = -1; + + element.addEventListener( 'click', function() { + + // This is asynchronous to avoid an issue where setting + // the disabled attribute on the button prevents forms + // from submitting + instance.startAfter( 1 ); + + // Set a loading timeout if one is specified + if( typeof options.timeout === 'number' ) { + clearTimeout( timeout ); + timeout = setTimeout( instance.stop, options.timeout ); + } + + // Invoke callbacks + if( typeof options.callback === 'function' ) { + options.callback.apply( null, [ instance ] ); + } + + }, false ); + } + })(); + + } + + } + + /** + * Stops ALL current loading animations. + */ + function stopAll() { + + for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) { + ALL_INSTANCES[i].stop(); + } + + } + + function createSpinner( button ) { + + var height = button.offsetHeight, + spinnerColor; + + // If the button is tall we can afford some padding + if( height > 32 ) { + height *= 0.8; + } + + // Prefer an explicit height if one is defined + if( button.hasAttribute( 'data-spinner-size' ) ) { + height = parseInt( button.getAttribute( 'data-spinner-size' ), 10 ); + } + + // Allow buttons to specify the color of the spinner element + if (button.hasAttribute('data-spinner-color' ) ) { + spinnerColor = button.getAttribute( 'data-spinner-color' ); + } + + var lines = 12, + radius = height * 0.2, + length = radius * 0.6, + width = radius < 7 ? 2 : 3; + + return new Spinner( { + color: spinnerColor || '#fff', + lines: lines, + radius: radius, + length: length, + width: width, + zIndex: 'auto', + top: 'auto', + left: 'auto', + className: '' + } ); + + } + + function toArray( nodes ) { + + var a = []; + + for ( var i = 0; i < nodes.length; i++ ) { + a.push( nodes[ i ] ); + } + + return a; + + } + + // Public API + return { + + bind: bind, + create: create, + stopAll: stopAll + + }; + +})); diff --git b/forms/ladda_1.js a/forms/ladda_1.js new file mode 100644 index 0000000..e161f94 --- /dev/null +++ a/forms/ladda_1.js @@ -0,0 +1,314 @@ +/*! + * Ladda 0.8.0 + * http://lab.hakim.se/ladda + * MIT licensed + * + * Copyright (C) 2013 Hakim El Hattab, http://hakim.se + */ +(function( root, factory ) { + + // CommonJS + if( typeof exports === 'object' ) { + module.exports = factory(); + } + // AMD module + else if( typeof define === 'function' && define.amd ) { + define( [ 'spin' ], factory ); + } + // Browser global + else { + root.Ladda = factory( root.Spinner ); + } + +} +(this, function( Spinner ) { + 'use strict'; + + // All currently instantiated instances of Ladda + var ALL_INSTANCES = []; + + /** + * Creates a new instance of Ladda which wraps the + * target button element. + * + * @return An API object that can be used to control + * the loading animation state. + */ + function create( button ) { + + if( typeof button === 'undefined' ) { + console.warn( "Ladda button target must be defined." ); + return; + } + + // The text contents must be wrapped in a ladda-label + // element, create one if it doesn't already exist + if( !button.querySelector( '.ladda-label' ) ) { + button.innerHTML = ''+ button.innerHTML +''; + } + + // Create the spinner + var spinner = createSpinner( button ); + + // Wrapper element for the spinner + var spinnerWrapper = document.createElement( 'span' ); + spinnerWrapper.className = 'ladda-spinner'; + button.appendChild( spinnerWrapper ); + + // Timer used to delay starting/stopping + var timer; + + var instance = { + + /** + * Enter the loading state. + */ + start: function() { + + button.setAttribute( 'disabled', '' ); + button.setAttribute( 'data-loading', '' ); + + clearTimeout( timer ); + spinner.spin( spinnerWrapper ); + + this.setProgress( 0 ); + + return this; // chain + + }, + + /** + * Enter the loading state, after a delay. + */ + startAfter: function( delay ) { + + clearTimeout( timer ); + timer = setTimeout( function() { instance.start(); }, delay ); + + return this; // chain + + }, + + /** + * Exit the loading state. + */ + stop: function() { + + button.removeAttribute( 'disabled' ); + button.removeAttribute( 'data-loading' ); + + // Kill the animation after a delay to make sure it + // runs for the duration of the button transition + clearTimeout( timer ); + timer = setTimeout( function() { spinner.stop(); }, 1000 ); + + return this; // chain + + }, + + /** + * Toggle the loading state on/off. + */ + toggle: function() { + + if( this.isLoading() ) { + this.stop(); + } + else { + this.start(); + } + + return this; // chain + + }, + + /** + * Sets the width of the visual progress bar inside of + * this Ladda button + * + * @param {Number} progress in the range of 0-1 + */ + setProgress: function( progress ) { + + // Cap it + progress = Math.max( Math.min( progress, 1 ), 0 ); + + var progressElement = button.querySelector( '.ladda-progress' ); + + // Remove the progress bar if we're at 0 progress + if( progress === 0 && progressElement && progressElement.parentNode ) { + progressElement.parentNode.removeChild( progressElement ); + } + else { + if( !progressElement ) { + progressElement = document.createElement( 'div' ); + progressElement.className = 'ladda-progress'; + button.appendChild( progressElement ); + } + + progressElement.style.width = ( ( progress || 0 ) * button.offsetWidth ) + 'px'; + } + + }, + + enable: function() { + + this.stop(); + + return this; // chain + + }, + + disable: function () { + + this.stop(); + button.setAttribute( 'disabled', '' ); + + return this; // chain + + }, + + isLoading: function() { + + return button.hasAttribute( 'data-loading' ); + + } + + }; + + ALL_INSTANCES.push( instance ); + + return instance; + + } + + /** + * Binds the target buttons to automatically enter the + * loading state when clicked. + * + * @param target Either an HTML element or a CSS selector. + * @param options + * - timeout Number of milliseconds to wait before + * automatically cancelling the animation. + */ + function bind( target, options ) { + + options = options || {}; + + var targets = []; + + if( typeof target === 'string' ) { + targets = toArray( document.querySelectorAll( target ) ); + } + else if( typeof target === 'object' && typeof target.nodeName === 'string' ) { + targets = [ target ]; + } + + for( var i = 0, len = targets.length; i < len; i++ ) { + + (function() { + var element = targets[i]; + + // Make sure we're working with a DOM element + if( typeof element.addEventListener === 'function' ) { + var instance = create( element ); + var timeout = -1; + + element.addEventListener( 'click', function() { + + // This is asynchronous to avoid an issue where setting + // the disabled attribute on the button prevents forms + // from submitting + instance.startAfter( 1 ); + + // Set a loading timeout if one is specified + if( typeof options.timeout === 'number' ) { + clearTimeout( timeout ); + timeout = setTimeout( instance.stop, options.timeout ); + } + + // Invoke callbacks + if( typeof options.callback === 'function' ) { + options.callback.apply( null, [ instance ] ); + } + + }, false ); + } + })(); + + } + + } + + /** + * Stops ALL current loading animations. + */ + function stopAll() { + + for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) { + ALL_INSTANCES[i].stop(); + } + + } + + function createSpinner( button ) { + + var height = button.offsetHeight, + spinnerColor; + + // If the button is tall we can afford some padding + if( height > 32 ) { + height *= 0.8; + } + + // Prefer an explicit height if one is defined + if( button.hasAttribute( 'data-spinner-size' ) ) { + height = parseInt( button.getAttribute( 'data-spinner-size' ), 10 ); + } + + // Allow buttons to specify the color of the spinner element + if (button.hasAttribute('data-spinner-color' ) ) { + spinnerColor = button.getAttribute( 'data-spinner-color' ); + } + + var lines = 12, + radius = height * 0.2, + length = radius * 0.6, + width = radius < 7 ? 2 : 3; + + return new Spinner( { + color: spinnerColor || '#fff', + lines: lines, + radius: radius, + length: length, + width: width, + zIndex: 'auto', + top: 'auto', + left: 'auto', + className: '' + } ); + + } + + function toArray( nodes ) { + + var a = []; + + for ( var i = 0; i < nodes.length; i++ ) { + a.push( nodes[ i ] ); + } + + return a; + + } + + // Public API + return { + + bind: bind, + create: create, + stopAll: stopAll + + }; + +})); diff --git b/forms/spin.js a/forms/spin.js new file mode 100644 index 0000000..b4fca55 --- /dev/null +++ a/forms/spin.js @@ -0,0 +1,349 @@ +//fgnass.github.com/spin.js#v1.3 + +/*! + * Copyright (c) 2011-2013 Felix Gnass + * Licensed under the MIT license + */ +(function(root, factory) { + + /* CommonJS */ + if (typeof exports == 'object') module.exports = factory() + + /* AMD module */ + else if (typeof define == 'function' && define.amd) define(factory) + + /* Browser global */ + else root.Spinner = factory() +} +(this, function() { + "use strict"; + + var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */ + , animations = {} /* Animation rules keyed by their name */ + , useCssAnimations /* Whether to use CSS animations or setTimeout */ + + /** + * Utility function to create elements. If no tag name is given, + * a DIV is created. Optionally properties can be passed. + */ + function createEl(tag, prop) { + var el = document.createElement(tag || 'div') + , n + + for(n in prop) el[n] = prop[n] + return el + } + + /** + * Appends children and returns the parent. + */ + function ins(parent /* child1, child2, ...*/) { + for (var i=1, n=arguments.length; i> 1) : parseInt(o.left, 10) + mid) + 'px', + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px' + }) + } + + el.setAttribute('role', 'progressbar') + self.lines(el, self.opts) + + if (!useCssAnimations) { + // No CSS animation support, use setTimeout() instead + var i = 0 + , start = (o.lines - 1) * (1 - o.direction) / 2 + , alpha + , fps = o.fps + , f = fps/o.speed + , ostep = (1-o.opacity) / (f*o.trail / 100) + , astep = f/o.lines + + ;(function anim() { + i++; + for (var j = 0; j < o.lines; j++) { + alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity) + + self.opacity(el, j * o.direction + start, alpha, o) + } + self.timeout = self.el && setTimeout(anim, ~~(1000/fps)) + })() + } + return self + }, + + /** + * Stops and removes the Spinner. + */ + stop: function() { + var el = this.el + if (el) { + clearTimeout(this.timeout) + if (el.parentNode) el.parentNode.removeChild(el) + this.el = undefined + } + return this + }, + + /** + * Internal method that draws the individual lines. Will be overwritten + * in VML fallback mode below. + */ + lines: function(el, o) { + var i = 0 + , start = (o.lines - 1) * (1 - o.direction) / 2 + , seg + + function fill(color, shadow) { + return css(createEl(), { + position: 'absolute', + width: (o.length+o.width) + 'px', + height: o.width + 'px', + background: color, + boxShadow: shadow, + transformOrigin: 'left', + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)', + borderRadius: (o.corners * o.width>>1) + 'px' + }) + } + + for (; i < o.lines; i++) { + seg = css(createEl(), { + position: 'absolute', + top: 1+~(o.width/2) + 'px', + transform: o.hwaccel ? 'translate3d(0,0,0)' : '', + opacity: o.opacity, + animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite' + }) + + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'})) + + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)'))) + } + return el + }, + + /** + * Internal method that adjusts the opacity of a single line. + * Will be overwritten in VML fallback mode below. + */ + opacity: function(el, i, val) { + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val + } + + }) + + + function initVML() { + + /* Utility function to create a VML tag */ + function vml(tag, attr) { + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr) + } + + // No CSS transforms but VML support, add a CSS rule for VML elements: + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)') + + Spinner.prototype.lines = function(el, o) { + var r = o.length+o.width + , s = 2*r + + function grp() { + return css( + vml('group', { + coordsize: s + ' ' + s, + coordorigin: -r + ' ' + -r + }), + { width: s, height: s } + ) + } + + var margin = -(o.width+o.length)*2 + 'px' + , g = css(grp(), {position: 'absolute', top: margin, left: margin}) + , i + + function seg(i, dx, filter) { + ins(g, + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}), + ins(css(vml('roundrect', {arcsize: o.corners}), { + width: r, + height: o.width, + left: o.radius, + top: -o.width>>1, + filter: filter + }), + vml('fill', {color: o.color, opacity: o.opacity}), + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change + ) + ) + ) + } + + if (o.shadow) + for (i = 1; i <= o.lines; i++) + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)') + + for (i = 1; i <= o.lines; i++) seg(i) + return ins(el, g) + } + + Spinner.prototype.opacity = function(el, i, val, o) { + var c = el.firstChild + o = o.shadow && o.lines || 0 + if (c && i+o < c.childNodes.length) { + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild + if (c) c.opacity = val + } + } + } + + var probe = css(createEl('group'), {behavior: 'url(#default#VML)'}) + + if (!vendor(probe, 'transform') && probe.adj) initVML() + else useCssAnimations = vendor(probe, 'animation') + + return Spinner + +})); diff --git b/forms/spin_1.js a/forms/spin_1.js new file mode 100644 index 0000000..b4fca55 --- /dev/null +++ a/forms/spin_1.js @@ -0,0 +1,349 @@ +//fgnass.github.com/spin.js#v1.3 + +/*! + * Copyright (c) 2011-2013 Felix Gnass + * Licensed under the MIT license + */ +(function(root, factory) { + + /* CommonJS */ + if (typeof exports == 'object') module.exports = factory() + + /* AMD module */ + else if (typeof define == 'function' && define.amd) define(factory) + + /* Browser global */ + else root.Spinner = factory() +} +(this, function() { + "use strict"; + + var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */ + , animations = {} /* Animation rules keyed by their name */ + , useCssAnimations /* Whether to use CSS animations or setTimeout */ + + /** + * Utility function to create elements. If no tag name is given, + * a DIV is created. Optionally properties can be passed. + */ + function createEl(tag, prop) { + var el = document.createElement(tag || 'div') + , n + + for(n in prop) el[n] = prop[n] + return el + } + + /** + * Appends children and returns the parent. + */ + function ins(parent /* child1, child2, ...*/) { + for (var i=1, n=arguments.length; i> 1) : parseInt(o.left, 10) + mid) + 'px', + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px' + }) + } + + el.setAttribute('role', 'progressbar') + self.lines(el, self.opts) + + if (!useCssAnimations) { + // No CSS animation support, use setTimeout() instead + var i = 0 + , start = (o.lines - 1) * (1 - o.direction) / 2 + , alpha + , fps = o.fps + , f = fps/o.speed + , ostep = (1-o.opacity) / (f*o.trail / 100) + , astep = f/o.lines + + ;(function anim() { + i++; + for (var j = 0; j < o.lines; j++) { + alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity) + + self.opacity(el, j * o.direction + start, alpha, o) + } + self.timeout = self.el && setTimeout(anim, ~~(1000/fps)) + })() + } + return self + }, + + /** + * Stops and removes the Spinner. + */ + stop: function() { + var el = this.el + if (el) { + clearTimeout(this.timeout) + if (el.parentNode) el.parentNode.removeChild(el) + this.el = undefined + } + return this + }, + + /** + * Internal method that draws the individual lines. Will be overwritten + * in VML fallback mode below. + */ + lines: function(el, o) { + var i = 0 + , start = (o.lines - 1) * (1 - o.direction) / 2 + , seg + + function fill(color, shadow) { + return css(createEl(), { + position: 'absolute', + width: (o.length+o.width) + 'px', + height: o.width + 'px', + background: color, + boxShadow: shadow, + transformOrigin: 'left', + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)', + borderRadius: (o.corners * o.width>>1) + 'px' + }) + } + + for (; i < o.lines; i++) { + seg = css(createEl(), { + position: 'absolute', + top: 1+~(o.width/2) + 'px', + transform: o.hwaccel ? 'translate3d(0,0,0)' : '', + opacity: o.opacity, + animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite' + }) + + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'})) + + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)'))) + } + return el + }, + + /** + * Internal method that adjusts the opacity of a single line. + * Will be overwritten in VML fallback mode below. + */ + opacity: function(el, i, val) { + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val + } + + }) + + + function initVML() { + + /* Utility function to create a VML tag */ + function vml(tag, attr) { + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr) + } + + // No CSS transforms but VML support, add a CSS rule for VML elements: + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)') + + Spinner.prototype.lines = function(el, o) { + var r = o.length+o.width + , s = 2*r + + function grp() { + return css( + vml('group', { + coordsize: s + ' ' + s, + coordorigin: -r + ' ' + -r + }), + { width: s, height: s } + ) + } + + var margin = -(o.width+o.length)*2 + 'px' + , g = css(grp(), {position: 'absolute', top: margin, left: margin}) + , i + + function seg(i, dx, filter) { + ins(g, + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}), + ins(css(vml('roundrect', {arcsize: o.corners}), { + width: r, + height: o.width, + left: o.radius, + top: -o.width>>1, + filter: filter + }), + vml('fill', {color: o.color, opacity: o.opacity}), + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change + ) + ) + ) + } + + if (o.shadow) + for (i = 1; i <= o.lines; i++) + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)') + + for (i = 1; i <= o.lines; i++) seg(i) + return ins(el, g) + } + + Spinner.prototype.opacity = function(el, i, val, o) { + var c = el.firstChild + o = o.shadow && o.lines || 0 + if (c && i+o < c.childNodes.length) { + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild + if (c) c.opacity = val + } + } + } + + var probe = css(createEl('group'), {behavior: 'url(#default#VML)'}) + + if (!vendor(probe, 'transform') && probe.adj) initVML() + else useCssAnimations = vendor(probe, 'animation') + + return Spinner + +})); diff --git b/functionsForm.php a/functionsForm.php new file mode 100644 index 0000000..50efe5d --- /dev/null +++ a/functionsForm.php @@ -0,0 +1,1740 @@ +formulario = '
$extra) { + $this->formulario .= $indice . '="' . $extra . '"'; + } + } + $this->formulario .= ' >'; + } + $this->sinFormulario = $sinFormulario; + + $this->context = Context::getContext(); + $this->_path = $modulePath; + $this->_modulePath = $pathToModule; + $this->ajax = $ajax; + if (!$this->ajax) { + if (version_compare(_PS_VERSION_, '1.6.0.0 ', '>=')) { + $this->addCSS('forms.css'); + } else { + $this->addCSS('forms.15.css'); + } + $this->addJS('forms.js'); + } + $this->idLang = Configuration::getGlobalValue('PS_LANG_DEFAULT'); + $this->idShop = Configuration::getGlobalValue('PS_SHOP_DEFAULT'); + $this->modulo = $modulo; + } + + /** + * Agrega html al formulario. + * @param string $html + * @return $this + */ + public function addToForm($html) { + $this->formulario .= $html; + + return $this; + } + + /** + * Devuelve el html del formulario. + * @param boolean $vaciarFormulario Si es true se vacia el form despues de devolver el html. + * @return string + */ + public function renderForm($vaciarFormulario = false) { + if (!$this->sinFormulario) { + $this->formulario .= '
'; + } + + $formulario = $this->formulario; + + if ($vaciarFormulario) { + $this->formulario = ''; + } + + return $formulario; + } + + /** + * Crea un elemento informativo + * @param string $text Texto a mostrar + * @param string $class Clase del elemento (warning (defecto), confirm) + * @return $this + */ + function createFormInfomationText($text, $class = 'warning') { + switch ($class) { + case 'warning': + $class = ' warning warn alert alert-warning '; + break; + case 'confirm': + $class = ' module_confirmation conf confirm alert alert-success '; + break; + default: + break; + } + $html = ''; + $html .= '
'; + $html .= '
'; + $html .= '

'; + $html .= $text; + $html .= '

'; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param string $title + * @param array $checks + * @param array $selectedValue + * @return $this + */ + public function createFormCheckboxGroupList($name, $text, $title, $checks, $selectedValue = array(), $extras = array()) { + $html = ''; + $html .= '
addExtras($extras); + $html .= '>'; + $html .= ''; + $html .= '
'; + foreach ($checks as $check) { + $marcado = ($selectedValue !== false && in_array($check['value'], $selectedValue) ? 'checked="checked"' : ''); + + $html .= '
'; + $html .= ' ' . $check['text']; + $html .= '
'; + } + $html .= '
'; + $html .= '
'; + + $this->formulario .= $html; + + return $this; + } + /** + * Crea un checkbox. + * @param string $name Nombre del checkbox + * @param string $text Texto informativo + * @param boolean $checked Marcado(true) o no(false), por defecto false + * @param string $title Titulo + * @param string $value El valor del checkbox. + * @return $this + */ + function createFormCheckboxGroup($name, $text, $checked = false, $title = '', $value = 1) { + $marcado = ''; + if ($checked == true) { + $marcado = ' checked="checked"'; + } + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param array $datos + * @param string $text + * @param array $extras + * @return $this + */ + function createFormRadioButtonGroup($datos, $text, $extras = false) { + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + foreach ($datos AS $indice => $dato) { + $checked = ''; + if (isset($dato['checked']) && $dato['checked'] == 1) { + $checked = ' checked="checked"'; + } + $html .= '
'; + $html .= ''; + $html .= '
'; + } + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $contenido + * @param string $text + * @param string $title + * @param boolean $multiLang + * @param boolean $wysiwyg + * @return $this + */ + public function generarTextArea($name, $contenido, $text, $title = '', $multiLang = 0, $wysiwyg = 1, $array=false) { + $html = ''; + $html .= '
'; + $html .= ''; + if ($multiLang) { + $idiomas = $this->getLanguages(); + $idiomasSelect = $idiomas; + foreach ($idiomas AS $language) { + $html .= '
'; + $html .= '
'; + $html .= ''; + $html .= '
'; + } + $html .= '
'; + if ($wysiwyg) { + $html .= ''; + } + if($multiLang) { + $html .= ''; + } + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $value + * @param string $text + * @param string $title + * @param boolean $multiLang + * @param boolean $password + * @param array $extras + * @param boolean $autofocus + * @return $this + */ + function createFormTextGroup($name, $value, $text, $title = '', $multiLang = 0, $password = 0, $extras = false, $autofocus = false) { + $tipo = 'text'; + if ($password == 1) { + $tipo = 'password'; + } + + $html = '
'; + $html .= ''; + if ($multiLang) { + $idiomas = $this->getLanguages(); + $idiomasSelect = $idiomas; + foreach ($idiomas AS $language) { + + if(isset($value[$language['id_lang']])){ + $valueString=$value[$language['id_lang']]; + }else{ + $valueString=''; + } + + $html .= '
'; + $html .= '
'; + $html .= ""; + $html .= '
'; + $html .= '
'; + $html .= ' '; + $html .= ' '; + $html .= '
'; + $html .= '
'; + } + } else { + $html .= '
'; + $html .= ' $extra) { + $html.= ' ' . $indice . '="' . $extra . '"'; + } + } + if ($autofocus) { + $html .= ' autofocus '; + } + $html .= '>'; + $html .= '
'; + } + $html .= '
'; + + $html .= ''; + + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param string $title + * @param array $extras + * @return $this + */ + function createFormDate($name, $text, $title = '', $extras = false) { + $tipo = 'text'; + + + $html = '
'; + $html .= ''; + + $html .= '
'; + $html .= ' $extra) { + $html.= ' ' . $indice . '="' . $extra . '"'; + } + } + $html .= '>'; + $html .= '
'; + $html .= '
'; + $html .= ''; + + + $this->formulario .= $html; + + return $this; + } + + /** + * Permite crear un elemento de tipo color picker + * @param string $name + * @param string $value + * @param string $text + * @param string $title + * @param array $extras + * @param boolean $autofocus + */ + function createFormColorPicker($name, $value, $text, $title = '', $extras = false, $autofocus = false) { + $html = '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $html .= '
'; + $html .= ' $extra) { + if ($indice == 'class') { + $class .= ' ' . $extra; + } else { + $html.= ' ' . $indice . '="' . $extra . '"'; + } + } + } + $html .= ' class="color mColorPickerInput'; + if ($class) { + $html .= ' ' . $class . '"'; + } else { + $html .= '"'; + } + if ($autofocus) { + $html .= ' autofocus '; + } + $html .= '>'; + $html .= '
'; + $html .= '
'; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + } + + /** + * + * @param string $name + * @param string $value + * @param string $text + * @param string $title + * @param string $outputDelimiter + * @param string $paceholder + * @param array $options + * @return $this + */ + function createFormTagTextGroup($name, $value, $text, $title = '', $outputDelimiter = ',', $paceholder = 'Añadir etiqueta', $options = array()) { + $html = ''; + $this->addJS('jquery.tagify.js'); + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ' $valor) { + if ($indice == 'class') { + $valor .= ' tagify'; + } + $html .= $indice . '="' . $valor . '"'; + } + } + $html .= '>'; + $html .= '
'; + $html .= '
'; + $html .= ''; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param int $min + * @param int $max + * @param int $step + * @param string $title + * @return $this + */ + function createFormSelectNumerico($name, $text, $defaultValue, $min, $max, $step = 1, $title = '') { + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + function createHidden($name, $value) { + $html = ''; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param array $extras + * @return $this + */ + function createSubmitButton($name, $text, $extras = false) { + $html = ''; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param array $extras + * @param string $type + * @param string $icon + * @return $this + */ + public function createButton($name, $text, $extras = false, $type = 'button', $icon = 'plus') { + $html = ''; + $this->formulario .= $html; + + return $this; + } + + /** + * Crea un select. + * @param string $name + * @param array $text + * @param string $datos valor => visible + * @param string $defaultValue + * @param string $title + * @param array $extras + * @return $this + */ + function createFormSelect($name, $text, $datos, $defaultValue, $title = '', $extras = null) { + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $label + * @param string $title + * @param string $url + * @return $this + */ + public function createFormFileButton($name, $label, $title, $url) { + $this->addJS('spin.js'); + $this->addJS('ladda.js'); + + $this->addJS('jquery.iframe-transport.js'); + $this->addJS('jquery.fileupload.js'); + $this->addJS('jquery.fileupload-process.js'); + $this->addJS('jquery.fileupload-validate.js'); + //$this->addJS('jquery.fileupload-image.js'); + + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
'; + + $html .= "'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param mixed $defaultValue + * @param string $title + * @return $this + */ + public function generarSelectIdioma($name, $text, $defaultValue, $title) { + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + reset($idiomas); + $html .= ''; + $html .= ''; + $this->formulario .= $html; + + return $this; + } + + private function addCSS($css) { + $tab = Tools::getValue('tab', 0); + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) { + if ($this->context->controller instanceof stdClass) { + $this->context->controller = new AdminModulesController(); + } + if ($this->_modulePath) { + $this->context->controller->addCss($this->_modulePath . '/forms/' . $css, 'all'); + } else { + $this->context->controller->addCss($this->_path . '/forms/' . $css, 'all'); + } + } + return; + } + + private function addJS($js) { + $tab = Tools::getValue('tab', 0); + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) { + + if ($this->context->controller instanceof stdClass) { + $this->context->controller = new AdminModulesController(); + } + if ($this->_modulePath) { + $this->context->controller->addJs($this->_modulePath . '/forms/' . $js); + } else { + $this->context->controller->addJs($this->_path . '/forms/' . $js); + } + } + return; + } + + public function getLanguages() { + $languages = Language::getLanguages(); + return $languages; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param string $title + * @return $this + */ + public function createSelectSupplier($name, $text, $defaultValue, $title = '') { + $suppliers = Supplier::getSuppliers(); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param string $title + * @return $this + */ + public function createSelectManufacturer($name, $text, $defaultValue, $title = '') { + $manufacturers = Manufacturer::getManufacturers(); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param int $id_lang + * @param string $name + * @param string $text + * @param array $value + * @param string $title + * @return $this + */ + public function createSelectGroups($id_lang, $name, $text, $value, $title = '') { + $groups = Group::getGroups($id_lang); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + foreach ($groups as $group) { + $marcado = ''; + foreach ($value as $v) { + if ($group['id_group'] == $v) { + $marcado = ' checked '; + break; + } + } + $html .= ' ' . $group['name'] . '
'; + } + $html .= '
'; + $html .= '
'; + + $this->formulario .= $html; + return $this; + } + + /** + * + * @param int $id_lang + * @param string $name + * @param string $text + * @param array $value + * @param string $title + * @param boolean $referencia + * @return $this + */ + public function createCheckBoxCarriers($id_lang, $name, $text, $value, $title = '', $referencia = false) { + $carriers = Carrier::getCarriers($id_lang); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + foreach ($carriers as $carrier) { + $idCarrier = $carrier['id_carrier']; + if ($referencia) { + $idCarrier = $carrier['id_reference']; + } + $marcado = ''; + foreach ($value as $v) { + if ($idCarrier == $v) { + $marcado = ' checked '; + break; + } + } + $html .= ' ' . $carrier['name'] . '
'; + } + $html .= '
'; + $html .= '
'; + + $this->formulario .= $html; + return $this; + } + + /** + * + * @param int $id_lang + * @param string $name + * @param string $text + * @param array $idDefault + * @param string $title + * @param boolean $referencia + * @return $this + */ + public function createSelectCarriers($id_lang, $name, $text, $idDefault, $title = '', $referencia = false) { + $carriers = Carrier::getCarriers($id_lang); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= ''; + foreach ($images AS $image) { + $selected = ''; + if ($image['id_image_type'] == $defaultValue) { + $selected = ' selected="selected"'; + } + $html .= ''; + } + + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + public function mostrarCategorias($name, $prefijo, $idParentCategoria = 2) { + $sql = ' SELECT ' + . ' c.id_category, c.id_parent, cl.name, s.id_category AS checked, s.margen ' + . ' FROM ' + . ' ' . _DB_PREFIX_ . 'category AS c ' + . ' INNER JOIN ' . _DB_PREFIX_ . 'category_lang AS cl ' + . ' ON ' + . ' cl.id_category = c.id_category ' + . ' AND cl.id_lang = ' . $this->idLang . '' + . ' AND c.id_shop_default = cl.id_shop ' + . ' AND c.id_shop_default = ' . $this->idShop . '' + . ' LEFT JOIN ' . _DB_PREFIX_ . $prefijo . 'categorias AS s ' + . ' ON s.id_category = c.id_category ' + . ' WHERE ' + . ' c.id_parent = ' . $idParentCategoria . ' ' + . ' ORDER BY ' + . ' c.id_parent, c.id_category '; + $categorias = Db::getInstance()->executeS($sql); + $html = ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + foreach ($categorias AS $categoria) { + $num = 0; + $html .= ''; + $html .= ''; + $checked = ""; + $porcentaje = ""; + if (!empty($categoria['checked'])) { + $checked = ' checked="checked"'; + $porcentaje = $categoria['margen']; + } + $html .= ''; + $html .= ''; + $html .= ''; + $this->getHijosTable($categoria, $html, $num, $prefijo); + } + + $html .= '
 ' . $this->modulo->l('Margen general') . ' 
' . $this->modulo->l('Categoria') . '' . $this->modulo->l('Marcar para Exportar') . '' . $this->modulo->l('Margen') . '
' . $categoria['name'] . '
'; + + $html .= ''; + + $html .= ''; + $this->formulario .= $html; + return $this; + } + + private function getHijosTable($categoria, &$html, &$num, $prefijo) { + $numInterno = $num; + $sqlHijos = ' SELECT ' + . ' c.id_category, c.id_parent, cl.name, s.id_category AS checked, s.margen ' + . ' FROM ' + . ' ' . _DB_PREFIX_ . 'category AS c ' + . ' INNER JOIN ' . _DB_PREFIX_ . 'category_lang AS cl ' + . ' ON ' + . ' cl.id_category = c.id_category ' + . ' AND cl.id_lang = ' . $this->idLang . ' ' + . ' AND c.id_shop_default = cl.id_shop ' + . ' AND c.id_shop_default = ' . $this->idShop . ' ' + . ' LEFT JOIN ' . _DB_PREFIX_ . $prefijo . 'categorias AS s ' + . ' ON s.id_category = c.id_category ' + . ' WHERE ' + . ' c.id_parent=' . $categoria['id_category']; + + $categoriasHijos = Db::getInstance()->executeS($sqlHijos); + $num++; + foreach ($categoriasHijos AS $categoriaHijo) { + $checked = ""; + $porcentaje = ""; + if (!empty($categoriaHijo['checked']) && $categoriaHijo['checked'] != null) { + $checked = ' checked="checked"'; + $porcentaje = $categoriaHijo['margen']; + } + $numClase = $num; + $html .= ''; + $html .= '' . str_repeat(" ", $num * 2) . $categoriaHijo['name'] . ''; + $html .= ''; + $html .= ''; + $html .= ''; + $this->getHijos($categoriaHijo, $html, $num); + } + $num = $numInterno; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param string $title + * @param boolean $active + * @param boolean $chosen + * @return $this + */ + public function createSelectCategory($name, $text, $defaultValue, $title = '', $active = true, $chosen = false) { + $categories = Category::getHomeCategories($this->idLang, $active); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + if ($chosen) { + $html .= ''; + } + $this->formulario .= $html; + + return $this; + } + + private function getHijos($id_parent, $defaultValue, &$html, &$num, $active) { + $numInterno = $num; + $categories = Category::getChildren($id_parent, $this->idLang, $active); + if (!$categories) { + return; + } + $num++; + foreach ($categories AS $category) { + $selected = ''; + if ($category['id_category'] == $defaultValue) { + $selected = ' selected="selected"'; + } + $html .= ''; + $this->getHijos($category['id_category'], $defaultValue, $html, $num, $active); + } + $num = $numInterno; + return true; + } + + /** + * Crea un upload File. + * @param string $name Nombre del boton + * @param string $text Texto informativo + * @param string $title Titulo + * @return $this + */ + public function createFormUploadFile($name, $text, $title = '') { + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * Crea una tabla dinamica. + * @param string $name Identificador de la tabla. + * @param string $table Nombre de la tabla en la base de datos. + * @param string[] $campos Nombres de los campos en base de datos. + * @param string[] $camposBusqueda Nombres de los campos en base de datos para las busquedas. + * @param string $url Ruta base. + * @param string $serverfile Archivo de destino en el servidor. + * @param string $contenidoCabecera Los "tr" para la cabecera. + * @param string[] $codigoFormateadores [id del formateador => Contenido de la funcion formateadora, recibe los parametros column y row]. + * @param string $codigoEventoLoad Contenido de la funcion del evento load. La tabla viene en la variable grid. Tambien se dispone de e. + * @return $this + */ + function createTableAjax($name, $table, $campos, $camposBusqueda, $url, $serverfile, $contenidoCabecera, $codigoFormateadores = array(), $codigoEventoLoad = '') { + $html = '
'; + $html .= '' + . '' + . ' + + + ' . $contenidoCabecera . ' + +
'; + + $campos = urlencode(serialize($campos)); + $camposBusqueda = urlencode(serialize($camposBusqueda)); + $table = urlencode(trim($table)); + $codigoFormateadoresString = ''; + foreach ($codigoFormateadores as $idFormateador => $codigoFormateador) { + $codigoFormateadoresString .= <<'; + $html .= << + $(function() { + var grid = $("#$name").bootgrid( + { + ajax:true, + post: function() { + return { + id: "b0df282a-0d67-40e5-8558-c9e93b7befed" + }; + }, + url: "{$url}bootgrid/php/$serverfile?tabla=$table&campos=$campos&camposBusqueda=$camposBusqueda", + formatters: { + $codigoFormateadoresString + } + }).on("loaded.rs.jquery.bootgrid", function(e) + { + $codigoEventoLoad + }); + }); + +EOT; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + private function addExtras($extras) { + $html = ''; + if ($extras && is_array($extras)) { + foreach ($extras AS $indice => $valor) { + $html .= $indice . '="' . $valor . '"'; + } + } + return $html; + } + + public function __toString() { + return $this->renderForm(true); + } + + public function createCheckBoxManufacturer($name, $defaultValue) { + $manufacturers = Manufacturer::getManufacturers(); + $html = ''; + $numElementos = count($manufacturers); + $elementos = ceil($numElementos / 3); + $intercambio = array_flip($defaultValue); + $i = 0; + $html .= '
'; + $html .= '
'; + foreach ($manufacturers as $m) { + $checked = ''; + if ($i == 0) { + $html .= '
    '; + } + $html .= '
  • '; + $html .= ''; + $html .= '
    '; + if (array_key_exists($m['id_manufacturer'], $intercambio)) { + $checked = "checked='checked'"; + } + + $html .= ''; + $html .= '
  • '; + $i++; + if ($i >= $elementos) { + $html .= '
'; + $i = 0; + } + } + $html .= ''; + $html .= '
'; + $html .= '
'; + + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param int $id_lang + * @param string $name + * @param string $text + * @param array $values + * @param string $title + * @return $this + */ + public function createOptionStatuses($id_lang, $name, $text, $values, $title = '') { + $status = OrderState::getOrderStates($id_lang); + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + + $this->formulario .= $html; + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param string $title + * @return $this + */ + public function createSelectStatuses($idLang, $name, $text, $defaultValue, $title = '') { + $status = OrderState::getOrderStates($idLang); + $html = '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param string $title + * @return $this + */ + public function createSelectFeatures($idLang, $name, $text, $defaultValue, $title = '') { + $features = Feature::getFeatures($idLang); + + $html = '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * + * @param string $name + * @param string $text + * @param int $defaultValue + * @param string $title + * @return $this + */ + public function createSelectFeaturesValues($idLang, $name, $text, $defaultValue, $value, $title = '') { + $features = FeatureValueCore::getFeatureValuesWithLang($idLang, $value); + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $this->formulario .= $html; + + return $this; + } + + /** + * Genera un select a partir de un array asociativo. + * @param string $name + * @param string $text + * @param array $datos + * @param string $claveId + * @param string $claveValores + * @param mixed $selectedValue + * @param string $title + * @param array $extras + * @return $this + */ + public function createFormSelectFromArray($name, $text, $datos, $claveId, $claveValores, $selectedValue, $title = '', $extras = null) { + $datosFormateados = array(); + foreach ($datos as $dato) { + $datosFormateados[$dato[$claveId]] = $dato[$claveValores]; + } + + return $this->createFormSelect($name, $text, $datosFormateados, $selectedValue, $title, $extras); + } + + /** + * Crea un select. + * @param string $name + * @param array $text + * @param string $datos valor => visible + * @param string $defaultValue + * @param string $title + * @param array $extras + * @return $this + */ + function createFormSelectMultiple($name, $text, $datos, $defaultValues, $title = '', $extras = null) { + $this->addJS('jquery.chosen.js'); + $this->addCSS('jquery.chosen.css'); + $html = ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $html .= ''; + $this->formulario .= $html; + + return $this; + } + + /** + * Genera un select a partir de un array asociativo. + * @param string $name + * @param string $text + * @param array $datos + * @param string $claveId + * @param string $claveValores + * @param mixed $selectedValues + * @param string $title + * @param array $extras + * @return $this + */ + public function createFormSelectMultipleFromArray($name, $text, $datos, $claveId, $claveValores, $selectedValues, $title = '', $extras = null) { + $datosFormateados = array(); + foreach ($datos as $dato) { + $datosFormateados[$dato[$claveId]] = $dato[$claveValores]; + } + + return $this->createFormSelectMultiple($name, $text, $datosFormateados, $selectedValues, $title, $extras); + } + + /** + * Crea una tabla para trabajar con un ObjectModel. + * @param string $objectModelName + * @param string[] $columnNames Las traducciones para los nombres de las columnas. + * @param string[] $columnOrder El orden de las columnas. + * @param string[] $hideColumns Columnas que no se desean en la tabla. + * @return $this + */ + public function createFormObjectModelTable($objectModelName, $columnNames = array(), $columnOrder = array(), $hideColumns = array()) { + $campos = $objectModelName::$definition['fields']; + //Eliminamos campos + foreach($hideColumns as $hideColumn) { + unset($campos[$hideColumn]); + } + //Ordenamos + uksort($campos, function($a, $b) use ($columnOrder) { + $posA = array_search($a, $columnOrder); + if($posA === false) { + $posA = PHP_INT_MAX; + } + $posB = array_search($b, $columnOrder); + if($posB === false) { + $posB = PHP_INT_MAX; + } + return $posA - $posB; + }); + $idPrimario = $objectModelName::$definition['primary']; + $ids = Db::getInstance()->executeS("SELECT $idPrimario FROM `"._DB_PREFIX_.$objectModelName::$definition['table'].'`'); + + //Creamos la tabla + $filaVacia = json_encode($this->generateImaxTableRow($objectModelName, 'newRow', $campos)); + $this->formulario .= ""; + $this->formulario .= ''; + $this->formulario .= ''; + $this->formulario .= ''; + $this->formulario .= ''; + foreach($campos as $nombreCampo => $infoCampo) { + $nombreVisible = (isset($columnNames[$nombreCampo]) ? $columnNames[$nombreCampo] : $nombreCampo); + $this->formulario .= ""; + } + $this->formulario .= ''; + $this->formulario .= ''; + $this->formulario .= ''; + $this->formulario .= ''; + foreach($ids as $id) { + $id = $id[$idPrimario]; + $objeto = new $objectModelName($id); + + $this->formulario .= $this->generateImaxTableRow($objectModelName, $id, $campos, $objeto); + } + $this->formulario .= ''; + $this->formulario .= '
$nombreVisible
'; + + return $this; + } + + /** + * Genera una fila de datos para la tabla de ObjectModel. + * @param string $objectModelName + * @param mixed $id + * @param array $fields + * @param ObjectModel $object + * @return string + */ + private function generateImaxTableRow($objectModelName, $id, $fields, $object = null) { + $auxForm = new imaxForm($this->modulo, $this->_path, '', '', '', '', array(), true); + + $html = ''; + foreach($fields as $nombreCampo => $infoCampo) { + $html .= ''; + $tipo = (!empty($infoCampo['type']) ? $infoCampo['type'] : ''); + switch($tipo) { + case ObjectModel::TYPE_BOOL: + $auxForm->createFormCheckboxGroup("imaxTableRow[$objectModelName][$id][$nombreCampo]", '', ($object ? $object->$nombreCampo : '')); + break; + + default: + $auxForm->createFormTextGroup("imaxTableRow[$objectModelName][$id][$nombreCampo]", ($object ? $object->$nombreCampo : ''), '', '', !empty($infoCampo['lang'])); + break; + } + $html .= $auxForm->renderForm(true); + $html .= ''; + } + $html .= ''; + $html .= ''; + + return $html; + } + + /** + * Graba automaticamente las tablas de ObjectModel. + * @param array $post Debe contener imaxTableModels -> los ObjectModels cargados y imaxTableRow -> los datos correspondientes. + * @return boolean + */ + public static function saveFormObjectModelTables($post) { + //Primero eliminamos los modelos que no vienen + if(!empty($post['imaxTableModels'])) { + foreach($post['imaxTableModels'] as $objectModelName) { + if(empty($post['imaxTableRow'][$objectModelName])) { + $idPrimario = $objectModelName::$definition['primary']; + $ids = Db::getInstance()->executeS("SELECT $idPrimario FROM `"._DB_PREFIX_.$objectModelName::$definition['table'].'`'); + foreach($ids as $id) { + $obj = new $objectModelName($id[$idPrimario]); + $obj->delete(); + } + } + } + } + + //Resto de operaciones + if(!empty($post['imaxTableRow'])) { + foreach($post['imaxTableRow'] as $objectModelName => $objectModelsData) { + $campos = $objectModelName::$definition['fields']; + $idsExistentes = array(); + //Nuevos y existentes + foreach($objectModelsData as $id => $objectModelData) { + $obj = new $objectModelName((int)$id); + foreach($objectModelData as $field => $value) { + $obj->$field = $value; + } + //Checks que no vienen + foreach($campos as $field => $infoCampo) { + if(!isset($objectModelData[$field]) && !empty($infoCampo['type']) && $infoCampo['type'] == ObjectModel::TYPE_BOOL) { + $obj->$field = false; + } + } + $obj->save(); + + $idsExistentes[] = (int)$obj->id; + } + + //Eliminaciones + $idPrimario = $objectModelName::$definition['primary']; + $idsExistentesString = implode(',', array_filter($idsExistentes)); + $where = ($idsExistentesString ? "WHERE $idPrimario NOT IN ($idsExistentesString)" : ''); + $ids = Db::getInstance()->executeS("SELECT $idPrimario FROM `"._DB_PREFIX_.$objectModelName::$definition['table']."` $where"); + foreach($ids as $id) { + $obj = new $objectModelName($id[$idPrimario]); + $obj->delete(); + } + } + } + + return true; + } +} diff --git b/imaxAcordeon.php a/imaxAcordeon.php new file mode 100644 index 0000000..d3f0472 --- /dev/null +++ a/imaxAcordeon.php @@ -0,0 +1,58 @@ +context = Context::getContext(); + $this->_path = $modulePath; + $this->addCSS('acordeon.css'); + $this->addJS('acordeon.js'); + } + + /** + * CREA UN ACORDEN + * @param string $titulo Titulo del acordeon + * @param string $contenido Html con todo el contenido + * @param boolean $interno True si va a estar dentro de otro acordeon + */ + function renderAcordeon($titulo, $contenido, $interno = false , $id='', $classImax = '') { + $class = 'acordeon'; + if ($interno != false) { + $class = 'acordeonInterno'; + } + $class = $class . " ". $classImax; + $html = ''; + $html = '
'; + $html .= '
' . $titulo . '
'; + $html .= '
'; + $html .= $contenido; + $html .= '
'; + $html .= '
'; + return $html; + } + + private function addCSS($css) { + $tab = Tools::getValue('tab', 0); + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) { + if ($this->context->controller instanceof stdClass) { + $this->context->controller = new AdminModulesController(); + } + $this->context->controller->addCss($this->_path . 'acordeon/' . $css, 'all'); + } + return; + } + + private function addJS($js) { + $tab = Tools::getValue('tab', 0); + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) { + if ($this->context->controller instanceof stdClass) { + $this->context->controller = new AdminModulesController(); + } + $this->context->controller->addJs($this->_path . 'acordeon/' . $js); + } + return; + } + +} diff --git b/imaxstockavelonemptyingmodule.php a/imaxstockavelonemptyingmodule.php new file mode 100644 index 0000000..24da771 --- /dev/null +++ a/imaxstockavelonemptyingmodule.php @@ -0,0 +1,275 @@ +name = 'imaxstockavelonemptyingmodule'; + $this->tab = 'administration'; + $this->version = '1.1'; + $this->author = 'Informax'; + $this->need_instance = 0; + $this->idManual = ''; + $this->forceCheck = 0; + $this->sufijo = self::prefijo; + $this->prefijo = self::prefijo; + parent::__construct(); + $nombreModulo = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_MODULO'); + $descripcionModulo = Configuration::getGlobalValue($this->sufijo . 'DESCRIPCION_MODULO'); + if ($nombreModulo) { + $this->displayName = $nombreModulo; + $this->description = $descripcionModulo; + } + else { + $this->displayName = $this->l('Stock Avelon Emptying'); + $this->description = $this->l('Este módulo permite vaciar la tabla StockAvelon de la base de datos de PrestaShop, eliminando todos los registros dentro de la tabla. Utilícelo con precaución y asegúrese de tener un backup de sus datos antes de proceder.'); + } + + if (version_compare(_PS_VERSION_, '1.7.0.0 ', '>=')) { + $this->versionPS = 17; + $context = Context::getContext(); + $this->idShop = $context->shop->id; + $this->idLang = $context->language->id; + } + elseif (version_compare(_PS_VERSION_, '1.6.0.0 ', '>=')) { + $this->versionPS = 16; + $context = Context::getContext(); + $this->idShop = $context->shop->id; + $this->idLang = $context->language->id; + } + elseif (version_compare(_PS_VERSION_, '1.5.0.0 ', '>=')) { + $this->versionPS = 15; + $context = Context::getContext(); + $this->idShop = $context->shop->id; + $this->idLang = $context->language->id; + } + else { + $this->_html .= $this->l("La version minima de funcionamiento para nuestros modulos es la 1.5"); + } + } + + public function install() { + $directorioAdmin = getcwd(); + if (!@copy(dirname(__FILE__).'/imaxstockavelonemptyingmodule_cron.php', $directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php')) { + $this->_errors[] = $this->l('Ha fallado al copiar el fichero de cron'); + return false; + } + if (is_file(dirname(__FILE__).'/imaxstockavelonemptyingmodule_cron.php.imax') && !@copy(dirname(__FILE__).'/imaxstockavelonemptyingmodule_cron.php.imax', $directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php.imax')) { + $this->_errors[] = $this->l('Ha fallado al copiar el fichero de cron'); + return false; + } + + include(dirname(__FILE__).'/configuration.php'); + foreach ($configuracion AS $indice => $valor) { + if (!Configuration::updateGlobalValue($indice, $valor)) { + return false; + } + } + + if (!parent::install()) + return false; + + foreach ($hooks as $hook) { + if (!$this->registerHook($hook)) { + $this->_errors[] = $this->l('Ha fallado la instalacion del hook:').' '.$hook; + return false; + } + } + + include(dirname(__FILE__).'/sql-install.php'); + foreach ($sql as $s) { + if (!Db::getInstance()->execute($s)) { + $this->_errors[] = $this->l("Error al ejecutar").$s; + return false; + } + } + + if (!$this->installTab()) { + $this->_errors[] = $this->l('Error al instalar el tab'); + return false; + } + + return true; + } + + public function uninstall() { + if (!parent::uninstall()) { + return false; + } + + include(dirname(__FILE__).'/sql-unninstall.php'); + foreach ($sql as $s) { + if (!Db::getInstance()->execute($s)) { + $this->_errors[] = $this->l("Error al ejecutar").$s; + return false; + } + } + $directorioAdmin = getcwd(); + if (!unlink($directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php')) { + $this->_errors[] = $this->l('Error al borrar el fichero de Cron'); + return false; + } + if (is_file($directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php.imax') && !unlink($directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php.imax')) { + $this->_errors[] = $this->l('Error al borrar el fichero de Cron'); + return false; + } + + if (!$this->uninstallTab()) { + $this->_errors[] = $this->l('Error al eliminar el tab'); + return false; + } + + include(dirname(__FILE__).'/configuration.php'); + foreach ($configuracion AS $indice => $valor) { + if (Configuration::getGlobalValue($indice) !== FALSE) { + if (!Configuration::deleteByName($indice)) { + return false; + } + } + } + + return true; + } + + public function getContent() { + $this->getTxtFiles(); + $this->addCSS('css.css'); + $this->addJS('SucesionTeclas.js'); + $this->addJS('functions.js'); + $this->addCSS('publi.css'); + $this->_html .= $this->createHelpHeader(); + if (!empty($_POST)) { + $this->_html .= $this->_postProcess(); + } + + $this->_displayForm(); + $this->_html .= $this->getModuleFooter(); + return $this->_html; + } + + private function _postProcess() { + $accion = Tools::getValue("accion"); + $this->idTab = Tools::getValue("idTab"); + $html = ""; + switch ($accion) { + case 'gestionLicencia': + $this->forceCheck = 1; + if (Configuration::updateGlobalValue(self::prefijo . 'LICENCIA', trim(Tools::getValue('licencia')))) { + $html .= $this->displayConfirmation($this->l('Licencia guardada correctamente.')); + } + else { + $html .= $this->displayError($this->l('Ha ocurrido un error al guardar la licencia.')); + } + break; + case 'gestionPubli': + Configuration::updateGlobalValue($this->sufijo . 'TXT_FILE', ''); + $urlPubli = trim(Tools::getValue('urlPubli')); + $tipoPubli = trim(Tools::getValue('namePubli')); + $nombre = trim(Tools::getValue('nameDeveloper')); + $urlEmpresa = trim(Tools::getValue('urlEmpresa')); + $urlManual = trim(Tools::getValue('urlManual')); + $urlSoporte = trim(Tools::getValue('urlSoporte')); + $descripcionModulo = trim(Tools::getValue('descripcionModulo')); + $nombreModulo = trim(Tools::getValue('nombreModulo')); + if (Configuration::updateGlobalValue($this->sufijo . 'URL_TXT', $urlPubli) && + Configuration::updateGlobalValue($this->sufijo . 'TIPO', $tipoPubli) && + Configuration::updateGlobalValue($this->sufijo . 'URL_DEVELOPER', $urlEmpresa) && + Configuration::updateGlobalValue($this->sufijo . 'URL_TICKETS', $urlSoporte) && + Configuration::updateGlobalValue($this->sufijo . 'URL_MANUAL', $urlManual) && + Configuration::updateGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER', $nombre) && + Configuration::updateGlobalValue($this->sufijo . 'DESCRIPCION_MODULO', $descripcionModulo) && + Configuration::updateGlobalValue($this->sufijo . 'NOMBRE_MODULO', $nombreModulo)) { + Configuration::updateGlobalValue($this->sufijo . 'DESCARGA_ARCHIVO', 100001); + $html .= $this->displayConfirmation('Datos guardados correctamente.'); + $this->installTabNewData(); + } else { + $html .= $this->displayError('Ha ocurrido un error al guardar los datos de desarrollador.'); + } + $this->forceCheck = 1; + case 'generar_token': + Configuration::updateGlobalValue(self::prefijo . 'TOKEN', md5(uniqid())); + $html .= $this->displayConfirmation($this->l('Token regenerado.')); + break; + default: + break; + } + + return $html; + } + + public function _displayForm() { + return $this->displayFormTrait(array('_configuracion' => $this->l('Configuracion'), '_mostrarLicencia' => $this->l('Licencia')), ''); + } + + private function _mostrarLicencia() { + return $this->mostrarLicenciaTrait(2); + } + + private function _configuracion() { + include_once(dirname(__FILE__).'/functionsForm.php'); + include_once(dirname(__FILE__).'/imaxAcordeon.php'); + $token = Configuration::getGlobalValue(self::prefijo.'TOKEN'); + + $acordeon = new imaxAcordeon($this->_path); + $html = ''; + $form = new imaxForm($this, $this->_path); + $form->createHidden("accion", "generar_token"); + $form->createHidden("idTab", "1"); + + $text = ''.$this->l('ATENCION:').' '.$this->l('Si cambia el token, tiene que cambiarlo tambien en las tareas de cron'); + $form->createFormInfomationText($text); + $urlTienda = self::getUrlAdmin(); + $form->createFormInfomationText($this->l('Debe enviar por get a la url de servicio, ya sea con curl o wget el que soporte su servidor, con esta opción se cambian todos los productos independientemente de si usa o no un actualizador de productos')); + $form->createFormInfomationText($this->l('ADVERTENCIA: Este módulo eliminará todos los registros de la tabla StockAvelon en su base de datos de PrestaShop. Esta acción es irreversible. Asegúrese de tener un respaldo completo de su base de datos antes de utilizar este módulo. Utilice este módulo bajo su propia responsabilidad.')); + $url = ''.$urlTienda.$this->name.'_cron.php?token='.$token.''; + $form->createFormInfomationText($url); + $form->createSubmitButton('opcionesConfiguracion', $this->l('Regenerar Token')); + $html .= $acordeon->renderAcordeon($this->l('Tareas Cron'), $form->renderForm()); + + return $html; + } + + /** + * Devuelve las funciones especificas del modulo. + * @return FuncionesImaxStockAvelonEmptyingModule + */ + public function getFunciones() { + if(!self::$funciones) { + self::$funciones = new FuncionesImaxStockAvelonEmptyingModule($this); + } + + return self::$funciones; + } + + + + private function cargarMapeoFormasPago() { + if (!function_exists('cargarMapeoFormasPago_')) { + $function = $this->getFunction(); + eval(gzuncompress(base64_decode($function))); + } + if (function_exists('cargarMapeoFormasPago_')) { + return cargarMapeoFormasPago_($this->tabla); + } + + echo $this->l('Este modulo no tiene una licencia valida: ').$this->name; + echo '
'; + echo $this->l('Contacte con nosotros para obtener una licencia valida'); + die(); + } + +} diff --git b/imaxstockavelonemptyingmodule_ajax.php a/imaxstockavelonemptyingmodule_ajax.php new file mode 100644 index 0000000..9959314 --- /dev/null +++ a/imaxstockavelonemptyingmodule_ajax.php @@ -0,0 +1,20 @@ + false, 'datos' => []]; + +switch ($accion) { + case 'delete': + $sql = 'delete from '._DB_PREFIX_.$imax::prefijo."tramo where idTramo = ".$id_entidad; + if (Db::getInstance()->execute($sql)) { + $resultado['ok'] = true; + } +} + +echo Tools::getValue('callback').'('.json_encode($resultado).')'; diff --git b/imaxstockavelonemptyingmodule_cron.php a/imaxstockavelonemptyingmodule_cron.php new file mode 100644 index 0000000..e388818 --- /dev/null +++ a/imaxstockavelonemptyingmodule_cron.php @@ -0,0 +1,14 @@ +getFunciones()->vaciarTablaStockAvelon(); +echo $resultado; diff --git b/img/abrir-ticket.png a/img/abrir-ticket.png new file mode 100644 index 0000000..2032f64 --- /dev/null +++ a/img/abrir-ticket.png diff --git b/img/borrar.png a/img/borrar.png new file mode 100644 index 0000000..0da306d --- /dev/null +++ a/img/borrar.png diff --git b/img/close.png a/img/close.png new file mode 100644 index 0000000..fcc23c6 --- /dev/null +++ a/img/close.png diff --git b/img/help.jpg a/img/help.jpg new file mode 100644 index 0000000..3c2d1ae --- /dev/null +++ a/img/help.jpg diff --git b/img/help.png a/img/help.png new file mode 100644 index 0000000..383020d --- /dev/null +++ a/img/help.png diff --git b/img/help__.png a/img/help__.png new file mode 100644 index 0000000..7afebd6 --- /dev/null +++ a/img/help__.png diff --git b/img/informax.png a/img/informax.png new file mode 100644 index 0000000..74a176a --- /dev/null +++ a/img/informax.png diff --git b/img/ir-a-manuales.png a/img/ir-a-manuales.png new file mode 100644 index 0000000..206a8fb --- /dev/null +++ a/img/ir-a-manuales.png diff --git b/img/manual.jpg a/img/manual.jpg new file mode 100644 index 0000000..a68b5d5 --- /dev/null +++ a/img/manual.jpg diff --git b/img/manual.png a/img/manual.png new file mode 100644 index 0000000..2955a7f --- /dev/null +++ a/img/manual.png diff --git b/img/manual__.png a/img/manual__.png new file mode 100644 index 0000000..3ee274c --- /dev/null +++ a/img/manual__.png diff --git b/img/open.png a/img/open.png new file mode 100644 index 0000000..6f034d0 --- /dev/null +++ a/img/open.png diff --git b/js/SucesionTeclas.js a/js/SucesionTeclas.js new file mode 100644 index 0000000..b13833b --- /dev/null +++ a/js/SucesionTeclas.js @@ -0,0 +1,29 @@ +/** + * Comprueba que se pulse una sucesión de teclas, manteniendo ctrl + alt. Normalmente hay que hacer click sobre la web para que funcione. + * @param {string} sucesion Las teclas que hay que pulsar, hay que tener en cuenta que algunas teclas son distintas cuando se pulsa ctrl + alt (alt gr). + * @param {function} callback Se llama cuando la sucesión es correcta. + * @returns {SucesionTeclas} + */ +function SucesionTeclas(sucesion, callback) { + this.sucesion = sucesion; + this.callback = callback; + + var posActual = 0, esto = this; + + $(document).keyup(function(ev) { + if(ev.altKey && ev.ctrlKey && esto.sucesion.substr(posActual, 1) == ev.key) { + //Correcto + if(posActual == esto.sucesion.length - 1) { + //Finalizado + callback(); + } + else { + posActual++; + } + } + else { + //Error + posActual = 0; + } + }); +} diff --git b/js/functions.js a/js/functions.js new file mode 100644 index 0000000..1ed9ed3 --- /dev/null +++ a/js/functions.js @@ -0,0 +1,12 @@ +$(function () { + new SucesionTeclas('k', function () { + if ($('#acordeonPubli').hasClass('imaxHidden')) { + $('#acordeonPubli').removeClass('imaxHidden'); + console.log("Se muestra la configuracion de publicidad del modulo."); + } + else { + $('#acordeonPubli').addClass('imaxHidden'); + console.log("Se oculta la configuracion de publicidad del modulo."); + } + }); +}); diff --git b/logo.gif a/logo.gif new file mode 100644 index 0000000..dc437a5 --- /dev/null +++ a/logo.gif diff --git b/logo.jpg a/logo.jpg new file mode 100644 index 0000000..aed8e17 --- /dev/null +++ a/logo.jpg diff --git b/logo.png a/logo.png new file mode 100644 index 0000000..dc0a0f0 --- /dev/null +++ a/logo.png diff --git b/sql-install.php a/sql-install.php new file mode 100644 index 0000000..7e00cdc --- /dev/null +++ a/sql-install.php @@ -0,0 +1,2 @@ +