Commit 9eb228eae3ea017b104c6b147db5747e26a35c9d

Authored by Adrian
0 parents

Versión inicial.

Showing 67 changed files with 8888 additions and 0 deletions
AdminInformax.gif 0 → 100644

574 Bytes

ComunesImaxStockAvelonEmptyingModule.php 0 → 100644
  1 +++ a/ComunesImaxStockAvelonEmptyingModule.php
  1 +<?php
  2 +
  3 +/**
  4 + * Contiene las funciones comunes de los modulos.
  5 + * @version 1.5
  6 + */
  7 +Trait ComunesImaxStockAvelonEmptyingModule {
  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 + return true;
  295 + // $data = array();
  296 + // $f = Configuration::getGlobalValue(self::prefijo.'F');
  297 + // $check = Configuration::getGlobalValue(self::prefijo.'F_CHECK');
  298 + // $url = Configuration::getGlobalValue(self::prefijo.'F_SERVER');
  299 + // $data['server'] = $this->getDomain();
  300 + // $data['modulo'] = $this->name;
  301 + // $data['action'] = 'checkLicencia';
  302 + // $data['licencia'] = Configuration::getGlobalValue(self::prefijo.'LICENCIA');
  303 + // if ($check >= 100 || $force == 1) {
  304 + // $ch = curl_init($url);
  305 + // curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  306 + // curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  307 + // curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  308 + // $datos = curl_exec($ch);
  309 + // $datos = json_decode($datos);
  310 + // $info = curl_getinfo($ch);
  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'] = 'AdminImaxStockAvelonEmptyingModule';
  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 +}
... ...
FuncionesImaxStockAvelonEmptyingModule.php 0 → 100644
  1 +++ a/FuncionesImaxStockAvelonEmptyingModule.php
  1 +<?php
  2 +
  3 +class FuncionesImaxStockAvelonEmptyingModule {
  4 + private $modulo;
  5 +
  6 + public function __construct($modulo) {
  7 + $this->modulo = $modulo;
  8 + }
  9 +
  10 + public function vaciarTablaStockAvelon(){
  11 + $module = Module::getInstanceByName('imaxavelonimportcloud');
  12 + if ($module->id) {
  13 + if(Db::getInstance()->execute('TRUNCATE TABLE `' . _DB_PREFIX_ . 'imaxStockAvelon`')){
  14 + return 'La tabla ' . _DB_PREFIX_ . 'imaxStockAvelon se vació correctamente';
  15 + }else{
  16 + return 'Ocurrió un error al vaciar la tabla ' . _DB_PREFIX_ . 'imaxStockAvelon.';
  17 + }
  18 + } else {
  19 + return 'El módulo imaxavelonimportcloud no existe o no está instalado.';
  20 + }
  21 + }
  22 +}
... ...
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>imaxstockavelonemptyingmodule</name>
  4 + <displayName><![CDATA[Stock Avelon Emptying]]></displayName>
  5 + <version><![CDATA[1.1]]></version>
  6 + <description><![CDATA[Este módulo permite vaciar la tabla StockAvelon de la base de datos de PrestaShop, eliminando todos los registros dentro de la tabla. Utilícelo con precaución y asegúrese de tener un backup de sus datos antes de proceder.]]></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 +
  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'] = 'Stock Avelon Emptying';
  47 +$moduleTabs[0]['clase'] = 'AdminImaxStockAvelonEmptyingModule';
  48 +$moduleTabs[0]['padre'] = 'AdminInformax';
  49 +$moduleTabs[0]['imagen'] = 'informax.gif';
... ...
controllers/admin/Adminimaxstockavelonemptyingmodule.php 0 → 100644
  1 +++ a/controllers/admin/Adminimaxstockavelonemptyingmodule.php
  1 +<?php
  2 +class AdminimaxstockavelonemptyingmoduleController 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=imaxstockavelonemptyingmodule&tab_module=administration&module_name=imaxstockavelonemptyingmodule&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=imaxstockavelonemptyingmodule&tab_module=administration&module_name=imaxstockavelonemptyingmodule&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 +}
... ...
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 +}
... ...
imaxstockavelonemptyingmodule.php 0 → 100644
  1 +++ a/imaxstockavelonemptyingmodule.php
  1 +<?php
  2 +
  3 +require_once dirname(__FILE__).'/ComunesImaxStockAvelonEmptyingModule.php';
  4 +require_once dirname(__FILE__).'/FuncionesImaxStockAvelonEmptyingModule.php';
  5 +
  6 +class ImaxStockAvelonEmptyingModule extends Module {
  7 +
  8 + use ComunesImaxStockAvelonEmptyingModule;
  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 = '';
  19 +
  20 + public function __construct() {
  21 + $this->name = 'imaxstockavelonemptyingmodule';
  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('Stock Avelon Emptying');
  39 + $this->description = $this->l('Este módulo permite vaciar la tabla StockAvelon de la base de datos de PrestaShop, eliminando todos los registros dentro de la tabla. Utilícelo con precaución y asegúrese de tener un backup de sus datos antes de proceder.');
  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 + $directorioAdmin = getcwd();
  67 + if (!@copy(dirname(__FILE__).'/imaxstockavelonemptyingmodule_cron.php', $directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php')) {
  68 + $this->_errors[] = $this->l('Ha fallado al copiar el fichero de cron');
  69 + return false;
  70 + }
  71 + if (is_file(dirname(__FILE__).'/imaxstockavelonemptyingmodule_cron.php.imax') && !@copy(dirname(__FILE__).'/imaxstockavelonemptyingmodule_cron.php.imax', $directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php.imax')) {
  72 + $this->_errors[] = $this->l('Ha fallado al copiar el fichero de cron');
  73 + return false;
  74 + }
  75 +
  76 + include(dirname(__FILE__).'/configuration.php');
  77 + foreach ($configuracion AS $indice => $valor) {
  78 + if (!Configuration::updateGlobalValue($indice, $valor)) {
  79 + return false;
  80 + }
  81 + }
  82 +
  83 + if (!parent::install())
  84 + return false;
  85 +
  86 + foreach ($hooks as $hook) {
  87 + if (!$this->registerHook($hook)) {
  88 + $this->_errors[] = $this->l('Ha fallado la instalacion del hook:').' '.$hook;
  89 + return false;
  90 + }
  91 + }
  92 +
  93 + include(dirname(__FILE__).'/sql-install.php');
  94 + foreach ($sql as $s) {
  95 + if (!Db::getInstance()->execute($s)) {
  96 + $this->_errors[] = $this->l("Error al ejecutar").$s;
  97 + return false;
  98 + }
  99 + }
  100 +
  101 + if (!$this->installTab()) {
  102 + $this->_errors[] = $this->l('Error al instalar el tab');
  103 + return false;
  104 + }
  105 +
  106 + return true;
  107 + }
  108 +
  109 + public function uninstall() {
  110 + if (!parent::uninstall()) {
  111 + return false;
  112 + }
  113 +
  114 + include(dirname(__FILE__).'/sql-unninstall.php');
  115 + foreach ($sql as $s) {
  116 + if (!Db::getInstance()->execute($s)) {
  117 + $this->_errors[] = $this->l("Error al ejecutar").$s;
  118 + return false;
  119 + }
  120 + }
  121 + $directorioAdmin = getcwd();
  122 + if (!unlink($directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php')) {
  123 + $this->_errors[] = $this->l('Error al borrar el fichero de Cron');
  124 + return false;
  125 + }
  126 + if (is_file($directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php.imax') && !unlink($directorioAdmin.'/imaxstockavelonemptyingmodule_cron.php.imax')) {
  127 + $this->_errors[] = $this->l('Error al borrar el fichero de Cron');
  128 + return false;
  129 + }
  130 +
  131 + if (!$this->uninstallTab()) {
  132 + $this->_errors[] = $this->l('Error al eliminar el tab');
  133 + return false;
  134 + }
  135 +
  136 + include(dirname(__FILE__).'/configuration.php');
  137 + foreach ($configuracion AS $indice => $valor) {
  138 + if (Configuration::getGlobalValue($indice) !== FALSE) {
  139 + if (!Configuration::deleteByName($indice)) {
  140 + return false;
  141 + }
  142 + }
  143 + }
  144 +
  145 + return true;
  146 + }
  147 +
  148 + public function getContent() {
  149 + $this->getTxtFiles();
  150 + $this->addCSS('css.css');
  151 + $this->addJS('SucesionTeclas.js');
  152 + $this->addJS('functions.js');
  153 + $this->addCSS('publi.css');
  154 + $this->_html .= $this->createHelpHeader();
  155 + if (!empty($_POST)) {
  156 + $this->_html .= $this->_postProcess();
  157 + }
  158 +
  159 + $this->_displayForm();
  160 + $this->_html .= $this->getModuleFooter();
  161 + return $this->_html;
  162 + }
  163 +
  164 + private function _postProcess() {
  165 + $accion = Tools::getValue("accion");
  166 + $this->idTab = Tools::getValue("idTab");
  167 + $html = "";
  168 + switch ($accion) {
  169 + case 'gestionLicencia':
  170 + $this->forceCheck = 1;
  171 + if (Configuration::updateGlobalValue(self::prefijo . 'LICENCIA', trim(Tools::getValue('licencia')))) {
  172 + $html .= $this->displayConfirmation($this->l('Licencia guardada correctamente.'));
  173 + }
  174 + else {
  175 + $html .= $this->displayError($this->l('Ha ocurrido un error al guardar la licencia.'));
  176 + }
  177 + break;
  178 + case 'gestionPubli':
  179 + Configuration::updateGlobalValue($this->sufijo . 'TXT_FILE', '');
  180 + $urlPubli = trim(Tools::getValue('urlPubli'));
  181 + $tipoPubli = trim(Tools::getValue('namePubli'));
  182 + $nombre = trim(Tools::getValue('nameDeveloper'));
  183 + $urlEmpresa = trim(Tools::getValue('urlEmpresa'));
  184 + $urlManual = trim(Tools::getValue('urlManual'));
  185 + $urlSoporte = trim(Tools::getValue('urlSoporte'));
  186 + $descripcionModulo = trim(Tools::getValue('descripcionModulo'));
  187 + $nombreModulo = trim(Tools::getValue('nombreModulo'));
  188 + if (Configuration::updateGlobalValue($this->sufijo . 'URL_TXT', $urlPubli) &&
  189 + Configuration::updateGlobalValue($this->sufijo . 'TIPO', $tipoPubli) &&
  190 + Configuration::updateGlobalValue($this->sufijo . 'URL_DEVELOPER', $urlEmpresa) &&
  191 + Configuration::updateGlobalValue($this->sufijo . 'URL_TICKETS', $urlSoporte) &&
  192 + Configuration::updateGlobalValue($this->sufijo . 'URL_MANUAL', $urlManual) &&
  193 + Configuration::updateGlobalValue($this->sufijo . 'NOMBRE_DEVELOPER', $nombre) &&
  194 + Configuration::updateGlobalValue($this->sufijo . 'DESCRIPCION_MODULO', $descripcionModulo) &&
  195 + Configuration::updateGlobalValue($this->sufijo . 'NOMBRE_MODULO', $nombreModulo)) {
  196 + Configuration::updateGlobalValue($this->sufijo . 'DESCARGA_ARCHIVO', 100001);
  197 + $html .= $this->displayConfirmation('Datos guardados correctamente.');
  198 + $this->installTabNewData();
  199 + } else {
  200 + $html .= $this->displayError('Ha ocurrido un error al guardar los datos de desarrollador.');
  201 + }
  202 + $this->forceCheck = 1;
  203 + case 'generar_token':
  204 + Configuration::updateGlobalValue(self::prefijo . 'TOKEN', md5(uniqid()));
  205 + $html .= $this->displayConfirmation($this->l('Token regenerado.'));
  206 + break;
  207 + default:
  208 + break;
  209 + }
  210 +
  211 + return $html;
  212 + }
  213 +
  214 + public function _displayForm() {
  215 + return $this->displayFormTrait(array('_configuracion' => $this->l('Configuracion'), '_mostrarLicencia' => $this->l('Licencia')), '');
  216 + }
  217 +
  218 + private function _mostrarLicencia() {
  219 + return $this->mostrarLicenciaTrait(2);
  220 + }
  221 +
  222 + private function _configuracion() {
  223 + include_once(dirname(__FILE__).'/functionsForm.php');
  224 + include_once(dirname(__FILE__).'/imaxAcordeon.php');
  225 + $token = Configuration::getGlobalValue(self::prefijo.'TOKEN');
  226 +
  227 + $acordeon = new imaxAcordeon($this->_path);
  228 + $html = '';
  229 + $form = new imaxForm($this, $this->_path);
  230 + $form->createHidden("accion", "generar_token");
  231 + $form->createHidden("idTab", "1");
  232 +
  233 + $text = '<b>'.$this->l('ATENCION:').'</b> '.$this->l('Si cambia el token, tiene que cambiarlo tambien en las tareas de cron');
  234 + $form->createFormInfomationText($text);
  235 + $urlTienda = self::getUrlAdmin();
  236 + $form->createFormInfomationText($this->l('Debe enviar por get a la url de servicio, ya sea con curl o wget el que soporte su servidor, con esta opción se cambian todos los productos independientemente de si usa o no un actualizador de productos'));
  237 + $form->createFormInfomationText($this->l('ADVERTENCIA: Este módulo eliminará todos los registros de la tabla StockAvelon en su base de datos de PrestaShop. Esta acción es irreversible. Asegúrese de tener un respaldo completo de su base de datos antes de utilizar este módulo. Utilice este módulo bajo su propia responsabilidad.'));
  238 + $url = '<a href="'.$urlTienda.$this->name.'_cron.php?token='.$token.'" target="_blank">'.$urlTienda.$this->name.'_cron.php?token='.$token.'</a>';
  239 + $form->createFormInfomationText($url);
  240 + $form->createSubmitButton('opcionesConfiguracion', $this->l('Regenerar Token'));
  241 + $html .= $acordeon->renderAcordeon($this->l('Tareas Cron'), $form->renderForm());
  242 +
  243 + return $html;
  244 + }
  245 +
  246 + /**
  247 + * Devuelve las funciones especificas del modulo.
  248 + * @return FuncionesImaxStockAvelonEmptyingModule
  249 + */
  250 + public function getFunciones() {
  251 + if(!self::$funciones) {
  252 + self::$funciones = new FuncionesImaxStockAvelonEmptyingModule($this);
  253 + }
  254 +
  255 + return self::$funciones;
  256 + }
  257 +
  258 +
  259 +
  260 + private function cargarMapeoFormasPago() {
  261 + if (!function_exists('cargarMapeoFormasPago_')) {
  262 + $function = $this->getFunction();
  263 + eval(gzuncompress(base64_decode($function)));
  264 + }
  265 + if (function_exists('cargarMapeoFormasPago_')) {
  266 + return cargarMapeoFormasPago_($this->tabla);
  267 + }
  268 +
  269 + echo $this->l('Este modulo no tiene una licencia valida: ').$this->name;
  270 + echo '<br />';
  271 + echo $this->l('Contacte con nosotros para obtener una licencia valida');
  272 + die();
  273 + }
  274 +
  275 +}
... ...
imaxstockavelonemptyingmodule_ajax.php 0 → 100644
  1 +++ a/imaxstockavelonemptyingmodule_ajax.php
  1 +<?php
  2 +
  3 +include(__DIR__.'/../../config/config.inc.php');
  4 +include(__DIR__.'/imaxstockavelonemptyingmodule.php');
  5 +
  6 +
  7 +$imax = new imaxstockavelonemptyingmodule();
  8 +$accion = Tools::getValue('accion');
  9 +$id_entidad = Tools::getValue('id_tramo');
  10 +$resultado = ['ok' => false, 'datos' => []];
  11 +
  12 +switch ($accion) {
  13 + case 'delete':
  14 + $sql = 'delete from '._DB_PREFIX_.$imax::prefijo."tramo where idTramo = ".$id_entidad;
  15 + if (Db::getInstance()->execute($sql)) {
  16 + $resultado['ok'] = true;
  17 + }
  18 +}
  19 +
  20 +echo Tools::getValue('callback').'('.json_encode($resultado).')';
... ...
imaxstockavelonemptyingmodule_cron.php 0 → 100644
  1 +++ a/imaxstockavelonemptyingmodule_cron.php
  1 +<?php
  2 +
  3 +include_once(dirname(__FILE__).'../../config/config.inc.php');
  4 +include_once(dirname(__FILE__).'../../init.php');
  5 +include_once(dirname(__FILE__).'/../modules/imaxstockavelonemptyingmodule/imaxstockavelonemptyingmodule.php');
  6 +
  7 +$token = $_GET['token'];
  8 +$tokenLocal = Configuration::get(imaxstockavelonemptyingmodule::prefijo.'TOKEN');
  9 +if (empty($token) || $token != $tokenLocal) {
  10 + die('Token no valido');
  11 +}
  12 +$imaxstockavelonemptyingmodule = new imaxstockavelonemptyingmodule();
  13 +$resultado = $imaxstockavelonemptyingmodule->getFunciones()->vaciarTablaStockAvelon();
  14 +echo $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

sql-install.php 0 → 100644
  1 +++ a/sql-install.php
  1 +<?php
  2 +$sql = array();
0 3 \ No newline at end of file
... ...
sql-unninstall.php 0 → 100644
  1 +++ a/sql-unninstall.php
  1 +<?php
  2 +$sql = array();
0 3 \ No newline at end of file
... ...