Commit 4f13b785ac2f12df5bfed35fce7b1b745becbe4f

Authored by alberto.alvarez
0 parents

VERSION INICIAL

Showing 71 changed files with 9300 additions and 0 deletions
AdminInformax.gif 0 → 100644

574 Bytes

ComunesImaxAddCatbyFeature.php 0 → 100644
  1 +++ a/ComunesImaxAddCatbyFeature.php
  1 +<?php
  2 +
  3 +/**
  4 + * Contiene las funciones comunes de los modulos.
  5 + * @version 1.3
  6 + */
  7 +Trait ComunesImaxAddCatbyFeature {
  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> 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="http://www.informax.es">';
  54 + $html .= '<img src="../modules/'.$this->name.'/img/informax.png">';
  55 + $html .= '<div>'.$this->l('Informax').'</div>';
  56 + $html .= '</a>';
  57 + $html .= '</li>';
  58 +
  59 + $html .= '<li>';
  60 + $html .= '<a target="_blank" href="http://tickets.informax.es/open.php?topicId=10">';
  61 + $html .= '<img src="../modules/'.$this->name.'/img/abrir-ticket.png">';
  62 + $html .= '<div>'.$this->l('Asistencia').'</div>';
  63 + $html .= '</a>';
  64 + $html .= '</li>';
  65 +
  66 + $html .= '<li>';
  67 + $html .= '<a target="_blank" href="http://docs.informax.es/?p='.$this->idManual.'">';
  68 + $html .= '<img src="../modules/'.$this->name.'/img/ir-a-manuales.png">';
  69 + $html .= '<div>'.$this->l('Manuales').'</div>';
  70 + $html .= '</a>';
  71 + $html .= '</li>';
  72 +
  73 + $html .= '</ul>';
  74 + $html .= '</div>';
  75 + $html .= '</div>';
  76 + return $html;
  77 + }
  78 +
  79 + public function getModuleFooter() {
  80 + $url = Configuration::getGlobalValue(self::prefijo.'URL_FALDON');
  81 + $html = '<div class="module-newsletter">';
  82 + $html .= '<form action="http://www.informax.es/subscribe/" method="post" target="_blank">
  83 + <input type="hidden" name="accion" value="newsletter">
  84 + <input type="hidden" name="idTab" value="1" />';
  85 + $html .= '<p>'.$this->l('Si deseas enterarte de todos los cambios en nuestros modulos, nuevos Modulos, como funciona Prestashop, apuntate a nuestras news').'</p>';
  86 + $html .= ' <p>';
  87 + $html .= ' <label>'.$this->l('email').'</label>';
  88 + $html .= '<input type="text" name="email" value="" />';
  89 + $html .= '<input type="submit" name="subscribe" value="'.$this->l('Guardar').'" />';
  90 + $html .= '</p>';
  91 + $html .= '</form><br />';
  92 + $html .= '</div>';
  93 + $html .= '<div class="module-footer">';
  94 + $html .= '<div class="module-footer-left">';
  95 + $html .= $this->getDatosPubli('footera');
  96 + $html .= '</div>';
  97 + $html .= '<div class="module-footer-right">';
  98 + $html .= $this->getDatosPubli('footerb');
  99 + $html .= '</div>';
  100 + $html .= '</div>';
  101 + return $html;
  102 + }
  103 +
  104 + public function getDatosPubli($tipo) {
  105 + $datosCompletos = Configuration::getGlobalValue(self::prefijo.'TXT_FILE');
  106 +
  107 + $html = '';
  108 + if ($datosCompletos) {
  109 + $aperturaA = '';
  110 + $cierreA = '';
  111 + $datosCompletos = @unserialize($datosCompletos);
  112 + if (!is_array($datosCompletos)) {
  113 + return '';
  114 + }
  115 + shuffle($datosCompletos);
  116 + foreach ($datosCompletos AS $datoElemento) {
  117 + if ($datoElemento[1] == $tipo) {
  118 + if (trim($datoElemento[2]) != '') {
  119 + $aperturaA .= '<a href="'.$datoElemento[2].'" target="_blank">';
  120 + $cierreA = '</a>';
  121 + }
  122 + if (trim($datoElemento[0]) != '') {
  123 + $html = $aperturaA.'<img src="'.Configuration::getGlobalValue(self::prefijo.'URL_TXT').'/img/'.$datoElemento[0].'" />'.$cierreA;
  124 + }
  125 + return $html;
  126 + }
  127 + }
  128 + }
  129 + return $html;
  130 + }
  131 +
  132 + public function getTxtFiles() {
  133 + if (Configuration::getGlobalValue(self::prefijo.'DESCARGA_ARCHIVO') < 10000) {
  134 + Configuration::updateGlobalValue(self::prefijo.'DESCARGA_ARCHIVO', Configuration::getGlobalValue(self::prefijo.'DESCARGA_ARCHIVO') + 1);
  135 + return false;
  136 + }
  137 + $url = Configuration::getGlobalValue(self::prefijo.'URL_TXT').'/'.Configuration::getGlobalValue(self::prefijo.'TIPO').'.txt';
  138 + $basedir_active = ini_get('open_basedir');
  139 + $ch = curl_init($url);
  140 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  141 + curl_setopt($ch, CURLOPT_BINARYTRANSFER, TRUE);
  142 + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  143 + curl_setopt($ch, CURLOPT_TIMEOUT, 15);
  144 + if (!$basedir_active) {
  145 + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
  146 + }
  147 + $datos = curl_exec($ch);
  148 + $infoCurl = curl_getinfo($ch);
  149 + if (!isset($infoCurl['http_code']) || $infoCurl['http_code'] != 200) {
  150 +
  151 + return false;
  152 + }
  153 + Configuration::updateGlobalValue(self::prefijo.'DESCARGA_ARCHIVO', 0);
  154 + return Configuration::updateGlobalValue(self::prefijo.'TXT_FILE', $this->readCsv($datos));
  155 + }
  156 +
  157 + private function readCsv($string) {
  158 + $string = trim($string);
  159 + $temp = explode("\n", $string);
  160 + $respuesta = array();
  161 + foreach ($temp AS $elemento) {
  162 + $elementoInterno = array();
  163 + $elementoInterno = explode(";", $elemento);
  164 + $respuesta[] = $elementoInterno;
  165 + }
  166 + return serialize($respuesta);
  167 + }
  168 +
  169 + public function l($msg, $modulo = '') {
  170 + if ($modulo == '') {
  171 + $modulo = 'traducciones'.strtolower($this->name);
  172 + }
  173 +
  174 + if ($this->versionPS > 17) {
  175 + return parent::trans($msg, $modulo);
  176 + }
  177 +
  178 + return parent::l($msg, $modulo);
  179 + }
  180 +
  181 + /**
  182 + * Crea un nuevo tab.
  183 + * @param string $clase
  184 + * @param string $nombre
  185 + * @param string $padre
  186 + * @return boolean
  187 + */
  188 + private function crearTab($clase, $nombre, $padre = '') {
  189 + if (!Tab::getIdFromClassName($clase)) {
  190 + $tab = new Tab();
  191 + $tab->active = 1;
  192 + $tab->class_name = $clase;
  193 + $tab->name = array();
  194 + foreach (Language::getLanguages(true) as $lang) {
  195 + $tab->name[$lang['id_lang']] = $nombre;
  196 + }
  197 + if ($padre == '') {
  198 + $posicion = 0;
  199 + }
  200 + else {
  201 + $posicion = Tab::getIdFromClassName($padre);
  202 + }
  203 + $tab->id_parent = intval($posicion);
  204 + $tab->module = $this->name;
  205 + try {
  206 + if (!$tab->add()) {
  207 + return false;
  208 + }
  209 + }
  210 + catch (Exception $exc) {
  211 + return false;
  212 + }
  213 + }
  214 +
  215 + return true;
  216 + }
  217 +
  218 + /**
  219 + * Borra un tab.
  220 + * @param string $clase
  221 + * @return boolean
  222 + */
  223 + private function borrarTab($clase) {
  224 + $id_tab = (int)Tab::getIdFromClassName($clase);
  225 + if ($id_tab) {
  226 + $tab = new Tab($id_tab);
  227 + try {
  228 + if (!$tab->delete()) {
  229 + return false;
  230 + }
  231 + }
  232 + catch (Exception $exc) {
  233 + return false;
  234 + }
  235 + }
  236 +
  237 + return true;
  238 + }
  239 +
  240 + /**
  241 + * Instala los tabs del módulo.
  242 + * @return boolean
  243 + */
  244 + private function installTab() {
  245 + include(dirname(__FILE__).'/configuration.php');
  246 +
  247 + //Instalamos el root
  248 + if (isset($moduleTabRoot) && $moduleTabRoot) {
  249 + $this->crearTab($moduleTabRoot['clase'], $moduleTabRoot['name']);
  250 + }
  251 +
  252 + //Instalamos el resto de tabs
  253 + if (isset($moduleTabs) && $moduleTabs) {
  254 + foreach ($moduleTabs AS $moduleTab) {
  255 + $this->borrarTab($moduleTab['clase']);
  256 + if (!$this->crearTab($moduleTab['clase'], $moduleTab['name'], $moduleTab['padre'])) {
  257 + return false;
  258 + }
  259 + }
  260 + }
  261 +
  262 + return true;
  263 + }
  264 +
  265 + /**
  266 + * Desinstala los tabs del módulo.
  267 + * @return boolean
  268 + */
  269 + private function uninstallTab() {
  270 + include(dirname(__FILE__).'/configuration.php');
  271 +
  272 + //Desinstalamos las tabs de este módulo
  273 + if (isset($moduleTabs) && $moduleTabs) {
  274 + foreach ($moduleTabs AS $moduleTab) {
  275 + if (!$this->borrarTab($moduleTab['clase'])) {
  276 + return false;
  277 + }
  278 + }
  279 + }
  280 +
  281 + //Desinstalamos el root si está vacío
  282 + if (isset($moduleTabRoot) && $moduleTabRoot) {
  283 + $id_tab = (int)Tab::getIdFromClassName($moduleTabRoot['clase']);
  284 + if ($id_tab && Tab::getNbTabs($id_tab) == 0) {
  285 + if (!$this->borrarTab($moduleTabRoot['clase'])) {
  286 + return false;
  287 + }
  288 + }
  289 + }
  290 +
  291 + return true;
  292 + }
  293 +
  294 + public function checkLicencia($force = 0) {
  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 + return $html;
  483 + }
  484 +
  485 +}
... ...
FuncionesImaxAddCatbyFeature.php 0 → 100644
  1 +++ a/FuncionesImaxAddCatbyFeature.php
  1 +<?php
  2 +
  3 +class FuncionesImaxAddCatbyFeature {
  4 + private $modulo;
  5 +
  6 + public function __construct($modulo) {
  7 + $this->modulo = $modulo;
  8 + }
  9 +
  10 + /**
  11 + * Agrega al producto las categorias pasadas.
  12 + * @param Product $producto
  13 + * @param int[] $categorias
  14 + */
  15 + public function agregarCategorias($producto, $categorias) {
  16 + $categoriasActuales = $producto->getCategories();
  17 + $nuevasCategorias = array_diff($categorias, $categoriasActuales);
  18 + foreach($nuevasCategorias as $categoria) {
  19 + $this->guardarMovimiento($producto->id, $categoria);
  20 + }
  21 + $producto->addToCategories($nuevasCategorias);
  22 + }
  23 +
  24 + /**
  25 + * Anota una nueva categoria de producto.
  26 + * @param int $id_product
  27 + * @param int $id_category
  28 + * @return boolean
  29 + */
  30 + private function guardarMovimiento($id_product, $id_category) {
  31 + $id_product = (int)$id_product;
  32 + $id_category = (int)$id_category;
  33 +
  34 + return Db::getInstance()->execute('INSERT IGNORE INTO `'._DB_PREFIX_.$this->modulo->sufijo."movimiento` (id_product, id_category) VALUES ('$id_product', '$id_category')");
  35 + }
  36 +
  37 + /**
  38 + * Devuelve los ids de categoria a los que el producto fue agregado.
  39 + * @param int $id_product
  40 + * @return int[]
  41 + */
  42 + private function cargarMovimientos($id_product) {
  43 + $id_product = (int)$id_product;
  44 +
  45 + $movimientos = Db::getInstance()->executeS('SELECT id_category FROM `'._DB_PREFIX_.$this->modulo->sufijo."movimiento` WHERE id_product = '$id_product'");
  46 + return array_column($movimientos, 'id_category');
  47 + }
  48 +
  49 + /**
  50 + * Elimina los movimientos de un producto.
  51 + * @param int $id_product
  52 + * @return boolean
  53 + */
  54 + private function eliminarMovimientos($id_product) {
  55 + $id_product = (int)$id_product;
  56 +
  57 + return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.$this->modulo->sufijo."movimiento` WHERE id_product = '$id_product'");
  58 + }
  59 +
  60 + /**
  61 + * Quita al producto las categorias agregadas.
  62 + * @param Product $producto
  63 + */
  64 + public function deshacerMovimientos($producto) {
  65 + $idsCategoria = $this->cargarMovimientos($producto->id);
  66 + $num = count($idsCategoria);
  67 + foreach($idsCategoria as $clave => $idCategoria) {
  68 + $producto->deleteCategory($idCategoria, ($clave == $num - 1));
  69 + }
  70 + $this->eliminarMovimientos($producto->id);
  71 + }
  72 +
  73 + /**
  74 + * Monta una lista de categorias recursivamente.
  75 + * @param array $categoria
  76 + * @param string $html
  77 + * @param int[] $categoriasSeleccionada
  78 + */
  79 + public function getHijosLista($categoria, &$html, $categoriasSeleccionada) {
  80 + $categoriasHijos = Category::getChildren($categoria['id_category'], $this->modulo->idLang);
  81 +
  82 + if (count($categoriasHijos)) {
  83 + $html .= '<ul>';
  84 + foreach ($categoriasHijos AS $categoriaHijo) {
  85 + if (in_array($categoriaHijo['id_category'], $categoriasSeleccionada)) {
  86 + $seleccionada = 'class="selected"';
  87 + }
  88 + else {
  89 + $seleccionada = '';
  90 + }
  91 +
  92 + $html .= "<li id='{$categoriaHijo['id_category']}' $seleccionada>{$categoriaHijo['name']}";
  93 + $this->getHijosLista($categoriaHijo, $html, $categoriasSeleccionada);
  94 +
  95 + $html .= '</li>';
  96 + }
  97 + $html .= '</ul>';
  98 + }
  99 + }
  100 +}
... ...
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>imaxaddcatbyfeature</name>
  4 + <displayName><![CDATA[Informax add to category by feature]]></displayName>
  5 + <version><![CDATA[1.1]]></version>
  6 + <description><![CDATA[Informax add to category by feature]]></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 . 'CATS_NOVEDAD'] = serialize(array());
  4 +$configuracion[$this->sufijo . 'DIAS_NOVEDAD'] = 7;
  5 +$configuracion[$this->sufijo . 'TOKEN'] = md5(uniqid());
  6 +
  7 +$configuracion[$this->sufijo . 'LICENCIA'] = '';
  8 +$configuracion[$this->sufijo . 'F_SERVER'] = 'http://licencia.informax.es/gestionarLicencias.php';
  9 +$configuracion[$this->sufijo . 'F'] = '';
  10 +$configuracion[$this->sufijo . 'F_CHECK'] = 1000;
  11 +
  12 +
  13 +$hooks = array();
  14 +$hooks[] = 'actionObjectProductAddAfter';
  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'] = 'Feature by carac';
  47 +$moduleTabs[0]['clase'] = 'Adminxaddcatbyfeature';
  48 +$moduleTabs[0]['padre'] = 'AdminInformax';
  49 +$moduleTabs[0]['imagen'] = 'informax.gif';
... ...
controllers/admin/Adminxaddcatbyfeature.php 0 → 100644
  1 +++ a/controllers/admin/Adminxaddcatbyfeature.php
  1 +<?php
  2 +class AdminimaxaddcatbyfeatureController 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=imaxaddcatbyfeature&tab_module=administration&module_name=imaxaddcatbyfeature&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=imaxaddcatbyfeature&tab_module=administration&module_name=imaxaddcatbyfeature&controller=AdminModules&token=' . $token);
  14 + exit;
  15 + }
  16 +}
... ...
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 +.imax_marcado {
  43 + background-color: yellow;
  44 +}
  45 +
  46 +#arbolCategorias {
  47 + margin-bottom: 15px;
  48 +}
0 49 \ No newline at end of file
... ...
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 +}
... ...
forms/forms.js 0 → 100644
  1 +++ a/forms/forms.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/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.34
  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 + $html .= '<div class="translatable-field row lang-' . $language['id_lang'] . '">';
  327 + $html .= '<div class="col-lg-9">';
  328 + $html .= "<input type='text' id='{$name}_{$language['id_lang']}' name='{$name}[{$language['id_lang']}]' value='{$value[$language['id_lang']]}'/>";
  329 + $html .= '</div>';
  330 + $html .= '<div class="col-lg-2">';
  331 + $html .= '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">';
  332 + $html .= $language['iso_code'];
  333 + $html .= '<span class="caret"></span>';
  334 + $html .= '</button> ';
  335 + $html .= '<ul class="dropdown-menu"> ';
  336 + foreach ($idiomasSelect AS $idiomaSelect) {
  337 + $html .= ' <li><a href="javascript:hideOtherLanguage(' . $idiomaSelect['id_lang'] . ');">' . $idiomaSelect['name'] . '</a></li>';
  338 + }
  339 + $html .= ' </ul> ';
  340 + $html .= ' </div> ';
  341 + $html .= ' </div> ';
  342 + }
  343 + } else {
  344 + $html .= '<div class="col-lg-3">';
  345 + $html .= '<input type="' . $tipo . '" value="' . $value . '" name="' . $name . '" id="' . $name . '"';
  346 + if ($extras && is_array($extras)) {
  347 + foreach ($extras AS $indice => $extra) {
  348 + $html.= ' ' . $indice . '="' . $extra . '"';
  349 + }
  350 + }
  351 + if ($autofocus) {
  352 + $html .= ' autofocus ';
  353 + }
  354 + $html .= '>';
  355 + $html .= '</div>';
  356 + }
  357 + $html .= '</div>';
  358 +
  359 + $html .= '<script>hideOtherLanguage(' . Configuration::getGlobalValue('PS_LANG_DEFAULT') . ');</script>';
  360 +
  361 + $this->formulario .= $html;
  362 +
  363 + return $this;
  364 + }
  365 +
  366 + /**
  367 + *
  368 + * @param string $name
  369 + * @param string $text
  370 + * @param string $title
  371 + * @param array $extras
  372 + * @return $this
  373 + */
  374 + function createFormDate($name, $text, $title = '', $extras = false) {
  375 + $tipo = 'text';
  376 +
  377 +
  378 + $html = '<div class="form-group bootstrap">';
  379 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  380 + $html .= '<span title="' . $title . '">';
  381 + $html .= $text;
  382 + $html .= '</span>';
  383 + $html .= '</label>';
  384 +
  385 + $html .= '<div class="col-lg-3">';
  386 + $html .= '<input type="' . $tipo . '" data-hex="true" value="" name="' . $name . '" id="' . $name . '"';
  387 + if ($extras && is_array($extras)) {
  388 + foreach ($extras AS $indice => $extra) {
  389 + $html.= ' ' . $indice . '="' . $extra . '"';
  390 + }
  391 + }
  392 + $html .= '>';
  393 + $html .= '</div>';
  394 + $html .= '</div>';
  395 + $html .= '<script type="text/javascript">';
  396 + $html .= '$(function(){ ';
  397 + $html .= ' $("#' . $name . '").datepicker(); ';
  398 + $html .= '});';
  399 + $html .= '</script>';
  400 +
  401 +
  402 + $this->formulario .= $html;
  403 +
  404 + return $this;
  405 + }
  406 +
  407 + /**
  408 + * Permite crear un elemento de tipo color picker
  409 + * @param string $name
  410 + * @param string $value
  411 + * @param string $text
  412 + * @param string $title
  413 + * @param array $extras
  414 + * @param boolean $autofocus
  415 + */
  416 + function createFormColorPicker($name, $value, $text, $title = '', $extras = false, $autofocus = false) {
  417 + $html = '<div class="form-group ';
  418 + if ($this->modulo->versionPS < 17) {
  419 + $html .= 'bootstrap';
  420 + }
  421 + $html .= '">';
  422 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  423 + $html .= '<span title="' . $title . '">';
  424 + $html .= $text;
  425 + $html .= '</span>';
  426 + $html .= '</label>';
  427 + $html .= '<div class="col-lg-2">';
  428 + $html .= '<div class="row">';
  429 + $html .= '<div class="input-group">';
  430 + $html .= '<input type="text" value="' . $value . '" name="' . $name . '" id="' . $name . '" data-hex="true"';
  431 + $class = '';
  432 + if ($extras && is_array($extras)) {
  433 + foreach ($extras AS $indice => $extra) {
  434 + if ($indice == 'class') {
  435 + $class .= ' ' . $extra;
  436 + } else {
  437 + $html.= ' ' . $indice . '="' . $extra . '"';
  438 + }
  439 + }
  440 + }
  441 + $html .= ' class="color mColorPickerInput';
  442 + if ($class) {
  443 + $html .= ' ' . $class . '"';
  444 + } else {
  445 + $html .= '"';
  446 + }
  447 + if ($autofocus) {
  448 + $html .= ' autofocus ';
  449 + }
  450 + $html .= '>';
  451 + $html .= '</div>';
  452 + $html .= '</div>';
  453 + $html .= '</div>';
  454 + $html .= '</div>';
  455 + $this->formulario .= $html;
  456 + }
  457 +
  458 + /**
  459 + *
  460 + * @param string $name
  461 + * @param string $value
  462 + * @param string $text
  463 + * @param string $title
  464 + * @param string $outputDelimiter
  465 + * @param string $paceholder
  466 + * @param array $options
  467 + * @return $this
  468 + */
  469 + function createFormTagTextGroup($name, $value, $text, $title = '', $outputDelimiter = ',', $paceholder = 'Añadir etiqueta', $options = array()) {
  470 + $html = '';
  471 + $this->addJS('jquery.tagify.js');
  472 + $html .= '<div class="form-group bootstrap">';
  473 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  474 + $html .= '<span title="' . $title . '">';
  475 + $html .= $text;
  476 + $html .= '</span>';
  477 + $html .= '</label>';
  478 + $html .= '<div class="col-lg-9">';
  479 + $html .= '<input type="text" value="' . $value . '" name="' . $name . '" id="' . $name . '"';
  480 + if ($options) {
  481 + foreach ($options AS $indice => $valor) {
  482 + if ($indice == 'class') {
  483 + $valor .= ' tagify';
  484 + }
  485 + $html .= $indice . '="' . $valor . '"';
  486 + }
  487 + }
  488 + $html .= '>';
  489 + $html .= '</div>';
  490 + $html .= '</div>';
  491 + $html .= '<script>';
  492 + $html .= '$().ready(function () { ';
  493 + $html .= ' $("#' . $name . '").tagify({'
  494 + . 'delimiters: [13,44], '
  495 + . 'addTagPrompt: "' . $paceholder . '",'
  496 + . 'outputDelimiter: "' . $outputDelimiter . '"'
  497 + . '});';
  498 +
  499 + $html .= ' $("#' . $name . '").closest("form").submit( function() {';
  500 + $html .= ' $(this).find("#' . $name . '").val($("#' . $name . '").tagify("serialize"));';
  501 + $html .= ' });';
  502 + $html .= '});';
  503 + $html .= '</script>';
  504 + $this->formulario .= $html;
  505 +
  506 + return $this;
  507 + }
  508 +
  509 + /**
  510 + *
  511 + * @param string $name
  512 + * @param string $text
  513 + * @param int $defaultValue
  514 + * @param int $min
  515 + * @param int $max
  516 + * @param int $step
  517 + * @param string $title
  518 + * @return $this
  519 + */
  520 + function createFormSelectNumerico($name, $text, $defaultValue, $min, $max, $step = 1, $title = '') {
  521 + $html = '';
  522 + $html .= '<div class="form-group">';
  523 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  524 + $html .= '<span title="' . $title . '">';
  525 + $html .= $text;
  526 + $html .= '</span>';
  527 + $html .= '</label>';
  528 + $html .= '<div class="col-lg-3">';
  529 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  530 + for ($i = $min; $i < $max; $i = $i + $step) {
  531 + $selected = '';
  532 + if ($i == $defaultValue) {
  533 + $selected = ' selected="selected" ';
  534 + }
  535 + $html .= '<option value="' . $i . '" ' . $selected . '>' . $i . '</option>';
  536 + }
  537 + $html .= '</select>';
  538 + $html .= '</div>';
  539 + $html .= '</div>';
  540 + $this->formulario .= $html;
  541 +
  542 + return $this;
  543 + }
  544 +
  545 + function createHidden($name, $value) {
  546 + $html = '<input type="hidden" name="' . $name . '" value="' . $value . '" id="' . $name . '" />';
  547 + $this->formulario .= $html;
  548 +
  549 + return $this;
  550 + }
  551 +
  552 + /**
  553 + *
  554 + * @param string $name
  555 + * @param string $text
  556 + * @param array $extras
  557 + * @return $this
  558 + */
  559 + function createSubmitButton($name, $text, $extras = false) {
  560 + $html = '<button class="btn btn-default pull-right" name="' . $name . '" type="submit"';
  561 + $html .= $this->addExtras($extras);
  562 + $html .= '>';
  563 + $html .= '<i class="process-icon-save"></i>' . $text . '</button>';
  564 + $this->formulario .= $html;
  565 +
  566 + return $this;
  567 + }
  568 +
  569 + /**
  570 + *
  571 + * @param string $name
  572 + * @param string $text
  573 + * @param array $extras
  574 + * @param string $type
  575 + * @param string $icon
  576 + * @return $this
  577 + */
  578 + public function createButton($name, $text, $extras = false, $type = 'button', $icon = 'plus') {
  579 + $html = '<button class="btn btn-default pull-right" name="' . $name . '" type="' . $type . '"';
  580 + if ($extras && is_array($extras)) {
  581 + foreach ($extras AS $indice => $extra) {
  582 + $html.= ' ' . $indice . '="' . $extra . '"';
  583 + }
  584 + }
  585 +
  586 + $html .= '>';
  587 + $html .= '<i class="process-icon-' . $icon . '"></i>' . $text . '</button>';
  588 + $this->formulario .= $html;
  589 +
  590 + return $this;
  591 + }
  592 +
  593 + /**
  594 + * Crea un select.
  595 + * @param string $name
  596 + * @param array $text
  597 + * @param string $datos valor => visible
  598 + * @param string $defaultValue
  599 + * @param string $title
  600 + * @param array $extras
  601 + * @return $this
  602 + */
  603 + function createFormSelect($name, $text, $datos, $defaultValue, $title = '', $extras = null) {
  604 + $html = '';
  605 + $html .= '<div class="form-group">';
  606 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  607 + $html .= '<span title="' . $title . '">';
  608 + $html .= $text;
  609 + $html .= '</span>';
  610 + $html .= '</label>';
  611 + $html .= '<div class="col-lg-3">';
  612 + $html .= '<select name="' . $name . '" id="' . $name . '" ';
  613 + if ($extras) {
  614 + foreach ($extras AS $indice => $extra) {
  615 + $html .= $indice . '="' . $extra . '"';
  616 + }
  617 + }
  618 + $html .= '>';
  619 + foreach ($datos AS $valor => $texto) {
  620 + $selected = '';
  621 + if ($valor == $defaultValue) {
  622 + $selected = ' selected="selected" ';
  623 + }
  624 + $html .= '<option value="' . $valor . '" ' . $selected . '>' . $texto . '</option>';
  625 + }
  626 + $html .= '</select>';
  627 + $html .= '</div>';
  628 + $html .= '</div>';
  629 + $this->formulario .= $html;
  630 +
  631 + return $this;
  632 + }
  633 +
  634 + /**
  635 + *
  636 + * @param string $name
  637 + * @param string $label
  638 + * @param string $title
  639 + * @param string $url
  640 + * @return $this
  641 + */
  642 + public function createFormFileButton($name, $label, $title, $url) {
  643 + $this->addJS('spin.js');
  644 + $this->addJS('ladda.js');
  645 +
  646 + $this->addJS('jquery.iframe-transport.js');
  647 + $this->addJS('jquery.fileupload.js');
  648 + $this->addJS('jquery.fileupload-process.js');
  649 + $this->addJS('jquery.fileupload-validate.js');
  650 + //$this->addJS('jquery.fileupload-image.js');
  651 +
  652 + $html = '';
  653 + $html .= '<div class="form-group">';
  654 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  655 + $html .= '<span title="" data-toggle="tooltip" class="label-tooltip" data-original-title="' . $title . '">' . $label . '</span>';
  656 + $html .= '</label>';
  657 + $html .= '<div class="col-lg-8 bootstrap">';
  658 + $html .= '<input type="file" class="hide" data-url="' . $url . '" name="' . $name . '" id="' . $name . '">';
  659 + $html .= '<button id="' . $name . '-add-button" type="button" data-size="s" data-style="expand-right" class="btn btn-default"><span class="ladda-label">';
  660 + $html .= '<i class="icon-plus-sign"></i> ' . $label . '';
  661 + $html .= '</span><span class="ladda-spinner"></span></button>';
  662 + $html .= '<!--';
  663 + $html .= '<div class="alert alert-success" id="' . $name . '-success" style="display:none">' . $this->modulo->l('Carga exitosa') . '</div>';
  664 + $html .= '<div class="alert alert-danger" id="' . $name . '-errors" style="display:none"></div>';
  665 + $html .= '-->';
  666 + $html .= '</div>';
  667 +
  668 + $html .= "<script type='text/javascript'>
  669 + function humanizeSize(bytes) {
  670 + if (typeof bytes !== 'number') {
  671 + return '';
  672 + }
  673 + if (bytes <= 1000000000) {
  674 + return (bytes / 1000000000).toFixed(2) + ' GB';
  675 + }
  676 + if (bytes <= 1000000) {
  677 + return (bytes / 1000000).toFixed(2) + ' MB';
  678 + }
  679 + return (bytes / 1000).toFixed(2) + ' KB';
  680 + }
  681 + $(document ).ready(function() {
  682 + var " . $name . "_add_button = Ladda.create(document.querySelector('#" . $name . "-add-button'));
  683 + var " . $name . "total_files=0;
  684 + var success_message = '" . $this->modulo->l("Carga exitosa") . "'
  685 + $('#" . $name . "-add-button').removeAttr('disabled');
  686 + $('#" . $name . "').fileupload({
  687 + dataType: 'jsonp',
  688 + autoUpload: true,
  689 + singleFileUploads: true,
  690 + maxFileSize: 838860800,
  691 + success: function (e) {
  692 + //showSuccessMessage(success_message);
  693 + },
  694 + start: function (e) {
  695 + " . $name . "_add_button.start();
  696 + },
  697 + fail: function (e, data) {
  698 + showErrorMessage(data.errorThrown.message);
  699 + },
  700 + done: function (e, data) {
  701 + if (data.result) {
  702 + if (data.result.codError != 0) {
  703 + showErrorMessage(data.result.msgError);
  704 + }else {
  705 + showSuccessMessage(data.result.msgError);
  706 + }
  707 + }
  708 + $('#" . $name . "-add-button').removeProp('disabled');
  709 + }
  710 + }).on('fileuploadalways', function (e, data) {
  711 + $('#" . $name . "-add-button').removeProp('disabled');
  712 + }).on('fileuploadprocessalways', function (e, data) {
  713 + var index = data.index, file = data.files[index];
  714 + }).on('fileuploadsubmit', function (e, data) {
  715 + var params = new Object();
  716 + $('input[id^=\"" . $name . "_name_\"]').each(function() {
  717 + id = $(this).prop('id').replace('" . $name . "_name_', '" . $name . "_name[') + ']';
  718 + params[id] = $(this).val();
  719 + });
  720 + $('textarea[id^=\"" . $name . "_description_\"]').each(function() {
  721 + id = $(this).prop('id').replace('" . $name . "_description_', '" . $name . "_description[') + ']';
  722 + params[id] = $(this).val();
  723 + });
  724 + data.formData = params;
  725 + });
  726 + $('#" . $name . "-add-button').on('click', function() {
  727 + //$('#attachment_file-success').hide();
  728 + //$('#attachment_file-errors').html('').hide();
  729 + " . $name . "_file_total_files = 0;
  730 + $('#" . $name . "').trigger('click');
  731 + });
  732 + });";
  733 + $html .= '</script>';
  734 + $html .= '</div>';
  735 + $this->formulario .= $html;
  736 +
  737 + return $this;
  738 + }
  739 +
  740 + /**
  741 + *
  742 + * @param string $name
  743 + * @param string $text
  744 + * @param mixed $defaultValue
  745 + * @param string $title
  746 + * @return $this
  747 + */
  748 + public function generarSelectIdioma($name, $text, $defaultValue, $title) {
  749 + $html = '';
  750 + $html .= '<div class="form-group">';
  751 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  752 + $html .= '<span title="' . $title . '">';
  753 + $html .= $text;
  754 + $html .= '</span>';
  755 + $html .= '</label>';
  756 + $html .= '<div class="col-lg-3">';
  757 + $html .= '<select name="' . $name . '" id="' . $name . '" class="selectIdioma">';
  758 + $idiomas = Language::getLanguages();
  759 + foreach ($idiomas AS $idioma) {
  760 + $selected = '';
  761 + if ($defaultValue == $idioma['id_lang']) {
  762 + $selected = ' selected="selected" ';
  763 + }
  764 + $html .= '<option value="' . $idioma['id_lang'] . '" ' . $selected . '>' . $idioma['name'] . '</option>';
  765 + }
  766 + $html .= '</select>';
  767 + $html .= '</div>';
  768 + $html .= '</div>';
  769 + reset($idiomas);
  770 + $html .= '<style>';
  771 + foreach ($idiomas AS $idioma) {
  772 + $html .= " .selectIdioma option[value='{$idioma['id_lang']}'] { background-image: url('.." . _THEME_LANG_DIR_ . "{$idioma['id_lang']}.jpg'); }";
  773 + }
  774 + $html .= '</style>';
  775 + $html .= '<script>';
  776 + $html .= ' var rutaImagenes="..' . _THEME_LANG_DIR_ . '"';
  777 + $html .= '</script>';
  778 + $this->formulario .= $html;
  779 +
  780 + return $this;
  781 + }
  782 +
  783 + private function addCSS($css) {
  784 + $tab = Tools::getValue('tab', 0);
  785 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  786 + if ($this->context->controller instanceof stdClass) {
  787 + $this->context->controller = new AdminModulesController();
  788 + }
  789 + if ($this->_modulePath) {
  790 + $this->context->controller->addCss($this->_modulePath . '/forms/' . $css, 'all');
  791 + } else {
  792 + $this->context->controller->addCss($this->_path . '/forms/' . $css, 'all');
  793 + }
  794 + }
  795 + return;
  796 + }
  797 +
  798 + private function addJS($js) {
  799 + $tab = Tools::getValue('tab', 0);
  800 + if (!$tab || ($tab && $tab != 'AdminSelfUpgrade')) {
  801 +
  802 + if ($this->context->controller instanceof stdClass) {
  803 + $this->context->controller = new AdminModulesController();
  804 + }
  805 + if ($this->_modulePath) {
  806 + $this->context->controller->addJs($this->_modulePath . '/forms/' . $js);
  807 + } else {
  808 + $this->context->controller->addJs($this->_path . '/forms/' . $js);
  809 + }
  810 + }
  811 + return;
  812 + }
  813 +
  814 + public function getLanguages() {
  815 + $languages = Language::getLanguages();
  816 + return $languages;
  817 + }
  818 +
  819 + /**
  820 + *
  821 + * @param string $name
  822 + * @param string $text
  823 + * @param int $defaultValue
  824 + * @param string $title
  825 + * @return $this
  826 + */
  827 + public function createSelectSupplier($name, $text, $defaultValue, $title = '') {
  828 + $suppliers = Supplier::getSuppliers();
  829 + $html = '';
  830 + $html .= '<div class="form-group">';
  831 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  832 + $html .= '<span title="' . $title . '">';
  833 + $html .= $text;
  834 + $html .= '</span>';
  835 + $html .= '</label>';
  836 + $html .= '<div class="col-lg-3">';
  837 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  838 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un proveedor - ') . '</option>';
  839 + foreach ($suppliers AS $supplier) {
  840 + $selected = '';
  841 + if ($supplier['id_supplier'] == $defaultValue) {
  842 + $selected = ' selected="selected"';
  843 + }
  844 + $html .= '<option ' . $selected . ' value="' . $supplier['id_supplier'] . '">' . htmlspecialchars(trim($supplier['name'])) . '</option>';
  845 + }
  846 +
  847 + $html .= '</select>';
  848 + $html .= '</div>';
  849 + $html .= '</div>';
  850 + $this->formulario .= $html;
  851 +
  852 + return $this;
  853 + }
  854 +
  855 + /**
  856 + *
  857 + * @param string $name
  858 + * @param string $text
  859 + * @param int $defaultValue
  860 + * @param string $title
  861 + * @return $this
  862 + */
  863 + public function createSelectManufacturer($name, $text, $defaultValue, $title = '') {
  864 + $manufacturers = Manufacturer::getManufacturers();
  865 + $html = '';
  866 + $html .= '<div class="form-group">';
  867 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  868 + $html .= '<span title="' . $title . '">';
  869 + $html .= $text;
  870 + $html .= '</span>';
  871 + $html .= '</label>';
  872 + $html .= '<div class="col-lg-3">';
  873 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  874 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un fabricante - ') . '</option>';
  875 + foreach ($manufacturers AS $manufacturer) {
  876 + $selected = '';
  877 + if ($manufacturer['id_manufacturer'] == $defaultValue) {
  878 + $selected = ' selected="selected"';
  879 + }
  880 + $html .= '<option ' . $selected . ' value="' . $manufacturer['id_manufacturer'] . '">' . htmlspecialchars(trim($manufacturer['name'])) . '</option>';
  881 + }
  882 +
  883 + $html .= '</select>';
  884 + $html .= '</div>';
  885 + $html .= '</div>';
  886 + $this->formulario .= $html;
  887 +
  888 + return $this;
  889 + }
  890 +
  891 + /**
  892 + *
  893 + * @param int $id_lang
  894 + * @param string $name
  895 + * @param string $text
  896 + * @param array $value
  897 + * @param string $title
  898 + * @return $this
  899 + */
  900 + public function createSelectGroups($id_lang, $name, $text, $value, $title = '') {
  901 + $groups = Group::getGroups($id_lang);
  902 + $html = '';
  903 + $html .= '<div class="form-group">';
  904 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  905 + $html .= '<span title="' . $title . '">';
  906 + $html .= $text;
  907 + $html .= '</span>';
  908 + $html .= '</label>';
  909 + $html .= '<div class="col-lg-3">';
  910 + foreach ($groups as $group) {
  911 + $marcado = '';
  912 + foreach ($value as $v) {
  913 + if ($group['id_group'] == $v) {
  914 + $marcado = ' checked ';
  915 + break;
  916 + }
  917 + }
  918 + $html .= '<input type="checkbox" ' . $marcado . ' value="' . $group['id_group'] . '" name="' . $name . '[]" id="' . $name . '"> ' . $group['name'] . '<br/>';
  919 + }
  920 + $html .= '</div>';
  921 + $html .= '</div>';
  922 +
  923 + $this->formulario .= $html;
  924 + return $this;
  925 + }
  926 +
  927 + /**
  928 + *
  929 + * @param int $id_lang
  930 + * @param string $name
  931 + * @param string $text
  932 + * @param array $value
  933 + * @param string $title
  934 + * @param boolean $referencia
  935 + * @return $this
  936 + */
  937 + public function createCheckBoxCarriers($id_lang, $name, $text, $value, $title = '', $referencia = false) {
  938 + $carriers = Carrier::getCarriers($id_lang);
  939 + $html = '';
  940 + $html .= '<div class="form-group">';
  941 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  942 + $html .= '<span title="' . $title . '">';
  943 + $html .= $text;
  944 + $html .= '</span>';
  945 + $html .= '</label>';
  946 + $html .= '<div class="col-lg-3">';
  947 + foreach ($carriers as $carrier) {
  948 + $idCarrier = $carrier['id_carrier'];
  949 + if ($referencia) {
  950 + $idCarrier = $carrier['id_reference'];
  951 + }
  952 + $marcado = '';
  953 + foreach ($value as $v) {
  954 + if ($idCarrier == $v) {
  955 + $marcado = ' checked ';
  956 + break;
  957 + }
  958 + }
  959 + $html .= '<input type="checkbox" ' . $marcado . ' value="' . $idCarrier . '" name="' . $name . '[]" id="' . $name . '_' . $idCarrier . '"> ' . $carrier['name'] . '<br/>';
  960 + }
  961 + $html .= '</div>';
  962 + $html .= '</div>';
  963 +
  964 + $this->formulario .= $html;
  965 + return $this;
  966 + }
  967 +
  968 + /**
  969 + *
  970 + * @param int $id_lang
  971 + * @param string $name
  972 + * @param string $text
  973 + * @param array $idDefault
  974 + * @param string $title
  975 + * @param boolean $referencia
  976 + * @return $this
  977 + */
  978 + public function createSelectCarriers($id_lang, $name, $text, $idDefault, $title = '', $referencia = false) {
  979 + $carriers = Carrier::getCarriers($id_lang);
  980 + $html = '';
  981 + $html .= '<div class="form-group">';
  982 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  983 + $html .= '<span title="' . $title . '">';
  984 + $html .= $text;
  985 + $html .= '</span>';
  986 + $html .= '</label>';
  987 + $html .= '<div class="col-lg-3">';
  988 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  989 + foreach ($carriers as $carrier) {
  990 + $idCarrier = $carrier['id_carrier'];
  991 + if ($referencia) {
  992 + $idCarrier = $carrier['id_reference'];
  993 + }
  994 + $selected = '';
  995 + if ($idCarrier == $idDefault) {
  996 + $selected = ' selected="selected" ';
  997 + }
  998 + $html .= '<option ' . $selected . ' value="' . $idCarrier . '> ' . $carrier['name'] . '</option>';
  999 + }
  1000 + $html .= '</select>';
  1001 + $html .= '</div>';
  1002 + $html .= '</div>';
  1003 +
  1004 + $this->formulario .= $html;
  1005 + return $this;
  1006 + }
  1007 +
  1008 + /**
  1009 + *
  1010 + * @param string $name
  1011 + * @param string $text
  1012 + * @param mixed $defaultValue
  1013 + * @param string $title
  1014 + * @return $this
  1015 + */
  1016 + function createSelectNameImages($name, $text, $defaultValue, $title = '') {
  1017 + $images = ImageType::getImagesTypes();
  1018 + $html = '';
  1019 + $html .= '<div class="form-group">';
  1020 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1021 + $html .= '<span title="' . $title . '">';
  1022 + $html .= $text;
  1023 + $html .= '</span>';
  1024 + $html .= '</label>';
  1025 + $html .= '<div class="col-lg-3">';
  1026 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1027 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un tipo de Imagen - ') . '</option>';
  1028 + foreach ($images AS $image) {
  1029 + $selected = '';
  1030 + if ($image['id_image_type'] == $defaultValue) {
  1031 + $selected = ' selected="selected"';
  1032 + }
  1033 + $html .= '<option ' . $selected . ' value="' . $image['id_image_type'] . '">' . htmlspecialchars(trim($image['name'])) . '</option>';
  1034 + }
  1035 +
  1036 + $html .= '</select>';
  1037 + $html .= '</div>';
  1038 + $html .= '</div>';
  1039 + $this->formulario .= $html;
  1040 +
  1041 + return $this;
  1042 + }
  1043 +
  1044 + public function mostrarCategorias($name, $prefijo, $idParentCategoria = 2) {
  1045 + $sql = ' SELECT '
  1046 + . ' c.id_category, c.id_parent, cl.name, s.id_category AS checked, s.margen '
  1047 + . ' FROM '
  1048 + . ' ' . _DB_PREFIX_ . 'category AS c '
  1049 + . ' INNER JOIN ' . _DB_PREFIX_ . 'category_lang AS cl '
  1050 + . ' ON '
  1051 + . ' cl.id_category = c.id_category '
  1052 + . ' AND cl.id_lang = ' . $this->idLang . ''
  1053 + . ' AND c.id_shop_default = cl.id_shop '
  1054 + . ' AND c.id_shop_default = ' . $this->idShop . ''
  1055 + . ' LEFT JOIN ' . _DB_PREFIX_ . $prefijo . 'categorias AS s '
  1056 + . ' ON s.id_category = c.id_category '
  1057 + . ' WHERE '
  1058 + . ' c.id_parent = ' . $idParentCategoria . ' '
  1059 + . ' ORDER BY '
  1060 + . ' c.id_parent, c.id_category ';
  1061 + $categorias = Db::getInstance()->executeS($sql);
  1062 + $html = '<table id="' . $name . '">';
  1063 + $html .= '<thead>';
  1064 + $html .= '<tr>';
  1065 + $html .= '<td>&nbsp;</td>';
  1066 + $html .= '<td><input type="button" onclick="checkImaxAll(this.form, \'catCheckbox\')" name="checkme" value="' . $this->modulo->l('Invertir Seleccion') . '"></td>';
  1067 + $html .= '<td>' . $this->modulo->l('Margen general') . '&nbsp;<input type="textbox" onChange="llenarMargen(this.form)" id="llenarMargenText" value=""/></td>';
  1068 + $html .= '</tr>';
  1069 + $html .= '<tr>';
  1070 + $html .= '<th>' . $this->modulo->l('Categoria') . '</th>';
  1071 + $html .= '<th>' . $this->modulo->l('Marcar para Exportar') . '</th>';
  1072 + $html .= '<th>' . $this->modulo->l('Margen') . '</th>';
  1073 + $html .= '</tr>';
  1074 + $html .= '</thead>';
  1075 + foreach ($categorias AS $categoria) {
  1076 + $num = 0;
  1077 + $html .= '<tr>';
  1078 + $html .= '<td><b>' . $categoria['name'] . '</b></td>';
  1079 + $checked = "";
  1080 + $porcentaje = "";
  1081 + if (!empty($categoria['checked'])) {
  1082 + $checked = ' checked="checked"';
  1083 + $porcentaje = $categoria['margen'];
  1084 + }
  1085 + $html .= '<td><input type="checkbox" class="catCheckbox" ' . $checked . ' value="' . $categoria['id_category'] . '" name="categoria[' . $categoria['id_category'] . '][check]" /></td>';
  1086 + $html .= '<td><input class="catInput" type="text" name="categoria[' . $categoria['id_category'] . '][margen]" value="' . $porcentaje . '"/></td>';
  1087 + $html .= '</tr>';
  1088 + $this->getHijosTable($categoria, $html, $num, $prefijo);
  1089 + }
  1090 +
  1091 + $html .= '</table>';
  1092 +
  1093 + $html .= '<input type="hidden" name="accion" value="addPorcentajesCategorias">';
  1094 +
  1095 + $html .= '</fieldset>';
  1096 + $this->formulario .= $html;
  1097 + return $this;
  1098 + }
  1099 +
  1100 + private function getHijosTable($categoria, &$html, &$num, $prefijo) {
  1101 + $numInterno = $num;
  1102 + $sqlHijos = ' SELECT '
  1103 + . ' c.id_category, c.id_parent, cl.name, s.id_category AS checked, s.margen '
  1104 + . ' FROM '
  1105 + . ' ' . _DB_PREFIX_ . 'category AS c '
  1106 + . ' INNER JOIN ' . _DB_PREFIX_ . 'category_lang AS cl '
  1107 + . ' ON '
  1108 + . ' cl.id_category = c.id_category '
  1109 + . ' AND cl.id_lang = ' . $this->idLang . ' '
  1110 + . ' AND c.id_shop_default = cl.id_shop '
  1111 + . ' AND c.id_shop_default = ' . $this->idShop . ' '
  1112 + . ' LEFT JOIN ' . _DB_PREFIX_ . $prefijo . 'categorias AS s '
  1113 + . ' ON s.id_category = c.id_category '
  1114 + . ' WHERE '
  1115 + . ' c.id_parent=' . $categoria['id_category'];
  1116 +
  1117 + $categoriasHijos = Db::getInstance()->executeS($sqlHijos);
  1118 + $num++;
  1119 + foreach ($categoriasHijos AS $categoriaHijo) {
  1120 + $checked = "";
  1121 + $porcentaje = "";
  1122 + if (!empty($categoriaHijo['checked']) && $categoriaHijo['checked'] != null) {
  1123 + $checked = ' checked="checked"';
  1124 + $porcentaje = $categoriaHijo['margen'];
  1125 + }
  1126 + $numClase = $num;
  1127 + $html .= '<tr class="tr_' . $numClase . '">';
  1128 + $html .= '<td>' . str_repeat("&nbsp;", $num * 2) . $categoriaHijo['name'] . '</td>';
  1129 + $html .= '<td><input type="checkbox" class="catCheckbox" ' . $checked . ' value="' . $categoriaHijo['id_category'] . '" name="categoria[' . $categoriaHijo['id_category'] . '][check]" /></td>';
  1130 + $html .= '<td><input class="catInput" type="text" name="categoria[' . $categoriaHijo['id_category'] . '][margen]" value="' . $porcentaje . '"/></td>';
  1131 + $html .= '</tr>';
  1132 + $this->getHijos($categoriaHijo, $html, $num);
  1133 + }
  1134 + $num = $numInterno;
  1135 + }
  1136 +
  1137 + /**
  1138 + *
  1139 + * @param string $name
  1140 + * @param string $text
  1141 + * @param int $defaultValue
  1142 + * @param string $title
  1143 + * @param boolean $active
  1144 + * @param boolean $chosen
  1145 + * @return $this
  1146 + */
  1147 + public function createSelectCategory($name, $text, $defaultValue, $title = '', $active = true, $chosen = false) {
  1148 + $categories = Category::getHomeCategories($this->idLang, $active);
  1149 + $html = '';
  1150 + $html .= '<div class="form-group">';
  1151 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1152 + $html .= '<span title="' . $title . '">';
  1153 + $html .= $text;
  1154 + $html .= '</span>';
  1155 + $html .= '</label>';
  1156 + $html .= '<div class="col-lg-3">';
  1157 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1158 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione una categoria - ') . '</option>';
  1159 + $categoriaHome = new Category(Configuration::get('PS_HOME_CATEGORY'), $this->idLang);
  1160 + $temp = array();
  1161 + $temp['id_category'] = $categoriaHome->id;
  1162 + $temp['name'] = $categoriaHome->name;
  1163 + array_unshift($categories, $temp);
  1164 + //$categories[] = $temp;
  1165 + foreach ($categories AS $category) {
  1166 + $num = 1;
  1167 + $selected = '';
  1168 + if ($category['id_category'] == $defaultValue) {
  1169 + $selected = ' selected="selected"';
  1170 + }
  1171 + $html .= '<option class="level_1" ' . $selected . ' value="' . $category['id_category'] . '">' . htmlspecialchars(trim($category['name'])) . '</option>';
  1172 + if ($category['id_category'] == $categoriaHome->id) {
  1173 + continue;
  1174 + }
  1175 + $this->getHijos($category['id_category'], $defaultValue, $html, $num, $active);
  1176 + }
  1177 + $html .= '</select>';
  1178 + $html .= '</div>';
  1179 + $html .= '</div>';
  1180 + if ($chosen) {
  1181 + $html .= '<script language="javascript">
  1182 + $(function(){$("#' . $name . '").chosen();});
  1183 + </script>';
  1184 + }
  1185 + $this->formulario .= $html;
  1186 +
  1187 + return $this;
  1188 + }
  1189 +
  1190 + private function getHijos($id_parent, $defaultValue, &$html, &$num, $active) {
  1191 + $numInterno = $num;
  1192 + $categories = Category::getChildren($id_parent, $this->idLang, $active);
  1193 + if (!$categories) {
  1194 + return;
  1195 + }
  1196 + $num++;
  1197 + foreach ($categories AS $category) {
  1198 + $selected = '';
  1199 + if ($category['id_category'] == $defaultValue) {
  1200 + $selected = ' selected="selected"';
  1201 + }
  1202 + $html .= '<option class="level_' . $num . '" ' . $selected . ' value="' . $category['id_category'] . '">' . htmlspecialchars(trim($category['name'])) . '</option>';
  1203 + $this->getHijos($category['id_category'], $defaultValue, $html, $num, $active);
  1204 + }
  1205 + $num = $numInterno;
  1206 + return true;
  1207 + }
  1208 +
  1209 + /**
  1210 + * Crea un upload File.
  1211 + * @param string $name Nombre del boton
  1212 + * @param string $text Texto informativo
  1213 + * @param string $title Titulo
  1214 + * @return $this
  1215 + */
  1216 + public function createFormUploadFile($name, $text, $title = '') {
  1217 + $html = '';
  1218 + $html .= '<div class="form-group">';
  1219 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1220 + $html .= '<span title="' . $title . '">';
  1221 + $html .= $text;
  1222 + $html .= '</span>';
  1223 + $html .= '</label>';
  1224 + $html .= '<div class="col-lg-3">';
  1225 + $html .= '<input type="file" name="' . $name . '" id="' . $name . '">';
  1226 + $html .= '</div>';
  1227 + $html .= '</div>';
  1228 + $this->formulario .= $html;
  1229 +
  1230 + return $this;
  1231 + }
  1232 +
  1233 + /**
  1234 + * Crea una tabla dinamica.
  1235 + * @param string $name Identificador de la tabla.
  1236 + * @param string $table Nombre de la tabla en la base de datos.
  1237 + * @param string[] $campos Nombres de los campos en base de datos.
  1238 + * @param string[] $camposBusqueda Nombres de los campos en base de datos para las busquedas.
  1239 + * @param string $url Ruta base.
  1240 + * @param string $serverfile Archivo de destino en el servidor.
  1241 + * @param string $contenidoCabecera Los "tr" para la cabecera.
  1242 + * @param string[] $codigoFormateadores [id del formateador => Contenido de la funcion formateadora, recibe los parametros column y row].
  1243 + * @param string $codigoEventoLoad Contenido de la funcion del evento load. La tabla viene en la variable grid. Tambien se dispone de e.
  1244 + * @return $this
  1245 + */
  1246 + function createTableAjax($name, $table, $campos, $camposBusqueda, $url, $serverfile, $contenidoCabecera, $codigoFormateadores = array(), $codigoEventoLoad = '') {
  1247 + $html = '<div class="form-group" style="float:none">';
  1248 + $html .= '<style>
  1249 + #' . $name . '-footer { float: none !important; }
  1250 + </style>'
  1251 + . '<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">'
  1252 + . '<link href="' . $url . 'bootgrid/css/jquery.bootgrid.css" rel="stylesheet">
  1253 + <table id="' . $name . '" class="table table-condensed table-hover table-striped">
  1254 + <thead>
  1255 + ' . $contenidoCabecera . '
  1256 + </thead>
  1257 + </table>';
  1258 +
  1259 + $campos = urlencode(serialize($campos));
  1260 + $camposBusqueda = urlencode(serialize($camposBusqueda));
  1261 + $table = urlencode(trim($table));
  1262 + $codigoFormateadoresString = '';
  1263 + foreach ($codigoFormateadores as $idFormateador => $codigoFormateador) {
  1264 + $codigoFormateadoresString .= <<<EOT
  1265 + $idFormateador: function(column, row){
  1266 + $codigoFormateador
  1267 + },
  1268 +EOT;
  1269 + }
  1270 + $html .= '<script src="' . $url . 'bootgrid/js/jquery.bootgrid.min.js"></script>';
  1271 + $html .= <<<EOT
  1272 + <script language="javascript">
  1273 + $(function() {
  1274 + var grid = $("#$name").bootgrid(
  1275 + {
  1276 + ajax:true,
  1277 + post: function() {
  1278 + return {
  1279 + id: "b0df282a-0d67-40e5-8558-c9e93b7befed"
  1280 + };
  1281 + },
  1282 + url: "{$url}bootgrid/php/$serverfile?tabla=$table&campos=$campos&camposBusqueda=$camposBusqueda",
  1283 + formatters: {
  1284 + $codigoFormateadoresString
  1285 + }
  1286 + }).on("loaded.rs.jquery.bootgrid", function(e)
  1287 + {
  1288 + $codigoEventoLoad
  1289 + });
  1290 + });
  1291 + </script>
  1292 +EOT;
  1293 + $html .= '</div>';
  1294 + $this->formulario .= $html;
  1295 +
  1296 + return $this;
  1297 + }
  1298 +
  1299 + private function addExtras($extras) {
  1300 + $html = '';
  1301 + if ($extras && is_array($extras)) {
  1302 + foreach ($extras AS $indice => $valor) {
  1303 + $html .= $indice . '="' . $valor . '"';
  1304 + }
  1305 + }
  1306 + return $html;
  1307 + }
  1308 +
  1309 + public function __toString() {
  1310 + return $this->renderForm(true);
  1311 + }
  1312 +
  1313 + public function createCheckBoxManufacturer($name, $defaultValue) {
  1314 + $manufacturers = Manufacturer::getManufacturers();
  1315 + $html = '';
  1316 + $numElementos = count($manufacturers);
  1317 + $elementos = ceil($numElementos / 3);
  1318 + $intercambio = array_flip($defaultValue);
  1319 + $i = 0;
  1320 + $html .= '<div class="bootstrap">';
  1321 + $html .= '<div class="form-group">';
  1322 + foreach ($manufacturers as $m) {
  1323 + $checked = '';
  1324 + if ($i == 0) {
  1325 + $html .= '<ul class="col-md-4">';
  1326 + }
  1327 + $html .= '<li class="clearfix">';
  1328 + $html .= '<label for="' . $name . '" class="control-label col-lg-6 col-md-6">';
  1329 + $html .= $m['name'];
  1330 + $html .= '</label>';
  1331 + $html .= '<div class="col-lg-4 col-md-4">';
  1332 + if (array_key_exists($m['id_manufacturer'], $intercambio)) {
  1333 + $checked = "checked='checked'";
  1334 + }
  1335 +
  1336 + $html .= '<input type="checkbox" ' . $checked . ' value="' . $m['id_manufacturer'] . '" name="' . $name . '[]" id="' . $name . '">';
  1337 + $html .= '</li>';
  1338 + $i++;
  1339 + if ($i >= $elementos) {
  1340 + $html .= '</ul>';
  1341 + $i = 0;
  1342 + }
  1343 + }
  1344 + $html .= '</ul>';
  1345 + $html .= '</div>';
  1346 + $html .= '</div>';
  1347 +
  1348 + $this->formulario .= $html;
  1349 +
  1350 + return $this;
  1351 + }
  1352 +
  1353 + /**
  1354 + *
  1355 + * @param int $id_lang
  1356 + * @param string $name
  1357 + * @param string $text
  1358 + * @param array $values
  1359 + * @param string $title
  1360 + * @return $this
  1361 + */
  1362 + public function createOptionStatuses($id_lang, $name, $text, $values, $title = '') {
  1363 + $status = OrderState::getOrderStates($id_lang);
  1364 + $html .= '<div class="form-group">';
  1365 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1366 + $html .= '<span title="' . $title . '">';
  1367 + $html .= $text;
  1368 + $html .= '</span>';
  1369 + $html .= '</label>';
  1370 + $html .= '<div class="col-lg-3">';
  1371 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1372 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione el estado - ') . '</option>';
  1373 + foreach ($status AS $statuses) {
  1374 + $idStatus = $statuses['id_order_state'];
  1375 + $marcado = '';
  1376 + foreach ($values as $v) {
  1377 + if ($idStatus == $v) {
  1378 + $marcado = ' checked ';
  1379 + break;
  1380 + }
  1381 + }
  1382 + $html .= '<option " ' . $marcado . ' value="' . $statuses['id_order_state'] . '">' . htmlspecialchars(trim($statuses['name'])) . '</option>';
  1383 + }
  1384 + $html .= '</select>';
  1385 + $html .= '</div>';
  1386 + $html .= '</div>';
  1387 +
  1388 + $this->formulario .= $html;
  1389 + return $this;
  1390 + }
  1391 +
  1392 + /**
  1393 + *
  1394 + * @param string $name
  1395 + * @param string $text
  1396 + * @param int $defaultValue
  1397 + * @param string $title
  1398 + * @return $this
  1399 + */
  1400 + public function createSelectStatuses($idLang, $name, $text, $defaultValue, $title = '') {
  1401 + $status = OrderState::getOrderStates($idLang);
  1402 + $html = '<div class="form-group">';
  1403 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1404 + $html .= '<span title="' . $title . '">';
  1405 + $html .= $text;
  1406 + $html .= '</span>';
  1407 + $html .= '</label>';
  1408 + $html .= '<div class="col-lg-3">';
  1409 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1410 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione el estado - ') . '</option>';
  1411 + foreach ($status AS $statuses) {
  1412 + $selected = '';
  1413 + if ($statuses['id_order_state'] == $defaultValue) {
  1414 + $selected = ' selected="selected"';
  1415 + }
  1416 + $html .= '<option " ' . $selected . ' value="' . $statuses['id_order_state'] . '">' . htmlspecialchars(trim($statuses['name'])) . '</option>';
  1417 + }
  1418 +
  1419 + $html .= '</select>';
  1420 + $html .= '</div>';
  1421 + $html .= '</div>';
  1422 + $this->formulario .= $html;
  1423 +
  1424 + return $this;
  1425 + }
  1426 +
  1427 + /**
  1428 + *
  1429 + * @param string $name
  1430 + * @param string $text
  1431 + * @param int $defaultValue
  1432 + * @param string $title
  1433 + * @return $this
  1434 + */
  1435 + public function createSelectFeatures($idLang, $name, $text, $defaultValue, $title = '') {
  1436 + $features = Feature::getFeatures($idLang);
  1437 +
  1438 + $html = '<div class="form-group">';
  1439 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1440 + $html .= '<span title="' . $title . '">';
  1441 + $html .= $text;
  1442 + $html .= '</span>';
  1443 + $html .= '</label>';
  1444 + $html .= '<div class="col-lg-3">';
  1445 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1446 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione una característica - ') . '</option>';
  1447 + foreach ($features AS $feature) {
  1448 + $selected = '';
  1449 + if ($feature['id_feature'] == $defaultValue) {
  1450 + $selected = ' selected="selected"';
  1451 + }
  1452 + $html .= '<option " ' . $selected . ' value="' . $feature['id_feature'] . '">' . htmlspecialchars(trim($feature['name'])) . '</option>';
  1453 + }
  1454 +
  1455 + $html .= '</select>';
  1456 + $html .= '</div>';
  1457 + $html .= '</div>';
  1458 + $this->formulario .= $html;
  1459 +
  1460 + return $this;
  1461 + }
  1462 +
  1463 + /**
  1464 + *
  1465 + * @param string $name
  1466 + * @param string $text
  1467 + * @param int $defaultValue
  1468 + * @param string $title
  1469 + * @return $this
  1470 + */
  1471 + public function createSelectFeaturesValues($idLang, $name, $text, $defaultValue, $value, $title = '') {
  1472 + $features = FeatureValueCore::getFeatureValuesWithLang($idLang, $value);
  1473 + $html .= '<div class="form-group">';
  1474 + $html .= '<label for="' . $name . '" class="control-label col-lg-3">';
  1475 + $html .= '<span title="' . $title . '">';
  1476 + $html .= $text;
  1477 + $html .= '</span>';
  1478 + $html .= '</label>';
  1479 + $html .= '<div class="col-lg-3">';
  1480 + $html .= '<select name="' . $name . '" id="' . $name . '">';
  1481 + $html .= '<option value="0">' . $this->modulo->l(' - Seleccione un valor de característica - ') . '</option>';
  1482 + $html .= $text;
  1483 + $html .= '' . $this->modulo->l(' - Seleccione un estado - ') . '';
  1484 + foreach ($features AS $feature) {
  1485 + $selected = '';
  1486 + if ($feature['id_feature'] == $defaultValue) {
  1487 + $selected = ' selected="selected"';
  1488 + }
  1489 + $html .= '' . htmlspecialchars(trim($feature['name'])) . '';
  1490 + }
  1491 +
  1492 + $html .= '</select>';
  1493 + $html .= '</div>';
  1494 + $html .= '</div>';
  1495 + $this->formulario .= $html;
  1496 +
  1497 + return $this;
  1498 + }
  1499 +
  1500 + /**
  1501 + * Genera un select a partir de un array asociativo.
  1502 + * @param string $name
  1503 + * @param string $text
  1504 + * @param array $datos
  1505 + * @param string $claveId
  1506 + * @param string $claveValores
  1507 + * @param mixed $selectedValue
  1508 + * @param string $title
  1509 + * @param array $extras
  1510 + * @return $this
  1511 + */
  1512 + public function createFormSelectFromArray($name, $text, $datos, $claveId, $claveValores, $selectedValue, $title = '', $extras = null) {
  1513 + $datosFormateados = array();
  1514 + foreach ($datos as $dato) {
  1515 + $datosFormateados[$dato[$claveId]] = $dato[$claveValores];
  1516 + }
  1517 +
  1518 + return $this->createFormSelect($name, $text, $datosFormateados, $selectedValue, $title, $extras);
  1519 + }
  1520 +
  1521 +}
... ...
imaxAcordeon.php 0 → 100644
  1 +++ a/imaxAcordeon.php
  1 +<?php
  2 +
  3 +/**
  4 + * @version 1.2
  5 + */
  6 +class imaxAcordeon {
  7 +
  8 + function __construct($modulePath) {
  9 + $this->context = Context::getContext();
  10 + $this->_path = $modulePath;
  11 + $this->addCSS('acordeon.css');
  12 + $this->addJS('acordeon.js');
  13 + }
  14 +
  15 + /**
  16 + * CREA UN ACORDEN
  17 + * @param string $titulo Titulo del acordeon
  18 + * @param string $contenido Html con todo el contenido
  19 + * @param boolean $interno True si va a estar dentro de otro acordeon
  20 + */
  21 + function renderAcordeon($titulo, $contenido, $interno = false) {
  22 + $class = 'acordeon';
  23 + if ($interno != false) {
  24 + $class = 'acordeonInterno';
  25 + }
  26 + $html = '';
  27 + $html = '<dl 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 +}
... ...
imaxaddcatbyfeature.php 0 → 100644
  1 +++ a/imaxaddcatbyfeature.php
  1 +<?php
  2 +
  3 +require_once dirname(__FILE__) . '/ComunesImaxAddCatbyFeature.php';
  4 +require_once dirname(__FILE__) . '/FuncionesImaxAddCatbyFeature.php';
  5 +
  6 +class ImaxAddCatbyFeature extends Module {
  7 +
  8 + use ComunesImaxAddCatbyFeature;
  9 +
  10 + var $versionPS;
  11 + var $idShop;
  12 + var $idLang;
  13 + var $idTab;
  14 + private $_html = '';
  15 + private static $funciones;
  16 +
  17 + const prefijo = 'imax_addcat_';
  18 +
  19 + public function __construct() {
  20 + $this->name = 'imaxaddcatbyfeature';
  21 + $this->tab = 'administration';
  22 + $this->version = '1.1';
  23 + $this->author = 'Informax';
  24 + $this->need_instance = 0;
  25 + $this->idManual = '';
  26 + $this->forceCheck = 0;
  27 + $this->sufijo = self::prefijo;
  28 + parent::__construct();
  29 + $this->displayName = $this->l('Informax add to category by feature');
  30 + $this->description = $this->l('Informax add to category by feature');
  31 +
  32 + if (version_compare(_PS_VERSION_, '1.7.0.0 ', '>=')) {
  33 + $this->versionPS = 17;
  34 + $context = Context::getContext();
  35 + $this->idShop = $context->shop->id;
  36 + $this->idLang = $context->language->id;
  37 + } elseif (version_compare(_PS_VERSION_, '1.6.0.0 ', '>=')) {
  38 + $this->versionPS = 16;
  39 + $context = Context::getContext();
  40 + $this->idShop = $context->shop->id;
  41 + $this->idLang = $context->language->id;
  42 + } elseif (version_compare(_PS_VERSION_, '1.5.0.0 ', '>=')) {
  43 + $this->versionPS = 15;
  44 + $context = Context::getContext();
  45 + $this->idShop = $context->shop->id;
  46 + $this->idLang = $context->language->id;
  47 + } else {
  48 + $this->_html .= $this->l("La version minima de funcionamiento para nuestros modulos es la 1.5");
  49 + }
  50 + }
  51 +
  52 + public function install() {
  53 + $directorioAdmin = getcwd();
  54 + if (is_file(dirname(__FILE__) . '/' . $this->name . '_cron.php') && !@copy(dirname(__FILE__) . '/imaxaddcatbyfeature_cron.php', $directorioAdmin . '/imaxaddcatbyfeature_cron.php')) {
  55 + $this->_errors[] = $this->l('Ha fallado al copiar el fichero de cron');
  56 + return false;
  57 + }
  58 + if (is_file(dirname(__FILE__) . '/' . $this->name . '_cron.php.imax') && !@copy(dirname(__FILE__) . '/imaxaddcatbyfeature_cron.php.imax', $directorioAdmin . '/imaxaddcatbyfeature_cron.php.imax')) {
  59 + $this->_errors[] = $this->l('Ha fallado al copiar el fichero de cron ofuscado');
  60 + return false;
  61 + }
  62 +
  63 + include(dirname(__FILE__) . '/configuration.php');
  64 + foreach ($configuracion AS $indice => $valor) {
  65 + if (!Configuration::updateGlobalValue($indice, $valor)) {
  66 + return false;
  67 + }
  68 + }
  69 +
  70 + if (!parent::install())
  71 + return false;
  72 +
  73 + foreach ($hooks as $hook) {
  74 + if (!$this->registerHook($hook)) {
  75 + $this->_errors[] = $this->l('Ha fallado la instalacion del hook:') . ' ' . $hook;
  76 + return false;
  77 + }
  78 + }
  79 +
  80 + include(dirname(__FILE__) . '/sql-install.php');
  81 + foreach ($sql as $s) {
  82 + if (!Db::getInstance()->execute($s)) {
  83 + $this->_errors[] = $this->l("Error al ejecutar") . $s;
  84 + return false;
  85 + }
  86 + }
  87 +
  88 + if (!$this->installTab()) {
  89 + $this->_errors[] = $this->l('Error al instalar el tab');
  90 + return false;
  91 + }
  92 +
  93 + return true;
  94 + }
  95 +
  96 + public function uninstall() {
  97 + if (!parent::uninstall()) {
  98 + return false;
  99 + }
  100 +
  101 + include(dirname(__FILE__) . '/sql-unninstall.php');
  102 + foreach ($sql as $s) {
  103 + if (!Db::getInstance()->execute($s)) {
  104 + $this->_errors[] = $this->l("Error al ejecutar") . $s;
  105 + return false;
  106 + }
  107 + }
  108 + $directorioAdmin = getcwd();
  109 +
  110 + if (!unlink($directorioAdmin . '/imaxaddcatbyfeature_cron.php')) {
  111 + $this->_errors[] = $this->l('Error al borrar el fichero de Cron');
  112 + return false;
  113 + }
  114 +
  115 + if (!$this->uninstallTab()) {
  116 + $this->_errors[] = $this->l('Error al eliminar el tab');
  117 + return false;
  118 + }
  119 +
  120 + include(dirname(__FILE__) . '/configuration.php');
  121 + foreach ($configuracion AS $indice => $valor) {
  122 + if (Configuration::getGlobalValue($indice) !== FALSE) {
  123 + if (!Configuration::deleteByName($indice)) {
  124 + return false;
  125 + }
  126 + }
  127 + }
  128 +
  129 + return true;
  130 + }
  131 +
  132 + public function getContent() {
  133 + $this->getTxtFiles();
  134 + $this->addCSS('css.css');
  135 + //$this->addJS('jquery.dynatree.min.js');
  136 + $this->addJS('functions.js');
  137 + $this->addCSS('../skin/ui.dynatree.css');
  138 + $this->addCSS('publi.css');
  139 + $this->_html .= $this->createHelpHeader();
  140 + if (!empty($_POST))
  141 + $this->_html .= $this->_postProcess();
  142 +
  143 + $this->_displayForm();
  144 + $this->_html .= $this->getModuleFooter();
  145 + return $this->_html;
  146 + }
  147 +
  148 + private function _postProcess() {
  149 + $accion = Tools::getValue("accion");
  150 + $this->idTab = Tools::getValue("idTab");
  151 + $html = "";
  152 + switch ($accion) {
  153 + case 'gestionLicencia':
  154 + $this->forceCheck = 1;
  155 + if (Configuration::updateGlobalValue(self::prefijo . 'LICENCIA', trim(Tools::getValue('licencia')))) {
  156 + $html .= $this->displayConfirmation($this->l('Licencia guardada correctamente.'));
  157 + } else {
  158 + $html .= $this->displayError($this->l('Ha ocurrido un error al guardar la licencia.'));
  159 + }
  160 + break;
  161 +
  162 + case 'configuracion':
  163 + if (Configuration::updateGlobalValue(self::prefijo . 'DIAS_NOVEDAD', trim(Tools::getValue('diasNovedad', 7))) &&
  164 + Configuration::updateGlobalValue(self::prefijo . 'CATS_NOVEDAD', serialize(explode(',', Tools::getValue('catList'))))) {
  165 + $html .= $this->displayConfirmation($this->l('Configuracion guardada correctamente.'));
  166 + } else {
  167 + $html .= $this->displayError($this->l('Ha ocurrido un error al guardar la configuracion.'));
  168 + }
  169 + break;
  170 +
  171 + default:
  172 + break;
  173 + }
  174 +
  175 + return $html;
  176 + }
  177 +
  178 + public function _displayForm() {
  179 + return $this->displayFormTrait(array('_configuracion' => $this->l('Configuracion'), '_mostrarLicencia' => $this->l('Licencia')), '');
  180 + }
  181 +
  182 + private function _mostrarLicencia() {
  183 + return $this->mostrarLicenciaTrait(2);
  184 + }
  185 +
  186 + private function _configuracion() {
  187 + include_once(dirname(__FILE__) . '/functionsForm.php');
  188 + include_once(dirname(__FILE__) . '/imaxAcordeon.php');
  189 + $diasNovedad = Configuration::getGlobalValue(self::prefijo . 'DIAS_NOVEDAD');
  190 + $catsNovedad = unserialize(Configuration::getGlobalValue(self::prefijo . 'CATS_NOVEDAD'));
  191 + $token = Configuration::getGlobalValue(self::prefijo . 'TOKEN');
  192 + $arbol = '';
  193 + //
  194 +
  195 + $acordeon = new imaxAcordeon($this->_path);
  196 +
  197 + $form = new imaxForm($this, $this->_path);
  198 + $form->createHidden("accion", "configuracion");
  199 + $form->createHidden("idTab", "1");
  200 + $form->createHidden("catList", "");
  201 + $form->addToForm($this->l('Buscador:') .$this->crearSelectCategorias());
  202 +
  203 + $form->createSubmitButton('opcionesConfiguracion', $this->l('Guardar'));
  204 + $html = $acordeon->renderAcordeon($this->l('Configuracion'), $form->renderForm());
  205 +
  206 + unset($form);
  207 + $form = new imaxForm($this, $this->_path);
  208 + $form->createHidden("accion", "generar_token");
  209 + $form->createHidden("idTab", "1");
  210 +
  211 + $text = '<b>' . $this->l('ATENCION:') . '</b> ' . $this->l('Si cambia el token, tiene que cambiarlo tambien en las tareas de cron');
  212 + $form->createFormInfomationText($text);
  213 + $urlTienda = self::getUrlAdmin();
  214 + $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'));
  215 + $url = '<a href="' . $urlTienda . $this->name . '_cron.php?token=' . $token . '" target="_blank">' . $urlTienda . $this->name . '_cron.php?token=' . $token . '</a>';
  216 + $form->createFormInfomationText($url);
  217 + $form->createSubmitButton('opcionesConfiguracion', $this->l('Regenerar Token'));
  218 + $html .= $acordeon->renderAcordeon($this->l('Tareas Cron'), $form->renderForm());
  219 +
  220 + return $html;
  221 + }
  222 +
  223 + /**
  224 + * Devuelve las funciones especificas del modulo.
  225 + * @return FuncionesImaxNovedades
  226 + */
  227 + public function getFunciones() {
  228 + if (!self::$funciones) {
  229 + self::$funciones = new FuncionesImaxAddCatbyFeature($this);
  230 + }
  231 +
  232 + return self::$funciones;
  233 + }
  234 +
  235 + public function hookActionObjectProductAddAfter($params) {
  236 + //Si el producto es nuevo le agregamos categorias
  237 + $cats = unserialize(Configuration::getGlobalValue(self::prefijo . 'CATS_NOVEDAD'));
  238 + die();
  239 + /* if($cats && time() - strtotime($params['object']->date_add) <= Configuration::getGlobalValue(self::prefijo.'DIAS_NOVEDAD') * 3600) {
  240 + $this->getFunciones()->agregarCategorias($params['object'], $cats);
  241 + } */
  242 + }
  243 +
  244 + /**
  245 + * Elimina las categorias agregadas a los productos que ya no son novedad.
  246 + * @return int La cantidad de productos procesados.
  247 + */
  248 + /* public function procesar() {
  249 + $dias = (int)Configuration::getGlobalValue(self::prefijo.'DIAS_NOVEDAD');
  250 + $productos = Db::getInstance()->executeS('
  251 + SELECT DISTINCT m.id_product FROM `'._DB_PREFIX_.self::prefijo.'movimiento` m
  252 + INNER JOIN `'._DB_PREFIX_."product` p ON m.id_product = p.id_product
  253 + WHERE DATEDIFF(NOW(), date_add) > $dias");
  254 + foreach($productos as $producto) {
  255 + $this->getFunciones()->deshacerMovimientos(new Product($producto['id_product']));
  256 + }
  257 +
  258 + return count($productos);
  259 + } */
  260 +
  261 + public function procesar() {
  262 + if (!function_exists('procesar_')) {
  263 + $function = $this->getFunction();
  264 + eval(gzuncompress(base64_decode($function)));
  265 + }
  266 + if (function_exists('procesar_')) {
  267 + return procesar_($this);
  268 + }
  269 +
  270 + echo $this->l('Este modulo no tiene una licencia valida: ') . $this->name;
  271 + echo '<br />';
  272 + echo $this->l('Contacte con nosotros para obtener una licencia valida');
  273 + die();
  274 + }
  275 +
  276 + public function l($msg, $modulo = '', $locale = null) {
  277 + if ($modulo == '') {
  278 + $modulo = 'traducciones' . strtolower($this->name);
  279 + }
  280 + return parent::l($msg, $modulo, $locale);
  281 + }
  282 +
  283 + /**
  284 + * Devuelve el formulario para los criterios de desactivacion.
  285 + * @param int $idTab
  286 + * @return imaxForm
  287 + */
  288 + public function getFormCriteriosNovedades($idTab) {
  289 + //Datos para js
  290 + $caracteristicas = Feature::getFeatures($this->idLang);
  291 + $caracteristicasFormateadas = array(0 => $this->modulo->l('- Seleccione uno -'));
  292 + $valoresCaracteristicaFormateados = array();
  293 + foreach ($caracteristicas as $caracteristica) {
  294 + $caracteristicasFormateadas[$caracteristica['id_feature']] = $caracteristica['name'];
  295 + $valoresCaracteristica = FeatureValue::getFeatureValuesWithLang($this->idLang, $caracteristica['id_feature']);
  296 + if ($valoresCaracteristica) {
  297 + foreach ($valoresCaracteristica as $valorCaracteristica) {
  298 + if (!isset($valoresCaracteristicaFormateados[$caracteristica['id_feature']])) {
  299 + $valoresCaracteristicaFormateados[$caracteristica['id_feature']] = array(0 => $this->modulo->l('- Seleccione uno -'));
  300 + }
  301 +
  302 + $valoresCaracteristicaFormateados[$caracteristica['id_feature']][$valorCaracteristica['id_feature_value']] = $valorCaracteristica['value'];
  303 + }
  304 + } else {
  305 + $valoresCaracteristicaFormateados[$caracteristica['id_feature']] = array(0 => $this->modulo->l('- Seleccione uno -'));
  306 + }
  307 + }
  308 + $cats = $form = new imaxForm($this, $this->modulo->getPathUri());
  309 + $form->createHidden("accion", "configurarCriteriosNovedad");
  310 + $form->createHidden("idTab", $idTab);
  311 + $form->createFormTextGroup('numDiasNovedad', $numDiasNovedad, $this->modulo->l('Numero de dias a restar para los productos no nuevos'));
  312 + // $form->addToForm($this->crearSelectCategorias());
  313 + /* $form->addToForm('
  314 + <script>
  315 + var caracteristicasFormateadas = ' . json_encode($caracteristicasFormateadas) . ';
  316 + var valoresCaracteristicaFormateados = ' . json_encode($valoresCaracteristicaFormateados) . ';
  317 + var tiposFormateadosNovedad = ' . json_encode($tiposFormateadosNovedad) . ';
  318 + var criteriosSeleccionadosNovedad = ' . json_encode(unserialize(Configuration::getGlobalValue($this->prefijo . 'CRITERIOS_NOVEDAD'))) . ';
  319 + </script>
  320 + <table id="tablaCriteriosNovedad" class="table">
  321 + <thead>
  322 + <tr><th>' . $this->modulo->l('Caracteristica') . '</th><th>' . $this->modulo->l('Valor caracteristica') . '</th><th>' . $this->modulo->l('Accion') . '</th><th></th></tr>
  323 + </thead>
  324 + <tbody></tbody>
  325 + </table>'); */
  326 +
  327 + $form->createButton('agregarCriterioNovedad', $this->modulo->l('Nuevo'));
  328 + $form->createSubmitButton('save', $this->modulo->l('Guardar'));
  329 + return $form;
  330 + }
  331 +
  332 + /**
  333 + * Devuelve el html de las option de categorias.
  334 + * @return string
  335 + */
  336 + public function crearSelectCategorias() {
  337 + $html = '<option value="0">' . $this->l('Todos') . '</option>';
  338 + //$html .= '<option selected="selected" value="' . Configuration::get('PS_HOME_CATEGORY') . '">' . $this->l('Inicio') . '</option>';
  339 + $num = 0;
  340 + $categorias = $this->getHomeCategories($this->idLang);
  341 + foreach ($categorias AS $categoria) {
  342 + $html .= '<option class="imaxcategory" value="' . $categoria['id_category'] . '" >' . $categoria['name'] ." :: " . $categoria['id_category'] . '</option>';
  343 + $this->getHijos($categoria, $html, $this->idLang, $num);
  344 + }
  345 +
  346 + return $html;
  347 + }
  348 +
  349 + private function getHijos($categoria, &$html, $id_lang = 1, &$num) {
  350 + $numInterno = $num;
  351 + $sqlHijos = "SELECT
  352 + DISTINCT c.id_category, c.id_parent, cl.name
  353 + FROM
  354 + " . _DB_PREFIX_ . "category AS c
  355 + INNER JOIN " . _DB_PREFIX_ . "category_lang AS cl
  356 + ON
  357 + cl.id_category = c.id_category AND
  358 + cl.id_lang = " . $this->idLang . " AND
  359 + cl.id_shop = c.id_shop_default
  360 + WHERE
  361 + c.id_parent=" . $categoria['id_category'] . "
  362 + AND c.id_shop_default = " . $this->idShop;
  363 + $categoriasHijos = Db::getInstance()->executeS($sqlHijos);
  364 + $num++;
  365 + foreach ($categoriasHijos AS $categoriaHijo) {
  366 + $html .= "<option class='imaxcategory_" . $num . "' value='" . $categoriaHijo['id_category'] . "'>" . $categoriaHijo['name'] ." :: " . $categoriaHijo['id_category'] . '</option>';
  367 + $objRespueta = $this->getHijos($categoriaHijo, $html, $id_lang, $num);
  368 + }
  369 + $num = $numInterno;
  370 + }
  371 +
  372 + public function getHomeCategories($id_lang, $id_shop = false, $active = true) {
  373 + if (!Validate::isBool($active))
  374 + die(Tools::displayError());
  375 + $query = 'SELECT '
  376 + . ' c.`id_category`, cl.`name`, cl.`link_rewrite`, cs.`id_shop` '
  377 + . ' FROM '
  378 + . ' `' . _DB_PREFIX_ . 'category` c '
  379 + . ' LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl '
  380 + . ' ON (c.`id_category` = cl.`id_category`) '
  381 + . ' INNER JOIN `' . _DB_PREFIX_ . 'category_shop` cs '
  382 + . ' ON (cs.`id_category` = c.`id_category` ) '
  383 + . ' WHERE '
  384 + . ' `id_lang` = ' . $id_lang . ' '
  385 + . ($id_shop ? ' AND cs.id_shop=' . $id_shop : ' ')
  386 + . ' AND c.`is_root_category` = 1 '
  387 + . ' AND `active` = ' . $active
  388 + . ' GROUP BY c.`id_category` '
  389 + . ' ORDER BY cs.`position` ASC ';
  390 + $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
  391 + return $result;
  392 + }
  393 +
  394 +}
... ...
imaxaddcatbyfeature_cron.php 0 → 100644
  1 +++ a/imaxaddcatbyfeature_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/imaxaddcatbyfeature/imaxaddcatbyfeature.php');
  6 +
  7 +$token = $_GET['token'];
  8 +$tokenLocal = Configuration::get(imaxnovedades::prefijo.'TOKEN');
  9 +if (empty($token) || $token != $tokenLocal) {
  10 + die('Token no valido');
  11 +}
  12 +$imaxaddcatbyfeature = new imaxaddcatbyfeature();
  13 +
  14 +$resultado = $imaxaddcatbyfeature->procesar();
  15 +echo $imaxaddcatbyfeature->l('Se han procesado').' '.$resultado.' '.$imaxaddcatbyfeature->l('producto(s)');
0 16 \ No newline at end of file
... ...
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

6.84 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

4.6 KB

img/manual.png 0 → 100644

495 Bytes

img/manual__.png 0 → 100644

4.6 KB

img/open.png 0 → 100644

881 Bytes

js/functions.js 0 → 100644
  1 +++ a/js/functions.js
  1 +$(function () {
  2 + $("#arbolCategorias").dynatree({
  3 + checkbox: true,
  4 + selectMode: 2,
  5 + debugLevel: 0,
  6 + onDblClick: function (node, event) {
  7 + //Marcar una opción al hacerle doble click
  8 + node.toggleSelect();
  9 + },
  10 + onKeydown: function (node, event) {
  11 + //Marcar la opción activa usando la barra espaciadora
  12 + if (event.which == 32) {
  13 + node.toggleSelect();
  14 + return false;
  15 + }
  16 + },
  17 + // The following options are only required, if we have more than one tree on one page:
  18 + // initId: "treeData",
  19 + cookieId: "dynatree-Cb3",
  20 + idPrefix: "dynatree-Cb3-"
  21 + });
  22 +
  23 + //Expandir los nodos activos
  24 + var arbol = $("#arbolCategorias").dynatree("getTree");
  25 + var marcados = arbol.getSelectedNodes(false);
  26 + for (var i = 0, len = marcados.length; i < len; i++) {
  27 + marcados[i].makeVisible();
  28 + }
  29 +
  30 + //Buscador
  31 + $('input[name="buscador"]').keyup(function () {
  32 + var texto = $(this).val();
  33 + var arbol = $("#arbolCategorias").dynatree("getTree");
  34 + arbol.getRoot().toDict(true, function (dict) {
  35 + if (dict.title !== null) {
  36 + var nodo = arbol.getNodeByKey(dict.key);
  37 + if (texto !== '' && dict.title.toLowerCase().search(texto.toLowerCase()) >= 0) {
  38 + //Lo marcamos
  39 + nodo.makeVisible();
  40 + $(nodo.span).addClass('imax_marcado');
  41 + }
  42 + else {
  43 + //Lo desmarcamos
  44 + $(nodo.span).removeClass('imax_marcado');
  45 + }
  46 + }
  47 + });
  48 + });
  49 +
  50 + //Botón enviar
  51 + $('button[name="opcionesConfiguracion"]').click(function () {
  52 + //Así accedo a los ids de los check marcados
  53 + var arbolCat = $("#arbolCategorias").dynatree("getTree");
  54 + var marcadosCat = arbolCat.getSelectedNodes(false);
  55 +
  56 + var catString = "";
  57 + for (var x in marcadosCat) {
  58 + catString += marcadosCat[x].data.key + ",";
  59 + }
  60 + if (catString.length > 0) {
  61 + catString = catString.substring(0, catString.length - 1);
  62 + }
  63 + $('[name="catList"]').val(catString);
  64 + $(this).parent().submit();
  65 + });
  66 +});
  67 +
  68 +
  69 +
  70 +//Agregamos los criterios de desactivacion
  71 + $('#tablaCriteriosDesactivacion tbody').sortable({
  72 + stop: rellenarOrden
  73 + });
  74 + for(var i in criteriosSeleccionados) {
  75 + agregarFilaCriterioDesactivacion(criteriosSeleccionados[i]['caracteristicaSeleccionada'], criteriosSeleccionados[i]['valorCaracteristicaSeleccionado'], criteriosSeleccionados[i]['tipoSeleccionado']);
  76 + }
  77 + $('button[name="agregarCriterio"]').click(function() {
  78 + agregarFilaCriterioDesactivacion();
  79 + });
  80 +
  81 +
  82 +/**
  83 + * Pone valor al input de orden.
  84 + */
  85 +function rellenarOrden() {
  86 + var orden = 0;
  87 + $('#tablaCriteriosDesactivacion input[name*="orden"]').each(function() {
  88 + $(this).val(orden);
  89 + orden++;
  90 + });
  91 +}
  92 +
  93 +/**
  94 + * Agrega una fila de desactivacion en la tabla.
  95 + * @param {int} caracteristicaSeleccionada
  96 + * @param {int} valorCaracteristicaSeleccionado
  97 + * @param {int} tipoSeleccionado
  98 + */
  99 +function agregarFilaCriterioDesactivacion(caracteristicaSeleccionada, valorCaracteristicaSeleccionado, tipoSeleccionado) {
  100 + var indice = -1;
  101 + $('#tablaCriteriosDesactivacion').find('td:first-child').children('select[name^="desactivacion"]').each(function() {
  102 + var nombreTemp = $(this).attr('name').split('[');
  103 + var indiceTemp = nombreTemp[1].substr(0, nombreTemp[1].length - 1);
  104 + if(indiceTemp > indice) {
  105 + indice = indiceTemp;
  106 + }
  107 + });
  108 + indice++;
  109 +
  110 + var html = '<tr>';
  111 + html += '<td>' + generarSelectDesactivacion('caracteristicaSeleccionada', indice, caracteristicasFormateadas, caracteristicaSeleccionada) + '</td>';
  112 + html += '<td>' + generarSelectDesactivacion('valorCaracteristicaSeleccionado', indice, (typeof valoresCaracteristicaFormateados[caracteristicaSeleccionada] !== 'undefined' ? valoresCaracteristicaFormateados[caracteristicaSeleccionada] : { 0: '- Seleccione uno -' }), valorCaracteristicaSeleccionado) + '</td>';
  113 + html += '<td>' + generarSelectDesactivacion('tipoSeleccionado', indice, tiposFormateados, tipoSeleccionado) + '</td>';
  114 + html += '<td><input type="hidden" name="desactivacion[' + indice + '][orden]" value=""/> <input class="btn btn-default" type="button" name="eliminarFilaCriterioDesactivacion" value="-"/></td>';
  115 + html += '</tr>';
  116 +
  117 + var fila = $(html);
  118 + //Eliminar fila
  119 + fila.find('input[name="eliminarFilaCriterioDesactivacion"]').click(function() {
  120 + $(this).parent().parent().remove();
  121 + });
  122 + //Modificar el select de valores
  123 + fila.find('select[name*="caracteristicaSeleccionada"]').change(function() {
  124 + var parent = fila.find('select[name*="valorCaracteristicaSeleccionado"]').parent();
  125 + var nombreTemp = $(this).attr('name').split('[');
  126 + var indiceTemp = nombreTemp[1].substr(0, nombreTemp[1].length - 1);
  127 + fila.find('select[name*="valorCaracteristicaSeleccionado"]').remove();
  128 + parent.append(generarSelectDesactivacion('valorCaracteristicaSeleccionado', indiceTemp, (typeof valoresCaracteristicaFormateados[$(this).val()] !== 'undefined' ? valoresCaracteristicaFormateados[$(this).val()] : { 0: '- Seleccione uno -' })));
  129 + });
  130 +
  131 + $('#tablaCriteriosDesactivacion tbody').append(fila);
  132 +
  133 + rellenarOrden();
  134 +}
... ...
js/jquery.dynatree.min.js 0 → 100644
  1 +++ a/js/jquery.dynatree.min.js
  1 +/*! jQuery Dynatree Plugin - v1.2.4 - 2013-02-12
  2 +* http://dynatree.googlecode.com/
  3 +* Copyright (c) 2013 Martin Wendt; Licensed MIT, GPL */
  4 +function _log(e,t){if(!_canLog)return;var n=Array.prototype.slice.apply(arguments,[1]),r=new Date,i=r.getHours()+":"+r.getMinutes()+":"+r.getSeconds()+"."+r.getMilliseconds();n[0]=i+" - "+n[0];try{switch(e){case"info":window.console.info.apply(window.console,n);break;case"warn":window.console.warn.apply(window.console,n);break;default:window.console.log.apply(window.console,n)}}catch(s){window.console?s.number===-2146827850&&window.console.log(n.join(", ")):_canLog=!1}}function _checkBrowser(){function n(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}}var e,t;return e=n(navigator.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),t}function logMsg(e){Array.prototype.unshift.apply(arguments,["debug"]),_log.apply(this,arguments)}var _canLog=!0,BROWSER=jQuery.browser||_checkBrowser(),getDynaTreePersistData=null,DTNodeStatus_Error=-1,DTNodeStatus_Loading=1,DTNodeStatus_Ok=0;(function($){function getDtNodeFromElement(e){return alert("getDtNodeFromElement is deprecated"),$.ui.dynatree.getNode(e)}function noop(){}function versionCompare(e,t){var n=(""+e).split("."),r=(""+t).split("."),i=Math.min(n.length,r.length),s,o,u;for(u=0;u<i;u++){s=parseInt(n[u],10),o=parseInt(r[u],10),isNaN(s)&&(s=n[u]),isNaN(o)&&(o=r[u]);if(s==o)continue;return s>o?1:s<o?-1:NaN}return n.length===r.length?0:n.length<r.length?-1:1}function _initDragAndDrop(e){var t=e.options.dnd||null;t&&(t.onDragStart||t.onDrop)&&_registerDnd(),t&&t.onDragStart&&e.$tree.draggable({addClasses:!1,appendTo:"body",containment:!1,delay:0,distance:4,revert:!1,scroll:!0,scrollSpeed:7,scrollSensitivity:10,connectToDynatree:!0,helper:function(e){var t=$.ui.dynatree.getNode(e.target);return t?t.tree._onDragEvent("helper",t,null,e,null,null):"<div></div>"},start:function(e,t){var n=t.helper.data("dtSourceNode");return!!n},_last:null}),t&&t.onDrop&&e.$tree.droppable({addClasses:!1,tolerance:"intersect",greedy:!1,_last:null})}var Class={create:function(){return function(){this.initialize.apply(this,arguments)}}},DynaTreeNode=Class.create();DynaTreeNode.prototype={initialize:function(e,t,n){this.parent=e,this.tree=t,typeof n=="string"&&(n={title:n}),n.key?n.key=""+n.key:n.key="_"+t._nodeCount++,this.data=$.extend({},$.ui.dynatree.nodedatadefaults,n),this.li=null,this.span=null,this.ul=null,this.childList=null,this._isLoading=!1,this.hasSubSel=!1,this.bExpanded=!1,this.bSelected=!1},toString:function(){return"DynaTreeNode<"+this.data.key+">: '"+this.data.title+"'"},toDict:function(e,t){var n=$.extend({},this.data);n.activate=this.tree.activeNode===this,n.focus=this.tree.focusNode===this,n.expand=this.bExpanded,n.select=this.bSelected,t&&t(n);if(e&&this.childList){n.children=[];for(var r=0,i=this.childList.length;r<i;r++)n.children.push(this.childList[r].toDict(!0,t))}else delete n.children;return n},fromDict:function(e){var t=e.children;if(t===undefined){this.data=$.extend(this.data,e),this.render();return}e=$.extend({},e),e.children=undefined,this.data=$.extend(this.data,e),this.removeChildren(),this.addChild(t)},_getInnerHtml:function(){var e=this.tree,t=e.options,n=e.cache,r=this.getLevel(),i=this.data,s="",o;r<t.minExpandLevel?r>1&&(s+=n.tagConnector):this.hasChildren()!==!1?s+=n.tagExpander:s+=n.tagConnector,t.checkbox&&i.hideCheckbox!==!0&&!i.isStatusNode&&(s+=n.tagCheckbox),i.icon?(i.icon.charAt(0)==="/"?o=i.icon:o=t.imagePath+i.icon,s+="<img src='"+o+"' alt='' />"):i.icon!==!1&&(i.iconClass?s+="<span class=' "+i.iconClass+"'></span>":s+=n.tagNodeIcon);var u="";t.onCustomRender&&(u=t.onCustomRender.call(e,this)||"");if(!u){var a=i.tooltip?' title="'+i.tooltip.replace(/\"/g,"&quot;")+'"':"",f=i.href||"#";t.noLink||i.noLink?u='<span style="display:inline-block;" class="'+t.classNames.title+'"'+a+">"+i.title+"</span>":u='<a href="'+f+'" class="'+t.classNames.title+'"'+a+">"+i.title+"</a>"}return s+=u,s},_fixOrder:function(){var e=this.childList;if(!e||!this.ul)return;var t=this.ul.firstChild;for(var n=0,r=e.length-1;n<r;n++){var i=e[n],s=t.dtnode;i!==s?(this.tree.logDebug("_fixOrder: mismatch at index "+n+": "+i+" != "+s),this.ul.insertBefore(i.li,s.li)):t=t.nextSibling}},render:function(e,t){var n=this.tree,r=this.parent,i=this.data,s=n.options,o=s.classNames,u=this.isLastSibling(),a=!1;if(!r&&!this.ul)this.li=this.span=null,this.ul=document.createElement("ul"),s.minExpandLevel>1?this.ul.className=o.container+" "+o.noConnector:this.ul.className=o.container;else if(r){this.li||(a=!0,this.li=document.createElement("li"),this.li.dtnode=this,i.key&&s.generateIds&&(this.li.id=s.idPrefix+i.key),this.span=document.createElement("span"),this.span.className=o.title,this.li.appendChild(this.span),r.ul||(r.ul=document.createElement("ul"),r.ul.style.display="none",r.li.appendChild(r.ul)),r.ul.appendChild(this.li)),this.span.innerHTML=this._getInnerHtml();var f=[];f.push(o.node),i.isFolder&&f.push(o.folder),this.bExpanded&&f.push(o.expanded),this.hasChildren()!==!1&&f.push(o.hasChildren),i.isLazy&&this.childList===null&&f.push(o.lazy),u&&f.push(o.lastsib),this.bSelected&&f.push(o.selected),this.hasSubSel&&f.push(o.partsel),n.activeNode===this&&f.push(o.active),i.addClass&&f.push(i.addClass),f.push(o.combinedExpanderPrefix+(this.bExpanded?"e":"c")+(i.isLazy&&this.childList===null?"d":"")+(u?"l":"")),f.push(o.combinedIconPrefix+(this.bExpanded?"e":"c")+(i.isFolder?"f":"")),this.span.className=f.join(" "),this.li.className=u?o.lastsib:"",a&&s.onCreate&&s.onCreate.call(n,this,this.span),s.onRender&&s.onRender.call(n,this,this.span)}if((this.bExpanded||t===!0)&&this.childList){for(var l=0,c=this.childList.length;l<c;l++)this.childList[l].render(!1,t);this._fixOrder()}if(this.ul){var h=this.ul.style.display==="none",p=!!this.bExpanded;if(e&&s.fx&&h===p){var d=s.fx.duration||200;$(this.ul).animate(s.fx,d)}else this.ul.style.display=this.bExpanded||!r?"":"none"}},getKeyPath:function(e){var t=[];return this.visitParents(function(e){e.parent&&t.unshift(e.data.key)},!e),"/"+t.join(this.tree.options.keyPathSeparator)},getParent:function(){return this.parent},getChildren:function(){return this.hasChildren()===undefined?undefined:this.childList},hasChildren:function(){if(this.data.isLazy)return this.childList===null||this.childList===undefined?undefined:this.childList.length===0?!1:this.childList.length===1&&this.childList[0].isStatusNode()?undefined:!0;return!!this.childList},isFirstSibling:function(){var e=this.parent;return!e||e.childList[0]===this},isLastSibling:function(){var e=this.parent;return!e||e.childList[e.childList.length-1]===this},isLoading:function(){return!!this._isLoading},getPrevSibling:function(){if(!this.parent)return null;var e=this.parent.childList;for(var t=1,n=e.length;t<n;t++)if(e[t]===this)return e[t-1];return null},getNextSibling:function(){if(!this.parent)return null;var e=this.parent.childList;for(var t=0,n=e.length-1;t<n;t++)if(e[t]===this)return e[t+1];return null},isStatusNode:function(){return this.data.isStatusNode===!0},isChildOf:function(e){return this.parent&&this.parent===e},isDescendantOf:function(e){if(!e)return!1;var t=this.parent;while(t){if(t===e)return!0;t=t.parent}return!1},countChildren:function(){var e=this.childList;if(!e)return 0;var t=e.length;for(var n=0,r=t;n<r;n++){var i=e[n];t+=i.countChildren()}return t},sortChildren:function(e,t){var n=this.childList;if(!n)return;e=e||function(e,t){var n=e.data.title.toLowerCase(),r=t.data.title.toLowerCase();return n===r?0:n>r?1:-1},n.sort(e);if(t)for(var r=0,i=n.length;r<i;r++)n[r].childList&&n[r].sortChildren(e,"$norender$");t!=="$norender$"&&this.render()},_setStatusNode:function(e){var t=this.childList?this.childList[0]:null;if(!e){if(t&&t.isStatusNode()){try{this.ul&&(this.ul.removeChild(t.li),t.li=null)}catch(n){}this.childList.length===1?this.childList=[]:this.childList.shift()}}else t?(e.isStatusNode=!0,e.key="_statusNode",t.data=e,t.render()):(e.isStatusNode=!0,e.key="_statusNode",t=this.addChild(e))},setLazyNodeStatus:function(e,t){var n=t&&t.tooltip?t.tooltip:null,r=t&&t.info?" ("+t.info+")":"";switch(e){case DTNodeStatus_Ok:this._setStatusNode(null),$(this.span).removeClass(this.tree.options.classNames.nodeLoading),this._isLoading=!1,this.tree.options.autoFocus&&(this===this.tree.tnRoot&&this.childList&&this.childList.length>0?this.childList[0].focus():this.focus());break;case DTNodeStatus_Loading:this._isLoading=!0,$(this.span).addClass(this.tree.options.classNames.nodeLoading),this.parent||this._setStatusNode({title:this.tree.options.strings.loading+r,tooltip:n,addClass:this.tree.options.classNames.nodeWait});break;case DTNodeStatus_Error:this._isLoading=!1,this._setStatusNode({title:this.tree.options.strings.loadError+r,tooltip:n,addClass:this.tree.options.classNames.nodeError});break;default:throw"Bad LazyNodeStatus: '"+e+"'."}},_parentList:function(e,t){var n=[],r=t?this:this.parent;while(r)(e||r.parent)&&n.unshift(r),r=r.parent;return n},getLevel:function(){var e=0,t=this.parent;while(t)e++,t=t.parent;return e},_getTypeForOuterNodeEvent:function(e){var t=this.tree.options.classNames,n=e.target;if(n.className.indexOf(t.node)<0)return null;var r=e.pageX-n.offsetLeft,i=e.pageY-n.offsetTop;for(var s=0,o=n.childNodes.length;s<o;s++){var u=n.childNodes[s],a=u.offsetLeft-n.offsetLeft,f=u.offsetTop-n.offsetTop,l=u.clientWidth,c=u.clientHeight;if(r>=a&&r<=a+l&&i>=f&&i<=f+c){if(u.className==t.title)return"title";if(u.className==t.expander)return"expander";if(u.className==t.checkbox)return"checkbox";if(u.className==t.nodeIcon)return"icon"}}return"prefix"},getEventTargetType:function(e){var t=e&&e.target?e.target.className:"",n=this.tree.options.classNames;return t===n.title?"title":t===n.expander?"expander":t===n.checkbox?"checkbox":t===n.nodeIcon?"icon":t===n.empty||t===n.vline||t===n.connector?"prefix":t.indexOf(n.node)>=0?this._getTypeForOuterNodeEvent(e):null},isVisible:function(){var e=this._parentList(!0,!1);for(var t=0,n=e.length;t<n;t++)if(!e[t].bExpanded)return!1;return!0},makeVisible:function(){var e=this._parentList(!0,!1);for(var t=0,n=e.length;t<n;t++)e[t]._expand(!0)},focus:function(){this.makeVisible();try{$(this.span).find(">a").focus()}catch(e){}},isFocused:function(){return this.tree.tnFocused===this},_activate:function(e,t){this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o",e,t,this);var n=this.tree.options;if(this.data.isStatusNode)return;if(t&&n.onQueryActivate&&n.onQueryActivate.call(this.tree,e,this)===!1)return;if(e){if(this.tree.activeNode){if(this.tree.activeNode===this)return;this.tree.activeNode.deactivate()}n.activeVisible&&this.makeVisible(),this.tree.activeNode=this,n.persist&&$.cookie(n.cookieId+"-active",this.data.key,n.cookie),this.tree.persistence.activeKey=this.data.key,$(this.span).addClass(n.classNames.active),t&&n.onActivate&&n.onActivate.call(this.tree,this)}else if(this.tree.activeNode===this){if(n.onQueryActivate&&n.onQueryActivate.call(this.tree,!1,this)===!1)return;$(this.span).removeClass(n.classNames.active),n.persist&&$.cookie(n.cookieId+"-active","",n.cookie),this.tree.persistence.activeKey=null,this.tree.activeNode=null,t&&n.onDeactivate&&n.onDeactivate.call(this.tree,this)}},activate:function(){this._activate(!0,!0)},activateSilently:function(){this._activate(!0,!1)},deactivate:function(){this._activate(!1,!0)},isActive:function(){return this.tree.activeNode===this},_userActivate:function(){var e=!0,t=!1;if(this.data.isFolder)switch(this.tree.options.clickFolderMode){case 2:e=!1,t=!0;break;case 3:e=t=!0}this.parent===null&&(t=!1),t&&(this.toggleExpand(),this.focus()),e&&this.activate()},_setSubSel:function(e){e?(this.hasSubSel=!0,$(this.span).addClass(this.tree.options.classNames.partsel)):(this.hasSubSel=!1,$(this.span).removeClass(this.tree.options.classNames.partsel))},_updatePartSelectionState:function(){var e;if(!this.hasChildren())return e=this.bSelected&&!this.data.unselectable&&!this.data.isStatusNode,this._setSubSel(!1),e;var t,n,r=this.childList,i=!0,s=!0;for(t=0,n=r.length;t<n;t++){var o=r[t],u=o._updatePartSelectionState();u!==!1&&(s=!1),u!==!0&&(i=!1)}return i?e=!0:s?e=!1:e=undefined,this._setSubSel(e===undefined),this.bSelected=e===!0,e},_fixSelectionState:function(){var e,t,n;if(this.bSelected){this.visit(function(e){e.parent._setSubSel(!0),e.data.unselectable||e._select(!0,!1,!1)}),e=this.parent;while(e){e._setSubSel(!0);var r=!0;for(t=0,n=e.childList.length;t<n;t++){var i=e.childList[t];if(!i.bSelected&&!i.data.isStatusNode&&!i.data.unselectable){r=!1;break}}r&&e._select(!0,!1,!1),e=e.parent}}else{this._setSubSel(!1),this.visit(function(e){e._setSubSel(!1),e._select(!1,!1,!1)}),e=this.parent;while(e){e._select(!1,!1,!1);var s=!1;for(t=0,n=e.childList.length;t<n;t++)if(e.childList[t].bSelected||e.childList[t].hasSubSel){s=!0;break}e._setSubSel(s),e=e.parent}}},_select:function(e,t,n){var r=this.tree.options;if(this.data.isStatusNode)return;if(this.bSelected===e)return;if(t&&r.onQuerySelect&&r.onQuerySelect.call(this.tree,e,this)===!1)return;r.selectMode==1&&e&&this.tree.visit(function(e){if(e.bSelected)return e._select(!1,!1,!1),!1}),this.bSelected=e,e?(r.persist&&this.tree.persistence.addSelect(this.data.key),$(this.span).addClass(r.classNames.selected),n&&r.selectMode===3&&this._fixSelectionState(),t&&r.onSelect&&r.onSelect.call(this.tree,!0,this)):(r.persist&&this.tree.persistence.clearSelect(this.data.key),$(this.span).removeClass(r.classNames.selected),n&&r.selectMode===3&&this._fixSelectionState(),t&&r.onSelect&&r.onSelect.call(this.tree,!1,this))},select:function(e){return this.data.unselectable?this.bSelected:this._select(e!==!1,!0,!0)},toggleSelect:function(){return this.select(!this.bSelected)},isSelected:function(){return this.bSelected},isLazy:function(){return!!this.data.isLazy},_loadContent:function(){try{var e=this.tree.options;this.tree.logDebug("_loadContent: start - %o",this),this.setLazyNodeStatus(DTNodeStatus_Loading),!0===e.onLazyRead.call(this.tree,this)&&(this.setLazyNodeStatus(DTNodeStatus_Ok),this.tree.logDebug("_loadContent: succeeded - %o",this))}catch(t){this.tree.logWarning("_loadContent: failed - %o",t),this.setLazyNodeStatus(DTNodeStatus_Error,{tooltip:""+t})}},_expand:function(e,t){if(this.bExpanded===e){this.tree.logDebug("dtnode._expand(%o) IGNORED - %o",e,this);return}this.tree.logDebug("dtnode._expand(%o) - %o",e,this);var n=this.tree.options;if(!e&&this.getLevel()<n.minExpandLevel){this.tree.logDebug("dtnode._expand(%o) prevented collapse - %o",e,this);return}if(n.onQueryExpand&&n.onQueryExpand.call(this.tree,e,this)===!1)return;this.bExpanded=e,n.persist&&(e?this.tree.persistence.addExpand(this.data.key):this.tree.persistence.clearExpand(this.data.key));var r=(!this.data.isLazy||this.childList!==null)&&!this._isLoading&&!t;this.render(r);if(this.bExpanded&&this.parent&&n.autoCollapse){var i=this._parentList(!1,!0);for(var s=0,o=i.length;s<o;s++)i[s].collapseSiblings()}n.activeVisible&&this.tree.activeNode&&!this.tree.activeNode.isVisible()&&this.tree.activeNode.deactivate();if(e&&this.data.isLazy&&this.childList===null&&!this._isLoading){this._loadContent();return}n.onExpand&&n.onExpand.call(this.tree,e,this)},isExpanded:function(){return this.bExpanded},expand:function(e){e=e!==!1;if(!this.childList&&!this.data.isLazy&&e)return;if(this.parent===null&&!e)return;this._expand(e)},scheduleAction:function(e,t){this.tree.timer&&(clearTimeout(this.tree.timer),this.tree.logDebug("clearTimeout(%o)",this.tree.timer));var n=this;switch(e){case"cancel":break;case"expand":this.tree.timer=setTimeout(function(){n.tree.logDebug("setTimeout: trigger expand"),n.expand(!0)},t);break;case"activate":this.tree.timer=setTimeout(function(){n.tree.logDebug("setTimeout: trigger activate"),n.activate()},t);break;default:throw"Invalid mode "+e}this.tree.logDebug("setTimeout(%s, %s): %s",e,t,this.tree.timer)},toggleExpand:function(){this.expand(!this.bExpanded)},collapseSiblings:function(){if(this.parent===null)return;var e=this.parent.childList;for(var t=0,n=e.length;t<n;t++)e[t]!==this&&e[t].bExpanded&&e[t]._expand(!1)},_onClick:function(e){var t=this.getEventTargetType(e);if(t==="expander")this.toggleExpand(),this.focus();else if(t==="checkbox")this.toggleSelect(),this.focus();else{this._userActivate();var n=this.span.getElementsByTagName("a");if(!n[0])return!0;BROWSER.msie&&parseInt(BROWSER.version,10)<9||n[0].focus()}e.preventDefault()},_onDblClick:function(e){},_onKeydown:function(e){var t=!0,n;switch(e.which){case 107:case 187:this.bExpanded||this.toggleExpand();break;case 109:case 189:this.bExpanded&&this.toggleExpand();break;case 32:this._userActivate();break;case 8:this.parent&&this.parent.focus();break;case 37:this.bExpanded?(this.toggleExpand(),this.focus()):this.parent&&this.parent.parent&&this.parent.focus();break;case 39:!this.bExpanded&&(this.childList||this.data.isLazy)?(this.toggleExpand(),this.focus()):this.childList&&this.childList[0].focus();break;case 38:n=this.getPrevSibling();while(n&&n.bExpanded&&n.childList)n=n.childList[n.childList.length-1];!n&&this.parent&&this.parent.parent&&(n=this.parent),n&&n.focus();break;case 40:if(this.bExpanded&&this.childList)n=this.childList[0];else{var r=this._parentList(!1,!0);for(var i=r.length-1;i>=0;i--){n=r[i].getNextSibling();if(n)break}}n&&n.focus();break;default:t=!1}t&&e.preventDefault()},_onKeypress:function(e){},_onFocus:function(e){var t=this.tree.options;if(e.type=="blur"||e.type=="focusout")t.onBlur&&t.onBlur.call(this.tree,this),this.tree.tnFocused&&$(this.tree.tnFocused.span).removeClass(t.classNames.focused),this.tree.tnFocused=null,t.persist&&$.cookie(t.cookieId+"-focus","",t.cookie);else if(e.type=="focus"||e.type=="focusin")this.tree.tnFocused&&this.tree.tnFocused!==this&&(this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o",this.tree.tnFocused),$(this.tree.tnFocused.span).removeClass(t.classNames.focused)),this.tree.tnFocused=this,t.onFocus&&t.onFocus.call(this.tree,this),$(this.tree.tnFocused.span).addClass(t.classNames.focused),t.persist&&$.cookie(t.cookieId+"-focus",this.data.key,t.cookie)},visit:function(e,t){var n=!0;if(t===!0){n=e(this);if(n===!1||n=="skip")return n}if(this.childList)for(var r=0,i=this.childList.length;r<i;r++){n=this.childList[r].visit(e,!0);if(n===!1)break}return n},visitParents:function(e,t){if(t&&e(this)===!1)return!1;var n=this.parent;while(n){if(e(n)===!1)return!1;n=n.parent}return!0},remove:function(){if(this===this.tree.root)throw"Cannot remove system root";return this.parent.removeChild(this)},removeChild:function(e){var t=this.childList;if(t.length==1){if(e!==t[0])throw"removeChild: invalid child";return this.removeChildren()}e===this.tree.activeNode&&e.deactivate(),this.tree.options.persist&&(e.bSelected&&this.tree.persistence.clearSelect(e.data.key),e.bExpanded&&this.tree.persistence.clearExpand(e.data.key)),e.removeChildren(!0),this.ul&&this.ul.removeChild(e.li);for(var n=0,r=t.length;n<r;n++)if(t[n]===e){this.childList.splice(n,1);break}},removeChildren:function(e,t){this.tree.logDebug("%s.removeChildren(%o)",this,e);var n=this.tree,r=this.childList;if(r){for(var i=0,s=r.length;i<s;i++){var o=r[i];o===n.activeNode&&!t&&o.deactivate(),this.tree.options.persist&&!t&&(o.bSelected&&this.tree.persistence.clearSelect(o.data.key),o.bExpanded&&this.tree.persistence.clearExpand(o.data.key)),o.removeChildren(!0,t),this.ul&&$("li",$(this.ul)).remove()}this.childList=null}e||(this._isLoading=!1,this.render())},setTitle:function(e){this.fromDict({title:e})},reload:function(e){throw"Use reloadChildren() instead"},reloadChildren:function(e){if(this.parent===null)throw"Use tree.reload() instead";if(!this.data.isLazy)throw"node.reloadChildren() requires lazy nodes.";if(e){var t=this,n="nodeLoaded.dynatree."+this.tree.$tree.attr("id")+"."+this.data.key;this.tree.$tree.bind(n,function(r,i,s){t.tree.$tree.unbind(n),t.tree.logDebug("loaded %o, %o, %o",r,i,s);if(i!==t)throw"got invalid load event";e.call(t.tree,i,s)})}this.removeChildren(),this._loadContent()},_loadKeyPath:function(e,t){var n=this.tree;n.logDebug("%s._loadKeyPath(%s)",this,e);if(e==="")throw"Key path must not be empty";var r=e.split(n.options.keyPathSeparator);if(r[0]==="")throw"Key path must be relative (don't start with '/')";var i=r.shift();if(this.childList)for(var s=0,o=this.childList.length;s<o;s++){var u=this.childList[s];if(u.data.key===i){if(r.length===0)t.call(n,u,"ok");else if(!u.data.isLazy||u.childList!==null&&u.childList!==undefined)t.call(n,u,"loaded"),u._loadKeyPath(r.join(n.options.keyPathSeparator),t);else{n.logDebug("%s._loadKeyPath(%s) -> reloading %s...",this,e,u);var a=this;u.reloadChildren(function(i,s){s?(n.logDebug("%s._loadKeyPath(%s) -> reloaded %s.",i,e,i),t.call(n,u,"loaded"),i._loadKeyPath(r.join(n.options.keyPathSeparator),t)):(n.logWarning("%s._loadKeyPath(%s) -> reloadChildren() failed.",a,e),t.call(n,u,"error"))})}return}}t.call(n,undefined,"notfound",i,r.length===0),n.logWarning("Node not found: "+i);return},resetLazy:function(){if(this.parent===null)throw"Use tree.reload() instead";if(!this.data.isLazy)throw"node.resetLazy() requires lazy nodes.";this.expand(!1),this.removeChildren()},_addChildNode:function(e,t){var n=this.tree,r=n.options,i=n.persistence;e.parent=this,this.childList===null?this.childList=[]:t||this.childList.length>0&&$(this.childList[this.childList.length-1].span).removeClass(r.classNames.lastsib);if(t){var s=$.inArray(t,this.childList);if(s<0)throw"<beforeNode> must be a child of <this>";this.childList.splice(s,0,e)}else this.childList.push(e);var o=n.isInitializing();r.persist&&i.cookiesFound&&o?(i.activeKey===e.data.key&&(n.activeNode=e),i.focusedKey===e.data.key&&(n.focusNode=e),e.bExpanded=$.inArray(e.data.key,i.expandedKeyList)>=0,e.bSelected=$.inArray(e.data.key,i.selectedKeyList)>=0):(e.data.activate&&(n.activeNode=e,r.persist&&(i.activeKey=e.data.key)),e.data.focus&&(n.focusNode=e,r.persist&&(i.focusedKey=e.data.key)),e.bExpanded=e.data.expand===!0,e.bExpanded&&r.persist&&i.addExpand(e.data.key),e.bSelected=e.data.select===!0,e.bSelected&&r.persist&&i.addSelect(e.data.key)),r.minExpandLevel>=e.getLevel()&&(this.bExpanded=!0);if(e.bSelected&&r.selectMode==3){var u=this;while(u)u.hasSubSel||u._setSubSel(!0),u=u.parent}return n.bEnableUpdate&&this.render(),e},addChild:function(e,t){if(typeof e=="string")throw"Invalid data type for "+e;if(!e||e.length===0)return;if(e instanceof DynaTreeNode)return this._addChildNode(e,t);e.length||(e=[e]);var n=this.tree.enableUpdate(!1),r=null;for(var i=0,s=e.length;i<s;i++){var o=e[i],u=this._addChildNode(new DynaTreeNode(this,this.tree,o),t);r||(r=u),o.children&&u.addChild(o.children,null)}return this.tree.enableUpdate(n),r},append:function(e){return this.tree.logWarning("node.append() is deprecated (use node.addChild() instead)."),this.addChild(e,null)},appendAjax:function(e){var t=this;this.removeChildren(!1,!0),this.setLazyNodeStatus(DTNodeStatus_Loading);if(e.debugLazyDelay){var n=e.debugLazyDelay;e.debugLazyDelay=0,this.tree.logInfo("appendAjax: waiting for debugLazyDelay "+n),setTimeout(function(){t.appendAjax(e)},n);return}var r=e.success,i=e.error,s="nodeLoaded.dynatree."+this.tree.$tree.attr("id")+"."+this.data.key,o=$.extend({},this.tree.options.ajaxDefaults,e,{success:function(e,n,i){var u=t.tree.phase;t.tree.phase="init",o.postProcess?e=o.postProcess.call(this,e,this.dataType):e&&e.hasOwnProperty("d")&&(e=typeof e.d=="string"?$.parseJSON(e.d):e.d),(!$.isArray(e)||e.length!==0)&&t.addChild(e,null),t.tree.phase="postInit",r&&r.call(o,t,e,n),t.tree.logDebug("trigger "+s),t.tree.$tree.trigger(s,[t,!0]),t.tree.phase=u,t.setLazyNodeStatus(DTNodeStatus_Ok),$.isArray(e)&&e.length===0&&(t.childList=[],t.render())},error:function(e,n,r){t.tree.logWarning("appendAjax failed:",n,":\n",e,"\n",r),i&&i.call(o,t,e,n,r),t.tree.$tree.trigger(s,[t,!1]),t.setLazyNodeStatus(DTNodeStatus_Error,{info:n,tooltip:""+r})}});$.ajax(o)},move:function(e,t){var n;if(this===e)return;if(!this.parent)throw"Cannot move system root";if(t===undefined||t=="over")t="child";var r=this.parent,i=t==="child"?e:e.parent;if(i.isDescendantOf(this))throw"Cannot move a node to it's own descendant";if(this.parent.childList.length==1)this.parent.childList=this.parent.data.isLazy?[]:null,this.parent.bExpanded=!1;else{n=$.inArray(this,this.parent.childList);if(n<0)throw"Internal error";this.parent.childList.splice(n,1)}this.parent.ul&&this.parent.ul.removeChild(this.li),this.parent=i;if(i.hasChildren())switch(t){case"child":i.childList.push(this);break;case"before":n=$.inArray(e,i.childList);if(n<0)throw"Internal error";i.childList.splice(n,0,this);break;case"after":n=$.inArray(e,i.childList);if(n<0)throw"Internal error";i.childList.splice(n+1,0,this);break;default:throw"Invalid mode "+t}else i.childList=[this];i.ul||(i.ul=document.createElement("ul"),i.ul.style.display="none",i.li.appendChild(i.ul)),this.li&&i.ul.appendChild(this.li);if(this.tree!==e.tree)throw this.visit(function(t){t.tree=e.tree},null,!0),"Not yet implemented.";r.isDescendantOf(i)||r.render(),i.isDescendantOf(r)||i.render()},lastentry:undefined};var DynaTreeStatus=Class.create();DynaTreeStatus._getTreePersistData=function(e,t){var n=new DynaTreeStatus(e,t);return n.read(),n.toDict()},getDynaTreePersistData=DynaTreeStatus._getTreePersistData,DynaTreeStatus.prototype={initialize:function(e,t){e===undefined&&(e=$.ui.dynatree.prototype.options.cookieId),t=$.extend({},$.ui.dynatree.prototype.options.cookie,t),this.cookieId=e,this.cookieOpts=t,this.cookiesFound=undefined,this.activeKey=null,this.focusedKey=null,this.expandedKeyList=null,this.selectedKeyList=null},_log:function(e){Array.prototype.unshift.apply(arguments,["debug"]),_log.apply(this,arguments)},read:function(){this.cookiesFound=!1;var e=$.cookie(this.cookieId+"-active");this.activeKey=e===null?"":e,e!==null&&(this.cookiesFound=!0),e=$.cookie(this.cookieId+"-focus"),this.focusedKey=e===null?"":e,e!==null&&(this.cookiesFound=!0),e=$.cookie(this.cookieId+"-expand"),this.expandedKeyList=e===null?[]:e.split(","),e!==null&&(this.cookiesFound=!0),e=$.cookie(this.cookieId+"-select"),this.selectedKeyList=e===null?[]:e.split(","),e!==null&&(this.cookiesFound=!0)},write:function(){$.cookie(this.cookieId+"-active",this.activeKey===null?"":this.activeKey,this.cookieOpts),$.cookie(this.cookieId+"-focus",this.focusedKey===null?"":this.focusedKey,this.cookieOpts),$.cookie(this.cookieId+"-expand",this.expandedKeyList===null?"":this.expandedKeyList.join(","),this.cookieOpts),$.cookie(this.cookieId+"-select",this.selectedKeyList===null?"":this.selectedKeyList.join(","),this.cookieOpts)},addExpand:function(e){$.inArray(e,this.expandedKeyList)<0&&(this.expandedKeyList.push(e),$.cookie(this.cookieId+"-expand",this.expandedKeyList.join(","),this.cookieOpts))},clearExpand:function(e){var t=$.inArray(e,this.expandedKeyList);t>=0&&(this.expandedKeyList.splice(t,1),$.cookie(this.cookieId+"-expand",this.expandedKeyList.join(","),this.cookieOpts))},addSelect:function(e){$.inArray(e,this.selectedKeyList)<0&&(this.selectedKeyList.push(e),$.cookie(this.cookieId+"-select",this.selectedKeyList.join(","),this.cookieOpts))},clearSelect:function(e){var t=$.inArray(e,this.selectedKeyList);t>=0&&(this.selectedKeyList.splice(t,1),$.cookie(this.cookieId+"-select",this.selectedKeyList.join(","),this.cookieOpts))},isReloading:function(){return this.cookiesFound===!0},toDict:function(){return{cookiesFound:this.cookiesFound,activeKey:this.activeKey,focusedKey:this.activeKey,expandedKeyList:this.expandedKeyList,selectedKeyList:this.selectedKeyList}},lastentry:undefined};var DynaTree=Class.create();DynaTree.version="$Version:$",DynaTree.prototype={initialize:function(e){this.phase="init",this.$widget=e,this.options=e.options,this.$tree=e.element,this.timer=null,this.divTree=this.$tree.get(0),_initDragAndDrop(this)},_load:function(e){var t=this.$widget,n=this.options,r=this;this.bEnableUpdate=!0,this._nodeCount=1,this.activeNode=null,this.focusNode=null,n.rootVisible!==undefined&&this.logWarning("Option 'rootVisible' is no longer supported."),n.minExpandLevel<1&&(this.logWarning("Option 'minExpandLevel' must be >= 1."),n.minExpandLevel=1),n.classNames!==$.ui.dynatree.prototype.options.classNames&&(n.classNames=$.extend({},$.ui.dynatree.prototype.options.classNames,n.classNames)),n.ajaxDefaults!==$.ui.dynatree.prototype.options.ajaxDefaults&&(n.ajaxDefaults=$.extend({},$.ui.dynatree.prototype.options.ajaxDefaults,n.ajaxDefaults)),n.dnd!==$.ui.dynatree.prototype.options.dnd&&(n.dnd=$.extend({},$.ui.dynatree.prototype.options.dnd,n.dnd)),n.imagePath||$("script").each(function(){var e=/.*dynatree[^\/]*\.js$/i;if(this.src.search(e)>=0)return this.src.indexOf("/")>=0?n.imagePath=this.src.slice(0,this.src.lastIndexOf("/"))+"/skin/":n.imagePath="skin/",r.logDebug("Guessing imagePath from '%s': '%s'",this.src,n.imagePath),!1}),this.persistence=new DynaTreeStatus(n.cookieId,n.cookie),n.persist&&($.cookie||_log("warn","Please include jquery.cookie.js to use persistence."),this.persistence.read()),this.logDebug("DynaTree.persistence: %o",this.persistence.toDict()),this.cache={tagEmpty:"<span class='"+n.classNames.empty+"'></span>",tagVline:"<span class='"+n.classNames.vline+"'></span>",tagExpander:"<span class='"+n.classNames.expander+"'></span>",tagConnector:"<span class='"+n.classNames.connector+"'></span>",tagNodeIcon:"<span class='"+n.classNames.nodeIcon+"'></span>",tagCheckbox:"<span class='"+n.classNames.checkbox+"'></span>",lastentry:undefined},(n.children||n.initAjax&&n.initAjax.url||n.initId)&&$(this.divTree).empty();var i=this.$tree.find(">ul:first").hide();this.tnRoot=new DynaTreeNode(null,this,{}),this.tnRoot.bExpanded=!0,this.tnRoot.render(),this.divTree.appendChild(this.tnRoot.ul);var s=this.tnRoot,o=n.persist&&this.persistence.isReloading(),u=!1,a=this.enableUpdate(!1);this.logDebug("Dynatree._load(): read tree structure..."),n.children?s.addChild(n.children):n.initAjax&&n.initAjax.url?(u=!0,s.data.isLazy=!0,this._reloadAjax(e)):n.initId?this._createFromTag(s,$("#"+n.initId)):(this._createFromTag(s,i),i.remove()),this._checkConsistency(),!u&&n.selectMode==3&&s._updatePartSelectionState(),this.logDebug("Dynatree._load(): render nodes..."),this.enableUpdate(a),this.logDebug("Dynatree._load(): bind events..."),this.$widget.bind(),this.logDebug("Dynatree._load(): postInit..."),this.phase="postInit",n.persist&&this.persistence.write(),this.focusNode&&this.focusNode.isVisible()&&(this.logDebug("Focus on init: %o",this.focusNode),this.focusNode.focus()),u||(n.onPostInit&&n.onPostInit.call(this,o,!1),e&&e.call(this,"ok")),this.phase="idle"},_reloadAjax:function(e){var t=this.options;if(!t.initAjax||!t.initAjax.url)throw"tree.reload() requires 'initAjax' mode.";var n=this.persistence,r=$.extend({},t.initAjax);r.addActiveKey&&(r.data.activeKey=n.activeKey),r.addFocusedKey&&(r.data.focusedKey=n.focusedKey),r.addExpandedKeyList&&(r.data.expandedKeyList=n.expandedKeyList.join(",")),r.addSelectedKeyList&&(r.data.selectedKeyList=n.selectedKeyList.join(",")),r.success&&this.logWarning("initAjax: success callback is ignored; use onPostInit instead."),r.error&&this.logWarning("initAjax: error callback is ignored; use onPostInit instead.");var i=n.isReloading();r.success=function(n,r,s){t.selectMode==3&&n.tree.tnRoot._updatePartSelectionState(),t.onPostInit&&t.onPostInit.call(n.tree,i,!1),e&&e.call(n.tree,"ok")},r.error=function(n,r,s,o){t.onPostInit&&t.onPostInit.call(n.tree,i,!0,r,s,o),e&&e.call(n.tree,"error",r,s,o)},this.logDebug("Dynatree._init(): send Ajax request..."),this.tnRoot.appendAjax(r)},toString:function(){return"Dynatree '"+this.$tree.attr("id")+"'"},toDict:function(){return this.tnRoot.toDict(!0)},serializeArray:function(e){var t=this.getSelectedNodes(e),n=this.$tree.attr("name")||this.$tree.attr("id"),r=[];for(var i=0,s=t.length;i<s;i++)r.push({name:n,value:t[i].data.key});return r},getPersistData:function(){return this.persistence.toDict()},logDebug:function(e){this.options.debugLevel>=2&&(Array.prototype.unshift.apply(arguments,["debug"]),_log.apply(this,arguments))},logInfo:function(e){this.options.debugLevel>=1&&(Array.prototype.unshift.apply(arguments,["info"]),_log.apply(this,arguments))},logWarning:function(e){Array.prototype.unshift.apply(arguments,["warn"]),_log.apply(this,arguments)},isInitializing:function(){return this.phase=="init"||this.phase=="postInit"},isReloading:function(){return(this.phase=="init"||this.phase=="postInit")&&this.options.persist&&this.persistence.cookiesFound},isUserEvent:function(){return this.phase=="userEvent"},redraw:function(){this.tnRoot.render(!1,!1)},renderInvisibleNodes:function(){this.tnRoot.render(!1,!0)},reload:function(e){this._load(e)},getRoot:function(){return this.tnRoot},enable:function(){this.$widget.enable()},disable:function(){this.$widget.disable()},getNodeByKey:function(e){var t=document.getElementById(this.options.idPrefix+e);if(t)return t.dtnode?t.dtnode:null;var n=null;return this.visit(function(t){if(t.data.key===e)return n=t,!1},!0),n},getActiveNode:function(){return this.activeNode},reactivate:function(e){var t=this.activeNode;t&&(this.activeNode=null,t.activate(),e&&t.focus())},getSelectedNodes:function(e){var t=[];return this.tnRoot.visit(function(n){if(n.bSelected){t.push(n);if(e===!0)return"skip"}}),t},activateKey:function(e){var t=e===null?null:this.getNodeByKey(e);return t?(t.focus(),t.activate(),t):(this.activeNode&&this.activeNode.deactivate(),this.activeNode=null,null)},loadKeyPath:function(e,t){var n=e.split(this.options.keyPathSeparator);return n[0]===""&&n.shift(),n[0]==this.tnRoot.data.key&&(this.logDebug("Removed leading root key."),n.shift()),e=n.join(this.options.keyPathSeparator),this.tnRoot._loadKeyPath(e,t)},selectKey:function(e,t){var n=this.getNodeByKey(e);return n?(n.select(t),n):null},enableUpdate:function(e){return this.bEnableUpdate==e?e:(this.bEnableUpdate=e,e&&this.redraw(),!e)},count:function(){return this.tnRoot.countChildren()},visit:function(e,t){return this.tnRoot.visit(e,t)},_createFromTag:function(parentTreeNode,$ulParent){var self=this;$ulParent.find(">li").each(function(){var $li=$(this),$liSpan=$li.find(">span:first"),$liA=$li.find(">a:first"),title,href=null,target=null,tooltip;if($liSpan.length)title=$liSpan.html();else if($liA.length)title=$liA.html(),href=$liA.attr("href"),target=$liA.attr("target"),tooltip=$liA.attr("title");else{title=$li.html();var iPos=title.search(/<ul/i);iPos>=0?title=$.trim(title.substring(0,iPos)):title=$.trim(title)}var data={title:title,tooltip:tooltip,isFolder:$li.hasClass("folder"),isLazy:$li.hasClass("lazy"),expand:$li.hasClass("expanded"),select:$li.hasClass("selected"),activate:$li.hasClass("active"),focus:$li.hasClass("focused"),noLink:$li.hasClass("noLink")};href&&(data.href=href,data.target=target),$li.attr("title")&&(data.tooltip=$li.attr("title")),$li.attr("id")&&(data.key=""+$li.attr("id"));if($li.attr("data")){var dataAttr=$.trim($li.attr("data"));if(dataAttr){dataAttr.charAt(0)!="{"&&(dataAttr="{"+dataAttr+"}");try{$.extend(data,eval("("+dataAttr+")"))}catch(e){throw"Error parsing node data: "+e+"\ndata:\n'"+dataAttr+"'"}}}var childNode=parentTreeNode.addChild(data),$ul=$li.find(">ul:first");$ul.length&&self._createFromTag(childNode,$ul)})},_checkConsistency:function(){},_setDndStatus:function(e,t,n,r,i){var s=e?$(e.span):null,o=$(t.span);this.$dndMarker||(this.$dndMarker=$("<div id='dynatree-drop-marker'></div>").hide().css({"z-index":1e3}).prependTo($(this.divTree).parent()));if(r==="after"||r==="before"||r==="over"){var u="0 0";switch(r){case"before":this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-over"),this.$dndMarker.addClass("dynatree-drop-before"),u="0 -8";break;case"after":this.$dndMarker.removeClass("dynatree-drop-before dynatree-drop-over"),this.$dndMarker.addClass("dynatree-drop-after"),u="0 8";break;default:this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-before"),this.$dndMarker.addClass("dynatree-drop-over"),o.addClass("dynatree-drop-target"),u="8 0"}this.$dndMarker.show().position({my:"left top",at:"left top",of:o,offset:u})}else o.removeClass("dynatree-drop-target"),this.$dndMarker.hide();r==="after"?o.addClass("dynatree-drop-after"):o.removeClass("dynatree-drop-after"),r==="before"?o.addClass("dynatree-drop-before"):o.removeClass("dynatree-drop-before"),i===!0?(s&&s.addClass("dynatree-drop-accept"),o.addClass("dynatree-drop-accept"),n.addClass("dynatree-drop-accept")):(s&&s.removeClass("dynatree-drop-accept"),o.removeClass("dynatree-drop-accept"),n.removeClass("dynatree-drop-accept")),i===!1?(s&&s.addClass("dynatree-drop-reject"),o.addClass("dynatree-drop-reject"),n.addClass("dynatree-drop-reject")):(s&&s.removeClass("dynatree-drop-reject"),o.removeClass("dynatree-drop-reject"),n.removeClass("dynatree-drop-reject"))},_onDragEvent:function(e,t,n,r,i,s){var o=this.options,u=this.options.dnd,a=null,f=$(t.span),l,c;switch(e){case"helper":var h=$("<div class='dynatree-drag-helper'><span class='dynatree-drag-helper-img' /></div>").append($(r.target).closest(".dynatree-title").clone());$("ul.dynatree-container",t.tree.divTree).append(h),h.data("dtSourceNode",t),a=h;break;case"start":t.isStatusNode()?a=!1:u.onDragStart&&(a=u.onDragStart(t)),a===!1?(this.logDebug("tree.onDragStart() cancelled"),i.helper.trigger("mouseup"),i.helper.hide()):f.addClass("dynatree-drag-source");break;case"enter":a=u.onDragEnter?u.onDragEnter(t,n):null,a?a={over:a===!0||a==="over"||$.inArray("over",a)>=0,before:a===!0||a==="before"||$.inArray("before",a)>=0,after:a===!0||a==="after"||$.inArray("after",a)>=0}:a=!1,i.helper.data("enterResponse",a);break;case"over":c=i.helper.data("enterResponse"),l=null;if(c!==!1)if(typeof c=="string")l=c;else{var p=f.offset(),d={x:r.pageX-p.left,y:r.pageY-p.top},v={x:d.x/f.width(),y:d.y/f.height()};c.after&&v.y>.75?l="after":!c.over&&c.after&&v.y>.5?l="after":c.before&&v.y<=.25?l="before":!c.over&&c.before&&v.y<=.5?l="before":c.over&&(l="over"),u.preventVoidMoves&&(t===n?l=null:l==="before"&&n&&t===n.getNextSibling()?l=null:l==="after"&&n&&t===n.getPrevSibling()?l=null:l==="over"&&n&&n.parent===t&&n.isLastSibling()&&(l=null)),i.helper.data("hitMode",l)}l==="over"&&u.autoExpandMS&&t.hasChildren()!==!1&&!t.bExpanded&&t.scheduleAction("expand",u.autoExpandMS);if(l&&u.onDragOver){a=u.onDragOver(t,n,l);if(a==="over"||a==="before"||a==="after")l=a}this._setDndStatus(n,t,i.helper,l,a!==!1&&l!==null);break;case"drop":var m=i.helper.hasClass("dynatree-drop-reject");l=i.helper.data("hitMode"),l&&u.onDrop&&!m&&u.onDrop(t,n,l,i,s);break;case"leave":t.scheduleAction("cancel"),i.helper.data("enterResponse",null),i.helper.data("hitMode",null),this._setDndStatus(n,t,i.helper,"out",undefined),u.onDragLeave&&u.onDragLeave(t,n);break;case"stop":f.removeClass("dynatree-drag-source"),u.onDragStop&&u.onDragStop(t);break;default:throw"Unsupported drag event: "+e}return a},cancelDrag:function(){var e=$.ui.ddmanager.current;e&&e.cancel()},lastentry:undefined},$.widget("ui.dynatree",{_init:function(){if(versionCompare($.ui.version,"1.8")<0)return this.options.debugLevel>=0&&_log("warn","ui.dynatree._init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher."),this._create();this.options.debugLevel>=2&&_log("debug","ui.dynatree._init() was called; no current default functionality.")},_create:function(){var e=this.options;e.debugLevel>=1&&logMsg("Dynatree._create(): version='%s', debugLevel=%o.",$.ui.dynatree.version,this.options.debugLevel),this.options.event+=".dynatree";var t=this.element.get(0);this.tree=new DynaTree(this),this.tree._load(),this.tree.logDebug("Dynatree._init(): done.")},bind:function(){function t(e){e=$.event.fix(e||window.event);var t=$.ui.dynatree.getNode(e.target);return t?t._onFocus(e):!1}this.unbind();var e="click.dynatree dblclick.dynatree";this.options.keyboard&&(e+=" keypress.dynatree keydown.dynatree"),this.element.bind(e,function(e){var t=$.ui.dynatree.getNode(e.target);if(!t)return!0;var n=t.tree,r=n.options;n.logDebug("event(%s): dtnode: %s",e.type,t);var i=n.phase;n.phase="userEvent";try{switch(e.type){case"click":return r.onClick&&r.onClick.call(n,t,e)===!1?!1:t._onClick(e);case"dblclick":return r.onDblClick&&r.onDblClick.call(n,t,e)===!1?!1:t._onDblClick(e);case"keydown":return r.onKeydown&&r.onKeydown.call(n,t,e)===!1?!1:t._onKeydown(e);case"keypress":return r.onKeypress&&r.onKeypress.call(n,t,e)===!1?!1:t._onKeypress(e)}}catch(s){var o=null;n.logWarning("bind(%o): dtnode: %o, error: %o",e,t,s)}finally{n.phase=i}});var n=this.tree.divTree;n.addEventListener?(n.addEventListener("focus",t,!0),n.addEventListener("blur",t,!0)):n.onfocusin=n.onfocusout=t},unbind:function(){this.element.unbind(".dynatree")},enable:function(){this.bind(),$.Widget.prototype.enable.apply(this,arguments)},disable:function(){this.unbind(),$.Widget.prototype.disable.apply(this,arguments)},getTree:function(){return this.tree},getRoot:function(){return this.tree.getRoot()},getActiveNode:function(){return this.tree.getActiveNode()},getSelectedNodes:function(){return this.tree.getSelectedNodes()},lastentry:undefined}),versionCompare($.ui.version,"1.8")<0&&($.ui.dynatree.getter="getTree getRoot getActiveNode getSelectedNodes"),$.ui.dynatree.version="$Version:$",$.ui.dynatree.getNode=function(e){if(e instanceof DynaTreeNode)return e;e.selector!==undefined&&(e=e[0]);while(e){if(e.dtnode)return e.dtnode;e=e.parentNode}return null},$.ui.dynatree.getPersistData=DynaTreeStatus._getTreePersistData,$.ui.dynatree.prototype.options={title:"Dynatree",minExpandLevel:1,imagePath:null,children:null,initId:null,initAjax:null,autoFocus:!0,keyboard:!0,persist:!1,autoCollapse:!1,clickFolderMode:3,activeVisible:!0,checkbox:!1,selectMode:2,fx:null,noLink:!1,onClick:null,onDblClick:null,onKeydown:null,onKeypress:null,onFocus:null,onBlur:null,onQueryActivate:null,onQuerySelect:null,onQueryExpand:null,onPostInit:null,onActivate:null,onDeactivate:null,onSelect:null,onExpand:null,onLazyRead:null,onCustomRender:null,onCreate:null,onRender:null,postProcess:null,dnd:{onDragStart:null,onDragStop:null,autoExpandMS:1e3,preventVoidMoves:!0,onDragEnter:null,onDragOver:null,onDrop:null,onDragLeave:null},ajaxDefaults:{cache:!1,timeout:0,dataType:"json"},strings:{loading:"Loading&#8230;",loadError:"Load error!"},generateIds:!1,idPrefix:"dynatree-id-",keyPathSeparator:"/",cookieId:"dynatree",cookie:{expires:null},classNames:{container:"dynatree-container",node:"dynatree-node",folder:"dynatree-folder",empty:"dynatree-empty",vline:"dynatree-vline",expander:"dynatree-expander",connector:"dynatree-connector",checkbox:"dynatree-checkbox",nodeIcon:"dynatree-icon",title:"dynatree-title",noConnector:"dynatree-no-connector",nodeError:"dynatree-statusnode-error",nodeWait:"dynatree-statusnode-wait",hidden:"dynatree-hidden",combinedExpanderPrefix:"dynatree-exp-",combinedIconPrefix:"dynatree-ico-",nodeLoading:"dynatree-loading",hasChildren:"dynatree-has-children",active:"dynatree-active",selected:"dynatree-selected",expanded:"dynatree-expanded",lazy:"dynatree-lazy",focused:"dynatree-focused",partsel:"dynatree-partsel",lastsib:"dynatree-lastsib"},debugLevel:2,lastentry:undefined},versionCompare($.ui.version,"1.8")<0&&($.ui.dynatree.defaults=$.ui.dynatree.prototype.options),$.ui.dynatree.nodedatadefaults={title:null,key:null,isFolder:!1,isLazy:!1,tooltip:null,href:null,icon:null,addClass:null,noLink:!1,activate:!1,focus:!1,expand:!1,select:!1,hideCheckbox:!1,unselectable:!1,children:null,lastentry:undefined};var didRegisterDnd=!1,_registerDnd=function(){if(didRegisterDnd)return;$.ui.plugin.add("draggable","connectToDynatree",{start:function(e,t){var n=$(this).data("ui-draggable")||$(this).data("draggable"),r=t.helper.data("dtSourceNode")||null;if(r)return n.offset.click.top=-2,n.offset.click.left=16,r.tree._onDragEvent("start",r,null,e,t,n)},drag:function(e,t){var n=$(this).data("ui-draggable")||$(this).data("draggable"),r=t.helper.data("dtSourceNode")||null,i=t.helper.data("dtTargetNode")||null,s=$.ui.dynatree.getNode(e.target);if(e.target&&!s){var o=$(e.target).closest("div.dynatree-drag-helper,#dynatree-drop-marker").length>0;if(o)return}t.helper.data("dtTargetNode",s),i&&i!==s&&i.tree._onDragEvent("leave",i,r,e,t,n),s&&(!s.tree.options.dnd.onDrop||(s===i?s.tree._onDragEvent("over",s,r,e,t,n):s.tree._onDragEvent("enter",s,r,e,t,n)))},stop:function(e,t){var n=$(this).data("ui-draggable")||$(this).data("draggable"),r=t.helper.data("dtSourceNode")||null,i=t.helper.data("dtTargetNode")||null,s=n._mouseDownEvent,o=e.type,u=o=="mouseup"&&e.which==1;logMsg("draggable-connectToDynatree.stop: targetNode(from event): %s, dtTargetNode: %s",i,t.helper.data("dtTargetNode")),u||logMsg("Drag was cancelled"),i&&(u&&i.tree._onDragEvent("drop",i,r,e,t,n),i.tree._onDragEvent("leave",i,r,e,t,n)),r&&r.tree._onDragEvent("stop",r,null,e,t,n)}}),didRegisterDnd=!0}})(jQuery);
... ...
logo.gif 0 → 100644

1.72 KB

logo.jpg 0 → 100644

2.94 KB

logo.png 0 → 100644

3.49 KB

skin/digits.png 0 → 100644

13.1 KB

skin/icons-rtl.gif 0 → 100644

3.95 KB

skin/icons.gif 0 → 100644

3.95 KB

skin/loading.gif 0 → 100644

570 Bytes

skin/ui.dynatree.css 0 → 100644
  1 +++ a/skin/ui.dynatree.css
  1 +/*******************************************************************************
  2 + * Tree container
  3 + */
  4 +ul.dynatree-container
  5 +{
  6 + font-family: tahoma, arial, helvetica;
  7 + font-size: 10pt; /* font size should not be too big */
  8 + white-space: nowrap;
  9 + padding: 3px;
  10 + margin: 0; /* issue 201 */
  11 + background-color: white;
  12 + border: 1px dotted gray;
  13 + overflow: auto;
  14 + height: 100%; /* issue 263 */
  15 +}
  16 +
  17 +ul.dynatree-container ul
  18 +{
  19 + padding: 0 0 0 16px;
  20 + margin: 0;
  21 +}
  22 +
  23 +ul.dynatree-container li
  24 +{
  25 + list-style-image: none;
  26 + list-style-position: outside;
  27 + list-style-type: none;
  28 + -moz-background-clip:border;
  29 + -moz-background-inline-policy: continuous;
  30 + -moz-background-origin: padding;
  31 + background-attachment: scroll;
  32 + background-color: transparent;
  33 + background-repeat: repeat-y;
  34 + background-image: url("vline.gif");
  35 + background-position: 0 0;
  36 + /*
  37 + background-image: url("icons_96x256.gif");
  38 + background-position: -80px -64px;
  39 + */
  40 + margin: 0;
  41 + padding: 1px 0 0 0;
  42 +}
  43 +/* Suppress lines for last child node */
  44 +ul.dynatree-container li.dynatree-lastsib
  45 +{
  46 + background-image: none;
  47 +}
  48 +/* Suppress lines if level is fixed expanded (option minExpandLevel) */
  49 +ul.dynatree-no-connector > li
  50 +{
  51 + background-image: none;
  52 +}
  53 +
  54 +/* Style, when control is disabled */
  55 +.ui-dynatree-disabled ul.dynatree-container
  56 +{
  57 + opacity: 0.5;
  58 +/* filter: alpha(opacity=50); /* Yields a css warning */
  59 + background-color: silver;
  60 +}
  61 +
  62 +/*******************************************************************************
  63 + * Common icon definitions
  64 + */
  65 +span.dynatree-empty,
  66 +span.dynatree-vline,
  67 +span.dynatree-connector,
  68 +span.dynatree-expander,
  69 +span.dynatree-icon,
  70 +span.dynatree-checkbox,
  71 +span.dynatree-radio,
  72 +span.dynatree-drag-helper-img,
  73 +#dynatree-drop-marker
  74 +{
  75 + width: 16px;
  76 + height: 16px;
  77 +/* display: -moz-inline-box; /* @ FF 1+2 removed for issue 221 */
  78 +/* -moz-box-align: start; /* issue 221 */
  79 + display: inline-block; /* Required to make a span sizeable */
  80 + vertical-align: top;
  81 + background-repeat: no-repeat;
  82 + background-position: left;
  83 + background-image: url("icons.gif");
  84 + background-position: 0 0;
  85 +}
  86 +
  87 +/** Used by 'icon' node option: */
  88 +ul.dynatree-container img
  89 +{
  90 + width: 16px;
  91 + height: 16px;
  92 + margin-left: 3px;
  93 + vertical-align: top;
  94 + border-style: none;
  95 +}
  96 +
  97 +
  98 +/*******************************************************************************
  99 + * Lines and connectors
  100 + */
  101 +
  102 +span.dynatree-connector
  103 +{
  104 + background-position: -16px -64px;
  105 +}
  106 +
  107 +/*******************************************************************************
  108 + * Expander icon
  109 + * Note: IE6 doesn't correctly evaluate multiples class names,
  110 + * so we create combined class names that can be used in the CSS.
  111 + *
  112 + * Prefix: dynatree-exp-
  113 + * 1st character: 'e': expanded, 'c': collapsed
  114 + * 2nd character (optional): 'd': lazy (Delayed)
  115 + * 3rd character (optional): 'l': Last sibling
  116 + */
  117 +
  118 +span.dynatree-expander
  119 +{
  120 + background-position: 0px -80px;
  121 + cursor: pointer;
  122 +}
  123 +.dynatree-exp-cl span.dynatree-expander /* Collapsed, not delayed, last sibling */
  124 +{
  125 + background-position: 0px -96px;
  126 +}
  127 +.dynatree-exp-cd span.dynatree-expander /* Collapsed, delayed, not last sibling */
  128 +{
  129 + background-position: -64px -80px;
  130 +}
  131 +.dynatree-exp-cdl span.dynatree-expander /* Collapsed, delayed, last sibling */
  132 +{
  133 + background-position: -64px -96px;
  134 +}
  135 +.dynatree-exp-e span.dynatree-expander, /* Expanded, not delayed, not last sibling */
  136 +.dynatree-exp-ed span.dynatree-expander /* Expanded, delayed, not last sibling */
  137 +{
  138 + background-position: -32px -80px;
  139 +}
  140 +.dynatree-exp-el span.dynatree-expander, /* Expanded, not delayed, last sibling */
  141 +.dynatree-exp-edl span.dynatree-expander /* Expanded, delayed, last sibling */
  142 +{
  143 + background-position: -32px -96px;
  144 +}
  145 +.dynatree-loading span.dynatree-expander /* 'Loading' status overrides all others */
  146 +{
  147 + background-position: 0 0;
  148 + background-image: url("loading.gif");
  149 +}
  150 +
  151 +
  152 +/*******************************************************************************
  153 + * Checkbox icon
  154 + */
  155 +span.dynatree-checkbox
  156 +{
  157 + margin-left: 3px;
  158 + background-position: 0px -32px;
  159 +}
  160 +span.dynatree-checkbox:hover
  161 +{
  162 + background-position: -16px -32px;
  163 +}
  164 +
  165 +.dynatree-partsel span.dynatree-checkbox
  166 +{
  167 + background-position: -64px -32px;
  168 +}
  169 +.dynatree-partsel span.dynatree-checkbox:hover
  170 +{
  171 + background-position: -80px -32px;
  172 +}
  173 +
  174 +.dynatree-selected span.dynatree-checkbox
  175 +{
  176 + background-position: -32px -32px;
  177 +}
  178 +.dynatree-selected span.dynatree-checkbox:hover
  179 +{
  180 + background-position: -48px -32px;
  181 +}
  182 +
  183 +/*******************************************************************************
  184 + * Radiobutton icon
  185 + * This is a customization, that may be activated by overriding the 'checkbox'
  186 + * class name as 'dynatree-radio' in the tree options.
  187 + */
  188 +span.dynatree-radio
  189 +{
  190 + margin-left: 3px;
  191 + background-position: 0px -48px;
  192 +}
  193 +span.dynatree-radio:hover
  194 +{
  195 + background-position: -16px -48px;
  196 +}
  197 +
  198 +.dynatree-partsel span.dynatree-radio
  199 +{
  200 + background-position: -64px -48px;
  201 +}
  202 +.dynatree-partsel span.dynatree-radio:hover
  203 +{
  204 + background-position: -80px -48px;
  205 +}
  206 +
  207 +.dynatree-selected span.dynatree-radio
  208 +{
  209 + background-position: -32px -48px;
  210 +}
  211 +.dynatree-selected span.dynatree-radio:hover
  212 +{
  213 + background-position: -48px -48px;
  214 +}
  215 +
  216 +/*******************************************************************************
  217 + * Node type icon
  218 + * Note: IE6 doesn't correctly evaluate multiples class names,
  219 + * so we create combined class names that can be used in the CSS.
  220 + *
  221 + * Prefix: dynatree-ico-
  222 + * 1st character: 'e': expanded, 'c': collapsed
  223 + * 2nd character (optional): 'f': folder
  224 + */
  225 +
  226 +span.dynatree-icon /* Default icon */
  227 +{
  228 + margin-left: 3px;
  229 + background-position: 0px 0px;
  230 +}
  231 +
  232 +.dynatree-ico-cf span.dynatree-icon /* Collapsed Folder */
  233 +{
  234 + background-position: 0px -16px;
  235 +}
  236 +
  237 +.dynatree-ico-ef span.dynatree-icon /* Expanded Folder */
  238 +{
  239 + background-position: -64px -16px;
  240 +}
  241 +
  242 +/* Status node icons */
  243 +
  244 +.dynatree-statusnode-wait span.dynatree-icon
  245 +{
  246 + background-image: url("loading.gif");
  247 +}
  248 +
  249 +.dynatree-statusnode-error span.dynatree-icon
  250 +{
  251 + background-position: 0px -112px;
  252 +/* background-image: url("ltError.gif");*/
  253 +}
  254 +
  255 +/*******************************************************************************
  256 + * Node titles
  257 + */
  258 +
  259 +/* @Chrome: otherwise hit area of node titles is broken (issue 133)
  260 + Removed again for issue 165; (133 couldn't be reproduced) */
  261 +span.dynatree-node
  262 +{
  263 +/* display: -moz-inline-box; /* issue 133, 165, 172, 192. removed for issue 221*/
  264 +/* -moz-box-align: start; /* issue 221 */
  265 +/* display: inline-block; /* Required to make a span sizeable */
  266 +}
  267 +
  268 +
  269 +/* Remove blue color and underline from title links */
  270 +ul.dynatree-container a
  271 +/*, ul.dynatree-container a:visited*/
  272 +{
  273 + color: black; /* inherit doesn't work on IE */
  274 + text-decoration: none;
  275 + vertical-align: top;
  276 + margin: 0px;
  277 + margin-left: 3px;
  278 +/* outline: 0; /* @ Firefox, prevent dotted border after click */
  279 +}
  280 +
  281 +ul.dynatree-container a:hover
  282 +{
  283 +/* text-decoration: underline; */
  284 + background-color: #F2F7FD; /* light blue */
  285 + border-color: #B8D6FB; /* darker light blue */
  286 +}
  287 +
  288 +span.dynatree-node a
  289 +{
  290 + font-size: 10pt; /* required for IE, quirks mode */
  291 + display: inline-block; /* Better alignment, when title contains <br> */
  292 +/* vertical-align: top;*/
  293 + padding-left: 3px;
  294 + padding-right: 3px; /* Otherwise italic font will be outside bounds */
  295 + /* line-height: 16px; /* should be the same as img height, in case 16 px */
  296 +}
  297 +span.dynatree-folder a
  298 +{
  299 + font-weight: bold;
  300 +}
  301 +
  302 +ul.dynatree-container a:focus,
  303 +span.dynatree-focused a:link /* @IE */
  304 +{
  305 + background-color: #EFEBDE; /* gray */
  306 +}
  307 +
  308 +span.dynatree-has-children a
  309 +{
  310 +}
  311 +
  312 +span.dynatree-expanded a
  313 +{
  314 +}
  315 +
  316 +span.dynatree-selected a
  317 +{
  318 + color: green;
  319 + font-style: italic;
  320 +}
  321 +
  322 +span.dynatree-active a
  323 +{
  324 + background-color: #3169C6 !important;
  325 + color: white !important; /* @ IE6 */
  326 +}
  327 +
  328 +/*******************************************************************************
  329 + * Drag'n'drop support
  330 + */
  331 +
  332 +/*** Helper object ************************************************************/
  333 +div.dynatree-drag-helper
  334 +{
  335 +}
  336 +div.dynatree-drag-helper a
  337 +{
  338 + border: 1px solid gray;
  339 + background-color: white;
  340 + padding-left: 5px;
  341 + padding-right: 5px;
  342 + opacity: 0.8;
  343 +}
  344 +span.dynatree-drag-helper-img
  345 +{
  346 + /*
  347 + position: relative;
  348 + left: -16px;
  349 + */
  350 +}
  351 +div.dynatree-drag-helper /*.dynatree-drop-accept*/
  352 +{
  353 +
  354 +/* border-color: green;
  355 + background-color: red;*/
  356 +}
  357 +div.dynatree-drop-accept span.dynatree-drag-helper-img
  358 +{
  359 + background-position: -32px -112px;
  360 +}
  361 +div.dynatree-drag-helper.dynatree-drop-reject
  362 +{
  363 + border-color: red;
  364 +}
  365 +div.dynatree-drop-reject span.dynatree-drag-helper-img
  366 +{
  367 + background-position: -16px -112px;
  368 +}
  369 +
  370 +/*** Drop marker icon *********************************************************/
  371 +
  372 +#dynatree-drop-marker
  373 +{
  374 + width: 24px;
  375 + position: absolute;
  376 + background-position: 0 -128px;
  377 + margin: 0;
  378 +/* border: 1px solid red; */
  379 +}
  380 +#dynatree-drop-marker.dynatree-drop-after,
  381 +#dynatree-drop-marker.dynatree-drop-before
  382 +{
  383 + width:64px;
  384 + background-position: 0 -144px;
  385 +}
  386 +#dynatree-drop-marker.dynatree-drop-copy
  387 +{
  388 + background-position: -64px -128px;
  389 +}
  390 +#dynatree-drop-marker.dynatree-drop-move
  391 +{
  392 + background-position: -64px -128px;
  393 +}
  394 +
  395 +/*** Source node while dragging ***********************************************/
  396 +
  397 +span.dynatree-drag-source
  398 +{
  399 + /* border: 1px dotted gray; */
  400 + background-color: #e0e0e0;
  401 +}
  402 +span.dynatree-drag-source a
  403 +{
  404 + color: gray;
  405 +}
  406 +
  407 +/*** Target node while dragging cursor is over it *****************************/
  408 +
  409 +span.dynatree-drop-target
  410 +{
  411 + /*border: 1px solid gray;*/
  412 +}
  413 +span.dynatree-drop-target a
  414 +{
  415 +}
  416 +span.dynatree-drop-target.dynatree-drop-accept a
  417 +{
  418 + /*border: 1px solid green;*/
  419 + background-color: #3169C6 !important;
  420 + color: white !important; /* @ IE6 */
  421 + text-decoration: none;
  422 +}
  423 +span.dynatree-drop-target.dynatree-drop-reject
  424 +{
  425 + /*border: 1px solid red;*/
  426 +}
  427 +span.dynatree-drop-target.dynatree-drop-after a
  428 +{
  429 +}
  430 +
  431 +
  432 +/*******************************************************************************
  433 + * Custom node classes (sample)
  434 + */
  435 +
  436 +span.custom1 a
  437 +{
  438 + background-color: maroon;
  439 + color: yellow;
  440 +}
... ...
skin/vline-rtl.gif 0 → 100644

842 Bytes

skin/vline.gif 0 → 100644

844 Bytes

sql-install.php 0 → 100644
  1 +++ a/sql-install.php
  1 +<?php
  2 +$sql = array();
  3 +
  4 +$sql[] = "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_.self::prefijo."movimiento` (
  5 + `id_product` INT(10) NOT NULL,
  6 + `id_category` INT(10) NOT NULL,
  7 + PRIMARY KEY (`id_product`, `id_category`)
  8 + )
  9 + COLLATE='utf8_general_ci'
  10 + ENGINE=MyIsam;";
  11 +
  12 +
  13 +$sql[] = "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_.self::prefijo."addCatFeature` (
  14 + `id_feature_value` INT(10) NOT NULL,
  15 + `id_category` INT(10) NOT NULL,
  16 + PRIMARY KEY (`id_feature_value`, `id_category`)
  17 + )
  18 + COLLATE='utf8_general_ci'
  19 + ENGINE=MyIsam;";
0 20 \ No newline at end of file
... ...
sql-unninstall.php 0 → 100644
  1 +++ a/sql-unninstall.php
  1 +<?php
  2 +$sql = array();
  3 +$sql[] = 'DROP TABLE IF EXISTS `'._DB_PREFIX_.self::prefijo.'movimiento`';
  4 +$sql[] = 'DROP TABLE IF EXISTS `'._DB_PREFIX_.self::prefijo.'addCatFeature`';
... ...