From 0df779f6da288f32d0809a2a557bacb88ba2bfa0 Mon Sep 17 00:00:00 2001 From: Fabrice Creuzot Date: Thu, 7 Jul 2022 20:00:00 +0000 Subject: [PATCH] Version 6.9.0 --- README.md | 6 +- composer.json | 3 +- .../Apijs/Block/Adminhtml/Config/Help.php | 8 +- .../Apijs/Block/Adminhtml/Rewrite/Gallery.php | 28 ++- .../community/Luigifab/Apijs/Helper/Data.php | 26 ++- .../Luigifab/Apijs/Helper/Rewrite/Image.php | 168 +++++++++++++----- .../Luigifab/Apijs/Model/Observer.php | 13 +- .../community/Luigifab/Apijs/Model/Python.php | 64 ++++--- .../Luigifab/Apijs/Model/Rewrite/Media.php | 6 +- .../Luigifab/Apijs/Model/Rewrite/Mediares.php | 2 +- .../Apijs/Model/Rewrite/Validator.php | 16 +- .../Luigifab/Apijs/Model/Useragentparser.php | 14 +- .../controllers/Apijs/MediaController.php | 32 +++- .../controllers/Apijs/WysiwygController.php | 6 +- .../community/Luigifab/Apijs/etc/config.xml | 14 +- .../community/Luigifab/Apijs/etc/system.xml | 18 +- .../community/Luigifab/Apijs/lib/image.py | 138 ++++++++++++-- src/app/code/community/Luigifab/Apijs/readme | 11 +- .../{install-6.8.2.php => install-6.9.0.php} | 0 .../template/luigifab/apijs/demo.phtml | 4 +- .../template/luigifab/apijs/files.phtml | 6 +- .../template/luigifab/apijs/gallery.phtml | 6 +- src/app/locale/cs_CZ/Luigifab_Apijs.csv | 1 + src/app/locale/de_AT/Luigifab_Apijs.csv | 1 + src/app/locale/de_CH/Luigifab_Apijs.csv | 1 + src/app/locale/de_DE/Luigifab_Apijs.csv | 1 + src/app/locale/el_GR/Luigifab_Apijs.csv | 3 +- src/app/locale/es_AR/Luigifab_Apijs.csv | 5 +- src/app/locale/es_CL/Luigifab_Apijs.csv | 5 +- src/app/locale/es_CO/Luigifab_Apijs.csv | 5 +- src/app/locale/es_CR/Luigifab_Apijs.csv | 5 +- src/app/locale/es_ES/Luigifab_Apijs.csv | 5 +- src/app/locale/es_MX/Luigifab_Apijs.csv | 5 +- src/app/locale/es_PA/Luigifab_Apijs.csv | 5 +- src/app/locale/es_PE/Luigifab_Apijs.csv | 5 +- src/app/locale/es_VE/Luigifab_Apijs.csv | 5 +- src/app/locale/fr_CA/Luigifab_Apijs.csv | 4 +- src/app/locale/fr_CH/Luigifab_Apijs.csv | 4 +- src/app/locale/fr_FR/Luigifab_Apijs.csv | 4 +- src/app/locale/hu_HU/Luigifab_Apijs.csv | 1 + src/app/locale/it_CH/Luigifab_Apijs.csv | 1 + src/app/locale/it_IT/Luigifab_Apijs.csv | 1 + src/app/locale/ja_JP/Luigifab_Apijs.csv | 3 +- src/app/locale/nl_NL/Luigifab_Apijs.csv | 3 +- src/app/locale/pl_PL/Luigifab_Apijs.csv | 1 + src/app/locale/pt_BR/Luigifab_Apijs.csv | 3 +- src/app/locale/pt_PT/Luigifab_Apijs.csv | 3 +- src/app/locale/ro_RO/Luigifab_Apijs.csv | 3 +- src/app/locale/ru_RU/Luigifab_Apijs.csv | 3 +- src/app/locale/sk_SK/Luigifab_Apijs.csv | 1 + src/app/locale/tr_TR/Luigifab_Apijs.csv | 11 +- src/app/locale/uk_UA/Luigifab_Apijs.csv | 3 +- src/app/locale/zh_CN/Luigifab_Apijs.csv | 3 +- .../css/luigifab/apijs/apijs-openmage.css | 32 ++-- .../css/luigifab/apijs/apijs-openmage.min.css | 2 +- .../luigifab/apijs/apijs-openmage.min.css.map | 2 +- .../css/luigifab/apijs/apijs-print.min.css | 2 +- .../luigifab/apijs/apijs-print.min.css.map | 2 +- .../luigifab/apijs/apijs-screen-rtl.min.css | 4 +- .../apijs/apijs-screen-rtl.min.css.map | 2 +- .../css/luigifab/apijs/apijs-screen.min.css | 4 +- .../luigifab/apijs/apijs-screen.min.css.map | 2 +- .../js/luigifab/apijs/apijs-openmage.js | 7 +- .../js/luigifab/apijs/apijs-openmage.min.js | 2 +- .../luigifab/apijs/apijs-openmage.min.js.map | 2 +- .../default/js/luigifab/apijs/apijs.min.js | 4 +- .../js/luigifab/apijs/apijs.min.js.map | 2 +- .../css/luigifab/apijs/apijs-print.min.css | 2 +- .../luigifab/apijs/apijs-print.min.css.map | 2 +- .../luigifab/apijs/apijs-screen-rtl.min.css | 4 +- .../apijs/apijs-screen-rtl.min.css.map | 2 +- .../css/luigifab/apijs/apijs-screen.min.css | 4 +- .../luigifab/apijs/apijs-screen.min.css.map | 2 +- .../default/js/luigifab/apijs/apijs.min.js | 4 +- .../js/luigifab/apijs/apijs.min.js.map | 2 +- 75 files changed, 559 insertions(+), 225 deletions(-) rename src/app/code/community/Luigifab/Apijs/sql/apijs_setup/{install-6.8.2.php => install-6.9.0.php} (100%) diff --git a/README.md b/README.md index 8857dc2..f136935 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +Stop russian war. **🇺🇦 Free Ukraine!** + # apijs JavaScript pop-ups and slideshow for [OpenMage](https://github.com/OpenMage/magento-lts). @@ -6,8 +8,8 @@ For more information, go to https://www.luigifab.fr/apijs and https://www.luigif This repository is a releases mirror. To install the module, please use the composer key available in the documentation. -- Current version: 6.8.2 (01/01/2022) -- Compatibility: OpenMage 19.x / 20.x / 21.x, PHP 7.2 / 7.3 / 7.4 / 8.0 / 8.1, Python 3.3 - 3.10 +- Current version: 6.9.0 (07/07/2022) +- Compatibility: OpenMage 19.x / 20.x / 21.x, PHP 7.2 / 7.3 / 7.4 / 8.0 / 8.1, Python 3.3+ - Client compatibility: Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ - Translations: English (en), French (fr-FR/fr-CA), German (de), Italian (it), Portuguese (pt-PT/pt-BR), Spanish (es) / Chinese (zh), Czech (cs), Dutch (nl), Greek (el), Hungarian (hu), Japanese (ja), Polish (pl), Romanian (ro), Russian (ru), Slovak (sk), Turkish (tr), Ukrainian (uk) - License: GNU GPL 2+ diff --git a/composer.json b/composer.json index 3707b1d..4607972 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "require": { "php": ">=7.2.0", "openmage/magento-lts": ">=19.4.0", + "symfony/polyfill-php80": "*", "ext-mbstring": "*", "ext-json": "*", "ext-curl": "*" @@ -86,4 +87,4 @@ ["src/skin/frontend/base/default/js/luigifab/apijs/", "skin/frontend/base/default/js/luigifab/apijs/"] ] } -} \ No newline at end of file +} diff --git a/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Config/Help.php b/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Config/Help.php index ed89786..13b2e30 100644 --- a/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Config/Help.php +++ b/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Config/Help.php @@ -1,7 +1,7 @@ * https://www.luigifab.fr/openmage/apijs @@ -23,12 +23,12 @@ public function render(Varien_Data_Form_Element_Abstract $element) { $msg = $this->checkRewrites(); if ($msg !== true) - return sprintf('

%s %s %3$s | ⚠ IPv6

%s
%s

', + return sprintf('

%s %s Stop russian war. 🇺🇦 Free Ukraine! | %3$s | ⚠ IPv6

%s
%s

', 'Luigifab/Apijs', $this->helper('apijs')->getVersion(), 'luigifab.fr/openmage/apijs', $this->__('INCOMPLETE MODULE INSTALLATION'), $this->__('There is conflict (%s).', $msg)); - return sprintf('

%s %s %3$s | %4$s | ⚠ IPv6

', + return sprintf('

%s %s Stop russian war. 🇺🇦 Free Ukraine! | %3$s | %4$s | ⚠ IPv6

', 'Luigifab/Apijs', $this->helper('apijs')->getVersion(), 'luigifab.fr/openmage/apijs', 'luigifab.fr/apijs'); } @@ -46,7 +46,7 @@ protected function checkRewrites() { ['model' => 'catalog_resource/product_attribute_backend_image'], ['model' => 'catalog_resource/product_attribute_backend_media'], ['model' => 'cms/wysiwyg_images_storage'], - ['model' => 'core/file_validator_image'] + ['model' => 'core/file_validator_image'], ]; foreach ($rewrites as $rewrite) { diff --git a/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Rewrite/Gallery.php b/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Rewrite/Gallery.php index 4aa4b52..1c2b8b6 100644 --- a/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Rewrite/Gallery.php +++ b/src/app/code/community/Luigifab/Apijs/Block/Adminhtml/Rewrite/Gallery.php @@ -1,7 +1,7 @@ * https://www.luigifab.fr/openmage/apijs @@ -68,20 +68,36 @@ public function getImages(bool $sortByStore) { $counts[$image->getData('apijs_group')] = 1; } + $database = Mage::getSingleton('core/resource'); + $writer = $database->getConnection('core_write'); + $reader = $database->getConnection('core_read'); + $table = $database->getTableName('catalog_product_entity_varchar'); + $ids = []; foreach ($attributes as $code => $attribute) { + if (($attribute->getIsText() !== true) && ($attribute->getIsCheckbox() !== true)) { + $ids[] = $attribute->getId(); $globalValues[$code] = $product->getResource()->getAttributeRawValue($productId, $code, 0); + + // bug de merde, quand la valeur par défaut est non présente, la lecture de la valeur par vue ne marche pas + if ($globalValues[$code] === false) { + try { + $writer->fetchAll('INSERT INTO '.$table.' (entity_type_id, attribute_id, store_id, entity_id, value) VALUES + (4, '.$attribute->getId().', 0, '.$productId.', "no_selection")'); + $globalValues[$code] = 'no_selection'; + } + catch (Throwable $t) { + Mage::logException($t); + } + } + $storeValues[$code] = $product->getResource()->getAttributeRawValue($productId, $code, $storeId); } } - $database = Mage::getSingleton('core/resource'); - $reader = $database->getConnection('core_read'); - $table = $database->getTableName('catalog_product_entity_varchar'); - $values = $reader->fetchAll('SELECT store_id, attribute_id, value FROM '.$table.' WHERE entity_id = '.$productId.' AND attribute_id IN ('.implode(',', $ids).')'); - + $values = $reader->fetchAll('SELECT store_id, attribute_id, value FROM '.$table.' WHERE entity_id = '.$productId.' AND attribute_id IN ('.implode(',', $ids).')'); foreach ($values as $value) { if (!empty($images[$value['value']])) { $image = $images[$value['value']]; diff --git a/src/app/code/community/Luigifab/Apijs/Helper/Data.php b/src/app/code/community/Luigifab/Apijs/Helper/Data.php index d071a2f..d9f3753 100644 --- a/src/app/code/community/Luigifab/Apijs/Helper/Data.php +++ b/src/app/code/community/Luigifab/Apijs/Helper/Data.php @@ -1,7 +1,7 @@ * https://www.luigifab.fr/openmage/apijs @@ -37,8 +37,8 @@ public function formatDate($date = null, $format = Zend_Date::DATETIME_LONG, $sh return str_replace($object->date($date)->toString(Zend_Date::TIMEZONE), '', $object->date($date)->toString($format)); } - public function getHumanEmailAddress(string $email) { - return $this->escapeEntities(str_replace(['<', '>', ',', '"'], ['(', ')', ', ', ''], $email)); + public function getHumanEmailAddress($email) { + return empty($email) ? '' : $this->escapeEntities(str_replace(['<', '>', ',', '"'], ['(', ')', ', ', ''], $email)); } public function getHumanDuration($start, $end = null) { @@ -138,9 +138,9 @@ public function getWysiwygImageDir(bool $cache = false, bool $old = false) { } - public function resizeImage($product, $type, $path, int $width, int $height, bool $fixed) { + public function resizeImage($product, $type, $path, int $width, int $height, bool $fixed, bool $webp = false) { - $resource = Mage::helper('catalog/image')->init($product, $type, $path, $fixed); + $resource = Mage::helper('catalog/image')->init($product, $type, $path, $fixed, $webp); if (Mage::getStoreConfigFlag('apijs/general/python')) $resource->resize($width, $height); @@ -312,6 +312,22 @@ protected function searchAndRemoveFiles(string $dir, string $file) { Mage::log($cmd, Zend_Log::DEBUG, 'apijs.log'); exec($cmd); + + // supprime aussi les éventuels fichiers webp, uniquement dans le dossier cache + $webp = str_ireplace(['.jpg', '.jpeg', '.png', '.gif'], '.webp', $file); + if ($file != $webp) { + + if (mb_stripos($dir, '/cache') === false) + $dir .= '/cache'; + + if (mb_stripos($webp, '/') === false) + $cmd = 'find '.escapeshellarg($dir).' -name '.escapeshellarg($webp).' | xargs rm'; + else + $cmd = 'find '.escapeshellarg($dir).' -wholename '.escapeshellarg('*/'.trim($webp, '/')).' | xargs rm'; + + Mage::log($cmd, Zend_Log::DEBUG, 'apijs.log'); + exec($cmd); + } } public function removeFiles(string $dir, string $file, bool $now = false) { diff --git a/src/app/code/community/Luigifab/Apijs/Helper/Rewrite/Image.php b/src/app/code/community/Luigifab/Apijs/Helper/Rewrite/Image.php index ce8d84b..d67ba89 100644 --- a/src/app/code/community/Luigifab/Apijs/Helper/Rewrite/Image.php +++ b/src/app/code/community/Luigifab/Apijs/Helper/Rewrite/Image.php @@ -1,7 +1,7 @@ * Copyright 2019-2022 | Fabrice Creuzot @@ -20,9 +20,10 @@ class Luigifab_Apijs_Helper_Rewrite_Image extends Mage_Catalog_Helper_Image { - public function init($product, $attribute, $path = null, $fixed = true) { + public function init($product, $attribute, $path = null, $fixed = true, $webp = false) { $this->_reset(); + $this->_webp = $webp; // debug //if (!isset($this->_debugBegin)) $this->_debugBegin = microtime(true); @@ -36,7 +37,8 @@ public function init($product, $attribute, $path = null, $fixed = true) { if (empty($this->_helper)) { $this->_helper = Mage::helper('apijs'); $this->_modelImg = Mage::getModel('catalog/product_image'); - $this->_cleanUrl = (PHP_SAPI != 'cli') && (mb_strpos(Mage::getBaseUrl('media'), Mage::getBaseUrl('web')) === 0); + $this->_cleanUrl = (PHP_SAPI != 'cli') && str_starts_with(Mage::getBaseUrl('media'), Mage::getBaseUrl('web')); + $this->_storeId = Mage::app()->getStore()->getId(); } // sans le dossier, cela ne génère pas les miniatures wysiwyg ou category @@ -62,24 +64,68 @@ public function init($product, $attribute, $path = null, $fixed = true) { $attribute = $model->getDestinationSubdir(); $this->_setModel($model); - // cache de la config et des urls générées + // cache de la config et des chemins des images et des urls générées if (empty($this->_cacheConfig) || empty($this->_cacheUrls)) { $this->_processor = Mage::getSingleton('apijs/python'); $this->_cacheConfig = Mage::app()->useCache('config') ? @json_decode(Mage::app()->loadCache('apijs_config'), true) : null; if (empty($this->_cacheConfig) || !is_array($this->_cacheConfig)) { + $this->_cacheConfig = [ 'date' => date('Y-m-d H:i:s \U\T\C'), 'apijs/general/python' => Mage::getStoreConfigFlag('apijs/general/python'), - 'apijs/general/remove_store_id' => Mage::getStoreConfigFlag('apijs/general/remove_store_id') + 'apijs/general/remove_store_id' => Mage::getStoreConfigFlag('apijs/general/remove_store_id'), + 'list_search' => [], + 'list_replace' => [], ]; + + if (Mage::getStoreConfigFlag('apijs/general/use_link')) { + + $dir = Mage::getBaseDir('media'); + $all = ['wysiwyg/cache', 'catalog/category/cache']; + + $attrs = Mage::getModel('catalog/product')->getMediaAttributes(); + if ($this->_cacheConfig['apijs/general/remove_store_id']) { + foreach ($attrs as $attrCode => $attr) + $all[] = 'catalog/product/cache/'.$attrCode; + } + else { + $storeIds = Mage::getResourceModel('core/store_collection')->getAllIds(); + foreach ($attrs as $attrCode => $attr) { + $all[] = 'catalog/product/cache/'.$attrCode; + foreach ($storeIds as $storeId) + $all[] = 'catalog/product/cache/'.$storeId.'/'.$attrCode; + } + } + + foreach ($all as $full) { + + $short = ''; + $key = crc32($full); + $idx = 1; + + // ajoute l'id pour éviter une boucle infini + foreach (explode('/', $full) as $word) + $short .= substr($word, 0, $idx); + while (in_array('/media/'.$short.'/', $this->_cacheConfig['list_replace'])) + $short .= substr($word.$key, ++$idx, 1); + + if (!file_exists($dir.'/'.$short)) + @mkdir($dir.'/'.$full, 0755, true); + if (!file_exists($dir.'/'.$short)) + @symlink($full, $dir.'/'.$short); + + $this->_cacheConfig['list_search'][] = '/media/'.$full.'/'; + $this->_cacheConfig['list_replace'][] = '/media/'.$short.'/'; + } + } } $this->_cacheUrls = Mage::app()->useCache('block_html') ? @json_decode(Mage::app()->loadCache('apijs_urls'), true) : null; if (empty($this->_cacheUrls) || !is_array($this->_cacheUrls)) { $this->_cacheUrls = [ - 'date' => date('Y-m-d H:i:s \U\T\C') + 'date' => date('Y-m-d H:i:s \U\T\C'), ]; } } @@ -139,7 +185,15 @@ public function getOriginalHeight() { } public function validateUploadFile($path) { - return (is_file($path) && in_array(mime_content_type($path), ['image/svg', 'image/svg+xml'])) ? true : parent::validateUploadFile($path); + + if (!is_file($path)) + return false; + + if (!Mage::getStoreConfigFlag('apijs/general/python')) + return parent::validateUploadFile($path); + + // @todo + return in_array(mime_content_type($path), ['image/svg', 'image/svg+xml', 'image/webp']) ? true : parent::validateUploadFile($path); } public function setBaseFile() { @@ -192,7 +246,20 @@ public function setBaseFile() { } public function cleanUrl(string $url) { - return $this->_cleanUrl ? mb_substr($url, strpos($url, '/', 9)) : $url; + + $url = $this->_cleanUrl ? mb_substr($url, strpos($url, '/', 9)) : $url; + + if ($this->_cacheConfig['apijs/general/remove_store_id']) + $url = str_replace('/cache/'.$this->_storeId.'/', '/cache/', $url); + + if (!empty($this->_cacheConfig['list_search'])) + $url = str_replace($this->_cacheConfig['list_search'], $this->_cacheConfig['list_replace'], $url); + + return $url; + } + + public function hasWebp() { + return Mage::getStoreConfigFlag('apijs/general/python'); } public function __toString() { @@ -219,19 +286,24 @@ public function __toString() { // oui mais non car il faut supprimer les ../ des chemins et des urls // ../../wysiwyg/abc/xyz.jpg // .../media/catalog/product/cache/[0/]wysiwyg/1200x/040ec09b1e35df139433887a97daa66f/../../wysiwyg/abc/xyz.jpg - // .../media/wysiwyg/cache/1200x/040ec09b1e35df139433887a97daa66f/wysiwyg/abc/xyz.jpg + // .../media/wysiwyg/cache/[0/]1200x/040ec09b1e35df139433887a97daa66f/wysiwyg/abc/xyz.jpg $dir = Mage_Cms_Model_Wysiwyg_Config::IMAGE_DIRECTORY; - $filename = $model->getNewFile(); - $filename = str_replace(['../', '//'], ['', '/'], $this->_helper->getWysiwygImageDir(true). - mb_substr($filename, mb_stripos($filename, '/'.$dir.'/') + mb_strlen('/'.$dir.'/'))); + $fileName = $model->getNewFile(); + $fileName = str_replace(['../', '//'], ['', '/'], $this->_helper->getWysiwygImageDir(true). + $this->_storeId.'/'.mb_substr($fileName, mb_stripos($fileName, '/'.$dir.'/') + mb_strlen('/'.$dir.'/'))); - if (array_key_exists($filename, $this->_cacheUrls)) { - //Mage::log(' CACHE HIT '.$filename, Zend_Log::DEBUG); $this->_debugCache++; - $url = $this->_cacheUrls[$filename]; + if ($this->_cacheConfig['apijs/general/remove_store_id']) + $fileName = str_replace('/cache/'.$this->_storeId.'/', '/cache/', $fileName); + if ($this->_webp) + $fileName = mb_substr($fileName, 0, mb_strrpos($fileName, '.')).'.webp'; + + if (array_key_exists($fileName, $this->_cacheUrls)) { + //Mage::log(' CACHE HIT '.$fileName, Zend_Log::DEBUG); $this->_debugCache++; + $url = $this->_cacheUrls[$fileName]; } else { - //Mage::log(' generate '.$filename, Zend_Log::DEBUG); $this->_debugRenew++; - if (!is_file($filename)) { + //Mage::log(' generate '.$fileName, Zend_Log::DEBUG); $this->_debugRenew++; + if (!is_file($fileName)) { if (!empty($this->_scheduleRotate)) $model->rotate($this->getAngle()); @@ -242,17 +314,18 @@ public function __toString() { // $url = $model->saveFile()->getUrl(); // oui mais non car il faut supprimer les ../ des chemins et des urls - $model->getImageProcessor()->save($filename); + $model->getImageProcessor()->save($fileName); } // return $model->getUrl(); // .../media/catalog/product/cache/[0/]wysiwyg/1200x/040ec09b1e35df139433887a97daa66f/../../wysiwyg/abc/xyz.jpg - // .../media/wysiwyg/cache/1200x/040ec09b1e35df139433887a97daa66f/wysiwyg/abc/xyz.jpg - $url = $this->cleanUrl($model->getUrl()); - $url = str_replace('../', '', preg_replace('#catalog/product/cache/(?:\d+/)?'.$dir.'/#', $dir.'/cache/', $url)); + // .../media/wysiwyg/cache/[0/]1200x/040ec09b1e35df139433887a97daa66f/wysiwyg/abc/xyz.jpg + $url = $model->getUrl(); + $url = str_replace(['catalog/product/cache/'.$dir.'/', 'catalog/product/cache/'.$this->_storeId.'/'.$dir.'/', '../'], [$dir.'/cache/', $dir.'/cache/'.$this->_storeId.'/', ''], $url); + $url = $this->cleanUrl($url); // cache - $this->_cacheUrls[$filename] = $url; + $this->_cacheUrls[$fileName] = $url; } } else if ($model->getDestinationSubdir() == 'category') { @@ -261,16 +334,21 @@ public function __toString() { // ../category/xyz.jpg // .../media/catalog/product/cache/[0/]category/1200x/040ec09b1e35df139433887a97daa66f/../category/xyz.jpg // .../media/catalog/category/cache/[0/]1200x/040ec09b1e35df139433887a97daa66f/xyz.jpg - $filename = $model->getNewFile(); - $filename = str_replace(['../', '//', '/category/', '/catalog/product/'], ['', '/', '/', '/catalog/category/'], $filename); + $fileName = $model->getNewFile(); + $fileName = str_replace(['../', '//', '/category/', '/catalog/product/'], ['', '/', '/', '/catalog/category/'], $fileName); + + if ($this->_cacheConfig['apijs/general/remove_store_id']) + $fileName = str_replace('/cache/'.$this->_storeId.'/', '/cache/', $fileName); + if ($this->_webp) + $fileName = mb_substr($fileName, 0, mb_strrpos($fileName, '.')).'.webp'; - if (array_key_exists($filename, $this->_cacheUrls)) { - //Mage::log(' CACHE HIT '.$filename, Zend_Log::DEBUG); $this->_debugCache++; - $url = $this->_cacheUrls[$filename]; + if (array_key_exists($fileName, $this->_cacheUrls)) { + //Mage::log(' CACHE HIT '.$fileName, Zend_Log::DEBUG); $this->_debugCache++; + $url = $this->_cacheUrls[$fileName]; } else { - //Mage::log(' generate '.$filename, Zend_Log::DEBUG); $this->_debugRenew++; - if (!is_file($filename)) { + //Mage::log(' generate '.$fileName, Zend_Log::DEBUG); $this->_debugRenew++; + if (!is_file($fileName)) { if (!empty($this->_scheduleRotate)) $model->rotate($this->getAngle()); @@ -281,34 +359,38 @@ public function __toString() { // $url = $model->saveFile()->getUrl(); // oui mais non car il faut supprimer les ../ des chemins et des urls - $model->getImageProcessor()->save($filename); + $model->getImageProcessor()->save($fileName); } // return $model->getUrl(); // .../media/catalog/product/cache/[0/]category/1200x/040ec09b1e35df139433887a97daa66f/../category/xyz.jpg // .../media/catalog/category/cache/[0/]1200x/040ec09b1e35df139433887a97daa66f/xyz.jpg - $url = $this->cleanUrl($model->getUrl()); + $url = $model->getUrl(); $url = str_replace(['../', '/category/', '/catalog/product/'], ['', '/', '/catalog/category/'], $url); + $url = $this->cleanUrl($url); // cache - $this->_cacheUrls[$filename] = $url; + $this->_cacheUrls[$fileName] = $url; } } else { // if ($model->isCached()) // /x/y/xyz.jpg // .../media/catalog/product/cache/[0/]category/1200x/040ec09b1e35df139433887a97daa66f/x/y/xyz.jpg - $filename = $model->getNewFile(); + $fileName = $model->getNewFile(); + if ($this->_cacheConfig['apijs/general/remove_store_id']) - $filename = preg_replace('#/cache/\d+/#', '/cache/', $filename); + $fileName = str_replace('/cache/'.$this->_storeId.'/', '/cache/', $fileName); + if ($this->_webp) + $fileName = mb_substr($fileName, 0, mb_strrpos($fileName, '.')).'.webp'; - if (array_key_exists($filename, $this->_cacheUrls)) { - //Mage::log(' CACHE HIT '.$filename, Zend_Log::DEBUG); $this->_debugCache++; - $url = $this->_cacheUrls[$filename]; + if (array_key_exists($fileName, $this->_cacheUrls)) { + //Mage::log(' CACHE HIT '.$fileName, Zend_Log::DEBUG); $this->_debugCache++; + $url = $this->_cacheUrls[$fileName]; } else { - //Mage::log(' generate '.$filename, Zend_Log::DEBUG); $this->_debugRenew++; - if (!is_file($filename)) { + //Mage::log(' generate '.$fileName, Zend_Log::DEBUG); $this->_debugRenew++; + if (!is_file($fileName)) { if (!empty($this->_scheduleRotate)) $model->rotate($this->getAngle()); @@ -319,17 +401,15 @@ public function __toString() { // $url = $model->saveFile()->getUrl(); // oui mais non car il faut supprimer les ../ des chemins et des urls - $model->getImageProcessor()->save($filename); + $model->getImageProcessor()->save($fileName); } // return $model->getUrl(); // .../media/catalog/product/cache/[0/]category/1200x/040ec09b1e35df139433887a97daa66f/x/y/xyz.jpg $url = $this->cleanUrl($model->getUrl()); - if ($this->_cacheConfig['apijs/general/remove_store_id']) - $url = preg_replace('#/cache/\d+/#', '/cache/', $url); // cache - $this->_cacheUrls[$filename] = $url; + $this->_cacheUrls[$fileName] = $url; } } } @@ -340,7 +420,7 @@ public function __toString() { //Mage::log(' toString '.number_format(microtime(true) - $go, 4), Zend_Log::DEBUG); //Mage::log(' closing file after '.number_format(microtime(true) - $this->_debugStart, 4).' / since first init '.number_format(microtime(true) - $this->_debugBegin, 4).' / total '.$this->_debugCount.' = cache '.$this->_debugCache.' + generate '.$this->_debugRenew, Zend_Log::DEBUG); - return $url; + return $this->_webp ? mb_substr($url, 0, mb_strrpos($url, '.')).'.webp' : $url; } public function __destruct() { diff --git a/src/app/code/community/Luigifab/Apijs/Model/Observer.php b/src/app/code/community/Luigifab/Apijs/Model/Observer.php index 26b5e7e..4e1d186 100644 --- a/src/app/code/community/Luigifab/Apijs/Model/Observer.php +++ b/src/app/code/community/Luigifab/Apijs/Model/Observer.php @@ -1,7 +1,7 @@ * https://www.luigifab.fr/openmage/apijs @@ -19,6 +19,15 @@ class Luigifab_Apijs_Model_Observer extends Luigifab_Apijs_Helper_Data { + // EVENT admin_system_config_changed_section_apijs (adminhtml) + public function clearCache(Varien_Event_Observer $observer) { + + Mage::app()->cleanCache(); + Mage::dispatchEvent('adminhtml_cache_flush_system'); + + Mage::getSingleton('adminhtml/session')->addSuccess(str_replace('Magento', 'OpenMage', Mage::helper('adminhtml')->__('The Magento cache storage has been flushed.'))); + } + // EVENT catalog_category_delete_commit_after (global) public function removeCategoryImages(Varien_Event_Observer $observer) { @@ -51,7 +60,7 @@ public function removeProductImages(Varien_Event_Observer $observer) { $database = Mage::getSingleton('core/resource'); $reader = $database->getConnection('core_read'); - $table = $database->getTableName('catalog_product_entity_varchar'); + $table = $database->getTableName('catalog_product_entity_media_gallery'); $entityType = Mage::getSingleton('eav/config')->getEntityType(Mage_Catalog_Model_Product::ENTITY)->getId(); $attributes = Mage::getResourceModel('eav/entity_attribute_collection') diff --git a/src/app/code/community/Luigifab/Apijs/Model/Python.php b/src/app/code/community/Luigifab/Apijs/Model/Python.php index 331e24a..373d4fe 100644 --- a/src/app/code/community/Luigifab/Apijs/Model/Python.php +++ b/src/app/code/community/Luigifab/Apijs/Model/Python.php @@ -1,7 +1,7 @@ * https://www.luigifab.fr/openmage/apijs @@ -22,6 +22,7 @@ class Luigifab_Apijs_Model_Python extends Varien_Image { // singleton protected $_python; protected $_quality = 100; + protected $_imageSize = []; protected $_files = []; protected $_pids = []; protected $_core = 1; @@ -86,23 +87,35 @@ public function open() { return $this; } - public function save($destination = null, $newFilename = null) { + public function save($destination = null, $newFilename = null, $immediate = false) { $this->open(); try { - $core = max(1, $this->_core - 1); // pour laisser 1 coeur de libre + $core = max(1, $this->_core - 2); while (count($this->_pids) >= $core) { foreach ($this->_pids as $key => $pid) { - if (!file_exists('/proc/'.$pid)) - unset($this->_pids[$key]); - else + if (file_exists('/proc/'.$pid)) clearstatcache('/proc/'.$pid); + else + unset($this->_pids[$key]); } usleep(100000); // 0.1 s } - $cmd = sprintf('%s %s %s %s %d %d %d %s >/dev/null 2>&1 & echo $!', + exec('ps aux | grep Apijs/lib/image.py', $runs); + $core = ceil($core * 1.5); + while ((count($runs) - 1) >= $core) { + usleep(90000); // 0.09 s + $runs = []; + exec('ps aux | grep Apijs/lib/image.py', $runs); + } + + $dir = Mage::getBaseDir('log'); + if (!is_dir($dir)) + @mkdir($dir, 0755); + + $cmd = sprintf('%s %s %s %s %d %d %d %s >> %s 2>&1'.($immediate ? '' : ' & echo $!'), $this->_python, str_replace('Apijs/etc', 'Apijs/lib/image.py', Mage::getModuleDir('etc', 'Luigifab_Apijs')), escapeshellarg($this->_fileName), @@ -113,16 +126,23 @@ public function save($destination = null, $newFilename = null) { empty($this->_resizeFixed) ? (empty($this->_resizeHeight) ? 0 : $this->_resizeHeight) : (empty($this->_resizeHeight) ? (empty($this->_resizeWidth) ? 0 : $this->_resizeWidth) : $this->_resizeHeight), - // uniquement pour JPEG (ignoré et toujouts à 9 pour PNG, inutile pour GIF) + // uniquement pour JPEG (ignoré et toujours à 9 pour PNG, inutile pour GIF) $this->_quality, - empty($this->_resizeFixed) ? '' : 'fixed' + empty($this->_resizeFixed) ? '' : 'fixed', + $dir.'/apijs.log' ); // ne génère pas deux fois la même image if (!in_array($destination, $this->_files)) { + + //Mage::log(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), Zend_Log::DEBUG, 'apijs.log'); Mage::log($cmd, Zend_Log::DEBUG, 'apijs.log'); + $this->_files[] = $destination; $this->_pids[] = exec($cmd); + + if ($immediate) + array_pop($this->_pids); } $this->reset(); @@ -139,10 +159,10 @@ public function waitThreads() { while (!empty($this->_pids)) { foreach ($this->_pids as $key => $pid) { - if (!file_exists('/proc/'.$pid)) - unset($this->_pids[$key]); - else + if (file_exists('/proc/'.$pid)) clearstatcache('/proc/'.$pid); + else + unset($this->_pids[$key]); } usleep(100000); // 0.1 s } @@ -159,12 +179,12 @@ public function getOriginalWidth() { if ($this->isSvg()) return 0; - if (empty($this->_imagesize)) { + if (empty($this->_imageSize)) { $this->open(); - $this->_imagesize = (array) getimagesize($this->_fileName); // (yes) + $this->_imageSize = (array) getimagesize($this->_fileName); // (yes) } - return $this->_imagesize[0] ?? 0; + return $this->_imageSize[0] ?? 0; } public function getOriginalHeight() { @@ -172,12 +192,12 @@ public function getOriginalHeight() { if ($this->isSvg()) return 0; - if (empty($this->_imagesize)) { + if (empty($this->_imageSize)) { $this->open(); - $this->_imagesize = (array) getimagesize($this->_fileName); // (yes) + $this->_imageSize = (array) getimagesize($this->_fileName); // (yes) } - return $this->_imagesize[1] ?? 0; + return $this->_imageSize[1] ?? 0; } public function getMimeType() { @@ -185,12 +205,12 @@ public function getMimeType() { if ($this->isSvg()) return 'image/svg+xml'; - if (empty($this->_imagesize)) { + if (empty($this->_imageSize)) { $this->open(); - $this->_imagesize = (array) getimagesize($this->_fileName); // (yes) + $this->_imageSize = (array) getimagesize($this->_fileName); // (yes) } - return $this->_imagesize[2] ?? null; + return $this->_imageSize[2] ?? null; } public function isSvg() { @@ -292,6 +312,7 @@ public function setWatermarkHeigth($value) { public function setFilename($value) { $this->_fileName = $value; + $this->_imageSize = []; $this->_svg = null; return $this; } @@ -325,6 +346,7 @@ public function reset() { $this->_watermarkWidth = null; $this->_watermarkHeigth = null; $this->_fileName = null; + $this->_imageSize = []; $this->_svg = null; $this->_resizeFixed = null; return $this; diff --git a/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Media.php b/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Media.php index 1cc2ddf..e9a1a9c 100644 --- a/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Media.php +++ b/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Media.php @@ -1,7 +1,7 @@ * Copyright 2019-2022 | Fabrice Creuzot @@ -52,7 +52,7 @@ public function afterLoad($object) { public function afterSave($object) { - if ($object->getIsDuplicate() == true) { + if ($object->getIsDuplicate()) { $this->duplicate($object); return; } @@ -91,7 +91,7 @@ public function afterSave($object) { $image['value_id'] = $this->_getResource()->insertGallery([ 'entity_id' => $object->getId(), 'attribute_id' => $this->getAttribute()->getId(), - 'value' => $image['file'] + 'value' => $image['file'], ]); } diff --git a/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Mediares.php b/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Mediares.php index fe3e6e0..72c92eb 100644 --- a/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Mediares.php +++ b/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Mediares.php @@ -1,7 +1,7 @@ * Copyright 2019-2022 | Fabrice Creuzot diff --git a/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Validator.php b/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Validator.php index d6642c1..38b1827 100644 --- a/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Validator.php +++ b/src/app/code/community/Luigifab/Apijs/Model/Rewrite/Validator.php @@ -1,7 +1,7 @@ * https://www.luigifab.fr/openmage/apijs @@ -21,16 +21,22 @@ class Luigifab_Apijs_Model_Rewrite_Validator extends Mage_Core_Model_File_Valida public function validate($path) { - if (is_file($path) && in_array(mime_content_type($path), ['image/svg', 'image/svg+xml', 'application/pdf'])) - return true; - if (!Mage::getStoreConfigFlag('apijs/general/python')) return parent::validate($path); + // @todo + // svg pdf + if (is_file($path) && in_array(mime_content_type($path), ['image/svg', 'image/svg+xml', 'application/pdf'])) + return true; + // replace tmp image with re-sampled copy to exclude images with malicious data + // jpg jpeg gif png + webp try { $processor = Mage::getSingleton('apijs/python'); - $processor->setFilename($path)->resize($processor->getOriginalWidth(), $processor->getOriginalHeight())->save($path); + $processor->setFilename($path); + $processor->quality(100); + $processor->resize($processor->getOriginalWidth(), $processor->getOriginalHeight()); + $processor->save($path, null, true); } catch (Throwable $t) { Mage::throwException('Invalid image: '.$t->getMessage()); diff --git a/src/app/code/community/Luigifab/Apijs/Model/Useragentparser.php b/src/app/code/community/Luigifab/Apijs/Model/Useragentparser.php index cab2e27..1bf8613 100644 --- a/src/app/code/community/Luigifab/Apijs/Model/Useragentparser.php +++ b/src/app/code/community/Luigifab/Apijs/Model/Useragentparser.php @@ -1,10 +1,10 @@ + * Copyright 2013-2022 | Jesse G. Donat * https://github.com/donatj/PhpUserAgent * - * Copyright 2019-2021 | Fabrice Creuzot (luigifab) - * https://gist.github.com/luigifab/4cb373e75f3cd2f342ca6bc25504b149 (1.5.0-fork1) + * Copyright 2019-2022 | Fabrice Creuzot (luigifab) + * https://gist.github.com/luigifab/4cb373e75f3cd2f342ca6bc25504b149 (1.6.1-fork1) * * Parses a user agent string into its important parts * Licensed under the MIT License @@ -49,14 +49,14 @@ public function parse($userAgent = null) { preg_match_all( // ["browser" => ["Firefox"...], "version" => ["45.0"...]] - '%(?PCamino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|Edg|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi\/MiuiBrowser|Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|Valve\ Steam\ Tenfoot|NintendoBrowser|PLAYSTATION\ (\d|Vita)+)\)?;?(?:[:/ ](?P[0-9A-Z.]+)|/[A-Z]*)%ix', + '%(?PCamino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|(?-i:Edge)|EdgA?|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi/MiuiBrowser|Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|Valve\ Steam\ Tenfoot|NintendoBrowser|PLAYSTATION\ (\d|Vita)+)\)?;?(?:[:/ ](?P[\dA-Z.]+)|/[A-Z]*)%ix', $userAgent, $result); // If nothing matched, return null (to avoid undefined index errors) if (!isset($result['browser'][0], $result['version'][0])) { - if (preg_match('%^(?!Mozilla)(?P[A-Z0-9\-]+)(/(?P[0-9A-Z.]+))?%ix', $userAgent, $result)) { + if (preg_match('%^(?!Mozilla)(?P[A-Z\d\-]+)(/(?P[\dA-Z.]+))?%ix', $userAgent, $result)) { return [ 'platform' => $platform ?: null, 'browser' => $result['browser'], @@ -67,7 +67,7 @@ public function parse($userAgent = null) { } - if (preg_match('/rv:(?P[0-9A-Z.]+)/i', $userAgent, $rv_result)) { + if (preg_match('/rv:(?P[\dA-Z.]+)/i', $userAgent, $rv_result)) { $rv_result = $rv_result['version']; } @@ -78,7 +78,7 @@ public function parse($userAgent = null) { $key = 0; $val = ''; - if ($this->findT($lowerBrowser, ['OPR' => 'Opera', 'Facebot' => 'iMessageBot', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge', 'XiaoMi/MiuiBrowser' => 'MiuiBrowser'], $key, $browser)) { + if ($this->findT($lowerBrowser, ['OPR' => 'Opera', 'Facebot' => 'iMessageBot', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge', 'EdgA' => 'Edge', 'XiaoMi/MiuiBrowser' => 'MiuiBrowser'], $key, $browser)) { $version = is_numeric(substr($result['version'][$key], 0, 1)) ? $result['version'][$key] : null; } else if ($this->find($lowerBrowser, 'Playstation Vita', $key, $platform)) { diff --git a/src/app/code/community/Luigifab/Apijs/controllers/Apijs/MediaController.php b/src/app/code/community/Luigifab/Apijs/controllers/Apijs/MediaController.php index 37816d3..4992dcd 100644 --- a/src/app/code/community/Luigifab/Apijs/controllers/Apijs/MediaController.php +++ b/src/app/code/community/Luigifab/Apijs/controllers/Apijs/MediaController.php @@ -1,7 +1,7 @@ * Copyright 2019-2022 | Fabrice Creuzot @@ -87,11 +87,18 @@ public function uploadWidgetAction() { $exts[] = 'pdf'; // sauvegarde du ou des fichiers - $keys = array_keys($_FILES); + $keys = array_keys($_FILES['myimage']['name']); foreach ($keys as $key) { try { - $uploader = new Varien_File_Uploader($key); + $uploader = new Varien_File_Uploader([ + 'name' => $_FILES['myimage']['name'][$key], + 'type' => $_FILES['myimage']['type'][$key], + 'tmp_name' => $_FILES['myimage']['tmp_name'][$key], + 'error' => $_FILES['myimage']['error'][$key], + 'size' => $_FILES['myimage']['size'][$key], + ]); + $uploader->setAllowedExtensions($exts); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(false); @@ -146,11 +153,18 @@ public function uploadProductAction() { Mage::throwException('No files uploaded.'); // sauvegarde du ou des fichiers - $keys = array_keys($_FILES); + $keys = array_keys($_FILES['myimage']['name']); foreach ($keys as $key) { try { - $uploader = new Varien_File_Uploader($key); + $uploader = new Varien_File_Uploader([ + 'name' => $_FILES['myimage']['name'][$key], + 'type' => $_FILES['myimage']['type'][$key], + 'tmp_name' => $_FILES['myimage']['tmp_name'][$key], + 'error' => $_FILES['myimage']['error'][$key], + 'size' => $_FILES['myimage']['size'][$key], + ]); + $uploader->setAllowedExtensions($storage->getAllowedExtensions('image')); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(true); @@ -266,8 +280,10 @@ public function saveAction() { if (!empty($gallery)) { $product->setData('media_gallery', $gallery); - foreach ($gallery['values'] as $code => $value) - $product->setData($code, $value); + if (!empty($gallery['values']) && is_array($gallery['values'])) { + foreach ($gallery['values'] as $code => $value) + $product->setData($code, $value); + } if ($product->hasDataChanges()) $product->save(); @@ -331,7 +347,7 @@ public function removeAction() { $writer->query('DELETE FROM '.$cpev.' WHERE entity_id = ? AND value = ?', [$productId, $filepath]); // si par défaut // supprime enfin les fichiers s'ils ne sont pas utilisés dans d'autres produits - if ($reader->fetchOne('SELECT count(*) FROM '.$cpev.' WHERE value = ?', [$filepath]) == 0) + if ($reader->fetchOne('SELECT count(*) FROM '.$cpemg.' WHERE value = ?', [$filepath]) == 0) $help->removeFiles($help->getCatalogProductImageDir(), $filename); // pas uniquement dans le cache } diff --git a/src/app/code/community/Luigifab/Apijs/controllers/Apijs/WysiwygController.php b/src/app/code/community/Luigifab/Apijs/controllers/Apijs/WysiwygController.php index 46f6c04..ed3135c 100644 --- a/src/app/code/community/Luigifab/Apijs/controllers/Apijs/WysiwygController.php +++ b/src/app/code/community/Luigifab/Apijs/controllers/Apijs/WysiwygController.php @@ -1,7 +1,7 @@ * Copyright 2019-2022 | Fabrice Creuzot @@ -141,12 +141,12 @@ public function renameFileAction() { // vérifie que l'extension du fichier ne change pas trop if (mb_strtolower(pathinfo($newfile, PATHINFO_EXTENSION)) != mb_strtolower(pathinfo($oldfile, PATHINFO_EXTENSION))) { - $html = preg_replace('#\.([0-9a-zA-Z.]+)#', '.[b]$1[/b]', basename($oldfile).' ~ '.basename($newfile)); + $html = preg_replace('#\.([a-zA-Z\d.]+)#', '.[b]$1[/b]', basename($oldfile).' ~ '.basename($newfile)); Mage::throwException($help->__('The file extension can not be changed.').'[br][em]'.$html.'[/em]'); } // s'il faut déplacer le fichier dans un autre dossier - if (mb_strpos($name, '/') !== false) { + if (str_contains($name, '/')) { // supprime le dossier parent lorsque l'enfant est .. tant qu'il y en a while (mb_stripos($newfile, '/../') !== false) diff --git a/src/app/code/community/Luigifab/Apijs/etc/config.xml b/src/app/code/community/Luigifab/Apijs/etc/config.xml index 68ff2b1..f0229b0 100644 --- a/src/app/code/community/Luigifab/Apijs/etc/config.xml +++ b/src/app/code/community/Luigifab/Apijs/etc/config.xml @@ -1,7 +1,7 @@