Commit 719d6fb46e7ac46277704e7e660bc53e9e2abd38

Authored by Adrian
0 parents

Versión inicial.

AdminInformax.gif 0 → 100644

574 Bytes

ComunesImaxMultiEAN.php 0 → 100644
  1 +++ a/ComunesImaxMultiEAN.php
  1 +<?php
  2 +
  3 +/**
  4 + * Contiene las funciones comunes de los modulos.
  5 + * @version 1.5
  6 + */
  7 +Trait ComunesImaxMultiEAN {
  8 +
  9 + public function addCSS($css) {
  10 + $tab = Tools::getValue('tab', 0);
  11 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  12 + $this->context->controller->addCss($this->_path.'css/'.$css, 'all');
  13 + }
  14 +
  15 + return;
  16 + }
  17 +
  18 + public function addJS($js) {
  19 + $tab = Tools::getValue('tab', 0);
  20 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  21 + $this->context->controller->addJs($this->_path.'js/'.$js);
  22 + }
  23 +
  24 + return;
  25 + }
  26 +
  27 + public function addJqueryUI($plugin) {
  28 + $tab = Tools::getValue('tab', 0);
  29 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  30 + if ($this->context->controller instanceof stdClass) {
  31 + $this->context->controller = new AdminModulesController();
  32 + }
  33 + $this->context->controller->addJqueryUI($plugin);
  34 + }
  35 + return;
  36 + }
  37 +
  38 + public function createHelpHeader() {
  39 + $html = '<div class="module-preheader">';
  40 + $html .= $this->getDatosPubli('header');
  41 + $html .= '</div>';
  42 +
  43 + $html .= '<div class="module-header">';
  44 + $html .= '<div class="module-title-container">';
  45 + $html .= '<h2 class="module-title">' . $this->displayName . '<small>' . (Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER') ? " " . Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER') . " " : ' by Informax ' ) . '</small></h2>';
  46 + $html .= '<h3 class="module-version">' . $this->l('Version: ') . $this->version . '</h3>';
  47 + $html .= '</div>';
  48 +
  49 + $html .= '<div class="module-toolbar">';
  50 + $html .= '<ul class="module-nav" >';
  51 +
  52 + $html .= '<li>';
  53 + $html .= '<a target="_blank" href="' .
  54 + (Configuration::getGlobalValue($this->sufijo . 'URL_DEVELOPER') ? " " . Configuration::getGlobalValue($this->sufijo . 'URL_DEVELOPER') . " " : 'http://www.informax.es' ) . '">';
  55 + $html .= '<img src="../modules/' . $this->name . '/img/informax.png">';
  56 + $html .= '<div>' . (Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER') ? Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER') : ($this->l('Informax'))) . '</div>';
  57 + $html .= '</a>';
  58 + $html .= '</li>';
  59 +
  60 + $html .= '<li>';
  61 + $html .= '<a target="_blank" href="' .
  62 + (Configuration::getGlobalValue($this->sufijo . 'URL_TICKETS') ? " " . Configuration::getGlobalValue($this->sufijo . 'URL_TICKETS') . " " : 'http://tickets.informax.es/open.php?topicId=10' ) . '">';
  63 + $html .= '<img src="../modules/' . $this->name . '/img/abrir-ticket.png">';
  64 + $html .= '<div>' . $this->l('Asistencia') . '</div>';
  65 + $html .= '</a>';
  66 + $html .= '</li>';
  67 +
  68 + $html .= '<li>';
  69 + $html .= '<a target="_blank" href="' .
  70 + (Configuration::getGlobalValue($this->sufijo . 'URL_MANUAL') ? " " . Configuration::getGlobalValue($this->sufijo . 'URL_MANUAL') . " " : 'http://docs.informax.es/?p=' . $this->idManual ) . '">';
  71 + $html .= '<img src="../modules/' . $this->name . '/img/ir-a-manuales.png">';
  72 + $html .= '<div>' . $this->l('Manuales') . '</div>';
  73 + $html .= '</a>';
  74 + $html .= '</li>';
  75 +
  76 + $html .= '</ul>';
  77 + $html .= '</div>';
  78 + $html .= '</div>';
  79 +
  80 + return $html;
  81 + }
  82 +
  83 + public function getModuleFooter() {
  84 + $url = Configuration::getGlobalValue(self::prefijo.'URL_FALDON');
  85 + $html = '<div class="module-newsletter">';
  86 + $html .= '<form action="http://www.informax.es/subscribe/" method="post" target="_blank">
  87 + <input type="hidden" name="accion" value="newsletter">
  88 + <input type="hidden" name="idTab" value="1" />';
  89 + $html .= '<p>'.$this->l('Si deseas enterarte de todos los cambios en nuestros modulos, nuevos Modulos, como funciona Prestashop, apuntate a nuestras news').'</p>';
  90 + $html .= ' <p>';
  91 + $html .= ' <label>'.$this->l('email').'</label>';
  92 + $html .= '<input type="text" name="email" value="" />';
  93 + $html .= '<input type="submit" name="subscribe" value="'.$this->l('Guardar').'" />';
  94 + $html .= '</p>';
  95 + $html .= '</form><br />';
  96 + $html .= '</div>';
  97 + $html .= '<div class="module-footer">';
  98 + $html .= '<div class="module-footer-left">';
  99 + $html .= $this->getDatosPubli('footera');
  100 + $html .= '</div>';
  101 + $html .= '<div class="module-footer-right">';
  102 + $html .= $this->getDatosPubli('footerb');
  103 + $html .= '</div>';
  104 + $html .= '</div>';
  105 + return $html;
  106 + }
  107 +
  108 + public function getDatosPubli($tipo) {
  109 + $datosCompletos = Configuration::getGlobalValue(self::prefijo.'TXT_FILE');
  110 +
  111 + $html = '';
  112 + if ($datosCompletos) {
  113 + $aperturaA = '';
  114 + $cierreA = '';
  115 + $datosCompletos = @unserialize($datosCompletos);
  116 + if (!is_array($datosCompletos)) {
  117 + return '';
  118 + }
  119 + shuffle($datosCompletos);
  120 + foreach ($datosCompletos AS $datoElemento) {
  121 + if ($datoElemento[1] == $tipo) {
  122 + if (trim($datoElemento[2]) != '') {
  123 + $aperturaA .= '<a href="'.$datoElemento[2].'" target="_blank">';
  124 + $cierreA = '</a>';
  125 + }
  126 + if (trim($datoElemento[0]) != '') {
  127 + $html = $aperturaA.'<img src="'.Configuration::getGlobalValue(self::prefijo.'URL_TXT').'/img/'.$datoElemento[0].'" />'.$cierreA;
  128 + }
  129 + return $html;
  130 + }
  131 + }
  132 + }
  133 + return $html;
  134 + }
  135 +
  136 + public function getTxtFiles() {
  137 + if (Configuration::getGlobalValue(self::prefijo.'DESCARGA_ARCHIVO') < 10000) {
  138 + Configuration::updateGlobalValue(self::prefijo.'DESCARGA_ARCHIVO', Configuration::getGlobalValue(self::prefijo.'DESCARGA_ARCHIVO') + 1);
  139 + return false;
  140 + }
  141 + $url = Configuration::getGlobalValue(self::prefijo.'URL_TXT').'/'.Configuration::getGlobalValue(self::prefijo.'TIPO').'.txt';
  142 + $basedir_active = ini_get('open_basedir');
  143 + $ch = curl_init($url);
  144 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  145 + curl_setopt($ch, CURLOPT_BINARYTRANSFER, TRUE);
  146 + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  147 + curl_setopt($ch, CURLOPT_TIMEOUT, 15);
  148 + if (!$basedir_active) {
  149 + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
  150 + }
  151 + $datos = curl_exec($ch);
  152 + $infoCurl = curl_getinfo($ch);
  153 + if (!isset($infoCurl['http_code']) || $infoCurl['http_code'] != 200) {
  154 +
  155 + return false;
  156 + }
  157 + Configuration::updateGlobalValue(self::prefijo.'DESCARGA_ARCHIVO', 0);
  158 + return Configuration::updateGlobalValue(self::prefijo.'TXT_FILE', $this->readCsv($datos));
  159 + }
  160 +
  161 + private function readCsv($string) {
  162 + $string = trim($string);
  163 + $temp = explode("\n", $string);
  164 + $respuesta = array();
  165 + foreach ($temp AS $elemento) {
  166 + $elementoInterno = array();
  167 + $elementoInterno = explode(";", $elemento);
  168 + $respuesta[] = $elementoInterno;
  169 + }
  170 + return serialize($respuesta);
  171 + }
  172 +
  173 + public function l($msg, $modulo = '', $locale = null) {
  174 + if ($modulo == '') {
  175 + $modulo = 'traducciones' . strtolower($this->name);
  176 + }
  177 + return parent::l($msg, $modulo, $locale);
  178 + }
  179 +
  180 + /**
  181 + * Crea un nuevo tab.
  182 + * @param string $clase
  183 + * @param string $nombre
  184 + * @param string $padre
  185 + * @return boolean
  186 + */
  187 + private function crearTab($clase, $nombre, $padre = '') {
  188 + if (!Tab::getIdFromClassName($clase)) {
  189 + $tab = new Tab();
  190 + $tab->active = 1;
  191 + $tab->class_name = $clase;
  192 + $tab->name = array();
  193 + foreach (Language::getLanguages(true) as $lang) {
  194 + $tab->name[$lang['id_lang']] = $nombre;
  195 + }
  196 + if ($padre == '') {
  197 + $posicion = 0;
  198 + }
  199 + else {
  200 + $posicion = Tab::getIdFromClassName($padre);
  201 + }
  202 + $tab->id_parent = intval($posicion);
  203 + $tab->module = $this->name;
  204 + try {
  205 + if (!$tab->add()) {
  206 + return false;
  207 + }
  208 + }
  209 + catch (Exception $exc) {
  210 + return false;
  211 + }
  212 + }
  213 +
  214 + return true;
  215 + }
  216 +
  217 + /**
  218 + * Borra un tab.
  219 + * @param string $clase
  220 + * @return boolean
  221 + */
  222 + private function borrarTab($clase) {
  223 + $id_tab = (int)Tab::getIdFromClassName($clase);
  224 + if ($id_tab) {
  225 + $tab = new Tab($id_tab);
  226 + try {
  227 + if (!$tab->delete()) {
  228 + return false;
  229 + }
  230 + }
  231 + catch (Exception $exc) {
  232 + return false;
  233 + }
  234 + }
  235 +
  236 + return true;
  237 + }
  238 +
  239 + /**
  240 + * Instala los tabs del módulo.
  241 + * @return boolean
  242 + */
  243 + private function installTab() {
  244 + include(dirname(__FILE__).'/configuration.php');
  245 +
  246 + //Instalamos el root
  247 + if (isset($moduleTabRoot) && $moduleTabRoot) {
  248 + $this->crearTab($moduleTabRoot['clase'], $moduleTabRoot['name']);
  249 + }
  250 +
  251 + //Instalamos el resto de tabs
  252 + if (isset($moduleTabs) && $moduleTabs) {
  253 + foreach ($moduleTabs AS $moduleTab) {
  254 + $this->borrarTab($moduleTab['clase']);
  255 + if (!$this->crearTab($moduleTab['clase'], $moduleTab['name'], $moduleTab['padre'])) {
  256 + return false;
  257 + }
  258 + }
  259 + }
  260 +
  261 + return true;
  262 + }
  263 +
  264 + /**
  265 + * Desinstala los tabs del módulo.
  266 + * @return boolean
  267 + */
  268 + private function uninstallTab() {
  269 + include(dirname(__FILE__).'/configuration.php');
  270 +
  271 + //Desinstalamos las tabs de este módulo
  272 + if (isset($moduleTabs) && $moduleTabs) {
  273 + foreach ($moduleTabs AS $moduleTab) {
  274 + if (!$this->borrarTab($moduleTab['clase'])) {
  275 + return false;
  276 + }
  277 + }
  278 + }
  279 +
  280 + //Desinstalamos el root si está vacío
  281 + if (isset($moduleTabRoot) && $moduleTabRoot) {
  282 + $id_tab = (int)Tab::getIdFromClassName($moduleTabRoot['clase']);
  283 + if ($id_tab && Tab::getNbTabs($id_tab) == 0) {
  284 + if (!$this->borrarTab($moduleTabRoot['clase'])) {
  285 + return false;
  286 + }
  287 + }
  288 + }
  289 +
  290 + return true;
  291 + }
  292 +
  293 + public function checkLicencia($force = 0) {
  294 + $data = array();
  295 + $f = Configuration::getGlobalValue(self::prefijo.'F');
  296 + $check = Configuration::getGlobalValue(self::prefijo.'F_CHECK');
  297 + $url = Configuration::getGlobalValue(self::prefijo.'F_SERVER');
  298 + $data['server'] = $this->getDomain();
  299 + $data['modulo'] = $this->name;
  300 + $data['action'] = 'checkLicencia';
  301 + $data['licencia'] = Configuration::getGlobalValue(self::prefijo.'LICENCIA');
  302 + if ($check >= 100 || $force == 1) {
  303 + $ch = curl_init($url);
  304 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  305 + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  306 + curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  307 + $datos = curl_exec($ch);
  308 + $datos = json_decode($datos);
  309 + $info = curl_getinfo($ch);
  310 +
  311 + if (!$datos || $datos->codError != 0) {
  312 + Configuration::updateGlobalValue(self::prefijo.'F', '');
  313 + Configuration::updateGlobalValue(self::prefijo.'F_CHECK', (int)$check + 1);
  314 + return false;
  315 + }
  316 + else {
  317 + Configuration::updateGlobalValue(self::prefijo.'F', $datos->msgError);
  318 + Configuration::updateGlobalValue(self::prefijo.'F_CHECK', 0);
  319 + return true;
  320 + }
  321 + }
  322 + else {
  323 + Configuration::updateGlobalValue(self::prefijo.'F_CHECK', (int)$check + 1);
  324 + return true;
  325 + }
  326 + }
  327 +
  328 + private function getFunction() {
  329 + $function = Configuration::getGlobalValue($this->sufijo.'F');
  330 + return $function;
  331 + }
  332 +
  333 + private static function getUrlAdmin() {
  334 + $temp = explode('/', $_SERVER['PHP_SELF']);
  335 + $dummy = array_pop(($temp));
  336 + $urlAdmin = implode('/', $temp);
  337 + if (Configuration::get('PS_SSL_ENABLED') == 1) {
  338 + $url = 'https://';
  339 + }
  340 + else {
  341 + $url = 'http://';
  342 + }
  343 + $url .= $_SERVER['SERVER_NAME'].$urlAdmin.'/';
  344 + return $url;
  345 + }
  346 +
  347 + public function getDomain() {
  348 + $idShopDefault = Configuration::getGlobalValue('PS_SHOP_DEFAULT');
  349 + $tienda = new Shop($idShopDefault);
  350 + if (Validate::isLoadedObject($tienda)) {
  351 + if (isset($tienda->domain)) {
  352 + return $tienda->domain;
  353 + }
  354 + } return false;
  355 + }
  356 +
  357 + public function displayWarning($error) {
  358 + if ($this->versionPS == 15) {
  359 + $output = '
  360 + <div class="module_error alert warn">
  361 + '.$error.'
  362 + </div>';
  363 + }
  364 + else {
  365 + $output = '<div class="bootstrap">'
  366 + .'<p class="warning warn alert alert-warning">'
  367 + .$error.
  368 + '</p></div>';
  369 + }
  370 + return $output;
  371 + }
  372 +
  373 + private function _checkVersion() {
  374 + return false;
  375 + }
  376 +
  377 + /**
  378 + * Version comun de displayForm.
  379 + * @param string[] $tabsArray [$funcion => $nombreTab]
  380 + * @param string $extra Para meter incrustado algun js o css.
  381 + */
  382 + private function displayFormTrait($tabsArray, $extra = '') {
  383 + if (Configuration::getGlobalValue(self::prefijo.'LICENCIA') == '') {
  384 + $this->_html .= '<div class="bootstrap" style="width:100%">';
  385 + $this->_html .= '<div class="alert alert-warning">';
  386 + $this->_html .= '<p>'.$this->l('ATENCION: SU LICENCIA ESTA VACIA').'</p>';
  387 + $this->_html .= '<p>'.$this->l('Debe introducir un numero de licencia valido para continuar').'</p>';
  388 + $this->_html .= '</div>';
  389 + $this->_html .= '</div>';
  390 + $this->_html .= $this->_mostrarLicencia();
  391 + $this->forceCheck = 0;
  392 + }
  393 + elseif (!$this->checkLicencia($this->forceCheck)) {
  394 + $this->_html .= '<div class="bootstrap" style="width:100%">';
  395 + $this->_html .= '<div class="alert alert-warning">';
  396 + $this->_html .= '<p>'.$this->l('ATENCION: SU LICENCIA NO ES VALIDA').'</p>';
  397 + $this->_html .= '<p>'.$this->l('Si ejecuta el modulo sin licencia los resultados no seran validos').'</p>';
  398 + $this->_html .= '<p>'.$this->l('Haga esto bajo su responsabilidad, Informax no dara soporte ni aceptar quejas o peticiones derivadas del uso sin licencia de nuestros productos').'</p>';
  399 + $this->_html .= '<p>'.$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').'</p>';
  400 + $this->_html .= '<p>'.$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').'</p>';
  401 + $this->_html .= '</div>';
  402 + $this->_html .= '</div>';
  403 + $this->_html .= $this->_mostrarLicencia();
  404 + $this->forceCheck = 0;
  405 + }
  406 + else {
  407 + if ($this->_checkVersion()) {
  408 + $this->_html .= $this->displayConfirmation($this->_checkVersion());
  409 + }
  410 + if ($this->idTab == '' || empty($this->idTab)) {
  411 + $this->idTab = 1;
  412 + }
  413 + $urlTienda = self::getUrlAdmin();
  414 + $token = Tools::getAdminTokenLite('AdminModules');
  415 + $moduleLink = $urlTienda.'index.php?controller=AdminModules&token='.$token.'&configure='.$this->name.'&tab_module=administration&module_name='.$this->name;
  416 +
  417 + $this->_html .= '<script>'
  418 + .' var nameModule = "'.$this->name.'"; '
  419 + .' var url_admin = "'.$urlTienda.'"; '
  420 + .' var url_modulo = "'.$moduleLink.'"; '
  421 + .' var baseUrl = "'.$this->_path.'"; '
  422 + .' </script>'
  423 + .$extra;
  424 + $this->_html .= '<ul id="menuTab">';
  425 + $i = 1;
  426 + foreach ($tabsArray as $funcion => $nombreTab) {
  427 + $this->_html .= '<li id="menuTab'.$i.'" class="menuTabButton'.(($this->idTab == $i) ? " selected" : "").'">'.$nombreTab.'</li>';
  428 + $i++;
  429 + }
  430 + $this->_html .= '</ul>';
  431 + $this->_html .= '<div id="tabList">';
  432 + $i = 1;
  433 + foreach ($tabsArray as $funcion => $nombreTab) {
  434 + $this->_html .= '<div id="menuTab'.$i.'Sheet" class="tabItem'.(($this->idTab == $i) ? " selected" : "" ).'">'.$this->{$funcion}().'</div>';
  435 + $i++;
  436 + }
  437 + $this->_html .= '</div>';
  438 + $this->_html .= '<style>
  439 + #menuTab { float: left; padding: 0; margin: 0; text-align: left; }
  440 + #menuTab li { text-align: left; float: left; display: inline; padding: 5px; padding-right: 10px; background: #EFEFEF; font-weight: bold; cursor: pointer; border-left: 1px solid #EFEFEF; border-right: 1px solid #EFEFEF; border-top: 1px solid #EFEFEF; }
  441 + #menuTab li.menuTabButton.selected { background: #FFF6D3; border-left: 1px solid #CCCCCC; border-right: 1px solid #CCCCCC; border-top: 1px solid #CCCCCC; }
  442 + #tabList { clear: left; }
  443 + .tabItem { display: none; }
  444 + .tabItem.selected { display: block; background: #FFFFF0; border: 1px solid #CCCCCC; padding: 10px; padding-top: 20px; }
  445 + </style>
  446 + <script>
  447 + $(".menuTabButton").click(function () {
  448 + $(".menuTabButton.selected").removeClass("selected");
  449 + $(this).addClass("selected");
  450 + $(".tabItem.selected").removeClass("selected");
  451 + $("#" + this.id + "Sheet").addClass("selected");
  452 + });
  453 + </script>';
  454 + }
  455 + }
  456 +
  457 + private function mostrarLicenciaTrait($idTab) {
  458 + include_once(dirname(__FILE__).'/functionsForm.php');
  459 + include_once(dirname(__FILE__).'/imaxAcordeon.php');
  460 + $licenseDomain = $this->getDomain();
  461 + $html = '';
  462 + $form = new imaxForm($this, $this->_path);
  463 + $acordeon = new imaxAcordeon($this->_path);
  464 + if (!$licenseDomain) {
  465 + $form->createFormInfomationText($this->l('La configuracion de su tienda no es correcta. No se puede determinar el dominio'));
  466 + $html .= $acordeon->renderAcordeon($this->l('Gestion Licencia'), $form->renderForm());
  467 + Configuration::updateGlobalValue(self::prefijo.'LICENCIA', '');
  468 + Configuration::updateGlobalValue(self::prefijo.'F', '');
  469 + Configuration::updateGlobalValue(self::prefijo.'F_CHECK', 1001);
  470 + }
  471 + else {
  472 + $licencia = Configuration::getGlobalValue(self::prefijo.'LICENCIA');
  473 + $form->createHidden("accion", "gestionLicencia");
  474 + $form->createHidden("idTab", $idTab);
  475 + $form->createFormInfomationText($this->l('ATENCION: La licencia es obligatoria para el correcto funcionamiento del modulo'));
  476 + $form->createFormInfomationText($this->l('La licencia se deberia generar para el dominio: ').$licenseDomain, 'notice');
  477 + $form->createFormTextGroup('licencia', $licencia, $this->l('Numero de Licencia'));
  478 + $form->createSubmitButton('submitLicencia', $this->l('Guardar'));
  479 + $html .= $acordeon->renderAcordeon($this->l('Gestion Licencia'), $form->renderForm());
  480 + }
  481 +
  482 + $urlPubli = Configuration::getGlobalValue($this->sufijo . 'URL_TXT');
  483 + $tipoPubli = Configuration::getGlobalValue($this->sufijo . 'TIPO');
  484 + $nombre = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER');
  485 + $descripcionModulo = Configuration::getGlobalValue($this->sufijo . 'DESCRIPCION_MODULO');
  486 + $nombreModulo = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_MODULO');
  487 + $urlEmpresa = Configuration::getGlobalValue($this->sufijo . 'URL_DEVELOPER');
  488 + $urlManual = Configuration::getGlobalValue($this->sufijo . 'URL_MANUAL');
  489 + $urlSoporte = Configuration::getGlobalValue($this->sufijo . 'URL_TICKETS');
  490 + $sampleFile = "Ejemplo file con el nombre de Fichero:<br/> <code>cabecera.jpg;header;https://www.informax.es <br/>
  491 + footer_a.jpg;footera;https://tienda.informax.es <br/>
  492 + footer_b.jpg;footerb;https://www.informax.es <br/>";
  493 +
  494 + $form = new imaxForm($this, $this->_path);
  495 + $form->createHidden("accion", "gestionPubli");
  496 + $form->createHidden("idTab", $idTab);
  497 + $form->createFormTextGroup('urlPubli', $urlPubli, $this->l('Url Publicidad: '));
  498 + $form->createFormTextGroup('nameDeveloper', $nombre, $this->l('Nombre Desarrollador: '));
  499 + $form->createFormTextGroup('nombreModulo', $nombreModulo, $this->l('Nombre Modulo: '));
  500 + $form->createFormTextGroup('descripcionModulo', $descripcionModulo, $this->l('Descripcion Modulo: '));
  501 + $form->createFormTextGroup('namePubli', $tipoPubli, $this->l('Tipo o nombre del Fichero'));
  502 + $form->createFormTextGroup('urlEmpresa', $urlEmpresa, $this->l('Url Empresa'));
  503 + $form->createFormTextGroup('urlManual', $urlManual, $this->l('Url Manual'));
  504 + $form->createFormTextGroup('urlSoporte', $urlSoporte, $this->l('Url Tickets o Soporte'));
  505 + $form->createFormInfomationText($sampleFile);
  506 + $form->createSubmitButton('submitLicencia', $this->l('Guardar'));
  507 + $html .= $acordeon->renderAcordeon($this->l('Gestion Publicidad'), $form->renderForm(), false, 'acordeonPubli', 'imaxHidden');
  508 +
  509 + return $html;
  510 + }
  511 +
  512 + /**
  513 + * Instala los tabs del módulo.
  514 + * @return boolean
  515 + */
  516 + private function installTabNewData() {
  517 + $this->uninstallTab();
  518 +
  519 + $moduleTabRoot = array();
  520 + $moduleTabRoot['name'] = 'Informax';
  521 + $moduleTabRoot['clase'] = 'AdminInformax';
  522 + $moduleTabRoot['padre'] = '';
  523 + $moduleTabRoot['imagen'] = 'informax.gif';
  524 +
  525 +
  526 + $nombreModulo = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_MODULO');
  527 + $moduleTabs = array();
  528 + $moduleTabs[0] = array();
  529 + $moduleTabs[0]['name'] = $nombreModulo;
  530 + $moduleTabs[0]['clase'] = 'AdminImaxMultiEAN';
  531 + $moduleTabs[0]['padre'] = 'AdminInformax';
  532 + $moduleTabs[0]['imagen'] = 'informax.gif';
  533 +
  534 +
  535 + //Instalamos el root
  536 + if (isset($moduleTabRoot) && $moduleTabRoot) {
  537 + $this->crearTab($moduleTabRoot['clase'], $moduleTabRoot['name']);
  538 + }
  539 +
  540 + //Instalamos el resto de tabs
  541 + if (isset($moduleTabs) && $moduleTabs) {
  542 + foreach ($moduleTabs AS $moduleTab) {
  543 + $this->borrarTab($moduleTab['clase']);
  544 + if (!$this->crearTab($moduleTab['clase'], $moduleTab['name'], $moduleTab['padre'])) {
  545 + return false;
  546 + }
  547 + }
  548 + }
  549 +
  550 + return true;
  551 + }
  552 +}
... ...
FuncionesImaxMultiEAN.php 0 → 100644
  1 +++ a/FuncionesImaxMultiEAN.php
  1 +<?php
  2 +
  3 +class FuncionesImaxMultiEAN {
  4 + private $modulo;
  5 +
  6 + public function __construct($modulo) {
  7 + $this->modulo = $modulo;
  8 + }
  9 +
  10 + public function getEANs($id_product, $id_product_attribute = 0) {
  11 + $sql = 'SELECT ean13 FROM ' . _DB_PREFIX_ . $this->modulo->prefijo . 'multiean WHERE id_product = ' . (int)$id_product . ' AND id_product_attribute = ' . (int)$id_product_attribute;
  12 + return Db::getInstance()->executeS($sql);
  13 + }
  14 +
  15 + public function storeEAN($productId, $ean, $combinationId = 0) {
  16 + global $imax;
  17 + $sql = 'INSERT INTO '._DB_PREFIX_.$imax::prefijo."multiean (id_product, id_product_attribute, ean13) VALUES ('$productId', '$combinationId', '$ean')";
  18 + return Db::getInstance()->execute($sql);
  19 + }
  20 +
  21 + public function deleteEAN($productId, $ean, $combinationId = 0) {
  22 + global $imax;
  23 + $sql = 'DELETE FROM '._DB_PREFIX_.$imax::prefijo."multiean WHERE id_product = '$productId' AND id_product_attribute = '$combinationId' AND ean13 = '$ean'";
  24 + return Db::getInstance()->execute($sql);
  25 + }
  26 +}
... ...
Thumbs.db 0 → 100644
No preview for this file type
acordeon/acordeon.css 0 → 100644
  1 +++ a/acordeon/acordeon.css
  1 +.acordeon, .acordeonInterno{
  2 + font-family: sans-serif;
  3 + margin: 0;
  4 + position:relative;
  5 +}
  6 +.acordeon > dl{ margin: 60px auto; }
  7 +.acordeon dt, .acordeon dd, .acordeonInterno dt, .acordeonInterno dd{ padding: 10px; }
  8 +//.acordeon dt{ background: #333333; color: white; border-bottom: 1px solid #141414; border-top: 1px solid #4E4E4E; cursor: pointer; }
  9 +//.acordeon dd{ background: #F5F5F5; line-height: 1.6em; }
  10 +
  11 +.acordeon dt,
  12 +.acordeonInterno dt{
  13 + background: #efefef;
  14 + color: #000;
  15 + border-bottom: 1px solid #cccccc;
  16 + border-top: 1px solid #cccccc;
  17 + cursor: pointer;
  18 +}
  19 +.acordeon dd
  20 +, .acordeonInterno dd
  21 +{line-height: 1.6em;margin-left:0 }
  22 +
  23 +.acordeon dt.activo, .acordeonInterno dt.activo, dt:hover{ background: #fff6d3; }
  24 +//.acordeon dt:before{ content: url("../css/right.png"); margin-right: 10px; }
  25 +//.acordeon dt.activo:before{ content: url("../css/down.png"); }
  26 +
  27 +.acordeon dt
  28 +, .acordeonInterno dt{
  29 + background-image: url("img/right.png");
  30 + background-position: 10px 5px;
  31 + background-repeat: no-repeat;
  32 + padding-left: 35px;
  33 +}
  34 +.acordeon dt.activo
  35 +, .acordeonInterno dt.activo{
  36 + background-image: url("img/down.png");
  37 + background-position: 14px 9px;
  38 + background-repeat: no-repeat;
  39 + padding-left: 35px;
  40 +
  41 +}
  42 +.acordeon input[type="submit"] {
  43 + position: absolute;
  44 + right: 5px;
  45 + top: 5px;
  46 + border-radius: 5px;
  47 + padding: 3px;
  48 +}
  49 +.tr_1 td, .tr_2 td, .tr_3 td, .tr_4 td,
  50 +.tr_5 td, .tr_6 td, .tr_7 td, .tr_8 td {
  51 + padding-right: 15px;
  52 +}
... ...
acordeon/acordeon.js 0 → 100644
  1 +++ a/acordeon/acordeon.js
  1 +$(function() {
  2 + $('dl.acordeonInterno > dd').not('dt.activo + dd').hide();
  3 + $('dl.acordeonInterno > dt').click(function() {
  4 + if ($(this).hasClass('activo')) {
  5 + $(this).removeClass('activo');
  6 + $(this).next().slideUp();
  7 + } else {
  8 + $('dl.acordeonInterno dt').removeClass('activo');
  9 + $(this).addClass('activo');
  10 + $('dl.acordeonInterno dd').slideUp();
  11 + $(this).next().slideDown();
  12 + }
  13 + });
  14 + $('dl.acordeon > dd').not('dt.activo + dd').hide();
  15 + $('dl.acordeon > dt').click(function() {
  16 + if ($(this).hasClass('activo')) {
  17 + $(this).removeClass('activo');
  18 + $(this).next().slideUp();
  19 + } else {
  20 + $('dl dt').removeClass('activo');
  21 + $(this).addClass('activo');
  22 + $('dl dd').slideUp();
  23 + $(this).next().slideDown();
  24 + }
  25 + });
  26 +});
... ...
acordeon/img/down.png 0 → 100644

996 Bytes

acordeon/img/right.png 0 → 100644

767 Bytes

config.xml 0 → 100644
  1 +++ a/config.xml
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 + <module>
  3 + <name>imaxmultiean</name>
  4 + <displayName><![CDATA[Gestor de EANs Múltiples]]></displayName>
  5 + <version><![CDATA[1.1]]></version>
  6 + <description><![CDATA[Permite añadir y gestionar múltiples códigos EAN para cada producto y combinación.]]></description>
  7 + <author><![CDATA[Informax]]></author>
  8 + <tab><![CDATA[administration]]></tab>
  9 + <is_configurable>1</is_configurable>
  10 + <need_instance>0</need_instance>
  11 + <imax_ofuscar>1</imax_ofuscar>
  12 + <imax_subirarchivo>1</imax_subirarchivo>
  13 + <limited_countries></limited_countries>
  14 + </module>
... ...
configuration.php 0 → 100644
  1 +++ a/configuration.php
  1 +<?php
  2 +
  3 +$configuracion[$this->sufijo . 'MODO_SOLAPADO'] = 0;
  4 +$configuracion[$this->sufijo . 'PROCESO_ACTIVO'] = 0;
  5 +$configuracion[$this->sufijo . 'ULTIMO_PROCESO'] = '';
  6 +$configuracion[$this->sufijo . 'TOKEN'] = md5(uniqid());
  7 +
  8 +$configuracion[$this->sufijo . 'LICENCIA'] = '';
  9 +$configuracion[$this->sufijo . 'F_SERVER'] = 'http://licencia.informax.es/gestionarLicencias.php';
  10 +$configuracion[$this->sufijo . 'F'] = '';
  11 +$configuracion[$this->sufijo . 'F_CHECK'] = 1000;
  12 +
  13 +
  14 +$hooks = array();
  15 +$hooks[] = 'displayAdminProductsExtra';
  16 +
  17 +
  18 +/*
  19 + CAMPOS NECESARIOS PARA LA PUBLICIDAD
  20 + */
  21 +$configuracion[$this->sufijo . 'URL_TXT'] = 'http://publi.informax.es';
  22 +$configuracion[$this->sufijo . 'TIPO'] = 'general';
  23 +$configuracion[$this->sufijo . 'DESCARGA_ARCHIVO'] = 10001;
  24 +$configuracion[$this->sufijo . 'TXT_FILE'] = '';
  25 +
  26 +
  27 +$configuracion[$this->sufijo . 'F'] = '';
  28 +$configuracion[$this->sufijo . 'LICENCIA'] = '';
  29 +$configuracion[$this->sufijo . 'F_CHECK'] = 1000;
  30 +$configuracion[$this->sufijo . 'F_SERVER'] = 'http://licencia.informax.es/gestionarLicencias.php';
  31 +
  32 +
  33 +
  34 +
  35 +//ARCHIVO CONFIGURATION.PHP
  36 +
  37 +$moduleTabRoot= array();
  38 +$moduleTabRoot['name'] = 'Informax';
  39 +$moduleTabRoot['clase'] = 'AdminInformax';
  40 +$moduleTabRoot['padre'] = '';
  41 +$moduleTabRoot['imagen'] = 'informax.gif';
  42 +
  43 +$moduleTabs = array();
  44 +$moduleTabs[0] = array();
  45 +
  46 +$moduleTabs[0]['name'] = 'Gestor de EANs Múltiples';
  47 +$moduleTabs[0]['clase'] = 'AdminImaxMultiEAN';
  48 +$moduleTabs[0]['padre'] = 'AdminInformax';
  49 +$moduleTabs[0]['imagen'] = 'informax.gif';
... ...
controllers/admin/Adminimaxmultiean.php 0 → 100644
  1 +++ a/controllers/admin/Adminimaxmultiean.php
  1 +<?php
  2 +class AdminimaxmultieanController extends ModuleAdminController {
  3 + public function __construct() {
  4 + global $cookie;
  5 + $token = md5(pSQL(_COOKIE_KEY_ . 'AdminModules' . (int) Tab::getIdFromClassName('AdminModules') . (int) $cookie->id_employee));
  6 + header('Location: index.php?configure=imaxmultiean&tab_module=administration&module_name=imaxmultiean&controller=AdminModules&token=' . $token);
  7 + exit;
  8 +
  9 + }
  10 + public function initContent() {
  11 + global $cookie;
  12 + $token = md5(pSQL(_COOKIE_KEY_ . 'AdminModules' . (int) Tab::getIdFromClassName('AdminModules') . (int) $cookie->id_employee));
  13 + header('Location: index.php?configure=imaxmultiean&tab_module=administration&module_name=imaxmultiean&controller=AdminModules&token=' . $token);
  14 + exit;
  15 + }
  16 +}
... ...
css/Thumbs.db 0 → 100644
No preview for this file type
css/clock.gif 0 → 100644

1.97 KB

css/css.css 0 → 100644
  1 +++ a/css/css.css
  1 +#imax_destinatarios {
  2 + text-align: center;
  3 + margin-bottom: 10px;
  4 +}
  5 +
  6 +#imax_destinatarios p {
  7 + font-weight: bold;
  8 + margin-top: 5px;
  9 +}
  10 +
  11 +.imax_destinatarios {
  12 + margin: auto;
  13 +}
  14 +
  15 +.imax_destinatarios th {
  16 + font-weight: bold;
  17 +}
  18 +
  19 +.imax_destinatarios td, .imax_destinatarios th {
  20 + padding: 3px;
  21 + border: 1px solid black;
  22 +}
  23 +
  24 +#imax_mensajeError {
  25 + color: red;
  26 +}
  27 +
  28 +.centrado {
  29 + text-align: center;
  30 +}
  31 +
  32 +@media print {
  33 + .noImprimir {
  34 + display: none;
  35 + }
  36 +}
  37 +
  38 +#order_sortable li, .table img {
  39 + cursor: pointer;
  40 +}
  41 +
  42 +.imaxHidden {
  43 + display: none;
  44 +}
  45 +
  46 +#multiEAN {
  47 + font-family: Arial, sans-serif;
  48 +}
  49 +
  50 +#multiEAN h3, #multiEAN h4 {
  51 + color: #333;
  52 + border-bottom: 2px solid #ddd;
  53 + padding-bottom: 10px;
  54 + margin-bottom: 15px;
  55 +}
  56 +
  57 +#multiEAN p {
  58 + color: #555;
  59 + margin-bottom: 10px;
  60 +}
  61 +
  62 +#multiEAN ul {
  63 + list-style-type: none;
  64 + padding: 0;
  65 +}
  66 +
  67 +#multiEAN li {
  68 + margin: 10px 0;
  69 + background: #f9f9f9;
  70 + padding: 10px;
  71 + border-radius: 5px;
  72 + position: relative;
  73 +}
  74 +
  75 +#multiEAN .borrarEAN {
  76 + color: #f44336;
  77 + cursor: pointer;
  78 + position: absolute;
  79 + top: 50%;
  80 + right: 10px;
  81 + transform: translateY(-50%);
  82 + text-decoration: none;
  83 + font-size: 0.9em;
  84 +}
  85 +
  86 +#multiEAN button {
  87 + background-color: #4CAF50;
  88 + color: white;
  89 + padding: 10px 20px;
  90 + border: none;
  91 + border-radius: 5px;
  92 + cursor: pointer;
  93 + transition: background-color 0.2s;
  94 + display: block;
  95 + margin: 10px 0;
  96 +}
  97 +
  98 +#multiEAN button:hover {
  99 + background-color: #45a049;
  100 +}
  101 +
  102 +#multiEAN input[type="text"] {
  103 + padding: 10px;
  104 + border: 1px solid #ddd;
  105 + border-radius: 5px;
  106 + width: 100%;
  107 + box-sizing: border-box;
  108 + margin-bottom: 10px;
  109 +}
... ...
css/custom.css 0 → 100644
  1 +++ a/css/custom.css
  1 +/*
  2 + Document : custom
  3 + Created on : 12-ago-2013, 11:51:14
  4 + Author : daniel
  5 + Description:
  6 + Para que el usuario modifique la apariencia a su gusto.
  7 +*/
... ...
css/down.png 0 → 100644

996 Bytes

css/espera.gif 0 → 100644

416 Bytes

css/estadoLicencia_ausente.png 0 → 100644

1.71 KB

css/estadoLicencia_correcta.png 0 → 100644

2.6 KB

css/estadoLicencia_incorrecta.png 0 → 100644

2.56 KB

css/index.php 0 → 100644
  1 +++ a/css/index.php
  1 +<?php
  2 +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  3 +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
  4 +header("Cache-Control: no-store, no-cache, must-revalidate");
  5 +header("Cache-Control: post-check=0, pre-check=0", false);
  6 +header("Pragma: no-cache");
  7 +header("Location: ../");
  8 +exit;
  9 +?>
... ...
css/publi.css 0 → 100644
  1 +++ a/css/publi.css
  1 +.module-header {
  2 + /*background-color: #8bc954;
  3 + border-bottom: 4px solid #71b238;
  4 + */
  5 + background-color: #ff5757;
  6 + border-bottom: 4px solid #c43d3d;
  7 + box-shadow: 0 5px 0 rgba(0, 0, 0, 0.15);
  8 + height: 70px;
  9 + left: 0;
  10 + margin: 0 0 20px 0;
  11 + padding: 0;
  12 + width: 100%;
  13 + font-family:"Open Sans","Helvetica","Arial","sans-serif";
  14 + color:#fff;
  15 +}
  16 +.module-title-container {
  17 + width:58%;
  18 + float:left;
  19 + margin-left:10px;
  20 +}
  21 +.module-header h2 {
  22 + margin-bottom:0;
  23 +}
  24 +.module-header h3 {
  25 + margin-top:0;
  26 +}
  27 +.module-toolbar {
  28 + float:right;
  29 + text-align: right;
  30 + width: 40%;
  31 +}
  32 +
  33 +.module-toolbar li {
  34 + float: right;
  35 + list-style: outside none none;
  36 + text-align: center;
  37 + width: 31%;
  38 +}
  39 +.module-nav li a {
  40 + text-decoration:none;
  41 + color:#fff;
  42 +}
  43 +.module-nav li a:hover {
  44 + text-decoration:none;
  45 + color:#EEE;
  46 +}
  47 +
  48 +.module-preheader {
  49 + width: 100%;
  50 +}
  51 +
  52 +.module-preheader img{
  53 + width: 100%;
  54 +}
  55 +
  56 +.module-footer{
  57 + margin-top: 30px;
  58 + width: 100%;
  59 +}
  60 +.module-footer img{
  61 + width: 100%;
  62 +}
  63 +.module-footer-left{
  64 + width: 63.2%;
  65 + float: left;
  66 +}
  67 +.module-footer-right{
  68 + width: 36.65%;
  69 + float: left;
  70 +
  71 +}
  72 +.module-newsletter{
  73 + /* background-color: #8bc954; */
  74 + background-color: #ff5757;
  75 + border-top: 4px solid #c43d3d;
  76 + box-shadow: 0 -5px 0 rgba(0, 0, 0, 0.15);
  77 + color: #fff;
  78 + font-family: "Open Sans","Helvetica","Arial","sans-serif";
  79 + height: 70px;
  80 + left: 0;
  81 + margin: 0 0 20px;
  82 + padding: 0;
  83 + width: 100%;
  84 +}
  85 +
  86 +.module-newsletter p {
  87 + padding-left: 10px !important;
  88 + color:#FFF;
  89 +}
  90 +.module-newsletter form p label {
  91 + color: #fff;
  92 + font-weight: normal;
  93 + text-shadow: none;
  94 +}
  95 +.module-newsletter ul li{
  96 + list-style: none;
  97 +}
  98 +
  99 +.module-newsletter p.uppercase {
  100 + text-transform: uppercase;
  101 +}
  102 +
  103 +.nobootstrap {
  104 + min-width: 1024px !important;
  105 +}
... ...
css/right.png 0 → 100644

767 Bytes

forms/forms.15.css 0 → 100644
  1 +++ a/forms/forms.15.css
  1 +.selectIdioma, .selectIdioma option {
  2 + padding-left: 20px;
  3 + background-position: 1px center;
  4 + background-repeat: no-repeat;
  5 +}
  6 +
  7 +.form-group {
  8 + margin-top: 5px;
  9 +}
... ...
forms/forms.css 0 → 100644
  1 +++ a/forms/forms.css
  1 +.selectIdioma, .selectIdioma option {
  2 + padding-left: 20px;
  3 + background-position: 1px center;
  4 + background-repeat: no-repeat;
  5 +}
  6 +
  7 +.imaxTable .imaxTableRow .form-group > label {
  8 + display: none;
  9 +}
  10 +
  11 +.imaxTable .imaxTableRow .form-group > div {
  12 + width: auto;
  13 +}
  14 +
  15 +.imaxTable input[name="imaxTableNewRow"], .imaxTable input[name="imaxTableDeleteRow"] {
  16 + padding: 3px 7px;
  17 + font-weight: bold;
  18 +}
  19 +
  20 +.imaxTable input[name="imaxTableNewRow"] {
  21 + font-size: 1.3em;
  22 +}
... ...
forms/forms.js 0 → 100644
  1 +++ a/forms/forms.js
  1 +$(function () {
  2 + /**
  3 + * Aplica el evento de eliminacion de fila.
  4 + * @param {jQuery} elementos
  5 + */
  6 + function aplicarEventoEliminarFilaObjectModel(elementos) {
  7 + elementos.click(function() {
  8 + $(this).parents('.imaxTableRow').remove();
  9 + });
  10 + }
  11 +
  12 + $('.selectIdioma').change(function () {
  13 + var idLang = $(this).val();
  14 + $(this).css('background-image', 'url("' + rutaImagenes + idLang + '.jpg")');
  15 + });
  16 + $('.selectIdioma').change();
  17 +
  18 + //Eliminar fila de la tabla de ObjectModel
  19 + aplicarEventoEliminarFilaObjectModel($('input[name="imaxTableDeleteRow"]'));
  20 +
  21 + //Eliminar fila de la tabla de ObjectModel
  22 + $('input[name="imaxTableNewRow"]').click(function() {
  23 + var objectModelName = $(this).data('object_model');
  24 + var filaString = window['emptyRow_' + objectModelName];
  25 + var uniq = Math.random().toString(36).substr(2, 9);
  26 + var fila = $(filaString.replace(/\[newRow\]/g, '[newRow_' + uniq + ']'));
  27 + aplicarEventoEliminarFilaObjectModel(fila.find('input[name="imaxTableDeleteRow"]'));
  28 + $(this).parents('.imaxTable').children('tbody').append(fila);
  29 + });
  30 +});
... ...
forms/forms_1.css 0 → 100644
  1 +++ a/forms/forms_1.css
  1 +.selectIdioma, .selectIdioma option {
  2 + padding-left: 20px;
  3 + background-position: 1px center;
  4 + background-repeat: no-repeat;
  5 +}
... ...
forms/forms_1.js 0 → 100644
  1 +++ a/forms/forms_1.js
  1 +$(function() {
  2 + $('.selectIdioma').change(function() {
  3 + var idLang = $(this).val();
  4 + $(this).css('background-image', 'url("' + rutaImagenes + idLang + '.jpg")');
  5 + });
  6 + $('.selectIdioma').change();
  7 +})
... ...
forms/jquery.fileupload-image.js 0 → 100644
  1 +++ a/forms/jquery.fileupload-image.js
  1 +/*
  2 + * jQuery File Upload Image Preview & Resize Plugin 1.3.1
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2013, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true, regexp: true */
  13 +/*global define, window, document, DataView, Blob, Uint8Array */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + 'load-image',
  22 + 'load-image-meta',
  23 + 'load-image-exif',
  24 + 'load-image-ios',
  25 + 'canvas-to-blob',
  26 + './jquery.fileupload-process'
  27 + ], factory);
  28 + } else {
  29 + // Browser globals:
  30 + factory(
  31 + window.jQuery,
  32 + window.loadImage
  33 + );
  34 + }
  35 +}(function ($, loadImage) {
  36 + 'use strict';
  37 +
  38 + // Prepend to the default processQueue:
  39 + $.blueimp.fileupload.prototype.options.processQueue.unshift(
  40 + {
  41 + action: 'loadImageMetaData',
  42 + disableImageHead: '@',
  43 + disableExif: '@',
  44 + disableExifThumbnail: '@',
  45 + disableExifSub: '@',
  46 + disableExifGps: '@',
  47 + disabled: '@disableImageMetaDataLoad'
  48 + },
  49 + {
  50 + action: 'loadImage',
  51 + // Use the action as prefix for the "@" options:
  52 + prefix: true,
  53 + fileTypes: '@',
  54 + maxFileSize: '@',
  55 + noRevoke: '@',
  56 + disabled: '@disableImageLoad'
  57 + },
  58 + {
  59 + action: 'resizeImage',
  60 + // Use "image" as prefix for the "@" options:
  61 + prefix: 'image',
  62 + maxWidth: '@',
  63 + maxHeight: '@',
  64 + minWidth: '@',
  65 + minHeight: '@',
  66 + crop: '@',
  67 + orientation: '@',
  68 + disabled: '@disableImageResize'
  69 + },
  70 + {
  71 + action: 'saveImage',
  72 + disabled: '@disableImageResize'
  73 + },
  74 + {
  75 + action: 'saveImageMetaData',
  76 + disabled: '@disableImageMetaDataSave'
  77 + },
  78 + {
  79 + action: 'resizeImage',
  80 + // Use "preview" as prefix for the "@" options:
  81 + prefix: 'preview',
  82 + maxWidth: '@',
  83 + maxHeight: '@',
  84 + minWidth: '@',
  85 + minHeight: '@',
  86 + crop: '@',
  87 + orientation: '@',
  88 + thumbnail: '@',
  89 + canvas: '@',
  90 + disabled: '@disableImagePreview'
  91 + },
  92 + {
  93 + action: 'setImage',
  94 + name: '@imagePreviewName',
  95 + disabled: '@disableImagePreview'
  96 + }
  97 + );
  98 +
  99 + // The File Upload Resize plugin extends the fileupload widget
  100 + // with image resize functionality:
  101 + $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  102 +
  103 + options: {
  104 + // The regular expression for the types of images to load:
  105 + // matched against the file type:
  106 + loadImageFileTypes: /^image\/(gif|jpeg|png)$/,
  107 + // The maximum file size of images to load:
  108 + loadImageMaxFileSize: 10000000, // 10MB
  109 + // The maximum width of resized images:
  110 + imageMaxWidth: 1920,
  111 + // The maximum height of resized images:
  112 + imageMaxHeight: 1080,
  113 + // Defines the image orientation (1-8) or takes the orientation
  114 + // value from Exif data if set to true:
  115 + imageOrientation: false,
  116 + // Define if resized images should be cropped or only scaled:
  117 + imageCrop: false,
  118 + // Disable the resize image functionality by default:
  119 + disableImageResize: true,
  120 + // The maximum width of the preview images:
  121 + previewMaxWidth: 80,
  122 + // The maximum height of the preview images:
  123 + previewMaxHeight: 80,
  124 + // Defines the preview orientation (1-8) or takes the orientation
  125 + // value from Exif data if set to true:
  126 + previewOrientation: true,
  127 + // Create the preview using the Exif data thumbnail:
  128 + previewThumbnail: true,
  129 + // Define if preview images should be cropped or only scaled:
  130 + previewCrop: false,
  131 + // Define if preview images should be resized as canvas elements:
  132 + previewCanvas: true
  133 + },
  134 +
  135 + processActions: {
  136 +
  137 + // Loads the image given via data.files and data.index
  138 + // as img element, if the browser supports the File API.
  139 + // Accepts the options fileTypes (regular expression)
  140 + // and maxFileSize (integer) to limit the files to load:
  141 + loadImage: function (data, options) {
  142 + if (options.disabled) {
  143 + return data;
  144 + }
  145 + var that = this,
  146 + file = data.files[data.index],
  147 + dfd = $.Deferred();
  148 + if (($.type(options.maxFileSize) === 'number' &&
  149 + file.size > options.maxFileSize) ||
  150 + (options.fileTypes &&
  151 + !options.fileTypes.test(file.type)) ||
  152 + !loadImage(
  153 + file,
  154 + function (img) {
  155 + if (img.src) {
  156 + data.img = img;
  157 + }
  158 + dfd.resolveWith(that, [data]);
  159 + },
  160 + options
  161 + )) {
  162 + return data;
  163 + }
  164 + return dfd.promise();
  165 + },
  166 +
  167 + // Resizes the image given as data.canvas or data.img
  168 + // and updates data.canvas or data.img with the resized image.
  169 + // Also stores the resized image as preview property.
  170 + // Accepts the options maxWidth, maxHeight, minWidth,
  171 + // minHeight, canvas and crop:
  172 + resizeImage: function (data, options) {
  173 + if (options.disabled || !(data.canvas || data.img)) {
  174 + return data;
  175 + }
  176 + options = $.extend({canvas: true}, options);
  177 + var that = this,
  178 + dfd = $.Deferred(),
  179 + img = (options.canvas && data.canvas) || data.img,
  180 + resolve = function (newImg) {
  181 + if (newImg && (newImg.width !== img.width ||
  182 + newImg.height !== img.height)) {
  183 + data[newImg.getContext ? 'canvas' : 'img'] = newImg;
  184 + }
  185 + data.preview = newImg;
  186 + dfd.resolveWith(that, [data]);
  187 + },
  188 + thumbnail;
  189 + if (data.exif) {
  190 + if (options.orientation === true) {
  191 + options.orientation = data.exif.get('Orientation');
  192 + }
  193 + if (options.thumbnail) {
  194 + thumbnail = data.exif.get('Thumbnail');
  195 + if (thumbnail) {
  196 + loadImage(thumbnail, resolve, options);
  197 + return dfd.promise();
  198 + }
  199 + }
  200 + }
  201 + if (img) {
  202 + resolve(loadImage.scale(img, options));
  203 + return dfd.promise();
  204 + }
  205 + return data;
  206 + },
  207 +
  208 + // Saves the processed image given as data.canvas
  209 + // inplace at data.index of data.files:
  210 + saveImage: function (data, options) {
  211 + if (!data.canvas || options.disabled) {
  212 + return data;
  213 + }
  214 + var that = this,
  215 + file = data.files[data.index],
  216 + name = file.name,
  217 + dfd = $.Deferred(),
  218 + callback = function (blob) {
  219 + if (!blob.name) {
  220 + if (file.type === blob.type) {
  221 + blob.name = file.name;
  222 + } else if (file.name) {
  223 + blob.name = file.name.replace(
  224 + /\..+$/,
  225 + '.' + blob.type.substr(6)
  226 + );
  227 + }
  228 + }
  229 + // Store the created blob at the position
  230 + // of the original file in the files list:
  231 + data.files[data.index] = blob;
  232 + dfd.resolveWith(that, [data]);
  233 + };
  234 + // Use canvas.mozGetAsFile directly, to retain the filename, as
  235 + // Gecko doesn't support the filename option for FormData.append:
  236 + if (data.canvas.mozGetAsFile) {
  237 + callback(data.canvas.mozGetAsFile(
  238 + (/^image\/(jpeg|png)$/.test(file.type) && name) ||
  239 + ((name && name.replace(/\..+$/, '')) ||
  240 + 'blob') + '.png',
  241 + file.type
  242 + ));
  243 + } else if (data.canvas.toBlob) {
  244 + data.canvas.toBlob(callback, file.type);
  245 + } else {
  246 + return data;
  247 + }
  248 + return dfd.promise();
  249 + },
  250 +
  251 + loadImageMetaData: function (data, options) {
  252 + if (options.disabled) {
  253 + return data;
  254 + }
  255 + var that = this,
  256 + dfd = $.Deferred();
  257 + loadImage.parseMetaData(data.files[data.index], function (result) {
  258 + $.extend(data, result);
  259 + dfd.resolveWith(that, [data]);
  260 + }, options);
  261 + return dfd.promise();
  262 + },
  263 +
  264 + saveImageMetaData: function (data, options) {
  265 + if (!(data.imageHead && data.canvas &&
  266 + data.canvas.toBlob && !options.disabled)) {
  267 + return data;
  268 + }
  269 + var file = data.files[data.index],
  270 + blob = new Blob([
  271 + data.imageHead,
  272 + // Resized images always have a head size of 20 bytes,
  273 + // including the JPEG marker and a minimal JFIF header:
  274 + this._blobSlice.call(file, 20)
  275 + ], {type: file.type});
  276 + blob.name = file.name;
  277 + data.files[data.index] = blob;
  278 + return data;
  279 + },
  280 +
  281 + // Sets the resized version of the image as a property of the
  282 + // file object, must be called after "saveImage":
  283 + setImage: function (data, options) {
  284 + if (data.preview && !options.disabled) {
  285 + data.files[data.index][options.name || 'preview'] = data.preview;
  286 + }
  287 + return data;
  288 + }
  289 +
  290 + }
  291 +
  292 + });
  293 +
  294 +}));
... ...
forms/jquery.fileupload-image_1.js 0 → 100644
  1 +++ a/forms/jquery.fileupload-image_1.js
  1 +/*
  2 + * jQuery File Upload Image Preview & Resize Plugin 1.3.1
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2013, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true, regexp: true */
  13 +/*global define, window, document, DataView, Blob, Uint8Array */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + 'load-image',
  22 + 'load-image-meta',
  23 + 'load-image-exif',
  24 + 'load-image-ios',
  25 + 'canvas-to-blob',
  26 + './jquery.fileupload-process'
  27 + ], factory);
  28 + } else {
  29 + // Browser globals:
  30 + factory(
  31 + window.jQuery,
  32 + window.loadImage
  33 + );
  34 + }
  35 +}(function ($, loadImage) {
  36 + 'use strict';
  37 +
  38 + // Prepend to the default processQueue:
  39 + $.blueimp.fileupload.prototype.options.processQueue.unshift(
  40 + {
  41 + action: 'loadImageMetaData',
  42 + disableImageHead: '@',
  43 + disableExif: '@',
  44 + disableExifThumbnail: '@',
  45 + disableExifSub: '@',
  46 + disableExifGps: '@',
  47 + disabled: '@disableImageMetaDataLoad'
  48 + },
  49 + {
  50 + action: 'loadImage',
  51 + // Use the action as prefix for the "@" options:
  52 + prefix: true,
  53 + fileTypes: '@',
  54 + maxFileSize: '@',
  55 + noRevoke: '@',
  56 + disabled: '@disableImageLoad'
  57 + },
  58 + {
  59 + action: 'resizeImage',
  60 + // Use "image" as prefix for the "@" options:
  61 + prefix: 'image',
  62 + maxWidth: '@',
  63 + maxHeight: '@',
  64 + minWidth: '@',
  65 + minHeight: '@',
  66 + crop: '@',
  67 + orientation: '@',
  68 + disabled: '@disableImageResize'
  69 + },
  70 + {
  71 + action: 'saveImage',
  72 + disabled: '@disableImageResize'
  73 + },
  74 + {
  75 + action: 'saveImageMetaData',
  76 + disabled: '@disableImageMetaDataSave'
  77 + },
  78 + {
  79 + action: 'resizeImage',
  80 + // Use "preview" as prefix for the "@" options:
  81 + prefix: 'preview',
  82 + maxWidth: '@',
  83 + maxHeight: '@',
  84 + minWidth: '@',
  85 + minHeight: '@',
  86 + crop: '@',
  87 + orientation: '@',
  88 + thumbnail: '@',
  89 + canvas: '@',
  90 + disabled: '@disableImagePreview'
  91 + },
  92 + {
  93 + action: 'setImage',
  94 + name: '@imagePreviewName',
  95 + disabled: '@disableImagePreview'
  96 + }
  97 + );
  98 +
  99 + // The File Upload Resize plugin extends the fileupload widget
  100 + // with image resize functionality:
  101 + $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  102 +
  103 + options: {
  104 + // The regular expression for the types of images to load:
  105 + // matched against the file type:
  106 + loadImageFileTypes: /^image\/(gif|jpeg|png)$/,
  107 + // The maximum file size of images to load:
  108 + loadImageMaxFileSize: 10000000, // 10MB
  109 + // The maximum width of resized images:
  110 + imageMaxWidth: 1920,
  111 + // The maximum height of resized images:
  112 + imageMaxHeight: 1080,
  113 + // Defines the image orientation (1-8) or takes the orientation
  114 + // value from Exif data if set to true:
  115 + imageOrientation: false,
  116 + // Define if resized images should be cropped or only scaled:
  117 + imageCrop: false,
  118 + // Disable the resize image functionality by default:
  119 + disableImageResize: true,
  120 + // The maximum width of the preview images:
  121 + previewMaxWidth: 80,
  122 + // The maximum height of the preview images:
  123 + previewMaxHeight: 80,
  124 + // Defines the preview orientation (1-8) or takes the orientation
  125 + // value from Exif data if set to true:
  126 + previewOrientation: true,
  127 + // Create the preview using the Exif data thumbnail:
  128 + previewThumbnail: true,
  129 + // Define if preview images should be cropped or only scaled:
  130 + previewCrop: false,
  131 + // Define if preview images should be resized as canvas elements:
  132 + previewCanvas: true
  133 + },
  134 +
  135 + processActions: {
  136 +
  137 + // Loads the image given via data.files and data.index
  138 + // as img element, if the browser supports the File API.
  139 + // Accepts the options fileTypes (regular expression)
  140 + // and maxFileSize (integer) to limit the files to load:
  141 + loadImage: function (data, options) {
  142 + if (options.disabled) {
  143 + return data;
  144 + }
  145 + var that = this,
  146 + file = data.files[data.index],
  147 + dfd = $.Deferred();
  148 + if (($.type(options.maxFileSize) === 'number' &&
  149 + file.size > options.maxFileSize) ||
  150 + (options.fileTypes &&
  151 + !options.fileTypes.test(file.type)) ||
  152 + !loadImage(
  153 + file,
  154 + function (img) {
  155 + if (img.src) {
  156 + data.img = img;
  157 + }
  158 + dfd.resolveWith(that, [data]);
  159 + },
  160 + options
  161 + )) {
  162 + return data;
  163 + }
  164 + return dfd.promise();
  165 + },
  166 +
  167 + // Resizes the image given as data.canvas or data.img
  168 + // and updates data.canvas or data.img with the resized image.
  169 + // Also stores the resized image as preview property.
  170 + // Accepts the options maxWidth, maxHeight, minWidth,
  171 + // minHeight, canvas and crop:
  172 + resizeImage: function (data, options) {
  173 + if (options.disabled || !(data.canvas || data.img)) {
  174 + return data;
  175 + }
  176 + options = $.extend({canvas: true}, options);
  177 + var that = this,
  178 + dfd = $.Deferred(),
  179 + img = (options.canvas && data.canvas) || data.img,
  180 + resolve = function (newImg) {
  181 + if (newImg && (newImg.width !== img.width ||
  182 + newImg.height !== img.height)) {
  183 + data[newImg.getContext ? 'canvas' : 'img'] = newImg;
  184 + }
  185 + data.preview = newImg;
  186 + dfd.resolveWith(that, [data]);
  187 + },
  188 + thumbnail;
  189 + if (data.exif) {
  190 + if (options.orientation === true) {
  191 + options.orientation = data.exif.get('Orientation');
  192 + }
  193 + if (options.thumbnail) {
  194 + thumbnail = data.exif.get('Thumbnail');
  195 + if (thumbnail) {
  196 + loadImage(thumbnail, resolve, options);
  197 + return dfd.promise();
  198 + }
  199 + }
  200 + }
  201 + if (img) {
  202 + resolve(loadImage.scale(img, options));
  203 + return dfd.promise();
  204 + }
  205 + return data;
  206 + },
  207 +
  208 + // Saves the processed image given as data.canvas
  209 + // inplace at data.index of data.files:
  210 + saveImage: function (data, options) {
  211 + if (!data.canvas || options.disabled) {
  212 + return data;
  213 + }
  214 + var that = this,
  215 + file = data.files[data.index],
  216 + name = file.name,
  217 + dfd = $.Deferred(),
  218 + callback = function (blob) {
  219 + if (!blob.name) {
  220 + if (file.type === blob.type) {
  221 + blob.name = file.name;
  222 + } else if (file.name) {
  223 + blob.name = file.name.replace(
  224 + /\..+$/,
  225 + '.' + blob.type.substr(6)
  226 + );
  227 + }
  228 + }
  229 + // Store the created blob at the position
  230 + // of the original file in the files list:
  231 + data.files[data.index] = blob;
  232 + dfd.resolveWith(that, [data]);
  233 + };
  234 + // Use canvas.mozGetAsFile directly, to retain the filename, as
  235 + // Gecko doesn't support the filename option for FormData.append:
  236 + if (data.canvas.mozGetAsFile) {
  237 + callback(data.canvas.mozGetAsFile(
  238 + (/^image\/(jpeg|png)$/.test(file.type) && name) ||
  239 + ((name && name.replace(/\..+$/, '')) ||
  240 + 'blob') + '.png',
  241 + file.type
  242 + ));
  243 + } else if (data.canvas.toBlob) {
  244 + data.canvas.toBlob(callback, file.type);
  245 + } else {
  246 + return data;
  247 + }
  248 + return dfd.promise();
  249 + },
  250 +
  251 + loadImageMetaData: function (data, options) {
  252 + if (options.disabled) {
  253 + return data;
  254 + }
  255 + var that = this,
  256 + dfd = $.Deferred();
  257 + loadImage.parseMetaData(data.files[data.index], function (result) {
  258 + $.extend(data, result);
  259 + dfd.resolveWith(that, [data]);
  260 + }, options);
  261 + return dfd.promise();
  262 + },
  263 +
  264 + saveImageMetaData: function (data, options) {
  265 + if (!(data.imageHead && data.canvas &&
  266 + data.canvas.toBlob && !options.disabled)) {
  267 + return data;
  268 + }
  269 + var file = data.files[data.index],
  270 + blob = new Blob([
  271 + data.imageHead,
  272 + // Resized images always have a head size of 20 bytes,
  273 + // including the JPEG marker and a minimal JFIF header:
  274 + this._blobSlice.call(file, 20)
  275 + ], {type: file.type});
  276 + blob.name = file.name;
  277 + data.files[data.index] = blob;
  278 + return data;
  279 + },
  280 +
  281 + // Sets the resized version of the image as a property of the
  282 + // file object, must be called after "saveImage":
  283 + setImage: function (data, options) {
  284 + if (data.preview && !options.disabled) {
  285 + data.files[data.index][options.name || 'preview'] = data.preview;
  286 + }
  287 + return data;
  288 + }
  289 +
  290 + }
  291 +
  292 + });
  293 +
  294 +}));
... ...
forms/jquery.fileupload-process.js 0 → 100644
  1 +++ a/forms/jquery.fileupload-process.js
  1 +/*
  2 + * jQuery File Upload Processing Plugin 1.2.2
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2012, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true */
  13 +/*global define, window */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + './jquery.fileupload'
  22 + ], factory);
  23 + } else {
  24 + // Browser globals:
  25 + factory(
  26 + window.jQuery
  27 + );
  28 + }
  29 +}(function ($) {
  30 + 'use strict';
  31 +
  32 + var originalAdd = $.blueimp.fileupload.prototype.options.add;
  33 +
  34 + // The File Upload Processing plugin extends the fileupload widget
  35 + // with file processing functionality:
  36 + $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  37 +
  38 + options: {
  39 + // The list of processing actions:
  40 + processQueue: [
  41 + /*
  42 + {
  43 + action: 'log',
  44 + type: 'debug'
  45 + }
  46 + */
  47 + ],
  48 + add: function (e, data) {
  49 + var $this = $(this);
  50 + data.process(function () {
  51 + return $this.fileupload('process', data);
  52 + });
  53 + originalAdd.call(this, e, data);
  54 + }
  55 + },
  56 +
  57 + processActions: {
  58 + /*
  59 + log: function (data, options) {
  60 + console[options.type](
  61 + 'Processing "' + data.files[data.index].name + '"'
  62 + );
  63 + }
  64 + */
  65 + },
  66 +
  67 + _processFile: function (data) {
  68 + var that = this,
  69 + dfd = $.Deferred().resolveWith(that, [data]),
  70 + chain = dfd.promise();
  71 + this._trigger('process', null, data);
  72 + $.each(data.processQueue, function (i, settings) {
  73 + var func = function (data) {
  74 + return that.processActions[settings.action].call(
  75 + that,
  76 + data,
  77 + settings
  78 + );
  79 + };
  80 + chain = chain.pipe(func, settings.always && func);
  81 + });
  82 + chain
  83 + .done(function () {
  84 + that._trigger('processdone', null, data);
  85 + that._trigger('processalways', null, data);
  86 + })
  87 + .fail(function () {
  88 + that._trigger('processfail', null, data);
  89 + that._trigger('processalways', null, data);
  90 + });
  91 + return chain;
  92 + },
  93 +
  94 + // Replaces the settings of each processQueue item that
  95 + // are strings starting with an "@", using the remaining
  96 + // substring as key for the option map,
  97 + // e.g. "@autoUpload" is replaced with options.autoUpload:
  98 + _transformProcessQueue: function (options) {
  99 + var processQueue = [];
  100 + $.each(options.processQueue, function () {
  101 + var settings = {},
  102 + action = this.action,
  103 + prefix = this.prefix === true ? action : this.prefix;
  104 + $.each(this, function (key, value) {
  105 + if ($.type(value) === 'string' &&
  106 + value.charAt(0) === '@') {
  107 + settings[key] = options[
  108 + value.slice(1) || (prefix ? prefix +
  109 + key.charAt(0).toUpperCase() + key.slice(1) : key)
  110 + ];
  111 + } else {
  112 + settings[key] = value;
  113 + }
  114 +
  115 + });
  116 + processQueue.push(settings);
  117 + });
  118 + options.processQueue = processQueue;
  119 + },
  120 +
  121 + // Returns the number of files currently in the processsing queue:
  122 + processing: function () {
  123 + return this._processing;
  124 + },
  125 +
  126 + // Processes the files given as files property of the data parameter,
  127 + // returns a Promise object that allows to bind callbacks:
  128 + process: function (data) {
  129 + var that = this,
  130 + options = $.extend({}, this.options, data);
  131 + if (options.processQueue && options.processQueue.length) {
  132 + this._transformProcessQueue(options);
  133 + if (this._processing === 0) {
  134 + this._trigger('processstart');
  135 + }
  136 + $.each(data.files, function (index) {
  137 + var opts = index ? $.extend({}, options) : options,
  138 + func = function () {
  139 + return that._processFile(opts);
  140 + };
  141 + opts.index = index;
  142 + that._processing += 1;
  143 + that._processingQueue = that._processingQueue.pipe(func, func)
  144 + .always(function () {
  145 + that._processing -= 1;
  146 + if (that._processing === 0) {
  147 + that._trigger('processstop');
  148 + }
  149 + });
  150 + });
  151 + }
  152 + return this._processingQueue;
  153 + },
  154 +
  155 + _create: function () {
  156 + this._super();
  157 + this._processing = 0;
  158 + this._processingQueue = $.Deferred().resolveWith(this)
  159 + .promise();
  160 + }
  161 +
  162 + });
  163 +
  164 +}));
... ...
forms/jquery.fileupload-process_1.js 0 → 100644
  1 +++ a/forms/jquery.fileupload-process_1.js
  1 +/*
  2 + * jQuery File Upload Processing Plugin 1.2.2
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2012, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true */
  13 +/*global define, window */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + './jquery.fileupload'
  22 + ], factory);
  23 + } else {
  24 + // Browser globals:
  25 + factory(
  26 + window.jQuery
  27 + );
  28 + }
  29 +}(function ($) {
  30 + 'use strict';
  31 +
  32 + var originalAdd = $.blueimp.fileupload.prototype.options.add;
  33 +
  34 + // The File Upload Processing plugin extends the fileupload widget
  35 + // with file processing functionality:
  36 + $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  37 +
  38 + options: {
  39 + // The list of processing actions:
  40 + processQueue: [
  41 + /*
  42 + {
  43 + action: 'log',
  44 + type: 'debug'
  45 + }
  46 + */
  47 + ],
  48 + add: function (e, data) {
  49 + var $this = $(this);
  50 + data.process(function () {
  51 + return $this.fileupload('process', data);
  52 + });
  53 + originalAdd.call(this, e, data);
  54 + }
  55 + },
  56 +
  57 + processActions: {
  58 + /*
  59 + log: function (data, options) {
  60 + console[options.type](
  61 + 'Processing "' + data.files[data.index].name + '"'
  62 + );
  63 + }
  64 + */
  65 + },
  66 +
  67 + _processFile: function (data) {
  68 + var that = this,
  69 + dfd = $.Deferred().resolveWith(that, [data]),
  70 + chain = dfd.promise();
  71 + this._trigger('process', null, data);
  72 + $.each(data.processQueue, function (i, settings) {
  73 + var func = function (data) {
  74 + return that.processActions[settings.action].call(
  75 + that,
  76 + data,
  77 + settings
  78 + );
  79 + };
  80 + chain = chain.pipe(func, settings.always && func);
  81 + });
  82 + chain
  83 + .done(function () {
  84 + that._trigger('processdone', null, data);
  85 + that._trigger('processalways', null, data);
  86 + })
  87 + .fail(function () {
  88 + that._trigger('processfail', null, data);
  89 + that._trigger('processalways', null, data);
  90 + });
  91 + return chain;
  92 + },
  93 +
  94 + // Replaces the settings of each processQueue item that
  95 + // are strings starting with an "@", using the remaining
  96 + // substring as key for the option map,
  97 + // e.g. "@autoUpload" is replaced with options.autoUpload:
  98 + _transformProcessQueue: function (options) {
  99 + var processQueue = [];
  100 + $.each(options.processQueue, function () {
  101 + var settings = {},
  102 + action = this.action,
  103 + prefix = this.prefix === true ? action : this.prefix;
  104 + $.each(this, function (key, value) {
  105 + if ($.type(value) === 'string' &&
  106 + value.charAt(0) === '@') {
  107 + settings[key] = options[
  108 + value.slice(1) || (prefix ? prefix +
  109 + key.charAt(0).toUpperCase() + key.slice(1) : key)
  110 + ];
  111 + } else {
  112 + settings[key] = value;
  113 + }
  114 +
  115 + });
  116 + processQueue.push(settings);
  117 + });
  118 + options.processQueue = processQueue;
  119 + },
  120 +
  121 + // Returns the number of files currently in the processsing queue:
  122 + processing: function () {
  123 + return this._processing;
  124 + },
  125 +
  126 + // Processes the files given as files property of the data parameter,
  127 + // returns a Promise object that allows to bind callbacks:
  128 + process: function (data) {
  129 + var that = this,
  130 + options = $.extend({}, this.options, data);
  131 + if (options.processQueue && options.processQueue.length) {
  132 + this._transformProcessQueue(options);
  133 + if (this._processing === 0) {
  134 + this._trigger('processstart');
  135 + }
  136 + $.each(data.files, function (index) {
  137 + var opts = index ? $.extend({}, options) : options,
  138 + func = function () {
  139 + return that._processFile(opts);
  140 + };
  141 + opts.index = index;
  142 + that._processing += 1;
  143 + that._processingQueue = that._processingQueue.pipe(func, func)
  144 + .always(function () {
  145 + that._processing -= 1;
  146 + if (that._processing === 0) {
  147 + that._trigger('processstop');
  148 + }
  149 + });
  150 + });
  151 + }
  152 + return this._processingQueue;
  153 + },
  154 +
  155 + _create: function () {
  156 + this._super();
  157 + this._processing = 0;
  158 + this._processingQueue = $.Deferred().resolveWith(this)
  159 + .promise();
  160 + }
  161 +
  162 + });
  163 +
  164 +}));
... ...
forms/jquery.fileupload-validate.js 0 → 100644
  1 +++ a/forms/jquery.fileupload-validate.js
  1 +/*
  2 + * jQuery File Upload Validation Plugin 1.1.1
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2013, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true, regexp: true */
  13 +/*global define, window */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + './jquery.fileupload-process'
  22 + ], factory);
  23 + } else {
  24 + // Browser globals:
  25 + factory(
  26 + window.jQuery
  27 + );
  28 + }
  29 +}(function ($) {
  30 + 'use strict';
  31 +
  32 + // Append to the default processQueue:
  33 + $.blueimp.fileupload.prototype.options.processQueue.push(
  34 + {
  35 + action: 'validate',
  36 + // Always trigger this action,
  37 + // even if the previous action was rejected:
  38 + always: true,
  39 + // Options taken from the global options map:
  40 + acceptFileTypes: '@',
  41 + maxFileSize: '@',
  42 + minFileSize: '@',
  43 + maxNumberOfFiles: '@',
  44 + disabled: '@disableValidation'
  45 + }
  46 + );
  47 +
  48 + // The File Upload Validation plugin extends the fileupload widget
  49 + // with file validation functionality:
  50 + $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  51 +
  52 + options: {
  53 + /*
  54 + // The regular expression for allowed file types, matches
  55 + // against either file type or file name:
  56 + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
  57 + // The maximum allowed file size in bytes:
  58 + maxFileSize: 10000000, // 10 MB
  59 + // The minimum allowed file size in bytes:
  60 + minFileSize: undefined, // No minimal file size
  61 + // The limit of files to be uploaded:
  62 + maxNumberOfFiles: 10,
  63 + */
  64 +
  65 + // Function returning the current number of files,
  66 + // has to be overriden for maxNumberOfFiles validation:
  67 + getNumberOfFiles: $.noop,
  68 +
  69 + // Error and info messages:
  70 + messages: {
  71 + maxNumberOfFiles: 'Maximum number of files exceeded',
  72 + acceptFileTypes: 'File type not allowed',
  73 + maxFileSize: 'File is too large',
  74 + minFileSize: 'File is too small'
  75 + }
  76 + },
  77 +
  78 + processActions: {
  79 +
  80 + validate: function (data, options) {
  81 + if (options.disabled) {
  82 + return data;
  83 + }
  84 + var dfd = $.Deferred(),
  85 + settings = this.options,
  86 + file = data.files[data.index];
  87 + if ($.type(options.maxNumberOfFiles) === 'number' &&
  88 + (settings.getNumberOfFiles() || 0) + data.files.length >
  89 + options.maxNumberOfFiles) {
  90 + file.error = settings.i18n('maxNumberOfFiles');
  91 + } else if (options.acceptFileTypes &&
  92 + !(options.acceptFileTypes.test(file.type) ||
  93 + options.acceptFileTypes.test(file.name))) {
  94 + file.error = settings.i18n('acceptFileTypes');
  95 + } else if (options.maxFileSize && file.size >
  96 + options.maxFileSize) {
  97 + file.error = settings.i18n('maxFileSize');
  98 + } else if ($.type(file.size) === 'number' &&
  99 + file.size < options.minFileSize) {
  100 + file.error = settings.i18n('minFileSize');
  101 + } else {
  102 + delete file.error;
  103 + }
  104 + if (file.error || data.files.error) {
  105 + data.files.error = true;
  106 + dfd.rejectWith(this, [data]);
  107 + } else {
  108 + dfd.resolveWith(this, [data]);
  109 + }
  110 + return dfd.promise();
  111 + }
  112 +
  113 + }
  114 +
  115 + });
  116 +
  117 +}));
... ...
forms/jquery.fileupload-validate_1.js 0 → 100644
  1 +++ a/forms/jquery.fileupload-validate_1.js
  1 +/*
  2 + * jQuery File Upload Validation Plugin 1.1.1
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2013, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true, regexp: true */
  13 +/*global define, window */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + './jquery.fileupload-process'
  22 + ], factory);
  23 + } else {
  24 + // Browser globals:
  25 + factory(
  26 + window.jQuery
  27 + );
  28 + }
  29 +}(function ($) {
  30 + 'use strict';
  31 +
  32 + // Append to the default processQueue:
  33 + $.blueimp.fileupload.prototype.options.processQueue.push(
  34 + {
  35 + action: 'validate',
  36 + // Always trigger this action,
  37 + // even if the previous action was rejected:
  38 + always: true,
  39 + // Options taken from the global options map:
  40 + acceptFileTypes: '@',
  41 + maxFileSize: '@',
  42 + minFileSize: '@',
  43 + maxNumberOfFiles: '@',
  44 + disabled: '@disableValidation'
  45 + }
  46 + );
  47 +
  48 + // The File Upload Validation plugin extends the fileupload widget
  49 + // with file validation functionality:
  50 + $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  51 +
  52 + options: {
  53 + /*
  54 + // The regular expression for allowed file types, matches
  55 + // against either file type or file name:
  56 + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
  57 + // The maximum allowed file size in bytes:
  58 + maxFileSize: 10000000, // 10 MB
  59 + // The minimum allowed file size in bytes:
  60 + minFileSize: undefined, // No minimal file size
  61 + // The limit of files to be uploaded:
  62 + maxNumberOfFiles: 10,
  63 + */
  64 +
  65 + // Function returning the current number of files,
  66 + // has to be overriden for maxNumberOfFiles validation:
  67 + getNumberOfFiles: $.noop,
  68 +
  69 + // Error and info messages:
  70 + messages: {
  71 + maxNumberOfFiles: 'Maximum number of files exceeded',
  72 + acceptFileTypes: 'File type not allowed',
  73 + maxFileSize: 'File is too large',
  74 + minFileSize: 'File is too small'
  75 + }
  76 + },
  77 +
  78 + processActions: {
  79 +
  80 + validate: function (data, options) {
  81 + if (options.disabled) {
  82 + return data;
  83 + }
  84 + var dfd = $.Deferred(),
  85 + settings = this.options,
  86 + file = data.files[data.index];
  87 + if ($.type(options.maxNumberOfFiles) === 'number' &&
  88 + (settings.getNumberOfFiles() || 0) + data.files.length >
  89 + options.maxNumberOfFiles) {
  90 + file.error = settings.i18n('maxNumberOfFiles');
  91 + } else if (options.acceptFileTypes &&
  92 + !(options.acceptFileTypes.test(file.type) ||
  93 + options.acceptFileTypes.test(file.name))) {
  94 + file.error = settings.i18n('acceptFileTypes');
  95 + } else if (options.maxFileSize && file.size >
  96 + options.maxFileSize) {
  97 + file.error = settings.i18n('maxFileSize');
  98 + } else if ($.type(file.size) === 'number' &&
  99 + file.size < options.minFileSize) {
  100 + file.error = settings.i18n('minFileSize');
  101 + } else {
  102 + delete file.error;
  103 + }
  104 + if (file.error || data.files.error) {
  105 + data.files.error = true;
  106 + dfd.rejectWith(this, [data]);
  107 + } else {
  108 + dfd.resolveWith(this, [data]);
  109 + }
  110 + return dfd.promise();
  111 + }
  112 +
  113 + }
  114 +
  115 + });
  116 +
  117 +}));
... ...
forms/jquery.fileupload.js 0 → 100644
  1 +++ a/forms/jquery.fileupload.js
  1 +/*
  2 + * jQuery File Upload Plugin 5.34.0
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2010, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true, regexp: true */
  13 +/*global define, window, document, location, File, Blob, FormData */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + 'jquery.ui.widget'
  22 + ], factory);
  23 + } else {
  24 + // Browser globals:
  25 + factory(window.jQuery);
  26 + }
  27 +}(function ($) {
  28 + 'use strict';
  29 +
  30 + // Detect file input support, based on
  31 + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
  32 + $.support.fileInput = !(new RegExp(
  33 + // Handle devices which give false positives for the feature detection:
  34 + '(Android (1\\.[0156]|2\\.[01]))' +
  35 + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
  36 + '|(w(eb)?OSBrowser)|(webOS)' +
  37 + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
  38 + ).test(window.navigator.userAgent) ||
  39 + // Feature detection for all other devices:
  40 + $('<input type="file">').prop('disabled'));
  41 +
  42 + // The FileReader API is not actually used, but works as feature detection,
  43 + // as e.g. Safari supports XHR file uploads via the FormData API,
  44 + // but not non-multipart XHR file uploads:
  45 + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
  46 + $.support.xhrFormDataFileUpload = !!window.FormData;
  47 +
  48 + // Detect support for Blob slicing (required for chunked uploads):
  49 + $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
  50 + Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
  51 +
  52 + // The fileupload widget listens for change events on file input fields defined
  53 + // via fileInput setting and paste or drop events of the given dropZone.
  54 + // In addition to the default jQuery Widget methods, the fileupload widget
  55 + // exposes the "add" and "send" methods, to add or directly send files using
  56 + // the fileupload API.
  57 + // By default, files added via file input selection, paste, drag & drop or
  58 + // "add" method are uploaded immediately, but it is possible to override
  59 + // the "add" callback option to queue file uploads.
  60 + $.widget('blueimp.fileupload', {
  61 +
  62 + options: {
  63 + // The drop target element(s), by the default the complete document.
  64 + // Set to null to disable drag & drop support:
  65 + dropZone: $(document),
  66 + // The paste target element(s), by the default the complete document.
  67 + // Set to null to disable paste support:
  68 + pasteZone: $(document),
  69 + // The file input field(s), that are listened to for change events.
  70 + // If undefined, it is set to the file input fields inside
  71 + // of the widget element on plugin initialization.
  72 + // Set to null to disable the change listener.
  73 + fileInput: undefined,
  74 + // By default, the file input field is replaced with a clone after
  75 + // each input field change event. This is required for iframe transport
  76 + // queues and allows change events to be fired for the same file
  77 + // selection, but can be disabled by setting the following option to false:
  78 + replaceFileInput: true,
  79 + // The parameter name for the file form data (the request argument name).
  80 + // If undefined or empty, the name property of the file input field is
  81 + // used, or "files[]" if the file input name property is also empty,
  82 + // can be a string or an array of strings:
  83 + paramName: undefined,
  84 + // By default, each file of a selection is uploaded using an individual
  85 + // request for XHR type uploads. Set to false to upload file
  86 + // selections in one request each:
  87 + singleFileUploads: true,
  88 + // To limit the number of files uploaded with one XHR request,
  89 + // set the following option to an integer greater than 0:
  90 + limitMultiFileUploads: undefined,
  91 + // Set the following option to true to issue all file upload requests
  92 + // in a sequential order:
  93 + sequentialUploads: false,
  94 + // To limit the number of concurrent uploads,
  95 + // set the following option to an integer greater than 0:
  96 + limitConcurrentUploads: undefined,
  97 + // Set the following option to true to force iframe transport uploads:
  98 + forceIframeTransport: false,
  99 + // Set the following option to the location of a redirect url on the
  100 + // origin server, for cross-domain iframe transport uploads:
  101 + redirect: undefined,
  102 + // The parameter name for the redirect url, sent as part of the form
  103 + // data and set to 'redirect' if this option is empty:
  104 + redirectParamName: undefined,
  105 + // Set the following option to the location of a postMessage window,
  106 + // to enable postMessage transport uploads:
  107 + postMessage: undefined,
  108 + // By default, XHR file uploads are sent as multipart/form-data.
  109 + // The iframe transport is always using multipart/form-data.
  110 + // Set to false to enable non-multipart XHR uploads:
  111 + multipart: true,
  112 + // To upload large files in smaller chunks, set the following option
  113 + // to a preferred maximum chunk size. If set to 0, null or undefined,
  114 + // or the browser does not support the required Blob API, files will
  115 + // be uploaded as a whole.
  116 + maxChunkSize: undefined,
  117 + // When a non-multipart upload or a chunked multipart upload has been
  118 + // aborted, this option can be used to resume the upload by setting
  119 + // it to the size of the already uploaded bytes. This option is most
  120 + // useful when modifying the options object inside of the "add" or
  121 + // "send" callbacks, as the options are cloned for each file upload.
  122 + uploadedBytes: undefined,
  123 + // By default, failed (abort or error) file uploads are removed from the
  124 + // global progress calculation. Set the following option to false to
  125 + // prevent recalculating the global progress data:
  126 + recalculateProgress: true,
  127 + // Interval in milliseconds to calculate and trigger progress events:
  128 + progressInterval: 100,
  129 + // Interval in milliseconds to calculate progress bitrate:
  130 + bitrateInterval: 500,
  131 + // By default, uploads are started automatically when adding files:
  132 + autoUpload: true,
  133 +
  134 + // Error and info messages:
  135 + messages: {
  136 + uploadedBytes: 'Uploaded bytes exceed file size'
  137 + },
  138 +
  139 + // Translation function, gets the message key to be translated
  140 + // and an object with context specific data as arguments:
  141 + i18n: function (message, context) {
  142 + message = this.messages[message] || message.toString();
  143 + if (context) {
  144 + $.each(context, function (key, value) {
  145 + message = message.replace('{' + key + '}', value);
  146 + });
  147 + }
  148 + return message;
  149 + },
  150 +
  151 + // Additional form data to be sent along with the file uploads can be set
  152 + // using this option, which accepts an array of objects with name and
  153 + // value properties, a function returning such an array, a FormData
  154 + // object (for XHR file uploads), or a simple object.
  155 + // The form of the first fileInput is given as parameter to the function:
  156 + formData: function (form) {
  157 + return form.serializeArray();
  158 + },
  159 +
  160 + // The add callback is invoked as soon as files are added to the fileupload
  161 + // widget (via file input selection, drag & drop, paste or add API call).
  162 + // If the singleFileUploads option is enabled, this callback will be
  163 + // called once for each file in the selection for XHR file uploads, else
  164 + // once for each file selection.
  165 + //
  166 + // The upload starts when the submit method is invoked on the data parameter.
  167 + // The data object contains a files property holding the added files
  168 + // and allows you to override plugin options as well as define ajax settings.
  169 + //
  170 + // Listeners for this callback can also be bound the following way:
  171 + // .bind('fileuploadadd', func);
  172 + //
  173 + // data.submit() returns a Promise object and allows to attach additional
  174 + // handlers using jQuery's Deferred callbacks:
  175 + // data.submit().done(func).fail(func).always(func);
  176 + add: function (e, data) {
  177 + if (e.isDefaultPrevented()) {
  178 + return false;
  179 + }
  180 + if (data.autoUpload || (data.autoUpload !== false &&
  181 + $(this).fileupload('option', 'autoUpload'))) {
  182 + data.process().done(function () {
  183 + data.submit();
  184 + });
  185 + }
  186 + },
  187 +
  188 + // Other callbacks:
  189 +
  190 + // Callback for the submit event of each file upload:
  191 + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
  192 +
  193 + // Callback for the start of each file upload request:
  194 + // send: function (e, data) {}, // .bind('fileuploadsend', func);
  195 +
  196 + // Callback for successful uploads:
  197 + // done: function (e, data) {}, // .bind('fileuploaddone', func);
  198 +
  199 + // Callback for failed (abort or error) uploads:
  200 + // fail: function (e, data) {}, // .bind('fileuploadfail', func);
  201 +
  202 + // Callback for completed (success, abort or error) requests:
  203 + // always: function (e, data) {}, // .bind('fileuploadalways', func);
  204 +
  205 + // Callback for upload progress events:
  206 + // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
  207 +
  208 + // Callback for global upload progress events:
  209 + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
  210 +
  211 + // Callback for uploads start, equivalent to the global ajaxStart event:
  212 + // start: function (e) {}, // .bind('fileuploadstart', func);
  213 +
  214 + // Callback for uploads stop, equivalent to the global ajaxStop event:
  215 + // stop: function (e) {}, // .bind('fileuploadstop', func);
  216 +
  217 + // Callback for change events of the fileInput(s):
  218 + // change: function (e, data) {}, // .bind('fileuploadchange', func);
  219 +
  220 + // Callback for paste events to the pasteZone(s):
  221 + // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
  222 +
  223 + // Callback for drop events of the dropZone(s):
  224 + // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
  225 +
  226 + // Callback for dragover events of the dropZone(s):
  227 + // dragover: function (e) {}, // .bind('fileuploaddragover', func);
  228 +
  229 + // Callback for the start of each chunk upload request:
  230 + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
  231 +
  232 + // Callback for successful chunk uploads:
  233 + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
  234 +
  235 + // Callback for failed (abort or error) chunk uploads:
  236 + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
  237 +
  238 + // Callback for completed (success, abort or error) chunk upload requests:
  239 + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
  240 +
  241 + // The plugin options are used as settings object for the ajax calls.
  242 + // The following are jQuery ajax settings required for the file uploads:
  243 + processData: false,
  244 + contentType: false,
  245 + cache: false
  246 + },
  247 +
  248 + // A list of options that require reinitializing event listeners and/or
  249 + // special initialization code:
  250 + _specialOptions: [
  251 + 'fileInput',
  252 + 'dropZone',
  253 + 'pasteZone',
  254 + 'multipart',
  255 + 'forceIframeTransport'
  256 + ],
  257 +
  258 + _blobSlice: $.support.blobSlice && function () {
  259 + var slice = this.slice || this.webkitSlice || this.mozSlice;
  260 + return slice.apply(this, arguments);
  261 + },
  262 +
  263 + _BitrateTimer: function () {
  264 + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
  265 + this.loaded = 0;
  266 + this.bitrate = 0;
  267 + this.getBitrate = function (now, loaded, interval) {
  268 + var timeDiff = now - this.timestamp;
  269 + if (!this.bitrate || !interval || timeDiff > interval) {
  270 + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
  271 + this.loaded = loaded;
  272 + this.timestamp = now;
  273 + }
  274 + return this.bitrate;
  275 + };
  276 + },
  277 +
  278 + _isXHRUpload: function (options) {
  279 + return !options.forceIframeTransport &&
  280 + ((!options.multipart && $.support.xhrFileUpload) ||
  281 + $.support.xhrFormDataFileUpload);
  282 + },
  283 +
  284 + _getFormData: function (options) {
  285 + var formData;
  286 + if (typeof options.formData === 'function') {
  287 + return options.formData(options.form);
  288 + }
  289 + if ($.isArray(options.formData)) {
  290 + return options.formData;
  291 + }
  292 + if ($.type(options.formData) === 'object') {
  293 + formData = [];
  294 + $.each(options.formData, function (name, value) {
  295 + formData.push({name: name, value: value});
  296 + });
  297 + return formData;
  298 + }
  299 + return [];
  300 + },
  301 +
  302 + _getTotal: function (files) {
  303 + var total = 0;
  304 + $.each(files, function (index, file) {
  305 + total += file.size || 1;
  306 + });
  307 + return total;
  308 + },
  309 +
  310 + _initProgressObject: function (obj) {
  311 + var progress = {
  312 + loaded: 0,
  313 + total: 0,
  314 + bitrate: 0
  315 + };
  316 + if (obj._progress) {
  317 + $.extend(obj._progress, progress);
  318 + } else {
  319 + obj._progress = progress;
  320 + }
  321 + },
  322 +
  323 + _initResponseObject: function (obj) {
  324 + var prop;
  325 + if (obj._response) {
  326 + for (prop in obj._response) {
  327 + if (obj._response.hasOwnProperty(prop)) {
  328 + delete obj._response[prop];
  329 + }
  330 + }
  331 + } else {
  332 + obj._response = {};
  333 + }
  334 + },
  335 +
  336 + _onProgress: function (e, data) {
  337 + if (e.lengthComputable) {
  338 + var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
  339 + loaded;
  340 + if (data._time && data.progressInterval &&
  341 + (now - data._time < data.progressInterval) &&
  342 + e.loaded !== e.total) {
  343 + return;
  344 + }
  345 + data._time = now;
  346 + loaded = Math.floor(
  347 + e.loaded / e.total * (data.chunkSize || data._progress.total)
  348 + ) + (data.uploadedBytes || 0);
  349 + // Add the difference from the previously loaded state
  350 + // to the global loaded counter:
  351 + this._progress.loaded += (loaded - data._progress.loaded);
  352 + this._progress.bitrate = this._bitrateTimer.getBitrate(
  353 + now,
  354 + this._progress.loaded,
  355 + data.bitrateInterval
  356 + );
  357 + data._progress.loaded = data.loaded = loaded;
  358 + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
  359 + now,
  360 + loaded,
  361 + data.bitrateInterval
  362 + );
  363 + // Trigger a custom progress event with a total data property set
  364 + // to the file size(s) of the current upload and a loaded data
  365 + // property calculated accordingly:
  366 + this._trigger(
  367 + 'progress',
  368 + $.Event('progress', {delegatedEvent: e}),
  369 + data
  370 + );
  371 + // Trigger a global progress event for all current file uploads,
  372 + // including ajax calls queued for sequential file uploads:
  373 + this._trigger(
  374 + 'progressall',
  375 + $.Event('progressall', {delegatedEvent: e}),
  376 + this._progress
  377 + );
  378 + }
  379 + },
  380 +
  381 + _initProgressListener: function (options) {
  382 + var that = this,
  383 + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
  384 + // Accesss to the native XHR object is required to add event listeners
  385 + // for the upload progress event:
  386 + if (xhr.upload) {
  387 + $(xhr.upload).bind('progress', function (e) {
  388 + var oe = e.originalEvent;
  389 + // Make sure the progress event properties get copied over:
  390 + e.lengthComputable = oe.lengthComputable;
  391 + e.loaded = oe.loaded;
  392 + e.total = oe.total;
  393 + that._onProgress(e, options);
  394 + });
  395 + options.xhr = function () {
  396 + return xhr;
  397 + };
  398 + }
  399 + },
  400 +
  401 + _isInstanceOf: function (type, obj) {
  402 + // Cross-frame instanceof check
  403 + return Object.prototype.toString.call(obj) === '[object ' + type + ']';
  404 + },
  405 +
  406 + _initXHRData: function (options) {
  407 + var that = this,
  408 + formData,
  409 + file = options.files[0],
  410 + // Ignore non-multipart setting if not supported:
  411 + multipart = options.multipart || !$.support.xhrFileUpload,
  412 + paramName = options.paramName[0];
  413 + options.headers = $.extend({}, options.headers);
  414 + if (options.contentRange) {
  415 + options.headers['Content-Range'] = options.contentRange;
  416 + }
  417 + if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
  418 + options.headers['Content-Disposition'] = 'attachment; filename="' +
  419 + encodeURI(file.name) + '"';
  420 + }
  421 + if (!multipart) {
  422 + options.contentType = file.type;
  423 + options.data = options.blob || file;
  424 + } else if ($.support.xhrFormDataFileUpload) {
  425 + if (options.postMessage) {
  426 + // window.postMessage does not allow sending FormData
  427 + // objects, so we just add the File/Blob objects to
  428 + // the formData array and let the postMessage window
  429 + // create the FormData object out of this array:
  430 + formData = this._getFormData(options);
  431 + if (options.blob) {
  432 + formData.push({
  433 + name: paramName,
  434 + value: options.blob
  435 + });
  436 + } else {
  437 + $.each(options.files, function (index, file) {
  438 + formData.push({
  439 + name: options.paramName[index] || paramName,
  440 + value: file
  441 + });
  442 + });
  443 + }
  444 + } else {
  445 + if (that._isInstanceOf('FormData', options.formData)) {
  446 + formData = options.formData;
  447 + } else {
  448 + formData = new FormData();
  449 + $.each(this._getFormData(options), function (index, field) {
  450 + formData.append(field.name, field.value);
  451 + });
  452 + }
  453 + if (options.blob) {
  454 + formData.append(paramName, options.blob, file.name);
  455 + } else {
  456 + $.each(options.files, function (index, file) {
  457 + // This check allows the tests to run with
  458 + // dummy objects:
  459 + if (that._isInstanceOf('File', file) ||
  460 + that._isInstanceOf('Blob', file)) {
  461 + formData.append(
  462 + options.paramName[index] || paramName,
  463 + file,
  464 + file.name
  465 + );
  466 + }
  467 + });
  468 + }
  469 + }
  470 + options.data = formData;
  471 + }
  472 + // Blob reference is not needed anymore, free memory:
  473 + options.blob = null;
  474 + },
  475 +
  476 + _initIframeSettings: function (options) {
  477 + var targetHost = $('<a></a>').prop('href', options.url).prop('host');
  478 + // Setting the dataType to iframe enables the iframe transport:
  479 + options.dataType = 'iframe ' + (options.dataType || '');
  480 + // The iframe transport accepts a serialized array as form data:
  481 + options.formData = this._getFormData(options);
  482 + // Add redirect url to form data on cross-domain uploads:
  483 + if (options.redirect && targetHost && targetHost !== location.host) {
  484 + options.formData.push({
  485 + name: options.redirectParamName || 'redirect',
  486 + value: options.redirect
  487 + });
  488 + }
  489 + },
  490 +
  491 + _initDataSettings: function (options) {
  492 + if (this._isXHRUpload(options)) {
  493 + if (!this._chunkedUpload(options, true)) {
  494 + if (!options.data) {
  495 + this._initXHRData(options);
  496 + }
  497 + this._initProgressListener(options);
  498 + }
  499 + if (options.postMessage) {
  500 + // Setting the dataType to postmessage enables the
  501 + // postMessage transport:
  502 + options.dataType = 'postmessage ' + (options.dataType || '');
  503 + }
  504 + } else {
  505 + this._initIframeSettings(options);
  506 + }
  507 + },
  508 +
  509 + _getParamName: function (options) {
  510 + var fileInput = $(options.fileInput),
  511 + paramName = options.paramName;
  512 + if (!paramName) {
  513 + paramName = [];
  514 + fileInput.each(function () {
  515 + var input = $(this),
  516 + name = input.prop('name') || 'files[]',
  517 + i = (input.prop('files') || [1]).length;
  518 + while (i) {
  519 + paramName.push(name);
  520 + i -= 1;
  521 + }
  522 + });
  523 + if (!paramName.length) {
  524 + paramName = [fileInput.prop('name') || 'files[]'];
  525 + }
  526 + } else if (!$.isArray(paramName)) {
  527 + paramName = [paramName];
  528 + }
  529 + return paramName;
  530 + },
  531 +
  532 + _initFormSettings: function (options) {
  533 + // Retrieve missing options from the input field and the
  534 + // associated form, if available:
  535 + if (!options.form || !options.form.length) {
  536 + options.form = $(options.fileInput.prop('form'));
  537 + // If the given file input doesn't have an associated form,
  538 + // use the default widget file input's form:
  539 + if (!options.form.length) {
  540 + options.form = $(this.options.fileInput.prop('form'));
  541 + }
  542 + }
  543 + options.paramName = this._getParamName(options);
  544 + if (!options.url) {
  545 + options.url = options.form.prop('action') || location.href;
  546 + }
  547 + // The HTTP request method must be "POST" or "PUT":
  548 + options.type = (options.type ||
  549 + ($.type(options.form.prop('method')) === 'string' &&
  550 + options.form.prop('method')) || ''
  551 + ).toUpperCase();
  552 + if (options.type !== 'POST' && options.type !== 'PUT' &&
  553 + options.type !== 'PATCH') {
  554 + options.type = 'POST';
  555 + }
  556 + if (!options.formAcceptCharset) {
  557 + options.formAcceptCharset = options.form.attr('accept-charset');
  558 + }
  559 + },
  560 +
  561 + _getAJAXSettings: function (data) {
  562 + var options = $.extend({}, this.options, data);
  563 + this._initFormSettings(options);
  564 + this._initDataSettings(options);
  565 + options.url = options.url+'&rand=' + new Date().getTime();
  566 + return options;
  567 + },
  568 +
  569 + // jQuery 1.6 doesn't provide .state(),
  570 + // while jQuery 1.8+ removed .isRejected() and .isResolved():
  571 + _getDeferredState: function (deferred) {
  572 + if (deferred.state) {
  573 + return deferred.state();
  574 + }
  575 + if (deferred.isResolved()) {
  576 + return 'resolved';
  577 + }
  578 + if (deferred.isRejected()) {
  579 + return 'rejected';
  580 + }
  581 + return 'pending';
  582 + },
  583 +
  584 + // Maps jqXHR callbacks to the equivalent
  585 + // methods of the given Promise object:
  586 + _enhancePromise: function (promise) {
  587 + promise.success = promise.done;
  588 + promise.error = promise.fail;
  589 + promise.complete = promise.always;
  590 + return promise;
  591 + },
  592 +
  593 + // Creates and returns a Promise object enhanced with
  594 + // the jqXHR methods abort, success, error and complete:
  595 + _getXHRPromise: function (resolveOrReject, context, args) {
  596 + var dfd = $.Deferred(),
  597 + promise = dfd.promise();
  598 + context = context || this.options.context || promise;
  599 + if (resolveOrReject === true) {
  600 + dfd.resolveWith(context, args);
  601 + } else if (resolveOrReject === false) {
  602 + dfd.rejectWith(context, args);
  603 + }
  604 + promise.abort = dfd.promise;
  605 + return this._enhancePromise(promise);
  606 + },
  607 +
  608 + // Adds convenience methods to the data callback argument:
  609 + _addConvenienceMethods: function (e, data) {
  610 + var that = this,
  611 + getPromise = function (data) {
  612 + return $.Deferred().resolveWith(that, [data]).promise();
  613 + };
  614 + data.process = function (resolveFunc, rejectFunc) {
  615 + if (resolveFunc || rejectFunc) {
  616 + data._processQueue = this._processQueue =
  617 + (this._processQueue || getPromise(this))
  618 + .pipe(resolveFunc, rejectFunc);
  619 + }
  620 + return this._processQueue || getPromise(this);
  621 + };
  622 + data.submit = function () {
  623 + if (this.state() !== 'pending') {
  624 + data.jqXHR = this.jqXHR =
  625 + (that._trigger(
  626 + 'submit',
  627 + $.Event('submit', {delegatedEvent: e}),
  628 + this
  629 + ) !== false) && that._onSend(e, this);
  630 + }
  631 + return this.jqXHR || that._getXHRPromise();
  632 + };
  633 + data.abort = function () {
  634 + if (this.jqXHR) {
  635 + return this.jqXHR.abort();
  636 + }
  637 + return that._getXHRPromise();
  638 + };
  639 + data.state = function () {
  640 + if (this.jqXHR) {
  641 + return that._getDeferredState(this.jqXHR);
  642 + }
  643 + if (this._processQueue) {
  644 + return that._getDeferredState(this._processQueue);
  645 + }
  646 + };
  647 + data.progress = function () {
  648 + return this._progress;
  649 + };
  650 + data.response = function () {
  651 + return this._response;
  652 + };
  653 + },
  654 +
  655 + // Parses the Range header from the server response
  656 + // and returns the uploaded bytes:
  657 + _getUploadedBytes: function (jqXHR) {
  658 + var range = jqXHR.getResponseHeader('Range'),
  659 + parts = range && range.split('-'),
  660 + upperBytesPos = parts && parts.length > 1 &&
  661 + parseInt(parts[1], 10);
  662 + return upperBytesPos && upperBytesPos + 1;
  663 + },
  664 +
  665 + // Uploads a file in multiple, sequential requests
  666 + // by splitting the file up in multiple blob chunks.
  667 + // If the second parameter is true, only tests if the file
  668 + // should be uploaded in chunks, but does not invoke any
  669 + // upload requests:
  670 + _chunkedUpload: function (options, testOnly) {
  671 + options.uploadedBytes = options.uploadedBytes || 0;
  672 + var that = this,
  673 + file = options.files[0],
  674 + fs = file.size,
  675 + ub = options.uploadedBytes,
  676 + mcs = options.maxChunkSize || fs,
  677 + slice = this._blobSlice,
  678 + dfd = $.Deferred(),
  679 + promise = dfd.promise(),
  680 + jqXHR,
  681 + upload;
  682 + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
  683 + options.data) {
  684 + return false;
  685 + }
  686 + if (testOnly) {
  687 + return true;
  688 + }
  689 + if (ub >= fs) {
  690 + file.error = options.i18n('uploadedBytes');
  691 + return this._getXHRPromise(
  692 + false,
  693 + options.context,
  694 + [null, 'error', file.error]
  695 + );
  696 + }
  697 + // The chunk upload method:
  698 + upload = function () {
  699 + // Clone the options object for each chunk upload:
  700 + var o = $.extend({}, options),
  701 + currentLoaded = o._progress.loaded;
  702 + o.blob = slice.call(
  703 + file,
  704 + ub,
  705 + ub + mcs,
  706 + file.type
  707 + );
  708 + // Store the current chunk size, as the blob itself
  709 + // will be dereferenced after data processing:
  710 + o.chunkSize = o.blob.size;
  711 + // Expose the chunk bytes position range:
  712 + o.contentRange = 'bytes ' + ub + '-' +
  713 + (ub + o.chunkSize - 1) + '/' + fs;
  714 + // Process the upload data (the blob and potential form data):
  715 + that._initXHRData(o);
  716 + // Add progress listeners for this chunk upload:
  717 + that._initProgressListener(o);
  718 + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
  719 + that._getXHRPromise(false, o.context))
  720 + .done(function (result, textStatus, jqXHR) {
  721 + ub = that._getUploadedBytes(jqXHR) ||
  722 + (ub + o.chunkSize);
  723 + // Create a progress event if no final progress event
  724 + // with loaded equaling total has been triggered
  725 + // for this chunk:
  726 + if (currentLoaded + o.chunkSize - o._progress.loaded) {
  727 + that._onProgress($.Event('progress', {
  728 + lengthComputable: true,
  729 + loaded: ub - o.uploadedBytes,
  730 + total: ub - o.uploadedBytes
  731 + }), o);
  732 + }
  733 + options.uploadedBytes = o.uploadedBytes = ub;
  734 + o.result = result;
  735 + o.textStatus = textStatus;
  736 + o.jqXHR = jqXHR;
  737 + that._trigger('chunkdone', null, o);
  738 + that._trigger('chunkalways', null, o);
  739 + if (ub < fs) {
  740 + // File upload not yet complete,
  741 + // continue with the next chunk:
  742 + upload();
  743 + } else {
  744 + dfd.resolveWith(
  745 + o.context,
  746 + [result, textStatus, jqXHR]
  747 + );
  748 + }
  749 + })
  750 + .fail(function (jqXHR, textStatus, errorThrown) {
  751 + o.jqXHR = jqXHR;
  752 + o.textStatus = textStatus;
  753 + o.errorThrown = errorThrown;
  754 + that._trigger('chunkfail', null, o);
  755 + that._trigger('chunkalways', null, o);
  756 + dfd.rejectWith(
  757 + o.context,
  758 + [jqXHR, textStatus, errorThrown]
  759 + );
  760 + });
  761 + };
  762 + this._enhancePromise(promise);
  763 + promise.abort = function () {
  764 + return jqXHR.abort();
  765 + };
  766 + upload();
  767 + return promise;
  768 + },
  769 +
  770 + _beforeSend: function (e, data) {
  771 + if (this._active === 0) {
  772 + // the start callback is triggered when an upload starts
  773 + // and no other uploads are currently running,
  774 + // equivalent to the global ajaxStart event:
  775 + this._trigger('start');
  776 + // Set timer for global bitrate progress calculation:
  777 + this._bitrateTimer = new this._BitrateTimer();
  778 + // Reset the global progress values:
  779 + this._progress.loaded = this._progress.total = 0;
  780 + this._progress.bitrate = 0;
  781 + }
  782 + // Make sure the container objects for the .response() and
  783 + // .progress() methods on the data object are available
  784 + // and reset to their initial state:
  785 + this._initResponseObject(data);
  786 + this._initProgressObject(data);
  787 + data._progress.loaded = data.loaded = data.uploadedBytes || 0;
  788 + data._progress.total = data.total = this._getTotal(data.files) || 1;
  789 + data._progress.bitrate = data.bitrate = 0;
  790 + this._active += 1;
  791 + // Initialize the global progress values:
  792 + this._progress.loaded += data.loaded;
  793 + this._progress.total += data.total;
  794 + },
  795 +
  796 + _onDone: function (result, textStatus, jqXHR, options) {
  797 + var total = options._progress.total,
  798 + response = options._response;
  799 + if (options._progress.loaded < total) {
  800 + // Create a progress event if no final progress event
  801 + // with loaded equaling total has been triggered:
  802 + this._onProgress($.Event('progress', {
  803 + lengthComputable: true,
  804 + loaded: total,
  805 + total: total
  806 + }), options);
  807 + }
  808 + response.result = options.result = result;
  809 + response.textStatus = options.textStatus = textStatus;
  810 + response.jqXHR = options.jqXHR = jqXHR;
  811 + this._trigger('done', null, options);
  812 + },
  813 +
  814 + _onFail: function (jqXHR, textStatus, errorThrown, options) {
  815 + var response = options._response;
  816 + if (options.recalculateProgress) {
  817 + // Remove the failed (error or abort) file upload from
  818 + // the global progress calculation:
  819 + this._progress.loaded -= options._progress.loaded;
  820 + this._progress.total -= options._progress.total;
  821 + }
  822 + response.jqXHR = options.jqXHR = jqXHR;
  823 + response.textStatus = options.textStatus = textStatus;
  824 + response.errorThrown = options.errorThrown = errorThrown;
  825 + this._trigger('fail', null, options);
  826 + },
  827 +
  828 + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
  829 + // jqXHRorResult, textStatus and jqXHRorError are added to the
  830 + // options object via done and fail callbacks
  831 + this._trigger('always', null, options);
  832 + },
  833 +
  834 + _onSend: function (e, data) {
  835 + if (!data.submit) {
  836 + this._addConvenienceMethods(e, data);
  837 + }
  838 + var that = this,
  839 + jqXHR,
  840 + aborted,
  841 + slot,
  842 + pipe,
  843 + options = that._getAJAXSettings(data),
  844 + send = function () {
  845 + that._sending += 1;
  846 + // Set timer for bitrate progress calculation:
  847 + options._bitrateTimer = new that._BitrateTimer();
  848 + jqXHR = jqXHR || (
  849 + ((aborted || that._trigger(
  850 + 'send',
  851 + $.Event('send', {delegatedEvent: e}),
  852 + options
  853 + ) === false) &&
  854 + that._getXHRPromise(false, options.context, aborted)) ||
  855 + that._chunkedUpload(options) || $.ajax(options)
  856 + ).done(function (result, textStatus, jqXHR) {
  857 + that._onDone(result, textStatus, jqXHR, options);
  858 + }).fail(function (jqXHR, textStatus, errorThrown) {
  859 + that._onFail(jqXHR, textStatus, errorThrown, options);
  860 + }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
  861 + that._onAlways(
  862 + jqXHRorResult,
  863 + textStatus,
  864 + jqXHRorError,
  865 + options
  866 + );
  867 + that._sending -= 1;
  868 + that._active -= 1;
  869 + if (options.limitConcurrentUploads &&
  870 + options.limitConcurrentUploads > that._sending) {
  871 + // Start the next queued upload,
  872 + // that has not been aborted:
  873 + var nextSlot = that._slots.shift();
  874 + while (nextSlot) {
  875 + if (that._getDeferredState(nextSlot) === 'pending') {
  876 + nextSlot.resolve();
  877 + break;
  878 + }
  879 + nextSlot = that._slots.shift();
  880 + }
  881 + }
  882 + if (that._active === 0) {
  883 + // The stop callback is triggered when all uploads have
  884 + // been completed, equivalent to the global ajaxStop event:
  885 + that._trigger('stop');
  886 + }
  887 + });
  888 + return jqXHR;
  889 + };
  890 + this._beforeSend(e, options);
  891 + if (this.options.sequentialUploads ||
  892 + (this.options.limitConcurrentUploads &&
  893 + this.options.limitConcurrentUploads <= this._sending)) {
  894 + if (this.options.limitConcurrentUploads > 1) {
  895 + slot = $.Deferred();
  896 + this._slots.push(slot);
  897 + pipe = slot.pipe(send);
  898 + } else {
  899 + this._sequence = this._sequence.pipe(send, send);
  900 + pipe = this._sequence;
  901 + }
  902 + // Return the piped Promise object, enhanced with an abort method,
  903 + // which is delegated to the jqXHR object of the current upload,
  904 + // and jqXHR callbacks mapped to the equivalent Promise methods:
  905 + pipe.abort = function () {
  906 + aborted = [undefined, 'abort', 'abort'];
  907 + if (!jqXHR) {
  908 + if (slot) {
  909 + slot.rejectWith(options.context, aborted);
  910 + }
  911 + return send();
  912 + }
  913 + return jqXHR.abort();
  914 + };
  915 + return this._enhancePromise(pipe);
  916 + }
  917 + return send();
  918 + },
  919 +
  920 + _onAdd: function (e, data) {
  921 + var that = this,
  922 + result = true,
  923 + options = $.extend({}, this.options, data),
  924 + limit = options.limitMultiFileUploads,
  925 + paramName = this._getParamName(options),
  926 + paramNameSet,
  927 + paramNameSlice,
  928 + fileSet,
  929 + i;
  930 + if (!(options.singleFileUploads || limit) ||
  931 + !this._isXHRUpload(options)) {
  932 + fileSet = [data.files];
  933 + paramNameSet = [paramName];
  934 + } else if (!options.singleFileUploads && limit) {
  935 + fileSet = [];
  936 + paramNameSet = [];
  937 + for (i = 0; i < data.files.length; i += limit) {
  938 + fileSet.push(data.files.slice(i, i + limit));
  939 + paramNameSlice = paramName.slice(i, i + limit);
  940 + if (!paramNameSlice.length) {
  941 + paramNameSlice = paramName;
  942 + }
  943 + paramNameSet.push(paramNameSlice);
  944 + }
  945 + } else {
  946 + paramNameSet = paramName;
  947 + }
  948 + data.originalFiles = data.files;
  949 + $.each(fileSet || data.files, function (index, element) {
  950 + var newData = $.extend({}, data);
  951 + newData.files = fileSet ? element : [element];
  952 + newData.paramName = paramNameSet[index];
  953 + that._initResponseObject(newData);
  954 + that._initProgressObject(newData);
  955 + that._addConvenienceMethods(e, newData);
  956 + result = that._trigger(
  957 + 'add',
  958 + $.Event('add', {delegatedEvent: e}),
  959 + newData
  960 + );
  961 + return result;
  962 + });
  963 + return result;
  964 + },
  965 +
  966 + _replaceFileInput: function (input) {
  967 + var inputClone = input.clone(true);
  968 + $('<form></form>').append(inputClone)[0].reset();
  969 + // Detaching allows to insert the fileInput on another form
  970 + // without loosing the file input value:
  971 + input.after(inputClone).detach();
  972 + // Avoid memory leaks with the detached file input:
  973 + $.cleanData(input.unbind('remove'));
  974 + // Replace the original file input element in the fileInput
  975 + // elements set with the clone, which has been copied including
  976 + // event handlers:
  977 + this.options.fileInput = this.options.fileInput.map(function (i, el) {
  978 + if (el === input[0]) {
  979 + return inputClone[0];
  980 + }
  981 + return el;
  982 + });
  983 + // If the widget has been initialized on the file input itself,
  984 + // override this.element with the file input clone:
  985 + if (input[0] === this.element[0]) {
  986 + this.element = inputClone;
  987 + }
  988 + },
  989 +
  990 + _handleFileTreeEntry: function (entry, path) {
  991 + var that = this,
  992 + dfd = $.Deferred(),
  993 + errorHandler = function (e) {
  994 + if (e && !e.entry) {
  995 + e.entry = entry;
  996 + }
  997 + // Since $.when returns immediately if one
  998 + // Deferred is rejected, we use resolve instead.
  999 + // This allows valid files and invalid items
  1000 + // to be returned together in one set:
  1001 + dfd.resolve([e]);
  1002 + },
  1003 + dirReader;
  1004 + path = path || '';
  1005 + if (entry.isFile) {
  1006 + if (entry._file) {
  1007 + // Workaround for Chrome bug #149735
  1008 + entry._file.relativePath = path;
  1009 + dfd.resolve(entry._file);
  1010 + } else {
  1011 + entry.file(function (file) {
  1012 + file.relativePath = path;
  1013 + dfd.resolve(file);
  1014 + }, errorHandler);
  1015 + }
  1016 + } else if (entry.isDirectory) {
  1017 + dirReader = entry.createReader();
  1018 + dirReader.readEntries(function (entries) {
  1019 + that._handleFileTreeEntries(
  1020 + entries,
  1021 + path + entry.name + '/'
  1022 + ).done(function (files) {
  1023 + dfd.resolve(files);
  1024 + }).fail(errorHandler);
  1025 + }, errorHandler);
  1026 + } else {
  1027 + // Return an empy list for file system items
  1028 + // other than files or directories:
  1029 + dfd.resolve([]);
  1030 + }
  1031 + return dfd.promise();
  1032 + },
  1033 +
  1034 + _handleFileTreeEntries: function (entries, path) {
  1035 + var that = this;
  1036 + return $.when.apply(
  1037 + $,
  1038 + $.map(entries, function (entry) {
  1039 + return that._handleFileTreeEntry(entry, path);
  1040 + })
  1041 + ).pipe(function () {
  1042 + return Array.prototype.concat.apply(
  1043 + [],
  1044 + arguments
  1045 + );
  1046 + });
  1047 + },
  1048 +
  1049 + _getDroppedFiles: function (dataTransfer) {
  1050 + dataTransfer = dataTransfer || {};
  1051 + var items = dataTransfer.items;
  1052 + if (items && items.length && (items[0].webkitGetAsEntry ||
  1053 + items[0].getAsEntry)) {
  1054 + return this._handleFileTreeEntries(
  1055 + $.map(items, function (item) {
  1056 + var entry;
  1057 + if (item.webkitGetAsEntry) {
  1058 + entry = item.webkitGetAsEntry();
  1059 + if (entry) {
  1060 + // Workaround for Chrome bug #149735:
  1061 + entry._file = item.getAsFile();
  1062 + }
  1063 + return entry;
  1064 + }
  1065 + return item.getAsEntry();
  1066 + })
  1067 + );
  1068 + }
  1069 + return $.Deferred().resolve(
  1070 + $.makeArray(dataTransfer.files)
  1071 + ).promise();
  1072 + },
  1073 +
  1074 + _getSingleFileInputFiles: function (fileInput) {
  1075 + fileInput = $(fileInput);
  1076 + var entries = fileInput.prop('webkitEntries') ||
  1077 + fileInput.prop('entries'),
  1078 + files,
  1079 + value;
  1080 + if (entries && entries.length) {
  1081 + return this._handleFileTreeEntries(entries);
  1082 + }
  1083 + files = $.makeArray(fileInput.prop('files'));
  1084 + if (!files.length) {
  1085 + value = fileInput.prop('value');
  1086 + if (!value) {
  1087 + return $.Deferred().resolve([]).promise();
  1088 + }
  1089 + // If the files property is not available, the browser does not
  1090 + // support the File API and we add a pseudo File object with
  1091 + // the input value as name with path information removed:
  1092 + files = [{name: value.replace(/^.*\\/, '')}];
  1093 + } else if (files[0].name === undefined && files[0].fileName) {
  1094 + // File normalization for Safari 4 and Firefox 3:
  1095 + $.each(files, function (index, file) {
  1096 + file.name = file.fileName;
  1097 + file.size = file.fileSize;
  1098 + });
  1099 + }
  1100 + return $.Deferred().resolve(files).promise();
  1101 + },
  1102 +
  1103 + _getFileInputFiles: function (fileInput) {
  1104 + if (!(fileInput instanceof $) || fileInput.length === 1) {
  1105 + return this._getSingleFileInputFiles(fileInput);
  1106 + }
  1107 + return $.when.apply(
  1108 + $,
  1109 + $.map(fileInput, this._getSingleFileInputFiles)
  1110 + ).pipe(function () {
  1111 + return Array.prototype.concat.apply(
  1112 + [],
  1113 + arguments
  1114 + );
  1115 + });
  1116 + },
  1117 +
  1118 + _onChange: function (e) {
  1119 + var that = this,
  1120 + data = {
  1121 + fileInput: $(e.target),
  1122 + form: $(e.target.form)
  1123 + };
  1124 + this._getFileInputFiles(data.fileInput).always(function (files) {
  1125 + data.files = files;
  1126 + if (that.options.replaceFileInput) {
  1127 + that._replaceFileInput(data.fileInput);
  1128 + }
  1129 + if (that._trigger(
  1130 + 'change',
  1131 + $.Event('change', {delegatedEvent: e}),
  1132 + data
  1133 + ) !== false) {
  1134 + that._onAdd(e, data);
  1135 + }
  1136 + });
  1137 + },
  1138 +
  1139 + _onPaste: function (e) {
  1140 + var items = e.originalEvent && e.originalEvent.clipboardData &&
  1141 + e.originalEvent.clipboardData.items,
  1142 + data = {files: []};
  1143 + if (items && items.length) {
  1144 + $.each(items, function (index, item) {
  1145 + var file = item.getAsFile && item.getAsFile();
  1146 + if (file) {
  1147 + data.files.push(file);
  1148 + }
  1149 + });
  1150 + if (this._trigger(
  1151 + 'paste',
  1152 + $.Event('paste', {delegatedEvent: e}),
  1153 + data
  1154 + ) !== false) {
  1155 + this._onAdd(e, data);
  1156 + }
  1157 + }
  1158 + },
  1159 +
  1160 + _onDrop: function (e) {
  1161 + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
  1162 + var that = this,
  1163 + dataTransfer = e.dataTransfer,
  1164 + data = {};
  1165 + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
  1166 + e.preventDefault();
  1167 + this._getDroppedFiles(dataTransfer).always(function (files) {
  1168 + data.files = files;
  1169 + if (that._trigger(
  1170 + 'drop',
  1171 + $.Event('drop', {delegatedEvent: e}),
  1172 + data
  1173 + ) !== false) {
  1174 + that._onAdd(e, data);
  1175 + }
  1176 + });
  1177 + }
  1178 + },
  1179 +
  1180 + _onDragOver: function (e) {
  1181 + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
  1182 + var dataTransfer = e.dataTransfer;
  1183 + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
  1184 + this._trigger(
  1185 + 'dragover',
  1186 + $.Event('dragover', {delegatedEvent: e})
  1187 + ) !== false) {
  1188 + e.preventDefault();
  1189 + dataTransfer.dropEffect = 'copy';
  1190 + }
  1191 + },
  1192 +
  1193 + _initEventHandlers: function () {
  1194 + if (this._isXHRUpload(this.options)) {
  1195 + this._on(this.options.dropZone, {
  1196 + dragover: this._onDragOver,
  1197 + drop: this._onDrop
  1198 + });
  1199 + this._on(this.options.pasteZone, {
  1200 + paste: this._onPaste
  1201 + });
  1202 + }
  1203 + if ($.support.fileInput) {
  1204 + this._on(this.options.fileInput, {
  1205 + change: this._onChange
  1206 + });
  1207 + }
  1208 + },
  1209 +
  1210 + _destroyEventHandlers: function () {
  1211 + this._off(this.options.dropZone, 'dragover drop');
  1212 + this._off(this.options.pasteZone, 'paste');
  1213 + this._off(this.options.fileInput, 'change');
  1214 + },
  1215 +
  1216 + _setOption: function (key, value) {
  1217 + var reinit = $.inArray(key, this._specialOptions) !== -1;
  1218 + if (reinit) {
  1219 + this._destroyEventHandlers();
  1220 + }
  1221 + this._super(key, value);
  1222 + if (reinit) {
  1223 + this._initSpecialOptions();
  1224 + this._initEventHandlers();
  1225 + }
  1226 + },
  1227 +
  1228 + _initSpecialOptions: function () {
  1229 + var options = this.options;
  1230 + if (options.fileInput === undefined) {
  1231 + options.fileInput = this.element.is('input[type="file"]') ?
  1232 + this.element : this.element.find('input[type="file"]');
  1233 + } else if (!(options.fileInput instanceof $)) {
  1234 + options.fileInput = $(options.fileInput);
  1235 + }
  1236 + if (!(options.dropZone instanceof $)) {
  1237 + options.dropZone = $(options.dropZone);
  1238 + }
  1239 + if (!(options.pasteZone instanceof $)) {
  1240 + options.pasteZone = $(options.pasteZone);
  1241 + }
  1242 + },
  1243 +
  1244 + _getRegExp: function (str) {
  1245 + var parts = str.split('/'),
  1246 + modifiers = parts.pop();
  1247 + parts.shift();
  1248 + return new RegExp(parts.join('/'), modifiers);
  1249 + },
  1250 +
  1251 + _isRegExpOption: function (key, value) {
  1252 + return key !== 'url' && $.type(value) === 'string' &&
  1253 + /^\/.*\/[igm]{0,3}$/.test(value);
  1254 + },
  1255 +
  1256 + _initDataAttributes: function () {
  1257 + var that = this,
  1258 + options = this.options;
  1259 + // Initialize options set via HTML5 data-attributes:
  1260 + $.each(
  1261 + $(this.element[0].cloneNode(false)).data(),
  1262 + function (key, value) {
  1263 + if (that._isRegExpOption(key, value)) {
  1264 + value = that._getRegExp(value);
  1265 + }
  1266 + options[key] = value;
  1267 + }
  1268 + );
  1269 + },
  1270 +
  1271 + _create: function () {
  1272 + this._initDataAttributes();
  1273 + this._initSpecialOptions();
  1274 + this._slots = [];
  1275 + this._sequence = this._getXHRPromise(true);
  1276 + this._sending = this._active = 0;
  1277 + this._initProgressObject(this);
  1278 + this._initEventHandlers();
  1279 + },
  1280 +
  1281 + // This method is exposed to the widget API and allows to query
  1282 + // the number of active uploads:
  1283 + active: function () {
  1284 + return this._active;
  1285 + },
  1286 +
  1287 + // This method is exposed to the widget API and allows to query
  1288 + // the widget upload progress.
  1289 + // It returns an object with loaded, total and bitrate properties
  1290 + // for the running uploads:
  1291 + progress: function () {
  1292 + return this._progress;
  1293 + },
  1294 +
  1295 + // This method is exposed to the widget API and allows adding files
  1296 + // using the fileupload API. The data parameter accepts an object which
  1297 + // must have a files property and can contain additional options:
  1298 + // .fileupload('add', {files: filesList});
  1299 + add: function (data) {
  1300 + var that = this;
  1301 + if (!data || this.options.disabled) {
  1302 + return;
  1303 + }
  1304 + if (data.fileInput && !data.files) {
  1305 + this._getFileInputFiles(data.fileInput).always(function (files) {
  1306 + data.files = files;
  1307 + that._onAdd(null, data);
  1308 + });
  1309 + } else {
  1310 + data.files = $.makeArray(data.files);
  1311 + this._onAdd(null, data);
  1312 + }
  1313 + },
  1314 +
  1315 + // This method is exposed to the widget API and allows sending files
  1316 + // using the fileupload API. The data parameter accepts an object which
  1317 + // must have a files or fileInput property and can contain additional options:
  1318 + // .fileupload('send', {files: filesList});
  1319 + // The method returns a Promise object for the file upload call.
  1320 + send: function (data) {
  1321 + if (data && !this.options.disabled) {
  1322 + if (data.fileInput && !data.files) {
  1323 + var that = this,
  1324 + dfd = $.Deferred(),
  1325 + promise = dfd.promise(),
  1326 + jqXHR,
  1327 + aborted;
  1328 + promise.abort = function () {
  1329 + aborted = true;
  1330 + if (jqXHR) {
  1331 + return jqXHR.abort();
  1332 + }
  1333 + dfd.reject(null, 'abort', 'abort');
  1334 + return promise;
  1335 + };
  1336 + this._getFileInputFiles(data.fileInput).always(
  1337 + function (files) {
  1338 + if (aborted) {
  1339 + return;
  1340 + }
  1341 + if (!files.length) {
  1342 + dfd.reject();
  1343 + return;
  1344 + }
  1345 + data.files = files;
  1346 + jqXHR = that._onSend(null, data).then(
  1347 + function (result, textStatus, jqXHR) {
  1348 + dfd.resolve(result, textStatus, jqXHR);
  1349 + },
  1350 + function (jqXHR, textStatus, errorThrown) {
  1351 + dfd.reject(jqXHR, textStatus, errorThrown);
  1352 + }
  1353 + );
  1354 + }
  1355 + );
  1356 + return this._enhancePromise(promise);
  1357 + }
  1358 + data.files = $.makeArray(data.files);
  1359 + if (data.files.length) {
  1360 + return this._onSend(null, data);
  1361 + }
  1362 + }
  1363 + return this._getXHRPromise(false, data && data.context);
  1364 + }
  1365 +
  1366 + });
  1367 +
  1368 +}));
... ...
forms/jquery.fileupload_1.js 0 → 100644
  1 +++ a/forms/jquery.fileupload_1.js
  1 +/*
  2 + * jQuery File Upload Plugin 5.34.0
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2010, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint nomen: true, unparam: true, regexp: true */
  13 +/*global define, window, document, location, File, Blob, FormData */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define([
  20 + 'jquery',
  21 + 'jquery.ui.widget'
  22 + ], factory);
  23 + } else {
  24 + // Browser globals:
  25 + factory(window.jQuery);
  26 + }
  27 +}(function ($) {
  28 + 'use strict';
  29 +
  30 + // Detect file input support, based on
  31 + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
  32 + $.support.fileInput = !(new RegExp(
  33 + // Handle devices which give false positives for the feature detection:
  34 + '(Android (1\\.[0156]|2\\.[01]))' +
  35 + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
  36 + '|(w(eb)?OSBrowser)|(webOS)' +
  37 + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
  38 + ).test(window.navigator.userAgent) ||
  39 + // Feature detection for all other devices:
  40 + $('<input type="file">').prop('disabled'));
  41 +
  42 + // The FileReader API is not actually used, but works as feature detection,
  43 + // as e.g. Safari supports XHR file uploads via the FormData API,
  44 + // but not non-multipart XHR file uploads:
  45 + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
  46 + $.support.xhrFormDataFileUpload = !!window.FormData;
  47 +
  48 + // Detect support for Blob slicing (required for chunked uploads):
  49 + $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
  50 + Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
  51 +
  52 + // The fileupload widget listens for change events on file input fields defined
  53 + // via fileInput setting and paste or drop events of the given dropZone.
  54 + // In addition to the default jQuery Widget methods, the fileupload widget
  55 + // exposes the "add" and "send" methods, to add or directly send files using
  56 + // the fileupload API.
  57 + // By default, files added via file input selection, paste, drag & drop or
  58 + // "add" method are uploaded immediately, but it is possible to override
  59 + // the "add" callback option to queue file uploads.
  60 + $.widget('blueimp.fileupload', {
  61 +
  62 + options: {
  63 + // The drop target element(s), by the default the complete document.
  64 + // Set to null to disable drag & drop support:
  65 + dropZone: $(document),
  66 + // The paste target element(s), by the default the complete document.
  67 + // Set to null to disable paste support:
  68 + pasteZone: $(document),
  69 + // The file input field(s), that are listened to for change events.
  70 + // If undefined, it is set to the file input fields inside
  71 + // of the widget element on plugin initialization.
  72 + // Set to null to disable the change listener.
  73 + fileInput: undefined,
  74 + // By default, the file input field is replaced with a clone after
  75 + // each input field change event. This is required for iframe transport
  76 + // queues and allows change events to be fired for the same file
  77 + // selection, but can be disabled by setting the following option to false:
  78 + replaceFileInput: true,
  79 + // The parameter name for the file form data (the request argument name).
  80 + // If undefined or empty, the name property of the file input field is
  81 + // used, or "files[]" if the file input name property is also empty,
  82 + // can be a string or an array of strings:
  83 + paramName: undefined,
  84 + // By default, each file of a selection is uploaded using an individual
  85 + // request for XHR type uploads. Set to false to upload file
  86 + // selections in one request each:
  87 + singleFileUploads: true,
  88 + // To limit the number of files uploaded with one XHR request,
  89 + // set the following option to an integer greater than 0:
  90 + limitMultiFileUploads: undefined,
  91 + // Set the following option to true to issue all file upload requests
  92 + // in a sequential order:
  93 + sequentialUploads: false,
  94 + // To limit the number of concurrent uploads,
  95 + // set the following option to an integer greater than 0:
  96 + limitConcurrentUploads: undefined,
  97 + // Set the following option to true to force iframe transport uploads:
  98 + forceIframeTransport: false,
  99 + // Set the following option to the location of a redirect url on the
  100 + // origin server, for cross-domain iframe transport uploads:
  101 + redirect: undefined,
  102 + // The parameter name for the redirect url, sent as part of the form
  103 + // data and set to 'redirect' if this option is empty:
  104 + redirectParamName: undefined,
  105 + // Set the following option to the location of a postMessage window,
  106 + // to enable postMessage transport uploads:
  107 + postMessage: undefined,
  108 + // By default, XHR file uploads are sent as multipart/form-data.
  109 + // The iframe transport is always using multipart/form-data.
  110 + // Set to false to enable non-multipart XHR uploads:
  111 + multipart: true,
  112 + // To upload large files in smaller chunks, set the following option
  113 + // to a preferred maximum chunk size. If set to 0, null or undefined,
  114 + // or the browser does not support the required Blob API, files will
  115 + // be uploaded as a whole.
  116 + maxChunkSize: undefined,
  117 + // When a non-multipart upload or a chunked multipart upload has been
  118 + // aborted, this option can be used to resume the upload by setting
  119 + // it to the size of the already uploaded bytes. This option is most
  120 + // useful when modifying the options object inside of the "add" or
  121 + // "send" callbacks, as the options are cloned for each file upload.
  122 + uploadedBytes: undefined,
  123 + // By default, failed (abort or error) file uploads are removed from the
  124 + // global progress calculation. Set the following option to false to
  125 + // prevent recalculating the global progress data:
  126 + recalculateProgress: true,
  127 + // Interval in milliseconds to calculate and trigger progress events:
  128 + progressInterval: 100,
  129 + // Interval in milliseconds to calculate progress bitrate:
  130 + bitrateInterval: 500,
  131 + // By default, uploads are started automatically when adding files:
  132 + autoUpload: true,
  133 +
  134 + // Error and info messages:
  135 + messages: {
  136 + uploadedBytes: 'Uploaded bytes exceed file size'
  137 + },
  138 +
  139 + // Translation function, gets the message key to be translated
  140 + // and an object with context specific data as arguments:
  141 + i18n: function (message, context) {
  142 + message = this.messages[message] || message.toString();
  143 + if (context) {
  144 + $.each(context, function (key, value) {
  145 + message = message.replace('{' + key + '}', value);
  146 + });
  147 + }
  148 + return message;
  149 + },
  150 +
  151 + // Additional form data to be sent along with the file uploads can be set
  152 + // using this option, which accepts an array of objects with name and
  153 + // value properties, a function returning such an array, a FormData
  154 + // object (for XHR file uploads), or a simple object.
  155 + // The form of the first fileInput is given as parameter to the function:
  156 + formData: function (form) {
  157 + return form.serializeArray();
  158 + },
  159 +
  160 + // The add callback is invoked as soon as files are added to the fileupload
  161 + // widget (via file input selection, drag & drop, paste or add API call).
  162 + // If the singleFileUploads option is enabled, this callback will be
  163 + // called once for each file in the selection for XHR file uploads, else
  164 + // once for each file selection.
  165 + //
  166 + // The upload starts when the submit method is invoked on the data parameter.
  167 + // The data object contains a files property holding the added files
  168 + // and allows you to override plugin options as well as define ajax settings.
  169 + //
  170 + // Listeners for this callback can also be bound the following way:
  171 + // .bind('fileuploadadd', func);
  172 + //
  173 + // data.submit() returns a Promise object and allows to attach additional
  174 + // handlers using jQuery's Deferred callbacks:
  175 + // data.submit().done(func).fail(func).always(func);
  176 + add: function (e, data) {
  177 + if (e.isDefaultPrevented()) {
  178 + return false;
  179 + }
  180 + if (data.autoUpload || (data.autoUpload !== false &&
  181 + $(this).fileupload('option', 'autoUpload'))) {
  182 + data.process().done(function () {
  183 + data.submit();
  184 + });
  185 + }
  186 + },
  187 +
  188 + // Other callbacks:
  189 +
  190 + // Callback for the submit event of each file upload:
  191 + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
  192 +
  193 + // Callback for the start of each file upload request:
  194 + // send: function (e, data) {}, // .bind('fileuploadsend', func);
  195 +
  196 + // Callback for successful uploads:
  197 + // done: function (e, data) {}, // .bind('fileuploaddone', func);
  198 +
  199 + // Callback for failed (abort or error) uploads:
  200 + // fail: function (e, data) {}, // .bind('fileuploadfail', func);
  201 +
  202 + // Callback for completed (success, abort or error) requests:
  203 + // always: function (e, data) {}, // .bind('fileuploadalways', func);
  204 +
  205 + // Callback for upload progress events:
  206 + // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
  207 +
  208 + // Callback for global upload progress events:
  209 + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
  210 +
  211 + // Callback for uploads start, equivalent to the global ajaxStart event:
  212 + // start: function (e) {}, // .bind('fileuploadstart', func);
  213 +
  214 + // Callback for uploads stop, equivalent to the global ajaxStop event:
  215 + // stop: function (e) {}, // .bind('fileuploadstop', func);
  216 +
  217 + // Callback for change events of the fileInput(s):
  218 + // change: function (e, data) {}, // .bind('fileuploadchange', func);
  219 +
  220 + // Callback for paste events to the pasteZone(s):
  221 + // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
  222 +
  223 + // Callback for drop events of the dropZone(s):
  224 + // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
  225 +
  226 + // Callback for dragover events of the dropZone(s):
  227 + // dragover: function (e) {}, // .bind('fileuploaddragover', func);
  228 +
  229 + // Callback for the start of each chunk upload request:
  230 + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
  231 +
  232 + // Callback for successful chunk uploads:
  233 + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
  234 +
  235 + // Callback for failed (abort or error) chunk uploads:
  236 + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
  237 +
  238 + // Callback for completed (success, abort or error) chunk upload requests:
  239 + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
  240 +
  241 + // The plugin options are used as settings object for the ajax calls.
  242 + // The following are jQuery ajax settings required for the file uploads:
  243 + processData: false,
  244 + contentType: false,
  245 + cache: false
  246 + },
  247 +
  248 + // A list of options that require reinitializing event listeners and/or
  249 + // special initialization code:
  250 + _specialOptions: [
  251 + 'fileInput',
  252 + 'dropZone',
  253 + 'pasteZone',
  254 + 'multipart',
  255 + 'forceIframeTransport'
  256 + ],
  257 +
  258 + _blobSlice: $.support.blobSlice && function () {
  259 + var slice = this.slice || this.webkitSlice || this.mozSlice;
  260 + return slice.apply(this, arguments);
  261 + },
  262 +
  263 + _BitrateTimer: function () {
  264 + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
  265 + this.loaded = 0;
  266 + this.bitrate = 0;
  267 + this.getBitrate = function (now, loaded, interval) {
  268 + var timeDiff = now - this.timestamp;
  269 + if (!this.bitrate || !interval || timeDiff > interval) {
  270 + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
  271 + this.loaded = loaded;
  272 + this.timestamp = now;
  273 + }
  274 + return this.bitrate;
  275 + };
  276 + },
  277 +
  278 + _isXHRUpload: function (options) {
  279 + return !options.forceIframeTransport &&
  280 + ((!options.multipart && $.support.xhrFileUpload) ||
  281 + $.support.xhrFormDataFileUpload);
  282 + },
  283 +
  284 + _getFormData: function (options) {
  285 + var formData;
  286 + if (typeof options.formData === 'function') {
  287 + return options.formData(options.form);
  288 + }
  289 + if ($.isArray(options.formData)) {
  290 + return options.formData;
  291 + }
  292 + if ($.type(options.formData) === 'object') {
  293 + formData = [];
  294 + $.each(options.formData, function (name, value) {
  295 + formData.push({name: name, value: value});
  296 + });
  297 + return formData;
  298 + }
  299 + return [];
  300 + },
  301 +
  302 + _getTotal: function (files) {
  303 + var total = 0;
  304 + $.each(files, function (index, file) {
  305 + total += file.size || 1;
  306 + });
  307 + return total;
  308 + },
  309 +
  310 + _initProgressObject: function (obj) {
  311 + var progress = {
  312 + loaded: 0,
  313 + total: 0,
  314 + bitrate: 0
  315 + };
  316 + if (obj._progress) {
  317 + $.extend(obj._progress, progress);
  318 + } else {
  319 + obj._progress = progress;
  320 + }
  321 + },
  322 +
  323 + _initResponseObject: function (obj) {
  324 + var prop;
  325 + if (obj._response) {
  326 + for (prop in obj._response) {
  327 + if (obj._response.hasOwnProperty(prop)) {
  328 + delete obj._response[prop];
  329 + }
  330 + }
  331 + } else {
  332 + obj._response = {};
  333 + }
  334 + },
  335 +
  336 + _onProgress: function (e, data) {
  337 + if (e.lengthComputable) {
  338 + var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
  339 + loaded;
  340 + if (data._time && data.progressInterval &&
  341 + (now - data._time < data.progressInterval) &&
  342 + e.loaded !== e.total) {
  343 + return;
  344 + }
  345 + data._time = now;
  346 + loaded = Math.floor(
  347 + e.loaded / e.total * (data.chunkSize || data._progress.total)
  348 + ) + (data.uploadedBytes || 0);
  349 + // Add the difference from the previously loaded state
  350 + // to the global loaded counter:
  351 + this._progress.loaded += (loaded - data._progress.loaded);
  352 + this._progress.bitrate = this._bitrateTimer.getBitrate(
  353 + now,
  354 + this._progress.loaded,
  355 + data.bitrateInterval
  356 + );
  357 + data._progress.loaded = data.loaded = loaded;
  358 + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
  359 + now,
  360 + loaded,
  361 + data.bitrateInterval
  362 + );
  363 + // Trigger a custom progress event with a total data property set
  364 + // to the file size(s) of the current upload and a loaded data
  365 + // property calculated accordingly:
  366 + this._trigger(
  367 + 'progress',
  368 + $.Event('progress', {delegatedEvent: e}),
  369 + data
  370 + );
  371 + // Trigger a global progress event for all current file uploads,
  372 + // including ajax calls queued for sequential file uploads:
  373 + this._trigger(
  374 + 'progressall',
  375 + $.Event('progressall', {delegatedEvent: e}),
  376 + this._progress
  377 + );
  378 + }
  379 + },
  380 +
  381 + _initProgressListener: function (options) {
  382 + var that = this,
  383 + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
  384 + // Accesss to the native XHR object is required to add event listeners
  385 + // for the upload progress event:
  386 + if (xhr.upload) {
  387 + $(xhr.upload).bind('progress', function (e) {
  388 + var oe = e.originalEvent;
  389 + // Make sure the progress event properties get copied over:
  390 + e.lengthComputable = oe.lengthComputable;
  391 + e.loaded = oe.loaded;
  392 + e.total = oe.total;
  393 + that._onProgress(e, options);
  394 + });
  395 + options.xhr = function () {
  396 + return xhr;
  397 + };
  398 + }
  399 + },
  400 +
  401 + _isInstanceOf: function (type, obj) {
  402 + // Cross-frame instanceof check
  403 + return Object.prototype.toString.call(obj) === '[object ' + type + ']';
  404 + },
  405 +
  406 + _initXHRData: function (options) {
  407 + var that = this,
  408 + formData,
  409 + file = options.files[0],
  410 + // Ignore non-multipart setting if not supported:
  411 + multipart = options.multipart || !$.support.xhrFileUpload,
  412 + paramName = options.paramName[0];
  413 + options.headers = $.extend({}, options.headers);
  414 + if (options.contentRange) {
  415 + options.headers['Content-Range'] = options.contentRange;
  416 + }
  417 + if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
  418 + options.headers['Content-Disposition'] = 'attachment; filename="' +
  419 + encodeURI(file.name) + '"';
  420 + }
  421 + if (!multipart) {
  422 + options.contentType = file.type;
  423 + options.data = options.blob || file;
  424 + } else if ($.support.xhrFormDataFileUpload) {
  425 + if (options.postMessage) {
  426 + // window.postMessage does not allow sending FormData
  427 + // objects, so we just add the File/Blob objects to
  428 + // the formData array and let the postMessage window
  429 + // create the FormData object out of this array:
  430 + formData = this._getFormData(options);
  431 + if (options.blob) {
  432 + formData.push({
  433 + name: paramName,
  434 + value: options.blob
  435 + });
  436 + } else {
  437 + $.each(options.files, function (index, file) {
  438 + formData.push({
  439 + name: options.paramName[index] || paramName,
  440 + value: file
  441 + });
  442 + });
  443 + }
  444 + } else {
  445 + if (that._isInstanceOf('FormData', options.formData)) {
  446 + formData = options.formData;
  447 + } else {
  448 + formData = new FormData();
  449 + $.each(this._getFormData(options), function (index, field) {
  450 + formData.append(field.name, field.value);
  451 + });
  452 + }
  453 + if (options.blob) {
  454 + formData.append(paramName, options.blob, file.name);
  455 + } else {
  456 + $.each(options.files, function (index, file) {
  457 + // This check allows the tests to run with
  458 + // dummy objects:
  459 + if (that._isInstanceOf('File', file) ||
  460 + that._isInstanceOf('Blob', file)) {
  461 + formData.append(
  462 + options.paramName[index] || paramName,
  463 + file,
  464 + file.name
  465 + );
  466 + }
  467 + });
  468 + }
  469 + }
  470 + options.data = formData;
  471 + }
  472 + // Blob reference is not needed anymore, free memory:
  473 + options.blob = null;
  474 + },
  475 +
  476 + _initIframeSettings: function (options) {
  477 + var targetHost = $('<a></a>').prop('href', options.url).prop('host');
  478 + // Setting the dataType to iframe enables the iframe transport:
  479 + options.dataType = 'iframe ' + (options.dataType || '');
  480 + // The iframe transport accepts a serialized array as form data:
  481 + options.formData = this._getFormData(options);
  482 + // Add redirect url to form data on cross-domain uploads:
  483 + if (options.redirect && targetHost && targetHost !== location.host) {
  484 + options.formData.push({
  485 + name: options.redirectParamName || 'redirect',
  486 + value: options.redirect
  487 + });
  488 + }
  489 + },
  490 +
  491 + _initDataSettings: function (options) {
  492 + if (this._isXHRUpload(options)) {
  493 + if (!this._chunkedUpload(options, true)) {
  494 + if (!options.data) {
  495 + this._initXHRData(options);
  496 + }
  497 + this._initProgressListener(options);
  498 + }
  499 + if (options.postMessage) {
  500 + // Setting the dataType to postmessage enables the
  501 + // postMessage transport:
  502 + options.dataType = 'postmessage ' + (options.dataType || '');
  503 + }
  504 + } else {
  505 + this._initIframeSettings(options);
  506 + }
  507 + },
  508 +
  509 + _getParamName: function (options) {
  510 + var fileInput = $(options.fileInput),
  511 + paramName = options.paramName;
  512 + if (!paramName) {
  513 + paramName = [];
  514 + fileInput.each(function () {
  515 + var input = $(this),
  516 + name = input.prop('name') || 'files[]',
  517 + i = (input.prop('files') || [1]).length;
  518 + while (i) {
  519 + paramName.push(name);
  520 + i -= 1;
  521 + }
  522 + });
  523 + if (!paramName.length) {
  524 + paramName = [fileInput.prop('name') || 'files[]'];
  525 + }
  526 + } else if (!$.isArray(paramName)) {
  527 + paramName = [paramName];
  528 + }
  529 + return paramName;
  530 + },
  531 +
  532 + _initFormSettings: function (options) {
  533 + // Retrieve missing options from the input field and the
  534 + // associated form, if available:
  535 + if (!options.form || !options.form.length) {
  536 + options.form = $(options.fileInput.prop('form'));
  537 + // If the given file input doesn't have an associated form,
  538 + // use the default widget file input's form:
  539 + if (!options.form.length) {
  540 + options.form = $(this.options.fileInput.prop('form'));
  541 + }
  542 + }
  543 + options.paramName = this._getParamName(options);
  544 + if (!options.url) {
  545 + options.url = options.form.prop('action') || location.href;
  546 + }
  547 + // The HTTP request method must be "POST" or "PUT":
  548 + options.type = (options.type ||
  549 + ($.type(options.form.prop('method')) === 'string' &&
  550 + options.form.prop('method')) || ''
  551 + ).toUpperCase();
  552 + if (options.type !== 'POST' && options.type !== 'PUT' &&
  553 + options.type !== 'PATCH') {
  554 + options.type = 'POST';
  555 + }
  556 + if (!options.formAcceptCharset) {
  557 + options.formAcceptCharset = options.form.attr('accept-charset');
  558 + }
  559 + },
  560 +
  561 + _getAJAXSettings: function (data) {
  562 + var options = $.extend({}, this.options, data);
  563 + this._initFormSettings(options);
  564 + this._initDataSettings(options);
  565 + options.url = options.url+'&rand=' + new Date().getTime();
  566 + return options;
  567 + },
  568 +
  569 + // jQuery 1.6 doesn't provide .state(),
  570 + // while jQuery 1.8+ removed .isRejected() and .isResolved():
  571 + _getDeferredState: function (deferred) {
  572 + if (deferred.state) {
  573 + return deferred.state();
  574 + }
  575 + if (deferred.isResolved()) {
  576 + return 'resolved';
  577 + }
  578 + if (deferred.isRejected()) {
  579 + return 'rejected';
  580 + }
  581 + return 'pending';
  582 + },
  583 +
  584 + // Maps jqXHR callbacks to the equivalent
  585 + // methods of the given Promise object:
  586 + _enhancePromise: function (promise) {
  587 + promise.success = promise.done;
  588 + promise.error = promise.fail;
  589 + promise.complete = promise.always;
  590 + return promise;
  591 + },
  592 +
  593 + // Creates and returns a Promise object enhanced with
  594 + // the jqXHR methods abort, success, error and complete:
  595 + _getXHRPromise: function (resolveOrReject, context, args) {
  596 + var dfd = $.Deferred(),
  597 + promise = dfd.promise();
  598 + context = context || this.options.context || promise;
  599 + if (resolveOrReject === true) {
  600 + dfd.resolveWith(context, args);
  601 + } else if (resolveOrReject === false) {
  602 + dfd.rejectWith(context, args);
  603 + }
  604 + promise.abort = dfd.promise;
  605 + return this._enhancePromise(promise);
  606 + },
  607 +
  608 + // Adds convenience methods to the data callback argument:
  609 + _addConvenienceMethods: function (e, data) {
  610 + var that = this,
  611 + getPromise = function (data) {
  612 + return $.Deferred().resolveWith(that, [data]).promise();
  613 + };
  614 + data.process = function (resolveFunc, rejectFunc) {
  615 + if (resolveFunc || rejectFunc) {
  616 + data._processQueue = this._processQueue =
  617 + (this._processQueue || getPromise(this))
  618 + .pipe(resolveFunc, rejectFunc);
  619 + }
  620 + return this._processQueue || getPromise(this);
  621 + };
  622 + data.submit = function () {
  623 + if (this.state() !== 'pending') {
  624 + data.jqXHR = this.jqXHR =
  625 + (that._trigger(
  626 + 'submit',
  627 + $.Event('submit', {delegatedEvent: e}),
  628 + this
  629 + ) !== false) && that._onSend(e, this);
  630 + }
  631 + return this.jqXHR || that._getXHRPromise();
  632 + };
  633 + data.abort = function () {
  634 + if (this.jqXHR) {
  635 + return this.jqXHR.abort();
  636 + }
  637 + return that._getXHRPromise();
  638 + };
  639 + data.state = function () {
  640 + if (this.jqXHR) {
  641 + return that._getDeferredState(this.jqXHR);
  642 + }
  643 + if (this._processQueue) {
  644 + return that._getDeferredState(this._processQueue);
  645 + }
  646 + };
  647 + data.progress = function () {
  648 + return this._progress;
  649 + };
  650 + data.response = function () {
  651 + return this._response;
  652 + };
  653 + },
  654 +
  655 + // Parses the Range header from the server response
  656 + // and returns the uploaded bytes:
  657 + _getUploadedBytes: function (jqXHR) {
  658 + var range = jqXHR.getResponseHeader('Range'),
  659 + parts = range && range.split('-'),
  660 + upperBytesPos = parts && parts.length > 1 &&
  661 + parseInt(parts[1], 10);
  662 + return upperBytesPos && upperBytesPos + 1;
  663 + },
  664 +
  665 + // Uploads a file in multiple, sequential requests
  666 + // by splitting the file up in multiple blob chunks.
  667 + // If the second parameter is true, only tests if the file
  668 + // should be uploaded in chunks, but does not invoke any
  669 + // upload requests:
  670 + _chunkedUpload: function (options, testOnly) {
  671 + options.uploadedBytes = options.uploadedBytes || 0;
  672 + var that = this,
  673 + file = options.files[0],
  674 + fs = file.size,
  675 + ub = options.uploadedBytes,
  676 + mcs = options.maxChunkSize || fs,
  677 + slice = this._blobSlice,
  678 + dfd = $.Deferred(),
  679 + promise = dfd.promise(),
  680 + jqXHR,
  681 + upload;
  682 + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
  683 + options.data) {
  684 + return false;
  685 + }
  686 + if (testOnly) {
  687 + return true;
  688 + }
  689 + if (ub >= fs) {
  690 + file.error = options.i18n('uploadedBytes');
  691 + return this._getXHRPromise(
  692 + false,
  693 + options.context,
  694 + [null, 'error', file.error]
  695 + );
  696 + }
  697 + // The chunk upload method:
  698 + upload = function () {
  699 + // Clone the options object for each chunk upload:
  700 + var o = $.extend({}, options),
  701 + currentLoaded = o._progress.loaded;
  702 + o.blob = slice.call(
  703 + file,
  704 + ub,
  705 + ub + mcs,
  706 + file.type
  707 + );
  708 + // Store the current chunk size, as the blob itself
  709 + // will be dereferenced after data processing:
  710 + o.chunkSize = o.blob.size;
  711 + // Expose the chunk bytes position range:
  712 + o.contentRange = 'bytes ' + ub + '-' +
  713 + (ub + o.chunkSize - 1) + '/' + fs;
  714 + // Process the upload data (the blob and potential form data):
  715 + that._initXHRData(o);
  716 + // Add progress listeners for this chunk upload:
  717 + that._initProgressListener(o);
  718 + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
  719 + that._getXHRPromise(false, o.context))
  720 + .done(function (result, textStatus, jqXHR) {
  721 + ub = that._getUploadedBytes(jqXHR) ||
  722 + (ub + o.chunkSize);
  723 + // Create a progress event if no final progress event
  724 + // with loaded equaling total has been triggered
  725 + // for this chunk:
  726 + if (currentLoaded + o.chunkSize - o._progress.loaded) {
  727 + that._onProgress($.Event('progress', {
  728 + lengthComputable: true,
  729 + loaded: ub - o.uploadedBytes,
  730 + total: ub - o.uploadedBytes
  731 + }), o);
  732 + }
  733 + options.uploadedBytes = o.uploadedBytes = ub;
  734 + o.result = result;
  735 + o.textStatus = textStatus;
  736 + o.jqXHR = jqXHR;
  737 + that._trigger('chunkdone', null, o);
  738 + that._trigger('chunkalways', null, o);
  739 + if (ub < fs) {
  740 + // File upload not yet complete,
  741 + // continue with the next chunk:
  742 + upload();
  743 + } else {
  744 + dfd.resolveWith(
  745 + o.context,
  746 + [result, textStatus, jqXHR]
  747 + );
  748 + }
  749 + })
  750 + .fail(function (jqXHR, textStatus, errorThrown) {
  751 + o.jqXHR = jqXHR;
  752 + o.textStatus = textStatus;
  753 + o.errorThrown = errorThrown;
  754 + that._trigger('chunkfail', null, o);
  755 + that._trigger('chunkalways', null, o);
  756 + dfd.rejectWith(
  757 + o.context,
  758 + [jqXHR, textStatus, errorThrown]
  759 + );
  760 + });
  761 + };
  762 + this._enhancePromise(promise);
  763 + promise.abort = function () {
  764 + return jqXHR.abort();
  765 + };
  766 + upload();
  767 + return promise;
  768 + },
  769 +
  770 + _beforeSend: function (e, data) {
  771 + if (this._active === 0) {
  772 + // the start callback is triggered when an upload starts
  773 + // and no other uploads are currently running,
  774 + // equivalent to the global ajaxStart event:
  775 + this._trigger('start');
  776 + // Set timer for global bitrate progress calculation:
  777 + this._bitrateTimer = new this._BitrateTimer();
  778 + // Reset the global progress values:
  779 + this._progress.loaded = this._progress.total = 0;
  780 + this._progress.bitrate = 0;
  781 + }
  782 + // Make sure the container objects for the .response() and
  783 + // .progress() methods on the data object are available
  784 + // and reset to their initial state:
  785 + this._initResponseObject(data);
  786 + this._initProgressObject(data);
  787 + data._progress.loaded = data.loaded = data.uploadedBytes || 0;
  788 + data._progress.total = data.total = this._getTotal(data.files) || 1;
  789 + data._progress.bitrate = data.bitrate = 0;
  790 + this._active += 1;
  791 + // Initialize the global progress values:
  792 + this._progress.loaded += data.loaded;
  793 + this._progress.total += data.total;
  794 + },
  795 +
  796 + _onDone: function (result, textStatus, jqXHR, options) {
  797 + var total = options._progress.total,
  798 + response = options._response;
  799 + if (options._progress.loaded < total) {
  800 + // Create a progress event if no final progress event
  801 + // with loaded equaling total has been triggered:
  802 + this._onProgress($.Event('progress', {
  803 + lengthComputable: true,
  804 + loaded: total,
  805 + total: total
  806 + }), options);
  807 + }
  808 + response.result = options.result = result;
  809 + response.textStatus = options.textStatus = textStatus;
  810 + response.jqXHR = options.jqXHR = jqXHR;
  811 + this._trigger('done', null, options);
  812 + },
  813 +
  814 + _onFail: function (jqXHR, textStatus, errorThrown, options) {
  815 + var response = options._response;
  816 + if (options.recalculateProgress) {
  817 + // Remove the failed (error or abort) file upload from
  818 + // the global progress calculation:
  819 + this._progress.loaded -= options._progress.loaded;
  820 + this._progress.total -= options._progress.total;
  821 + }
  822 + response.jqXHR = options.jqXHR = jqXHR;
  823 + response.textStatus = options.textStatus = textStatus;
  824 + response.errorThrown = options.errorThrown = errorThrown;
  825 + this._trigger('fail', null, options);
  826 + },
  827 +
  828 + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
  829 + // jqXHRorResult, textStatus and jqXHRorError are added to the
  830 + // options object via done and fail callbacks
  831 + this._trigger('always', null, options);
  832 + },
  833 +
  834 + _onSend: function (e, data) {
  835 + if (!data.submit) {
  836 + this._addConvenienceMethods(e, data);
  837 + }
  838 + var that = this,
  839 + jqXHR,
  840 + aborted,
  841 + slot,
  842 + pipe,
  843 + options = that._getAJAXSettings(data),
  844 + send = function () {
  845 + that._sending += 1;
  846 + // Set timer for bitrate progress calculation:
  847 + options._bitrateTimer = new that._BitrateTimer();
  848 + jqXHR = jqXHR || (
  849 + ((aborted || that._trigger(
  850 + 'send',
  851 + $.Event('send', {delegatedEvent: e}),
  852 + options
  853 + ) === false) &&
  854 + that._getXHRPromise(false, options.context, aborted)) ||
  855 + that._chunkedUpload(options) || $.ajax(options)
  856 + ).done(function (result, textStatus, jqXHR) {
  857 + that._onDone(result, textStatus, jqXHR, options);
  858 + }).fail(function (jqXHR, textStatus, errorThrown) {
  859 + that._onFail(jqXHR, textStatus, errorThrown, options);
  860 + }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
  861 + that._onAlways(
  862 + jqXHRorResult,
  863 + textStatus,
  864 + jqXHRorError,
  865 + options
  866 + );
  867 + that._sending -= 1;
  868 + that._active -= 1;
  869 + if (options.limitConcurrentUploads &&
  870 + options.limitConcurrentUploads > that._sending) {
  871 + // Start the next queued upload,
  872 + // that has not been aborted:
  873 + var nextSlot = that._slots.shift();
  874 + while (nextSlot) {
  875 + if (that._getDeferredState(nextSlot) === 'pending') {
  876 + nextSlot.resolve();
  877 + break;
  878 + }
  879 + nextSlot = that._slots.shift();
  880 + }
  881 + }
  882 + if (that._active === 0) {
  883 + // The stop callback is triggered when all uploads have
  884 + // been completed, equivalent to the global ajaxStop event:
  885 + that._trigger('stop');
  886 + }
  887 + });
  888 + return jqXHR;
  889 + };
  890 + this._beforeSend(e, options);
  891 + if (this.options.sequentialUploads ||
  892 + (this.options.limitConcurrentUploads &&
  893 + this.options.limitConcurrentUploads <= this._sending)) {
  894 + if (this.options.limitConcurrentUploads > 1) {
  895 + slot = $.Deferred();
  896 + this._slots.push(slot);
  897 + pipe = slot.pipe(send);
  898 + } else {
  899 + this._sequence = this._sequence.pipe(send, send);
  900 + pipe = this._sequence;
  901 + }
  902 + // Return the piped Promise object, enhanced with an abort method,
  903 + // which is delegated to the jqXHR object of the current upload,
  904 + // and jqXHR callbacks mapped to the equivalent Promise methods:
  905 + pipe.abort = function () {
  906 + aborted = [undefined, 'abort', 'abort'];
  907 + if (!jqXHR) {
  908 + if (slot) {
  909 + slot.rejectWith(options.context, aborted);
  910 + }
  911 + return send();
  912 + }
  913 + return jqXHR.abort();
  914 + };
  915 + return this._enhancePromise(pipe);
  916 + }
  917 + return send();
  918 + },
  919 +
  920 + _onAdd: function (e, data) {
  921 + var that = this,
  922 + result = true,
  923 + options = $.extend({}, this.options, data),
  924 + limit = options.limitMultiFileUploads,
  925 + paramName = this._getParamName(options),
  926 + paramNameSet,
  927 + paramNameSlice,
  928 + fileSet,
  929 + i;
  930 + if (!(options.singleFileUploads || limit) ||
  931 + !this._isXHRUpload(options)) {
  932 + fileSet = [data.files];
  933 + paramNameSet = [paramName];
  934 + } else if (!options.singleFileUploads && limit) {
  935 + fileSet = [];
  936 + paramNameSet = [];
  937 + for (i = 0; i < data.files.length; i += limit) {
  938 + fileSet.push(data.files.slice(i, i + limit));
  939 + paramNameSlice = paramName.slice(i, i + limit);
  940 + if (!paramNameSlice.length) {
  941 + paramNameSlice = paramName;
  942 + }
  943 + paramNameSet.push(paramNameSlice);
  944 + }
  945 + } else {
  946 + paramNameSet = paramName;
  947 + }
  948 + data.originalFiles = data.files;
  949 + $.each(fileSet || data.files, function (index, element) {
  950 + var newData = $.extend({}, data);
  951 + newData.files = fileSet ? element : [element];
  952 + newData.paramName = paramNameSet[index];
  953 + that._initResponseObject(newData);
  954 + that._initProgressObject(newData);
  955 + that._addConvenienceMethods(e, newData);
  956 + result = that._trigger(
  957 + 'add',
  958 + $.Event('add', {delegatedEvent: e}),
  959 + newData
  960 + );
  961 + return result;
  962 + });
  963 + return result;
  964 + },
  965 +
  966 + _replaceFileInput: function (input) {
  967 + var inputClone = input.clone(true);
  968 + $('<form></form>').append(inputClone)[0].reset();
  969 + // Detaching allows to insert the fileInput on another form
  970 + // without loosing the file input value:
  971 + input.after(inputClone).detach();
  972 + // Avoid memory leaks with the detached file input:
  973 + $.cleanData(input.unbind('remove'));
  974 + // Replace the original file input element in the fileInput
  975 + // elements set with the clone, which has been copied including
  976 + // event handlers:
  977 + this.options.fileInput = this.options.fileInput.map(function (i, el) {
  978 + if (el === input[0]) {
  979 + return inputClone[0];
  980 + }
  981 + return el;
  982 + });
  983 + // If the widget has been initialized on the file input itself,
  984 + // override this.element with the file input clone:
  985 + if (input[0] === this.element[0]) {
  986 + this.element = inputClone;
  987 + }
  988 + },
  989 +
  990 + _handleFileTreeEntry: function (entry, path) {
  991 + var that = this,
  992 + dfd = $.Deferred(),
  993 + errorHandler = function (e) {
  994 + if (e && !e.entry) {
  995 + e.entry = entry;
  996 + }
  997 + // Since $.when returns immediately if one
  998 + // Deferred is rejected, we use resolve instead.
  999 + // This allows valid files and invalid items
  1000 + // to be returned together in one set:
  1001 + dfd.resolve([e]);
  1002 + },
  1003 + dirReader;
  1004 + path = path || '';
  1005 + if (entry.isFile) {
  1006 + if (entry._file) {
  1007 + // Workaround for Chrome bug #149735
  1008 + entry._file.relativePath = path;
  1009 + dfd.resolve(entry._file);
  1010 + } else {
  1011 + entry.file(function (file) {
  1012 + file.relativePath = path;
  1013 + dfd.resolve(file);
  1014 + }, errorHandler);
  1015 + }
  1016 + } else if (entry.isDirectory) {
  1017 + dirReader = entry.createReader();
  1018 + dirReader.readEntries(function (entries) {
  1019 + that._handleFileTreeEntries(
  1020 + entries,
  1021 + path + entry.name + '/'
  1022 + ).done(function (files) {
  1023 + dfd.resolve(files);
  1024 + }).fail(errorHandler);
  1025 + }, errorHandler);
  1026 + } else {
  1027 + // Return an empy list for file system items
  1028 + // other than files or directories:
  1029 + dfd.resolve([]);
  1030 + }
  1031 + return dfd.promise();
  1032 + },
  1033 +
  1034 + _handleFileTreeEntries: function (entries, path) {
  1035 + var that = this;
  1036 + return $.when.apply(
  1037 + $,
  1038 + $.map(entries, function (entry) {
  1039 + return that._handleFileTreeEntry(entry, path);
  1040 + })
  1041 + ).pipe(function () {
  1042 + return Array.prototype.concat.apply(
  1043 + [],
  1044 + arguments
  1045 + );
  1046 + });
  1047 + },
  1048 +
  1049 + _getDroppedFiles: function (dataTransfer) {
  1050 + dataTransfer = dataTransfer || {};
  1051 + var items = dataTransfer.items;
  1052 + if (items && items.length && (items[0].webkitGetAsEntry ||
  1053 + items[0].getAsEntry)) {
  1054 + return this._handleFileTreeEntries(
  1055 + $.map(items, function (item) {
  1056 + var entry;
  1057 + if (item.webkitGetAsEntry) {
  1058 + entry = item.webkitGetAsEntry();
  1059 + if (entry) {
  1060 + // Workaround for Chrome bug #149735:
  1061 + entry._file = item.getAsFile();
  1062 + }
  1063 + return entry;
  1064 + }
  1065 + return item.getAsEntry();
  1066 + })
  1067 + );
  1068 + }
  1069 + return $.Deferred().resolve(
  1070 + $.makeArray(dataTransfer.files)
  1071 + ).promise();
  1072 + },
  1073 +
  1074 + _getSingleFileInputFiles: function (fileInput) {
  1075 + fileInput = $(fileInput);
  1076 + var entries = fileInput.prop('webkitEntries') ||
  1077 + fileInput.prop('entries'),
  1078 + files,
  1079 + value;
  1080 + if (entries && entries.length) {
  1081 + return this._handleFileTreeEntries(entries);
  1082 + }
  1083 + files = $.makeArray(fileInput.prop('files'));
  1084 + if (!files.length) {
  1085 + value = fileInput.prop('value');
  1086 + if (!value) {
  1087 + return $.Deferred().resolve([]).promise();
  1088 + }
  1089 + // If the files property is not available, the browser does not
  1090 + // support the File API and we add a pseudo File object with
  1091 + // the input value as name with path information removed:
  1092 + files = [{name: value.replace(/^.*\\/, '')}];
  1093 + } else if (files[0].name === undefined && files[0].fileName) {
  1094 + // File normalization for Safari 4 and Firefox 3:
  1095 + $.each(files, function (index, file) {
  1096 + file.name = file.fileName;
  1097 + file.size = file.fileSize;
  1098 + });
  1099 + }
  1100 + return $.Deferred().resolve(files).promise();
  1101 + },
  1102 +
  1103 + _getFileInputFiles: function (fileInput) {
  1104 + if (!(fileInput instanceof $) || fileInput.length === 1) {
  1105 + return this._getSingleFileInputFiles(fileInput);
  1106 + }
  1107 + return $.when.apply(
  1108 + $,
  1109 + $.map(fileInput, this._getSingleFileInputFiles)
  1110 + ).pipe(function () {
  1111 + return Array.prototype.concat.apply(
  1112 + [],
  1113 + arguments
  1114 + );
  1115 + });
  1116 + },
  1117 +
  1118 + _onChange: function (e) {
  1119 + var that = this,
  1120 + data = {
  1121 + fileInput: $(e.target),
  1122 + form: $(e.target.form)
  1123 + };
  1124 + this._getFileInputFiles(data.fileInput).always(function (files) {
  1125 + data.files = files;
  1126 + if (that.options.replaceFileInput) {
  1127 + that._replaceFileInput(data.fileInput);
  1128 + }
  1129 + if (that._trigger(
  1130 + 'change',
  1131 + $.Event('change', {delegatedEvent: e}),
  1132 + data
  1133 + ) !== false) {
  1134 + that._onAdd(e, data);
  1135 + }
  1136 + });
  1137 + },
  1138 +
  1139 + _onPaste: function (e) {
  1140 + var items = e.originalEvent && e.originalEvent.clipboardData &&
  1141 + e.originalEvent.clipboardData.items,
  1142 + data = {files: []};
  1143 + if (items && items.length) {
  1144 + $.each(items, function (index, item) {
  1145 + var file = item.getAsFile && item.getAsFile();
  1146 + if (file) {
  1147 + data.files.push(file);
  1148 + }
  1149 + });
  1150 + if (this._trigger(
  1151 + 'paste',
  1152 + $.Event('paste', {delegatedEvent: e}),
  1153 + data
  1154 + ) !== false) {
  1155 + this._onAdd(e, data);
  1156 + }
  1157 + }
  1158 + },
  1159 +
  1160 + _onDrop: function (e) {
  1161 + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
  1162 + var that = this,
  1163 + dataTransfer = e.dataTransfer,
  1164 + data = {};
  1165 + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
  1166 + e.preventDefault();
  1167 + this._getDroppedFiles(dataTransfer).always(function (files) {
  1168 + data.files = files;
  1169 + if (that._trigger(
  1170 + 'drop',
  1171 + $.Event('drop', {delegatedEvent: e}),
  1172 + data
  1173 + ) !== false) {
  1174 + that._onAdd(e, data);
  1175 + }
  1176 + });
  1177 + }
  1178 + },
  1179 +
  1180 + _onDragOver: function (e) {
  1181 + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
  1182 + var dataTransfer = e.dataTransfer;
  1183 + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
  1184 + this._trigger(
  1185 + 'dragover',
  1186 + $.Event('dragover', {delegatedEvent: e})
  1187 + ) !== false) {
  1188 + e.preventDefault();
  1189 + dataTransfer.dropEffect = 'copy';
  1190 + }
  1191 + },
  1192 +
  1193 + _initEventHandlers: function () {
  1194 + if (this._isXHRUpload(this.options)) {
  1195 + this._on(this.options.dropZone, {
  1196 + dragover: this._onDragOver,
  1197 + drop: this._onDrop
  1198 + });
  1199 + this._on(this.options.pasteZone, {
  1200 + paste: this._onPaste
  1201 + });
  1202 + }
  1203 + if ($.support.fileInput) {
  1204 + this._on(this.options.fileInput, {
  1205 + change: this._onChange
  1206 + });
  1207 + }
  1208 + },
  1209 +
  1210 + _destroyEventHandlers: function () {
  1211 + this._off(this.options.dropZone, 'dragover drop');
  1212 + this._off(this.options.pasteZone, 'paste');
  1213 + this._off(this.options.fileInput, 'change');
  1214 + },
  1215 +
  1216 + _setOption: function (key, value) {
  1217 + var reinit = $.inArray(key, this._specialOptions) !== -1;
  1218 + if (reinit) {
  1219 + this._destroyEventHandlers();
  1220 + }
  1221 + this._super(key, value);
  1222 + if (reinit) {
  1223 + this._initSpecialOptions();
  1224 + this._initEventHandlers();
  1225 + }
  1226 + },
  1227 +
  1228 + _initSpecialOptions: function () {
  1229 + var options = this.options;
  1230 + if (options.fileInput === undefined) {
  1231 + options.fileInput = this.element.is('input[type="file"]') ?
  1232 + this.element : this.element.find('input[type="file"]');
  1233 + } else if (!(options.fileInput instanceof $)) {
  1234 + options.fileInput = $(options.fileInput);
  1235 + }
  1236 + if (!(options.dropZone instanceof $)) {
  1237 + options.dropZone = $(options.dropZone);
  1238 + }
  1239 + if (!(options.pasteZone instanceof $)) {
  1240 + options.pasteZone = $(options.pasteZone);
  1241 + }
  1242 + },
  1243 +
  1244 + _getRegExp: function (str) {
  1245 + var parts = str.split('/'),
  1246 + modifiers = parts.pop();
  1247 + parts.shift();
  1248 + return new RegExp(parts.join('/'), modifiers);
  1249 + },
  1250 +
  1251 + _isRegExpOption: function (key, value) {
  1252 + return key !== 'url' && $.type(value) === 'string' &&
  1253 + /^\/.*\/[igm]{0,3}$/.test(value);
  1254 + },
  1255 +
  1256 + _initDataAttributes: function () {
  1257 + var that = this,
  1258 + options = this.options;
  1259 + // Initialize options set via HTML5 data-attributes:
  1260 + $.each(
  1261 + $(this.element[0].cloneNode(false)).data(),
  1262 + function (key, value) {
  1263 + if (that._isRegExpOption(key, value)) {
  1264 + value = that._getRegExp(value);
  1265 + }
  1266 + options[key] = value;
  1267 + }
  1268 + );
  1269 + },
  1270 +
  1271 + _create: function () {
  1272 + this._initDataAttributes();
  1273 + this._initSpecialOptions();
  1274 + this._slots = [];
  1275 + this._sequence = this._getXHRPromise(true);
  1276 + this._sending = this._active = 0;
  1277 + this._initProgressObject(this);
  1278 + this._initEventHandlers();
  1279 + },
  1280 +
  1281 + // This method is exposed to the widget API and allows to query
  1282 + // the number of active uploads:
  1283 + active: function () {
  1284 + return this._active;
  1285 + },
  1286 +
  1287 + // This method is exposed to the widget API and allows to query
  1288 + // the widget upload progress.
  1289 + // It returns an object with loaded, total and bitrate properties
  1290 + // for the running uploads:
  1291 + progress: function () {
  1292 + return this._progress;
  1293 + },
  1294 +
  1295 + // This method is exposed to the widget API and allows adding files
  1296 + // using the fileupload API. The data parameter accepts an object which
  1297 + // must have a files property and can contain additional options:
  1298 + // .fileupload('add', {files: filesList});
  1299 + add: function (data) {
  1300 + var that = this;
  1301 + if (!data || this.options.disabled) {
  1302 + return;
  1303 + }
  1304 + if (data.fileInput && !data.files) {
  1305 + this._getFileInputFiles(data.fileInput).always(function (files) {
  1306 + data.files = files;
  1307 + that._onAdd(null, data);
  1308 + });
  1309 + } else {
  1310 + data.files = $.makeArray(data.files);
  1311 + this._onAdd(null, data);
  1312 + }
  1313 + },
  1314 +
  1315 + // This method is exposed to the widget API and allows sending files
  1316 + // using the fileupload API. The data parameter accepts an object which
  1317 + // must have a files or fileInput property and can contain additional options:
  1318 + // .fileupload('send', {files: filesList});
  1319 + // The method returns a Promise object for the file upload call.
  1320 + send: function (data) {
  1321 + if (data && !this.options.disabled) {
  1322 + if (data.fileInput && !data.files) {
  1323 + var that = this,
  1324 + dfd = $.Deferred(),
  1325 + promise = dfd.promise(),
  1326 + jqXHR,
  1327 + aborted;
  1328 + promise.abort = function () {
  1329 + aborted = true;
  1330 + if (jqXHR) {
  1331 + return jqXHR.abort();
  1332 + }
  1333 + dfd.reject(null, 'abort', 'abort');
  1334 + return promise;
  1335 + };
  1336 + this._getFileInputFiles(data.fileInput).always(
  1337 + function (files) {
  1338 + if (aborted) {
  1339 + return;
  1340 + }
  1341 + if (!files.length) {
  1342 + dfd.reject();
  1343 + return;
  1344 + }
  1345 + data.files = files;
  1346 + jqXHR = that._onSend(null, data).then(
  1347 + function (result, textStatus, jqXHR) {
  1348 + dfd.resolve(result, textStatus, jqXHR);
  1349 + },
  1350 + function (jqXHR, textStatus, errorThrown) {
  1351 + dfd.reject(jqXHR, textStatus, errorThrown);
  1352 + }
  1353 + );
  1354 + }
  1355 + );
  1356 + return this._enhancePromise(promise);
  1357 + }
  1358 + data.files = $.makeArray(data.files);
  1359 + if (data.files.length) {
  1360 + return this._onSend(null, data);
  1361 + }
  1362 + }
  1363 + return this._getXHRPromise(false, data && data.context);
  1364 + }
  1365 +
  1366 + });
  1367 +
  1368 +}));
... ...
forms/jquery.iframe-transport.js 0 → 100644
  1 +++ a/forms/jquery.iframe-transport.js
  1 +/*
  2 + * jQuery Iframe Transport Plugin 1.8.0
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2011, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint unparam: true, nomen: true */
  13 +/*global define, window, document */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define(['jquery'], factory);
  20 + } else {
  21 + // Browser globals:
  22 + factory(window.jQuery);
  23 + }
  24 +}(function ($) {
  25 + 'use strict';
  26 +
  27 + // Helper variable to create unique names for the transport iframes:
  28 + var counter = 0;
  29 +
  30 + // The iframe transport accepts four additional options:
  31 + // options.fileInput: a jQuery collection of file input fields
  32 + // options.paramName: the parameter name for the file form data,
  33 + // overrides the name property of the file input field(s),
  34 + // can be a string or an array of strings.
  35 + // options.formData: an array of objects with name and value properties,
  36 + // equivalent to the return data of .serializeArray(), e.g.:
  37 + // [{name: 'a', value: 1}, {name: 'b', value: 2}]
  38 + // options.initialIframeSrc: the URL of the initial iframe src,
  39 + // by default set to "javascript:false;"
  40 + $.ajaxTransport('iframe', function (options) {
  41 + if (options.async) {
  42 + // javascript:false as initial iframe src
  43 + // prevents warning popups on HTTPS in IE6:
  44 + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
  45 + form,
  46 + iframe,
  47 + addParamChar;
  48 + return {
  49 + send: function (_, completeCallback) {
  50 + form = $('<form style="display:none;"></form>');
  51 + form.attr('accept-charset', options.formAcceptCharset);
  52 + addParamChar = /\?/.test(options.url) ? '&' : '?';
  53 + // XDomainRequest only supports GET and POST:
  54 + if (options.type === 'DELETE') {
  55 + options.url = options.url + addParamChar + '_method=DELETE';
  56 + options.type = 'POST';
  57 + } else if (options.type === 'PUT') {
  58 + options.url = options.url + addParamChar + '_method=PUT';
  59 + options.type = 'POST';
  60 + } else if (options.type === 'PATCH') {
  61 + options.url = options.url + addParamChar + '_method=PATCH';
  62 + options.type = 'POST';
  63 + }
  64 + // IE versions below IE8 cannot set the name property of
  65 + // elements that have already been added to the DOM,
  66 + // so we set the name along with the iframe HTML markup:
  67 + counter += 1;
  68 + iframe = $(
  69 + '<iframe src="' + initialIframeSrc +
  70 + '" name="iframe-transport-' + counter + '"></iframe>'
  71 + ).bind('load', function () {
  72 + var fileInputClones,
  73 + paramNames = $.isArray(options.paramName) ?
  74 + options.paramName : [options.paramName];
  75 + iframe
  76 + .unbind('load')
  77 + .bind('load', function () {
  78 + var response;
  79 + // Wrap in a try/catch block to catch exceptions thrown
  80 + // when trying to access cross-domain iframe contents:
  81 + try {
  82 + response = iframe.contents();
  83 + // Google Chrome and Firefox do not throw an
  84 + // exception when calling iframe.contents() on
  85 + // cross-domain requests, so we unify the response:
  86 + if (!response.length || !response[0].firstChild) {
  87 + throw new Error();
  88 + }
  89 + } catch (e) {
  90 + response = undefined;
  91 + }
  92 + // The complete callback returns the
  93 + // iframe content document as response object:
  94 + completeCallback(
  95 + 200,
  96 + 'success',
  97 + {'iframe': response}
  98 + );
  99 + // Fix for IE endless progress bar activity bug
  100 + // (happens on form submits to iframe targets):
  101 + $('<iframe src="' + initialIframeSrc + '"></iframe>')
  102 + .appendTo(form);
  103 + window.setTimeout(function () {
  104 + // Removing the form in a setTimeout call
  105 + // allows Chrome's developer tools to display
  106 + // the response result
  107 + form.remove();
  108 + }, 0);
  109 + });
  110 + form
  111 + .prop('target', iframe.prop('name'))
  112 + .prop('action', options.url)
  113 + .prop('method', options.type);
  114 + if (options.formData) {
  115 + $.each(options.formData, function (index, field) {
  116 + $('<input type="hidden"/>')
  117 + .prop('name', field.name)
  118 + .val(field.value)
  119 + .appendTo(form);
  120 + });
  121 + }
  122 + if (options.fileInput && options.fileInput.length &&
  123 + options.type === 'POST') {
  124 + fileInputClones = options.fileInput.clone();
  125 + // Insert a clone for each file input field:
  126 + options.fileInput.after(function (index) {
  127 + return fileInputClones[index];
  128 + });
  129 + if (options.paramName) {
  130 + options.fileInput.each(function (index) {
  131 + $(this).prop(
  132 + 'name',
  133 + paramNames[index] || options.paramName
  134 + );
  135 + });
  136 + }
  137 + // Appending the file input fields to the hidden form
  138 + // removes them from their original location:
  139 + form
  140 + .append(options.fileInput)
  141 + .prop('enctype', 'multipart/form-data')
  142 + // enctype must be set as encoding for IE:
  143 + .prop('encoding', 'multipart/form-data');
  144 + }
  145 + form.submit();
  146 + // Insert the file input fields at their original location
  147 + // by replacing the clones with the originals:
  148 + if (fileInputClones && fileInputClones.length) {
  149 + options.fileInput.each(function (index, input) {
  150 + var clone = $(fileInputClones[index]);
  151 + $(input).prop('name', clone.prop('name'));
  152 + clone.replaceWith(input);
  153 + });
  154 + }
  155 + });
  156 + form.append(iframe).appendTo(document.body);
  157 + },
  158 + abort: function () {
  159 + if (iframe) {
  160 + // javascript:false as iframe src aborts the request
  161 + // and prevents warning popups on HTTPS in IE6.
  162 + // concat is used to avoid the "Script URL" JSLint error:
  163 + iframe
  164 + .unbind('load')
  165 + .prop('src', initialIframeSrc);
  166 + }
  167 + if (form) {
  168 + form.remove();
  169 + }
  170 + }
  171 + };
  172 + }
  173 + });
  174 +
  175 + // The iframe transport returns the iframe content document as response.
  176 + // The following adds converters from iframe to text, json, html, xml
  177 + // and script.
  178 + // Please note that the Content-Type for JSON responses has to be text/plain
  179 + // or text/html, if the browser doesn't include application/json in the
  180 + // Accept header, else IE will show a download dialog.
  181 + // The Content-Type for XML responses on the other hand has to be always
  182 + // application/xml or text/xml, so IE properly parses the XML response.
  183 + // See also
  184 + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
  185 + $.ajaxSetup({
  186 + converters: {
  187 + 'iframe text': function (iframe) {
  188 + return iframe && $(iframe[0].body).text();
  189 + },
  190 + 'iframe json': function (iframe) {
  191 + return iframe && $.parseJSON($(iframe[0].body).text());
  192 + },
  193 + 'iframe html': function (iframe) {
  194 + return iframe && $(iframe[0].body).html();
  195 + },
  196 + 'iframe xml': function (iframe) {
  197 + var xmlDoc = iframe && iframe[0];
  198 + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
  199 + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
  200 + $(xmlDoc.body).html());
  201 + },
  202 + 'iframe script': function (iframe) {
  203 + return iframe && $.globalEval($(iframe[0].body).text());
  204 + }
  205 + }
  206 + });
  207 +
  208 +}));
... ...
forms/jquery.iframe-transport_1.js 0 → 100644
  1 +++ a/forms/jquery.iframe-transport_1.js
  1 +/*
  2 + * jQuery Iframe Transport Plugin 1.8.0
  3 + * https://github.com/blueimp/jQuery-File-Upload
  4 + *
  5 + * Copyright 2011, Sebastian Tschan
  6 + * https://blueimp.net
  7 + *
  8 + * Licensed under the MIT license:
  9 + * http://www.opensource.org/licenses/MIT
  10 + */
  11 +
  12 +/*jslint unparam: true, nomen: true */
  13 +/*global define, window, document */
  14 +
  15 +(function (factory) {
  16 + 'use strict';
  17 + if (typeof define === 'function' && define.amd) {
  18 + // Register as an anonymous AMD module:
  19 + define(['jquery'], factory);
  20 + } else {
  21 + // Browser globals:
  22 + factory(window.jQuery);
  23 + }
  24 +}(function ($) {
  25 + 'use strict';
  26 +
  27 + // Helper variable to create unique names for the transport iframes:
  28 + var counter = 0;
  29 +
  30 + // The iframe transport accepts four additional options:
  31 + // options.fileInput: a jQuery collection of file input fields
  32 + // options.paramName: the parameter name for the file form data,
  33 + // overrides the name property of the file input field(s),
  34 + // can be a string or an array of strings.
  35 + // options.formData: an array of objects with name and value properties,
  36 + // equivalent to the return data of .serializeArray(), e.g.:
  37 + // [{name: 'a', value: 1}, {name: 'b', value: 2}]
  38 + // options.initialIframeSrc: the URL of the initial iframe src,
  39 + // by default set to "javascript:false;"
  40 + $.ajaxTransport('iframe', function (options) {
  41 + if (options.async) {
  42 + // javascript:false as initial iframe src
  43 + // prevents warning popups on HTTPS in IE6:
  44 + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
  45 + form,
  46 + iframe,
  47 + addParamChar;
  48 + return {
  49 + send: function (_, completeCallback) {
  50 + form = $('<form style="display:none;"></form>');
  51 + form.attr('accept-charset', options.formAcceptCharset);
  52 + addParamChar = /\?/.test(options.url) ? '&' : '?';
  53 + // XDomainRequest only supports GET and POST:
  54 + if (options.type === 'DELETE') {
  55 + options.url = options.url + addParamChar + '_method=DELETE';
  56 + options.type = 'POST';
  57 + } else if (options.type === 'PUT') {
  58 + options.url = options.url + addParamChar + '_method=PUT';
  59 + options.type = 'POST';
  60 + } else if (options.type === 'PATCH') {
  61 + options.url = options.url + addParamChar + '_method=PATCH';
  62 + options.type = 'POST';
  63 + }
  64 + // IE versions below IE8 cannot set the name property of
  65 + // elements that have already been added to the DOM,
  66 + // so we set the name along with the iframe HTML markup:
  67 + counter += 1;
  68 + iframe = $(
  69 + '<iframe src="' + initialIframeSrc +
  70 + '" name="iframe-transport-' + counter + '"></iframe>'
  71 + ).bind('load', function () {
  72 + var fileInputClones,
  73 + paramNames = $.isArray(options.paramName) ?
  74 + options.paramName : [options.paramName];
  75 + iframe
  76 + .unbind('load')
  77 + .bind('load', function () {
  78 + var response;
  79 + // Wrap in a try/catch block to catch exceptions thrown
  80 + // when trying to access cross-domain iframe contents:
  81 + try {
  82 + response = iframe.contents();
  83 + // Google Chrome and Firefox do not throw an
  84 + // exception when calling iframe.contents() on
  85 + // cross-domain requests, so we unify the response:
  86 + if (!response.length || !response[0].firstChild) {
  87 + throw new Error();
  88 + }
  89 + } catch (e) {
  90 + response = undefined;
  91 + }
  92 + // The complete callback returns the
  93 + // iframe content document as response object:
  94 + completeCallback(
  95 + 200,
  96 + 'success',
  97 + {'iframe': response}
  98 + );
  99 + // Fix for IE endless progress bar activity bug
  100 + // (happens on form submits to iframe targets):
  101 + $('<iframe src="' + initialIframeSrc + '"></iframe>')
  102 + .appendTo(form);
  103 + window.setTimeout(function () {
  104 + // Removing the form in a setTimeout call
  105 + // allows Chrome's developer tools to display
  106 + // the response result
  107 + form.remove();
  108 + }, 0);
  109 + });
  110 + form
  111 + .prop('target', iframe.prop('name'))
  112 + .prop('action', options.url)
  113 + .prop('method', options.type);
  114 + if (options.formData) {
  115 + $.each(options.formData, function (index, field) {
  116 + $('<input type="hidden"/>')
  117 + .prop('name', field.name)
  118 + .val(field.value)
  119 + .appendTo(form);
  120 + });
  121 + }
  122 + if (options.fileInput && options.fileInput.length &&
  123 + options.type === 'POST') {
  124 + fileInputClones = options.fileInput.clone();
  125 + // Insert a clone for each file input field:
  126 + options.fileInput.after(function (index) {
  127 + return fileInputClones[index];
  128 + });
  129 + if (options.paramName) {
  130 + options.fileInput.each(function (index) {
  131 + $(this).prop(
  132 + 'name',
  133 + paramNames[index] || options.paramName
  134 + );
  135 + });
  136 + }
  137 + // Appending the file input fields to the hidden form
  138 + // removes them from their original location:
  139 + form
  140 + .append(options.fileInput)
  141 + .prop('enctype', 'multipart/form-data')
  142 + // enctype must be set as encoding for IE:
  143 + .prop('encoding', 'multipart/form-data');
  144 + }
  145 + form.submit();
  146 + // Insert the file input fields at their original location
  147 + // by replacing the clones with the originals:
  148 + if (fileInputClones && fileInputClones.length) {
  149 + options.fileInput.each(function (index, input) {
  150 + var clone = $(fileInputClones[index]);
  151 + $(input).prop('name', clone.prop('name'));
  152 + clone.replaceWith(input);
  153 + });
  154 + }
  155 + });
  156 + form.append(iframe).appendTo(document.body);
  157 + },
  158 + abort: function () {
  159 + if (iframe) {
  160 + // javascript:false as iframe src aborts the request
  161 + // and prevents warning popups on HTTPS in IE6.
  162 + // concat is used to avoid the "Script URL" JSLint error:
  163 + iframe
  164 + .unbind('load')
  165 + .prop('src', initialIframeSrc);
  166 + }
  167 + if (form) {
  168 + form.remove();
  169 + }
  170 + }
  171 + };
  172 + }
  173 + });
  174 +
  175 + // The iframe transport returns the iframe content document as response.
  176 + // The following adds converters from iframe to text, json, html, xml
  177 + // and script.
  178 + // Please note that the Content-Type for JSON responses has to be text/plain
  179 + // or text/html, if the browser doesn't include application/json in the
  180 + // Accept header, else IE will show a download dialog.
  181 + // The Content-Type for XML responses on the other hand has to be always
  182 + // application/xml or text/xml, so IE properly parses the XML response.
  183 + // See also
  184 + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
  185 + $.ajaxSetup({
  186 + converters: {
  187 + 'iframe text': function (iframe) {
  188 + return iframe && $(iframe[0].body).text();
  189 + },
  190 + 'iframe json': function (iframe) {
  191 + return iframe && $.parseJSON($(iframe[0].body).text());
  192 + },
  193 + 'iframe html': function (iframe) {
  194 + return iframe && $(iframe[0].body).html();
  195 + },
  196 + 'iframe xml': function (iframe) {
  197 + var xmlDoc = iframe && iframe[0];
  198 + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
  199 + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
  200 + $(xmlDoc.body).html());
  201 + },
  202 + 'iframe script': function (iframe) {
  203 + return iframe && $.globalEval($(iframe[0].body).text());
  204 + }
  205 + }
  206 + });
  207 +
  208 +}));
... ...
forms/jquery.tagify.js 0 → 100644
  1 +++ a/forms/jquery.tagify.js
  1 +/* Author: Alicia Liu */
  2 +
  3 +(function ($) {
  4 +
  5 + $.widget("ui.tagify", {
  6 + options: {
  7 + delimiters: [13, 188], // what user can type to complete a tag in char codes: [enter], [comma]
  8 + outputDelimiter: ',', // delimiter for tags in original input field
  9 + cssClass: 'tagify-container', // CSS class to style the tagify div and tags, see stylesheet
  10 + addTagPrompt: 'add tags' // placeholder text
  11 + },
  12 +
  13 + _create: function() {
  14 + var self = this,
  15 + el = self.element,
  16 + opts = self.options;
  17 +
  18 + this.tags = [];
  19 +
  20 + // hide text field and replace with a div that contains it's own input field for entering tags
  21 + this.tagInput = $("<input type='text'>")
  22 + .attr( 'placeholder', opts.addTagPrompt )
  23 + .keypress( function(e) {
  24 + var $this = $(this),
  25 + pressed = e.which;
  26 +
  27 + for ( i in opts.delimiters ) {
  28 +
  29 + if (pressed == opts.delimiters[i]) {
  30 + self.add( $this.val() );
  31 + e.preventDefault();
  32 + return false;
  33 + }
  34 + }
  35 + })
  36 + // for some reason, in Safari, backspace is only recognized on keyup
  37 + .keyup( function(e) {
  38 + var $this = $(this),
  39 + pressed = e.which;
  40 +
  41 + // if backspace is hit with no input, remove the last tag
  42 + if (pressed == 8) { // backspace
  43 + return;
  44 + }
  45 + });
  46 +
  47 + this.tagDiv = $("<div></div>")
  48 + .addClass( opts.cssClass )
  49 + .click( function() {
  50 + $(this).children('input').focus();
  51 + })
  52 + .append( this.tagInput )
  53 + .insertAfter( el.hide() );
  54 +
  55 + // if the field isn't empty, parse the field for tags, and prepopulate existing tags
  56 + var initVal = $.trim( el.val() );
  57 +
  58 + if ( initVal ) {
  59 + var initTags = initVal.split( opts.outputDelimiter );
  60 + $.each( initTags, function(i, tag) {
  61 + self.add( tag );
  62 + });
  63 + }
  64 + },
  65 +
  66 + _setOption: function( key, value ) {
  67 + options.key = value;
  68 + },
  69 +
  70 + // add a tag, public function
  71 + add: function(text) {
  72 + var self = this;
  73 + text = text || self.tagInput.val();
  74 + if (text) {
  75 + var tagIndex = self.tags.length;
  76 +
  77 + var removeButton = $("<a href='#'>x</a>")
  78 + .click( function() {
  79 + self.remove( tagIndex );
  80 + return false;
  81 + });
  82 + var newTag = $("<span></span>")
  83 + .text( text )
  84 + .append( removeButton );
  85 +
  86 + self.tagInput.before( newTag );
  87 + self.tags.push( text );
  88 + self.tagInput.val('');
  89 + }
  90 + },
  91 +
  92 + // remove a tag by index, public function
  93 + // if index is blank, remove the last tag
  94 + remove: function( tagIndex ) {
  95 + var self = this;
  96 + if ( tagIndex == null || tagIndex === (self.tags.length - 1) ) {
  97 + this.tagDiv.children("span").last().remove();
  98 + self.tags.pop();
  99 + }
  100 + if ( typeof(tagIndex) == 'number' ) {
  101 + // otherwise just hide this tag, and we don't mess up the index
  102 + this.tagDiv.children( "span:eq(" + tagIndex + ")" ).hide();
  103 + // we rely on the serialize function to remove null values
  104 + delete( self.tags[tagIndex] );
  105 + }
  106 + },
  107 +
  108 + // serialize the tags with the given delimiter, and write it back into the tagified field
  109 + serialize: function() {
  110 + var self = this;
  111 + var delim = self.options.outputDelimiter;
  112 + var tagsStr = self.tags.join( delim );
  113 +
  114 + // our tags might have deleted entries, remove them here
  115 + var dupes = new RegExp(delim + delim + '+', 'g'); // regex: /,,+/g
  116 + var ends = new RegExp('^' + delim + '|' + delim + '$', 'g'); // regex: /^,|,$/g
  117 + var outputStr = tagsStr.replace( dupes, delim ).replace(ends, '');
  118 +
  119 + self.element.val(outputStr);
  120 + return outputStr;
  121 + },
  122 +
  123 + inputField: function() {
  124 + return this.tagInput;
  125 + },
  126 +
  127 + containerDiv: function() {
  128 + return this.tagDiv;
  129 + },
  130 +
  131 + // remove the div, and show original input
  132 + destroy: function() {
  133 + $.Widget.prototype.destroy.apply(this);
  134 + this.tagDiv.remove();
  135 + this.element.show();
  136 + }
  137 + });
  138 +
  139 +})(jQuery);
... ...
forms/ladda.js 0 → 100644
  1 +++ a/forms/ladda.js
  1 +/*!
  2 + * Ladda 0.8.0
  3 + * http://lab.hakim.se/ladda
  4 + * MIT licensed
  5 + *
  6 + * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
  7 + */
  8 +(function( root, factory ) {
  9 +
  10 + // CommonJS
  11 + if( typeof exports === 'object' ) {
  12 + module.exports = factory();
  13 + }
  14 + // AMD module
  15 + else if( typeof define === 'function' && define.amd ) {
  16 + define( [ 'spin' ], factory );
  17 + }
  18 + // Browser global
  19 + else {
  20 + root.Ladda = factory( root.Spinner );
  21 + }
  22 +
  23 +}
  24 +(this, function( Spinner ) {
  25 + 'use strict';
  26 +
  27 + // All currently instantiated instances of Ladda
  28 + var ALL_INSTANCES = [];
  29 +
  30 + /**
  31 + * Creates a new instance of Ladda which wraps the
  32 + * target button element.
  33 + *
  34 + * @return An API object that can be used to control
  35 + * the loading animation state.
  36 + */
  37 + function create( button ) {
  38 +
  39 + if( typeof button === 'undefined' ) {
  40 + console.warn( "Ladda button target must be defined." );
  41 + return;
  42 + }
  43 +
  44 + // The text contents must be wrapped in a ladda-label
  45 + // element, create one if it doesn't already exist
  46 + if( !button.querySelector( '.ladda-label' ) ) {
  47 + button.innerHTML = '<span class="ladda-label">'+ button.innerHTML +'</span>';
  48 + }
  49 +
  50 + // Create the spinner
  51 + var spinner = createSpinner( button );
  52 +
  53 + // Wrapper element for the spinner
  54 + var spinnerWrapper = document.createElement( 'span' );
  55 + spinnerWrapper.className = 'ladda-spinner';
  56 + button.appendChild( spinnerWrapper );
  57 +
  58 + // Timer used to delay starting/stopping
  59 + var timer;
  60 +
  61 + var instance = {
  62 +
  63 + /**
  64 + * Enter the loading state.
  65 + */
  66 + start: function() {
  67 +
  68 + button.setAttribute( 'disabled', '' );
  69 + button.setAttribute( 'data-loading', '' );
  70 +
  71 + clearTimeout( timer );
  72 + spinner.spin( spinnerWrapper );
  73 +
  74 + this.setProgress( 0 );
  75 +
  76 + return this; // chain
  77 +
  78 + },
  79 +
  80 + /**
  81 + * Enter the loading state, after a delay.
  82 + */
  83 + startAfter: function( delay ) {
  84 +
  85 + clearTimeout( timer );
  86 + timer = setTimeout( function() { instance.start(); }, delay );
  87 +
  88 + return this; // chain
  89 +
  90 + },
  91 +
  92 + /**
  93 + * Exit the loading state.
  94 + */
  95 + stop: function() {
  96 +
  97 + button.removeAttribute( 'disabled' );
  98 + button.removeAttribute( 'data-loading' );
  99 +
  100 + // Kill the animation after a delay to make sure it
  101 + // runs for the duration of the button transition
  102 + clearTimeout( timer );
  103 + timer = setTimeout( function() { spinner.stop(); }, 1000 );
  104 +
  105 + return this; // chain
  106 +
  107 + },
  108 +
  109 + /**
  110 + * Toggle the loading state on/off.
  111 + */
  112 + toggle: function() {
  113 +
  114 + if( this.isLoading() ) {
  115 + this.stop();
  116 + }
  117 + else {
  118 + this.start();
  119 + }
  120 +
  121 + return this; // chain
  122 +
  123 + },
  124 +
  125 + /**
  126 + * Sets the width of the visual progress bar inside of
  127 + * this Ladda button
  128 + *
  129 + * @param {Number} progress in the range of 0-1
  130 + */
  131 + setProgress: function( progress ) {
  132 +
  133 + // Cap it
  134 + progress = Math.max( Math.min( progress, 1 ), 0 );
  135 +
  136 + var progressElement = button.querySelector( '.ladda-progress' );
  137 +
  138 + // Remove the progress bar if we're at 0 progress
  139 + if( progress === 0 && progressElement && progressElement.parentNode ) {
  140 + progressElement.parentNode.removeChild( progressElement );
  141 + }
  142 + else {
  143 + if( !progressElement ) {
  144 + progressElement = document.createElement( 'div' );
  145 + progressElement.className = 'ladda-progress';
  146 + button.appendChild( progressElement );
  147 + }
  148 +
  149 + progressElement.style.width = ( ( progress || 0 ) * button.offsetWidth ) + 'px';
  150 + }
  151 +
  152 + },
  153 +
  154 + enable: function() {
  155 +
  156 + this.stop();
  157 +
  158 + return this; // chain
  159 +
  160 + },
  161 +
  162 + disable: function () {
  163 +
  164 + this.stop();
  165 + button.setAttribute( 'disabled', '' );
  166 +
  167 + return this; // chain
  168 +
  169 + },
  170 +
  171 + isLoading: function() {
  172 +
  173 + return button.hasAttribute( 'data-loading' );
  174 +
  175 + }
  176 +
  177 + };
  178 +
  179 + ALL_INSTANCES.push( instance );
  180 +
  181 + return instance;
  182 +
  183 + }
  184 +
  185 + /**
  186 + * Binds the target buttons to automatically enter the
  187 + * loading state when clicked.
  188 + *
  189 + * @param target Either an HTML element or a CSS selector.
  190 + * @param options
  191 + * - timeout Number of milliseconds to wait before
  192 + * automatically cancelling the animation.
  193 + */
  194 + function bind( target, options ) {
  195 +
  196 + options = options || {};
  197 +
  198 + var targets = [];
  199 +
  200 + if( typeof target === 'string' ) {
  201 + targets = toArray( document.querySelectorAll( target ) );
  202 + }
  203 + else if( typeof target === 'object' && typeof target.nodeName === 'string' ) {
  204 + targets = [ target ];
  205 + }
  206 +
  207 + for( var i = 0, len = targets.length; i < len; i++ ) {
  208 +
  209 + (function() {
  210 + var element = targets[i];
  211 +
  212 + // Make sure we're working with a DOM element
  213 + if( typeof element.addEventListener === 'function' ) {
  214 + var instance = create( element );
  215 + var timeout = -1;
  216 +
  217 + element.addEventListener( 'click', function() {
  218 +
  219 + // This is asynchronous to avoid an issue where setting
  220 + // the disabled attribute on the button prevents forms
  221 + // from submitting
  222 + instance.startAfter( 1 );
  223 +
  224 + // Set a loading timeout if one is specified
  225 + if( typeof options.timeout === 'number' ) {
  226 + clearTimeout( timeout );
  227 + timeout = setTimeout( instance.stop, options.timeout );
  228 + }
  229 +
  230 + // Invoke callbacks
  231 + if( typeof options.callback === 'function' ) {
  232 + options.callback.apply( null, [ instance ] );
  233 + }
  234 +
  235 + }, false );
  236 + }
  237 + })();
  238 +
  239 + }
  240 +
  241 + }
  242 +
  243 + /**
  244 + * Stops ALL current loading animations.
  245 + */
  246 + function stopAll() {
  247 +
  248 + for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) {
  249 + ALL_INSTANCES[i].stop();
  250 + }
  251 +
  252 + }
  253 +
  254 + function createSpinner( button ) {
  255 +
  256 + var height = button.offsetHeight,
  257 + spinnerColor;
  258 +
  259 + // If the button is tall we can afford some padding
  260 + if( height > 32 ) {
  261 + height *= 0.8;
  262 + }
  263 +
  264 + // Prefer an explicit height if one is defined
  265 + if( button.hasAttribute( 'data-spinner-size' ) ) {
  266 + height = parseInt( button.getAttribute( 'data-spinner-size' ), 10 );
  267 + }
  268 +
  269 + // Allow buttons to specify the color of the spinner element
  270 + if (button.hasAttribute('data-spinner-color' ) ) {
  271 + spinnerColor = button.getAttribute( 'data-spinner-color' );
  272 + }
  273 +
  274 + var lines = 12,
  275 + radius = height * 0.2,
  276 + length = radius * 0.6,
  277 + width = radius < 7 ? 2 : 3;
  278 +
  279 + return new Spinner( {
  280 + color: spinnerColor || '#fff',
  281 + lines: lines,
  282 + radius: radius,
  283 + length: length,
  284 + width: width,
  285 + zIndex: 'auto',
  286 + top: 'auto',
  287 + left: 'auto',
  288 + className: ''
  289 + } );
  290 +
  291 + }
  292 +
  293 + function toArray( nodes ) {
  294 +
  295 + var a = [];
  296 +
  297 + for ( var i = 0; i < nodes.length; i++ ) {
  298 + a.push( nodes[ i ] );
  299 + }
  300 +
  301 + return a;
  302 +
  303 + }
  304 +
  305 + // Public API
  306 + return {
  307 +
  308 + bind: bind,
  309 + create: create,
  310 + stopAll: stopAll
  311 +
  312 + };
  313 +
  314 +}));
... ...
forms/ladda_1.js 0 → 100644
  1 +++ a/forms/ladda_1.js
  1 +/*!
  2 + * Ladda 0.8.0
  3 + * http://lab.hakim.se/ladda
  4 + * MIT licensed
  5 + *
  6 + * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
  7 + */
  8 +(function( root, factory ) {
  9 +
  10 + // CommonJS
  11 + if( typeof exports === 'object' ) {
  12 + module.exports = factory();
  13 + }
  14 + // AMD module
  15 + else if( typeof define === 'function' && define.amd ) {
  16 + define( [ 'spin' ], factory );
  17 + }
  18 + // Browser global
  19 + else {
  20 + root.Ladda = factory( root.Spinner );
  21 + }
  22 +
  23 +}
  24 +(this, function( Spinner ) {
  25 + 'use strict';
  26 +
  27 + // All currently instantiated instances of Ladda
  28 + var ALL_INSTANCES = [];
  29 +
  30 + /**
  31 + * Creates a new instance of Ladda which wraps the
  32 + * target button element.
  33 + *
  34 + * @return An API object that can be used to control
  35 + * the loading animation state.
  36 + */
  37 + function create( button ) {
  38 +
  39 + if( typeof button === 'undefined' ) {
  40 + console.warn( "Ladda button target must be defined." );
  41 + return;
  42 + }
  43 +
  44 + // The text contents must be wrapped in a ladda-label
  45 + // element, create one if it doesn't already exist
  46 + if( !button.querySelector( '.ladda-label' ) ) {
  47 + button.innerHTML = '<span class="ladda-label">'+ button.innerHTML +'</span>';
  48 + }
  49 +
  50 + // Create the spinner
  51 + var spinner = createSpinner( button );
  52 +
  53 + // Wrapper element for the spinner
  54 + var spinnerWrapper = document.createElement( 'span' );
  55 + spinnerWrapper.className = 'ladda-spinner';
  56 + button.appendChild( spinnerWrapper );
  57 +
  58 + // Timer used to delay starting/stopping
  59 + var timer;
  60 +
  61 + var instance = {
  62 +
  63 + /**
  64 + * Enter the loading state.
  65 + */
  66 + start: function() {
  67 +
  68 + button.setAttribute( 'disabled', '' );
  69 + button.setAttribute( 'data-loading', '' );
  70 +
  71 + clearTimeout( timer );
  72 + spinner.spin( spinnerWrapper );
  73 +
  74 + this.setProgress( 0 );
  75 +
  76 + return this; // chain
  77 +
  78 + },
  79 +
  80 + /**
  81 + * Enter the loading state, after a delay.
  82 + */
  83 + startAfter: function( delay ) {
  84 +
  85 + clearTimeout( timer );
  86 + timer = setTimeout( function() { instance.start(); }, delay );
  87 +
  88 + return this; // chain
  89 +
  90 + },
  91 +
  92 + /**
  93 + * Exit the loading state.
  94 + */
  95 + stop: function() {
  96 +
  97 + button.removeAttribute( 'disabled' );
  98 + button.removeAttribute( 'data-loading' );
  99 +
  100 + // Kill the animation after a delay to make sure it
  101 + // runs for the duration of the button transition
  102 + clearTimeout( timer );
  103 + timer = setTimeout( function() { spinner.stop(); }, 1000 );
  104 +
  105 + return this; // chain
  106 +
  107 + },
  108 +
  109 + /**
  110 + * Toggle the loading state on/off.
  111 + */
  112 + toggle: function() {
  113 +
  114 + if( this.isLoading() ) {
  115 + this.stop();
  116 + }
  117 + else {
  118 + this.start();
  119 + }
  120 +
  121 + return this; // chain
  122 +
  123 + },
  124 +
  125 + /**
  126 + * Sets the width of the visual progress bar inside of
  127 + * this Ladda button
  128 + *
  129 + * @param {Number} progress in the range of 0-1
  130 + */
  131 + setProgress: function( progress ) {
  132 +
  133 + // Cap it
  134 + progress = Math.max( Math.min( progress, 1 ), 0 );
  135 +
  136 + var progressElement = button.querySelector( '.ladda-progress' );
  137 +
  138 + // Remove the progress bar if we're at 0 progress
  139 + if( progress === 0 && progressElement && progressElement.parentNode ) {
  140 + progressElement.parentNode.removeChild( progressElement );
  141 + }
  142 + else {
  143 + if( !progressElement ) {
  144 + progressElement = document.createElement( 'div' );
  145 + progressElement.className = 'ladda-progress';
  146 + button.appendChild( progressElement );
  147 + }
  148 +
  149 + progressElement.style.width = ( ( progress || 0 ) * button.offsetWidth ) + 'px';
  150 + }
  151 +
  152 + },
  153 +
  154 + enable: function() {
  155 +
  156 + this.stop();
  157 +
  158 + return this; // chain
  159 +
  160 + },
  161 +
  162 + disable: function () {
  163 +
  164 + this.stop();
  165 + button.setAttribute( 'disabled', '' );
  166 +
  167 + return this; // chain
  168 +
  169 + },
  170 +
  171 + isLoading: function() {
  172 +
  173 + return button.hasAttribute( 'data-loading' );
  174 +
  175 + }
  176 +
  177 + };
  178 +
  179 + ALL_INSTANCES.push( instance );
  180 +
  181 + return instance;
  182 +
  183 + }
  184 +
  185 + /**
  186 + * Binds the target buttons to automatically enter the
  187 + * loading state when clicked.
  188 + *
  189 + * @param target Either an HTML element or a CSS selector.
  190 + * @param options
  191 + * - timeout Number of milliseconds to wait before
  192 + * automatically cancelling the animation.
  193 + */
  194 + function bind( target, options ) {
  195 +
  196 + options = options || {};
  197 +
  198 + var targets = [];
  199 +
  200 + if( typeof target === 'string' ) {
  201 + targets = toArray( document.querySelectorAll( target ) );
  202 + }
  203 + else if( typeof target === 'object' && typeof target.nodeName === 'string' ) {
  204 + targets = [ target ];
  205 + }
  206 +
  207 + for( var i = 0, len = targets.length; i < len; i++ ) {
  208 +
  209 + (function() {
  210 + var element = targets[i];
  211 +
  212 + // Make sure we're working with a DOM element
  213 + if( typeof element.addEventListener === 'function' ) {
  214 + var instance = create( element );
  215 + var timeout = -1;
  216 +
  217 + element.addEventListener( 'click', function() {
  218 +
  219 + // This is asynchronous to avoid an issue where setting
  220 + // the disabled attribute on the button prevents forms
  221 + // from submitting
  222 + instance.startAfter( 1 );
  223 +
  224 + // Set a loading timeout if one is specified
  225 + if( typeof options.timeout === 'number' ) {
  226 + clearTimeout( timeout );
  227 + timeout = setTimeout( instance.stop, options.timeout );
  228 + }
  229 +
  230 + // Invoke callbacks
  231 + if( typeof options.callback === 'function' ) {
  232 + options.callback.apply( null, [ instance ] );
  233 + }
  234 +
  235 + }, false );
  236 + }
  237 + })();
  238 +
  239 + }
  240 +
  241 + }
  242 +
  243 + /**
  244 + * Stops ALL current loading animations.
  245 + */
  246 + function stopAll() {
  247 +
  248 + for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) {
  249 + ALL_INSTANCES[i].stop();
  250 + }
  251 +
  252 + }
  253 +
  254 + function createSpinner( button ) {
  255 +
  256 + var height = button.offsetHeight,
  257 + spinnerColor;
  258 +
  259 + // If the button is tall we can afford some padding
  260 + if( height > 32 ) {
  261 + height *= 0.8;
  262 + }
  263 +
  264 + // Prefer an explicit height if one is defined
  265 + if( button.hasAttribute( 'data-spinner-size' ) ) {
  266 + height = parseInt( button.getAttribute( 'data-spinner-size' ), 10 );
  267 + }
  268 +
  269 + // Allow buttons to specify the color of the spinner element
  270 + if (button.hasAttribute('data-spinner-color' ) ) {
  271 + spinnerColor = button.getAttribute( 'data-spinner-color' );
  272 + }
  273 +
  274 + var lines = 12,
  275 + radius = height * 0.2,
  276 + length = radius * 0.6,
  277 + width = radius < 7 ? 2 : 3;
  278 +
  279 + return new Spinner( {
  280 + color: spinnerColor || '#fff',
  281 + lines: lines,
  282 + radius: radius,
  283 + length: length,
  284 + width: width,
  285 + zIndex: 'auto',
  286 + top: 'auto',
  287 + left: 'auto',
  288 + className: ''
  289 + } );
  290 +
  291 + }
  292 +
  293 + function toArray( nodes ) {
  294 +
  295 + var a = [];
  296 +
  297 + for ( var i = 0; i < nodes.length; i++ ) {
  298 + a.push( nodes[ i ] );
  299 + }
  300 +
  301 + return a;
  302 +
  303 + }
  304 +
  305 + // Public API
  306 + return {
  307 +
  308 + bind: bind,
  309 + create: create,
  310 + stopAll: stopAll
  311 +
  312 + };
  313 +
  314 +}));
... ...
forms/spin.js 0 → 100644
  1 +++ a/forms/spin.js
  1 +//fgnass.github.com/spin.js#v1.3
  2 +
  3 +/*!
  4 + * Copyright (c) 2011-2013 Felix Gnass
  5 + * Licensed under the MIT license
  6 + */
  7 +(function(root, factory) {
  8 +
  9 + /* CommonJS */
  10 + if (typeof exports == 'object') module.exports = factory()
  11 +
  12 + /* AMD module */
  13 + else if (typeof define == 'function' && define.amd) define(factory)
  14 +
  15 + /* Browser global */
  16 + else root.Spinner = factory()
  17 +}
  18 +(this, function() {
  19 + "use strict";
  20 +
  21 + var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
  22 + , animations = {} /* Animation rules keyed by their name */
  23 + , useCssAnimations /* Whether to use CSS animations or setTimeout */
  24 +
  25 + /**
  26 + * Utility function to create elements. If no tag name is given,
  27 + * a DIV is created. Optionally properties can be passed.
  28 + */
  29 + function createEl(tag, prop) {
  30 + var el = document.createElement(tag || 'div')
  31 + , n
  32 +
  33 + for(n in prop) el[n] = prop[n]
  34 + return el
  35 + }
  36 +
  37 + /**
  38 + * Appends children and returns the parent.
  39 + */
  40 + function ins(parent /* child1, child2, ...*/) {
  41 + for (var i=1, n=arguments.length; i<n; i++)
  42 + parent.appendChild(arguments[i])
  43 +
  44 + return parent
  45 + }
  46 +
  47 + /**
  48 + * Insert a new stylesheet to hold the @keyframe or VML rules.
  49 + */
  50 + var sheet = (function() {
  51 + var el = createEl('style', {type : 'text/css'})
  52 + ins(document.getElementsByTagName('head')[0], el)
  53 + return el.sheet || el.styleSheet
  54 + }())
  55 +
  56 + /**
  57 + * Creates an opacity keyframe animation rule and returns its name.
  58 + * Since most mobile Webkits have timing issues with animation-delay,
  59 + * we create separate rules for each line/segment.
  60 + */
  61 + function addAnimation(alpha, trail, i, lines) {
  62 + var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
  63 + , start = 0.01 + i/lines * 100
  64 + , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
  65 + , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
  66 + , pre = prefix && '-' + prefix + '-' || ''
  67 +
  68 + if (!animations[name]) {
  69 + sheet.insertRule(
  70 + '@' + pre + 'keyframes ' + name + '{' +
  71 + '0%{opacity:' + z + '}' +
  72 + start + '%{opacity:' + alpha + '}' +
  73 + (start+0.01) + '%{opacity:1}' +
  74 + (start+trail) % 100 + '%{opacity:' + alpha + '}' +
  75 + '100%{opacity:' + z + '}' +
  76 + '}', sheet.cssRules.length)
  77 +
  78 + animations[name] = 1
  79 + }
  80 +
  81 + return name
  82 + }
  83 +
  84 + /**
  85 + * Tries various vendor prefixes and returns the first supported property.
  86 + */
  87 + function vendor(el, prop) {
  88 + var s = el.style
  89 + , pp
  90 + , i
  91 +
  92 + if(s[prop] !== undefined) return prop
  93 + prop = prop.charAt(0).toUpperCase() + prop.slice(1)
  94 + for(i=0; i<prefixes.length; i++) {
  95 + pp = prefixes[i]+prop
  96 + if(s[pp] !== undefined) return pp
  97 + }
  98 + }
  99 +
  100 + /**
  101 + * Sets multiple style properties at once.
  102 + */
  103 + function css(el, prop) {
  104 + for (var n in prop)
  105 + el.style[vendor(el, n)||n] = prop[n]
  106 +
  107 + return el
  108 + }
  109 +
  110 + /**
  111 + * Fills in default values.
  112 + */
  113 + function merge(obj) {
  114 + for (var i=1; i < arguments.length; i++) {
  115 + var def = arguments[i]
  116 + for (var n in def)
  117 + if (obj[n] === undefined) obj[n] = def[n]
  118 + }
  119 + return obj
  120 + }
  121 +
  122 + /**
  123 + * Returns the absolute page-offset of the given element.
  124 + */
  125 + function pos(el) {
  126 + var o = { x:el.offsetLeft, y:el.offsetTop }
  127 + while((el = el.offsetParent))
  128 + o.x+=el.offsetLeft, o.y+=el.offsetTop
  129 +
  130 + return o
  131 + }
  132 +
  133 + // Built-in defaults
  134 +
  135 + var defaults = {
  136 + lines: 12, // The number of lines to draw
  137 + length: 7, // The length of each line
  138 + width: 5, // The line thickness
  139 + radius: 10, // The radius of the inner circle
  140 + rotate: 0, // Rotation offset
  141 + corners: 1, // Roundness (0..1)
  142 + color: '#000', // #rgb or #rrggbb
  143 + direction: 1, // 1: clockwise, -1: counterclockwise
  144 + speed: 1, // Rounds per second
  145 + trail: 100, // Afterglow percentage
  146 + opacity: 1/4, // Opacity of the lines
  147 + fps: 20, // Frames per second when using setTimeout()
  148 + zIndex: 2e9, // Use a high z-index by default
  149 + className: 'spinner', // CSS class to assign to the element
  150 + top: 'auto', // center vertically
  151 + left: 'auto', // center horizontally
  152 + position: 'relative' // element position
  153 + }
  154 +
  155 + /** The constructor */
  156 + function Spinner(o) {
  157 + if (typeof this == 'undefined') return new Spinner(o)
  158 + this.opts = merge(o || {}, Spinner.defaults, defaults)
  159 + }
  160 +
  161 + // Global defaults that override the built-ins:
  162 + Spinner.defaults = {}
  163 +
  164 + merge(Spinner.prototype, {
  165 +
  166 + /**
  167 + * Adds the spinner to the given target element. If this instance is already
  168 + * spinning, it is automatically removed from its previous target b calling
  169 + * stop() internally.
  170 + */
  171 + spin: function(target) {
  172 + this.stop()
  173 +
  174 + var self = this
  175 + , o = self.opts
  176 + , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
  177 + , mid = o.radius+o.length+o.width
  178 + , ep // element position
  179 + , tp // target position
  180 +
  181 + if (target) {
  182 + target.insertBefore(el, target.firstChild||null)
  183 + tp = pos(target)
  184 + ep = pos(el)
  185 + css(el, {
  186 + left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
  187 + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px'
  188 + })
  189 + }
  190 +
  191 + el.setAttribute('role', 'progressbar')
  192 + self.lines(el, self.opts)
  193 +
  194 + if (!useCssAnimations) {
  195 + // No CSS animation support, use setTimeout() instead
  196 + var i = 0
  197 + , start = (o.lines - 1) * (1 - o.direction) / 2
  198 + , alpha
  199 + , fps = o.fps
  200 + , f = fps/o.speed
  201 + , ostep = (1-o.opacity) / (f*o.trail / 100)
  202 + , astep = f/o.lines
  203 +
  204 + ;(function anim() {
  205 + i++;
  206 + for (var j = 0; j < o.lines; j++) {
  207 + alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
  208 +
  209 + self.opacity(el, j * o.direction + start, alpha, o)
  210 + }
  211 + self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
  212 + })()
  213 + }
  214 + return self
  215 + },
  216 +
  217 + /**
  218 + * Stops and removes the Spinner.
  219 + */
  220 + stop: function() {
  221 + var el = this.el
  222 + if (el) {
  223 + clearTimeout(this.timeout)
  224 + if (el.parentNode) el.parentNode.removeChild(el)
  225 + this.el = undefined
  226 + }
  227 + return this
  228 + },
  229 +
  230 + /**
  231 + * Internal method that draws the individual lines. Will be overwritten
  232 + * in VML fallback mode below.
  233 + */
  234 + lines: function(el, o) {
  235 + var i = 0
  236 + , start = (o.lines - 1) * (1 - o.direction) / 2
  237 + , seg
  238 +
  239 + function fill(color, shadow) {
  240 + return css(createEl(), {
  241 + position: 'absolute',
  242 + width: (o.length+o.width) + 'px',
  243 + height: o.width + 'px',
  244 + background: color,
  245 + boxShadow: shadow,
  246 + transformOrigin: 'left',
  247 + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
  248 + borderRadius: (o.corners * o.width>>1) + 'px'
  249 + })
  250 + }
  251 +
  252 + for (; i < o.lines; i++) {
  253 + seg = css(createEl(), {
  254 + position: 'absolute',
  255 + top: 1+~(o.width/2) + 'px',
  256 + transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
  257 + opacity: o.opacity,
  258 + animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
  259 + })
  260 +
  261 + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
  262 +
  263 + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')))
  264 + }
  265 + return el
  266 + },
  267 +
  268 + /**
  269 + * Internal method that adjusts the opacity of a single line.
  270 + * Will be overwritten in VML fallback mode below.
  271 + */
  272 + opacity: function(el, i, val) {
  273 + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
  274 + }
  275 +
  276 + })
  277 +
  278 +
  279 + function initVML() {
  280 +
  281 + /* Utility function to create a VML tag */
  282 + function vml(tag, attr) {
  283 + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
  284 + }
  285 +
  286 + // No CSS transforms but VML support, add a CSS rule for VML elements:
  287 + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
  288 +
  289 + Spinner.prototype.lines = function(el, o) {
  290 + var r = o.length+o.width
  291 + , s = 2*r
  292 +
  293 + function grp() {
  294 + return css(
  295 + vml('group', {
  296 + coordsize: s + ' ' + s,
  297 + coordorigin: -r + ' ' + -r
  298 + }),
  299 + { width: s, height: s }
  300 + )
  301 + }
  302 +
  303 + var margin = -(o.width+o.length)*2 + 'px'
  304 + , g = css(grp(), {position: 'absolute', top: margin, left: margin})
  305 + , i
  306 +
  307 + function seg(i, dx, filter) {
  308 + ins(g,
  309 + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
  310 + ins(css(vml('roundrect', {arcsize: o.corners}), {
  311 + width: r,
  312 + height: o.width,
  313 + left: o.radius,
  314 + top: -o.width>>1,
  315 + filter: filter
  316 + }),
  317 + vml('fill', {color: o.color, opacity: o.opacity}),
  318 + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
  319 + )
  320 + )
  321 + )
  322 + }
  323 +
  324 + if (o.shadow)
  325 + for (i = 1; i <= o.lines; i++)
  326 + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
  327 +
  328 + for (i = 1; i <= o.lines; i++) seg(i)
  329 + return ins(el, g)
  330 + }
  331 +
  332 + Spinner.prototype.opacity = function(el, i, val, o) {
  333 + var c = el.firstChild
  334 + o = o.shadow && o.lines || 0
  335 + if (c && i+o < c.childNodes.length) {
  336 + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
  337 + if (c) c.opacity = val
  338 + }
  339 + }
  340 + }
  341 +
  342 + var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
  343 +
  344 + if (!vendor(probe, 'transform') && probe.adj) initVML()
  345 + else useCssAnimations = vendor(probe, 'animation')
  346 +
  347 + return Spinner
  348 +
  349 +}));
... ...
forms/spin_1.js 0 → 100644
  1 +++ a/forms/spin_1.js
  1 +//fgnass.github.com/spin.js#v1.3
  2 +
  3 +/*!
  4 + * Copyright (c) 2011-2013 Felix Gnass
  5 + * Licensed under the MIT license
  6 + */
  7 +(function(root, factory) {
  8 +
  9 + /* CommonJS */
  10 + if (typeof exports == 'object') module.exports = factory()
  11 +
  12 + /* AMD module */
  13 + else if (typeof define == 'function' && define.amd) define(factory)
  14 +
  15 + /* Browser global */
  16 + else root.Spinner = factory()
  17 +}
  18 +(this, function() {
  19 + "use strict";
  20 +
  21 + var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
  22 + , animations = {} /* Animation rules keyed by their name */
  23 + , useCssAnimations /* Whether to use CSS animations or setTimeout */
  24 +
  25 + /**
  26 + * Utility function to create elements. If no tag name is given,
  27 + * a DIV is created. Optionally properties can be passed.
  28 + */
  29 + function createEl(tag, prop) {
  30 + var el = document.createElement(tag || 'div')
  31 + , n
  32 +
  33 + for(n in prop) el[n] = prop[n]
  34 + return el
  35 + }
  36 +
  37 + /**
  38 + * Appends children and returns the parent.
  39 + */
  40 + function ins(parent /* child1, child2, ...*/) {
  41 + for (var i=1, n=arguments.length; i<n; i++)
  42 + parent.appendChild(arguments[i])
  43 +
  44 + return parent
  45 + }
  46 +
  47 + /**
  48 + * Insert a new stylesheet to hold the @keyframe or VML rules.
  49 + */
  50 + var sheet = (function() {
  51 + var el = createEl('style', {type : 'text/css'})
  52 + ins(document.getElementsByTagName('head')[0], el)
  53 + return el.sheet || el.styleSheet
  54 + }())
  55 +
  56 + /**
  57 + * Creates an opacity keyframe animation rule and returns its name.
  58 + * Since most mobile Webkits have timing issues with animation-delay,
  59 + * we create separate rules for each line/segment.
  60 + */
  61 + function addAnimation(alpha, trail, i, lines) {
  62 + var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
  63 + , start = 0.01 + i/lines * 100
  64 + , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
  65 + , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
  66 + , pre = prefix && '-' + prefix + '-' || ''
  67 +
  68 + if (!animations[name]) {
  69 + sheet.insertRule(
  70 + '@' + pre + 'keyframes ' + name + '{' +
  71 + '0%{opacity:' + z + '}' +
  72 + start + '%{opacity:' + alpha + '}' +
  73 + (start+0.01) + '%{opacity:1}' +
  74 + (start+trail) % 100 + '%{opacity:' + alpha + '}' +
  75 + '100%{opacity:' + z + '}' +
  76 + '}', sheet.cssRules.length)
  77 +
  78 + animations[name] = 1
  79 + }
  80 +
  81 + return name
  82 + }
  83 +
  84 + /**
  85 + * Tries various vendor prefixes and returns the first supported property.
  86 + */
  87 + function vendor(el, prop) {
  88 + var s = el.style
  89 + , pp
  90 + , i
  91 +
  92 + if(s[prop] !== undefined) return prop
  93 + prop = prop.charAt(0).toUpperCase() + prop.slice(1)
  94 + for(i=0; i<prefixes.length; i++) {
  95 + pp = prefixes[i]+prop
  96 + if(s[pp] !== undefined) return pp
  97 + }
  98 + }
  99 +
  100 + /**
  101 + * Sets multiple style properties at once.
  102 + */
  103 + function css(el, prop) {
  104 + for (var n in prop)
  105 + el.style[vendor(el, n)||n] = prop[n]
  106 +
  107 + return el
  108 + }
  109 +
  110 + /**
  111 + * Fills in default values.
  112 + */
  113 + function merge(obj) {
  114 + for (var i=1; i < arguments.length; i++) {
  115 + var def = arguments[i]
  116 + for (var n in def)
  117 + if (obj[n] === undefined) obj[n] = def[n]
  118 + }
  119 + return obj
  120 + }
  121 +
  122 + /**
  123 + * Returns the absolute page-offset of the given element.
  124 + */
  125 + function pos(el) {
  126 + var o = { x:el.offsetLeft, y:el.offsetTop }
  127 + while((el = el.offsetParent))
  128 + o.x+=el.offsetLeft, o.y+=el.offsetTop
  129 +
  130 + return o
  131 + }
  132 +
  133 + // Built-in defaults
  134 +
  135 + var defaults = {
  136 + lines: 12, // The number of lines to draw
  137 + length: 7, // The length of each line
  138 + width: 5, // The line thickness
  139 + radius: 10, // The radius of the inner circle
  140 + rotate: 0, // Rotation offset
  141 + corners: 1, // Roundness (0..1)
  142 + color: '#000', // #rgb or #rrggbb
  143 + direction: 1, // 1: clockwise, -1: counterclockwise
  144 + speed: 1, // Rounds per second
  145 + trail: 100, // Afterglow percentage
  146 + opacity: 1/4, // Opacity of the lines
  147 + fps: 20, // Frames per second when using setTimeout()
  148 + zIndex: 2e9, // Use a high z-index by default
  149 + className: 'spinner', // CSS class to assign to the element
  150 + top: 'auto', // center vertically
  151 + left: 'auto', // center horizontally
  152 + position: 'relative' // element position
  153 + }
  154 +
  155 + /** The constructor */
  156 + function Spinner(o) {
  157 + if (typeof this == 'undefined') return new Spinner(o)
  158 + this.opts = merge(o || {}, Spinner.defaults, defaults)
  159 + }
  160 +
  161 + // Global defaults that override the built-ins:
  162 + Spinner.defaults = {}
  163 +
  164 + merge(Spinner.prototype, {
  165 +
  166 + /**
  167 + * Adds the spinner to the given target element. If this instance is already
  168 + * spinning, it is automatically removed from its previous target b calling
  169 + * stop() internally.
  170 + */
  171 + spin: function(target) {
  172 + this.stop()
  173 +
  174 + var self = this
  175 + , o = self.opts
  176 + , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
  177 + , mid = o.radius+o.length+o.width
  178 + , ep // element position
  179 + , tp // target position
  180 +
  181 + if (target) {
  182 + target.insertBefore(el, target.firstChild||null)
  183 + tp = pos(target)
  184 + ep = pos(el)
  185 + css(el, {
  186 + left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
  187 + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px'
  188 + })
  189 + }
  190 +
  191 + el.setAttribute('role', 'progressbar')
  192 + self.lines(el, self.opts)
  193 +
  194 + if (!useCssAnimations) {
  195 + // No CSS animation support, use setTimeout() instead
  196 + var i = 0
  197 + , start = (o.lines - 1) * (1 - o.direction) / 2
  198 + , alpha
  199 + , fps = o.fps
  200 + , f = fps/o.speed
  201 + , ostep = (1-o.opacity) / (f*o.trail / 100)
  202 + , astep = f/o.lines
  203 +
  204 + ;(function anim() {
  205 + i++;
  206 + for (var j = 0; j < o.lines; j++) {
  207 + alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
  208 +
  209 + self.opacity(el, j * o.direction + start, alpha, o)
  210 + }
  211 + self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
  212 + })()
  213 + }
  214 + return self
  215 + },
  216 +
  217 + /**
  218 + * Stops and removes the Spinner.
  219 + */
  220 + stop: function() {
  221 + var el = this.el
  222 + if (el) {
  223 + clearTimeout(this.timeout)
  224 + if (el.parentNode) el.parentNode.removeChild(el)
  225 + this.el = undefined
  226 + }
  227 + return this
  228 + },
  229 +
  230 + /**
  231 + * Internal method that draws the individual lines. Will be overwritten
  232 + * in VML fallback mode below.
  233 + */
  234 + lines: function(el, o) {
  235 + var i = 0
  236 + , start = (o.lines - 1) * (1 - o.direction) / 2
  237 + , seg
  238 +
  239 + function fill(color, shadow) {
  240 + return css(createEl(), {
  241 + position: 'absolute',
  242 + width: (o.length+o.width) + 'px',
  243 + height: o.width + 'px',
  244 + background: color,
  245 + boxShadow: shadow,
  246 + transformOrigin: 'left',
  247 + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
  248 + borderRadius: (o.corners * o.width>>1) + 'px'
  249 + })
  250 + }
  251 +
  252 + for (; i < o.lines; i++) {
  253 + seg = css(createEl(), {
  254 + position: 'absolute',
  255 + top: 1+~(o.width/2) + 'px',
  256 + transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
  257 + opacity: o.opacity,
  258 + animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
  259 + })
  260 +
  261 + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
  262 +
  263 + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')))
  264 + }
  265 + return el
  266 + },
  267 +
  268 + /**
  269 + * Internal method that adjusts the opacity of a single line.
  270 + * Will be overwritten in VML fallback mode below.
  271 + */
  272 + opacity: function(el, i, val) {
  273 + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
  274 + }
  275 +
  276 + })
  277 +
  278 +
  279 + function initVML() {
  280 +
  281 + /* Utility function to create a VML tag */
  282 + function vml(tag, attr) {
  283 + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
  284 + }
  285 +
  286 + // No CSS transforms but VML support, add a CSS rule for VML elements:
  287 + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
  288 +
  289 + Spinner.prototype.lines = function(el, o) {
  290 + var r = o.length+o.width
  291 + , s = 2*r
  292 +
  293 + function grp() {
  294 + return css(
  295 + vml('group', {
  296 + coordsize: s + ' ' + s,
  297 + coordorigin: -r + ' ' + -r
  298 + }),
  299 + { width: s, height: s }
  300 + )
  301 + }
  302 +
  303 + var margin = -(o.width+o.length)*2 + 'px'
  304 + , g = css(grp(), {position: 'absolute', top: margin, left: margin})
  305 + , i
  306 +
  307 + function seg(i, dx, filter) {
  308 + ins(g,
  309 + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
  310 + ins(css(vml('roundrect', {arcsize: o.corners}), {
  311 + width: r,
  312 + height: o.width,
  313 + left: o.radius,
  314 + top: -o.width>>1,
  315 + filter: filter
  316 + }),
  317 + vml('fill', {color: o.color, opacity: o.opacity}),
  318 + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
  319 + )
  320 + )
  321 + )
  322 + }
  323 +
  324 + if (o.shadow)
  325 + for (i = 1; i <= o.lines; i++)
  326 + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
  327 +
  328 + for (i = 1; i <= o.lines; i++) seg(i)
  329 + return ins(el, g)
  330 + }
  331 +
  332 + Spinner.prototype.opacity = function(el, i, val, o) {
  333 + var c = el.firstChild
  334 + o = o.shadow && o.lines || 0
  335 + if (c && i+o < c.childNodes.length) {
  336 + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
  337 + if (c) c.opacity = val
  338 + }
  339 + }
  340 + }
  341 +
  342 + var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
  343 +
  344 + if (!vendor(probe, 'transform') && probe.adj) initVML()
  345 + else useCssAnimations = vendor(probe, 'animation')
  346 +
  347 + return Spinner
  348 +
  349 +}));
... ...
functionsForm.php 0 → 100644
  1 +++ a/functionsForm.php
  1 +<?php
  2 +
  3 +/**
  4 + * @version 1.37
  5 + */
  6 +class imaxForm {
  7 +
  8 + private $sinFormulario, $formulario = '', $modulo;
  9 +
  10 + function __construct($modulo, $modulePath = '', $action = '', $method = 'post', $name = '', $enctype = '', $extras = false, $sinFormulario = false, $pathToModule = '', $ajax = false) {
  11 + if ($action == '') {
  12 + $action = Tools::safeOutput($_SERVER['REQUEST_URI']);
  13 + }
  14 + if ($enctype != '') {
  15 + $enctype = 'enctype="' . $enctype . '"';
  16 + }
  17 + if (!$sinFormulario) {
  18 + $this->formulario = '<form name="' . $name . '" action="' . $action . '" method="' . $method . '" ' . $enctype;
  19 + if ($extras) {
  20 + if (!is_array($extras)) {
  21 + $extras = array($extras);
  22 + }
  23 + foreach ($extras AS $indice => $extra) {
  24 + $this->formulario .= $indice . '="' . $extra . '"';
  25 + }
  26 + }
  27 + $this->formulario .= ' >';
  28 + }
  29 + $this->sinFormulario = $sinFormulario;
  30 +
  31 + $this->context = Context::getContext();
  32 + $this->_path = $modulePath;
  33 + $this->_modulePath = $pathToModule;
  34 + $this->ajax = $ajax;
  35 + if (!$this->ajax) {
  36 + if (version_compare(_PS_VERSION_, '1.6.0.0 ', '>=')) {
  37 + $this->addCSS('forms.css');
  38 + } else {
  39 + $this->addCSS('forms.15.css');
  40 + }
  41 + $this->addJS('forms.js');
  42 + }
  43 + $this->idLang = Configuration::getGlobalValue('PS_LANG_DEFAULT');
  44 + $this->idShop = Configuration::getGlobalValue('PS_SHOP_DEFAULT');
  45 + $this->modulo = $modulo;
  46 + }
  47 +
  48 + /**
  49 + * Agrega html al formulario.
  50 + * @param string $html
  51 + * @return $this
  52 + */
  53 + public function addToForm($html) {
  54 + $this->formulario .= $html;
  55 +
  56 + return $this;
  57 + }
  58 +
  59 + /**
  60 + * Devuelve el html del formulario.
  61 + * @param boolean $vaciarFormulario Si es true se vacia el form despues de devolver el html.
  62 + * @return string
  63 + */
  64 + public function renderForm($vaciarFormulario = false) {
  65 + if (!$this->sinFormulario) {
  66 + $this->formulario .= '</form>';
  67 + }
  68 +
  69 + $formulario = $this->formulario;
  70 +
  71 + if ($vaciarFormulario) {
  72 + $this->formulario = '';
  73 + }
  74 +
  75 + return $formulario;
  76 + }
  77 +
  78 + /**
  79 + * Crea un elemento informativo
  80 + * @param string $text Texto a mostrar
  81 + * @param string $class Clase del elemento (warning (defecto), confirm)
  82 + * @return $this
  83 + */
  84 + function createFormInfomationText($text, $class = 'warning') {
  85 + switch ($class) {
  86 + case 'warning':
  87 + $class = ' warning warn alert alert-warning ';
  88 + break;
  89 + case 'confirm':
  90 + $class = ' module_confirmation conf confirm alert alert-success ';
  91 + break;
  92 + default:
  93 + break;
  94 + }
  95 + $html = '';
  96 + $html .= '<div class="form-group">';
  97 + $html .= '<div class="bootstrap imaxBootstrap">';
  98 + $html .= '<p class="' . $class . '">';
  99 + $html .= $text;
  100 + $html .= '</p>';
  101 + $html .= '</div>';
  102 + $html .= '</div>';
  103 + $this->formulario .= $html;
  104 +
  105 + return $this;
  106 + }
  107 +
  108 + /**
  109 + *
  110 + * @param string $name
  111 + * @param string $text
  112 + * @param string $title
  113 + * @param array $checks
  114 + * @param array $selectedValue
  115 + * @return $this
  116 + */
  117 + public function createFormCheckboxGroupList($name, $text, $title, $checks, $selectedValue = array(), $extras = array()) {
  118 + $html = '';
  119 + $html .= '<div class="form-group"';
  120 + $html .= $this->addExtras($extras);
  121 + $html .= '>';
  122 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  123 + $html .= '<span title="' . $title . '">';
  124 + $html .= $text;
  125 + $html .= '</span>';
  126 + $html .= '</label>';
  127 + $html .= '<div class="control-label col-lg-9">';
  128 + foreach ($checks as $check) {
  129 + $marcado = ($selectedValue !== false && in_array($check['value'], $selectedValue) ? 'checked="checked"' : '');
  130 +
  131 + $html .= '<div class="form-group">';
  132 + $html .= '<input type="checkbox" ' . $marcado . ' value="' . $check['value'] . '" name="' . $name . '[]" id="' . $name . '"> ' . $check['text'];
  133 + $html .= '</div>';
  134 + }
  135 + $html .= '</div>';
  136 + $html .= '</div>';
  137 +
  138 + $this->formulario .= $html;
  139 +
  140 + return $this;
  141 + }
  142 + /**
  143 + * Crea un checkbox.
  144 + * @param string $name Nombre del checkbox
  145 + * @param string $text Texto informativo
  146 + * @param boolean $checked Marcado(true) o no(false), por defecto false
  147 + * @param string $title Titulo
  148 + * @param string $value El valor del checkbox.
  149 + * @return $this
  150 + */
  151 + function createFormCheckboxGroup($name, $text, $checked = false, $title = '', $value = 1) {
  152 + $marcado = '';
  153 + if ($checked == true) {
  154 + $marcado = ' checked="checked"';
  155 + }
  156 + $html = '';
  157 + $html .= '<div class="form-group">';
  158 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  159 + $html .= '<span title="' . $title . '">';
  160 + $html .= $text;
  161 + $html .= '</span>';
  162 + $html .= '</label>';
  163 + $html .= '<div class="col-lg-3">';
  164 + $html .= '<input type="checkbox" ' . $marcado . ' value="' . $value . '" name="' . $name . '" id="' . $name . '">';
  165 + $html .= '</div>';
  166 + $html .= '</div>';
  167 + $this->formulario .= $html;
  168 +
  169 + return $this;
  170 + }
  171 +
  172 + /**
  173 + *
  174 + * @param array $datos
  175 + * @param string $text
  176 + * @param array $extras
  177 + * @return $this
  178 + */
  179 + function createFormRadioButtonGroup($datos, $text, $extras = false) {
  180 + $html = '';
  181 + $html .= '<div class="form-group">';
  182 + $html .= '<label class="control-label col-lg-3">' . $text . '</label>';
  183 + $html .= '<div class="col-lg-9">';
  184 + foreach ($datos AS $indice => $dato) {
  185 + $checked = '';
  186 + if (isset($dato['checked']) && $dato['checked'] == 1) {
  187 + $checked = ' checked="checked"';
  188 + }
  189 + $html .= '<div class="radio">';
  190 + $html .= '<label>';
  191 + $html .= '<input ' . $checked . ' type="radio" value="' . $dato['value'] . '" name="' . $dato['name'] . '"';
  192 +
  193 + if ($extras && $extras[$indice]) {
  194 + foreach ($extras[$indice] AS $indiceExtras => $extra) {
  195 + $html .= $indiceExtras . '="' . $extra . '"';
  196 + }
  197 + }
  198 + $html .= ' />';
  199 +
  200 + $html .= $dato['text'];
  201 + $html .= '</label>';
  202 + $html .= '</div>';
  203 + }
  204 + $html .= '</div>';
  205 + $html .= '</div>';
  206 + $this->formulario .= $html;
  207 +
  208 + return $this;
  209 + }
  210 +
  211 + /**
  212 + *
  213 + * @param string $name
  214 + * @param string $contenido
  215 + * @param string $text
  216 + * @param string $title
  217 + * @param boolean $multiLang
  218 + * @param boolean $wysiwyg
  219 + * @return $this
  220 + */
  221 + public function generarTextArea($name, $contenido, $text, $title = '', $multiLang = 0, $wysiwyg = 1, $array=false) {
  222 + $html = '';
  223 + $html .= '<div class="form-group bootstrap">';
  224 + $html .= '<label class="control-label col-lg-3">';
  225 + $html .= '<span class="label-tooltip" data-toggle="tooltip" title="" data-original-title="' . $title . '">';
  226 + $html .= $text;
  227 + $html .= '</span>';
  228 + $html .= '</label>';
  229 + if ($multiLang) {
  230 + $idiomas = $this->getLanguages();
  231 + $idiomasSelect = $idiomas;
  232 + foreach ($idiomas AS $language) {
  233 + $html .= '<div class="translatable-field row lang-' . $language['id_lang'] . '">';
  234 + $html .= '<div class="col-lg-9">';
  235 + $html .= '<textarea ';
  236 + $html .= ' id="' . $name . '_' . $language['id_lang'] . '"';
  237 + if(!$array) {
  238 + $html .= ' name="' . $name . '_' . $language['id_lang'] . '"';
  239 + }else {
  240 + $html .= " name='{$name}[{$language['id_lang']}]'";
  241 + }
  242 + if ($wysiwyg) {
  243 + $html .= ' class="autoload_rte">';
  244 + } else {
  245 + $html .= '>';
  246 + }
  247 +
  248 + if (isset($contenido[$language['id_lang']])) {
  249 + $html .= $contenido[$language['id_lang']];
  250 + }
  251 +
  252 + $html .= ' </textarea>';
  253 + $html .= ' </div>';
  254 + $html .= ' <div class="col-lg-2">';
  255 + $html .= ' <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">';
  256 + $html .= $language['iso_code'];
  257 + $html .= ' <span class="caret"></span>';
  258 + $html .= ' </button> ';
  259 + $html .= ' <ul class="dropdown-menu"> ';
  260 + foreach ($idiomasSelect AS $idiomaSelect) {
  261 + $html .= ' <li><a href="javascript:hideOtherLanguage(' . $idiomaSelect['id_lang'] . ');">' . $idiomaSelect['name'] . '</a></li>';
  262 + }
  263 + $html .= ' </ul> ';
  264 + $html .= ' </div> ';
  265 + $html .= ' </div> ';
  266 + }
  267 + } else {
  268 + $html .= '<div class="col-lg-9">';
  269 + $html .= '<textarea ';
  270 + $html .= ' id="' . $name . '"';
  271 + $html .= ' name="' . $name . '"';
  272 + if ($wysiwyg) {
  273 + $html .= ' class="autoload_rte">';
  274 + } else {
  275 + $html .= '>';
  276 + }
  277 +
  278 + $html .= $contenido;
  279 + $html .= ' </textarea>';
  280 + $html .= ' </div>';
  281 + }
  282 + $html .= '</div>';
  283 + if ($wysiwyg) {
  284 + $html .= '<script>';
  285 + $html .= 'tinySetup({ ';
  286 + $html .= 'editor_selector: "autoload_rte"';
  287 + $html .= '}); ';
  288 + $html .= '</script>';
  289 + }
  290 + if($multiLang) {
  291 + $html .= '<script>hideOtherLanguage(' . Configuration::getGlobalValue('PS_LANG_DEFAULT') . ');</script>';
  292 + }
  293 + $this->formulario .= $html;
  294 +
  295 + return $this;
  296 + }
  297 +
  298 + /**
  299 + *
  300 + * @param string $name
  301 + * @param string $value
  302 + * @param string $text
  303 + * @param string $title
  304 + * @param boolean $multiLang
  305 + * @param boolean $password
  306 + * @param array $extras
  307 + * @param boolean $autofocus
  308 + * @return $this
  309 + */
  310 + function createFormTextGroup($name, $value, $text, $title = '', $multiLang = 0, $password = 0, $extras = false, $autofocus = false) {
  311 + $tipo = 'text';
  312 + if ($password == 1) {
  313 + $tipo = 'password';
  314 + }
  315 +
  316 + $html = '<div class="form-group bootstrap">';
  317 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  318 + $html .= '<span title="' . $title . '">';
  319 + $html .= $text;
  320 + $html .= '</span>';
  321 + $html .= '</label>';
  322 + if ($multiLang) {
  323 + $idiomas = $this->getLanguages();
  324 + $idiomasSelect = $idiomas;
  325 + foreach ($idiomas AS $language) {
  326 +
  327 + if(isset($value[$language['id_lang']])){
  328 + $valueString=$value[$language['id_lang']];
  329 + }else{
  330 + $valueString='';
  331 + }
  332 +
  333 + $html .= '<div class="col-lg-3 translatable-field row lang-' . $language['id_lang'] . '">';
  334 + $html .= '<div class="col-lg-9">';
  335 + $html .= "<input type='text' id='{$name}_{$language['id_lang']}' name='{$name}[{$language['id_lang']}]' value='$valueString'/>";
  336 + $html .= '</div>';
  337 + $html .= '<div class="col-lg-2">';
  338 + $html .= '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">';
  339 + $html .= $language['iso_code'];
  340 + $html .= '<span class="caret"></span>';
  341 + $html .= '</button> ';
  342 + $html .= '<ul class="dropdown-menu"> ';
  343 + foreach ($idiomasSelect AS $idiomaSelect) {
  344 + $html .= ' <li><a href="javascript:hideOtherLanguage(' . $idiomaSelect['id_lang'] . ');">' . $idiomaSelect['name'] . '</a></li>';
  345 + }
  346 + $html .= ' </ul> ';
  347 + $html .= ' </div> ';
  348 + $html .= ' </div> ';
  349 + }
  350 + } else {
  351 + $html .= '<div class="col-lg-3">';
  352 + $html .= '<input type="' . $tipo . '" value="' . $value . '" name="' . $name . '" id="' . $name . '"';
  353 + if ($extras && is_array($extras)) {
  354 + foreach ($extras AS $indice => $extra) {
  355 + $html.= ' ' . $indice . '="' . $extra . '"';
  356 + }
  357 + }
  358 + if ($autofocus) {
  359 + $html .= ' autofocus ';
  360 + }
  361 + $html .= '>';
  362 + $html .= '</div>';
  363 + }
  364 + $html .= '</div>';
  365 +
  366 + $html .= '<script>hideOtherLanguage(' . Configuration::getGlobalValue('PS_LANG_DEFAULT') . ');</script>';
  367 +
  368 + $this->formulario .= $html;
  369 +
  370 + return $this;
  371 + }
  372 +
  373 + /**
  374 + *
  375 + * @param string $name
  376 + * @param string $text
  377 + * @param string $title
  378 + * @param array $extras
  379 + * @return $this
  380 + */
  381 + function createFormDate($name, $text, $title = '', $extras = false) {
  382 + $tipo = 'text';
  383 +
  384 +
  385 + $html = '<div class="form-group bootstrap">';
  386 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  387 + $html .= '<span title="' . $title . '">';
  388 + $html .= $text;
  389 + $html .= '</span>';
  390 + $html .= '</label>';
  391 +
  392 + $html .= '<div class="col-lg-3">';
  393 + $html .= '<input type="' . $tipo . '" data-hex="true" value="" name="' . $name . '" id="' . $name . '"';
  394 + if ($extras && is_array($extras)) {
  395 + foreach ($extras AS $indice => $extra) {
  396 + $html.= ' ' . $indice . '="' . $extra . '"';
  397 + }
  398 + }
  399 + $html .= '>';
  400 + $html .= '</div>';
  401 + $html .= '</div>';
  402 + $html .= '<script type="text/javascript">';
  403 + $html .= '$(function(){ ';
  404 + $html .= ' $("#' . $name . '").datepicker(); ';
  405 + $html .= '});';
  406 + $html .= '</script>';
  407 +
  408 +
  409 + $this->formulario .= $html;
  410 +
  411 + return $this;
  412 + }
  413 +
  414 + /**
  415 + * Permite crear un elemento de tipo color picker
  416 + * @param string $name
  417 + * @param string $value
  418 + * @param string $text
  419 + * @param string $title
  420 + * @param array $extras
  421 + * @param boolean $autofocus
  422 + */
  423 + function createFormColorPicker($name, $value, $text, $title = '', $extras = false, $autofocus = false) {
  424 + $html = '<div class="form-group ';
  425 + if ($this->modulo->versionPS < 17) {
  426 + $html .= 'bootstrap';
  427 + }
  428 + $html .= '">';
  429 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  430 + $html .= '<span title="' . $title . '">';
  431 + $html .= $text;
  432 + $html .= '</span>';
  433 + $html .= '</label>';
  434 + $html .= '<div class="col-lg-2">';
  435 + $html .= '<div class="row">';
  436 + $html .= '<div class="input-group">';
  437 + $html .= '<input type="text" value="' . $value . '" name="' . $name . '" id="' . $name . '" data-hex="true"';
  438 + $class = '';
  439 + if ($extras && is_array($extras)) {
  440 + foreach ($extras AS $indice => $extra) {
  441 + if ($indice == 'class') {
  442 + $class .= ' ' . $extra;
  443 + } else {
  444 + $html.= ' ' . $indice . '="' . $extra . '"';
  445 + }
  446 + }
  447 + }
  448 + $html .= ' class="color mColorPickerInput';
  449 + if ($class) {
  450 + $html .= ' ' . $class . '"';
  451 + } else {
  452 + $html .= '"';
  453 + }
  454 + if ($autofocus) {
  455 + $html .= ' autofocus ';
  456 + }
  457 + $html .= '>';
  458 + $html .= '</div>';
  459 + $html .= '</div>';
  460 + $html .= '</div>';
  461 + $html .= '</div>';
  462 + $this->formulario .= $html;
  463 + }
  464 +
  465 + /**
  466 + *
  467 + * @param string $name
  468 + * @param string $value
  469 + * @param string $text
  470 + * @param string $title
  471 + * @param string $outputDelimiter
  472 + * @param string $paceholder
  473 + * @param array $options
  474 + * @return $this
  475 + */
  476 + function createFormTagTextGroup($name, $value, $text, $title = '', $outputDelimiter = ',', $paceholder = 'Añadir etiqueta', $options = array()) {
  477 + $html = '';
  478 + $this->addJS('jquery.tagify.js');
  479 + $html .= '<div class="form-group bootstrap">';
  480 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  481 + $html .= '<span title="' . $title . '">';
  482 + $html .= $text;
  483 + $html .= '</span>';
  484 + $html .= '</label>';
  485 + $html .= '<div class="col-lg-9">';
  486 + $html .= '<input type="text" value="' . $value . '" name="' . $name . '" id="' . $name . '"';
  487 + if ($options) {
  488 + foreach ($options AS $indice => $valor) {
  489 + if ($indice == 'class') {
  490 + $valor .= ' tagify';
  491 + }
  492 + $html .= $indice . '="' . $valor . '"';
  493 + }
  494 + }
  495 + $html .= '>';
  496 + $html .= '</div>';
  497 + $html .= '</div>';
  498 + $html .= '<script>';
  499 + $html .= '$().ready(function () { ';
  500 + $html .= ' $("#' . $name . '").tagify({'
  501 + . 'delimiters: [13,44], '
  502 + . 'addTagPrompt: "' . $paceholder . '",'
  503 + . 'outputDelimiter: "' . $outputDelimiter . '"'
  504 + . '});';
  505 +
  506 + $html .= ' $("#' . $name . '").closest("form").submit( function() {';
  507 + $html .= ' $(this).find("#' . $name . '").val($("#' . $name . '").tagify("serialize"));';
  508 + $html .= ' });';
  509 + $html .= '});';
  510 + $html .= '</script>';
  511 + $this->formulario .= $html;
  512 +
  513 + return $this;
  514 + }
  515 +
  516 + /**
  517 + *
  518 + * @param string $name
  519 + * @param string $text
  520 + * @param int $defaultValue
  521 + * @param int $min
  522 + * @param int $max
  523 + * @param int $step
  524 + * @param string $title
  525 + * @return $this
  526 + */
  527 + function createFormSelectNumerico($name, $text, $defaultValue, $min, $max, $step = 1, $title = '') {
  528 + $html = '';
  529 + $html .= '<div class="form-group">';
  530 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  531 + $html .= '<span title="' . $title . '">';
  532 + $html .= $text;
  533 + $html .= '</span>';
  534 + $html .= '</label>';
  535 + $html .= '<div class="col-lg-3">';
  536 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  537 + for ($i = $min; $i < $max; $i = $i + $step) {
  538 + $selected = '';
  539 + if ($i == $defaultValue) {
  540 + $selected = ' selected="selected" ';
  541 + }
  542 + $html .= '<option value="' . $i . '" ' . $selected . '>' . $i . '</option>';
  543 + }
  544 + $html .= '</select>';
  545 + $html .= '</div>';
  546 + $html .= '</div>';
  547 + $this->formulario .= $html;
  548 +
  549 + return $this;
  550 + }
  551 +
  552 + function createHidden($name, $value) {
  553 + $html = '<input type="hidden" name="' . $name . '" value="' . $value . '" id="' . $name . '" />';
  554 + $this->formulario .= $html;
  555 +
  556 + return $this;
  557 + }
  558 +
  559 + /**
  560 + *
  561 + * @param string $name
  562 + * @param string $text
  563 + * @param array $extras
  564 + * @return $this
  565 + */
  566 + function createSubmitButton($name, $text, $extras = false) {
  567 + $html = '<button class="btn btn-default pull-right" name="' . $name . '" type="submit"';
  568 + $html .= $this->addExtras($extras);
  569 + $html .= '>';
  570 + $html .= '<i class="process-icon-save"></i>' . $text . '</button>';
  571 + $this->formulario .= $html;
  572 +
  573 + return $this;
  574 + }
  575 +
  576 + /**
  577 + *
  578 + * @param string $name
  579 + * @param string $text
  580 + * @param array $extras
  581 + * @param string $type
  582 + * @param string $icon
  583 + * @return $this
  584 + */
  585 + public function createButton($name, $text, $extras = false, $type = 'button', $icon = 'plus') {
  586 + $html = '<button class="btn btn-default pull-right" name="' . $name . '" type="' . $type . '"';
  587 + if ($extras && is_array($extras)) {
  588 + foreach ($extras AS $indice => $extra) {
  589 + $html.= ' ' . $indice . '="' . $extra . '"';
  590 + }
  591 + }
  592 +
  593 + $html .= '>';
  594 + $html .= '<i class="process-icon-' . $icon . '"></i>' . $text . '</button>';
  595 + $this->formulario .= $html;
  596 +
  597 + return $this;
  598 + }
  599 +
  600 + /**
  601 + * Crea un select.
  602 + * @param string $name
  603 + * @param array $text
  604 + * @param string $datos valor => visible
  605 + * @param string $defaultValue
  606 + * @param string $title
  607 + * @param array $extras
  608 + * @return $this
  609 + */
  610 + function createFormSelect($name, $text, $datos, $defaultValue, $title = '', $extras = null) {
  611 + $html = '';
  612 + $html .= '<div class="form-group">';
  613 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  614 + $html .= '<span title="' . $title . '">';
  615 + $html .= $text;
  616 + $html .= '</span>';
  617 + $html .= '</label>';
  618 + $html .= '<div class="col-lg-3">';
  619 + $html .= '<select name="' . $name . '" id="' . $name . '" ';
  620 + if ($extras) {
  621 + foreach ($extras AS $indice => $extra) {
  622 + $html .= $indice . '="' . $extra . '"';
  623 + }
  624 + }
  625 + $html .= '>';
  626 + foreach ($datos AS $valor => $texto) {
  627 + $selected = '';
  628 + if ($valor == $defaultValue) {
  629 + $selected = ' selected="selected" ';
  630 + }
  631 + $html .= '<option value="' . $valor . '" ' . $selected . '>' . $texto . '</option>';
  632 + }
  633 + $html .= '</select>';
  634 + $html .= '</div>';
  635 + $html .= '</div>';
  636 + $this->formulario .= $html;
  637 +
  638 + return $this;
  639 + }
  640 +
  641 + /**
  642 + *
  643 + * @param string $name
  644 + * @param string $label
  645 + * @param string $title
  646 + * @param string $url
  647 + * @return $this
  648 + */
  649 + public function createFormFileButton($name, $label, $title, $url) {
  650 + $this->addJS('spin.js');
  651 + $this->addJS('ladda.js');
  652 +
  653 + $this->addJS('jquery.iframe-transport.js');
  654 + $this->addJS('jquery.fileupload.js');
  655 + $this->addJS('jquery.fileupload-process.js');
  656 + $this->addJS('jquery.fileupload-validate.js');
  657 + //$this->addJS('jquery.fileupload-image.js');
  658 +
  659 + $html = '';
  660 + $html .= '<div class="form-group">';
  661 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  662 + $html .= '<span title="" data-toggle="tooltip" class="label-tooltip" data-original-title="' . $title . '">' . $label . '</span>';
  663 + $html .= '</label>';
  664 + $html .= '<div class="col-lg-8 bootstrap">';
  665 + $html .= '<input type="file" class="hide" data-url="' . $url . '" name="' . $name . '" id="' . $name . '">';
  666 + $html .= '<button id="' . $name . '-add-button" type="button" data-size="s" data-style="expand-right" class="btn btn-default"><span class="ladda-label">';
  667 + $html .= '<i class="icon-plus-sign"></i> ' . $label . '';
  668 + $html .= '</span><span class="ladda-spinner"></span></button>';
  669 + $html .= '<!--';
  670 + $html .= '<div class="alert alert-success" id="' . $name . '-success" style="display:none">' . $this->modulo->l('Carga exitosa') . '</div>';
  671 + $html .= '<div class="alert alert-danger" id="' . $name . '-errors" style="display:none"></div>';
  672 + $html .= '-->';
  673 + $html .= '</div>';
  674 +
  675 + $html .= "<script type='text/javascript'>
  676 + function humanizeSize(bytes) {
  677 + if (typeof bytes !== 'number') {
  678 + return '';
  679 + }
  680 + if (bytes <= 1000000000) {
  681 + return (bytes / 1000000000).toFixed(2) + ' GB';
  682 + }
  683 + if (bytes <= 1000000) {
  684 + return (bytes / 1000000).toFixed(2) + ' MB';
  685 + }
  686 + return (bytes / 1000).toFixed(2) + ' KB';
  687 + }
  688 + $(document ).ready(function() {
  689 + var " . $name . "_add_button = Ladda.create(document.querySelector('#" . $name . "-add-button'));
  690 + var " . $name . "total_files=0;
  691 + var success_message = '" . $this->modulo->l("Carga exitosa") . "'
  692 + $('#" . $name . "-add-button').removeAttr('disabled');
  693 + $('#" . $name . "').fileupload({
  694 + dataType: 'jsonp',
  695 + autoUpload: true,
  696 + singleFileUploads: true,
  697 + maxFileSize: 838860800,
  698 + success: function (e) {
  699 + //showSuccessMessage(success_message);
  700 + },
  701 + start: function (e) {
  702 + " . $name . "_add_button.start();
  703 + },
  704 + fail: function (e, data) {
  705 + showErrorMessage(data.errorThrown.message);
  706 + },
  707 + done: function (e, data) {
  708 + if (data.result) {
  709 + if (data.result.codError != 0) {
  710 + showErrorMessage(data.result.msgError);
  711 + }else {
  712 + showSuccessMessage(data.result.msgError);
  713 + }
  714 + }
  715 + $('#" . $name . "-add-button').removeProp('disabled');
  716 + }
  717 + }).on('fileuploadalways', function (e, data) {
  718 + $('#" . $name . "-add-button').removeProp('disabled');
  719 + }).on('fileuploadprocessalways', function (e, data) {
  720 + var index = data.index, file = data.files[index];
  721 + }).on('fileuploadsubmit', function (e, data) {
  722 + var params = new Object();
  723 + $('input[id^=\"" . $name . "_name_\"]').each(function() {
  724 + id = $(this).prop('id').replace('" . $name . "_name_', '" . $name . "_name[') + ']';
  725 + params[id] = $(this).val();
  726 + });
  727 + $('textarea[id^=\"" . $name . "_description_\"]').each(function() {
  728 + id = $(this).prop('id').replace('" . $name . "_description_', '" . $name . "_description[') + ']';
  729 + params[id] = $(this).val();
  730 + });
  731 + data.formData = params;
  732 + });
  733 + $('#" . $name . "-add-button').on('click', function() {
  734 + //$('#attachment_file-success').hide();
  735 + //$('#attachment_file-errors').html('').hide();
  736 + " . $name . "_file_total_files = 0;
  737 + $('#" . $name . "').trigger('click');
  738 + });
  739 + });";
  740 + $html .= '</script>';
  741 + $html .= '</div>';
  742 + $this->formulario .= $html;
  743 +
  744 + return $this;
  745 + }
  746 +
  747 + /**
  748 + *
  749 + * @param string $name
  750 + * @param string $text
  751 + * @param mixed $defaultValue
  752 + * @param string $title
  753 + * @return $this
  754 + */
  755 + public function generarSelectIdioma($name, $text, $defaultValue, $title) {
  756 + $html = '';
  757 + $html .= '<div class="form-group">';
  758 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  759 + $html .= '<span title="' . $title . '">';
  760 + $html .= $text;
  761 + $html .= '</span>';
  762 + $html .= '</label>';
  763 + $html .= '<div class="col-lg-3">';
  764 + $html .= '<select name="' . $name . '" id="' . $name . '" class="selectIdioma">';
  765 + $idiomas = Language::getLanguages();
  766 + foreach ($idiomas AS $idioma) {
  767 + $selected = '';
  768 + if ($defaultValue == $idioma['id_lang']) {
  769 + $selected = ' selected="selected" ';
  770 + }
  771 + $html .= '<option value="' . $idioma['id_lang'] . '" ' . $selected . '>' . $idioma['name'] . '</option>';
  772 + }
  773 + $html .= '</select>';
  774 + $html .= '</div>';
  775 + $html .= '</div>';
  776 + reset($idiomas);
  777 + $html .= '<style>';
  778 + foreach ($idiomas AS $idioma) {
  779 + $html .= " .selectIdioma option[value='{$idioma['id_lang']}'] { background-image: url('.." . _THEME_LANG_DIR_ . "{$idioma['id_lang']}.jpg'); }";
  780 + }
  781 + $html .= '</style>';
  782 + $html .= '<script>';
  783 + $html .= ' var rutaImagenes="..' . _THEME_LANG_DIR_ . '"';
  784 + $html .= '</script>';
  785 + $this->formulario .= $html;
  786 +
  787 + return $this;
  788 + }
  789 +
  790 + private function addCSS($css) {
  791 + $tab = Tools::getValue('tab', 0);
  792 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  793 + if ($this->context->controller instanceof stdClass) {
  794 + $this->context->controller = new AdminModulesController();
  795 + }
  796 + if ($this->_modulePath) {
  797 + $this->context->controller->addCss($this->_modulePath . '/forms/' . $css, 'all');
  798 + } else {
  799 + $this->context->controller->addCss($this->_path . '/forms/' . $css, 'all');
  800 + }
  801 + }
  802 + return;
  803 + }
  804 +
  805 + private function addJS($js) {
  806 + $tab = Tools::getValue('tab', 0);
  807 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  808 +
  809 + if ($this->context->controller instanceof stdClass) {
  810 + $this->context->controller = new AdminModulesController();
  811 + }
  812 + if ($this->_modulePath) {
  813 + $this->context->controller->addJs($this->_modulePath . '/forms/' . $js);
  814 + } else {
  815 + $this->context->controller->addJs($this->_path . '/forms/' . $js);
  816 + }
  817 + }
  818 + return;
  819 + }
  820 +
  821 + public function getLanguages() {
  822 + $languages = Language::getLanguages();
  823 + return $languages;
  824 + }
  825 +
  826 + /**
  827 + *
  828 + * @param string $name
  829 + * @param string $text
  830 + * @param int $defaultValue
  831 + * @param string $title
  832 + * @return $this
  833 + */
  834 + public function createSelectSupplier($name, $text, $defaultValue, $title = '') {
  835 + $suppliers = Supplier::getSuppliers();
  836 + $html = '';
  837 + $html .= '<div class="form-group">';
  838 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  839 + $html .= '<span title="' . $title . '">';
  840 + $html .= $text;
  841 + $html .= '</span>';
  842 + $html .= '</label>';
  843 + $html .= '<div class="col-lg-3">';
  844 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  845 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un proveedor - ') . '</option>';
  846 + foreach ($suppliers AS $supplier) {
  847 + $selected = '';
  848 + if ($supplier['id_supplier'] == $defaultValue) {
  849 + $selected = ' selected="selected"';
  850 + }
  851 + $html .= '<option ' . $selected . ' value="' . $supplier['id_supplier'] . '">' . htmlspecialchars(trim($supplier['name'])) . '</option>';
  852 + }
  853 +
  854 + $html .= '</select>';
  855 + $html .= '</div>';
  856 + $html .= '</div>';
  857 + $this->formulario .= $html;
  858 +
  859 + return $this;
  860 + }
  861 +
  862 + /**
  863 + *
  864 + * @param string $name
  865 + * @param string $text
  866 + * @param int $defaultValue
  867 + * @param string $title
  868 + * @return $this
  869 + */
  870 + public function createSelectManufacturer($name, $text, $defaultValue, $title = '') {
  871 + $manufacturers = Manufacturer::getManufacturers();
  872 + $html = '';
  873 + $html .= '<div class="form-group">';
  874 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  875 + $html .= '<span title="' . $title . '">';
  876 + $html .= $text;
  877 + $html .= '</span>';
  878 + $html .= '</label>';
  879 + $html .= '<div class="col-lg-3">';
  880 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  881 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un fabricante - ') . '</option>';
  882 + foreach ($manufacturers AS $manufacturer) {
  883 + $selected = '';
  884 + if ($manufacturer['id_manufacturer'] == $defaultValue) {
  885 + $selected = ' selected="selected"';
  886 + }
  887 + $html .= '<option ' . $selected . ' value="' . $manufacturer['id_manufacturer'] . '">' . htmlspecialchars(trim($manufacturer['name'])) . '</option>';
  888 + }
  889 +
  890 + $html .= '</select>';
  891 + $html .= '</div>';
  892 + $html .= '</div>';
  893 + $this->formulario .= $html;
  894 +
  895 + return $this;
  896 + }
  897 +
  898 + /**
  899 + *
  900 + * @param int $id_lang
  901 + * @param string $name
  902 + * @param string $text
  903 + * @param array $value
  904 + * @param string $title
  905 + * @return $this
  906 + */
  907 + public function createSelectGroups($id_lang, $name, $text, $value, $title = '') {
  908 + $groups = Group::getGroups($id_lang);
  909 + $html = '';
  910 + $html .= '<div class="form-group">';
  911 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  912 + $html .= '<span title="' . $title . '">';
  913 + $html .= $text;
  914 + $html .= '</span>';
  915 + $html .= '</label>';
  916 + $html .= '<div class="col-lg-3">';
  917 + foreach ($groups as $group) {
  918 + $marcado = '';
  919 + foreach ($value as $v) {
  920 + if ($group['id_group'] == $v) {
  921 + $marcado = ' checked ';
  922 + break;
  923 + }
  924 + }
  925 + $html .= '<input type="checkbox" ' . $marcado . ' value="' . $group['id_group'] . '" name="' . $name . '[]" id="' . $name . '"> ' . $group['name'] . '<br/>';
  926 + }
  927 + $html .= '</div>';
  928 + $html .= '</div>';
  929 +
  930 + $this->formulario .= $html;
  931 + return $this;
  932 + }
  933 +
  934 + /**
  935 + *
  936 + * @param int $id_lang
  937 + * @param string $name
  938 + * @param string $text
  939 + * @param array $value
  940 + * @param string $title
  941 + * @param boolean $referencia
  942 + * @return $this
  943 + */
  944 + public function createCheckBoxCarriers($id_lang, $name, $text, $value, $title = '', $referencia = false) {
  945 + $carriers = Carrier::getCarriers($id_lang);
  946 + $html = '';
  947 + $html .= '<div class="form-group">';
  948 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  949 + $html .= '<span title="' . $title . '">';
  950 + $html .= $text;
  951 + $html .= '</span>';
  952 + $html .= '</label>';
  953 + $html .= '<div class="col-lg-3">';
  954 + foreach ($carriers as $carrier) {
  955 + $idCarrier = $carrier['id_carrier'];
  956 + if ($referencia) {
  957 + $idCarrier = $carrier['id_reference'];
  958 + }
  959 + $marcado = '';
  960 + foreach ($value as $v) {
  961 + if ($idCarrier == $v) {
  962 + $marcado = ' checked ';
  963 + break;
  964 + }
  965 + }
  966 + $html .= '<input type="checkbox" ' . $marcado . ' value="' . $idCarrier . '" name="' . $name . '[]" id="' . $name . '_' . $idCarrier . '"> ' . $carrier['name'] . '<br/>';
  967 + }
  968 + $html .= '</div>';
  969 + $html .= '</div>';
  970 +
  971 + $this->formulario .= $html;
  972 + return $this;
  973 + }
  974 +
  975 + /**
  976 + *
  977 + * @param int $id_lang
  978 + * @param string $name
  979 + * @param string $text
  980 + * @param array $idDefault
  981 + * @param string $title
  982 + * @param boolean $referencia
  983 + * @return $this
  984 + */
  985 + public function createSelectCarriers($id_lang, $name, $text, $idDefault, $title = '', $referencia = false) {
  986 + $carriers = Carrier::getCarriers($id_lang);
  987 + $html = '';
  988 + $html .= '<div class="form-group">';
  989 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  990 + $html .= '<span title="' . $title . '">';
  991 + $html .= $text;
  992 + $html .= '</span>';
  993 + $html .= '</label>';
  994 + $html .= '<div class="col-lg-3">';
  995 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  996 + foreach ($carriers as $carrier) {
  997 + $idCarrier = $carrier['id_carrier'];
  998 + if ($referencia) {
  999 + $idCarrier = $carrier['id_reference'];
  1000 + }
  1001 + $selected = '';
  1002 + if ($idCarrier == $idDefault) {
  1003 + $selected = ' selected="selected" ';
  1004 + }
  1005 + $html .= '<option ' . $selected . ' value="' . $idCarrier . '> ' . $carrier['name'] . '</option>';
  1006 + }
  1007 + $html .= '</select>';
  1008 + $html .= '</div>';
  1009 + $html .= '</div>';
  1010 +
  1011 + $this->formulario .= $html;
  1012 + return $this;
  1013 + }
  1014 +
  1015 + /**
  1016 + *
  1017 + * @param string $name
  1018 + * @param string $text
  1019 + * @param mixed $defaultValue
  1020 + * @param string $title
  1021 + * @return $this
  1022 + */
  1023 + function createSelectNameImages($name, $text, $defaultValue, $title = '') {
  1024 + $images = ImageType::getImagesTypes();
  1025 + $html = '';
  1026 + $html .= '<div class="form-group">';
  1027 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1028 + $html .= '<span title="' . $title . '">';
  1029 + $html .= $text;
  1030 + $html .= '</span>';
  1031 + $html .= '</label>';
  1032 + $html .= '<div class="col-lg-3">';
  1033 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1034 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un tipo de Imagen - ') . '</option>';
  1035 + foreach ($images AS $image) {
  1036 + $selected = '';
  1037 + if ($image['id_image_type'] == $defaultValue) {
  1038 + $selected = ' selected="selected"';
  1039 + }
  1040 + $html .= '<option ' . $selected . ' value="' . $image['id_image_type'] . '">' . htmlspecialchars(trim($image['name'])) . '</option>';
  1041 + }
  1042 +
  1043 + $html .= '</select>';
  1044 + $html .= '</div>';
  1045 + $html .= '</div>';
  1046 + $this->formulario .= $html;
  1047 +
  1048 + return $this;
  1049 + }
  1050 +
  1051 + public function mostrarCategorias($name, $prefijo, $idParentCategoria = 2) {
  1052 + $sql = ' SELECT '
  1053 + . ' c.id_category, c.id_parent, cl.name, s.id_category AS checked, s.margen '
  1054 + . ' FROM '
  1055 + . ' ' . _DB_PREFIX_ . 'category AS c '
  1056 + . ' INNER JOIN ' . _DB_PREFIX_ . 'category_lang AS cl '
  1057 + . ' ON '
  1058 + . ' cl.id_category = c.id_category '
  1059 + . ' AND cl.id_lang = ' . $this->idLang . ''
  1060 + . ' AND c.id_shop_default = cl.id_shop '
  1061 + . ' AND c.id_shop_default = ' . $this->idShop . ''
  1062 + . ' LEFT JOIN ' . _DB_PREFIX_ . $prefijo . 'categorias AS s '
  1063 + . ' ON s.id_category = c.id_category '
  1064 + . ' WHERE '
  1065 + . ' c.id_parent = ' . $idParentCategoria . ' '
  1066 + . ' ORDER BY '
  1067 + . ' c.id_parent, c.id_category ';
  1068 + $categorias = Db::getInstance()->executeS($sql);
  1069 + $html = '<table id="' . $name . '">';
  1070 + $html .= '<thead>';
  1071 + $html .= '<tr>';
  1072 + $html .= '<td>&nbsp;</td>';
  1073 + $html .= '<td><input type="button" onclick="checkImaxAll(this.form, \'catCheckbox\')" name="checkme" value="' . $this->modulo->l('Invertir Seleccion') . '"></td>';
  1074 + $html .= '<td>' . $this->modulo->l('Margen general') . '&nbsp;<input type="textbox" onChange="llenarMargen(this.form)" id="llenarMargenText" value=""/></td>';
  1075 + $html .= '</tr>';
  1076 + $html .= '<tr>';
  1077 + $html .= '<th>' . $this->modulo->l('Categoria') . '</th>';
  1078 + $html .= '<th>' . $this->modulo->l('Marcar para Exportar') . '</th>';
  1079 + $html .= '<th>' . $this->modulo->l('Margen') . '</th>';
  1080 + $html .= '</tr>';
  1081 + $html .= '</thead>';
  1082 + foreach ($categorias AS $categoria) {
  1083 + $num = 0;
  1084 + $html .= '<tr>';
  1085 + $html .= '<td><b>' . $categoria['name'] . '</b></td>';
  1086 + $checked = "";
  1087 + $porcentaje = "";
  1088 + if (!empty($categoria['checked'])) {
  1089 + $checked = ' checked="checked"';
  1090 + $porcentaje = $categoria['margen'];
  1091 + }
  1092 + $html .= '<td><input type="checkbox" class="catCheckbox" ' . $checked . ' value="' . $categoria['id_category'] . '" name="categoria[' . $categoria['id_category'] . '][check]" /></td>';
  1093 + $html .= '<td><input class="catInput" type="text" name="categoria[' . $categoria['id_category'] . '][margen]" value="' . $porcentaje . '"/></td>';
  1094 + $html .= '</tr>';
  1095 + $this->getHijosTable($categoria, $html, $num, $prefijo);
  1096 + }
  1097 +
  1098 + $html .= '</table>';
  1099 +
  1100 + $html .= '<input type="hidden" name="accion" value="addPorcentajesCategorias">';
  1101 +
  1102 + $html .= '</fieldset>';
  1103 + $this->formulario .= $html;
  1104 + return $this;
  1105 + }
  1106 +
  1107 + private function getHijosTable($categoria, &$html, &$num, $prefijo) {
  1108 + $numInterno = $num;
  1109 + $sqlHijos = ' SELECT '
  1110 + . ' c.id_category, c.id_parent, cl.name, s.id_category AS checked, s.margen '
  1111 + . ' FROM '
  1112 + . ' ' . _DB_PREFIX_ . 'category AS c '
  1113 + . ' INNER JOIN ' . _DB_PREFIX_ . 'category_lang AS cl '
  1114 + . ' ON '
  1115 + . ' cl.id_category = c.id_category '
  1116 + . ' AND cl.id_lang = ' . $this->idLang . ' '
  1117 + . ' AND c.id_shop_default = cl.id_shop '
  1118 + . ' AND c.id_shop_default = ' . $this->idShop . ' '
  1119 + . ' LEFT JOIN ' . _DB_PREFIX_ . $prefijo . 'categorias AS s '
  1120 + . ' ON s.id_category = c.id_category '
  1121 + . ' WHERE '
  1122 + . ' c.id_parent=' . $categoria['id_category'];
  1123 +
  1124 + $categoriasHijos = Db::getInstance()->executeS($sqlHijos);
  1125 + $num++;
  1126 + foreach ($categoriasHijos AS $categoriaHijo) {
  1127 + $checked = "";
  1128 + $porcentaje = "";
  1129 + if (!empty($categoriaHijo['checked']) && $categoriaHijo['checked'] != null) {
  1130 + $checked = ' checked="checked"';
  1131 + $porcentaje = $categoriaHijo['margen'];
  1132 + }
  1133 + $numClase = $num;
  1134 + $html .= '<tr class="tr_' . $numClase . '">';
  1135 + $html .= '<td>' . str_repeat("&nbsp;", $num * 2) . $categoriaHijo['name'] . '</td>';
  1136 + $html .= '<td><input type="checkbox" class="catCheckbox" ' . $checked . ' value="' . $categoriaHijo['id_category'] . '" name="categoria[' . $categoriaHijo['id_category'] . '][check]" /></td>';
  1137 + $html .= '<td><input class="catInput" type="text" name="categoria[' . $categoriaHijo['id_category'] . '][margen]" value="' . $porcentaje . '"/></td>';
  1138 + $html .= '</tr>';
  1139 + $this->getHijos($categoriaHijo, $html, $num);
  1140 + }
  1141 + $num = $numInterno;
  1142 + }
  1143 +
  1144 + /**
  1145 + *
  1146 + * @param string $name
  1147 + * @param string $text
  1148 + * @param int $defaultValue
  1149 + * @param string $title
  1150 + * @param boolean $active
  1151 + * @param boolean $chosen
  1152 + * @return $this
  1153 + */
  1154 + public function createSelectCategory($name, $text, $defaultValue, $title = '', $active = true, $chosen = false) {
  1155 + $categories = Category::getHomeCategories($this->idLang, $active);
  1156 + $html = '';
  1157 + $html .= '<div class="form-group">';
  1158 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1159 + $html .= '<span title="' . $title . '">';
  1160 + $html .= $text;
  1161 + $html .= '</span>';
  1162 + $html .= '</label>';
  1163 + $html .= '<div class="col-lg-3">';
  1164 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1165 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione una categoria - ') . '</option>';
  1166 + $categoriaHome = new Category(Configuration::get('PS_HOME_CATEGORY'), $this->idLang);
  1167 + $temp = array();
  1168 + $temp['id_category'] = $categoriaHome->id;
  1169 + $temp['name'] = $categoriaHome->name;
  1170 + array_unshift($categories, $temp);
  1171 + //$categories[] = $temp;
  1172 + foreach ($categories AS $category) {
  1173 + $num = 1;
  1174 + $selected = '';
  1175 + if ($category['id_category'] == $defaultValue) {
  1176 + $selected = ' selected="selected"';
  1177 + }
  1178 + $html .= '<option class="level_1" ' . $selected . ' value="' . $category['id_category'] . '">' . htmlspecialchars(trim($category['name'])) . '</option>';
  1179 + if ($category['id_category'] == $categoriaHome->id) {
  1180 + continue;
  1181 + }
  1182 + $this->getHijos($category['id_category'], $defaultValue, $html, $num, $active);
  1183 + }
  1184 + $html .= '</select>';
  1185 + $html .= '</div>';
  1186 + $html .= '</div>';
  1187 + if ($chosen) {
  1188 + $html .= '<script language="javascript">
  1189 + $(function(){$("#' . $name . '").chosen();});
  1190 + </script>';
  1191 + }
  1192 + $this->formulario .= $html;
  1193 +
  1194 + return $this;
  1195 + }
  1196 +
  1197 + private function getHijos($id_parent, $defaultValue, &$html, &$num, $active) {
  1198 + $numInterno = $num;
  1199 + $categories = Category::getChildren($id_parent, $this->idLang, $active);
  1200 + if (!$categories) {
  1201 + return;
  1202 + }
  1203 + $num++;
  1204 + foreach ($categories AS $category) {
  1205 + $selected = '';
  1206 + if ($category['id_category'] == $defaultValue) {
  1207 + $selected = ' selected="selected"';
  1208 + }
  1209 + $html .= '<option class="level_' . $num . '" ' . $selected . ' value="' . $category['id_category'] . '">' . htmlspecialchars(trim($category['name'])) . '</option>';
  1210 + $this->getHijos($category['id_category'], $defaultValue, $html, $num, $active);
  1211 + }
  1212 + $num = $numInterno;
  1213 + return true;
  1214 + }
  1215 +
  1216 + /**
  1217 + * Crea un upload File.
  1218 + * @param string $name Nombre del boton
  1219 + * @param string $text Texto informativo
  1220 + * @param string $title Titulo
  1221 + * @return $this
  1222 + */
  1223 + public function createFormUploadFile($name, $text, $title = '') {
  1224 + $html = '';
  1225 + $html .= '<div class="form-group">';
  1226 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1227 + $html .= '<span title="' . $title . '">';
  1228 + $html .= $text;
  1229 + $html .= '</span>';
  1230 + $html .= '</label>';
  1231 + $html .= '<div class="col-lg-3">';
  1232 + $html .= '<input type="file" name="' . $name . '" id="' . $name . '">';
  1233 + $html .= '</div>';
  1234 + $html .= '</div>';
  1235 + $this->formulario .= $html;
  1236 +
  1237 + return $this;
  1238 + }
  1239 +
  1240 + /**
  1241 + * Crea una tabla dinamica.
  1242 + * @param string $name Identificador de la tabla.
  1243 + * @param string $table Nombre de la tabla en la base de datos.
  1244 + * @param string[] $campos Nombres de los campos en base de datos.
  1245 + * @param string[] $camposBusqueda Nombres de los campos en base de datos para las busquedas.
  1246 + * @param string $url Ruta base.
  1247 + * @param string $serverfile Archivo de destino en el servidor.
  1248 + * @param string $contenidoCabecera Los "tr" para la cabecera.
  1249 + * @param string[] $codigoFormateadores [id del formateador => Contenido de la funcion formateadora, recibe los parametros column y row].
  1250 + * @param string $codigoEventoLoad Contenido de la funcion del evento load. La tabla viene en la variable grid. Tambien se dispone de e.
  1251 + * @return $this
  1252 + */
  1253 + function createTableAjax($name, $table, $campos, $camposBusqueda, $url, $serverfile, $contenidoCabecera, $codigoFormateadores = array(), $codigoEventoLoad = '') {
  1254 + $html = '<div class="form-group" style="float:none">';
  1255 + $html .= '<style>
  1256 + #' . $name . '-footer { float: none !important; }
  1257 + </style>'
  1258 + . '<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">'
  1259 + . '<link href="' . $url . 'bootgrid/css/jquery.bootgrid.css" rel="stylesheet">
  1260 + <table id="' . $name . '" class="table table-condensed table-hover table-striped">
  1261 + <thead>
  1262 + ' . $contenidoCabecera . '
  1263 + </thead>
  1264 + </table>';
  1265 +
  1266 + $campos = urlencode(serialize($campos));
  1267 + $camposBusqueda = urlencode(serialize($camposBusqueda));
  1268 + $table = urlencode(trim($table));
  1269 + $codigoFormateadoresString = '';
  1270 + foreach ($codigoFormateadores as $idFormateador => $codigoFormateador) {
  1271 + $codigoFormateadoresString .= <<<EOT
  1272 + $idFormateador: function(column, row){
  1273 + $codigoFormateador
  1274 + },
  1275 +EOT;
  1276 + }
  1277 + $html .= '<script src="' . $url . 'bootgrid/js/jquery.bootgrid.min.js"></script>';
  1278 + $html .= <<<EOT
  1279 + <script language="javascript">
  1280 + $(function() {
  1281 + var grid = $("#$name").bootgrid(
  1282 + {
  1283 + ajax:true,
  1284 + post: function() {
  1285 + return {
  1286 + id: "b0df282a-0d67-40e5-8558-c9e93b7befed"
  1287 + };
  1288 + },
  1289 + url: "{$url}bootgrid/php/$serverfile?tabla=$table&campos=$campos&camposBusqueda=$camposBusqueda",
  1290 + formatters: {
  1291 + $codigoFormateadoresString
  1292 + }
  1293 + }).on("loaded.rs.jquery.bootgrid", function(e)
  1294 + {
  1295 + $codigoEventoLoad
  1296 + });
  1297 + });
  1298 + </script>
  1299 +EOT;
  1300 + $html .= '</div>';
  1301 + $this->formulario .= $html;
  1302 +
  1303 + return $this;
  1304 + }
  1305 +
  1306 + private function addExtras($extras) {
  1307 + $html = '';
  1308 + if ($extras && is_array($extras)) {
  1309 + foreach ($extras AS $indice => $valor) {
  1310 + $html .= $indice . '="' . $valor . '"';
  1311 + }
  1312 + }
  1313 + return $html;
  1314 + }
  1315 +
  1316 + public function __toString() {
  1317 + return $this->renderForm(true);
  1318 + }
  1319 +
  1320 + public function createCheckBoxManufacturer($name, $defaultValue) {
  1321 + $manufacturers = Manufacturer::getManufacturers();
  1322 + $html = '';
  1323 + $numElementos = count($manufacturers);
  1324 + $elementos = ceil($numElementos / 3);
  1325 + $intercambio = array_flip($defaultValue);
  1326 + $i = 0;
  1327 + $html .= '<div class="bootstrap">';
  1328 + $html .= '<div class="form-group">';
  1329 + foreach ($manufacturers as $m) {
  1330 + $checked = '';
  1331 + if ($i == 0) {
  1332 + $html .= '<ul class="col-md-4">';
  1333 + }
  1334 + $html .= '<li class="clearfix">';
  1335 + $html .= '<label for="' . $name . '" class="control-label col-lg-6 col-md-6">';
  1336 + $html .= $m['name'];
  1337 + $html .= '</label>';
  1338 + $html .= '<div class="col-lg-4 col-md-4">';
  1339 + if (array_key_exists($m['id_manufacturer'], $intercambio)) {
  1340 + $checked = "checked='checked'";
  1341 + }
  1342 +
  1343 + $html .= '<input type="checkbox" ' . $checked . ' value="' . $m['id_manufacturer'] . '" name="' . $name . '[]" id="' . $name . '">';
  1344 + $html .= '</li>';
  1345 + $i++;
  1346 + if ($i >= $elementos) {
  1347 + $html .= '</ul>';
  1348 + $i = 0;
  1349 + }
  1350 + }
  1351 + $html .= '</ul>';
  1352 + $html .= '</div>';
  1353 + $html .= '</div>';
  1354 +
  1355 + $this->formulario .= $html;
  1356 +
  1357 + return $this;
  1358 + }
  1359 +
  1360 + /**
  1361 + *
  1362 + * @param int $id_lang
  1363 + * @param string $name
  1364 + * @param string $text
  1365 + * @param array $values
  1366 + * @param string $title
  1367 + * @return $this
  1368 + */
  1369 + public function createOptionStatuses($id_lang, $name, $text, $values, $title = '') {
  1370 + $status = OrderState::getOrderStates($id_lang);
  1371 + $html .= '<div class="form-group">';
  1372 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1373 + $html .= '<span title="' . $title . '">';
  1374 + $html .= $text;
  1375 + $html .= '</span>';
  1376 + $html .= '</label>';
  1377 + $html .= '<div class="col-lg-3">';
  1378 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1379 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione el estado - ') . '</option>';
  1380 + foreach ($status AS $statuses) {
  1381 + $idStatus = $statuses['id_order_state'];
  1382 + $marcado = '';
  1383 + foreach ($values as $v) {
  1384 + if ($idStatus == $v) {
  1385 + $marcado = ' checked ';
  1386 + break;
  1387 + }
  1388 + }
  1389 + $html .= '<option " ' . $marcado . ' value="' . $statuses['id_order_state'] . '">' . htmlspecialchars(trim($statuses['name'])) . '</option>';
  1390 + }
  1391 + $html .= '</select>';
  1392 + $html .= '</div>';
  1393 + $html .= '</div>';
  1394 +
  1395 + $this->formulario .= $html;
  1396 + return $this;
  1397 + }
  1398 +
  1399 + /**
  1400 + *
  1401 + * @param string $name
  1402 + * @param string $text
  1403 + * @param int $defaultValue
  1404 + * @param string $title
  1405 + * @return $this
  1406 + */
  1407 + public function createSelectStatuses($idLang, $name, $text, $defaultValue, $title = '') {
  1408 + $status = OrderState::getOrderStates($idLang);
  1409 + $html = '<div class="form-group">';
  1410 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1411 + $html .= '<span title="' . $title . '">';
  1412 + $html .= $text;
  1413 + $html .= '</span>';
  1414 + $html .= '</label>';
  1415 + $html .= '<div class="col-lg-3">';
  1416 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1417 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione el estado - ') . '</option>';
  1418 + foreach ($status AS $statuses) {
  1419 + $selected = '';
  1420 + if ($statuses['id_order_state'] == $defaultValue) {
  1421 + $selected = ' selected="selected"';
  1422 + }
  1423 + $html .= '<option " ' . $selected . ' value="' . $statuses['id_order_state'] . '">' . htmlspecialchars(trim($statuses['name'])) . '</option>';
  1424 + }
  1425 +
  1426 + $html .= '</select>';
  1427 + $html .= '</div>';
  1428 + $html .= '</div>';
  1429 + $this->formulario .= $html;
  1430 +
  1431 + return $this;
  1432 + }
  1433 +
  1434 + /**
  1435 + *
  1436 + * @param string $name
  1437 + * @param string $text
  1438 + * @param int $defaultValue
  1439 + * @param string $title
  1440 + * @return $this
  1441 + */
  1442 + public function createSelectFeatures($idLang, $name, $text, $defaultValue, $title = '') {
  1443 + $features = Feature::getFeatures($idLang);
  1444 +
  1445 + $html = '<div class="form-group">';
  1446 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1447 + $html .= '<span title="' . $title . '">';
  1448 + $html .= $text;
  1449 + $html .= '</span>';
  1450 + $html .= '</label>';
  1451 + $html .= '<div class="col-lg-3">';
  1452 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1453 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione una característica - ') . '</option>';
  1454 + foreach ($features AS $feature) {
  1455 + $selected = '';
  1456 + if ($feature['id_feature'] == $defaultValue) {
  1457 + $selected = ' selected="selected"';
  1458 + }
  1459 + $html .= '<option " ' . $selected . ' value="' . $feature['id_feature'] . '">' . htmlspecialchars(trim($feature['name'])) . '</option>';
  1460 + }
  1461 +
  1462 + $html .= '</select>';
  1463 + $html .= '</div>';
  1464 + $html .= '</div>';
  1465 + $this->formulario .= $html;
  1466 +
  1467 + return $this;
  1468 + }
  1469 +
  1470 + /**
  1471 + *
  1472 + * @param string $name
  1473 + * @param string $text
  1474 + * @param int $defaultValue
  1475 + * @param string $title
  1476 + * @return $this
  1477 + */
  1478 + public function createSelectFeaturesValues($idLang, $name, $text, $defaultValue, $value, $title = '') {
  1479 + $features = FeatureValueCore::getFeatureValuesWithLang($idLang, $value);
  1480 + $html .= '<div class="form-group">';
  1481 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1482 + $html .= '<span title="' . $title . '">';
  1483 + $html .= $text;
  1484 + $html .= '</span>';
  1485 + $html .= '</label>';
  1486 + $html .= '<div class="col-lg-3">';
  1487 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1488 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un valor de característica - ') . '</option>';
  1489 + $html .= $text;
  1490 + $html .= '' . $this->modulo->l(' - Seleccione un estado - ') . '';
  1491 + foreach ($features AS $feature) {
  1492 + $selected = '';
  1493 + if ($feature['id_feature'] == $defaultValue) {
  1494 + $selected = ' selected="selected"';
  1495 + }
  1496 + $html .= '' . htmlspecialchars(trim($feature['name'])) . '';
  1497 + }
  1498 +
  1499 + $html .= '</select>';
  1500 + $html .= '</div>';
  1501 + $html .= '</div>';
  1502 + $this->formulario .= $html;
  1503 +
  1504 + return $this;
  1505 + }
  1506 +
  1507 + /**
  1508 + * Genera un select a partir de un array asociativo.
  1509 + * @param string $name
  1510 + * @param string $text
  1511 + * @param array $datos
  1512 + * @param string $claveId
  1513 + * @param string $claveValores
  1514 + * @param mixed $selectedValue
  1515 + * @param string $title
  1516 + * @param array $extras
  1517 + * @return $this
  1518 + */
  1519 + public function createFormSelectFromArray($name, $text, $datos, $claveId, $claveValores, $selectedValue, $title = '', $extras = null) {
  1520 + $datosFormateados = array();
  1521 + foreach ($datos as $dato) {
  1522 + $datosFormateados[$dato[$claveId]] = $dato[$claveValores];
  1523 + }
  1524 +
  1525 + return $this->createFormSelect($name, $text, $datosFormateados, $selectedValue, $title, $extras);
  1526 + }
  1527 +
  1528 + /**
  1529 + * Crea un select.
  1530 + * @param string $name
  1531 + * @param array $text
  1532 + * @param string $datos valor => visible
  1533 + * @param string $defaultValue
  1534 + * @param string $title
  1535 + * @param array $extras
  1536 + * @return $this
  1537 + */
  1538 + function createFormSelectMultiple($name, $text, $datos, $defaultValues, $title = '', $extras = null) {
  1539 + $this->addJS('jquery.chosen.js');
  1540 + $this->addCSS('jquery.chosen.css');
  1541 + $html = '';
  1542 + $html .= '<div class="form-group">';
  1543 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1544 + $html .= '<span title="' . $title . '">';
  1545 + $html .= $text;
  1546 + $html .= '</span>';
  1547 + $html .= '</label>';
  1548 + $html .= '<div class="col-lg-3">';
  1549 + $html .= '<select name="' . $name . '[]" id="' . $name . '" multiple ';
  1550 + if ($extras) {
  1551 + foreach ($extras AS $indice => $extra) {
  1552 + $html .= $indice . '="' . $extra . '"';
  1553 + }
  1554 + }
  1555 + $html .= '>';
  1556 + foreach ($datos AS $valor => $texto) {
  1557 + $selected = '';
  1558 + if (is_array($defaultValues) && in_array($valor, $defaultValues)) {
  1559 + $selected = ' selected="selected" ';
  1560 + }
  1561 + $html .= '<option value="' . $valor . '" ' . $selected . '>' . $texto . '</option>';
  1562 + }
  1563 + $html .= '</select>';
  1564 + $html .= '</div>';
  1565 + $html .= '</div>';
  1566 + $html .= '<script>';
  1567 + $html .= '$("#'.$name.'").chosen({})';
  1568 + $html .= '</script>';
  1569 + $this->formulario .= $html;
  1570 +
  1571 + return $this;
  1572 + }
  1573 +
  1574 + /**
  1575 + * Genera un select a partir de un array asociativo.
  1576 + * @param string $name
  1577 + * @param string $text
  1578 + * @param array $datos
  1579 + * @param string $claveId
  1580 + * @param string $claveValores
  1581 + * @param mixed $selectedValues
  1582 + * @param string $title
  1583 + * @param array $extras
  1584 + * @return $this
  1585 + */
  1586 + public function createFormSelectMultipleFromArray($name, $text, $datos, $claveId, $claveValores, $selectedValues, $title = '', $extras = null) {
  1587 + $datosFormateados = array();
  1588 + foreach ($datos as $dato) {
  1589 + $datosFormateados[$dato[$claveId]] = $dato[$claveValores];
  1590 + }
  1591 +
  1592 + return $this->createFormSelectMultiple($name, $text, $datosFormateados, $selectedValues, $title, $extras);
  1593 + }
  1594 +
  1595 + /**
  1596 + * Crea una tabla para trabajar con un ObjectModel.
  1597 + * @param string $objectModelName
  1598 + * @param string[] $columnNames Las traducciones para los nombres de las columnas.
  1599 + * @param string[] $columnOrder El orden de las columnas.
  1600 + * @param string[] $hideColumns Columnas que no se desean en la tabla.
  1601 + * @return $this
  1602 + */
  1603 + public function createFormObjectModelTable($objectModelName, $columnNames = array(), $columnOrder = array(), $hideColumns = array()) {
  1604 + $campos = $objectModelName::$definition['fields'];
  1605 + //Eliminamos campos
  1606 + foreach($hideColumns as $hideColumn) {
  1607 + unset($campos[$hideColumn]);
  1608 + }
  1609 + //Ordenamos
  1610 + uksort($campos, function($a, $b) use ($columnOrder) {
  1611 + $posA = array_search($a, $columnOrder);
  1612 + if($posA === false) {
  1613 + $posA = PHP_INT_MAX;
  1614 + }
  1615 + $posB = array_search($b, $columnOrder);
  1616 + if($posB === false) {
  1617 + $posB = PHP_INT_MAX;
  1618 + }
  1619 + return $posA - $posB;
  1620 + });
  1621 + $idPrimario = $objectModelName::$definition['primary'];
  1622 + $ids = Db::getInstance()->executeS("SELECT $idPrimario FROM `"._DB_PREFIX_.$objectModelName::$definition['table'].'`');
  1623 +
  1624 + //Creamos la tabla
  1625 + $filaVacia = json_encode($this->generateImaxTableRow($objectModelName, 'newRow', $campos));
  1626 + $this->formulario .= "<script>var emptyRow_$objectModelName = $filaVacia;</script>";
  1627 + $this->formulario .= '<input type="hidden" name="imaxTableModels[]" value="'.$objectModelName.'"/>';
  1628 + $this->formulario .= '<table class="table imaxTable">';
  1629 + $this->formulario .= '<thead>';
  1630 + $this->formulario .= '<tr>';
  1631 + foreach($campos as $nombreCampo => $infoCampo) {
  1632 + $nombreVisible = (isset($columnNames[$nombreCampo]) ? $columnNames[$nombreCampo] : $nombreCampo);
  1633 + $this->formulario .= "<th>$nombreVisible</th>";
  1634 + }
  1635 + $this->formulario .= '<td><input type="button" name="imaxTableNewRow" value="+" data-object_model="'.$objectModelName.'"/></td>';
  1636 + $this->formulario .= '</tr>';
  1637 + $this->formulario .= '</thead>';
  1638 + $this->formulario .= '<tbody>';
  1639 + foreach($ids as $id) {
  1640 + $id = $id[$idPrimario];
  1641 + $objeto = new $objectModelName($id);
  1642 +
  1643 + $this->formulario .= $this->generateImaxTableRow($objectModelName, $id, $campos, $objeto);
  1644 + }
  1645 + $this->formulario .= '</tbody>';
  1646 + $this->formulario .= '</table>';
  1647 +
  1648 + return $this;
  1649 + }
  1650 +
  1651 + /**
  1652 + * Genera una fila de datos para la tabla de ObjectModel.
  1653 + * @param string $objectModelName
  1654 + * @param mixed $id
  1655 + * @param array $fields
  1656 + * @param ObjectModel $object
  1657 + * @return string
  1658 + */
  1659 + private function generateImaxTableRow($objectModelName, $id, $fields, $object = null) {
  1660 + $auxForm = new imaxForm($this->modulo, $this->_path, '', '', '', '', array(), true);
  1661 +
  1662 + $html = '<tr class="imaxTableRow">';
  1663 + foreach($fields as $nombreCampo => $infoCampo) {
  1664 + $html .= '<td>';
  1665 + $tipo = (!empty($infoCampo['type']) ? $infoCampo['type'] : '');
  1666 + switch($tipo) {
  1667 + case ObjectModel::TYPE_BOOL:
  1668 + $auxForm->createFormCheckboxGroup("imaxTableRow[$objectModelName][$id][$nombreCampo]", '', ($object ? $object->$nombreCampo : ''));
  1669 + break;
  1670 +
  1671 + default:
  1672 + $auxForm->createFormTextGroup("imaxTableRow[$objectModelName][$id][$nombreCampo]", ($object ? $object->$nombreCampo : ''), '', '', !empty($infoCampo['lang']));
  1673 + break;
  1674 + }
  1675 + $html .= $auxForm->renderForm(true);
  1676 + $html .= '</td>';
  1677 + }
  1678 + $html .= '<td><input type="button" name="imaxTableDeleteRow" value="X"/></td>';
  1679 + $html .= '</tr>';
  1680 +
  1681 + return $html;
  1682 + }
  1683 +
  1684 + /**
  1685 + * Graba automaticamente las tablas de ObjectModel.
  1686 + * @param array $post Debe contener imaxTableModels -> los ObjectModels cargados y imaxTableRow -> los datos correspondientes.
  1687 + * @return boolean
  1688 + */
  1689 + public static function saveFormObjectModelTables($post) {
  1690 + //Primero eliminamos los modelos que no vienen
  1691 + if(!empty($post['imaxTableModels'])) {
  1692 + foreach($post['imaxTableModels'] as $objectModelName) {
  1693 + if(empty($post['imaxTableRow'][$objectModelName])) {
  1694 + $idPrimario = $objectModelName::$definition['primary'];
  1695 + $ids = Db::getInstance()->executeS("SELECT $idPrimario FROM `"._DB_PREFIX_.$objectModelName::$definition['table'].'`');
  1696 + foreach($ids as $id) {
  1697 + $obj = new $objectModelName($id[$idPrimario]);
  1698 + $obj->delete();
  1699 + }
  1700 + }
  1701 + }
  1702 + }
  1703 +
  1704 + //Resto de operaciones
  1705 + if(!empty($post['imaxTableRow'])) {
  1706 + foreach($post['imaxTableRow'] as $objectModelName => $objectModelsData) {
  1707 + $campos = $objectModelName::$definition['fields'];
  1708 + $idsExistentes = array();
  1709 + //Nuevos y existentes
  1710 + foreach($objectModelsData as $id => $objectModelData) {
  1711 + $obj = new $objectModelName((int)$id);
  1712 + foreach($objectModelData as $field => $value) {
  1713 + $obj->$field = $value;
  1714 + }
  1715 + //Checks que no vienen
  1716 + foreach($campos as $field => $infoCampo) {
  1717 + if(!isset($objectModelData[$field]) && !empty($infoCampo['type']) && $infoCampo['type'] == ObjectModel::TYPE_BOOL) {
  1718 + $obj->$field = false;
  1719 + }
  1720 + }
  1721 + $obj->save();
  1722 +
  1723 + $idsExistentes[] = (int)$obj->id;
  1724 + }
  1725 +
  1726 + //Eliminaciones
  1727 + $idPrimario = $objectModelName::$definition['primary'];
  1728 + $idsExistentesString = implode(',', array_filter($idsExistentes));
  1729 + $where = ($idsExistentesString ? "WHERE $idPrimario NOT IN ($idsExistentesString)" : '');
  1730 + $ids = Db::getInstance()->executeS("SELECT $idPrimario FROM `"._DB_PREFIX_.$objectModelName::$definition['table']."` $where");
  1731 + foreach($ids as $id) {
  1732 + $obj = new $objectModelName($id[$idPrimario]);
  1733 + $obj->delete();
  1734 + }
  1735 + }
  1736 + }
  1737 +
  1738 + return true;
  1739 + }
  1740 +}
... ...
imaxAcordeon.php 0 → 100644
  1 +++ a/imaxAcordeon.php
  1 +<?php
  2 +/**
  3 + * @version 1.3
  4 + */
  5 +class imaxAcordeon {
  6 +
  7 + function __construct($modulePath) {
  8 + $this->context = Context::getContext();
  9 + $this->_path = $modulePath;
  10 + $this->addCSS('acordeon.css');
  11 + $this->addJS('acordeon.js');
  12 + }
  13 +
  14 + /**
  15 + * CREA UN ACORDEN
  16 + * @param string $titulo Titulo del acordeon
  17 + * @param string $contenido Html con todo el contenido
  18 + * @param boolean $interno True si va a estar dentro de otro acordeon
  19 + */
  20 + function renderAcordeon($titulo, $contenido, $interno = false , $id='', $classImax = '') {
  21 + $class = 'acordeon';
  22 + if ($interno != false) {
  23 + $class = 'acordeonInterno';
  24 + }
  25 + $class = $class . " ". $classImax;
  26 + $html = '';
  27 + $html = '<dl id="'.$id.'" class="' . $class . '">';
  28 + $html .= '<dt>' . $titulo . '</dt>';
  29 + $html .= '<dd>';
  30 + $html .= $contenido;
  31 + $html .= '</dd>';
  32 + $html .= '</dl>';
  33 + return $html;
  34 + }
  35 +
  36 + private function addCSS($css) {
  37 + $tab = Tools::getValue('tab', 0);
  38 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  39 + if ($this->context->controller instanceof stdClass) {
  40 + $this->context->controller = new AdminModulesController();
  41 + }
  42 + $this->context->controller->addCss($this->_path . 'acordeon/' . $css, 'all');
  43 + }
  44 + return;
  45 + }
  46 +
  47 + private function addJS($js) {
  48 + $tab = Tools::getValue('tab', 0);
  49 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  50 + if ($this->context->controller instanceof stdClass) {
  51 + $this->context->controller = new AdminModulesController();
  52 + }
  53 + $this->context->controller->addJs($this->_path . 'acordeon/' . $js);
  54 + }
  55 + return;
  56 + }
  57 +
  58 +}
... ...
imaxmultiean.php 0 → 100644
  1 +++ a/imaxmultiean.php
  1 +<?php
  2 +
  3 +require_once dirname(__FILE__).'/ComunesImaxMultiEAN.php';
  4 +require_once dirname(__FILE__).'/FuncionesImaxMultiEAN.php';
  5 +
  6 +class ImaxMultiEAN extends Module {
  7 +
  8 + use ComunesImaxMultiEAN;
  9 +
  10 + var $versionPS;
  11 + var $idShop;
  12 + var $idLang;
  13 + var $idTab;
  14 + private $_html = '';
  15 +
  16 + private static $funciones;
  17 +
  18 + const prefijo = 'imaxmultiean_';
  19 +
  20 + public function __construct() {
  21 + $this->name = 'imaxmultiean';
  22 + $this->tab = 'administration';
  23 + $this->version = '1.1';
  24 + $this->author = 'Informax';
  25 + $this->need_instance = 0;
  26 + $this->idManual = '';
  27 + $this->forceCheck = 0;
  28 + $this->sufijo = self::prefijo;
  29 + $this->prefijo = self::prefijo;
  30 + parent::__construct();
  31 + $nombreModulo = Configuration::getGlobalValue($this->sufijo . 'NOMBRE_MODULO');
  32 + $descripcionModulo = Configuration::getGlobalValue($this->sufijo . 'DESCRIPCION_MODULO');
  33 + if ($nombreModulo) {
  34 + $this->displayName = $nombreModulo;
  35 + $this->description = $descripcionModulo;
  36 + }
  37 + else {
  38 + $this->displayName = $this->l('Gestor de EANs Múltiples');
  39 + $this->description = $this->l('Permite añadir y gestionar múltiples códigos EAN para cada producto y combinación.');
  40 + }
  41 +
  42 + if (version_compare(_PS_VERSION_, '1.7.0.0 ', '>=')) {
  43 + $this->versionPS = 17;
  44 + $context = Context::getContext();
  45 + $this->idShop = $context->shop->id;
  46 + $this->idLang = $context->language->id;
  47 + }
  48 + elseif (version_compare(_PS_VERSION_, '1.6.0.0 ', '>=')) {
  49 + $this->versionPS = 16;
  50 + $context = Context::getContext();
  51 + $this->idShop = $context->shop->id;
  52 + $this->idLang = $context->language->id;
  53 + }
  54 + elseif (version_compare(_PS_VERSION_, '1.5.0.0 ', '>=')) {
  55 + $this->versionPS = 15;
  56 + $context = Context::getContext();
  57 + $this->idShop = $context->shop->id;
  58 + $this->idLang = $context->language->id;
  59 + }
  60 + else {
  61 + $this->_html .= $this->l("La version minima de funcionamiento para nuestros modulos es la 1.5");
  62 + }
  63 + }
  64 +
  65 + public function install() {
  66 +
  67 + include(dirname(__FILE__).'/configuration.php');
  68 + foreach ($configuracion AS $indice => $valor) {
  69 + if (!Configuration::updateGlobalValue($indice, $valor)) {
  70 + return false;
  71 + }
  72 + }
  73 +
  74 + if (!parent::install())
  75 + return false;
  76 +
  77 + foreach ($hooks as $hook) {
  78 + if (!$this->registerHook($hook)) {
  79 + $this->_errors[] = $this->l('Ha fallado la instalacion del hook:').' '.$hook;
  80 + return false;
  81 + }
  82 + }
  83 +
  84 + include(dirname(__FILE__).'/sql-install.php');
  85 + foreach ($sql as $s) {
  86 + if (!Db::getInstance()->execute($s)) {
  87 + $this->_errors[] = $this->l("Error al ejecutar").$s;
  88 + return false;
  89 + }
  90 + }
  91 +
  92 + if (!$this->installTab()) {
  93 + $this->_errors[] = $this->l('Error al instalar el tab');
  94 + return false;
  95 + }
  96 +
  97 + return true;
  98 + }
  99 +
  100 + public function uninstall() {
  101 + if (!parent::uninstall()) {
  102 + return false;
  103 + }
  104 +
  105 + include(dirname(__FILE__).'/sql-unninstall.php');
  106 + foreach ($sql as $s) {
  107 + if (!Db::getInstance()->execute($s)) {
  108 + $this->_errors[] = $this->l("Error al ejecutar").$s;
  109 + return false;
  110 + }
  111 + }
  112 +
  113 + if (!$this->uninstallTab()) {
  114 + $this->_errors[] = $this->l('Error al eliminar el tab');
  115 + return false;
  116 + }
  117 +
  118 + include(dirname(__FILE__).'/configuration.php');
  119 + foreach ($configuracion AS $indice => $valor) {
  120 + if (Configuration::getGlobalValue($indice) !== FALSE) {
  121 + if (!Configuration::deleteByName($indice)) {
  122 + return false;
  123 + }
  124 + }
  125 + }
  126 +
  127 + return true;
  128 + }
  129 +
  130 + public function getContent() {
  131 + $this->getTxtFiles();
  132 + $this->addCSS('css.css');
  133 + $this->addJS('SucesionTeclas.js');
  134 + $this->addJS('functions.js');
  135 + $this->addCSS('publi.css');
  136 + $this->_html .= $this->createHelpHeader();
  137 + if (!empty($_POST)) {
  138 + $this->_html .= $this->_postProcess();
  139 + }
  140 +
  141 + $this->_displayForm();
  142 + $this->_html .= $this->getModuleFooter();
  143 + return $this->_html;
  144 + }
  145 +
  146 + private function _postProcess() {
  147 + $accion = Tools::getValue("accion");
  148 + $this->idTab = Tools::getValue("idTab");
  149 + $html = "";
  150 + switch ($accion) {
  151 + case 'gestionLicencia':
  152 + $this->forceCheck = 1;
  153 + if (Configuration::updateGlobalValue(self::prefijo . 'LICENCIA', trim(Tools::getValue('licencia')))) {
  154 + $html .= $this->displayConfirmation($this->l('Licencia guardada correctamente.'));
  155 + }
  156 + else {
  157 + $html .= $this->displayError($this->l('Ha ocurrido un error al guardar la licencia.'));
  158 + }
  159 + break;
  160 + case 'gestionPubli':
  161 + Configuration::updateGlobalValue($this->sufijo . 'TXT_FILE', '');
  162 + $urlPubli = trim(Tools::getValue('urlPubli'));
  163 + $tipoPubli = trim(Tools::getValue('namePubli'));
  164 + $nombre = trim(Tools::getValue('nameDeveloper'));
  165 + $urlEmpresa = trim(Tools::getValue('urlEmpresa'));
  166 + $urlManual = trim(Tools::getValue('urlManual'));
  167 + $urlSoporte = trim(Tools::getValue('urlSoporte'));
  168 + $descripcionModulo = trim(Tools::getValue('descripcionModulo'));
  169 + $nombreModulo = trim(Tools::getValue('nombreModulo'));
  170 + if (Configuration::updateGlobalValue($this->sufijo . 'URL_TXT', $urlPubli) &&
  171 + Configuration::updateGlobalValue($this->sufijo . 'TIPO', $tipoPubli) &&
  172 + Configuration::updateGlobalValue($this->sufijo . 'URL_DEVELOPER', $urlEmpresa) &&
  173 + Configuration::updateGlobalValue($this->sufijo . 'URL_TICKETS', $urlSoporte) &&
  174 + Configuration::updateGlobalValue($this->sufijo . 'URL_MANUAL', $urlManual) &&
  175 + Configuration::updateGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER', $nombre) &&
  176 + Configuration::updateGlobalValue($this->sufijo . 'DESCRIPCION_MODULO', $descripcionModulo) &&
  177 + Configuration::updateGlobalValue($this->sufijo . 'NOMBRE_MODULO', $nombreModulo)) {
  178 + Configuration::updateGlobalValue($this->sufijo . 'DESCARGA_ARCHIVO', 100001);
  179 + $html .= $this->displayConfirmation('Datos guardados correctamente.');
  180 + $this->installTabNewData();
  181 + } else {
  182 + $html .= $this->displayError('Ha ocurrido un error al guardar los datos de desarrollador.');
  183 + }
  184 + $this->forceCheck = 1;
  185 + default:
  186 + break;
  187 + }
  188 +
  189 + return $html;
  190 + }
  191 +
  192 + public function _displayForm() {
  193 + return $this->displayFormTrait(array('_configuracion' => $this->l('Configuracion'), '_mostrarLicencia' => $this->l('Licencia')), '');
  194 + }
  195 +
  196 + private function _mostrarLicencia() {
  197 + return $this->mostrarLicenciaTrait(2);
  198 + }
  199 +
  200 + private function _configuracion() {
  201 + include_once(dirname(__FILE__).'/functionsForm.php');
  202 + include_once(dirname(__FILE__).'/imaxAcordeon.php');
  203 + $cantidad = Configuration::getGlobalValue(self::prefijo.'PROCESAR');
  204 + $token = Configuration::getGlobalValue(self::prefijo.'TOKEN');
  205 + $shopDomain = Configuration::getGlobalValue('PS_SHOP_DOMAIN');
  206 + $modoCalculoCombinacion = Configuration::getGlobalValue(self::prefijo.'PRECIO_COMB') == 1 ? true : false;
  207 + $elementos_proceso = @unserialize(Configuration::getGlobalValue(self::prefijo.'ELEMENTOS_ORDEN'));
  208 + $orden_proceso = @unserialize(Configuration::getGlobalValue(self::prefijo.'ORDEN_PROCESO'));
  209 + $beneficio_minimo = Configuration::getGlobalValue(self::prefijo.'BENEFICIO_MIN');
  210 +
  211 + $acordeon = new imaxAcordeon($this->_path);
  212 +
  213 + $form = new imaxForm($this, $this->_path);
  214 + $form->createHidden("accion", "configuarion");
  215 + $form->createHidden("idTab", "1");
  216 +
  217 + $text = '<b>'.$this->l('ATENCION:').'</b> '.$this->l('Sin parámetros para configurar');
  218 + $form->createFormInfomationText($text, 'confirm');
  219 + $form->createSubmitButton('opcionesConfiguracion', $this->l('Guardar'));
  220 + $html = $acordeon->renderAcordeon($this->l('Configuracion'), $form->renderForm());
  221 +
  222 + return $html;
  223 + }
  224 +
  225 + /**
  226 + * Devuelve las funciones especificas del modulo.
  227 + * @return FuncionesImaxMultiEAN
  228 + */
  229 + public function getFunciones() {
  230 + if(!self::$funciones) {
  231 + self::$funciones = new FuncionesImaxMultiEAN($this);
  232 + }
  233 +
  234 + return self::$funciones;
  235 + }
  236 +
  237 + public function hookDisplayAdminProductsExtra($params) {
  238 + if (isset($params['id_product'])) {
  239 + $id_product = $params['id_product'];
  240 + } else {
  241 + $id_product = Tools::getValue('id_product');
  242 + }
  243 +
  244 + $product = new Product($id_product);
  245 +
  246 + // Obtener el nombre del producto
  247 + $product_name = $product->name[$this->context->language->id];
  248 +
  249 + // Si el producto no tiene combinaciones, obtenemos los EANs directamente para el producto.
  250 + $product_eans = $this->getFunciones()->getEANs($id_product);
  251 +
  252 + $combinations = $product->getAttributeCombinations($this->context->language->id);
  253 + $combinations_details = [];
  254 +
  255 + // Agrupar las combinaciones por id_product_attribute
  256 + $groupedCombinations = [];
  257 +
  258 + foreach ($combinations as $combination) {
  259 + $key = $combination['id_product_attribute'];
  260 +
  261 + if (!isset($groupedCombinations[$key])) {
  262 + $groupedCombinations[$key] = [
  263 + 'id_product_attribute' => $key,
  264 + 'attributes' => []
  265 + ];
  266 + }
  267 +
  268 + $groupedCombinations[$key]['attributes'][$combination['group_name']] = $combination['attribute_name'];
  269 + }
  270 +
  271 + // Procesar las combinaciones agrupadas
  272 + foreach ($groupedCombinations as $combination) {
  273 + $attribute_info = implode(' - ', $combination['attributes']);
  274 +
  275 + $combinations_details[$combination['id_product_attribute']] = [
  276 + 'id' => $combination['id_product_attribute'],
  277 + 'attribute_name' => $attribute_info,
  278 + 'eans' => $this->getFunciones()->getEANs($id_product, $combination['id_product_attribute'])
  279 + ];
  280 + }
  281 +
  282 + // Pasar la información a la plantilla .tpl para mostrarla
  283 + $this->context->smarty->assign([
  284 + 'product_id' => $id_product,
  285 + 'product_name' => $product_name,
  286 + 'product_eans' => $product_eans,
  287 + 'combinations_details' => $combinations_details,
  288 + 'urlBase' => $this->getFrontUrl() . '/modules/' . $this->name,
  289 + ]);
  290 +
  291 + return $this->display($this->name, 'templates/hook/admin_products_extra.tpl');
  292 + }
  293 +
  294 + private function getFrontUrl()
  295 + {
  296 + $url = false;
  297 + $idShopDefault = Configuration::getGlobalValue('PS_SHOP_DEFAULT');
  298 + $tienda = new Shop($idShopDefault);
  299 + if (Validate::isLoadedObject($tienda)) {
  300 + if (isset($tienda->domain)) {
  301 + $url = $tienda->domain;
  302 + }
  303 + if (!empty($tienda->physical_uri)) {
  304 + $url .= $tienda->physical_uri;
  305 + }
  306 +
  307 + if (!empty($tienda->virtual_uri)) {
  308 + $url .= $tienda->virtual_uri;
  309 + }
  310 + }
  311 + return $url;
  312 + }
  313 +
  314 +}
... ...
imaxmultiean_ajax.php 0 → 100644
  1 +++ a/imaxmultiean_ajax.php
  1 +<?php
  2 +
  3 +include(__DIR__.'/../../config/config.inc.php');
  4 +include(__DIR__.'/imaxmultiean.php');
  5 +
  6 +$imax = new imaxmultiean();
  7 +$accion = Tools::getValue('accion');
  8 +$id_product = Tools::getValue('productId');
  9 +$ean = Tools::getValue('ean');
  10 +$combinationId = Tools::getValue('combinationId', 0); // Valor por defecto 0
  11 +$resultado = ['ok' => false, 'datos' => []];
  12 +
  13 +
  14 +switch ($accion) {
  15 + case 'storeEAN':
  16 + $resultado['ok'] = $imax->getFunciones()->storeEAN($id_product, $ean, $combinationId);
  17 + break;
  18 +
  19 + case 'deleteEAN':
  20 + $resultado['ok'] = $imax->getFunciones()->deleteEAN($id_product, $ean, $combinationId);
  21 + break;
  22 +
  23 +}
  24 +
  25 +echo Tools::getValue('callback').'('.json_encode($resultado).')';
... ...
img/abrir-ticket.png 0 → 100644

585 Bytes

img/borrar.png 0 → 100644

4.54 KB

img/close.png 0 → 100644

841 Bytes

img/help.jpg 0 → 100644

12.1 KB

img/help.png 0 → 100644

801 Bytes

img/help__.png 0 → 100644

6.84 KB

img/informax.png 0 → 100644

1004 Bytes

img/ir-a-manuales.png 0 → 100644

599 Bytes

img/manual.jpg 0 → 100644

7.99 KB

img/manual.png 0 → 100644

495 Bytes

img/manual__.png 0 → 100644

4.6 KB

img/open.png 0 → 100644

881 Bytes

js/SucesionTeclas.js 0 → 100644
  1 +++ a/js/SucesionTeclas.js
  1 +/**
  2 + * Comprueba que se pulse una sucesión de teclas, manteniendo ctrl + alt. Normalmente hay que hacer click sobre la web para que funcione.
  3 + * @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).
  4 + * @param {function} callback Se llama cuando la sucesión es correcta.
  5 + * @returns {SucesionTeclas}
  6 + */
  7 +function SucesionTeclas(sucesion, callback) {
  8 + this.sucesion = sucesion;
  9 + this.callback = callback;
  10 +
  11 + var posActual = 0, esto = this;
  12 +
  13 + $(document).keyup(function(ev) {
  14 + if(ev.altKey && ev.ctrlKey && esto.sucesion.substr(posActual, 1) == ev.key) {
  15 + //Correcto
  16 + if(posActual == esto.sucesion.length - 1) {
  17 + //Finalizado
  18 + callback();
  19 + }
  20 + else {
  21 + posActual++;
  22 + }
  23 + }
  24 + else {
  25 + //Error
  26 + posActual = 0;
  27 + }
  28 + });
  29 +}
... ...
js/functions.js 0 → 100644
  1 +++ a/js/functions.js
  1 +$(function () {
  2 + new SucesionTeclas('k', function () {
  3 + if ($('#acordeonPubli').hasClass('imaxHidden')) {
  4 + $('#acordeonPubli').removeClass('imaxHidden');
  5 + console.log("Se muestra la configuracion de publicidad del modulo.");
  6 + }
  7 + else {
  8 + $('#acordeonPubli').addClass('imaxHidden');
  9 + console.log("Se oculta la configuracion de publicidad del modulo.");
  10 + }
  11 + });
  12 +});
... ...
logo.gif 0 → 100644

1.72 KB

logo.jpg 0 → 100644

5.21 KB

logo.png 0 → 100644

3.49 KB

override/classes/Search.php 0 → 100644
  1 +++ a/override/classes/Search.php
  1 +<?php
  2 +
  3 +class Search extends SearchCore
  4 +{
  5 +
  6 + public static function indexation($full = false, $id_product = false)
  7 + {
  8 + $result = parent::indexation($full, $id_product);
  9 +
  10 + if ($result) {
  11 +
  12 + $shops = Shop::getShops(true, null, true);
  13 + $languages = Language::getLanguages(true);
  14 + $sql = 'SELECT m.`id_product`, m.`ean13` FROM ' . _DB_PREFIX_ . 'imaxmultiean_multiean m';
  15 +
  16 + // Si se proporciona $id_product, solo indexamos ese producto
  17 + if ($id_product !== false) {
  18 + $sql .= ' WHERE m.`id_product` = ' . (int)$id_product;
  19 + } elseif (!$full) {
  20 + // Si no es una indexación completa y no se proporciona $id_product, indexamos solo los que faltan
  21 + $sql .= ' LEFT JOIN ' . _DB_PREFIX_ . 'search_word sw ON m.ean13 = sw.word WHERE sw.id_word IS NULL';
  22 + }
  23 +
  24 + $productos = Db::getInstance()->executeS($sql);
  25 +
  26 + foreach ($productos as $producto) {
  27 +
  28 + $word = pSQL($producto['ean13']);
  29 +
  30 + foreach ($languages as $lang) {
  31 + foreach ($shops as $shopId) {
  32 +
  33 + // Insertar o actualizar la palabra en search_word
  34 + $sqlInsertWord = "
  35 + INSERT IGNORE INTO " . _DB_PREFIX_ . "search_word (id_word, id_lang, id_shop, word)
  36 + VALUES (NULL, '" . (int)$lang['id_lang'] . "', '" . (int)$shopId . "', '$word')
  37 + ";
  38 + Db::getInstance()->execute($sqlInsertWord);
  39 +
  40 + $id_word = Db::getInstance()->Insert_ID();
  41 +
  42 + // Si la palabra fue insertada con éxito, proceder con la inserción o actualización en search_index
  43 + if ($id_word) {
  44 + $weight = Configuration::get('PS_SEARCH_WEIGHT_REF');
  45 + $idProduct = (int)$producto['id_product'];
  46 +
  47 + $sqlInsertIndex = "
  48 + INSERT INTO " . _DB_PREFIX_ . "search_index (id_product, id_word, weight)
  49 + VALUES ('$idProduct', '$id_word', '$weight')
  50 + ON DUPLICATE KEY UPDATE weight = '$weight'
  51 + ";
  52 + Db::getInstance()->execute($sqlInsertIndex);
  53 + }
  54 + }
  55 + }
  56 + }
  57 + }
  58 +
  59 + return $result;
  60 + }
  61 +}
... ...
sql-install.php 0 → 100644
  1 +++ a/sql-install.php
  1 +<?php
  2 +$sql = array();
  3 +
  4 +$sql[] = "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_.self::prefijo."multiean` (
  5 + `id` INT(10) NOT NULL AUTO_INCREMENT,
  6 + `id_product` INT(10) NOT NULL,
  7 + `id_product_attribute` INT(10) NOT NULL,
  8 + `ean13` VARCHAR(255) NOT NULL,
  9 + PRIMARY KEY (`id`)
  10 + )
  11 + COLLATE='utf8_general_ci'
  12 + ENGINE=MyIsam;";
... ...
sql-unninstall.php 0 → 100644
  1 +++ a/sql-unninstall.php
  1 +<?php
  2 +$sql = array();
  3 +$sql[] = 'DROP TABLE IF EXISTS `'._DB_PREFIX_.self::prefijo.'multiean`';
... ...
templates/hook/admin_products_extra.tpl 0 → 100644
  1 +++ a/templates/hook/admin_products_extra.tpl
  1 +<style>
  2 + #multiEAN {
  3 + font-family: Arial, sans-serif;
  4 + width: 50%;
  5 + }
  6 +
  7 + #multiEAN h3, #multiEAN h4 {
  8 + color: #333;
  9 + border-bottom: 2px solid #ddd;
  10 + padding-bottom: 10px;
  11 + margin-bottom: 15px;
  12 + }
  13 +
  14 + #multiEAN p {
  15 + color: #555;
  16 + margin-bottom: 10px;
  17 + }
  18 +
  19 + #multiEAN ul {
  20 + list-style-type: none;
  21 + padding: 0;
  22 + }
  23 +
  24 + #multiEAN li {
  25 + margin: 10px 0;
  26 + background: #f9f9f9;
  27 + padding: 10px;
  28 + border-radius: 5px;
  29 + position: relative;
  30 + }
  31 +
  32 + #multiEAN .borrarEAN {
  33 + color: #f44336;
  34 + cursor: pointer;
  35 + position: absolute;
  36 + top: 50%;
  37 + right: 10px;
  38 + transform: translateY(-50%);
  39 + text-decoration: none;
  40 + font-size: 0.9em;
  41 + }
  42 +
  43 + #multiEAN button {
  44 + background-color: #4CAF50;
  45 + color: white;
  46 + padding: 10px 20px;
  47 + border: none;
  48 + border-radius: 5px;
  49 + cursor: pointer;
  50 + transition: background-color 0.2s;
  51 + display: block;
  52 + margin: 10px 0;
  53 + }
  54 +
  55 + #multiEAN button:hover {
  56 + background-color: #45a049;
  57 + }
  58 +
  59 + #multiEAN input[type="text"] {
  60 + padding: 10px;
  61 + border: 1px solid #ddd;
  62 + border-radius: 5px;
  63 + width: 100%;
  64 + box-sizing: border-box;
  65 + margin-bottom: 10px;
  66 + }
  67 +
  68 +</style>
  69 +
  70 +<div id="multiEAN">
  71 + <h3>Producto: {$product_name}</h3>
  72 + <p>EANs:</p>
  73 + <ul id="product_eans_list">
  74 + {foreach $product_eans as $ean}
  75 + <li>{$ean.ean13} <span class="borrarEAN" data-ean13='{$ean.ean13}'>[Eliminar]</span></li>
  76 + {/foreach}
  77 + </ul>
  78 + <input type="text" id="new_product_ean" placeholder="Introduce el nuevo EAN" />
  79 + <button class="guardarEAN">Agregar EAN al producto</button>
  80 +
  81 +{foreach $combinations_details as $combination}
  82 + <h4>Combinación: {$combination.attribute_name}</h4>
  83 + <p>EANs:</p>
  84 + <ul id="combination_{$combination.id}_eans_list">
  85 + {foreach $combination.eans as $ean}
  86 + <li>{$ean.ean13} <span class="borrarEAN" data-ean13='{$ean.ean13}' data-id_combinacion='{$combination.id}'>[Eliminar]</span></li>
  87 + {/foreach}
  88 + </ul>
  89 + <input type="text" id="new_combination_{$combination.id}_ean" placeholder="Introduce el nuevo EAN para esta combinación" />
  90 + <button class="guardarEAN" data-id_combinacion='{$combination.id}'>Agregar EAN a la combinación</button>
  91 +{/foreach}
  92 +</div>
  93 +
  94 +<script>
  95 +$(function(){
  96 + $('.guardarEAN').click(function(){
  97 + let data = {
  98 + accion: 'storeEAN',
  99 + productId: "{$product_id}",
  100 + };
  101 +
  102 + let combinationId = $(this).data('id_combinacion');
  103 + let eanInput = combinationId ? $('#new_combination_' + combinationId + '_ean') : $('#new_product_ean');
  104 + data.ean = eanInput.val();
  105 +
  106 + if (combinationId) {
  107 + data.combinationId = combinationId;
  108 + }
  109 +
  110 + $.getJSON('//{$urlBase}/imaxmultiean_ajax.php?callback=?', data, function (respuesta) {
  111 + if (respuesta.ok) {
  112 + showSuccessMessage('EAN almacenado con éxito.');
  113 +
  114 + let eanList = combinationId ? $('#combination_' + combinationId + '_eans_list') : $('#product_eans_list');
  115 + let eanItem = $('<li>').text(data.ean).append($('<span>').addClass('borrarEAN').attr('data-ean13', data.ean).text('[Eliminar]'));
  116 + eanList.append(eanItem);
  117 +
  118 + eanInput.val(''); // Limpiar el campo de entrada
  119 +
  120 + } else {
  121 + showErrorMessage('Hubo un error al almacenar el EAN.');
  122 + }
  123 + });
  124 + });
  125 +
  126 + // En este bloque, usaremos la delegación de eventos para asegurarnos de que también funcione para los elementos EAN recién agregados.
  127 + $(document).on('click', '.borrarEAN', function(){
  128 + let data = {
  129 + accion: 'deleteEAN',
  130 + productId: "{$product_id}",
  131 + ean: $(this).data('ean13'),
  132 + };
  133 +
  134 + let combinationId = $(this).data('id_combinacion');
  135 + if (combinationId) {
  136 + data.combinationId = combinationId;
  137 + }
  138 +
  139 + let eanItem = $(this).closest('li'); // Referencia al elemento <li> que contiene el EAN
  140 +
  141 + $.getJSON('//{$urlBase}/imaxmultiean_ajax.php?callback=?', data, function (respuesta) {
  142 + if (respuesta.ok) {
  143 + showSuccessMessage('EAN eliminado con éxito.');
  144 +
  145 + eanItem.remove(); // Remover el elemento <li> del DOM
  146 +
  147 + } else {
  148 + showErrorMessage('Hubo un error al eliminar el EAN.');
  149 + }
  150 + });
  151 + });
  152 +});
  153 +
  154 +
  155 +</script>
0 156 \ No newline at end of file
... ...