diff --git a/core/class/.htaccess b/core/class/.htaccess new file mode 100644 index 0000000..3b355e3 --- /dev/null +++ b/core/class/.htaccess @@ -0,0 +1,3 @@ +# Bloque l'accès à la librairie +Order deny,allow +Deny from all \ No newline at end of file diff --git a/core/class/autoload.php b/core/class/autoload.php new file mode 100644 index 0000000..ac507c0 --- /dev/null +++ b/core/class/autoload.php @@ -0,0 +1,16 @@ + 0 ){ + return false; + } + if(function_exists('file_get_contents') && + ini_get('allow_url_fopen') ){ + $url_get_contents_data = @file_get_contents($url); // Masque un warning éventuel + }elseif(function_exists('curl_version')){ + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_URL, $url); + $url_get_contents_data = curl_exec($ch); + curl_close($ch); + }elseif(function_exists('fopen') && + function_exists('stream_get_contents') && + ini_get('allow_url_fopen')){ + $handle = fopen ($url, "r"); + $url_get_contents_data = stream_get_contents($handle); + }else{ + $url_get_contents_data = false; + } + return $url_get_contents_data; + } + + /** + * Retourne les valeurs d'une colonne du tableau de données + * @param array $array Tableau cible + * @param string $column Colonne à extraire + * @param string $sort Type de tri à appliquer au tableau (SORT_ASC, SORT_DESC, ou null) + * @return array + */ + public static function arrayCollumn($array, $column, $sort = null) { + $newArray = []; + if(empty($array) === false) { + $newArray = array_map(function($element) use($column) { + return $element[$column]; + }, $array); + switch($sort) { + case 'SORT_ASC': + asort($newArray); + break; + case 'SORT_DESC': + arsort($newArray); + break; + } + } + return $newArray; + } + + + /** + * Génère un backup des données de site + * @param string $folder dossier de sauvegarde + * @param array $exclude dossier exclus + * @return string nom du fichier de sauvegarde + */ + + public static function autoBackup($folder, $filter = ['backup','tmp'] ) { + // Creation du ZIP + $baseName = str_replace('/','',helper::baseUrl(false,false)); + $baseName = empty($baseName) ? 'DeltaCMS' : $baseName; + $fileName = $baseName . '-backup-' . date('Y-m-d-H-i-s', time()) . '.zip'; + $zip = new ZipArchive(); + $zip->open($folder . $fileName, ZipArchive::CREATE | ZipArchive::OVERWRITE); + $directory = 'site/'; + //$filter = array('backup','tmp','file'); + $files = new RecursiveIteratorIterator( + new RecursiveCallbackFilterIterator( + new RecursiveDirectoryIterator( + $directory, + RecursiveDirectoryIterator::SKIP_DOTS + ), + function ($fileInfo, $key, $iterator) use ($filter) { + return $fileInfo->isFile() || !in_array($fileInfo->getBaseName(), $filter); + } + ) + ); + foreach ($files as $name => $file) { + if (!$file->isDir()) { + $filePath = $file->getRealPath(); + $relativePath = substr($filePath, strlen(realpath($directory)) + 1); + $zip->addFile($filePath, $relativePath); + } + } + $zip->close(); + return ($fileName); + } + + + + /** + * Retourne la liste des modules installés dans un tableau composé + * du nom réel + * du numéro de version + */ + public static function getModules() { + $modules = array(); + $dirs = array_diff(scandir('module'), array('..', '.')); + foreach ($dirs as $key => $value) { + // Dossier non vide + if (file_exists('module/' . $value . '/' . $value . '.php')) { + // Lire les constantes en gérant les erreurs de nom de classe + try { + $class_reflex = new \ReflectionClass($value); + $class_constants = $class_reflex->getConstants(); + // Constante REALNAME + if (array_key_exists('REALNAME', $class_constants)) { + $realName = $value::REALNAME; + } else { + $realName = ucfirst($value); + } + // Constante VERSION + if (array_key_exists('VERSION', $class_constants)) { + $version = $value::VERSION; + } else { + $version = '0.0'; + } + // Constante UPDATE + if (array_key_exists('UPDATE', $class_constants)) { + $update = $value::UPDATE; + } else { + $update = '0.0'; + } + // Constante DELETE + if (array_key_exists('DELETE', $class_constants)) { + $delete = $value::DELETE; + } else { + $delete = true; + } + // Constante DATADIRECTORY + if ( array_key_exists('DATADIRECTORY', $class_constants)) { + $dataDirectory = $value::DATADIRECTORY; + } else { + $dataDirectory = ''; + } + // Affection + $modules [$value] = [ + 'realName' => $realName, + 'version' => $version, + 'update' => $update, + 'delete' => $delete, + 'dataDirectory' => $dataDirectory + ]; + + } catch (Exception $e){ + // on ne fait rien + } + } + } + return($modules); + } + + + + /** + * Retourne true si le protocole est en TLS + * @return bool + */ + public static function isHttps() { + if( + (empty($_SERVER['HTTPS']) === false AND $_SERVER['HTTPS'] !== 'off') + OR $_SERVER['SERVER_PORT'] === 443 + ) { + return true; + } else { + return false; + } + } + + + /** + * Retourne l'URL de base du site + * @param bool $queryString Affiche ou non le point d'interrogation + * @param bool $host Affiche ou non l'host + * @return string + */ + public static function baseUrl($queryString = true, $host = true) { + // Protocole + $protocol = helper::isHttps() === true ? 'https://' : 'http://'; + // Host + if($host) { + $host = $protocol . $_SERVER['HTTP_HOST']; + } + // Pathinfo + $pathInfo = pathinfo($_SERVER['PHP_SELF']); + // Querystring + if($queryString AND helper::checkRewrite() === false) { + $queryString = '?'; + } + else { + $queryString = ''; + } + return $host . rtrim($pathInfo['dirname'], ' ' . DIRECTORY_SEPARATOR) . '/' . $queryString; + } + + /** + * Check le statut de l'URL rewriting + * @return bool + */ + public static function checkRewrite() { + if(self::$rewriteStatus === null) { + // Ouvre et scinde le fichier .htaccess + $htaccess = explode('# URL rewriting', file_get_contents('.htaccess')); + // Retourne un boolean en fonction du contenu de la partie réservée à l'URL rewriting + self::$rewriteStatus = (empty($htaccess[1]) === false); + } + return self::$rewriteStatus; + } + + /** + * Renvoie le numéro de version de DeltaCMS est en ligne + * @return string + */ + public static function getOnlineVersion() { + return (helper::urlGetContents(common::DELTA_UPDATE_URL . common::DELTA_UPDATE_CHANNEL . '/version')); + } + + + /** + * Check si une nouvelle version de DeltaCMS est disponible + * @return bool + */ + public static function checkNewVersion() { + + if($version = helper::getOnlineVersion()) { + return ((version_compare(common::DELTA_VERSION,$version)) === -1); + } + else { + return false; + } + } + + + /** + * Génère des variations d'une couleur + * @param string $rgba Code rgba de la couleur + * @return array + */ + public static function colorVariants($rgba) { + preg_match('#\(+(.*)\)+#', $rgba, $matches); + $rgba = explode(', ', $matches[1]); + return [ + 'normal' => 'rgba(' . $rgba[0] . ',' . $rgba[1] . ',' . $rgba[2] . ',' . $rgba[3] . ')', + 'darken' => 'rgba(' . max(0, $rgba[0] - 15) . ',' . max(0, $rgba[1] - 15) . ',' . max(0, $rgba[2] - 15) . ',' . $rgba[3] . ')', + 'veryDarken' => 'rgba(' . max(0, $rgba[0] - 20) . ',' . max(0, $rgba[1] - 20) . ',' . max(0, $rgba[2] - 20) . ',' . $rgba[3] . ')', + 'text' => self::relativeLuminanceW3C($rgba) > .22 ? "#222" : "#DDD", + 'rgb' => 'rgb(' . $rgba[0] . ',' . $rgba[1] . ',' . $rgba[2] . ')' + ]; + } + + /** + * Supprime un cookie + * @param string $cookieKey Clé du cookie à supprimer + */ + public static function deleteCookie($cookieKey) { + unset($_COOKIE[$cookieKey]); + setcookie($cookieKey, '', time() - 3600, helper::baseUrl(false, false), '', false, true); + } + + /** + * Filtre une chaîne en fonction d'un tableau de données + * @param string $text Chaîne à filtrer + * @param int $filter Type de filtre à appliquer + * @return string + */ + public static function filter($text, $filter) { + $text = trim($text); + switch($filter) { + case self::FILTER_BOOLEAN: + $text = (bool) $text; + break; + case self::FILTER_DATETIME: + $timezone = new DateTimeZone(core::$timezone); + $date = new DateTime($text); + $date->setTimezone($timezone); + $text = (int) $date->format('U'); + break; + case self::FILTER_FLOAT: + $text = filter_var($text, FILTER_SANITIZE_NUMBER_FLOAT); + $text = (float) $text; + break; + case self::FILTER_ID: + $text = mb_strtolower($text, 'UTF-8'); + $text = strip_tags(str_replace( + explode(',', 'á,à,â,ä,ã,å,ç,é,è,ê,ë,í,ì,î,ï,ñ,ó,ò,ô,ö,õ,ú,ù,û,ü,ý,ÿ,\',", '), + explode(',', 'a,a,a,a,a,a,c,e,e,e,e,i,i,i,i,n,o,o,o,o,o,u,u,u,u,y,y,-,-,-'), + $text + )); + $text = preg_replace('/([^a-z0-9-])/', '', $text); + // Supprime les emoji + $text = preg_replace('/[[:^print:]]/', '', $text); + // Supprime les tirets en fin de chaine (emoji en fin de nom) + $text = rtrim($text,'-'); + // Cas où un identifiant est vide + if (empty($text)) { + $text = uniqid(''); + } + // Un ID ne peut pas être un entier, pour éviter les conflits avec le système de pagination + if(intval($text) !== 0) { + $text = '_' . $text; + } + break; + case self::FILTER_INT: + $text = (int) filter_var($text, FILTER_SANITIZE_NUMBER_INT); + break; + case self::FILTER_MAIL: + $text = filter_var($text, FILTER_SANITIZE_EMAIL); + break; + case self::FILTER_PASSWORD: + $text = password_hash($text, PASSWORD_BCRYPT); + break; + case self::FILTER_STRING_LONG: + $text = mb_substr(filter_var($text, FILTER_SANITIZE_STRING), 0, 500000); + break; + case self::FILTER_STRING_SHORT: + $text = mb_substr(filter_var($text, FILTER_SANITIZE_STRING), 0, 500); + break; + case self::FILTER_TIMESTAMP: + $text = date('Y-m-d H:i:s', $text); + break; + case self::FILTER_URL: + $text = filter_var($text, FILTER_SANITIZE_URL); + break; + } + return $text; + } + + /** + * Incrémente une clé en fonction des clés ou des valeurs d'un tableau + * @param mixed $key Clé à incrémenter + * @param array $array Tableau à vérifier + * @return string + */ + public static function increment($key, $array = []) { + // Pas besoin d'incrémenter si la clef n'existe pas + if($array === []) { + return $key; + } + // Incrémente la clef + else { + // Si la clef est numérique elle est incrémentée + if(is_numeric($key)) { + $newKey = $key; + while(array_key_exists($newKey, $array) OR in_array($newKey, $array)) { + $newKey++; + } + } + // Sinon l'incrémentation est ajoutée après la clef + else { + $i = 2; + $newKey = $key; + while(array_key_exists($newKey, $array) OR in_array($newKey, $array)) { + $newKey = $key . '-' . $i; + $i++; + } + } + return $newKey; + } + } + + /** + * Minimise du css + * @param string $css Css à minimiser + * @return string + */ + public static function minifyCss($css) { + // Supprime les commentaires + $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css); + // Supprime les tabulations, espaces, nouvelles lignes, etc... + $css = str_replace(["\r\n", "\r", "\n" ,"\t", ' ', ' ', ' '], '', $css); + $css = preg_replace(['(( )+{)', '({( )+)'], '{', $css); + $css = preg_replace(['(( )+})', '(}( )+)', '(;( )*})'], '}', $css); + $css = preg_replace(['(;( )+)', '(( )+;)'], ';', $css); + // Retourne le css minifié + return $css; + } + + /** + * Minimise du js + * @param string $js Js à minimiser + * @return string + */ + public static function minifyJs($js) { + // Supprime les commentaires + $js = preg_replace('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/|\s*(?getUrl() + * @param string $item pagination nombre d'éléments par page + * @param null|int $sufix Suffixe de l'url + * @return array + */ + public static function pagination($array, $url, $item, $sufix = null) { + // Scinde l'url + $url = explode('/', $url); + // Url de pagination + $urlPagination = is_numeric($url[count($url) - 1]) ? array_pop($url) : 1; + // Url de la page courante + $urlCurrent = implode('/', $url); + // Nombre d'éléments à afficher + $nbElements = count($array); + // Nombre de page + $nbPage = ceil($nbElements / $item); + // Page courante + $currentPage = is_numeric($urlPagination) ? self::filter($urlPagination, self::FILTER_INT) : 1; + // Premier élément de la page + $firstElement = ($currentPage - 1) * $item; + // Dernier élément de la page + $lastElement = $firstElement + $item; + $lastElement = ($lastElement > $nbElements) ? $nbElements : $lastElement; + // Mise en forme de la liste des pages + $pages = ''; + if($nbPage > 1) { + for($i = 1; $i <= $nbPage; $i++) { + $disabled = ($i === $currentPage) ? ' class="disabled"' : false; + $pages .= '' . $i . ''; + } + $pages = ''; + } + // Retourne un tableau contenant les informations sur la pagination + return [ + 'first' => $firstElement, + 'last' => $lastElement, + 'pages' => $pages + ]; + } + + /** + * Calcul de la luminance relative d'une couleur + */ + public static function relativeLuminanceW3C($rgba) { + // Conversion en sRGB + $RsRGB = $rgba[0] / 255; + $GsRGB = $rgba[1] / 255; + $BsRGB = $rgba[2] / 255; + // Ajout de la transparence + $RsRGBA = $rgba[3] * $RsRGB + (1 - $rgba[3]); + $GsRGBA = $rgba[3] * $GsRGB + (1 - $rgba[3]); + $BsRGBA = $rgba[3] * $BsRGB + (1 - $rgba[3]); + // Calcul de la luminance + $R = ($RsRGBA <= .03928) ? $RsRGBA / 12.92 : pow(($RsRGBA + .055) / 1.055, 2.4); + $G = ($GsRGBA <= .03928) ? $GsRGBA / 12.92 : pow(($GsRGBA + .055) / 1.055, 2.4); + $B = ($BsRGBA <= .03928) ? $BsRGBA / 12.92 : pow(($BsRGBA + .055) / 1.055, 2.4); + return .2126 * $R + .7152 * $G + .0722 * $B; + } + + /** + * Retourne les attributs d'une balise au bon format + * @param array $array Liste des attributs ($key => $value) + * @param array $exclude Clés à ignorer ($key) + * @return string + */ + public static function sprintAttributes(array $array = [], array $exclude = []) { + $exclude = array_merge( + [ + 'before', + 'classWrapper', + 'help', + 'label' + ], + $exclude + ); + $attributes = []; + foreach($array as $key => $value) { + if(($value OR $value === 0) AND in_array($key, $exclude) === false) { + // Désactive le message de modifications non enregistrées pour le champ + if($key === 'noDirty') { + $attributes[] = 'data-no-dirty'; + } + // Disabled + // Readonly + elseif(in_array($key, ['disabled', 'readonly'])) { + $attributes[] = sprintf('%s', $key); + } + // Autres + else { + $attributes[] = sprintf('%s="%s"', $key, $value); + } + } + } + return implode(' ', $attributes); + } + + /** + * Retourne un segment de chaîne sans couper de mot + * @param string $text Texte à scinder + * @param int $start (voir substr de PHP pour fonctionnement) + * @param int $length (voir substr de PHP pour fonctionnement) + * @return string + */ + public static function subword($text, $start, $length) { + $text = trim($text); + if(strlen($text) > $length) { + $text = mb_substr($text, $start, $length); + $text = mb_substr($text, 0, min(mb_strlen($text), mb_strrpos($text, ' '))); + } + return $text; + } + + /** + * Cryptage + * @param string $key la clé d'encryptage + * @param string $payload la chaine à coder + * @return string + */ + public static function encrypt($key, $payload) { + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); + $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $key, 0, $iv); + return base64_encode($encrypted . '::' . $iv); + } + + /** + * Décryptage + * @param string $key la clé d'encryptage + * @param string $garble la chaine à décoder + * @return string + */ + public static function decrypt($key, $garble) { + list($encrypted_data, $iv) = explode('::', base64_decode($garble), 2); + return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv); + } + +} diff --git a/core/class/jsondb/Dot.class.php b/core/class/jsondb/Dot.class.php new file mode 100644 index 0000000..3c4af64 --- /dev/null +++ b/core/class/jsondb/Dot.class.php @@ -0,0 +1,316 @@ +data = $data; + } + } + + /** + * Get value of path, default value if path doesn't exist or all data + * + * @param array $array Source Array + * @param mixed|null $key Path + * @param mixed|null $default Default value + * @return mixed Value of path + */ + public static function getValue($array, $key, $default = null) + { + if (is_string($key)) { + // Iterate path + $keys = explode('.', $key); + foreach ($keys as $key) { + if (!isset($array[$key])) { + return $default; + } + $array = &$array[$key]; + } + // Get value + return $array; + } elseif (is_null($key)) { + // Get all data + return $array; + } + return null; + } + + /** + * Set value or array of values to path + * + * @param array $array Target array with data + * @param mixed $key Path or array of paths and values + * @param mixed|null $value Value to set if path is not an array + */ + public static function setValue(&$array, $key, $value) + { + if (is_string($key)) { + // Iterate path + $keys = explode('.', $key); + foreach ($keys as $key) { + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + $array = &$array[$key]; + } + // Set value to path + $array = $value; + } elseif (is_array($key)) { + // Iterate array of paths and values + foreach ($key as $k => $v) { + self::setValue($array, $k, $v); + } + } + } + + /** + * Add value or array of values to path + * + * @param array $array Target array with data + * @param mixed $key Path or array of paths and values + * @param mixed|null $value Value to set if path is not an array + * @param boolean $pop Helper to pop out last key if value is an array + */ + public static function addValue(&$array, $key, $value = null, $pop = false) + { + if (is_string($key)) { + // Iterate path + $keys = explode('.', $key); + if ($pop === true) { + array_pop($keys); + } + foreach ($keys as $key) { + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + $array = &$array[$key]; + } + // Add value to path + $array[] = $value; + } elseif (is_array($key)) { + // Iterate array of paths and values + foreach ($key as $k => $v) { + self::addValue($array, $k, $v); + } + } + } + + /** + * Delete path or array of paths + * + * @param array $array Target array with data + * @param mixed $key Path or array of paths to delete + */ + public static function deleteValue(&$array, $key) + { + if (is_string($key)) { + // Iterate path + $keys = explode('.', $key); + $last = array_pop($keys); + foreach ($keys as $key) { + if (!isset($array[$key])) { + return; + } + $array = &$array[$key]; + } + if (isset($array[$last])) { + // Detele path + unset($array[$last]); + } + } elseif (is_array($key)) { + // Iterate array of paths + foreach ($key as $k) { + self::delete($k); + } + } + } + + + /** + * Get value of path, default value if path doesn't exist or all data + * + * @param mixed|null $key Path + * @param mixed|null $default Default value + * @return mixed Value of path + */ + public function get($key = null, $default = null) + { + return self::getValue($this->data, $key, $default); + } + + /** + * Set value or array of values to path + * + * @param mixed $key Path or array of paths and values + * @param mixed|null $value Value to set if path is not an array + */ + public function set($key, $value = null) + { + return self::setValue($this->data, $key, $value); + } + + /** + * Add value or array of values to path + * + * @param mixed $key Path or array of paths and values + * @param mixed|null $value Value to set if path is not an array + * @param boolean $pop Helper to pop out last key if value is an array + */ + public function add($key, $value = null, $pop = false) + { + return self::addValue($this->data, $key, $value, $pop); + } + + /** + * Check if path exists + * + * @param string $key Path + * @return boolean + */ + public function has($key) + { + $keys = explode('.', (string)$key); + $data = &$this->data; + foreach ($keys as $key) { + if (!isset($data[$key])) { + return false; + } + $data = &$data[$key]; + } + + return true; + } + + /** + * Delete path or array of paths + * + * @param mixed $key Path or array of paths to delete + */ + public function delete($key) + { + return self::deleteValue($this->data, $key); + } + + /** + * Delete all data, data from path or array of paths and + * optionally format path if it doesn't exist + * + * @param mixed|null $key Path or array of paths to clean + * @param boolean $format Format option + */ + public function clear($key = null, $format = false) + { + if (is_string($key)) { + // Iterate path + $keys = explode('.', $key); + $data = &$this->data; + foreach ($keys as $key) { + if (!isset($data[$key]) || !is_array($data[$key])) { + if ($format === true) { + $data[$key] = []; + } else { + return; + } + } + $data = &$data[$key]; + } + // Clear path + $data = []; + } elseif (is_array($key)) { + // Iterate array + foreach ($key as $k) { + $this->clear($k, $format); + } + } elseif (is_null($key)) { + // Clear all data + $this->data = []; + } + } + + /** + * Set data + * + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * Set data as a reference + * + * @param array $data + */ + public function setDataAsRef(array &$data) + { + $this->data = &$data; + } + + /** + * ArrayAccess abstract methods + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + public function offsetExists($offset) + { + return $this->has($offset); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetUnset($offset) + { + $this->delete($offset); + } + + /** + * Magic methods + */ + public function __set($key, $value = null) + { + $this->set($key, $value); + } + + public function __get($key) + { + return $this->get($key); + } + + public function __isset($key) + { + return $this->has($key); + } + + public function __unset($key) + { + $this->delete($key); + } +} diff --git a/core/class/jsondb/JsonDb.class.php b/core/class/jsondb/JsonDb.class.php new file mode 100644 index 0000000..978a813 --- /dev/null +++ b/core/class/jsondb/JsonDb.class.php @@ -0,0 +1,155 @@ +config = array_merge([ + 'name' => 'data.json', + 'backup' => false, + 'dir' => getcwd() + //'template' => getcwd() . DIRECTORY_SEPARATOR . 'data.template.json' + ], $config); + $this->loadData(); + parent::__construct(); + } + + /** + * Set value or array of values to path + * + * @param mixed $key Path or array of paths and values + * @param mixed|null $value Value to set if path is not an array + * @param bool $save Save data to database + * @return $this + */ + public function set($key, $value = null, $save = true) + { + parent::set($key, $value); + if ($save) $this->save(); + return $this; + } + + /** + * Add value or array of values to path + * + * @param mixed $key Path or array of paths and values + * @param mixed|null $value Value to set if path is not an array + * @param boolean $pop Helper to pop out last key if value is an array + * @param bool $save Save data to database + * @return $this + */ + public function add($key, $value = null, $pop = false, $save = true) + { + parent::add($key, $value, $pop); + if ($save) $this->save(); + return $this; + } + + /** + * Delete path or array of paths + * + * @param mixed $key Path or array of paths to delete + * @param bool $save Save data to database + * @return $this + */ + public function delete($key, $save = true) + { + parent::delete($key); + if ($save) $this->save(); + return $this; + } + + /** + * Delete all data, data from path or array of paths and + * optionally format path if it doesn't exist + * + * @param mixed|null $key Path or array of paths to clean + * @param boolean $format Format option + * @param bool $save Save data to database + * @return $this + */ + public function clear($key = null, $format = false, $save = true) + { + parent::clear($key, $format); + if ($save) $this->save(); + return $this; + } + + + /** + * Local database upload + * @param bool $reload Reboot data? + * @return array|mixed|null + */ + protected function loadData($reload = false) { + if ($this->data === null || $reload) { + // $this->db = $this->config['dir'] . DIRECTORY_SEPARATOR . $this->config['name']; + $this->db = $this->config['dir'] . $this->config['name']; + + if (!file_exists($this->db)) { + return null; + } else { + // 3 essais + for($i = 0; $i <3; $i++) { + if ($this->data = json_decode(@file_get_contents($this->db), true) ) { + break; + } + // Pause de 10 millisecondes + usleep(10000); + } + // Gestion de l'erreur + if (!$this->data === null) { + exit ('JsonDB : Erreur de lecture du fichier de données ' . $this->db .'. Aucune donnée lisible, essayez dans quelques instants ou vérifiez le système de fichiers.'); + } + } + } + return $this->data; + } + + /** + * Saving to local database + */ + public function save() { + // Fichier inexistant, le créer + if ( !file_exists($this->db) ) { + touch($this->db); + } + // Backup file + if ($this->config['backup'] === true) { + copy ($this->db, str_replace('json' , 'backup.json', $this->db)); + } + if ( is_writable($this->db) ) { + // 3 essais + for($i = 0; $i < 3; $i++) { + if( @file_put_contents($this->db, json_encode($this->data, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT|LOCK_EX)) !== false) { + break; + } + // Pause de 10 millisecondes + usleep(10000); + + } + if ($i === 2) { + exit ('Jsondb : Erreur d\'écriture dans le fichier de données ' . $this->db . '. Vérifiez le système de fichiers.' ); + } + } else { + exit ('Jsondb : Écriture interdite dans le fichier de données ' . $this->db .'. Vérifiez les permissions.' ); + } + } +} diff --git a/core/class/phpmailer/.htaccess b/core/class/phpmailer/.htaccess new file mode 100644 index 0000000..3b355e3 --- /dev/null +++ b/core/class/phpmailer/.htaccess @@ -0,0 +1,3 @@ +# Bloque l'accès à la librairie +Order deny,allow +Deny from all \ No newline at end of file diff --git a/core/class/phpmailer/Exception.class.php b/core/class/phpmailer/Exception.class.php new file mode 100644 index 0000000..b1e552f --- /dev/null +++ b/core/class/phpmailer/Exception.class.php @@ -0,0 +1,39 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2017 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage()) . "
\n"; + } +} diff --git a/core/class/phpmailer/PHPMailer.class.php b/core/class/phpmailer/PHPMailer.class.php new file mode 100644 index 0000000..51dff92 --- /dev/null +++ b/core/class/phpmailer/PHPMailer.class.php @@ -0,0 +1,4821 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2019 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = 'root@localhost'; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = 'Root User'; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP auth type. + * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. + * + * @var string + */ + public $AuthType = ''; + + /** + * An instance of the PHPMailer OAuth class. + * + * @var OAuth + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * * SMTP::DEBUG_OFF: No output + * * SMTP::DEBUG_CLIENT: Client messages + * * SMTP::DEBUG_SERVER: Client and server messages + * * SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * * SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep SMTP connection open after each message. + * If this is set to true then to close the connection + * requires an explicit call to smtpClose(). + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available languages. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.1.5'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if (ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $result = @mail($to, $subject, $body, $header, $params); + } + + return $result; + } + + /** + * Output debugging info via user-defined method. + * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $pos = strrpos($address, '@'); + if (false === $pos) { + // At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $params = [$kind, $address, $name]; + // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + // Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + foreach ($list as $address) { + if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress( + $address->mailbox . '@' . $address->host + )) { + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + if (static::validateAddress($email)) { + $addresses[] = [ + 'name' => trim(str_replace(['"', "'"], '', $name)), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + // Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ((false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + if (is_callable($patternselect)) { + return $patternselect($address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + // Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if (!empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003); + } else { + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ('smtp' === $this->Mailer + || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ('mail' === $this->Mailer + && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017) + || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error( + 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + E_USER_WARNING + ); + } + + try { + $this->error_count = 0; // Reset errors + $this->mailHeader = ''; + + // Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + // Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + $this->$address_kind = trim($this->$address_kind); + if (empty($this->$address_kind)) { + continue; + } + $this->$address_kind = $this->punyencodeAddress($this->$address_kind); + if (!static::validateAddress($this->$address_kind)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->$address_kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + // Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + // Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + // createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + // To capture the complete message when using mail(), create + // an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + // Sign with DKIM if enabled + if (!empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + // Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && self::isShellSafe($this->Sender)) { + if ('qmail' === $this->Mailer) { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } elseif ('qmail' === $this->Mailer) { + $sendmailFmt = '%s'; + } else { + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + [$toAddr], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + // Future-proof + if (escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + // All other characters have a special meaning in at least one common shell, including = and +. + // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + // Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + return !preg_match('#^[a-z]+://#i', $path); + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = implode(', ', $toArr); + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + // Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]]; + } + } + + // Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [$cb['to']], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + // Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if (!preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + )) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + // Not a valid host entry + continue; + } + // $hostinfo[1]: optional ssl or tls prefix + // $hostinfo[2]: the hostname + // $hostinfo[3]: optional port number + // The host string prefix can temporarily override the current setting for SMTPSecure + // If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; // Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + // tls doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if (array_key_exists(3, $hostinfo) && is_numeric($hostinfo[3]) && $hostinfo[3] > 0 && $hostinfo[3] < 65536) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + // * it's not disabled + // * we have openssl extension + // * we are not already using SSL + // * the server offers STARTTLS + if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + throw new Exception($this->lang('connect_host')); + } + // We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ($this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + )) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + // We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + // If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + // As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * Returns false if it cannot load the language file. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * + * @return bool + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + // Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (isset($renamed_langcodes[$langcode])) { + $langcode = $renamed_langcodes[$langcode]; + } + + // Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + 'extension_missing' => 'Extension missing: ', + ]; + if (empty($lang_path)) { + // Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + //Validate $langcode + if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { + $langcode = 'en'; + } + $foundlang = true; + $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; + // There is no English translation file + if ('en' !== $langcode) { + // Make sure language file path is readable + if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) { + $foundlang = false; + } else { + // Overwrite language-specific strings. + // This way we'll never have missing translation keys. + $foundlang = include $lang_file; + } + } + $this->language = $PHPMAILER_LANG; + + return (bool) $foundlang; // Returns false if language not found + } + + /** + * Get the array of strings for the current language. + * + * @return array + */ + public function getTranslations() + { + return $this->language; + } + + /** + * Create recipient headers. + * + * @param string $type + * @param array $addr An array of recipients, + * where each recipient is a 2-element indexed array with element 0 containing an address + * and element 1 containing a name, like: + * [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']] + * + * @return string + */ + public function addrAppend($type, $addr) + { + $addresses = []; + foreach ($addr as $address) { + $addresses[] = $this->addrFormat($address); + } + + return $type . ': ' . implode(', ', $addresses) . static::$LE; + } + + /** + * Format an address for use in a message header. + * + * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like + * ['joe@example.com', 'Joe User'] + * + * @return string + */ + public function addrFormat($addr) + { + if (empty($addr[1])) { // No name provided + return $this->secureHeader($addr[0]); + } + + return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . + ' <' . $this->secureHeader($addr[0]) . '>'; + } + + /** + * Word-wrap message. + * For use with mailers that do not automatically perform wrapping + * and for quoted-printable encoded messages. + * Original written by philippe. + * + * @param string $message The message to wrap + * @param int $length The line length to wrap to + * @param bool $qp_mode Whether to run in Quoted-Printable mode + * + * @return string + */ + public function wrapText($message, $length, $qp_mode = false) + { + if ($qp_mode) { + $soft_break = sprintf(' =%s', static::$LE); + } else { + $soft_break = static::$LE; + } + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet); + $lelen = strlen(static::$LE); + $crlflen = strlen(static::$LE); + + $message = static::normalizeBreaks($message); + //Remove a trailing line break + if (substr($message, -$lelen) === static::$LE) { + $message = substr($message, 0, -$lelen); + } + + //Split message into lines + $lines = explode(static::$LE, $message); + //Message will be rebuilt in here + $message = ''; + foreach ($lines as $line) { + $words = explode(' ', $line); + $buf = ''; + $firstword = true; + foreach ($words as $word) { + if ($qp_mode && (strlen($word) > $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if (!$firstword) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' === substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' === substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf('=%s', static::$LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while ($word !== '') { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' === substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' === substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = (string) substr($word, $len); + + if ($word !== '') { + $message .= $part . sprintf('=%s', static::$LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + if (!$firstword) { + $buf .= ' '; + } + $buf .= $word; + + if ('' !== $buf_o && strlen($buf) > $length) { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + $firstword = false; + } + $message .= $buf . static::$LE; + } + + return $message; + } + + /** + * Find the last character boundary prior to $maxLength in a utf-8 + * quoted-printable encoded string. + * Original written by Colin Brown. + * + * @param string $encodedText utf-8 QP text + * @param int $maxLength Find the last character boundary prior to this length + * + * @return int + */ + public function utf8CharBoundary($encodedText, $maxLength) + { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, '='); + if (false !== $encodedCharPos) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { + // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + if ($encodedCharPos > 0) { + $maxLength -= $lookBack - $encodedCharPos; + } + $foundSplitPos = true; + } elseif ($dec >= 192) { + // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength -= $lookBack - $encodedCharPos; + $foundSplitPos = true; + } elseif ($dec < 192) { + // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + + return $maxLength; + } + + /** + * Apply word wrapping to the message body. + * Wraps the message body to the number of chars set in the WordWrap property. + * You should only do this to plain-text bodies as wrapping HTML tags may break them. + * This is called automatically by createBody(), so you don't need to call it yourself. + */ + public function setWordWrap() + { + if ($this->WordWrap < 1) { + return; + } + + switch ($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->wrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assemble message headers. + * + * @return string The assembled headers + */ + public function createHeader() + { + $result = ''; + + $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate); + + // To be created automatically by mail() + if ($this->SingleTo) { + if ('mail' !== $this->Mailer) { + foreach ($this->to as $toaddr) { + $this->SingleToArray[] = $this->addrFormat($toaddr); + } + } + } elseif (count($this->to) > 0) { + if ('mail' !== $this->Mailer) { + $result .= $this->addrAppend('To', $this->to); + } + } elseif (count($this->cc) === 0) { + $result .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + + $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]); + + // sendmail and mail() extract Cc from the header before sending + if (count($this->cc) > 0) { + $result .= $this->addrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if (( + 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer + ) + && count($this->bcc) > 0 + ) { + $result .= $this->addrAppend('Bcc', $this->bcc); + } + + if (count($this->ReplyTo) > 0) { + $result .= $this->addrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if ('mail' !== $this->Mailer) { + $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); + } + + // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 + // https://tools.ietf.org/html/rfc5322#section-3.6.4 + if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) { + $this->lastMessageID = $this->MessageID; + } else { + $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); + } + $result .= $this->headerLine('Message-ID', $this->lastMessageID); + if (null !== $this->Priority) { + $result .= $this->headerLine('X-Priority', $this->Priority); + } + if ('' === $this->XMailer) { + $result .= $this->headerLine( + 'X-Mailer', + 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)' + ); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->headerLine('X-Mailer', $myXmailer); + } + } + + if ('' !== $this->ConfirmReadingTo) { + $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); + } + + // Add custom headers + foreach ($this->CustomHeader as $header) { + $result .= $this->headerLine( + trim($header[0]), + $this->encodeHeader(trim($header[1])) + ); + } + if (!$this->sign_key_file) { + $result .= $this->headerLine('MIME-Version', '1.0'); + $result .= $this->getMailMIME(); + } + + return $result; + } + + /** + * Get the message MIME type headers. + * + * @return string + */ + public function getMailMIME() + { + $result = ''; + $ismultipart = true; + switch ($this->message_type) { + case 'inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); + $ismultipart = false; + break; + } + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $this->Encoding) { + // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE + if ($ismultipart) { + if (static::ENCODING_8BIT === $this->Encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT); + } + // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + } else { + $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); + } + } + + if ('mail' !== $this->Mailer) { +// $result .= static::$LE; + } + + return $result; + } + + /** + * Returns the whole MIME message. + * Includes complete headers and body. + * Only valid post preSend(). + * + * @see PHPMailer::preSend() + * + * @return string + */ + public function getSentMIMEMessage() + { + return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) . + static::$LE . static::$LE . $this->MIMEBody; + } + + /** + * Create a unique ID to use for boundaries. + * + * @return string + */ + protected function generateId() + { + $len = 32; //32 bytes = 256 bits + $bytes = ''; + if (function_exists('random_bytes')) { + try { + $bytes = random_bytes($len); + } catch (\Exception $e) { + //Do nothing + } + } elseif (function_exists('openssl_random_pseudo_bytes')) { + /** @noinspection CryptographicallySecureRandomnessInspection */ + $bytes = openssl_random_pseudo_bytes($len); + } + if ($bytes === '') { + //We failed to produce a proper random string, so make do. + //Use a hash to force the length to the same as the other methods + $bytes = hash('sha256', uniqid((string) mt_rand(), true), true); + } + + //We don't care about messing up base64 format here, just want a random string + return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true))); + } + + /** + * Assemble the message body. + * Returns an empty string on failure. + * + * @throws Exception + * + * @return string The assembled message body + */ + public function createBody() + { + $body = ''; + //Create unique IDs and preset boundaries + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1_' . $this->uniqueid; + $this->boundary[2] = 'b2_' . $this->uniqueid; + $this->boundary[3] = 'b3_' . $this->uniqueid; + + if ($this->sign_key_file) { + $body .= $this->getMailMIME() . static::$LE; + } + + $this->setWordWrap(); + + $bodyEncoding = $this->Encoding; + $bodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) { + $bodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $bodyCharSet = static::CHARSET_ASCII; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the body part only + if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) { + $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + + $altBodyEncoding = $this->Encoding; + $altBodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) { + $altBodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $altBodyCharSet = static::CHARSET_ASCII; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the alt body part only + if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) { + $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + //Use this as a preamble in all multipart message types + $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE; + switch ($this->message_type) { + case 'inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $mimepre; + $body .= $this->getBoundary( + $this->boundary[1], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[1], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $method = static::ICAL_METHOD_REQUEST; + foreach (static::$IcalMethods as $imethod) { + if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) { + $method = $imethod; + break; + } + } + $body .= $this->getBoundary( + $this->boundary[1], + '', + static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method, + '' + ); + $body .= $this->encodeString($this->Ical, $this->Encoding); + $body .= static::$LE; + } + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $mimepre; + $body .= $this->getBoundary( + $this->boundary[1], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $method = static::ICAL_METHOD_REQUEST; + foreach (static::$IcalMethods as $imethod) { + if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) { + $method = $imethod; + break; + } + } + $body .= $this->getBoundary( + $this->boundary[2], + '', + static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method, + '' + ); + $body .= $this->encodeString($this->Ical, $this->Encoding); + } + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[2]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[3], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[3]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + default: + // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types + //Reset the `Encoding` property in case we changed it for line length reasons + $this->Encoding = $bodyEncoding; + $body .= $this->encodeString($this->Body, $this->Encoding); + break; + } + + if ($this->isError()) { + $body = ''; + if ($this->exceptions) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + $file = tempnam(sys_get_temp_dir(), 'srcsign'); + $signed = tempnam(sys_get_temp_dir(), 'mailsign'); + file_put_contents($file, $body); + + //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 + if (empty($this->sign_extracerts_file)) { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [] + ); + } else { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [], + PKCS7_DETACHED, + $this->sign_extracerts_file + ); + } + + @unlink($file); + if ($sign) { + $body = file_get_contents($signed); + @unlink($signed); + //The message returned by openssl contains both headers and body, so need to split them up + $parts = explode("\n\n", $body, 2); + $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE; + $body = $parts[1]; + } else { + @unlink($signed); + throw new Exception($this->lang('signing') . openssl_error_string()); + } + } catch (Exception $exc) { + $body = ''; + if ($this->exceptions) { + throw $exc; + } + } + } + + return $body; + } + + /** + * Return the start of a message boundary. + * + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * + * @return string + */ + protected function getBoundary($boundary, $charSet, $contentType, $encoding) + { + $result = ''; + if ('' === $charSet) { + $charSet = $this->CharSet; + } + if ('' === $contentType) { + $contentType = $this->ContentType; + } + if ('' === $encoding) { + $encoding = $this->Encoding; + } + $result .= $this->textLine('--' . $boundary); + $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); + $result .= static::$LE; + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); + } + $result .= static::$LE; + + return $result; + } + + /** + * Return the end of a message boundary. + * + * @param string $boundary + * + * @return string + */ + protected function endBoundary($boundary) + { + return static::$LE . '--' . $boundary . '--' . static::$LE; + } + + /** + * Set the message type. + * PHPMailer only supports some preset message types, not arbitrary MIME structures. + */ + protected function setMessageType() + { + $type = []; + if ($this->alternativeExists()) { + $type[] = 'alt'; + } + if ($this->inlineImageExists()) { + $type[] = 'inline'; + } + if ($this->attachmentExists()) { + $type[] = 'attach'; + } + $this->message_type = implode('_', $type); + if ('' === $this->message_type) { + //The 'plain' message_type refers to the message having a single body element, not that it is plain-text + $this->message_type = 'plain'; + } + } + + /** + * Format a header line. + * + * @param string $name + * @param string|int $value + * + * @return string + */ + public function headerLine($name, $value) + { + return $name . ': ' . $value . static::$LE; + } + + /** + * Return a formatted mail line. + * + * @param string $value + * + * @return string + */ + public function textLine($value) + { + return $value . static::$LE; + } + + /** + * Add an attachment from a path on the filesystem. + * Never use a user-supplied path to a file! + * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. + * + * @param string $path Path to the attachment + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool + */ + public function addAttachment( + $path, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + try { + if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + // If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($path); + } + + $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); + if ('' === $name) { + $name = $filename; + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $name, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Return the array of attachments. + * + * @return array + */ + public function getAttachments() + { + return $this->attachment; + } + + /** + * Attach all file, string, and binary attachments to the message. + * Returns an empty string on failure. + * + * @param string $disposition_type + * @param string $boundary + * + * @throws Exception + * + * @return string + */ + protected function attachAll($disposition_type, $boundary) + { + // Return text of body + $mime = []; + $cidUniq = []; + $incl = []; + + // Add all attachments + foreach ($this->attachment as $attachment) { + // Check if it is a valid disposition_filter + if ($attachment[6] === $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = hash('sha256', serialize($attachment)); + if (in_array($inclhash, $incl, true)) { + continue; + } + $incl[] = $inclhash; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) { + continue; + } + $cidUniq[$cid] = true; + + $mime[] = sprintf('--%s%s', $boundary, static::$LE); + //Only include a filename property if we have one + if (!empty($name)) { + $mime[] = sprintf( + 'Content-Type: %s; name="%s"%s', + $type, + $this->encodeHeader($this->secureHeader($name)), + static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Type: %s%s', + $type, + static::$LE + ); + } + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $encoding) { + $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE); + } + + //Only set Content-IDs on inline attachments + if ((string) $cid !== '' && $disposition === 'inline') { + $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE; + } + + // If a filename contains any of these chars, it should be quoted, + // but not otherwise: RFC2183 & RFC2045 5.1 + // Fixes a warning in IETF's msglint MIME checker + // Allow for bypassing the Content-Disposition header totally + if (!empty($disposition)) { + $encoded_name = $this->encodeHeader($this->secureHeader($name)); + if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename="%s"%s', + $disposition, + $encoded_name, + static::$LE . static::$LE + ); + } elseif (!empty($encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename=%s%s', + $disposition, + $encoded_name, + static::$LE . static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Disposition: %s%s', + $disposition, + static::$LE . static::$LE + ); + } + } else { + $mime[] = static::$LE; + } + + // Encode as string attachment + if ($bString) { + $mime[] = $this->encodeString($string, $encoding); + } else { + $mime[] = $this->encodeFile($path, $encoding); + } + if ($this->isError()) { + return ''; + } + $mime[] = static::$LE; + } + } + + $mime[] = sprintf('--%s--%s', $boundary, static::$LE); + + return implode('', $mime); + } + + /** + * Encode a file attachment in requested format. + * Returns an empty string on failure. + * + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @return string + */ + protected function encodeFile($path, $encoding = self::ENCODING_BASE64) + { + try { + if (!static::isPermittedPath($path) || !file_exists($path) || !is_readable($path)) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = file_get_contents($path); + if (false === $file_buffer) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = $this->encodeString($file_buffer, $encoding); + + return $file_buffer; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + return ''; + } + } + + /** + * Encode a string in requested format. + * Returns an empty string on failure. + * + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @throws Exception + * + * @return string + */ + public function encodeString($str, $encoding = self::ENCODING_BASE64) + { + $encoded = ''; + switch (strtolower($encoding)) { + case static::ENCODING_BASE64: + $encoded = chunk_split( + base64_encode($str), + static::STD_LINE_LENGTH, + static::$LE + ); + break; + case static::ENCODING_7BIT: + case static::ENCODING_8BIT: + $encoded = static::normalizeBreaks($str); + // Make sure it ends with a line break + if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) { + $encoded .= static::$LE; + } + break; + case static::ENCODING_BINARY: + $encoded = $str; + break; + case static::ENCODING_QUOTED_PRINTABLE: + $encoded = $this->encodeQP($str); + break; + default: + $this->setError($this->lang('encoding') . $encoding); + if ($this->exceptions) { + throw new Exception($this->lang('encoding') . $encoding); + } + break; + } + + return $encoded; + } + + /** + * Encode a header value (not including its label) optimally. + * Picks shortest of Q, B, or none. Result includes folding if needed. + * See RFC822 definitions for phrase, comment and text positions. + * + * @param string $str The header value to encode + * @param string $position What context the string will be used in + * + * @return string + */ + public function encodeHeader($str, $position = 'text') + { + $matchcount = 0; + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know the value of magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return $encoded; + } + + return "\"$encoded\""; + } + $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $matchcount = preg_match_all('/[()"]/', $str, $matches); + //fallthrough + case 'text': + default: + $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($this->has8bitChars($str)) { + $charset = $this->CharSet; + } else { + $charset = static::CHARSET_ASCII; + } + + // Q/B encoding adds 8 chars and the charset ("` =??[QB]??=`"). + $overhead = 8 + strlen($charset); + + if ('mail' === $this->Mailer) { + $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead; + } else { + $maxlen = static::MAX_LINE_LENGTH - $overhead; + } + + // Select the encoding that produces the shortest output and/or prevents corruption. + if ($matchcount > strlen($str) / 3) { + // More than 1/3 of the content needs encoding, use B-encode. + $encoding = 'B'; + } elseif ($matchcount > 0) { + // Less than 1/3 of the content needs encoding, use Q-encode. + $encoding = 'Q'; + } elseif (strlen($str) > $maxlen) { + // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption. + $encoding = 'Q'; + } else { + // No reformatting needed + $encoding = false; + } + + switch ($encoding) { + case 'B': + if ($this->hasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded); + break; + case 'Q': + $encoded = $this->encodeQ($str, $position); + $encoded = $this->wrapText($encoded, $maxlen, true); + $encoded = str_replace('=' . static::$LE, "\n", trim($encoded)); + $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded); + break; + default: + return $str; + } + + return trim(static::normalizeBreaks($encoded)); + } + + /** + * Check if a string contains multi-byte characters. + * + * @param string $str multi-byte text to wrap encode + * + * @return bool + */ + public function hasMultiBytes($str) + { + if (function_exists('mb_strlen')) { + return strlen($str) > mb_strlen($str, $this->CharSet); + } + + // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + + /** + * Does a string contain any 8-bit chars (in any charset)? + * + * @param string $text + * + * @return bool + */ + public function has8bitChars($text) + { + return (bool) preg_match('/[\x80-\xFF]/', $text); + } + + /** + * Encode and wrap long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid. + * + * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * + * @param string $str multi-byte text to wrap encode + * @param string $linebreak string to use as linefeed/end-of-line + * + * @return string + */ + public function base64EncodeWrapMB($str, $linebreak = null) + { + $start = '=?' . $this->CharSet . '?B?'; + $end = '?='; + $encoded = ''; + if (null === $linebreak) { + $linebreak = static::$LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $avgLength = floor($length * $ratio * .75); + + $offset = 0; + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + ++$lookBack; + } while (strlen($chunk) > $length); + $encoded .= $chunk . $linebreak; + } + + // Chomp the last linefeed + return substr($encoded, 0, -strlen($linebreak)); + } + + /** + * Encode a string in quoted-printable format. + * According to RFC2045 section 6.7. + * + * @param string $string The text to encode + * + * @return string + */ + public function encodeQP($string) + { + return static::normalizeBreaks(quoted_printable_encode($string)); + } + + /** + * Encode a string using Q encoding. + * + * @see http://tools.ietf.org/html/rfc2047#section-4.2 + * + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * + * @return string + */ + public function encodeQ($str, $position = 'text') + { + // There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(["\r", "\n"], '', $str); + switch (strtolower($position)) { + case 'phrase': + // RFC 2047 section 5.3 + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + /* + * RFC 2047 section 5.2. + * Build $pattern without including delimiters and [] + */ + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $pattern = '\(\)"'; + /* Intentional fall through */ + case 'text': + default: + // RFC 2047 section 5.1 + // Replace every high ascii, control, =, ? and _ characters + $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; + break; + } + $matches = []; + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + // If the string contains an '=', make sure it's the first thing we replace + // so as to avoid double-encoding + $eqkey = array_search('=', $matches[0], true); + if (false !== $eqkey) { + unset($matches[0][$eqkey]); + array_unshift($matches[0], '='); + } + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + // Replace spaces with _ (more readable than =20) + // RFC 2047 section 4.2(2) + return str_replace(' ', '_', $encoded); + } + + /** + * Add a string or binary attachment (non-filesystem). + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * + * @param string $string String attachment data + * @param string $filename Name of the attachment + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addStringAttachment( + $string, + $filename, + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + try { + // If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($filename); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $filename, + 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => 0, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Add an embedded (inline) attachment from a file. + * This can include images, sounds, and just about any other document type. + * These differ from 'regular' attachments in that they are intended to be + * displayed inline with the message, not just attached for download. + * This is used in HTML messages that embed the images + * the HTML refers to using the $cid value. + * Never use a user-supplied path to a file! + * + * @param string $path Path to the attachment + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File MIME type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addEmbeddedImage( + $path, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + try { + if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + // If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($path); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); + if ('' === $name) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type. + * + * @param string $string The attachment binary data + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name A filename for the attachment. If this contains an extension, + * PHPMailer will attempt to set a MIME type for the attachment. + * For example 'file.jpg' would get an 'image/jpeg' MIME type. + * @param string $encoding File encoding (see $Encoding), defaults to 'base64' + * @param string $type MIME type - will be used in preference to any automatically derived type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addStringEmbeddedImage( + $string, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + try { + // If a MIME type is not specified, try to work it out from the name + if ('' === $type && !empty($name)) { + $type = static::filenameToType($name); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Validate encodings. + * + * @param string $encoding + * + * @return bool + */ + protected function validateEncoding($encoding) + { + return in_array( + $encoding, + [ + self::ENCODING_7BIT, + self::ENCODING_QUOTED_PRINTABLE, + self::ENCODING_BASE64, + self::ENCODING_8BIT, + self::ENCODING_BINARY, + ], + true + ); + } + + /** + * Check if an embedded attachment is present with this cid. + * + * @param string $cid + * + * @return bool + */ + protected function cidExists($cid) + { + foreach ($this->attachment as $attachment) { + if ('inline' === $attachment[6] && $cid === $attachment[7]) { + return true; + } + } + + return false; + } + + /** + * Check if an inline attachment is present. + * + * @return bool + */ + public function inlineImageExists() + { + foreach ($this->attachment as $attachment) { + if ('inline' === $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if an attachment (non-inline) is present. + * + * @return bool + */ + public function attachmentExists() + { + foreach ($this->attachment as $attachment) { + if ('attachment' === $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if this message has an alternative body set. + * + * @return bool + */ + public function alternativeExists() + { + return !empty($this->AltBody); + } + + /** + * Clear queued addresses of given kind. + * + * @param string $kind 'to', 'cc', or 'bcc' + */ + public function clearQueuedAddresses($kind) + { + $this->RecipientsQueue = array_filter( + $this->RecipientsQueue, + static function ($params) use ($kind) { + return $params[0] !== $kind; + } + ); + } + + /** + * Clear all To recipients. + */ + public function clearAddresses() + { + foreach ($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = []; + $this->clearQueuedAddresses('to'); + } + + /** + * Clear all CC recipients. + */ + public function clearCCs() + { + foreach ($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = []; + $this->clearQueuedAddresses('cc'); + } + + /** + * Clear all BCC recipients. + */ + public function clearBCCs() + { + foreach ($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = []; + $this->clearQueuedAddresses('bcc'); + } + + /** + * Clear all ReplyTo recipients. + */ + public function clearReplyTos() + { + $this->ReplyTo = []; + $this->ReplyToQueue = []; + } + + /** + * Clear all recipient types. + */ + public function clearAllRecipients() + { + $this->to = []; + $this->cc = []; + $this->bcc = []; + $this->all_recipients = []; + $this->RecipientsQueue = []; + } + + /** + * Clear all filesystem, string, and binary attachments. + */ + public function clearAttachments() + { + $this->attachment = []; + } + + /** + * Clear all custom headers. + */ + public function clearCustomHeaders() + { + $this->CustomHeader = []; + } + + /** + * Add an error message to the error container. + * + * @param string $msg + */ + protected function setError($msg) + { + ++$this->error_count; + if ('smtp' === $this->Mailer && null !== $this->smtp) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror['error'])) { + $msg .= $this->lang('smtp_error') . $lasterror['error']; + if (!empty($lasterror['detail'])) { + $msg .= ' Detail: ' . $lasterror['detail']; + } + if (!empty($lasterror['smtp_code'])) { + $msg .= ' SMTP code: ' . $lasterror['smtp_code']; + } + if (!empty($lasterror['smtp_code_ex'])) { + $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; + } + } + } + $this->ErrorInfo = $msg; + } + + /** + * Return an RFC 822 formatted date. + * + * @return string + */ + public static function rfcDate() + { + // Set the time zone to whatever the default is to avoid 500 errors + // Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + + return date('D, j M Y H:i:s O'); + } + + /** + * Get the server hostname. + * Returns 'localhost.localdomain' if unknown. + * + * @return string + */ + protected function serverHostname() + { + $result = ''; + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) { + $result = $_SERVER['SERVER_NAME']; + } elseif (function_exists('gethostname') && gethostname() !== false) { + $result = gethostname(); + } elseif (php_uname('n') !== false) { + $result = php_uname('n'); + } + if (!static::isValidHost($result)) { + return 'localhost.localdomain'; + } + + return $result; + } + + /** + * Validate whether a string contains a valid value to use as a hostname or IP address. + * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`. + * + * @param string $host The host name or IP address to check + * + * @return bool + */ + public static function isValidHost($host) + { + //Simple syntax limits + if (empty($host) + || !is_string($host) + || strlen($host) > 256 + || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host) + ) { + return false; + } + //Looks like a bracketed IPv6 address + if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') { + return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; + } + //If removing all the dots results in a numeric string, it must be an IPv4 address. + //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names + if (is_numeric(str_replace('.', '', $host))) { + //Is it a valid IPv4 address? + return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; + } + if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) { + //Is it a syntactically valid hostname? + return true; + } + + return false; + } + + /** + * Get an error message in the current language. + * + * @param string $key + * + * @return string + */ + protected function lang($key) + { + if (count($this->language) < 1) { + $this->setLanguage(); // set the default language + } + + if (array_key_exists($key, $this->language)) { + if ('smtp_connect_failed' === $key) { + //Include a link to troubleshooting docs on SMTP connection failure + //this is by far the biggest cause of support questions + //but it's usually not PHPMailer's fault. + return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; + } + + return $this->language[$key]; + } + + //Return the key as a fallback + return $key; + } + + /** + * Check if an error occurred. + * + * @return bool True if an error did occur + */ + public function isError() + { + return $this->error_count > 0; + } + + /** + * Add a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + * + * @throws Exception + */ + public function addCustomHeader($name, $value = null) + { + if (null === $value && strpos($name, ':') !== false) { + // Value passed in as name:value + list($name, $value) = explode(':', $name, 2); + } + $name = trim($name); + $value = trim($value); + //Ensure name is not empty, and that neither name nor value contain line breaks + if (empty($name) || strpbrk($name . $value, "\r\n") !== false) { + if ($this->exceptions) { + throw new Exception('Invalid header name or value'); + } + + return false; + } + $this->CustomHeader[] = [$name, $value]; + + return true; + } + + /** + * Returns all custom headers. + * + * @return array + */ + public function getCustomHeaders() + { + return $this->CustomHeader; + } + + /** + * Create a message body from an HTML string. + * Automatically inlines images and creates a plain-text version by converting the HTML, + * overwriting any existing values in Body and AltBody. + * Do not source $message content from user input! + * $basedir is prepended when handling relative URLs, e.g. and must not be empty + * will look for an image file in $basedir/images/a.png and convert it to inline. + * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email) + * Converts data-uri images into embedded attachments. + * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. + * + * @param string $message HTML message string + * @param string $basedir Absolute path to a base directory to prepend to relative paths to images + * @param bool|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter @return string $message The transformed message Body + * + * @throws Exception + * + * @see PHPMailer::html2text() + */ + public function msgHTML($message, $basedir = '', $advanced = false) + { + preg_match_all('/(? 1 && '/' !== substr($basedir, -1)) { + // Ensure $basedir has a trailing / + $basedir .= '/'; + } + foreach ($images[2] as $imgindex => $url) { + // Convert data URIs into embedded images + //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + $match = []; + if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) { + if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) { + $data = base64_decode($match[3]); + } elseif ('' === $match[2]) { + $data = rawurldecode($match[3]); + } else { + //Not recognised so leave it alone + continue; + } + //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places + //will only be embedded once, even if it used a different encoding + $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2 + + if (!$this->cidExists($cid)) { + $this->addStringEmbeddedImage( + $data, + $cid, + 'embed' . $imgindex, + static::ENCODING_BASE64, + $match[1] + ); + } + $message = str_replace( + $images[0][$imgindex], + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + continue; + } + if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths) + !empty($basedir) + // Ignore URLs containing parent dir traversal (..) + && (strpos($url, '..') === false) + // Do not change urls that are already inline images + && 0 !== strpos($url, 'cid:') + // Do not change absolute URLs, including anonymous protocol + && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) + ) { + $filename = static::mb_pathinfo($url, PATHINFO_BASENAME); + $directory = dirname($url); + if ('.' === $directory) { + $directory = ''; + } + // RFC2392 S 2 + $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0'; + if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { + $basedir .= '/'; + } + if (strlen($directory) > 1 && '/' !== substr($directory, -1)) { + $directory .= '/'; + } + if ($this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + static::ENCODING_BASE64, + static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION)) + ) + ) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } + } + } + $this->isHTML(); + // Convert all message body line breaks to LE, makes quoted-printable encoding work much better + $this->Body = static::normalizeBreaks($message); + $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced)); + if (!$this->alternativeExists()) { + $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.' + . static::$LE; + } + + return $this->Body; + } + + /** + * Convert an HTML string into plain text. + * This is used by msgHTML(). + * Note - older versions of this function used a bundled advanced converter + * which was removed for license reasons in #232. + * Example usage: + * + * ```php + * // Use default conversion + * $plain = $mail->html2text($html); + * // Use your own custom converter + * $plain = $mail->html2text($html, function($html) { + * $converter = new MyHtml2text($html); + * return $converter->get_text(); + * }); + * ``` + * + * @param string $html The HTML text to convert + * @param bool|callable $advanced Any boolean value to use the internal converter, + * or provide your own callable for custom conversion + * + * @return string + */ + public function html2text($html, $advanced = false) + { + if (is_callable($advanced)) { + return $advanced($html); + } + + return html_entity_decode( + trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), + ENT_QUOTES, + $this->CharSet + ); + } + + /** + * Get the MIME type for a file extension. + * + * @param string $ext File extension + * + * @return string MIME type of file + */ + public static function _mime_types($ext = '') + { + $mimes = [ + 'xl' => 'application/excel', + 'js' => 'application/javascript', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'm4a' => 'audio/mp4', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'mka' => 'audio/x-matroska', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'webp' => 'image/webp', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'ics' => 'text/calendar', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'wmv' => 'video/x-ms-wmv', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mp4' => 'video/mp4', + 'm4v' => 'video/mp4', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'webm' => 'video/webm', + 'mkv' => 'video/x-matroska', + ]; + $ext = strtolower($ext); + if (array_key_exists($ext, $mimes)) { + return $mimes[$ext]; + } + + return 'application/octet-stream'; + } + + /** + * Map a file name to a MIME type. + * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. + * + * @param string $filename A file name or full path, does not need to exist as a file + * + * @return string + */ + public static function filenameToType($filename) + { + // In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if (false !== $qpos) { + $filename = substr($filename, 0, $qpos); + } + $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION); + + return static::_mime_types($ext); + } + + /** + * Multi-byte-safe pathinfo replacement. + * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe. + * + * @see http://www.php.net/manual/en/function.pathinfo.php#107461 + * + * @param string $path A filename or path, does not need to exist as a file + * @param int|string $options Either a PATHINFO_* constant, + * or a string name to return only the specified piece + * + * @return string|array + */ + public static function mb_pathinfo($path, $options = null) + { + $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '']; + $pathinfo = []; + if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) { + if (array_key_exists(1, $pathinfo)) { + $ret['dirname'] = $pathinfo[1]; + } + if (array_key_exists(2, $pathinfo)) { + $ret['basename'] = $pathinfo[2]; + } + if (array_key_exists(5, $pathinfo)) { + $ret['extension'] = $pathinfo[5]; + } + if (array_key_exists(3, $pathinfo)) { + $ret['filename'] = $pathinfo[3]; + } + } + switch ($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + default: + return $ret; + } + } + + /** + * Set or reset instance properties. + * You should avoid this function - it's more verbose, less efficient, more error-prone and + * harder to debug than setting properties directly. + * Usage Example: + * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);` + * is the same as: + * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`. + * + * @param string $name The property name to set + * @param mixed $value The value to set the property to + * + * @return bool + */ + public function set($name, $value = '') + { + if (property_exists($this, $name)) { + $this->$name = $value; + + return true; + } + $this->setError($this->lang('variable_set') . $name); + + return false; + } + + /** + * Strip newlines to prevent header injection. + * + * @param string $str + * + * @return string + */ + public function secureHeader($str) + { + return trim(str_replace(["\r", "\n"], '', $str)); + } + + /** + * Normalize line breaks in a string. + * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. + * Defaults to CRLF (for message bodies) and preserves consecutive breaks. + * + * @param string $text + * @param string $breaktype What kind of line break to use; defaults to static::$LE + * + * @return string + */ + public static function normalizeBreaks($text, $breaktype = null) + { + if (null === $breaktype) { + $breaktype = static::$LE; + } + // Normalise to \n + $text = str_replace([self::CRLF, "\r"], "\n", $text); + // Now convert LE as needed + if ("\n" !== $breaktype) { + $text = str_replace("\n", $breaktype, $text); + } + + return $text; + } + + /** + * Remove trailing breaks from a string. + * + * @param string $text + * + * @return string The text to remove breaks from + */ + public static function stripTrailingWSP($text) + { + return rtrim($text, " \r\n\t"); + } + + /** + * Return the current line break format string. + * + * @return string + */ + public static function getLE() + { + return static::$LE; + } + + /** + * Set the line break format string, e.g. "\r\n". + * + * @param string $le + */ + protected static function setLE($le) + { + static::$LE = $le; + } + + /** + * Set the public and private key files and password for S/MIME signing. + * + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + * @param string $extracerts_filename Optional path to chain certificate + */ + public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') + { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + $this->sign_extracerts_file = $extracerts_filename; + } + + /** + * Quoted-Printable-encode a DKIM header. + * + * @param string $txt + * + * @return string + */ + public function DKIM_QP($txt) + { + $line = ''; + $len = strlen($txt); + for ($i = 0; $i < $len; ++$i) { + $ord = ord($txt[$i]); + if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { + $line .= $txt[$i]; + } else { + $line .= '=' . sprintf('%02X', $ord); + } + } + + return $line; + } + + /** + * Generate a DKIM signature. + * + * @param string $signHeader + * + * @throws Exception + * + * @return string The DKIM signature value + */ + public function DKIM_Sign($signHeader) + { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + return ''; + } + $privKeyStr = !empty($this->DKIM_private_string) ? + $this->DKIM_private_string : + file_get_contents($this->DKIM_private); + if ('' !== $this->DKIM_passphrase) { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = openssl_pkey_get_private($privKeyStr); + } + if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { + openssl_pkey_free($privKey); + + return base64_encode($signature); + } + openssl_pkey_free($privKey); + + return ''; + } + + /** + * Generate a DKIM canonicalization header. + * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. + * Canonicalized headers should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.2 + * + * @param string $signHeader Header + * + * @return string + */ + public function DKIM_HeaderC($signHeader) + { + //Normalize breaks to CRLF (regardless of the mailer) + $signHeader = static::normalizeBreaks($signHeader, self::CRLF); + //Unfold header lines + //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` + //@see https://tools.ietf.org/html/rfc5322#section-2.2 + //That means this may break if you do something daft like put vertical tabs in your headers. + $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); + //Break headers out into an array + $lines = explode(self::CRLF, $signHeader); + foreach ($lines as $key => $line) { + //If the header is missing a :, skip it as it's invalid + //This is likely to happen because the explode() above will also split + //on the trailing LE, leaving an empty line + if (strpos($line, ':') === false) { + continue; + } + list($heading, $value) = explode(':', $line, 2); + //Lower-case header name + $heading = strtolower($heading); + //Collapse white space within the value, also convert WSP to space + $value = preg_replace('/[ \t]+/', ' ', $value); + //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value + //But then says to delete space before and after the colon. + //Net result is the same as trimming both ends of the value. + //By elimination, the same applies to the field name + $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t"); + } + + return implode(self::CRLF, $lines); + } + + /** + * Generate a DKIM canonicalization body. + * Uses the 'simple' algorithm from RFC6376 section 3.4.3. + * Canonicalized bodies should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.3 + * + * @param string $body Message Body + * + * @return string + */ + public function DKIM_BodyC($body) + { + if (empty($body)) { + return self::CRLF; + } + // Normalize line endings to CRLF + $body = static::normalizeBreaks($body, self::CRLF); + + //Reduce multiple trailing line breaks to a single one + return static::stripTrailingWSP($body) . self::CRLF; + } + + /** + * Create the DKIM header and body in a new message header. + * + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * + * @throws Exception + * + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) + { + $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time(); + //Always sign these headers without being asked + //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1 + $autoSignHeaders = [ + 'from', + 'to', + 'cc', + 'date', + 'subject', + 'reply-to', + 'message-id', + 'content-type', + 'mime-version', + 'x-mailer', + ]; + if (stripos($headers_line, 'Subject') === false) { + $headers_line .= 'Subject: ' . $subject . static::$LE; + } + $headerLines = explode(static::$LE, $headers_line); + $currentHeaderLabel = ''; + $currentHeaderValue = ''; + $parsedHeaders = []; + $headerLineIndex = 0; + $headerLineCount = count($headerLines); + foreach ($headerLines as $headerLine) { + $matches = []; + if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) { + if ($currentHeaderLabel !== '') { + //We were previously in another header; This is the start of a new header, so save the previous one + $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue]; + } + $currentHeaderLabel = $matches[1]; + $currentHeaderValue = $matches[2]; + } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) { + //This is a folded continuation of the current header, so unfold it + $currentHeaderValue .= ' ' . $matches[1]; + } + ++$headerLineIndex; + if ($headerLineIndex >= $headerLineCount) { + //This was the last line, so finish off this header + $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue]; + } + } + $copiedHeaders = []; + $headersToSignKeys = []; + $headersToSign = []; + foreach ($parsedHeaders as $header) { + //Is this header one that must be included in the DKIM signature? + if (in_array(strtolower($header['label']), $autoSignHeaders, true)) { + $headersToSignKeys[] = $header['label']; + $headersToSign[] = $header['label'] . ': ' . $header['value']; + if ($this->DKIM_copyHeaderFields) { + $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC + str_replace('|', '=7C', $this->DKIM_QP($header['value'])); + } + continue; + } + //Is this an extra custom header we've been asked to sign? + if (in_array($header['label'], $this->DKIM_extraHeaders, true)) { + //Find its value in custom headers + foreach ($this->CustomHeader as $customHeader) { + if ($customHeader[0] === $header['label']) { + $headersToSignKeys[] = $header['label']; + $headersToSign[] = $header['label'] . ': ' . $header['value']; + if ($this->DKIM_copyHeaderFields) { + $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC + str_replace('|', '=7C', $this->DKIM_QP($header['value'])); + } + //Skip straight to the next header + continue 2; + } + } + } + } + $copiedHeaderFields = ''; + if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) { + //Assemble a DKIM 'z' tag + $copiedHeaderFields = ' z='; + $first = true; + foreach ($copiedHeaders as $copiedHeader) { + if (!$first) { + $copiedHeaderFields .= static::$LE . ' |'; + } + //Fold long values + if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) { + $copiedHeaderFields .= substr( + chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS), + 0, + -strlen(static::$LE . self::FWS) + ); + } else { + $copiedHeaderFields .= $copiedHeader; + } + $first = false; + } + $copiedHeaderFields .= ';' . static::$LE; + } + $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE; + $headerValues = implode(static::$LE, $headersToSign); + $body = $this->DKIM_BodyC($body); + $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body + $ident = ''; + if ('' !== $this->DKIM_identity) { + $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE; + } + //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag + //which is appended after calculating the signature + //https://tools.ietf.org/html/rfc6376#section-3.5 + $dkimSignatureHeader = 'DKIM-Signature: v=1;' . + ' d=' . $this->DKIM_domain . ';' . + ' s=' . $this->DKIM_selector . ';' . static::$LE . + ' a=' . $DKIMsignatureType . ';' . + ' q=' . $DKIMquery . ';' . + ' t=' . $DKIMtime . ';' . + ' c=' . $DKIMcanonicalization . ';' . static::$LE . + $headerKeys . + $ident . + $copiedHeaderFields . + ' bh=' . $DKIMb64 . ';' . static::$LE . + ' b='; + //Canonicalize the set of headers + $canonicalizedHeaders = $this->DKIM_HeaderC( + $headerValues . static::$LE . $dkimSignatureHeader + ); + $signature = $this->DKIM_Sign($canonicalizedHeaders); + $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS)); + + return static::normalizeBreaks($dkimSignatureHeader . $signature); + } + + /** + * Detect if a string contains a line longer than the maximum line length + * allowed by RFC 2822 section 2.1.1. + * + * @param string $str + * + * @return bool + */ + public static function hasLineLongerThanMax($str) + { + return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str); + } + + /** + * Allows for public read access to 'to' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getToAddresses() + { + return $this->to; + } + + /** + * Allows for public read access to 'cc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getCcAddresses() + { + return $this->cc; + } + + /** + * Allows for public read access to 'bcc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getBccAddresses() + { + return $this->bcc; + } + + /** + * Allows for public read access to 'ReplyTo' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getReplyToAddresses() + { + return $this->ReplyTo; + } + + /** + * Allows for public read access to 'all_recipients' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getAllRecipientAddresses() + { + return $this->all_recipients; + } + + /** + * Perform a callback. + * + * @param bool $isSent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + * @param array $extra + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra) + { + if (!empty($this->action_function) && is_callable($this->action_function)) { + call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra); + } + } + + /** + * Get the OAuth instance. + * + * @return OAuth + */ + public function getOAuth() + { + return $this->oauth; + } + + /** + * Set an OAuth instance. + */ + public function setOAuth(OAuth $oauth) + { + $this->oauth = $oauth; + } +} diff --git a/core/class/phpmailer/SMTP.class.php b/core/class/phpmailer/SMTP.class.php new file mode 100644 index 0000000..bc08b27 --- /dev/null +++ b/core/class/phpmailer/SMTP.class.php @@ -0,0 +1,1371 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2019 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer RFC821 SMTP email transport class. + * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. + * + * @author Chris Ryan + * @author Marcus Bointon + */ +class SMTP +{ + /** + * The PHPMailer SMTP version number. + * + * @var string + */ + const VERSION = '6.1.5'; + + /** + * SMTP line break constant. + * + * @var string + */ + const LE = "\r\n"; + + /** + * The SMTP port to use if one is not specified. + * + * @var int + */ + const DEFAULT_PORT = 25; + + /** + * The maximum line length allowed by RFC 5321 section 4.5.3.1.6, + * *excluding* a trailing CRLF break. + * + * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6 + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5, + * *including* a trailing CRLF line break. + * + * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5 + * + * @var int + */ + const MAX_REPLY_LENGTH = 512; + + /** + * Debug level for no output. + * + * @var int + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages. + * + * @var int + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages. + * + * @var int + */ + const DEBUG_LOWLEVEL = 4; + + /** + * Debug output level. + * Options: + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages. + * + * @var int + */ + public $do_debug = self::DEBUG_OFF; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to use VERP. + * + * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Info on VERP + * + * @var bool + */ + public $do_verp = false; + + /** + * The timeout value for connection, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. + * + * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * + * @var int + */ + public $Timeout = 300; + + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timelimit = 300; + + /** + * Patterns to extract an SMTP transaction id from reply to a DATA command. + * The first capture group in each regex will be used as the ID. + * MS ESMTP returns the message ID, which may not be correct for internal tracking. + * + * @var string[] + */ + protected $smtp_transaction_id_patterns = [ + 'exim' => '/[\d]{3} OK id=(.*)/', + 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', + 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', + 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', + 'Amazon_SES' => '/[\d]{3} Ok (.*)/', + 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', + 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', + ]; + + /** + * The last transaction ID issued in response to a DATA command, + * if one was detected. + * + * @var string|bool|null + */ + protected $last_smtp_transaction_id; + + /** + * The socket for the server connection. + * + * @var ?resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * + * @var array + */ + protected $error = [ + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '', + ]; + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * + * @var string|null + */ + protected $helo_rply; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * + * @var array|null + */ + protected $server_caps; + + /** + * The most recent reply received from the server. + * + * @var string + */ + protected $last_reply = ''; + + /** + * Output debugging info via a user-selected method. + * + * @param string $str Debug string to output + * @param int $level The debug level of this message; see DEBUG_* constants + * + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug + */ + protected function edebug($str, $level = 0) + { + if ($level > $this->do_debug) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $level); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo gmdate('Y-m-d H:i:s'), ' ', htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Connect to an SMTP server. + * + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * + * @return bool + */ + public function connect($host, $port = null, $timeout = 30, $options = []) + { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if (null === $streamok) { + $streamok = function_exists('stream_socket_client'); + } + // Clear errors to avoid confusion + $this->setError(''); + // Make sure we are __not__ connected + if ($this->connected()) { + // Already connected, generate error + $this->setError('Already connected to a server'); + + return false; + } + if (empty($port)) { + $port = self::DEFAULT_PORT; + } + // Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=" . + (count($options) > 0 ? var_export($options, true) : 'array()'), + self::DEBUG_CONNECTION + ); + $errno = 0; + $errstr = ''; + if ($streamok) { + $socket_context = stream_context_create($options); + set_error_handler([$this, 'errorHandler']); + $this->smtp_conn = stream_socket_client( + $host . ':' . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + restore_error_handler(); + } else { + //Fall back to fsockopen which should work in more places, but is missing some features + $this->edebug( + 'Connection: stream_socket_client not available, falling back to fsockopen', + self::DEBUG_CONNECTION + ); + set_error_handler([$this, 'errorHandler']); + $this->smtp_conn = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + restore_error_handler(); + } + // Verify we connected properly + if (!is_resource($this->smtp_conn)) { + $this->setError( + 'Failed to connect to server', + '', + (string) $errno, + $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + + return false; + } + $this->edebug('Connection: opened', self::DEBUG_CONNECTION); + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if (strpos(PHP_OS, 'WIN') !== 0) { + $max = (int) ini_get('max_execution_time'); + // Don't bother if unlimited + if (0 !== $max && $timeout > $max) { + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + // Get any announcement + $announce = $this->get_lines(); + $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); + + return true; + } + + /** + * Initiate a TLS (encrypted) session. + * + * @return bool + */ + public function startTLS() + { + if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { + return false; + } + + //Allow the best TLS version(s) we can + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT + //so add them back in manually if we can + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } + + // Begin encrypted connection + set_error_handler([$this, 'errorHandler']); + $crypto_ok = stream_socket_enable_crypto( + $this->smtp_conn, + true, + $crypto_method + ); + restore_error_handler(); + + return (bool) $crypto_ok; + } + + /** + * Perform SMTP authentication. + * Must be run after hello(). + * + * @see hello() + * + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2) + * @param OAuth $OAuth An optional OAuth instance for XOAUTH2 authentication + * + * @return bool True if successfully authenticated + */ + public function authenticate( + $username, + $password, + $authtype = null, + $OAuth = null + ) { + if (!$this->server_caps) { + $this->setError('Authentication is not allowed before HELO/EHLO'); + + return false; + } + + if (array_key_exists('EHLO', $this->server_caps)) { + // SMTP extensions are available; try to find a proper authentication method + if (!array_key_exists('AUTH', $this->server_caps)) { + $this->setError('Authentication is not allowed at this stage'); + // 'at this stage' means that auth may be allowed after the stage changes + // e.g. after STARTTLS + + return false; + } + + $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); + $this->edebug( + 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), + self::DEBUG_LOWLEVEL + ); + + //If we have requested a specific auth type, check the server supports it before trying others + if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) { + $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL); + $authtype = null; + } + + if (empty($authtype)) { + //If no auth mechanism is specified, attempt to use these, in this order + //Try CRAM-MD5 first as it's more secure than the others + foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) { + if (in_array($method, $this->server_caps['AUTH'], true)) { + $authtype = $method; + break; + } + } + if (empty($authtype)) { + $this->setError('No supported authentication methods found'); + + return false; + } + $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); + } + + if (!in_array($authtype, $this->server_caps['AUTH'], true)) { + $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); + + return false; + } + } elseif (empty($authtype)) { + $authtype = 'LOGIN'; + } + switch ($authtype) { + case 'PLAIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { + return false; + } + // Send encoded username and password + if (!$this->sendCommand( + 'User & Password', + base64_encode("\0" . $username . "\0" . $password), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { + return false; + } + if (!$this->sendCommand('Username', base64_encode($username), 334)) { + return false; + } + if (!$this->sendCommand('Password', base64_encode($password), 235)) { + return false; + } + break; + case 'CRAM-MD5': + // Start authentication + if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { + return false; + } + // Get the challenge + $challenge = base64_decode(substr($this->last_reply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // send encoded credentials + return $this->sendCommand('Username', base64_encode($response), 235); + case 'XOAUTH2': + //The OAuth instance must be set up prior to requesting auth. + if (null === $OAuth) { + return false; + } + $oauth = $OAuth->getOauth64(); + + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { + return false; + } + break; + default: + $this->setError("Authentication method \"$authtype\" is not supported"); + + return false; + } + + return true; + } + + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available. + * + * @param string $data The data to hash + * @param string $key The key to hash with + * + * @return string + */ + protected function hmac($data, $key) + { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from + // http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // by Lance Rushing + + $bytelen = 64; // byte length for md5 + if (strlen($key) > $bytelen) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $bytelen, chr(0x00)); + $ipad = str_pad('', $bytelen, chr(0x36)); + $opad = str_pad('', $bytelen, chr(0x5c)); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Check connection state. + * + * @return bool True if connected + */ + public function connected() + { + if (is_resource($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if ($sock_status['eof']) { + // The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + + return false; + } + + return true; // everything looks good + } + + return false; + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * + * @see quit() + */ + public function close() + { + $this->setError(''); + $this->server_caps = null; + $this->helo_rply = null; + if (is_resource($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); + } + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by an additional . + * Implements RFC 821: DATA . + * + * @param string $msg_data Message data to send + * + * @return bool + */ + public function data($msg_data) + { + //This will use the standard timelimit + if (!$this->sendCommand('DATA', 'DATA', 354)) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the LE) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. + */ + + // Normalize line breaks before exploding + $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); + + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if (!empty($field) && strpos($field, ' ') === false) { + $in_headers = true; + } + + foreach ($lines as $line) { + $lines_out = []; + if ($in_headers && $line === '') { + $in_headers = false; + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while (isset($line[self::MAX_LINE_LENGTH])) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); + //Deliberately matches both false and 0 + if (!$pos) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + //Break at the found point + $lines_out[] = substr($line, 0, $pos); + //Move along by the amount we dealt with + $line = substr($line, $pos + 1); + } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + //Send the lines to the server + foreach ($lines_out as $line_out) { + //RFC2821 section 4.5.2 + if (!empty($line_out) && $line_out[0] === '.') { + $line_out = '.' . $line_out; + } + $this->client_send($line_out . static::LE, 'DATA'); + } + } + + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit *= 2; + $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); + //Restore timelimit + $this->Timelimit = $savetimelimit; + + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * + * @param string $host The host name or IP to connect to + * + * @return bool + */ + public function hello($host = '') + { + //Try extended hello first (RFC 2821) + return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello(). + * + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * + * @return bool + * + * @see hello() + */ + protected function sendHello($hello, $host) + { + $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); + $this->helo_rply = $this->last_reply; + if ($noerror) { + $this->parseHelloFields($hello); + } else { + $this->server_caps = null; + } + + return $noerror; + } + + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * + * @param string $type `HELO` or `EHLO` + */ + protected function parseHelloFields($type) + { + $this->server_caps = []; + $lines = explode("\n", $this->helo_rply); + + foreach ($lines as $n => $s) { + //First 4 chars contain response code followed by - or space + $s = trim(substr($s, 4)); + if (empty($s)) { + continue; + } + $fields = explode(' ', $s); + if (!empty($fields)) { + if (!$n) { + $name = $type; + $fields = $fields[0]; + } else { + $name = array_shift($fields); + switch ($name) { + case 'SIZE': + $fields = ($fields ? $fields[0] : 0); + break; + case 'AUTH': + if (!is_array($fields)) { + $fields = []; + } + break; + default: + $fields = true; + } + } + $this->server_caps[$name] = $fields; + } + } + } + + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements RFC 821: MAIL FROM: . + * + * @param string $from Source address of this message + * + * @return bool + */ + public function mail($from) + { + $useVerp = ($this->do_verp ? ' XVERP' : ''); + + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } + + /** + * Send an SMTP QUIT command. + * Closes the socket if there is no error or the $close_on_error argument is true. + * Implements from RFC 821: QUIT . + * + * @param bool $close_on_error Should the connection close if an error occurs? + * + * @return bool + */ + public function quit($close_on_error = true) + { + $noerror = $this->sendCommand('QUIT', 'QUIT', 221); + $err = $this->error; //Save any error + if ($noerror || $close_on_error) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + + return $noerror; + } + + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from RFC 821: RCPT TO: . + * + * @param string $address The address the message is being sent to + * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE + * or DELAY. If you specify NEVER all other notifications are ignored. + * + * @return bool + */ + public function recipient($address, $dsn = '') + { + if (empty($dsn)) { + $rcpt = 'RCPT TO:<' . $address . '>'; + } else { + $dsn = strtoupper($dsn); + $notify = []; + + if (strpos($dsn, 'NEVER') !== false) { + $notify[] = 'NEVER'; + } else { + foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) { + if (strpos($dsn, $value) !== false) { + $notify[] = $value; + } + } + } + + $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify); + } + + return $this->sendCommand( + 'RCPT TO', + $rcpt, + [250, 251] + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements RFC 821: RSET . + * + * @return bool True on success + */ + public function reset() + { + return $this->sendCommand('RSET', 'RSET', 250); + } + + /** + * Send a command to an SMTP server and check its return code. + * + * @param string $command The command name - not sent to the server + * @param string $commandstring The actual command to send + * @param int|array $expect One or more expected integer success codes + * + * @return bool True on success + */ + protected function sendCommand($command, $commandstring, $expect) + { + if (!$this->connected()) { + $this->setError("Called $command without being connected"); + + return false; + } + //Reject line breaks in all commands + if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) { + $this->setError("Command '$command' contained line breaks"); + + return false; + } + $this->client_send($commandstring . static::LE, $command); + + $this->last_reply = $this->get_lines(); + // Fetch SMTP code and possible error code explanation + $matches = []; + if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) { + $code = (int) $matches[1]; + $code_ex = (count($matches) > 2 ? $matches[2] : null); + // Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]" . + ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', + '', + $this->last_reply + ); + } else { + // Fall back to simple parsing if regex fails + $code = (int) substr($this->last_reply, 0, 3); + $code_ex = null; + $detail = substr($this->last_reply, 4); + } + + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + + if (!in_array($code, (array) $expect, true)) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + + return false; + } + + $this->setError(''); + + return true; + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements RFC 821: SAML FROM: . + * + * @param string $from The address the message is from + * + * @return bool + */ + public function sendAndMail($from) + { + return $this->sendCommand('SAML', "SAML FROM:$from", 250); + } + + /** + * Send an SMTP VRFY command. + * + * @param string $name The name to verify + * + * @return bool + */ + public function verify($name) + { + return $this->sendCommand('VRFY', "VRFY $name", [250, 251]); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything. + * + * @return bool + */ + public function noop() + { + return $this->sendCommand('NOOP', 'NOOP', 250); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future. + * Implements from RFC 821: TURN . + * + * @return bool + */ + public function turn() + { + $this->setError('The SMTP TURN command is not implemented'); + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + + return false; + } + + /** + * Send raw data to the server. + * + * @param string $data The data to send + * @param string $command Optionally, the command this is part of, used only for controlling debug output + * + * @return int|bool The number of bytes sent to the server or false on error + */ + public function client_send($data, $command = '') + { + //If SMTP transcripts are left enabled, or debug output is posted online + //it can leak credentials, so hide credentials in all but lowest level + if (self::DEBUG_LOWLEVEL > $this->do_debug && + in_array($command, ['User & Password', 'Username', 'Password'], true)) { + $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT); + } else { + $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); + } + set_error_handler([$this, 'errorHandler']); + $result = fwrite($this->smtp_conn, $data); + restore_error_handler(); + + return $result; + } + + /** + * Get the latest error. + * + * @return array + */ + public function getError() + { + return $this->error; + } + + /** + * Get SMTP extensions available on the server. + * + * @return array|null + */ + public function getServerExtList() + { + return $this->server_caps; + } + + /** + * Get metadata about the SMTP server from its HELO/EHLO response. + * The method works in three ways, dependent on argument value and current state: + * 1. HELO/EHLO has not been sent - returns null and populates $this->error. + * 2. HELO has been sent - + * $name == 'HELO': returns server name + * $name == 'EHLO': returns boolean false + * $name == any other string: returns null and populates $this->error + * 3. EHLO has been sent - + * $name == 'HELO'|'EHLO': returns the server name + * $name == any other string: if extension $name exists, returns True + * or its options (e.g. AUTH mechanisms supported). Otherwise returns False. + * + * @param string $name Name of SMTP extension or 'HELO'|'EHLO' + * + * @return string|bool|null + */ + public function getServerExt($name) + { + if (!$this->server_caps) { + $this->setError('No HELO/EHLO was sent'); + + return; + } + + if (!array_key_exists($name, $this->server_caps)) { + if ('HELO' === $name) { + return $this->server_caps['EHLO']; + } + if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) { + return false; + } + $this->setError('HELO handshake was used; No information about server extensions available'); + + return; + } + + return $this->server_caps[$name]; + } + + /** + * Get the last reply from the server. + * + * @return string + */ + public function getLastReply() + { + return $this->last_reply; + } + + /** + * Read the SMTP server's response. + * Either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * + * @return string + */ + protected function get_lines() + { + // If the connection is bad, give up straight away + if (!is_resource($this->smtp_conn)) { + return ''; + } + $data = ''; + $endtime = 0; + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + $selR = [$this->smtp_conn]; + $selW = null; + while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + //Must pass vars in here as params are by reference + if (!stream_select($selR, $selW, $selW, $this->Timelimit)) { + $this->edebug( + 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + //Deliberate noise suppression - errors are handled afterwards + $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH); + $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL); + $data .= $str; + // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), + // or 4th character is a space or a line break char, we are done reading, break the loop. + // String array access is a significant micro-optimisation over strlen + if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") { + break; + } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + $this->edebug( + 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + // Now check if reads took too long + if ($endtime && time() > $endtime) { + $this->edebug( + 'SMTP -> get_lines(): timelimit reached (' . + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + } + + return $data; + } + + /** + * Enable or disable VERP address generation. + * + * @param bool $enabled + */ + public function setVerp($enabled = false) + { + $this->do_verp = $enabled; + } + + /** + * Get VERP address generation mode. + * + * @return bool + */ + public function getVerp() + { + return $this->do_verp; + } + + /** + * Set error messages and codes. + * + * @param string $message The error message + * @param string $detail Further detail on the error + * @param string $smtp_code An associated SMTP error code + * @param string $smtp_code_ex Extended SMTP code + */ + protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') + { + $this->error = [ + 'error' => $message, + 'detail' => $detail, + 'smtp_code' => $smtp_code, + 'smtp_code_ex' => $smtp_code_ex, + ]; + } + + /** + * Set debug output method. + * + * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it + */ + public function setDebugOutput($method = 'echo') + { + $this->Debugoutput = $method; + } + + /** + * Get debug output method. + * + * @return string + */ + public function getDebugOutput() + { + return $this->Debugoutput; + } + + /** + * Set debug output level. + * + * @param int $level + */ + public function setDebugLevel($level = 0) + { + $this->do_debug = $level; + } + + /** + * Get debug output level. + * + * @return int + */ + public function getDebugLevel() + { + return $this->do_debug; + } + + /** + * Set SMTP timeout. + * + * @param int $timeout The timeout duration in seconds + */ + public function setTimeout($timeout = 0) + { + $this->Timeout = $timeout; + } + + /** + * Get SMTP timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->Timeout; + } + + /** + * Reports an error number and string. + * + * @param int $errno The error number returned by PHP + * @param string $errmsg The error message returned by PHP + * @param string $errfile The file the error occurred in + * @param int $errline The line number the error occurred on + */ + protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) + { + $notice = 'Connection failed.'; + $this->setError( + $notice, + $errmsg, + (string) $errno + ); + $this->edebug( + "$notice Error #$errno: $errmsg [$errfile line $errline]", + self::DEBUG_CONNECTION + ); + } + + /** + * Extract and return the ID of the last SMTP transaction based on + * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. + * Relies on the host providing the ID in response to a DATA command. + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * + * @return bool|string|null + */ + protected function recordLastTransactionID() + { + $reply = $this->getLastReply(); + + if (empty($reply)) { + $this->last_smtp_transaction_id = null; + } else { + $this->last_smtp_transaction_id = false; + foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { + $matches = []; + if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { + $this->last_smtp_transaction_id = trim($matches[1]); + break; + } + } + } + + return $this->last_smtp_transaction_id; + } + + /** + * Get the queue/transaction ID of the last SMTP transaction + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * + * @return bool|string|null + * + * @see recordLastTransactionID() + */ + public function getLastTransactionID() + { + return $this->last_smtp_transaction_id; + } +} diff --git a/core/class/sitemap/FileSystem.class.php b/core/class/sitemap/FileSystem.class.php new file mode 100644 index 0000000..261102c --- /dev/null +++ b/core/class/sitemap/FileSystem.class.php @@ -0,0 +1,36 @@ +\n") + private $sitemapUrlCount = 0; + private $generatedFiles = []; + + /** + * @param string $baseURL You site URL + * @param string $basePath Relative path where sitemap and robots should be stored. + * @param FileSystem|null $fs + * @param Runtime|null $runtime + */ + public function __construct(string $baseURL, string $basePath = "", FileSystem $fs = null, Runtime $runtime = null) + { + $this->urls = []; + $this->baseURL = rtrim($baseURL, '/'); + + if ($fs === null) { + $this->fs = new FileSystem(); + } else { + $this->fs = $fs; + } + + if ($runtime === null) { + $this->runtime = new Runtime(); + } else { + $this->runtime = $runtime; + } + + if ($this->runtime->is_writable($basePath) === false) { + throw new InvalidArgumentException( + sprintf('the provided basePath (%s) should be a writable directory,', $basePath) . + ' please check its existence and permissions' + ); + } + if (strlen($basePath) > 0 && substr($basePath, -1) != DIRECTORY_SEPARATOR) { + $basePath = $basePath . DIRECTORY_SEPARATOR; + } + $this->basePath = $basePath; + + $this->xmlWriter = $this->createXmlWriter(); + $this->flushedSitemapFilenameFormat = sprintf("sm-%%d-%d.xml", time()); + } + + private function createXmlWriter(): XMLWriter + { + $w = new XMLWriter(); + $w->openMemory(); + $w->setIndent(true); + return $w; + } + + /** + * @param string $filename + * @return SitemapGenerator + */ + public function setSitemapFilename(string $filename = ''): SitemapGenerator + { + if (strlen($filename) === 0) { + throw new InvalidArgumentException('sitemap filename should not be empty'); + } + if (pathinfo($filename, PATHINFO_EXTENSION) !== 'xml') { + throw new InvalidArgumentException('sitemap filename should have *.xml extension'); + } + $this->sitemapFileName = $filename; + return $this; + } + + /** + * @param string $filename + * @return $this + */ + public function setSitemapIndexFilename(string $filename = ''): SitemapGenerator + { + if (strlen($filename) === 0) { + throw new InvalidArgumentException('filename should not be empty'); + } + $this->sitemapIndexFileName = $filename; + return $this; + } + + /** + * @param string $filename + * @return $this + */ + public function setRobotsFileName(string $filename): SitemapGenerator + { + if (strlen($filename) === 0) { + throw new InvalidArgumentException('filename should not be empty'); + } + $this->robotsFileName = $filename; + return $this; + } + + /** + * @param int $value + * @return $this + */ + public function setMaxUrlsPerSitemap(int $value): SitemapGenerator + { + if ($value < 1 || self::MAX_URLS_PER_SITEMAP < $value) { + throw new OutOfRangeException( + sprintf('value %d is out of range 1-%d', $value, self::MAX_URLS_PER_SITEMAP) + ); + } + $this->maxUrlsPerSitemap = $value; + return $this; + } + + public function enableCompression(): SitemapGenerator + { + $this->isCompressionEnabled = true; + return $this; + } + + public function disableCompression(): SitemapGenerator + { + $this->isCompressionEnabled = false; + return $this; + } + + public function isCompressionEnabled(): bool + { + return $this->isCompressionEnabled; + } + + public function validate( + string $path, + DateTime $lastModified = null, + string $changeFrequency = null, + float $priority = null, + array $alternates = null, + array $extensions = []) + { + if (!(1 <= mb_strlen($path) && mb_strlen($path) <= self::MAX_URL_LEN)) { + throw new InvalidArgumentException( + sprintf("The urlPath argument length must be between 1 and %d.", self::MAX_URL_LEN) + ); + } + if ($changeFrequency !== null && !in_array($changeFrequency, $this->validChangefreqValues)) { + throw new InvalidArgumentException( + 'The change frequency argument should be one of: %s' . implode(',', $this->validChangefreqValues) + ); + } + if ($priority !== null && !in_array($priority, $this->validPriorities)) { + throw new InvalidArgumentException("Priority argument should be a float number in the range [0.0..1.0]"); + } + if ($extensions !== null && isset($extensions['google_video'])) { + GoogleVideoExtension::validate($this->baseURL . $path, $extensions['google_video']); + } + } + + /** + * Add url components. + * Instead of storing all urls in the memory, the generator will flush sets of added urls + * to the temporary files created on your disk. + * The file format is 'sm-{index}-{timestamp}.xml' + * @param string $path + * @param DateTime|null $lastModified + * @param string|null $changeFrequency + * @param float|null $priority + * @param array|null $alternates + * @param array $extensions + * @return $this + */ + public function addURL( + string $path, + DateTime $lastModified = null, + string $changeFrequency = null, + float $priority = null, + array $alternates = null, + array $extensions = [] + ): SitemapGenerator + { + $this->validate($path, $lastModified, $changeFrequency, $priority, $alternates, $extensions); + + if ($this->totalUrlCount >= self::TOTAL_MAX_URLS) { + throw new OutOfRangeException( + sprintf("Max url limit reached (%d)", self::TOTAL_MAX_URLS) + ); + } + if ($this->isSitemapStarted === false) { + $this->writeSitemapStart(); + } + + $this->writeSitemapUrl($this->baseURL . $path, $lastModified, $changeFrequency, $priority, $alternates, $extensions); + + if ($this->totalUrlCount % 1000 === 0 || $this->sitemapUrlCount >= $this->maxUrlsPerSitemap) { + $this->flushWriter(); + } + + if ($this->sitemapUrlCount === $this->maxUrlsPerSitemap) { + $this->writeSitemapEnd(); + } + + return $this; + } + + private function writeSitemapStart() + { + $this->xmlWriter->startDocument("1.0", "UTF-8"); + $this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this))); + $this->xmlWriter->writeComment(sprintf('generator-version="%s"', $this->classVersion)); + $this->xmlWriter->writeComment(sprintf('generated-on="%s"', date('c'))); + $this->xmlWriter->startElement('urlset'); + $this->xmlWriter->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $this->xmlWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $this->xmlWriter->writeAttribute('xmlns:video', 'http://www.google.com/schemas/sitemap-video/1.1'); + $this->xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $this->xmlWriter->writeAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'); + $this->isSitemapStarted = true; + } + + private function writeSitemapUrl($loc, $lastModified, $changeFrequency, $priority, $alternates, $extensions) + { + $this->xmlWriter->startElement('url'); + $this->xmlWriter->writeElement('loc', htmlspecialchars($loc, ENT_QUOTES)); + + if ($lastModified !== null) { + $this->xmlWriter->writeElement('lastmod', $lastModified->format(DateTime::ATOM)); + } + + if ($changeFrequency !== null) { + $this->xmlWriter->writeElement('changefreq', $changeFrequency); + } + + if ($priority !== null) { + $this->xmlWriter->writeElement('priority', number_format($priority, 1, ".", "")); + } + + if (is_array($alternates) && count($alternates) > 0) { + foreach ($alternates as $alternate) { + if (is_array($alternate) && isset($alternate['hreflang']) && isset($alternate['href'])) { + $this->xmlWriter->startElement('xhtml:link'); + $this->xmlWriter->writeAttribute('rel', 'alternate'); + $this->xmlWriter->writeAttribute('hreflang', $alternate['hreflang']); + $this->xmlWriter->writeAttribute('href', $alternate['href']); + $this->xmlWriter->endElement(); + } + } + } + + foreach ($extensions as $extName => $extFields) { + if ($extName === 'google_video') { + GoogleVideoExtension::writeVideoTag($this->xmlWriter, $loc, $extFields); + } + } + + $this->xmlWriter->endElement(); // url + $this->sitemapUrlCount++; + $this->totalUrlCount++; + } + + private function flushWriter() + { + $targetSitemapFilepath = $this->basePath . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter); + $flushedString = $this->xmlWriter->outputMemory(true); + $flushedStringLen = mb_strlen($flushedString); + + if ($flushedStringLen === 0) { + return; + } + + $this->flushedSitemapSize += $flushedStringLen; + + if ($this->flushedSitemapSize > self::MAX_FILE_SIZE - $this->urlsetClosingTagLen) { + $this->writeSitemapEnd(); + $this->writeSitemapStart(); + } + $this->fs->file_put_contents($targetSitemapFilepath, $flushedString, FILE_APPEND); + } + + private function writeSitemapEnd() + { + $targetSitemapFilepath = $this->basePath . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter); + $this->xmlWriter->endElement(); // urlset + $this->xmlWriter->endDocument(); + $this->fs->file_put_contents($targetSitemapFilepath, $this->xmlWriter->flush(true), FILE_APPEND); + $this->isSitemapStarted = false; + $this->flushedSitemaps[] = $targetSitemapFilepath; + $this->flushedSitemapCounter++; + $this->sitemapUrlCount = 0; + } + + /** + * Flush all stored urls from memory to the disk and close all necessary tags. + */ + public function flush() + { + $this->flushWriter(); + if ($this->isSitemapStarted) { + $this->writeSitemapEnd(); + } + } + + /** + * Move flushed files to their final location. Compress if necessary. + */ + public function finalize() + { + $this->generatedFiles = []; + + if (count($this->flushedSitemaps) === 1) { + $targetSitemapFilename = $this->sitemapFileName; + if ($this->isCompressionEnabled) { + $targetSitemapFilename .= '.gz'; + } + + $targetSitemapFilepath = $this->basePath . $targetSitemapFilename; + + if ($this->isCompressionEnabled) { + $this->fs->copy($this->flushedSitemaps[0], 'compress.zlib://' . $targetSitemapFilepath); + $this->fs->unlink($this->flushedSitemaps[0]); + } else { + $this->fs->rename($this->flushedSitemaps[0], $targetSitemapFilepath); + } + $this->generatedFiles['sitemaps_location'] = [$targetSitemapFilepath]; + $this->generatedFiles['sitemaps_index_url'] = $this->baseURL . '/' . $targetSitemapFilename; + } else if (count($this->flushedSitemaps) > 1) { + $ext = '.' . pathinfo($this->sitemapFileName, PATHINFO_EXTENSION); + $targetExt = $ext; + if ($this->isCompressionEnabled) { + $targetExt .= '.gz'; + } + + $sitemapsUrls = []; + $targetSitemapFilepaths = []; + foreach ($this->flushedSitemaps as $i => $flushedSitemap) { + $targetSitemapFilename = str_replace($ext, ($i + 1) . $targetExt, $this->sitemapFileName); + $targetSitemapFilepath = $this->basePath . $targetSitemapFilename; + + if ($this->isCompressionEnabled) { + $this->fs->copy($flushedSitemap, 'compress.zlib://' . $targetSitemapFilepath); + $this->fs->unlink($flushedSitemap); + } else { + $this->fs->rename($flushedSitemap, $targetSitemapFilepath); + } + $sitemapsUrls[] = htmlspecialchars($this->baseURL . '/' . $targetSitemapFilename, ENT_QUOTES); + $targetSitemapFilepaths[] = $targetSitemapFilepath; + } + + $targetSitemapIndexFilepath = $this->basePath . $this->sitemapIndexFileName; + $this->createSitemapIndex($sitemapsUrls, $targetSitemapIndexFilepath); + $this->generatedFiles['sitemaps_location'] = $targetSitemapFilepaths; + $this->generatedFiles['sitemaps_index_location'] = $targetSitemapIndexFilepath; + $this->generatedFiles['sitemaps_index_url'] = $this->baseURL . '/' . $this->sitemapIndexFileName; + } else { + throw new RuntimeException('failed to finalize, please add urls and flush first'); + } + } + + private function createSitemapIndex($sitemapsUrls, $sitemapIndexFileName) + { + $this->xmlWriter->flush(true); + $this->writeSitemapIndexStart(); + foreach ($sitemapsUrls as $sitemapsUrl) { + $this->writeSitemapIndexUrl($sitemapsUrl); + } + $this->writeSitemapIndexEnd(); + $this->fs->file_put_contents( + $sitemapIndexFileName, + $this->xmlWriter->flush(true), + FILE_APPEND + ); + } + + private function writeSitemapIndexStart() + { + $this->xmlWriter->startDocument("1.0", "UTF-8"); + $this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this))); + $this->xmlWriter->writeComment(sprintf('generator-version="%s"', $this->classVersion)); + $this->xmlWriter->writeComment(sprintf('generated-on="%s"', date('c'))); + $this->xmlWriter->startElement('sitemapindex'); + $this->xmlWriter->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $this->xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $this->xmlWriter->writeAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'); + } + + private function writeSitemapIndexUrl($url) + { + $this->xmlWriter->startElement('sitemap'); + $this->xmlWriter->writeElement('loc', htmlspecialchars($url, ENT_QUOTES)); + $this->xmlWriter->writeElement('lastmod', date('c')); + $this->xmlWriter->endElement(); // sitemap + } + + private function writeSitemapIndexEnd() + { + $this->xmlWriter->endElement(); // sitemapindex + $this->xmlWriter->endDocument(); + } + + /** + * @return array Array of previously generated files + */ + public function getGeneratedFiles(): array + { + return $this->generatedFiles; + } + + /** + * Will inform search engines about newly created sitemaps. + * Google, Ask, Bing and Yahoo will be noticed. + * If You don't pass yahooAppId, Yahoo still will be informed, + * but this method can be used once per day. If You will do this often, + * message that limit was exceeded will be returned from Yahoo. + * @param string $yahooAppId Your site Yahoo appid. + * @return array of messages and http codes from each search engine + * @access public + * @throws BadMethodCallException + */ + public function submitSitemap($yahooAppId = null): array + { + if (count($this->generatedFiles) === 0) { + throw new BadMethodCallException("To update robots.txt, call finalize() first."); + } + if (!$this->runtime->extension_loaded('curl')) { + throw new BadMethodCallException("cURL extension is needed to do submission."); + } + $searchEngines = $this->searchEngines; + $searchEngines[0] = isset($yahooAppId) ? + str_replace("USERID", $yahooAppId, $searchEngines[0][0]) : + $searchEngines[0][1]; + $result = []; + for ($i = 0; $i < count($searchEngines); $i++) { + $submitUrl = $searchEngines[$i] . htmlspecialchars($this->generatedFiles['sitemaps_index_url'], ENT_QUOTES); + $submitSite = $this->runtime->curl_init($submitUrl); + $this->runtime->curl_setopt($submitSite, CURLOPT_RETURNTRANSFER, true); + $responseContent = $this->runtime->curl_exec($submitSite); + $response = $this->runtime->curl_getinfo($submitSite); + $submitSiteShort = array_reverse(explode(".", parse_url($searchEngines[$i], PHP_URL_HOST))); + $result[] = [ + "site" => $submitSiteShort[1] . "." . $submitSiteShort[0], + "fullsite" => $submitUrl, + "http_code" => $response['http_code'], + "message" => str_replace("\n", " ", strip_tags($responseContent)), + ]; + } + return $result; + } + + /** + * Adds sitemap url to robots.txt file located in basePath. + * If robots.txt file exists, + * the function will append sitemap url to file. + * If robots.txt does not exist, + * the function will create new robots.txt file with sample content and sitemap url. + * @access public + * @throws BadMethodCallException + * @throws RuntimeException + */ + public function updateRobots(): SitemapGenerator + { + if (count($this->generatedFiles) === 0) { + throw new BadMethodCallException("To update robots.txt, call finalize() first."); + } + + $robotsFilePath = $this->basePath . $this->robotsFileName; + + $robotsFileContent = $this->createNewRobotsContentFromFile($robotsFilePath); + + $this->fs->file_put_contents($robotsFilePath, $robotsFileContent); + + return $this; + } + + /** + * @param $filepath + * @return string + */ + private function createNewRobotsContentFromFile($filepath): string + { + if ($this->fs->file_exists($filepath)) { + $robotsFileContent = ""; + $robotsFile = explode(PHP_EOL, $this->fs->file_get_contents($filepath)); + foreach ($robotsFile as $key => $value) { + if (substr($value, 0, 8) == 'Sitemap:') { + unset($robotsFile[$key]); + } else { + $robotsFileContent .= $value . PHP_EOL; + } + } + } else { + $robotsFileContent = $this->getSampleRobotsContent(); + } + + $robotsFileContent .= "Sitemap: {$this->generatedFiles['sitemaps_index_url']}"; + + return $robotsFileContent; + } + + /** + * @return string + * @access private + */ + private function getSampleRobotsContent(): string + { + return implode(PHP_EOL, $this->sampleRobotsLines) . PHP_EOL; + } +} diff --git a/core/class/template.class.php b/core/class/template.class.php new file mode 100644 index 0000000..544f5aa --- /dev/null +++ b/core/class/template.class.php @@ -0,0 +1,868 @@ + $value) + * @return string + */ + public static function button($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'class' => '', + 'disabled' => false, + 'href' => 'javascript:void(0);', + 'ico' => '', + 'id' => $nameId, + 'name' => $nameId, + 'target' => '', + 'uniqueSubmission' => false, + 'value' => 'Bouton' + ], $attributes); + // Retourne le html + return sprintf( + '%s', + helper::sprintAttributes($attributes, ['class', 'disabled', 'ico', 'value']), + $attributes['disabled'] ? 'disabled' : '', + $attributes['class'], + $attributes['uniqueSubmission'] ? 'uniqueSubmission' : '', + ($attributes['ico'] ? template::ico($attributes['ico'], 'right') : '') . $attributes['value'] + ); + } + + /** + * Crée un champ captcha + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function captcha($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'class' => '', + 'classWrapper' => '', + 'help' => '', + 'id' => $nameId, + 'name' => $nameId, + 'value' => '', + 'limit' => false, // captcha simple + 'type'=> 'alpha' // num(érique) ou alpha(bétique) + ], $attributes); + + // Captcha quatre opérations + // Limite addition et soustraction selon le type de captcha + $numbers = [0,1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20]; + $letters = ['u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a']; + $limit = $attributes['limit'] ? count($letters)-1 : 10; + + // Tirage de l'opération + mt_srand((float) microtime()*1000000); + // Captcha simple limité à l'addition + $operator = $attributes['limit'] ? mt_rand (1, 4) : 1; + + // Limite si multiplication ou division + if ($operator > 2) { + $limit = 10; + } + + // Tirage des nombres + mt_srand((float) microtime()*1000000); + $firstNumber = mt_rand (1, $limit); + mt_srand((float) microtime()*1000000); + $secondNumber = mt_rand (1, $limit); + + // Permutation si addition ou soustraction + if (($operator < 3) and ($firstNumber < $secondNumber)) { + $temp = $firstNumber; + $firstNumber = $secondNumber; + $secondNumber = $temp; + } + + // Icône de l'opérateur et calcul du résultat + switch ($operator) { + case 1: + $operator = template::ico('plus'); + $result = $firstNumber + $secondNumber; + break; + case 2: + $operator = template::ico('minus'); + $result = $firstNumber - $secondNumber; + break; + case 3: + $operator = template::ico('cancel'); + $result = $firstNumber * $secondNumber; + break; + case 4: + $operator = template::ico('divide'); + $limit2 = [10, 10, 6, 5, 4, 3, 2, 2, 2, 2]; + for ($i = 1; $i <= $firstNumber; $i++) { + $limit = $limit2[$i-1]; + } + mt_srand((float) microtime()*1000000); + $secondNumber = mt_rand(1, $limit); + $firstNumber = $firstNumber * $secondNumber; + $result = $firstNumber / $secondNumber; + break; + } + + // Hashage du résultat + $result = password_hash($result, PASSWORD_BCRYPT); + + // Codage des valeurs de l'opération + $firstLetter = uniqid(); + $secondLetter = uniqid(); + + // Masquage image source pour éviter un décodage + copy ('core/vendor/zwiico/png/' . $attributes['type'] . '/' . $letters[$firstNumber] . '.png', 'site/tmp/' . $firstLetter . '.png'); + copy ('core/vendor/zwiico/png/' . $attributes['type'] . '/' . $letters[$secondNumber] . '.png', 'site/tmp/' . $secondLetter . '.png'); + + + // Début du wrapper + $html = '
'; + // Label + $html .= self::label($attributes['id'], + ' ' . $operator . '  en chiffres ?', [ + 'help' => $attributes['help'] + ]); + + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + + // captcha + $html .= sprintf( + '', + helper::sprintAttributes($attributes) + ); + + // Champ résultat codé + $html .= self::hidden($attributes['id'] . 'Result', [ + 'value' => $result, + 'before' => false + ]); + + // Fin du wrapper + $html .= '
'; + + // Retourne le html + return $html; + } + + /** + * Crée une case à cocher à sélection multiple + * @param string $nameId Nom et id du champ + * @param string $value Valeur de la case à cocher + * @param string $label Label de la case à cocher + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function checkbox($nameId, $value, $label, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'before' => true, + 'checked' => '', + 'class' => '', + 'classWrapper' => '', + 'disabled' => false, + 'help' => '', + 'id' => $nameId, + 'name' => $nameId + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['checked'] = (bool) common::$inputBefore[$attributes['id']]; + } + // Début du wrapper + $html = '
'; + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Case à cocher + $html .= sprintf( + '', + $value, + helper::sprintAttributes($attributes) + ); + // Label + $html .= self::label($attributes['id'], '' . $label . '', [ + 'help' => $attributes['help'] + ]); + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée un champ date + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function date($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'autocomplete' => 'on', + 'before' => true, + 'class' => '', + 'classWrapper' => '', + 'noDirty' => false, + 'disabled' => false, + 'help' => '', + 'id' => $nameId, + 'label' => '', + 'name' => $nameId, + 'placeholder' => '', + 'readonly' => true, + 'value' => '' + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['value'] = common::$inputBefore[$attributes['id']]; + } + else { + $attributes['value'] = ($attributes['value'] ? helper::filter($attributes['value'], helper::FILTER_TIMESTAMP) : ''); + } + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Date visible + $html .= '
'; + $html .= sprintf( + '', + $attributes['class'], + $attributes['value'], + helper::sprintAttributes($attributes, ['class', 'value']) + ); + $html .= self::button($attributes['id'] . 'Delete', [ + 'class' => 'inputDateDelete', + 'value' => self::ico('cancel') + ]); + $html .= '
'; + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée un champ d'upload de fichier + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function file($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'before' => true, + 'class' => '', + 'classWrapper' => '', + 'noDirty' => false, + 'disabled' => false, + 'extensions' => '', + 'help' => '', + 'id' => $nameId, + 'label' => '', + 'maxlength' => '500', + 'name' => $nameId, + 'type' => 2, + 'value' => '' + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['value'] = common::$inputBefore[$attributes['id']]; + } + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Champ caché contenant l'url de la page + $html .= self::hidden($attributes['id'], [ + 'class' => 'inputFileHidden', + 'disabled' => $attributes['disabled'], + 'maxlength' => $attributes['maxlength'], + 'value' => $attributes['value'] + ]); + // Champ d'upload + $html .= '
'; + $html .= sprintf( + ' + ' . self::ico('upload', 'right') . ' + + ', + $attributes['class'], + $attributes['disabled'] ? 'disabled' : '', + helper::sprintAttributes($attributes, ['class', 'extensions', 'type', 'maxlength']) + + ); + $html .= self::button($attributes['id'] . 'Delete', [ + 'class' => 'inputFileDelete', + 'value' => self::ico('cancel') + ]); + $html .= '
'; + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Ferme un formulaire + * @return string + */ + public static function formClose() { + return ''; + } + + /** + * Ouvre un formulaire protégé par CSRF + * @param string $id Id du formulaire + * @return string + */ + public static function formOpen($id) { + // Ouverture formulaire + $html = '
'; + // Stock le token CSRF + $html .= self::hidden('csrf', [ + 'value' => $_SESSION['csrf'] + ]); + // Retourne le html + return $html; + } + + + + /** + * Crée une aide qui s'affiche au survole + * @param string $text Texte de l'aide + * @return string + */ + public static function help($text) { + return '' . self::ico('help') . ''; + } + + /** + * Crée un champ caché + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function hidden($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'before' => true, + 'class' => '', + 'noDirty' => false, + 'id' => $nameId, + //'maxlength' => '500', + 'name' => $nameId, + 'value' => '' + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['value'] = common::$inputBefore[$attributes['id']]; + } + // Texte + $html = sprintf('', helper::sprintAttributes($attributes, ['before'])); + // Retourne le html + return $html; + } + + /** + * Crée un icône + * @param string $ico Classe de l'icône + * @param string $margin Ajoute un margin autour de l'icône (choix : left, right, all) + * @param bool $animate Ajoute une animation à l'icône + * @param string $fontSize Taille de la police + * @return string + */ + public static function ico($ico, $margin = '', $animate = false, $fontSize = '1em') { + return ''; + } + + /** + * Crée un drapeau du site courant + * @param string $langId Id de la langue à affiche ou site pour la langue traduite courante + * @param string $margin Ajoute un margin autour de l'icône (choix : left, right, all) + * @return string + */ + public static function flag($langId, $size = 'auto') { + switch ($langId) { + case '': + $lang = 'fr'; + break; + case in_array($langId,['fr', 'de', 'en', 'es', 'it', 'nl', 'pt']): + $lang = $langId; + break; + case 'site': + if ( isset($_COOKIE['DELTA_I18N_SITE']) + ) { + $lang = $_COOKIE['DELTA_I18N_SITE']; + } else { + $lang = 'fr'; + } + } + return '(' . $lang . ')'; + } + + /** + * Crée un label + * @param string $for For du label + * @param array $attributes Attributs ($key => $value) + * @param string $text Texte du label + * @return string + */ + public static function label($for, $text, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'class' => '', + 'for' => $for, + 'help' => '' + ], $attributes); + // Ajout d'une aide + if($attributes['help'] !== '') { + $text = $text . self::help($attributes['help']); + } + // Retourne le html + return sprintf( + '', + helper::sprintAttributes($attributes), + $text + ); + } + + /** + * Crée un champ mail + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function mail($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'autocomplete' => 'on', + 'before' => true, + 'class' => '', + 'classWrapper' => '', + 'noDirty' => false, + 'disabled' => false, + 'help' => '', + 'id' => $nameId, + 'label' => '', + //'maxlength' => '500', + 'name' => $nameId, + 'placeholder' => '', + 'readonly' => false, + 'value' => '' + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['value'] = common::$inputBefore[$attributes['id']]; + } + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Texte + $html .= sprintf( + '', + helper::sprintAttributes($attributes) + ); + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée une notice + * @param string $id Id du champ + * @param string $notice Notice + * @return string + */ + public static function notice($id, $notice) { + return ' ' . $notice . ''; + } + + /** + * Crée un champ mot de passe + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function password($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'autocomplete' => 'on', + 'class' => '', + 'classWrapper' => '', + 'noDirty' => false, + 'disabled' => false, + 'help' => '', + 'id' => $nameId, + 'label' => '', + //'maxlength' => '500', + 'name' => $nameId, + 'placeholder' => '', + 'readonly' => false + ], $attributes); + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Mot de passe + $html .= sprintf( + '', + helper::sprintAttributes($attributes) + ); + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée un champ sélection + * @param string $nameId Nom et id du champ + * @param array $options Liste des options du champ de sélection ($value => $text) + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function select($nameId, array $options, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'before' => true, + 'class' => '', + 'classWrapper' => '', + 'noDirty' => false, + 'disabled' => false, + 'help' => '', + 'id' => $nameId, + 'label' => '', + 'name' => $nameId, + 'selected' => '', + 'fonts' => false + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['selected'] = common::$inputBefore[$attributes['id']]; + } + // Liste des polices à intégrer + if ($attributes['fonts'] === true) { + foreach ($options as $fontId) { + echo "\n"; + } + } + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Début sélection + $html .= sprintf(''; + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée une bulle de dialogue + * @param string $text Texte de la bulle + * @return string + */ + public static function speech($text) { + return '
' . $text . '
'; + } + + /** + * Crée un bouton validation + * @param string $nameId Nom & id du bouton validation + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function submit($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'class' => '', + 'disabled' => false, + 'ico' => 'check', + 'id' => $nameId, + 'name' => $nameId, + 'uniqueSubmission' => false, //true avant 9.1.08 + 'value' => 'Enregistrer' + ], $attributes); + // Retourne le html + return sprintf( + '', + $attributes['class'], + $attributes['uniqueSubmission'] ? 'uniqueSubmission' : '', + helper::sprintAttributes($attributes, ['class', 'ico', 'value']), + ($attributes['ico'] ? template::ico($attributes['ico'], 'right') : '') . $attributes['value'] + ); + } + + /** + * Crée un tableau + * @param array $cols Cols des colonnes (format: [col colonne1, col colonne2, etc]) + * @param array $body Contenu (format: [[contenu1, contenu2, etc], [contenu1, contenu2, etc]]) + * @param array $head Entêtes (format : [[titre colonne1, titre colonne2, etc]) + * @param array $rowsId Id pour la numérotation des rows (format : [id colonne1, id colonne2, etc]) + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function table(array $cols = [], array $body = [], array $head = [], array $attributes = [], array $rowsId = []) { + // Attributs par défaut + $attributes = array_merge([ + 'class' => '', + 'classWrapper' => '', + 'id' => '' + ], $attributes); + // Début du wrapper + $html = '
'; + // Début tableau + $html .= ''; + // Entêtes + if($head) { + // Début des entêtes + $html .= ''; + $html .= ''; + $i = 0; + foreach($head as $th) { + $html .= ''; + } + // Fin des entêtes + $html .= ''; + $html .= ''; + } + // Pas de tableau d'Id transmis, générer une numérotation + if (empty($rowsId)) { + $rowsId = range(0,count($body)); + } + // Début contenu + $j = 0; + foreach($body as $tr) { + // Id de ligne pour les tableaux drag and drop + $html .= ''; + $i = 0; + foreach($tr as $td) { + $html .= ''; + } + $html .= ''; + } + // Fin contenu + $html .= ''; + // Fin tableau + $html .= '
' . $th . '
' . $td . '
'; + // Fin container + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée un champ texte court + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function text($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'autocomplete' => 'on', + 'before' => true, + 'class' => '', + 'classWrapper' => '', + 'noDirty' => false, + 'disabled' => false, + 'help' => '', + 'id' => $nameId, + 'label' => '', + //'maxlength' => '500', + 'name' => $nameId, + 'placeholder' => '', + 'readonly' => false, + 'value' => '' + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['value'] = common::$inputBefore[$attributes['id']]; + } + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Texte + $html .= sprintf( + '', + helper::sprintAttributes($attributes) + ); + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } + + /** + * Crée un champ texte long + * @param string $nameId Nom et id du champ + * @param array $attributes Attributs ($key => $value) + * @return string + */ + public static function textarea($nameId, array $attributes = []) { + // Attributs par défaut + $attributes = array_merge([ + 'before' => true, + 'class' => '', // editorWysiwyg et editor possible pour utiliser un éditeur (il faut également instancier les librairies) + 'classWrapper' => '', + 'disabled' => false, + 'noDirty' => false, + 'help' => '', + 'id' => $nameId, + 'label' => '', + //'maxlength' => '500', + 'name' => $nameId, + 'readonly' => false, + 'value' => '' + ], $attributes); + // Sauvegarde des données en cas d'erreur + if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) { + $attributes['value'] = common::$inputBefore[$attributes['id']]; + } + // Début du wrapper + $html = '
'; + // Label + if($attributes['label']) { + $html .= self::label($attributes['id'], $attributes['label'], [ + 'help' => $attributes['help'] + ]); + } + // Notice + $notice = ''; + if(array_key_exists($attributes['id'], common::$inputNotices)) { + $notice = common::$inputNotices[$attributes['id']]; + $attributes['class'] .= ' notice'; + } + $html .= self::notice($attributes['id'], $notice); + // Texte long + $html .= sprintf( + '', + helper::sprintAttributes($attributes, ['value']), + $attributes['value'] + ); + // Fin du wrapper + $html .= '
'; + // Retourne le html + return $html; + } +} diff --git a/core/core.js.php b/core/core.js.php new file mode 100644 index 0000000..6817fb7 --- /dev/null +++ b/core/core.js.php @@ -0,0 +1,549 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +var core = {}; + +/** + * Crée un message d'alerte + */ +core.alert = function(text) { + var lightbox = lity(function($) { + return $("
") + .addClass("lightbox") + .append( + $("").text(text), + $("
") + .addClass("lightboxButtons") + .append( + $("") + .addClass("button") + .text("Ok") + .on("click", function() { + lightbox.close(); + }) + ) + ) + }(jQuery)); + // Validation de la lightbox avec le bouton entrée + $(document).on("keyup", function(event) { + if(event.keyCode === 13) { + lightbox.close(); + } + }); + return false; +}; + +/** + * Génère des variations d'une couleur + */ +core.colorVariants = function(rgba) { + rgba = rgba.match(/\(+(.*)\)/); + rgba = rgba[1].split(", "); + return { + "normal": "rgba(" + rgba[0] + "," + rgba[1] + "," + rgba[2] + "," + rgba[3] + ")", + "darken": "rgba(" + Math.max(0, rgba[0] - 15) + "," + Math.max(0, rgba[1] - 15) + "," + Math.max(0, rgba[2] - 15) + "," + rgba[3] + ")", + "veryDarken": "rgba(" + Math.max(0, rgba[0] - 20) + "," + Math.max(0, rgba[1] - 20) + "," + Math.max(0, rgba[2] - 20) + "," + rgba[3] + ")", + //"text": core.relativeLuminanceW3C(rgba) > .22 ? "inherit" : "white" + "text": core.relativeLuminanceW3C(rgba) > .22 ? "#222" : "#DDD" + }; +}; + +/** + * Crée un message de confirmation + */ +core.confirm = function(text, yesCallback, noCallback) { + var lightbox = lity(function($) { + return $("
") + .addClass("lightbox") + .append( + $("").text(text), + $("
") + .addClass("lightboxButtons") + .append( + $("") + .addClass("button grey") + .text("Non") + .on("click", function() { + lightbox.options('button', true); + lightbox.close(); + if(typeof noCallback !== "undefined") { + noCallback(); + } + }), + $("") + .addClass("button") + .text("Oui") + .on("click", function() { + lightbox.options('button', true); + lightbox.close(); + if(typeof yesCallback !== "undefined") { + yesCallback(); + } + }) + ) + ) + }(jQuery)); + // Callback lors d'un clic sur le fond et sur la croix de fermeture + lightbox.options('button', false); + $(document).on('lity:close', function(event, instance) { + if( + instance.options('button') === false + && typeof noCallback !== "undefined" + ) { + noCallback(); + } + }); + // Validation de la lightbox avec le bouton entrée + $(document).on("keyup", function(event) { + if(event.keyCode === 13) { + lightbox.close(); + if(typeof yesCallback !== "undefined") { + yesCallback(); + } + } + }); + return false; +}; + +/** + * Scripts à exécuter en dernier + */ +core.end = function() { + /** + * Modifications non enregistrées du formulaire + */ + var formDOM = $("form"); + // Ignore : + // - TinyMCE car il gère lui même le message + // - Les champs avec data-no-dirty + var inputsDOM = formDOM.find("input:not([data-no-dirty]), select:not([data-no-dirty]), textarea:not(.editorWysiwyg):not([data-no-dirty])"); + var inputSerialize = inputsDOM.serialize(); + $(window).on("beforeunload", function() { + if(inputsDOM.serialize() !== inputSerialize) { + return "Les modifications que vous avez apportées ne seront peut-être pas enregistrées."; + } + }); + formDOM.submit(function() { + $(window).off("beforeunload"); + }); +}; +$(function() { + core.end(); +}); + +/** + * Ajoute une notice + */ +core.noticeAdd = function(id, notice) { + $("#" + id + "Notice").text(notice).removeClass("displayNone"); + $("#" + id).addClass("notice"); +}; + +/** + * Supprime une notice + */ +core.noticeRemove = function(id) { + $("#" + id + "Notice").text("").addClass("displayNone"); + $("#" + id).removeClass("notice"); +}; + +/** + * Scripts à exécuter en premier + */ +core.start = function() { + /** + * Remonter en haut au clic sur le bouton + */ + var backToTopDOM = $("#backToTop"); + backToTopDOM.on("click", function() { + $("body, html").animate({scrollTop: 0}, "400"); + }); + /** + * Affiche / Cache le bouton pour remonter en haut + */ + $(window).on("scroll", function() { + if($(this).scrollTop() > 200) { + backToTopDOM.fadeIn(); + } + else { + backToTopDOM.fadeOut(); + } + }); + /** + * Cache les notifications + */ + var notificationTimer; + $("#notification") + .on("mouseenter", function() { + clearTimeout(notificationTimer); + $("#notificationProgress") + .stop() + .width("100%"); + }) + .on("mouseleave", function() { + // Disparition de la notification + notificationTimer = setTimeout(function() { + $("#notification").fadeOut(); + }, 3000); + // Barre de progression + $("#notificationProgress").animate({ + "width": "0%" + }, 3000, "linear"); + }) + .trigger("mouseleave"); + $("#notificationClose").on("click", function() { + clearTimeout(notificationTimer); + $("#notification").fadeOut(); + $("#notificationProgress").stop(); + }); + + /** + * Traitement du formulaire cookies + */ + $("#cookieForm").submit(function(event){ + + // Varables des cookies + var samesite = "samesite=lax"; + var getUrl = window.location; + var domain = "domain=" + getUrl.host; + var path = "path=" + getUrl.pathname.split('/')[1]; + var samesite = "samesite=lax"; + var e = new Date(); + e.setFullYear(e.getFullYear() + 1); + var expires = "expires=" + e.toUTCString(); + + // Crée le cookie d'acceptation Cookies Externes (tiers) si le message n'est pas vide + var messageCookieExt = "getData(['locale', 'cookies', 'cookiesExtText']);?>"; + // le message de consentement des cookies externes est défini en configuration, afficher la checkbox d'acceptation + if( messageCookieExt.length > 0){ + // Traitement du retour de la checkbox + if ($("#cookiesExt").is(":checked")) { + // L'URL du serveur faut TRUE + document.cookie = "DELTA_COOKIE_EXT_CONSENT=true;" + domain + ";" + path + ";" + samesite + ";" + expires; + } else { + document.cookie = "DELTA_COOKIE_EXT_CONSENT=false;" + domain + ";" + path + ";" + samesite + ";" + expires; + } + + } + + // Stocke le cookie d'acceptation + document.cookie = "DELTA_COOKIE_CONSENT=true;" + domain + ";" + path + ";" + samesite + ";" + expires; + }); + + + /** + * Fermeture de la popup des cookies + */ + $("#cookieConsent .cookieClose").on("click", function() { + $('#cookieConsent').addClass("displayNone"); + }); + + /** + * Commande de gestion des cookies dans le footer + */ + + $("#footerLinkCookie").on("click", function() { + $("#cookieConsent").removeClass("displayNone"); + }); + + /** + * Affiche / Cache le menu en mode responsive + */ + var menuDOM = $("#menu"); + $("#toggle").on("click", function() { + menuDOM.slideToggle(); + }); + $(window).on("resize", function() { + if($(window).width() > 768) { + menuDOM.css("display", ""); + } + }); + + /** + * Choix de page dans la barre de membre + */ + $("#barSelectPage").on("change", function() { + var pageUrl = $(this).val(); + if(pageUrl) { + $(location).attr("href", pageUrl); + } + }); + /** + * Champs d'upload de fichiers + */ + // Mise à jour de l'affichage des champs d'upload + $(".inputFileHidden").on("change", function() { + var inputFileHiddenDOM = $(this); + var fileName = inputFileHiddenDOM.val(); + if(fileName === "") { + fileName = "Choisissez un fichier"; + $(inputFileHiddenDOM).addClass("disabled"); + } + else { + $(inputFileHiddenDOM).removeClass("disabled"); + } + inputFileHiddenDOM.parent().find(".inputFileLabel").text(fileName); + }).trigger("change"); + // Suppression du fichier contenu dans le champ + $(".inputFileDelete").on("click", function() { + $(this).parents(".inputWrapper").find(".inputFileHidden").val("").trigger("change"); + }); + // Suppression de la date contenu dans le champ + $(".inputDateDelete").on("click", function() { + $(this).parents(".inputWrapper").find(".datepicker").val("").trigger("change"); + }); + // Confirmation de mise à jour + $("#barUpdate").on("click", function() { + return core.confirm("Effectuer la mise à jour ?", function() { + $(location).attr("href", $("#barUpdate").attr("href")); + }); + }); + // Confirmation de déconnexion + $("#barLogout").on("click", function() { + return core.confirm("Se déconnecter ?", function() { + $(location).attr("href", $("#barLogout").attr("href")); + }); + }); + /** + * Bloque la multi-soumission des boutons + */ + $("form").on("submit", function() { + $(this).find(".uniqueSubmission") + .addClass("disabled") + .prop("disabled", true) + .empty() + .append( + $("").addClass("zwiico-spin animate-spin") + ) + }); + /** + * Check adresse email + */ + $("[type=email]").on("change", function() { + var _this = $(this); + var pattern = /^([a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|"((([ \t]*\r\n)?[ \t]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([ \t]*\r\n)?[ \t]+)?")@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.?$/i; + if(pattern.test(_this.val())) { + core.noticeRemove(_this.attr("id")); + } + else { + core.noticeAdd(_this.attr("id"), "Format incorrect"); + } + }); + + /** + * Iframes et vidéos responsives + */ + var elementDOM = $("iframe, video, embed, object"); + // Calcul du ratio et suppression de la hauteur / largeur des iframes + elementDOM.each(function() { + var _this = $(this); + _this + .data("ratio", _this.height() / _this.width()) + .data("maxwidth", _this.width()) + .removeAttr("width height"); + }); + // Prend la largeur du parent et détermine la hauteur à l'aide du ratio lors du resize de la fenêtre + $(window).on("resize", function() { + elementDOM.each(function() { + var _this = $(this); + var width = _this.parent().first().width(); + if (width > _this.data("maxwidth")){ width = _this.data("maxwidth");} + _this + .width(width) + .height(width * _this.data("ratio")); + }); + }).trigger("resize"); + + /* + * Header responsive + */ + $(window).on("resize", function() { + var responsive = "getdata(['theme','header','imageContainer']);?>"; + var feature = "getdata(['theme','header','feature']);?>"; + if ( (responsive === "cover" || responsive === "contain") && feature !== "feature" ) { + var widthpx = "getdata(['theme','site','width']);?>"; + var width = widthpx.substr(0,widthpx.length-2); + var heightpx = "getdata(['theme','header','height']);?>"; + var height = heightpx.substr(0,heightpx.length-2); + var ratio = width / height; + var feature = "getdata(['theme','header','feature']);?>"; + if ( ($(window).width() / ratio) <= height) { + $("header").height( $(window).width() / ratio ); + $("header").css("line-height", $(window).width() / ratio + "px"); + } + } + }).trigger("resize"); + +}; + + +core.start(); + +/** + * Confirmation de suppression + */ +$("#pageDelete").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir supprimer cette page ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); + +/** + * Calcul de la luminance relative d'une couleur + */ +core.relativeLuminanceW3C = function(rgba) { + // Conversion en sRGB + var RsRGB = rgba[0] / 255; + var GsRGB = rgba[1] / 255; + var BsRGB = rgba[2] / 255; + // Ajout de la transparence + var RsRGBA = rgba[3] * RsRGB + (1 - rgba[3]); + var GsRGBA = rgba[3] * GsRGB + (1 - rgba[3]); + var BsRGBA = rgba[3] * BsRGB + (1 - rgba[3]); + // Calcul de la luminance + var R = (RsRGBA <= .03928) ? RsRGBA / 12.92 : Math.pow((RsRGBA + .055) / 1.055, 2.4); + var G = (GsRGBA <= .03928) ? GsRGBA / 12.92 : Math.pow((GsRGBA + .055) / 1.055, 2.4); + var B = (BsRGBA <= .03928) ? BsRGBA / 12.92 : Math.pow((BsRGBA + .055) / 1.055, 2.4); + return .2126 * R + .7152 * G + .0722 * B; +}; + + + +$(document).ready(function(){ + /** + * Affiche le sous-menu quand il est sticky + */ + $("nav").mouseenter(function(){ + $("#navfixedlogout .navSub").css({ 'pointer-events' : 'auto' }); + $("#navfixedconnected .navSub").css({ 'pointer-events' : 'auto' }); + }); + $("nav").mouseleave(function(){ + $("#navfixedlogout .navSub").css({ 'pointer-events' : 'none' }); + $("#navfixedconnected .navSub").css({ 'pointer-events' : 'none' }); + }); + + /** + * Chargement paresseux des images et des iframes + */ + $("img,picture,iframe").attr("loading","lazy"); + + /** + * Effet accordéon + */ + $('.accordion').each(function(e) { + // on stocke l'accordéon dans une variable locale + var accordion = $(this); + // on récupère la valeur data-speed si elle existe + var toggleSpeed = accordion.attr('data-speed') || 100; + + // fonction pour afficher un élément + function open(item, speed) { + // on récupère tous les éléments, on enlève l'élément actif de ce résultat, et on les cache + accordion.find('.accordion-item').not(item).removeClass('active') + .find('.accordion-content').slideUp(speed); + // on affiche l'élément actif + item.addClass('active') + .find('.accordion-content').slideDown(speed); + } + function close(item, speed) { + accordion.find('.accordion-item').removeClass('active') + .find('.accordion-content').slideUp(speed); + } + + // on initialise l'accordéon, sans animation + open(accordion.find('.active:first'), 0); + + // au clic sur un titre... + accordion.on('click', '.accordion-title', function(ev) { + ev.preventDefault(); + // Masquer l'élément déjà actif + if ($(this).closest('.accordion-item').hasClass('active')) { + close($(this).closest('.accordion-item'), toggleSpeed); + } else { + // ...on lance l'affichage de l'élément, avec animation + open($(this).closest('.accordion-item'), toggleSpeed); + } + }); + }); + + /** + * Icône du Menu Burger + */ + $("#toggle").click(function() { + var changeIcon = $('#toggle').children("span"); + if ( $(changeIcon).hasClass('zwiico-menu') ) { + $(changeIcon).removeClass('zwiico-menu').addClass('zwiico-cancel'); + } + else { + $(changeIcon).addClass('zwiico-menu'); + }; + }); + + /** + * Active le système d'aide interne + * + */ + + $(".buttonHelp").click(function() { + $(".helpDisplayContent").slideToggle(); + /** + if( $(".buttonHelp").css('opacity') > '0.75'){ + $(".buttonHelp").css('opacity','0.5'); + } + else{ + $(".buttonHelp").css('opacity','1'); + } + */ + }); + + $(".helpDisplayContent").click(function() { + $(".helpDisplayContent").slideToggle(); + }); + + /** + * Remove ID Facebook from URL + */ + if(/^\?fbclid=/.test(location.search)) + location.replace(location.href.replace(/\?fbclid.+/, "")); + + /** + * No translate Lity close + */ + $(document).on('lity:ready', function(event, instance) { + $('.lity-close').addClass('notranslate'); + }); + + /** + * Bouton screenshot + */ + var dataURL = {}; + $('#screenshot').click(function() { + html2canvas(document.querySelector("#main_screenshot")).then(canvas => { + dataURL = canvas.toDataURL(); + $.ajax({ + type: "POST", + contentType:"application/x-www-form-urlencoded", + url: "screenshot.php", + data: { + image: dataURL + }, + dataType: "html" + }); + }); + }); + +}); diff --git a/core/include/update.inc.php b/core/include/update.inc.php new file mode 100644 index 0000000..b6a4144 --- /dev/null +++ b/core/include/update.inc.php @@ -0,0 +1,13 @@ +getData(['core', 'dataVersion']) < 3101) { + // Mise à jour + $this->setData(['core', 'dataVersion', 3101]); +} + + + +?> diff --git a/core/layout/blank.css b/core/layout/blank.css new file mode 100644 index 0000000..4256b6c --- /dev/null +++ b/core/layout/blank.css @@ -0,0 +1,23 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Éléments génériques + */ + +body { + background : #FFF !important; +} \ No newline at end of file diff --git a/core/layout/blank.php b/core/layout/blank.php new file mode 100644 index 0000000..789b1af --- /dev/null +++ b/core/layout/blank.php @@ -0,0 +1,21 @@ + + + + + + showMetaTitle(); ?> + showMetaDescription(); ?> + showMetaType(); ?> + showMetaImage(); ?> + showFavicon(); ?> + showVendor(); ?> + showStyle(); ?> + + + + + +showContent(); ?> +showScript(); ?> + + \ No newline at end of file diff --git a/core/layout/common.css b/core/layout/common.css new file mode 100644 index 0000000..dd3c12c --- /dev/null +++ b/core/layout/common.css @@ -0,0 +1,1784 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** +* Police des icônes +*/ +@import url(https://use.fontawesome.com/releases/v5.7.2/css/all.css); + + + +/** + * Éléments génériques + */ + + +html, +body { + min-height: 100%; + /*min-height: 100vh;*/ +} + + +@media screen and (min-width: 769px) { + body { + /*margin:0px 10px;*/ + margin: 0; + } +} + + + +/** +* Petits écrans inférieurs à 768px de largeur, on supprime les marges +*/ +@media screen and (max-width: 768px) { + body { + margin: 0 auto !important; + } + + #site { + margin-top: 0 !important; + } + + body>header { + margin: 0 auto !important; + } + + .tippy-tooltip { + font-size: .8rem !important; + } + + section { + padding: 10px !important; + } +} + + + +@media screen and (max-width: 768px) { + .siteContainer { + display: flex; + flex-direction: column; + } + + .siteContainer>#contentLeft { + order: 1; + } + + .siteContainer>#contentRight { + order: 3; + } + + .siteContainer>#contentSite { + order: 2; + } +} + + + +/** + * Éléments spécifiques + */ + +/* Liens */ +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* Supprimer les pointillés lors d'un clic Firefox */ +a:focus { + outline: none; +} + +a:active { + outline: none; +} + +/* Titres */ +h1 { + font-size: 1.8em; +} + +h2 { + font-size: 1.6em; +} + +h3 { + font-size: 1.4em; +} + +h4 { + font-size: 1.0em; +} + +/* Listes */ +ul { + list-style: square; +} + +li ul { + margin: 0; +} + +option.pageInactive { + color: orange; + font-weight: bold; +} +option.pageHidden { + color: red; + font-style: italic; +} + +/* Séparateur */ +hr { + border: 0; + border-top: 1px solid #C5D1D4; +} + +/* Égalisation des margins */ +h1, +h2, +h3, +h4, +p, +hr, +ul, +ol { + margin: 15px 0; +} + +/* Image */ +img { + max-width: 100%; + height: auto; +} + +img[align='left'] { + margin-right: 10px; +} + +img[align='right'] { + margin-left: 10px; +} + +#metaImage { + height: 150px; +} + +/* +Signature dans les articles blog et news +*/ + +.signature { + font-size: 0.8em; + font-style: italic; + text-align: right; +} + + +/* Tableau */ +:not([class^="col"])>.tableWrapper { + margin: 10px 0; +} + +.table { + width: 100%; + border-spacing: 0; + border-collapse: collapse; +} + +.table thead tr { + /*background: #212223; + color: #FFF;*/ + text-align: left; +} + +.table tbody tr { + background: #F6F7F8; + transition: background .3s ease-out; +} + +.table tbody tr:nth-child(2n + 2) { + background: #ECEFF1; +} + +.table tbody tr:hover { + background: #FCF2E8; +} + +.table th { + font-weight: normal; + padding: 15px 10px; +} + +/* Supprime le padding des cols dans les cellules des tableaux */ +td>.col1, +td>.col2, +td>.col3, +td>.col4, +td>.col5, +td>.col6, +td>.col7, +td>.col8, +td>.col9, +td>.col10, +td>.col11, +td>.col12 { + padding: 0 !important; +} + +/* Tableau sur les écrans de petites tailles */ +@media screen and (max-width: 768px) { + .table thead { + font-size: 0.8em; + } +} +@media screen and (max-width: 668px) { + .table thead { + display:none; + } +} + +/* Notifications */ +#notification { + padding: 14px; + color: #FFF; + position: fixed; + left: 50%; + transform: translateX(-50%); + max-width: 500px; + width: 100%; + z-index: 20; + text-align: center; + animation: notificationBounce .5s; + top: 30px; + border-radius: 2px; +} + +#notification.notificationSuccess { + background: #27AE60; +} + +#notification.notificationError { + background: #E74C3C; +} + +#notification.notificationOther { + background: #F39C12; +} + +#notificationClose { + cursor: pointer; + float: right; + opacity: .8; +} + +#notificationProgress { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 6px; + border-radius: 2px; +} + +#notification.notificationSuccess #notificationProgress { + background: #219251; +} + +#notification.notificationError #notificationProgress { + background: #D62C1A; +} + +#notification.notificationOther #notificationProgress { + background: #D8890B; +} + +#notificationClose:hover { + opacity: 1; +} + +@keyframes notificationBounce { + 0% { + transform: translateX(-50%) translateY(-30px); + } + + 40% { + transform: translateX(-50%) translateY(10px); + } + + 60% { + transform: translateX(-50%) translateY(-10px); + } + + 80% { + transform: translateX(-50%) translateY(5px); + } + + 100% { + transform: translateX(-50%) translateY(-5px); + } +} + +/* Notice */ +.notice { + display: inline-block; + color: #E74C3C; +} +/* Mauvaise position dans les champs File*/ +.inputFile.notice { + display: block; +} + +/* Container */ +.container { + margin: auto; +} + +.container-large { + width: 100%; +} + +.container-large-fixed { + width: 100%; + position: fixed; + z-index: 15; +} + +/* Barre de membre */ +#bar { + background: #212223; + padding-left: 5px; + /* Pour éviter que le select touche le bord lorsque la fenêtre est redimensionnée */ + margin: 0; + /*-10px;*/ + text-align: right; + position: -webkit-sticky; + /* Safari */ + position: sticky; + top: 0; + z-index: 19; +} + +/* fin barre pour les membres */ +#bar:after { + content: " "; + clear: both; + display: block; +} + +#bar ul { + padding: 0; + margin: 0; + list-style: none; + height: 45px; + line-height: 45px; +} + +#bar li { + display: inline; +} + +#bar a { + display: inline-block; + padding: 0 12px; + color: #FFF; + transition: background .3s ease-out; +} + +#bar a:hover { + background: #191A1A; + text-decoration: none; +} + +#bar a:active { + background: #111112; +} + +#bar select { + width: 250px; + border: 0; + color: #111112; + font-size: 12px; + background-color: rgba(255, 255, 255, 1); +} + +@media screen and (min-width: 769px) { + #bar #barLeft { + float: left; + } + + #bar #barRight { + float: right; + font-size: 12px; + } +} + +@media screen and (max-width: 768px) { + #bar { + text-align: center; + /*padding: 0 1;*/ + margin: 0 auto !important; + } + + #bar ul { + height: auto; + } + + #bar #barSelectPage { + width: 40%; + font-size: 1em; + } + + #bar #barLeft { + font-size: 1.2em; + float: none; + } + + #bar #barRight { + font-size: 1.4em; + } + + #bar #displayUsername { + display: none; + } +} + + +/* Site */ +#site { + overflow: hidden; +} +/* Dans theme.css +@media screen and (min-width:769px) { + #site { + margin: 20px auto; + } +} +*/ + +/* Bannière */ + +@media screen and (min-width:769px) { + body>header { + margin: 0; + /*-10px;*/ + } + + header { + margin: 0; + } +} + +header { + position: relative; + padding: 0; +} + +header span { + display: inline-block; + vertical-align: middle; + line-height: 1.2; + margin: 0 10px; +} + +header .container { + overflow: hidden; + height: 100%; +} + +/* Marges au cotenu de la bannière personnalisée +header:not(.container) #featureContent { + margin: 0 10px; +}*/ + +header #featureContent { + overflow: hidden; + margin: 0 10px; +} + + + +/* Element du header + +#themeHeaderImage { + font-style: italic; + font-size: 0.9em; +}*/ + +/* Menu +body > nav { + margin: 0 -10px; +} +*/ + +/* Items du menu */ +nav a>img { + margin: -4px 0; + vertical-align: middle; +} + +nav #toggle { + cursor: pointer; + text-align: right; + display: none; + font-weight: bold; +} + +nav #toggle span { + display: block; +} + +nav #burgerText { + float: left; + font-size: 1.4em; + margin: 15px 0 0 10px; +} + +nav #burgerLogo { + float: left; + margin: 2px 0 0 5px; +} + +nav ul { + padding: 0; + margin: 0; + list-style: none; +} + +nav li { + display: inline-block; + position: relative; +} + +nav li ul { + display: block; + position: absolute; + width: 200px; + z-index: -1; + opacity: 0; + transition: .3s ease-out; + padding-left: 10px; +} + +nav li ul li { + display: block; + text-align: left; +} + +/* +nav .navSub a{ + background-color:red !important; +}*/ + +nav li:hover ul { + z-index: 8; + opacity: 1; +} + +nav a { + display: inherit; + transition: background .3s ease-out; +} + +nav a:hover { + text-decoration: none; +} + + +/* Barre de menu */ + +#menuLeft { + display: inline-flex; +} + +#menuRight { + display: inline-flex ; + float: right; +} + +/* fin barre de menu */ +nav::after { + content: " "; + clear: both; + display: flex; +} + +nav::before { + content: " "; + clear: left; + display: flex; +} + +@media screen and (min-width: 769px) { + nav #menu { + display: block; + } +} + +@media screen and (max-width: 768px) { + body>nav { + margin: 0 auto !important; + } + + nav #toggle, + nav #menuLeft { + display: block; + float: none; + } + + nav #menuLeft { + flex-direction: column; + float: none; + } + + nav #menuRight { + font-size: 2em; + } + + nav #menu { + display: none; + text-align: left; + } + + nav li { + display: block; + } + + nav li ul { + z-index: 1; + opacity: 1; + position: static; + min-width: inherit; + width: auto; + } + + /* Taille du menu hamburger */ + nav .zwiico-menu { + font-size: 1.5em; + } + + nav .zwiico-cancel { + font-size: 1.5em; + } +} + + +/* Barre de navigation fixe quand le menu est en-dehors du site */ +#navfixedlogout { + position: -webkit-sticky; + /* Safari */ + position: sticky; + top: 0px; + z-index: 10; +} + +#navfixedconnected { + top: 45px; + z-index: 10; + position: -webkit-sticky; + /* Safari */ + position: sticky; +} + +#navfixedconnected .navSub, +#navfixedlogout .navSub { + pointer-events: none; +} + + +/* Menu vertical */ + +.menuSide, +.menuSideChild { + padding-left: 10px; + margin: 0px; + list-style: none; +} + +ul .menuSideChild, +li .menuSideChild { + padding-left: 10px; +} + +/* Drapeaux */ + +.i18nFlag { + width: 70%; +} + +.i18nFlagSelected { + width: 100%; +} + + +/* +* Position du bloc dans le site sur les petits écrans +*/ + + +@media screen and (max-width:1024px){ + + + .i18nFlag { + width: 100%; + padding: 5px; + } + + .i18nFlagSelected { + width: 130%; + padding: 5px; + } + + } + + + +/* Corps */ +@media screen and (min-width:769px) { + section { + padding: 20px; + } +} + +section { + /*min-height: 100%;*/ + min-height: 65vh; +} + +section #sectionTitle { + margin-top: 0; +} + +.userLogin, +.updateForm { + min-height: 0px; +} + +section:after { + content: " "; + display: table; + clear: both; +} + +/* Version des modules*/ +.moduleVersion { + font-size: 0.8em; + font-style: italic; + text-align: right; +} + +/* Pied de page */ +body>footer { + margin: 0; + /* -10px;*/ +} + +/* +footer { + padding: 1px 20px; +} +*/ + +#footerbody, +#footersite { + margin: 0; +} + +.footerbodyFixed { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + z-index: 50; + background-color: inherit; + padding: inherit; +} + +#footersiteRight, +#footersiteLeft, +#footersiteCenter, +#footerbodyRight, +#footerbodyLeft, +#footerbodyCenter { + vertical-align: middle; + padding: 0; +} + +footer #footerbody>div { + margin: 0 +} + +footer #footersite>div { + padding: 0 +} + +footer #footerbody>div { + padding: 0 +} + +#footerText>p { + margin-top: 0; + margin-bottom: 0; +} + +#footerLoginLink, +#footerDisplayCopyright, +#footerDisplayVersion, +#footerDisplaySiteMap, +#footerDisplayLegal, +#footerDisplayCookie, +#footerDisplaySearch, +#footerDeltaCMS { + font-size: inherit; +} + +/* Conserve le pied de page sur une ligne */ +@media screen and (max-width: 768px) { + body>footer { + margin: 0 auto !important; + } + + footer .col4 { + width: 100%; + } + + #footerCopyright, + #footerText, + #footerSocials { + display: flex; + justify-content: center; + } +} + +footer #footerSocials span { + color: #FFF; + padding: 9px; + margin: 0 5px; + display: inline-block; + border-radius: 2px; + transition: background .3s ease-out; +} + +footer #footerSocials .zwiico-facebook { + background: #3B5999; +} + +footer #footerSocials .zwiico-facebook:hover { + background: #324B80; +} + +footer #footerSocials .zwiico-linkedin { + background: #007BB6; +} + +footer #footerSocials .zwiico-linkedin:hover { + background: #006881; +} + +footer #footerSocials .zwiico-instagram { + background: #E4405F; +} + +footer #footerSocials .zwiico-instagram:hover { + background: #E02246; +} + +footer #footerSocials .zwiico-pinterest { + background: #BD081C; +} + +footer #footerSocials .zwiico-pinterest:hover { + background: #9C0717; +} + +footer #footerSocials .zwiico-twitter { + background: #55ACEE; +} + +footer #footerSocials .zwiico-twitter:hover { + background: #369DEB; +} + +footer #footerSocials .zwiico-youtube { + background: #CD201F; +} + +footer #footerSocials .zwiico-youtube:hover { + background: #AF1B1B; +} + +footer #footerSocials .zwiico-github { + background: #000; +} + +footer #footerSocials .zwiico-github:hover { + background: #000; +} + +/* Bulle de dialogue */ +.speech { + margin: 16px; + text-align: center; +} + +.speechMimi { + display: block; + margin: auto; +} + +.speechBubble { + display: block; + padding: 20px; + position: relative; + max-width: 500px; + width: 100%; + margin: 16px auto; + text-align: left; + border-radius: 2px; + transition: background .3s ease-out; +} + +.speechBubble:before { + content: " "; + position: absolute; + left: 50%; + margin-left: -20px; + bottom: -30px; + border: 20px solid; +} + +/* Remonter en haut */ +#backToTop { + position: fixed; + z-index: 50; + right: 30px; + bottom: 100px; + padding: 13px 16px 16px; + /* + Paramétré dans le thème (9.2.21) + background: rgba(33, 34, 35, .8); + color: #FFF;*/ + cursor: pointer; + display: none; + border-radius: 50%; + transition: background .3s ease-out; +} + +#backToTop:hover { + background: rgba(33, 34, 35, .9); +} + +#backToTop:active { + background: rgba(33, 34, 35, 1); +} + +/* Message sur les cookies */ +#cookieConsent { + width: 60%; + margin: auto; + /* background: #212223; dans theme.css via core.php */ + position: fixed; + right: 0; + bottom: 5%; + left: 0; + /* color: #FFF; dans theme.css via core.php */ + padding: 10px; + z-index: 60; + text-align: center; + font-size: 1em; +} + +#cookieConsentConfirm { + /* background-color: green; dans theme.css via core.php */ +} + +#cookieConsentConfirm { + cursor: pointer; + margin-left: 10px; + padding: 4px 8px; + display: inline-block; + transition: background .3s ease-out; +} + +#cookieConsentConfirm:hover { + filter: grayscale(100%); +} + +#cookieConsent .cookieClose { + position: absolute; + right: 10px; + top: 10px; + font-size: 1.5em; + cursor: pointer; +} + +/* Bloc */ +.block { + /* border: 1px solid #D8DFE3;*/ + padding: 20px 20px 10px; + margin: 20px 0; + word-wrap: break-word; + border-radius: 2px; +} + +.block:first-of-type { + margin-top: 0; +} + +.block:last-of-type { + margin-bottom: 0; +} + +.block h4 { + margin: -20px -20px 10px -20px; + padding: 10px; + /* background: #ECEFF1;*/ +} + +.block h4 .openClose { + display: inline-flex; + float: right; +} + +/* Aides */ +.helpButton { + cursor: help; + margin: 0 5px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +/* Lightbox */ +.lightbox { + background: #FFF; + width: 100vw; + max-width: 500px; + padding: 20px; + +} + +.lightbox>span { + color: black; +} + +.lightbox .lightboxButtons { + text-align: center; + margin-top: 30px; +} + +.lightbox .lightboxButtons .button { + width: 100%; + max-width: 80px; + margin: 0 10px; +} + +/** + * Inputs + */ + +/* Inputs génériques */ +input[type='email'], +input[type='text'], +input[type='password'], +.inputFile, +select, +textarea { + padding: 9px; + /* -1px à cause des bordures */ + /*background: #FFF;*/ + border: 1px solid #D8DFE3; + width: 100%; + border-radius: 2px; + font-family: inherit; + transition: border .3s ease-out; +} + +select { + padding: 9px; +} + +input[type='email']:hover, +input[type='text']:hover, +input[type='password']:hover, +.inputFile:hover, +select:hover, +textarea:hover { + border: 1px solid; +} + +input[type='email'].notice, +input[type='text'].notice, +input[type='password'].notice, +.inputFile.notice, +select.notice, +textarea.notice { + border: 1px solid #E74C3C; + /*background: #FAD7D3;*/ +} + +input[type='email'].notice:hover, +input[type='text'].notice:hover, +input[type='password'].notice:hover, +.inputFile.notice:hover, +select.notice:hover, +textarea.notice:hover { + border: 1px solid #A82315; +} + +button:disabled, +input:disabled, +select:disabled, +textarea:disabled { + background: #F6F7F8 !important; + color: #94A5B0 !important; +} + +button:disabled .zwiico-spin { + color: #50616C !important + /* Icône de soumission unique */ +} + +button { + width: 100%; + padding: 11px; + border: 0; + cursor: pointer; + font-family: inherit; + border-radius: 2px; + transition: background .3s ease-out; +} + +textarea { + height: 100px; + resize: vertical; +} + +label { + display: block; + margin-bottom: 4px; +} + +.captcha label { + display:inline-block; +} + +/* Simule le padding des cols pour les inputs en dehors des cols */ +:not([class^="col"])>.inputWrapper { + padding: 10px 0; +} + +/* Supprime le padding d'une row dans un col */ +[class^="col"]>.row { + margin: -10px; +} + +/* Bouton */ +.button { + width: 100%; + display: inline-block; + padding: 11px; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + border-radius: 2px; + transition: background .3s ease-out; +} + +/* Bouton redimensionnable pour le formulaire*/ +#formSubmit { + width: max-content; + float: right; +} + +.button:hover { + text-decoration: none; +} + +.button.disabled { + pointer-events: none; + cursor: default; + background: #F6F7F8 !important; + color: #94A5B0 !important; +} + +/* Upload de fichiers */ +.inputFile, .datepicker { + margin: 0; + display: inline-block; + width: 88% !important; +} + +.inputFileDelete, .inputDateDelete { + display: block; + width: 10%; + padding: 10px 0; + background: #F5F5F5; + text-align: center; + float: right; + min-height: 100%; +} + +.inputFile:hover { + text-decoration: none; +} + +/* Empêche le débordement et les sauts de ligne */ +.inputFileManagerWrapper, .inputDateManagerWrapper { + display: inline; +} + +.inputFileManagerWrapper>.inputFile, .inputDateManagerWrapper>.inputFile { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Pagination */ +.pagination { + padding: 10px 0; +} + +.pagination a { + display: inline-block; + padding: 10px; + margin: 5px; + text-align: center; +} + +.pagination a:hover { + text-decoration: none; +} + +.pagination a.disabled { + pointer-events: none; + cursor: default; + background: #F6F7F8 !important; + color: #94A5B0 !important; +} + +.pagination a:first-child { + margin-left: 0; +} + +.pagination a:last-child { + margin-right: 0; +} + +/* Cases à cocher (pas de display none sinon le hover ne fonctionne pas sous Chrome) */ +input[type='checkbox'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + opacity: 0; + height: 0; + width: 0; +} + +input[type='checkbox']+label { + display: inline-block; + margin-right: 10px; + cursor: pointer; +} + +input[type='checkbox']+label span { + vertical-align: middle; +} + +input[type='checkbox']+label:before { + content: '\2713'; + display: inline-block; + text-align: center; + color: transparent; + margin-right: 5px; + width: 20px; + height: 20px; + line-height: 20px; + font-size: 10px; + font-weight: bold; + background: #FDFDFD; + border: 1px solid #D8DFE3; + vertical-align: top; + border-radius: 2px; +} + +input[type='checkbox'].notice+label:before { + background: #E74C3C; +} + +input[type='checkbox']:hover+label:before, +input[type='checkbox']:checked:active+label:before { + background: #ECEFF1; +} + +input[type='checkbox']:disabled+label:before { + background: #F6F7F8 !important; +} + +/* Sélecteur de date */ +.datepicker { + cursor: text; +} + +.pika-select { + padding: 0; + /* À cause du padding ajouté aux selects */ +} + +/* Paramètres de l'étiquette dans form */ +.formLabel { + margin-top: 20px; +} + +.formLabel hr { + border: 1px solid; + margin: 5px 0 5px; +} + + +/** + * Grille + */ + +*, +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.row { + margin: 0 -10px; + font-size: 0; +} + +/* Supprime les margins du premier et du dernier élément d'un col, utile pour les cols générés depuis l'éditeur de texte. (Ne s'applique pas aux rows pour ne pas supprimer les margins négatifs) */ +.row>div> :first-child:not(.row) { + margin-top: 0; +} + +.row>div> :last-child:not(.row) { + margin-bottom: 0; +} + +.col1, +.col2, +.col3, +.col4, +.col5, +.col6, +.col7, +.col8, +.col9, +.col10, +.col11, +.col12 { + vertical-align: top; + padding: 10px; + width: 100%; + min-height: 1px; + display: inline-block; +} + +@media screen and (min-width: 769px) { + .col1 { + width: 8.33333333%; + } + + .col2 { + width: 16.66666667%; + } + + .col3 { + width: 25%; + } + + .col4 { + width: 33.33333333%; + } + + .col5 { + width: 41.66666667%; + } + + .col6 { + width: 50%; + } + + .col7 { + width: 58.33333333%; + } + + .col8 { + width: 66.66666667%; + } + + .col9 { + width: 75%; + } + + .col10 { + width: 83.33333333%; + } + + .col11 { + width: 91.66666667%; + } + + .col12 { + width: 100%; + } + + .offset1 { + margin-left: 8.33333333%; + } + + .offset2 { + margin-left: 16.66666667%; + } + + .offset3 { + margin-left: 25%; + } + + .offset4 { + margin-left: 33.33333333%; + } + + .offset5 { + margin-left: 41.66666667%; + } + + .offset6 { + margin-left: 50%; + } + + .offset7 { + margin-left: 58.33333333%; + } + + .offset8 { + margin-left: 66.66666667%; + } + + .offset9 { + margin-left: 75%; + } + + .offset10 { + margin-left: 83.33333333%; + } + + .offset11 { + margin-left: 91.66666667%; + } +} + +/** + * Grille pour tableau + */ + +td.col1, +th.col1, +td.col2, +th.col2, +td.col3, +th.col3, +td.col4, +th.col4, +td.col5, +th.col5, +td.col6, +th.col6, +td.col7, +th.col7, +td.col8, +th.col8, +td.col9, +th.col9, +td.col10, +th.col10, +td.col11, +th.col11, +td.col12, +th.col12 { + vertical-align: inherit; + width: 100%; + min-height: 1px; + display: table-cell; +} + +td.col1, +th.col1 { + width: 8.33333333%; +} + +td.col2, +th.col2 { + width: 16.66666667%; +} + +td.col3, +th.col3 { + width: 25%; +} + +td.col4, +th.col4 { + width: 33.33333333%; +} + +td.col5, +th.col5 { + width: 41.66666667%; +} + +td.col6, +th.col6 { + width: 50%; +} + +td.col7, +th.col7 { + width: 58.33333333%; +} + +td.col8, +th.col8 { + width: 66.66666667%; +} + +td.col9, +th.col9 { + width: 75%; +} + +td.col10, +th.col10 { + width: 83.33333333%; +} + +td.col11, +th.col11 { + width: 91.66666667%; +} + +td.col12, +th.col12 { + width: 100%; +} + +/* Tableau sur les écrans de très petites tailles */ +@media screen and (max-width: 480px){ + .table tr{ + display: block; + margin-bottom: 10px; + } + .table td { + display: block; + text-align: right; + width: auto; + } +} + +/** + * Classes rapides + */ + +.displayNone { + display: none; +} + +.textAlignCenter { + text-align: center; +} + +.textAlignRight { + text-align: right; +} + +.verticalAlignBottom { + vertical-align: bottom; +} + +.verticalAlignMiddle { + vertical-align: middle; +} + +.clearBoth { + clear: both; +} + +.colorGreen { + color: #27AE60; +} + +.colorRed { + color: #E74C3C; +} + +.colorOrange { + color: #F39C12; +} + + + +/* +* Effet accordéon +*/ + +.accordion { + padding: 0; + list-style: none; +} + +.accordion-title { + display: block; + margin: 0; + padding: 0 7px; + line-height: 34px; + text-decoration: none; + cursor: pointer; +} + +.accordion-title:hover { + background: lightgrey; +} + +.accordion-content { + padding: 7px; +} + +/* Captcha +*/ + +.captchaNum, .captchaAlpha { + vertical-align: middle; + padding-left: 10px; + padding-right: 10px; +} + +.captchaNum { + height: 5em; +} + +.captchaAlpha { + height: 2em; +} + +.captcha input[type='text'] { + width: 4em; + text-align: center; + margin: auto auto auto 2em; +} + +/* +* Couleur des icônes + et - +*/ +.zwiico-minus-circled, +.zwiico-plus-circled { + color: #D8890B; + font-size: 1.3em !important; +} + +.zwiico-minus-circled, +.zwiico-plus-circled { + transition: all 1s ease; +} + +.zwiico-minus-circled:hover, +.zwiico-plus-circled:hover { + -webkit-transform:scale(1.25); /* Safari et Chrome */ + -moz-transform:scale(1.25); /* Firefox */ + -ms-transform:scale(1.25); /* Internet Explorer 9 */ + -o-transform:scale(1.25); /* Opera */ + transform:scale(1.25); + } + + +/* Traduction auto */ +/* +* Supprimer Le tooltip Google +* +.goog-tooltip { + display: none !important; +} +.goog-tooltip:hover { + display: none !important; +} +.goog-text-highlight { + background-color: transparent !important; + border: none !important; + box-shadow: none !important; +} */ + +/* Emplacement des conditions d'utilisation */ +#googTransLogo { + float: right; +} +#googTransLogo img { + width: 60%; +} + +/* Système d'aide */ + +.helpDisplayContent { + display: none; + width: 100%; + padding: 10px 10px; + -webkit-box-shadow: 5px 5px 11px 0px #222222; + box-shadow: 5px 5px 11px 0px #222222; + border-radius: 5px; + z-index: 30; +} + +.helpDisplayContent, .helpDisplayButton { + cursor: pointer; +} + +/* Bouton screenshot */ +.buttonScreenshot { + position: absolute; + padding: 0px; + background-color: rgba(255,255,255,0); + bottom: 30px; + right: 30px; + width: 100px; + height: 68px; +} + +.buttonScreenshot:active { + width: 90px; +} + +/* Bannière masquable en petit écran*/ +@media screen and (max-width: 768px) { + .bannerDisplay{ + display : none; + } +} diff --git a/core/layout/light.css b/core/layout/light.css new file mode 100644 index 0000000..22ae43b --- /dev/null +++ b/core/layout/light.css @@ -0,0 +1,28 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Éléments spécifiques + */ + +/* Site */ +#site { + max-width: 600px !important; +} + +section { + min-height: 0px; +} \ No newline at end of file diff --git a/core/layout/light.php b/core/layout/light.php new file mode 100644 index 0000000..833cb19 --- /dev/null +++ b/core/layout/light.php @@ -0,0 +1,25 @@ + + + + + + showMetaTitle(); ?> + showMetaDescription(); ?> + showMetaType(); ?> + showMetaImage(); ?> + showFavicon(); ?> + showVendor(); ?> + showStyle(); ?> + + + + + + +showNotification(); ?> +
+
showContent(); ?>
+
+showScript(); ?> + + \ No newline at end of file diff --git a/core/layout/mail.php b/core/layout/mail.php new file mode 100644 index 0000000..10ed608 --- /dev/null +++ b/core/layout/mail.php @@ -0,0 +1,138 @@ + + + + + + + + <?php echo $subject; ?> + + + + + +
+
+ +
+ +
+ + diff --git a/core/layout/main.php b/core/layout/main.php new file mode 100644 index 0000000..4078346 --- /dev/null +++ b/core/layout/main.php @@ -0,0 +1,229 @@ + + + + + + showMetaTitle(); ?> + showMetaDescription(); ?> + showMetaType(); ?> + showMetaImage(); ?> + showFavicon(); ?> + showVendor(); ?> + + + + + getData(['page', $this->getUrl(0), 'moduleId']) === 'blog' + OR $this->getData(['page', $this->getUrl(0), 'moduleId']) === 'news' ) + AND $this->getData(['module', $this->getUrl(0), 'config', 'feeds']) === TRUE ): ?> + + + showStyle(); ?> + + + + + + + + getUser('group') > self::GROUP_MEMBER): ?> + showBar(); ?> + + + + showNotification(); ?> + + + getData(['theme', 'menu', 'burgerLogo']); + if( $this->getData(['theme','menu','burgerContent']) === 'logo' + && file_exists( $fileLogo) + && $this->getData(['theme', 'menu', 'burgerLogo']) !== '' ){ + $fontsize = $this->getData(['theme', 'text', 'fontSize']); + $pospx = strpos($fontsize, 'px'); + $fontsize = (int) substr( $fontsize, 0, $pospx); + $height = $this->getData(['theme', 'menu', 'height']); + $pospx = strpos($height, 'px'); + $height = (int) substr( $height, 0, $pospx); + $heightLogo = 2*($height + $fontsize) - 4; + $arrayImage = getimagesize( $fileLogo ); + $heightImage = $arrayImage[1]; + $widthImage = $arrayImage[0]; + if( $heightImage !== 0 && $heightImage !== null){ + $widthLogo = (int) ($widthImage * ( ($heightLogo - 1) / $heightImage)); + } + else + { + $widthLogo = 30; + } + } ?> + + + +
+ + + + getData(['theme', 'menu', 'position']) === 'body-first' || $this->getData(['theme', 'menu', 'position']) === 'top' ): ?> + + getData(['theme', 'menu', 'position']) === 'top' + AND $this->getData(['theme', 'menu', 'fixed']) === true + AND $this->getUser('password') === $this->getInput('DELTA_USER_PASSWORD') + AND $this->getUser('group') > self::GROUP_MEMBER) { + echo '
+
+ + + +
+ + showCookies(); ?> + + showScript();?> + + + diff --git a/core/module/addon/addon.php b/core/module/addon/addon.php new file mode 100644 index 0000000..dec11de --- /dev/null +++ b/core/module/addon/addon.php @@ -0,0 +1,591 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class addon extends common { + + public static $actions = [ + 'index' => self::GROUP_ADMIN, + 'delete' => self::GROUP_ADMIN, + 'export' => self::GROUP_ADMIN, + 'import' => self::GROUP_ADMIN, + 'store' => self::GROUP_ADMIN, + 'item' => self::GROUP_ADMIN, + 'upload' => self::GROUP_ADMIN, + 'uploadItem'=> self::GROUP_ADMIN + ]; + + // URL des modules + const BASEURL_STORE = 'https://store.deltacms.fr/'; + const MODULE_STORE = '?modules/'; + + // Gestion des modules + public static $modInstal = []; + + // pour tests + public static $valeur = []; + + // le catalogue + public static $storeList = []; + public static $storeItem = []; + + + /* + * Effacement d'un module installé et non utilisé + */ + public function delete() { + + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'state' => false, + 'notification' => 'Action non autorisée' + ]); + } + else{ + // Suppression des dossiers + $infoModules = helper::getModules(); + $module = $this->getUrl(2); + //Liste des dossiers associés au module non effacés + if( $this->removeDir('./module/'.$module ) === true ){ + $success = true; + $notification = 'Module '. $module .' désinstallé'; + if( is_dir($infoModules[$this->getUrl(2)]['dataDirectory']) ) { + if (!$this->removeDir($infoModules[$this->getUrl(2)]['dataDirectory'])){ + $notification = 'Module '.$module .' désinstallé, il reste des données dans ' . $infoModules[$this->getUrl(2)]['dataDirectory']; + } + } + } + else{ + $success = false; + $notification = 'La suppression a échouée'; + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'notification' => $notification, + 'state' => $success + ]); + } + } + + /*** + * Installation d'un module + * Fonction utilisée par upload et storeUpload + */ + private function install ($moduleName, $checkValid){ + $tempFolder = 'datamodules';//uniqid(); + $zip = new ZipArchive(); + if ($zip->open($moduleName) === TRUE) { + $notification = 'Archive ouverte'; + mkdir (self::TEMP_DIR . $tempFolder, 0755); + $zip->extractTo(self::TEMP_DIR . $tempFolder ); + // Archive de module ? + $success = false; + $notification = 'Ce n\'est pas l\'archive d\'un module !'; + $moduleDir = self::TEMP_DIR . $tempFolder . '/module'; + $moduleName = ''; + if ( is_dir( $moduleDir )) { + // Lire le nom du module + if ($dh = opendir( $moduleDir )) { + while ( false !== ($file = readdir($dh)) ) { + if ($file != "." && $file != "..") { + $moduleName = $file; + } + } + closedir($dh); + } + // Module normalisé ? + if( is_file( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php' ) AND is_file( $moduleDir.'/'.$moduleName.'/view/index/index.php' ) ){ + + // Lecture de la version et de la validation d'update du module pour validation de la mise à jour + // Pour une version <= version installée l'utilisateur doit cocher 'Mise à jour forcée' + $version = '0.0'; + $update = '0.0'; + $valUpdate = false; + $file = file_get_contents( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php'); + $file = str_replace(' ','',$file); + $file = str_replace("\t",'',$file); + $pos1 = strpos($file, 'constVERSION'); + if( $pos1 !== false){ + $posdeb = strpos($file, "'", $pos1); + $posend = strpos($file, "'", $posdeb + 1); + $version = substr($file, $posdeb + 1, $posend - $posdeb - 1); + } + $pos1 = strpos($file, 'constUPDATE'); + if( $pos1 !== false){ + $posdeb = strpos($file, "'", $pos1); + $posend = strpos($file, "'", $posdeb + 1); + $update = substr($file, $posdeb + 1, $posend - $posdeb - 1); + } + // Si version actuelle >= version indiquée dans UPDATE la mise à jour est validée + $infoModules = helper::getModules(); + if( $infoModules[$moduleName]['update'] >= $update ) $valUpdate = true; + + // Module déjà installé ? + $moduleInstal = false; + foreach($infoModules as $key=>$value ){ + if($moduleName === $key){ + $moduleInstal = true; + } + } + + // Validation de la maj si autorisation du concepteur du module ET + // ( Version plus récente OU Check de forçage ) + $valNewVersion = floatval($version); + $valInstalVersion = floatval( $infoModules[$moduleName]['version'] ); + $newVersion = false; + if( $valNewVersion > $valInstalVersion ) $newVersion = true; + $validMaj = $valUpdate && ( $newVersion || $checkValid); + + // Nouvelle installation ou mise à jour du module + if( ! $moduleInstal || $validMaj ){ + // Copie récursive des dossiers + $this->copyDir( self::TEMP_DIR . $tempFolder, './' ); + $success = true; + if( ! $moduleInstal ){ + $notification = 'Module '.$moduleName.' installé'; + } + else{ + $notification = 'Module '.$moduleName.' mis à jour'; + } + } + else{ + $success = false; + if( $valNewVersion == $valInstalVersion){ + $notification = ' Version détectée '.$version.' = à celle installée '.$infoModules[$moduleName]['version']; + } + else{ + $notification = ' Version détectée '.$version.' < à celle installée '.$infoModules[$moduleName]['version']; + } + if( $valUpdate === false){ + if( $infoModules[$moduleName]['update'] === $update ){ + $notification = ' Mise à jour par ce procédé interdite par le concepteur du module'; + } + else{ + $notification = ' Mise à jour par ce procédé interdite, votre version est trop ancienne'; + } + } + } + } + } + // Supprimer le dossier temporaire même si le module est invalide + $this->removeDir(self::TEMP_DIR . $tempFolder); + $zip->close(); + } else { + // erreur à l'ouverture + $success = false; + $notification = 'Impossible d\'ouvrir l\'archive'; + } + return(['success' => $success, + 'notification'=> $notification + ]); + } + + /*** + * Installation d'un module à partir du gestionnaire de fichier + */ + public function upload() { + // Soumission du formulaire + if($this->isPost()) { + // Installation d'un module + $checkValidMaj = $this->getInput('configModulesCheck', helper::FILTER_BOOLEAN); + $zipFilename = $this->getInput('configModulesInstallation', helper::FILTER_STRING_SHORT); + if( $zipFilename !== ''){ + $success = [ + 'success' => false, + 'notification'=> '' + ]; + $state = $this->install(self::FILE_DIR.'source/'.$zipFilename, $checkValidMaj); + } + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(), + 'notification' => $state['notification'], + 'state' => $state['success'] + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Installer un module', + 'view' => 'upload' + ]); + } + + /*** + * Installation d'un module par le catalogue + */ + public function uploadItem() { + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'store', + 'state' => false, + 'notification' => 'Action non autorisée' + ]); + } else { + // Récupérer le module en ligne + $moduleName = $this->getUrl(2); + // Informations sur les module en ligne + $store = json_decode(helper::urlGetContents(self::BASEURL_STORE . self::MODULE_STORE . 'list'), true); + // Url du module à télécharger + $moduleFilePath = $store[$moduleName]['file']; + // Télécharger le fichier + $moduleData = helper::urlGetContents(self::BASEURL_STORE . self::FILE_DIR . 'source/' . $moduleFilePath); + // Extraire de l'arborescence + $d = explode('/',$moduleFilePath); + $moduleFile = $d[count($d)-1]; + // Créer le dossier modules + if (!is_dir(self::FILE_DIR . 'source/modules')) { + mkdir (self::FILE_DIR . 'source/modules', 0755); + } + // Sauver les données du fichiers + file_put_contents(self::FILE_DIR . 'source/modules/' . $moduleFile, $moduleData); + + /** + * $if( $moduleFile !== ''){ + * $success = [ + * 'success' => false, + * 'notification'=> '' + * ]; + * $state = $this->install(self::FILE_DIR.'source/modules/'.$moduleFile, false); + *} + */ + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon/store', + 'notification' => $moduleFile . ' téléchargé dans le dossier modules du gestionnaire de fichiers', + 'state' => true + ]); + + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Catalogue de modules', + 'view' => 'store' + ]); + } + + /** + * Catalogue des modules sur le site DeltaCMS.fr + */ + public function store() { + $store = json_decode(helper::urlGetContents(self::BASEURL_STORE . self::MODULE_STORE . 'list'), true); + if ($store) { + // Modules installés + $infoModules = helper::getModules(); + // Clés moduleIds dans les pages + $inPages = helper::arrayCollumn($this->getData(['page']),'moduleId', 'SORT_DESC'); + foreach( $inPages as $key=>$value){ + $inPagesTitle[ $this->getData(['page', $key, 'title' ]) ] = $value; + } + // Parcourir les données des modules + foreach ($store as $key=>$value) { + // Module non installé + $ico = template::ico('download'); + $class = ''; + // Le module est installé + if (array_key_exists($key,$infoModules) === true) { + $class = 'buttonGreen'; + $ico = template::ico('update'); + } + // Le module est installé et utilisé + if (in_array($key,$inPages) === true) { + $class = 'buttonRed'; + $ico = template::ico('update'); + } + self::$storeList [] = [ + $store[$key]['category'], + ''.$store[$key]['title'].'', + $store[$key]['version'], + mb_detect_encoding(strftime('%d %B %Y', $store[$key]['versionDate']), 'UTF-8', true) + ? strftime('%d %B %Y', $store[$key]['versionDate']) + : utf8_encode(strftime('%d %B %Y', $store[$key]['versionDate'])), + implode(', ', array_keys($inPagesTitle,$key)), + template::button('moduleExport' . $key, [ + 'class' => $class, + 'href' => helper::baseUrl(). $this->getUrl(0) . '/uploadItem/' . $key.'/' . $_SESSION['csrf'],// appel de fonction vaut exécution, utiliser un paramètre + 'value' => $ico + ]) + ]; + } + } + + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Catalogue de modules en ligne', + 'view' => 'store' + ]); + } + + /** + * Détail d'un objet du catalogue + */ + public function item() { + $store = json_decode(helper::urlGetContents(self::BASEURL_STORE . self::MODULE_STORE . 'list'), true); + self::$storeItem = $store [$this->getUrl(2)] ; + self::$storeItem ['fileDate'] = mb_detect_encoding(strftime('%d %B %Y',self::$storeItem ['fileDate']), 'UTF-8', true) + ? strftime('%d %B %Y', self::$storeItem ['fileDate']) + : utf8_encode(strftime('%d %B %Y', self::$storeItem ['fileDate'])); + // Valeurs en sortie + $this->addOutput([ + 'title' =>'Module ' . self::$storeItem['title'], + 'view' => 'item' + ]); + } + + /** + * Gestion des modules + */ + public function index() { + + // Lister les modules + // $infoModules[nom_module]['realName'], ['version'], ['update'], ['delete'], ['dataDirectory'] + $infoModules = helper::getModules(); + + // Clés moduleIds dans les pages + $inPages = helper::arrayCollumn($this->getData(['page']),'moduleId', 'SORT_DESC'); + foreach( $inPages as $key=>$value){ + $inPagesTitle[ $this->getData(['page', $key, 'title' ]) ] = $value; + } + + // Parcourir les données des modules + foreach ($infoModules as $key=>$value) { + // Construire le tableau de sortie + self::$modInstal[] = [ + $key, + $infoModules[$key]['realName'], + $infoModules[$key]['version'], + implode(', ', array_keys($inPagesTitle,$key)), + //|| ('delete',$infoModules[$key]) && $infoModules[$key]['delete'] === true && implode(', ',array_keys($inPages,$key)) === '' + $infoModules[$key]['delete'] === true && implode(', ',array_keys($inPages,$key)) === '' + ? template::button('moduleDelete' . $key, [ + 'class' => 'moduleDelete buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $key . '/' . $_SESSION['csrf'], + 'value' => template::ico('cancel') + ]) + : '', + implode(', ',array_keys($inPages,$key)) !== '' + ? template::button('moduleExport' . $key, [ + 'href' => helper::baseUrl(). $this->getUrl(0) . '/export/' . $key . '/' . $_SESSION['csrf'],// appel de fonction vaut exécution, utiliser un paramètre + 'value' => template::ico('download') + ]) + : '', + implode(', ',array_keys($inPages,$key)) === '' + ? template::button('moduleExport' . $key, [ + 'href' => helper::baseUrl(). $this->getUrl(0) . '/import/' . $key . '/' . $_SESSION['csrf'],// appel de fonction vaut exécution, utiliser un paramètre + 'value' => template::ico('upload') + ]) + : '' + ]; + } + + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Gestion des modules', + 'view' => 'index' + ]); + } + + /* + * Export des données d'un module externes ou interne à module.json + */ + public function export(){ + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'state' => false, + 'notification' => 'Action non autorisée' + ]); + } + else { + // Lire les données du module + $infoModules = helper::getModules(); + // Créer un dossier par défaut + $tmpFolder = self::TEMP_DIR . uniqid(); + //$tmpFolder = self::TEMP_DIR . 'test'; + if (!is_dir($tmpFolder)) { + mkdir($tmpFolder, 0755); + } + // Clés moduleIds dans les pages + $inPages = helper::arrayCollumn($this->getData(['page']),'moduleId', 'SORT_DESC'); + // Parcourir les pages utilisant le module + foreach (array_keys($inPages,$this->getUrl(2)) as $pageId) { + // Export des pages hébergeant le module + $pageParam[$pageId] = $this->getData(['page',$pageId]); + // Export du contenu de la page + //$pageContent[$pageId] = file_get_contents(self::DATA_DIR . self::$i18n . '/content/' . $this->getData(['page', $pageId, 'content'])); + $pageContent[$pageId] = $this->getPage($pageId, self::$i18n); + // Export de fr/module.json + $moduleId = 'fr/module.json'; + $moduleDir = str_replace('site/data/','',$infoModules[$this->getUrl(2)]['dataDirectory']); + // Création de l'arborescence des langues + // Pas de nom dossier de langue - dossier par défaut + $t = explode ('/',$moduleId); + if ( is_array($t)) { + $lang = 'fr'; + } else { + $lang = $t[0]; + } + // Créer le dossier temporaire si inexistant sinon le nettoie et le créer + if (!is_dir($tmpFolder . '/' . $lang)) { + mkdir ($tmpFolder . '/' . $lang, 0755, true); + } else { + $this->removeDir($tmpFolder . '/' . $lang); + mkdir ($tmpFolder . '/' . $lang, 0755, true); + } + // Créer le dossier temporaire des données du module + if ($infoModules[$this->getUrl(2)]['dataDirectory']) { + if (!is_dir($tmpFolder . '/' . $moduleDir)) { + mkdir ($tmpFolder . '/' . $moduleDir, 0755, true) ; + } + } + // Sauvegarde si données non vides + $tmpData [$pageId] = $this->getData(['module',$pageId ]); + if ($tmpData [$pageId] !== null) { + file_put_contents($tmpFolder . '/' . $moduleId, json_encode($tmpData)); + } + // Export des données localisées dans le dossier de données du module + if ($infoModules[$this->getUrl(2)]['dataDirectory'] && + is_dir($infoModules[$this->getUrl(2)]['dataDirectory'])) { + $this->copyDir ($infoModules[$this->getUrl(2)]['dataDirectory'], $tmpFolder . '/' . $moduleDir); + } + } + // Enregistrement des pages dans le dossier de langue identique à module + if (!file_exists($tmpFolder . '/' . $lang . '/page.json')) { + file_put_contents($tmpFolder . '/' . $lang . '/page.json', json_encode($pageParam)); + mkdir ($tmpFolder . '/' . $lang . '/content', 0755); + file_put_contents($tmpFolder . '/' . $lang . '/content/' . $this->getData(['page', $pageId, 'content']), $pageContent); + } + // création du zip + $fileName = $this->getUrl(2) . '.zip'; + $this->makeZip ($fileName, $tmpFolder, []); + if (file_exists($fileName)) { + ob_start(); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . $fileName . '"'); + header('Content-Length: ' . filesize($fileName)); + ob_clean(); + ob_end_flush(); + readfile( $fileName); + unlink($fileName); + $this->removeDir($tmpFolder); + exit(); + } else { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'notification' => 'Quelque chose s\'est mal passé', + 'state' => false + ]); + } + } + } + + /* + * Importer des données d'un module externes ou interne à module.json + */ + public function import(){ + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'state' => false, + 'notification' => 'Action non autorisée' + ]); + } + else { + // Soumission du formulaire + if($this->isPost()) { + // Récupérer le fichier et le décompacter + $zipFilename = $this->getInput('addonImportFile', helper::FILTER_STRING_SHORT, true); + $tempFolder = uniqid(); + mkdir (self::TEMP_DIR . $tempFolder, 0755); + $zip = new ZipArchive(); + if ($zip->open(self::FILE_DIR . 'source/' . $zipFilename) === TRUE) { + $zip->extractTo(self::TEMP_DIR . $tempFolder ); + } + // Import des données localisées page.json et module.json + // Pour chaque dossier localisé + $dataTarget = array(); + $dataSource = array(); + // Liste des pages de même nom dans l'archive et le site + $list = ''; + foreach (self::$i18nList as $key=>$value) { + // Les Pages et les modules + foreach (['page','module'] as $fileTarget){ + if (file_exists(self::TEMP_DIR . $tempFolder . '/' .$key . '/' . $fileTarget . '.json')) { + // Le dossier de langue existe + // faire la fusion + $dataSource = json_decode(file_get_contents(self::TEMP_DIR . $tempFolder . '/' .$key . '/' . $fileTarget . '.json'), true); + // Des pages de même nom que celles de l'archive existent + if( $fileTarget === 'page' ){ + foreach( $dataSource as $keydataSource=>$valuedataSource ){ + foreach( $this->getData(['page']) as $keypage=>$valuepage ){ + if( $keydataSource === $keypage){ + $list === '' ? $list .= ' '.$this->getData(['page', $keypage, 'title']) : $list .= ', '.$this->getData(['page', $keypage, 'title']); + } + } + } + } + $dataTarget = json_decode(file_get_contents(self::DATA_DIR . $key . '/' . $fileTarget . '.json'), true); + $data [$fileTarget] = array_merge($dataTarget[$fileTarget], $dataSource); + if( $list === ''){ + file_put_contents(self::DATA_DIR . '/' .$key . '/' . $fileTarget . '.json', json_encode( $data ,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT|LOCK_EX) ); + } + // copie du contenu de la page + $this->copyDir (self::TEMP_DIR . $tempFolder . '/' .$key . '/content', self::DATA_DIR . '/' .$key . '/content'); + // Supprimer les fichiers importés + unlink (self::TEMP_DIR . $tempFolder . '/' .$key . '/' . $fileTarget . '.json'); + } + } + } + + // Import des fichiers placés ailleurs que dans les dossiers localisés. + $this->copyDir (self::TEMP_DIR . $tempFolder,self::DATA_DIR ); + + // Supprimer le dossier temporaire + $this->removeDir(self::TEMP_DIR . $tempFolder); + $zip->close(); + if( $list !== '' ){ + $success = false; + strpos( $list, ',') === false ? $notification = 'Import impossible la page suivante doit être renommée :'.$list : $notification = 'Import impossible les pages suivantes doivent être renommées :'.$list; + } + else{ + $success = true; + $notification = 'Import réussi'; + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'state' => $success, + 'notification' => $notification + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Importer des données de module', + 'view' => 'import' + ]); + } + } + +} diff --git a/core/module/addon/view/import/import.css b/core/module/addon/view/import/import.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/addon/view/import/import.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/addon/view/import/import.php b/core/module/addon/view/import/import.php new file mode 100644 index 0000000..7cc41da --- /dev/null +++ b/core/module/addon/view/import/import.php @@ -0,0 +1,31 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'addon', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'Appliquer' + ]); ?> +
+
+
+
+
+

Installer des données de module

+
+
+ 'Archive ZIP :', + 'type' => 2 + ]); ?> +
+
+
+
+
\ No newline at end of file diff --git a/core/module/addon/view/index/index.css b/core/module/addon/view/index/index.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/addon/view/index/index.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/addon/view/index/index.js.php b/core/module/addon/view/index/index.js.php new file mode 100644 index 0000000..c88ba6c --- /dev/null +++ b/core/module/addon/view/index/index.js.php @@ -0,0 +1,25 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Confirmation de suppression + */ +$(".moduleDelete").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir supprimer ce module ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); \ No newline at end of file diff --git a/core/module/addon/view/index/index.php b/core/module/addon/view/index/index.php new file mode 100644 index 0000000..5dca4cc --- /dev/null +++ b/core/module/addon/view/index/index.php @@ -0,0 +1,31 @@ +
+
+ 'buttonGrey', + 'href' => helper::baseUrl(), + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'https://doc.deltacms.fr/gestion-des-modules', + 'target' => '_blank', + 'ico' => 'help', + 'value' => 'Aide', + 'class' => 'buttonHelp' + ]); ?> +
+
+ helper::baseUrl() . 'addon/upload', + 'value' => 'Installer un module' + ]); ?> +
+
+ + + + + + diff --git a/core/module/addon/view/item/item.css b/core/module/addon/view/item/item.css new file mode 100644 index 0000000..1fc2bc9 --- /dev/null +++ b/core/module/addon/view/item/item.css @@ -0,0 +1,16 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + diff --git a/core/module/addon/view/item/item.php b/core/module/addon/view/item/item.php new file mode 100644 index 0000000..196e652 --- /dev/null +++ b/core/module/addon/view/item/item.php @@ -0,0 +1,41 @@ +
+
+
+
+ +
+
+
+
+
+ '; + ?> +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ Auteur : + + +
+
+
+
+ Licence : + + +
+
+
\ No newline at end of file diff --git a/core/module/addon/view/store/store.css b/core/module/addon/view/store/store.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/addon/view/store/store.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/addon/view/store/store.php b/core/module/addon/view/store/store.php new file mode 100644 index 0000000..182d106 --- /dev/null +++ b/core/module/addon/view/store/store.php @@ -0,0 +1,15 @@ +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'addon/upload', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ + + + + \ No newline at end of file diff --git a/core/module/addon/view/upload/upload.css b/core/module/addon/view/upload/upload.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/addon/view/upload/upload.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/addon/view/upload/upload.php b/core/module/addon/view/upload/upload.php new file mode 100644 index 0000000..1f417b5 --- /dev/null +++ b/core/module/addon/view/upload/upload.php @@ -0,0 +1,58 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'addon', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'https://doc.deltacms.fr/installer-un-module', + 'target' => '_blank', + 'class' => 'buttonHelp', + 'ico' => 'help', + 'value' => 'Aide' + ]); ?> +
+ +
+ 'Valider', + 'ico' => 'check' + ]); ?> +
+
+
+
+
+

Installer ou mettre à jour un module téléchargé

+
+
+ 'Archive ZIP :', + 'type' => 2 + ]); ?> +
+
+
+
+ false, + 'help' => 'Permet de forcer une mise à jour même si la version du module est inférieure ou égale à celle du module installé.', + ]); ?> +
+
+
+
+
+ diff --git a/core/module/config/config.php b/core/module/config/config.php new file mode 100644 index 0000000..2ad8433 --- /dev/null +++ b/core/module/config/config.php @@ -0,0 +1,843 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class config extends common { + + public static $actions = [ + 'backup' => self::GROUP_ADMIN, + 'copyBackups'=> self::GROUP_ADMIN, + 'configOpenGraph' => self::GROUP_ADMIN, + 'generateFiles' => self::GROUP_ADMIN, + 'index' => self::GROUP_ADMIN, + 'restore' => self::GROUP_ADMIN, + 'updateBaseUrl' => self::GROUP_ADMIN, + 'script' => self::GROUP_ADMIN, + 'logReset' => self::GROUP_ADMIN, + 'logDownload'=> self::GROUP_ADMIN, + 'blacklistReset' => self::GROUP_ADMIN, + 'blacklistDownload' => self::GROUP_ADMIN, + + ]; + + public static $timezones = [ + 'Pacific/Midway' => '(GMT-11:00) Midway Island', + 'US/Samoa' => '(GMT-11:00) Samoa', + 'US/Hawaii' => '(GMT-10:00) Hawaii', + 'US/Alaska' => '(GMT-09:00) Alaska', + 'US/Pacific' => '(GMT-08:00) Pacific Time (US & Canada)', + 'America/Tijuana' => '(GMT-08:00) Tijuana', + 'US/Arizona' => '(GMT-07:00) Arizona', + 'US/Mountain' => '(GMT-07:00) Mountain Time (US & Canada)', + 'America/Chihuahua' => '(GMT-07:00) Chihuahua', + 'America/Mazatlan' => '(GMT-07:00) Mazatlan', + 'America/Mexico_City' => '(GMT-06:00) Mexico City', + 'America/Monterrey' => '(GMT-06:00) Monterrey', + 'Canada/Saskatchewan' => '(GMT-06:00) Saskatchewan', + 'US/Central' => '(GMT-06:00) Central Time (US & Canada)', + 'US/Eastern' => '(GMT-05:00) Eastern Time (US & Canada)', + 'US/East-Indiana' => '(GMT-05:00) Indiana (East)', + 'America/Bogota' => '(GMT-05:00) Bogota', + 'America/Lima' => '(GMT-05:00) Lima', + 'America/Caracas' => '(GMT-04:30) Caracas', + 'Canada/Atlantic' => '(GMT-04:00) Atlantic Time (Canada)', + 'America/La_Paz' => '(GMT-04:00) La Paz', + 'America/Santiago' => '(GMT-04:00) Santiago', + 'Canada/Newfoundland' => '(GMT-03:30) Newfoundland', + 'America/Buenos_Aires' => '(GMT-03:00) Buenos Aires', + 'Greenland' => '(GMT-03:00) Greenland', + 'Atlantic/Stanley' => '(GMT-02:00) Stanley', + 'Atlantic/Azores' => '(GMT-01:00) Azores', + 'Atlantic/Cape_Verde' => '(GMT-01:00) Cape Verde Is.', + 'Africa/Casablanca' => '(GMT) Casablanca', + 'Europe/Dublin' => '(GMT) Dublin', + 'Europe/Lisbon' => '(GMT) Lisbon', + 'Europe/London' => '(GMT) London', + 'Africa/Monrovia' => '(GMT) Monrovia', + 'Europe/Amsterdam' => '(GMT+01:00) Amsterdam', + 'Europe/Belgrade' => '(GMT+01:00) Belgrade', + 'Europe/Berlin' => '(GMT+01:00) Berlin', + 'Europe/Bratislava' => '(GMT+01:00) Bratislava', + 'Europe/Brussels' => '(GMT+01:00) Brussels', + 'Europe/Budapest' => '(GMT+01:00) Budapest', + 'Europe/Copenhagen' => '(GMT+01:00) Copenhagen', + 'Europe/Ljubljana' => '(GMT+01:00) Ljubljana', + 'Europe/Madrid' => '(GMT+01:00) Madrid', + 'Europe/Paris' => '(GMT+01:00) Paris', + 'Europe/Prague' => '(GMT+01:00) Prague', + 'Europe/Rome' => '(GMT+01:00) Rome', + 'Europe/Sarajevo' => '(GMT+01:00) Sarajevo', + 'Europe/Skopje' => '(GMT+01:00) Skopje', + 'Europe/Stockholm' => '(GMT+01:00) Stockholm', + 'Europe/Vienna' => '(GMT+01:00) Vienna', + 'Europe/Warsaw' => '(GMT+01:00) Warsaw', + 'Europe/Zagreb' => '(GMT+01:00) Zagreb', + 'Europe/Athens' => '(GMT+02:00) Athens', + 'Europe/Bucharest' => '(GMT+02:00) Bucharest', + 'Africa/Cairo' => '(GMT+02:00) Cairo', + 'Africa/Harare' => '(GMT+02:00) Harare', + 'Europe/Helsinki' => '(GMT+02:00) Helsinki', + 'Europe/Istanbul' => '(GMT+02:00) Istanbul', + 'Asia/Jerusalem' => '(GMT+02:00) Jerusalem', + 'Europe/Kiev' => '(GMT+02:00) Kyiv', + 'Europe/Minsk' => '(GMT+02:00) Minsk', + 'Europe/Riga' => '(GMT+02:00) Riga', + 'Europe/Sofia' => '(GMT+02:00) Sofia', + 'Europe/Tallinn' => '(GMT+02:00) Tallinn', + 'Europe/Vilnius' => '(GMT+02:00) Vilnius', + 'Asia/Baghdad' => '(GMT+03:00) Baghdad', + 'Asia/Kuwait' => '(GMT+03:00) Kuwait', + 'Europe/Moscow' => '(GMT+03:00) Moscow', + 'Africa/Nairobi' => '(GMT+03:00) Nairobi', + 'Asia/Riyadh' => '(GMT+03:00) Riyadh', + 'Europe/Volgograd' => '(GMT+03:00) Volgograd', + 'Asia/Tehran' => '(GMT+03:30) Tehran', + 'Asia/Baku' => '(GMT+04:00) Baku', + 'Asia/Muscat' => '(GMT+04:00) Muscat', + 'Asia/Tbilisi' => '(GMT+04:00) Tbilisi', + 'Asia/Yerevan' => '(GMT+04:00) Yerevan', + 'Asia/Kabul' => '(GMT+04:30) Kabul', + 'Asia/Yekaterinburg' => '(GMT+05:00) Ekaterinburg', + 'Asia/Karachi' => '(GMT+05:00) Karachi', + 'Asia/Tashkent' => '(GMT+05:00) Tashkent', + 'Asia/Kolkata' => '(GMT+05:30) Kolkata', + 'Asia/Kathmandu' => '(GMT+05:45) Kathmandu', + 'Asia/Almaty' => '(GMT+06:00) Almaty', + 'Asia/Dhaka' => '(GMT+06:00) Dhaka', + 'Asia/Novosibirsk' => '(GMT+06:00) Novosibirsk', + 'Asia/Bangkok' => '(GMT+07:00) Bangkok', + 'Asia/Jakarta' => '(GMT+07:00) Jakarta', + 'Asia/Krasnoyarsk' => '(GMT+07:00) Krasnoyarsk', + 'Asia/Chongqing' => '(GMT+08:00) Chongqing', + 'Asia/Hong_Kong' => '(GMT+08:00) Hong Kong', + 'Asia/Irkutsk' => '(GMT+08:00) Irkutsk', + 'Asia/Kuala_Lumpur' => '(GMT+08:00) Kuala Lumpur', + 'Australia/Perth' => '(GMT+08:00) Perth', + 'Asia/Singapore' => '(GMT+08:00) Singapore', + 'Asia/Taipei' => '(GMT+08:00) Taipei', + 'Asia/Ulaanbaatar' => '(GMT+08:00) Ulaan Bataar', + 'Asia/Urumqi' => '(GMT+08:00) Urumqi', + 'Asia/Seoul' => '(GMT+09:00) Seoul', + 'Asia/Tokyo' => '(GMT+09:00) Tokyo', + 'Asia/Yakutsk' => '(GMT+09:00) Yakutsk', + 'Australia/Adelaide' => '(GMT+09:30) Adelaide', + 'Australia/Darwin' => '(GMT+09:30) Darwin', + 'Australia/Brisbane' => '(GMT+10:00) Brisbane', + 'Australia/Canberra' => '(GMT+10:00) Canberra', + 'Pacific/Guam' => '(GMT+10:00) Guam', + 'Australia/Hobart' => '(GMT+10:00) Hobart', + 'Australia/Melbourne' => '(GMT+10:00) Melbourne', + 'Pacific/Port_Moresby' => '(GMT+10:00) Port Moresby', + 'Australia/Sydney' => '(GMT+10:00) Sydney', + 'Asia/Vladivostok' => '(GMT+10:00) Vladivostok', + 'Asia/Magadan' => '(GMT+11:00) Magadan', + 'Pacific/Auckland' => '(GMT+12:00) Auckland', + 'Pacific/Fiji' => '(GMT+12:00) Fiji', + 'Asia/Kamchatka' => '(GMT+12:00) Kamchatka' + ]; + // Type de proxy + public static $proxyType = [ + 'tcp://' => 'TCP', + 'http://' => 'HTTP' + ]; + // Authentification SMTP + public static $SMTPauth = [ + true => 'Oui', + false => 'Non' + ]; + // Encryptation SMTP + public static $SMTPEnc = [ + '' => 'Aucune', + 'tls' => 'START TLS', + 'ssl' => 'SSL/TLS' + ]; + // Sécurité de la connexion - tentative max avant blocage + public static $connectAttempt = [ + 999 => 'Sécurité désactivée', + 3 => '3 tentatives', + 5 => '5 tentatives', + 10 => '10 tentatives' + ]; + // Sécurité de la connexion - durée du blocage + public static $connectTimeout = [ + 0 => 'Sécurité désactivée', + 300 => '5 minutes', + 600 => '10 minutes', + 900 => '15 minutes' + ]; + // Anonymisation des IP du journal + public static $anonIP = [ + 4 => 'Non tronquées', + 3 => 'Niveau 1 (192.168.12.x)', + 2 => 'Niveau 2 (192.168.x.x)', + 1 => 'Niveau 3 (192.x.x.x)', + ]; + public static $captchaTypes = [ + 'num' => 'Chiffres', + 'alpha' => 'Lettres' + ]; + + // Langue traduite courante + public static $i18nSite = 'fr'; + + // Variable pour construire la liste des pages du site + public static $pagesList = []; + public static $orphansList = []; + + /** + * Génére les fichiers pour les crawlers + * Sitemap compressé et non compressé + * Robots.txt + */ + public function generateFiles() { + + // Mettre à jour le site map + $successSitemap=$this->createSitemap(); + + // Valeurs en sortie + $this->addOutput([ + /*'title' => 'Configuration', + 'view' => 'index',*/ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => $successSitemap ? 'Mises à jour des fichiers sitemap et robots.txt' : 'Echec d\'écriture, le site map n\'a pas été mis à jour', + 'state' => $successSitemap + ]); + } + + + /** + * Sauvegarde des données + */ + public function backup() { + // Soumission du formulaire + if($this->isPost()) { + // Creation du ZIP + $filter = $this->getInput('configBackupOption',helper::FILTER_BOOLEAN) === true ? ['backup','tmp'] : ['backup','tmp','file']; + $fileName = helper::autoBackup(self::TEMP_DIR,$filter); + // Créer le répertoire manquant + if (!is_dir(self::FILE_DIR.'source/backup')) { + mkdir(self::FILE_DIR.'source/backup', 0755); + } + // Copie dans les fichiers + $success = copy (self::TEMP_DIR . $fileName , self::FILE_DIR.'source/backup/' . $fileName); + // Détruire le temporaire + unlink(self::TEMP_DIR . $fileName); + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_JSON, + 'content' => json_encode($success) + ]); + } else { + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Sauvegarder', + 'view' => 'backup' + ]); + } + } + + /** + * Active ou désactive le mode Open Graph + */ + public function configOpenGraph() { + $texte=''; + if( $_SESSION['screenshot'] === 'on'){ + $_SESSION['screenshot'] = 'off'; + $texte = 'Mode de capture Opengraph désactivé'; + if( file_exists('./screenshot.php')) unlink ('./screenshot.php'); + } + else{ + $_SESSION['screenshot'] = 'on'; + $texte = 'Mode de capture Opengraph activé'; + if( !file_exists('./screenshot.php')) copy('./core/vendor/screenshot/screenshot.php','./screenshot.php'); + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => $texte, + 'state' => true + ]); + } + + /** + * Procédure d'importation + */ + public function restore() { + // Soumission du formulaire + if($this->isPost() ) { + + $success = false; + + if ($this->getInput('configRestoreImportFile', null, true) ) { + + $fileZip = $this->getInput('configRestoreImportFile'); + $file_parts = pathinfo($fileZip); + $folder = date('Y-m-d-h-i-s', time()); + $zip = new ZipArchive(); + if ($file_parts['extension'] !== 'zip') { + // Valeurs en sortie erreur + $this->addOutput([ + 'title' => 'Restaurer', + 'view' => 'restore', + 'notification' => 'Le fichier n\'est pas une archive valide', + 'state' => false + ]); + } + $successOpen = $zip->open(self::FILE_DIR . 'source/' . $fileZip); + if ($successOpen === FALSE) { + // Valeurs en sortie erreur + $this->addOutput([ + 'title' => 'Restaurer', + 'view' => 'restore', + 'notification' => 'Impossible de lire l\'archive', + 'state' => false + ]); + } + // Lire le contenu de l'archive dans le tableau files + for( $i = 0; $i < $zip->numFiles; $i++ ){ + $stat = $zip->statIndex( $i ); + $files [] = ( basename( $stat['name'] )); + } + + // Lire la dataversion + $tmpDir = uniqid(4); + $success = $zip->extractTo( self::TEMP_DIR . $tmpDir ); + $data = file_get_contents( self::TEMP_DIR . $tmpDir . '/data/core.json'); + $obj = json_decode($data); + $dataVersion = strval ($obj->core->dataVersion); + switch (strlen($dataVersion)) { + case 4: + if (substr($dataVersion,0,1) === '9' ) { + $version = 9; + } else { + $version = 0; + } + break; + case 5: + $version = substr($dataVersion,0,2); + break; + default: + $version = 0; + break; + } + $this->removeDir(self::TEMP_DIR . $tmpDir ); + + if ($version >= 10 ) { + // Option active, les users sont stockées + if ($this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN) === true ) { + $users = $this->getData(['user']); + } + } elseif ($version === 0) { // Version invalide + // Valeurs en sortie erreur + $this->addOutput([ + 'title' => 'Restaurer', + 'view' => 'restore', + 'notification' => 'Cette archive n\'est pas une sauvegarde valide', + 'state' => false + ]); + } + // Préserver les comptes des utilisateurs d'une version 9 si option cochée + // Positionnement d'une variable de session lue au constructeurs + if ($version === 9) { + $_SESSION['KEEP_USERS'] = $this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN); + } + // Extraire le zip ou 'site/' + $this->removeDir(self::DATA_DIR); + $success = $zip->extractTo( 'site/' ); + // Fermer l'archive + $zip->close(); + + + // Restaurer les users originaux d'une v10 si option cochée + if (!empty($users) && + $version >= 10 && + $this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN) === true) { + $this->setData(['user',$users]); + } + } + // Message de notification + $notification = $success === true ? 'Restaurer effectuée avec succès' : 'Erreur inconnue'; + $redirect = $this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN) === true ? helper::baseUrl() . 'config/restore' : helper::baseUrl() . 'user/login/'; + // Valeurs en sortie erreur + $this->addOutput([ + /*'title' => 'Restaurer', + 'view' => 'restore',*/ + 'redirect' => $redirect, + 'notification' => $notification, + 'state' => $success + ]); + } + + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Restaurer', + 'view' => 'restore' + ]); + } + + + /** + * Configuration + */ + public function index() { + // Soumission du formulaire + if($this->isPost()) { + + // Basculement en mise à jour auto, remise à 0 du compteur + if ($this->getData(['config','autoUpdate']) === false && + $this->getInput('configAutoUpdate', helper::FILTER_BOOLEAN) === true) { + $this->setData(['core','lastAutoUpdate',0]); + } + + // Eviter déconnexion automatique après son activation + if ( $this->getData(['config','connect', 'autoDisconnect']) === false + AND $this->getInput('configAutoDisconnect',helper::FILTER_BOOLEAN) === true ) { + $this->setData(['user',$this->getuser('id'),'accessCsrf',$_SESSION['csrf']]); + } + // Répercuter la suppression de la page dans la configuration du footer + if ( $this->getData(['theme','footer','displaySearch']) === true + AND $this->getInput('configSearchPageId') === 'none' + ){ + $this->setData(['theme', 'footer', 'displaySearch', false]); + } + if ( $this->getData(['theme','footer','displayLegal']) === true + AND $this->getInput('configLegalPageId') === 'none' + ){ + $this->setData(['theme', 'footer', 'displayLegal', false]); + } + + // Sauvegarder les locales + $this->setData([ + 'locale', + [ + 'homePageId' => $this->getInput('localeHomePageId', helper::FILTER_ID, true), + 'page404' => $this->getInput('localePage404'), + 'page403' => $this->getInput('localePage403'), + 'page302' => $this->getInput('localePage302'), + 'legalPageId' => $this->getInput('localeLegalPageId'), + 'searchPageId' => $this->getInput('localeSearchPageId'), + 'searchPageLabel' => empty($this->getInput('localeSearchPageLabel', helper::FILTER_STRING_SHORT)) ? 'Rechercher' : $this->getInput('localeSearchPageLabel', helper::FILTER_STRING_SHORT), + 'legalPageLabel' => empty($this->getInput('localeLegalPageLabel', helper::FILTER_STRING_SHORT)) ? 'Mentions légales' : $this->getInput('localeLegalPageLabel', helper::FILTER_STRING_SHORT), + 'sitemapPageLabel' => empty($this->getInput('localeSitemapPageLabel', helper::FILTER_STRING_SHORT)) ? 'Plan du site' : $this->getInput('localeSitemapPageLabel', helper::FILTER_STRING_SHORT), + 'metaDescription' => $this->getInput('localeMetaDescription', helper::FILTER_STRING_LONG, true), + 'title' => $this->getInput('localeTitle', helper::FILTER_STRING_SHORT, true), + 'cookies' => [ + // Les champs sont obligatoires si l'option consentement des cookies est active + 'cookiesDeltaText' => $this->getInput('localeCookiesDeltaText', helper::FILTER_STRING_LONG, $this->getData(['config', 'cookieConsent'])), + 'cookiesExtText' => $this->getInput('localeCookiesExtText', helper::FILTER_STRING_LONG), + 'cookiesTitleText' => $this->getInput('localeCookiesTitleText', helper::FILTER_STRING_SHORT, $this->getData(['config', 'cookieConsent'])), + 'cookiesLinkMlText' => $this->getInput('localeCookiesLinkMlText', helper::FILTER_STRING_SHORT, $this->getData(['config', 'cookieConsent'])), + 'cookiesCheckboxExtText' => $this->getInput('localeCookiesCheckboxExtText', helper::FILTER_STRING_SHORT), + 'cookiesFooterText' => $this->getInput('localeCookiesFooterText', helper::FILTER_STRING_SHORT, $this->getData(['config', 'cookieConsent'])), + 'cookiesButtonText' =>$this->getInput('localeCookiesButtonText', helper::FILTER_STRING_SHORT, $this->getData(['config', 'cookieConsent'])) + ] + ] + ]); + + // Sauvegarder la configuration + $this->setData([ + 'config', + [ + 'favicon' => $this->getInput('configFavicon'), + 'faviconDark' => $this->getInput('configFaviconDark'), + 'timezone' => $this->getInput('configTimezone', helper::FILTER_STRING_SHORT, true), + 'autoUpdate' => $this->getInput('configAutoUpdate', helper::FILTER_BOOLEAN), + 'autoUpdateHtaccess' => $this->getInput('configAutoUpdateHtaccess', helper::FILTER_BOOLEAN), + 'autoBackup' => $this->getInput('configAutoBackup', helper::FILTER_BOOLEAN), + 'maintenance' => $this->getInput('configMaintenance', helper::FILTER_BOOLEAN), + 'cookieConsent' => $this->getInput('configCookieConsent', helper::FILTER_BOOLEAN), + 'proxyType' => $this->getInput('configProxyType'), + 'proxyUrl' => $this->getInput('configProxyUrl'), + 'proxyPort' => $this->getInput('configProxyPort',helper::FILTER_INT), + 'social' => [ + 'facebookId' => $this->getInput('socialFacebookId'), + 'linkedinId' => $this->getInput('socialLinkedinId'), + 'instagramId' => $this->getInput('socialInstagramId'), + 'pinterestId' => $this->getInput('socialPinterestId'), + 'twitterId' => $this->getInput('socialTwitterId'), + 'youtubeId' => $this->getInput('socialYoutubeId'), + 'youtubeUserId' => $this->getInput('socialYoutubeUserId'), + 'githubId' => $this->getInput('socialGithubId') + ], + 'smtp' => [ + 'enable' => $this->getInput('smtpEnable',helper::FILTER_BOOLEAN), + 'host' => $this->getInput('smtpHost',helper::FILTER_STRING_SHORT,$this->getInput('smtpEnable',helper::FILTER_BOOLEAN)), + 'port' => $this->getInput('smtpPort',helper::FILTER_INT,$this->getInput('smtpEnable',helper::FILTER_BOOLEAN)), + 'auth' => $this->getInput('smtpAuth',helper::FILTER_BOOLEAN), + 'secure' => $this->getInput('smtpSecure',helper::FILTER_BOOLEAN), + 'username' => $this->getInput('smtpUsername',helper::FILTER_STRING_SHORT,$this->getInput('smtpAuth',helper::FILTER_BOOLEAN)), + 'password' =>helper::encrypt($this->getData(['config','smtp','username']),$this->getInput('smtpPassword',null,$this->getInput('smtpAuth',helper::FILTER_BOOLEAN))), + 'sender' => $this->getInput('smtpSender',helper::FILTER_MAIL) + ], + 'seo' => [ + 'robots' => $this->getInput('seoRobots',helper::FILTER_BOOLEAN), + 'analyticsId' => $this->getInput('seoAnalyticsId') + ], + 'connect' => [ + 'attempt' => $this->getInput('connectAttempt',helper::FILTER_INT), + 'timeout' => $this->getInput('connectTimeout',helper::FILTER_INT), + 'log' => $this->getInput('connectLog',helper::FILTER_BOOLEAN), + 'anonymousIp' => $this->getInput('connectAnonymousIp',helper::FILTER_INT), + 'captcha' => $this->getInput('connectCaptcha',helper::FILTER_BOOLEAN), + 'captchaStrong' => $this->getInput('connectCaptchaStrong',helper::FILTER_BOOLEAN), + 'autoDisconnect' => $this->getInput('connectAutoDisconnect',helper::FILTER_BOOLEAN), + 'captchaType' => $this->getInput('connectCaptchaType') + ], + 'i18n' => [ + 'enable' => $this->getInput('localei18n',helper::FILTER_BOOLEAN), + 'scriptGoogle' => $this->getData(['config', 'i18n', 'scriptGoogle']), + 'showCredits' => $this->getData(['config', 'i18n', 'showCredits']), + 'autoDetect' => $this->getData(['config', 'i18n', 'autoDetect']), + //'admin' => $this->getData(['config', 'i18n', 'admin']), + 'fr' => $this->getData(['config', 'i18n', 'fr']), + 'de' => $this->getData(['config', 'i18n', 'de']), + 'en' => $this->getData(['config', 'i18n', 'en']), + 'es' => $this->getData(['config', 'i18n', 'es']), + 'it' => $this->getData(['config', 'i18n', 'it']), + 'nl' => $this->getData(['config', 'i18n', 'nl']), + 'pt' => $this->getData(['config', 'i18n', 'pt']) + ] + ] + ]); + + // Efface les fichiers de backup lorsque l'option est désactivée + if ($this->getInput('configFileBackup', helper::FILTER_BOOLEAN) === false) { + $path = realpath('site/data'); + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $filename) + { + if (strpos($filename,'backup.json')) { + unlink($filename); + } + } + if (file_exists('site/data/.backup')) unlink('site/data/.backup'); + } else { + touch('site/data/.backup'); + } + // Notice + if(self::$inputNotices === []) { + // Active la réécriture d'URL + $rewrite = $this->getInput('configRewrite', helper::FILTER_BOOLEAN); + if( + $rewrite + AND helper::checkRewrite() === false + ) { + // Ajout des lignes dans le .htaccess + file_put_contents( + '.htaccess', + PHP_EOL . + '' . PHP_EOL . + "\tRewriteEngine on" . PHP_EOL . + "\tRewriteBase " . helper::baseUrl(false, false) . PHP_EOL . + "\tRewriteCond %{REQUEST_FILENAME} !-f" . PHP_EOL . + "\tRewriteCond %{REQUEST_FILENAME} !-d" . PHP_EOL . + "\tRewriteRule ^(.*)$ index.php?$1 [L]" . PHP_EOL . + '', + FILE_APPEND + ); + // Change le statut de la réécriture d'URL (pour le helper::baseUrl() de la redirection) + helper::$rewriteStatus = true; + } + // Désactive la réécriture d'URL + elseif( + $rewrite === false + AND helper::checkRewrite() + ) { + // Suppression des lignes dans le .htaccess + $htaccess = explode('# URL rewriting', file_get_contents('.htaccess')); + file_put_contents('.htaccess', $htaccess[0] . '# URL rewriting'); + // Change le statut de la réécriture d'URL (pour le helper::baseUrl() de la redirection) + helper::$rewriteStatus = false; + } + // Met à jour la baseUrl + $this->setData(['core', 'baseUrl', helper::baseUrl(true,false) ]); + } + // Générer robots.txt et sitemap + $this->generateFiles(); + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Modifications enregistrées ' , + 'state' => true + ]); + } + // Générer la list des pages disponibles + self::$pagesList = $this->getData(['page']); + foreach(self::$pagesList as $page => $pageId) { + if ($this->getData(['page',$page,'block']) === 'bar' || + $this->getData(['page',$page,'disable']) === true) { + unset(self::$pagesList[$page]); + } + } + + self::$orphansList = $this->getData(['page']); + foreach(self::$orphansList as $page => $pageId) { + if ($this->getData(['page',$page,'block']) === 'bar' || + $this->getData(['page',$page,'disable']) === true || + $this->getdata(['page',$page, 'position']) !== 0) { + unset(self::$orphansList[$page]); + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index' + ]); + } + + + public function script() { + // Soumission du formulaire + if($this->isPost()) { + // Ecrire les fichiers de script + if ($this->geturl(2) === 'head') { + file_put_contents(self::DATA_DIR . 'head.inc.html',$this->getInput('configScriptHead',null)); + } + if ($this->geturl(2) === 'body') { + file_put_contents(self::DATA_DIR . 'body.inc.html',$this->getInput('configScriptBody',null)); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Éditeur de script dans ' . ucfirst($this->geturl(2)) , + 'vendor' => [ + 'codemirror' + ], + 'view' => 'script', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Éditeur de script dans ' . ucfirst($this->geturl(2)) , + 'vendor' => [ + 'codemirror' + ], + 'view' => 'script' + ]); + } + + /** + * Met à jour les données de site avec l'adresse transmise + */ + public function updateBaseUrl () { + // Supprimer l'information de redirection + $old = str_replace('?','',$this->getData(['core', 'baseUrl'])); + $new = helper::baseUrl(false,false); + $c3 = 0; + $success = false ; + // Boucler sur les pages + foreach($this->getHierarchy(null,null,null) as $parentId => $childIds) { + $content = $this->getPage($parentId, self::$i18n); + $content = $titre . ' ' . $content ; + $replace = str_replace( 'href="' . $old , 'href="'. $new , stripslashes($content),$c1) ; + $replace = str_replace( 'src="' . $old , 'src="'. $new , stripslashes($replace),$c2) ; + + if ($c1 > 0 || $c2 > 0) { + $success = true; + $this->setPage($parentId, $replace, self::$i18n); + $c3 += $c1 + $c2; + } + foreach($childIds as $childId) { + $content = $this->getPage($childId, self::$i18n); + $content = $titre . ' ' . $content ; + $replace = str_replace( 'href="' . $old , 'href="'. $new , stripslashes($content),$c1) ; + $replace = str_replace( 'src="' . $old , 'src="'. $new , stripslashes($replace),$c2) ; + if ($c1 > 0 || $c2 > 0) { + $success = true; + $this->setPage($childId, $replace, self::$i18n); + $c3 += $c1 + $c2; + } + } + } + // Traiter les modules dont la redirection + $content = $this->getdata(['module']); + $replace = $this->recursive_array_replace('href="' . $old , 'href="'. $new, $content, $c1); + $replace = $this->recursive_array_replace('src="' . $old , 'src="'. $new, $replace, $c2); + if ($content !== $replace) { + $this->setdata(['module',$replace]); + $c3 += $c1 + $c2; + $success = true; + } + // Mettre à jour la base URl + $this->setData(['core','baseUrl',helper::baseUrl(true,false)]); + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Restaurer', + 'view' => 'restore', + 'notification' => $success ? $c3. ' conversion' . ($c3 > 1 ? 's' : '') . ' effectuée' . ($c3 > 1 ? 's' : '') : 'Aucune conversion', + 'state' => $success ? true : false + ]); + } + + /** + * Vider le fichier de log + */ + + public function logReset() { + if ( file_exists(self::DATA_DIR . 'journal.log') ) { + unlink(self::DATA_DIR . 'journal.log'); + // Créer les en-têtes des journaux + $d = 'Date;Heure;IP;Id;Action' . PHP_EOL; + file_put_contents(self::DATA_DIR . 'journal.log',$d); + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Journal réinitialisé avec succès', + 'state' => true + ]); + } else { + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Aucun journal à effacer', + 'state' => false + ]); + } + + } + + + + /** + * Télécharger le fichier de log + */ + public function logDownload() { + $fileName = self::DATA_DIR . 'journal.log'; + if (file_exists($fileName)) { + ob_start(); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . $fileName . '"'); + header('Content-Length: ' . filesize($fileName)); + ob_clean(); + ob_end_flush(); + readfile( $fileName); + exit(); + } else { + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Aucun fichier journal à télécharger', + 'state' => false + ]); + } + } + + /** + * Tableau des IP blacklistés + */ + public function blacklistDownload () { + ob_start(); + $fileName = self::TEMP_DIR . 'blacklist.log'; + $d = 'Date dernière tentative;Heure dernière tentative;Id;Adresse IP;Nombre d\'échecs' . PHP_EOL; + file_put_contents($fileName,$d); + if ( file_exists($fileName) ) { + $d = $this->getData(['blacklist']); + $data = ''; + foreach ($d as $key => $item) { + $data .= mb_detect_encoding(strftime('%d/%m/%y',$item['lastFail']), 'UTF-8', true) + ? strftime('%d/%m/%y',$item['lastFail']) . ';' . utf8_encode(strftime('%R',$item['lastFail'])) . ';' + : utf8_encode(strftime('%d/%m/%y',$item['lastFail'])) . ';' . utf8_encode(strftime('%R',$item['lastFail'])) . ';' ; + $data .= $key . ';' . $item['ip'] . ';' . $item['connectFail'] . PHP_EOL; + } + file_put_contents($fileName,$data,FILE_APPEND); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . $fileName . '"'); + header('Content-Length: ' . filesize($fileName)); + ob_clean(); + ob_end_flush(); + readfile( $fileName); + unlink(self::TEMP_DIR . 'blacklist.log'); + exit(); + } else { + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Aucune liste noire à télécharger', + 'state' => false + ]); + } + } + + /** + * Réinitialiser les ip blacklistées + */ + + public function blacklistReset() { + if ( file_exists(self::DATA_DIR . 'blacklist.json') ) { + $this->setData(['blacklist',[]]); + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Liste noire réinitialisée avec succès', + 'state' => true + ]); + } else { + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Pas de liste à effacer', + 'state' => false + ]); + } + } + + /** + * Récupération des backups auto dans le gestionnaire de fichiers + */ + public function copyBackups() { + // Créer le répertoire manquant + if (!is_dir(self::FILE_DIR.'source/backup')) { + mkdir(self::FILE_DIR.'source/backup', 0755); + } + $this->copyDir(self::BACKUP_DIR, self::FILE_DIR . 'source/backup' ); + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration', + 'view' => 'index', + 'notification' => 'Copie terminée', + 'state' => true + ]); + } + + + /** + * Fonction de parcours des données de module + * @param string $find donnée à rechercher + * @param string $replace donnée à remplacer + * @param array tableau à analyser + * @param int count nombres d'occurrences + * @return array avec les valeurs remplacées. + */ + private function recursive_array_replace ($find, $replace, $array, &$count) { + if (!is_array($array)) { + return str_replace($find, $replace, $array, $count); + } + + $newArray = []; + foreach ($array as $key => $value) { + $newArray[$key] = $this->recursive_array_replace($find, $replace, $value,$c); + $count += $c; + } + return $newArray; + } +} diff --git a/core/module/config/ressource/ajax-loader.png b/core/module/config/ressource/ajax-loader.png new file mode 100644 index 0000000..62b215f Binary files /dev/null and b/core/module/config/ressource/ajax-loader.png differ diff --git a/core/module/config/view/backup/backup.css b/core/module/config/view/backup/backup.css new file mode 100644 index 0000000..cf46ed2 --- /dev/null +++ b/core/module/config/view/backup/backup.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** NE PAS EFFACER +* admin.css +*/ + diff --git a/core/module/config/view/backup/backup.js.php b/core/module/config/view/backup/backup.js.php new file mode 100644 index 0000000..c78b78e --- /dev/null +++ b/core/module/config/view/backup/backup.js.php @@ -0,0 +1,58 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +$( document).ready(function() { + $("#configBackupForm").submit( function(e){ + //$("#configBackupSubmit").addClass("disabled").prop("disabled", true); + e.preventDefault(); + var url = "getUrl(0); ?>/backup"; + $.ajax({ + type: "POST", + url: url, + data: $("form").serialize(), + success: function(data){ + $('body, .button').css('cursor', 'default'); + core.alert("La sauvegarde a été générée avec succès."); + }, + error: function(data){ + $('body, .button').css('cursor', 'default'); + core.alert("Une erreur s'est produite, la sauvegarde n'a pas été générée !"); + }, + complete: function(){ + $("#configBackupSubmit").removeClass("disabled").prop("disabled", false); + $("#configBackupSubmit").removeClass("uniqueSubmission").prop("uniqueSubmission", false); + $("#configBackupSubmit span").removeClass("zwiico-spin animate-spin"); + $("#configBackupSubmit span").addClass("zwiico-check zwiico-margin-right").text("Sauvegarder"); + } + }); + }); + + + /** + * Confirmation de sauvegarde complète + */ + $("#configBackupSubmit").on("click", function() { + if ($("input[name=configBackupOption]").is(':checked')) { + return core.confirm("Une sauvegarde avec le contenu du gestionnaire de fichier peut prendre du temps à générer. Confirmez-vous ?", function() { + //$(location).attr("href", _this.attr("href")); + $('body, .button').css('cursor', 'wait'); + $('form#configBackupForm').submit(); + }); + } + }); + +}); + diff --git a/core/module/config/view/backup/backup.php b/core/module/config/view/backup/backup.php new file mode 100644 index 0000000..070ab2c --- /dev/null +++ b/core/module/config/view/backup/backup.php @@ -0,0 +1,36 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'Sauvegarder', + 'uniqueSubmission' => true + ]); ?> +
+
+
+
+
+

Paramètres de la sauvegarde

+
+
+ true, + 'help' => 'Si le contenu du gestionnaire de fichiers est très volumineux, mieux vaut une copie par FTP.' + ]); ?> +
+
+ L'archive est générée dans le dossier Backup du gestionnaire de fichiers. +
+
+
+
+
+ diff --git a/core/module/config/view/bodyheadscript/bodyheadscript.php b/core/module/config/view/bodyheadscript/bodyheadscript.php new file mode 100644 index 0000000..6bbdc08 --- /dev/null +++ b/core/module/config/view/bodyheadscript/bodyheadscript.php @@ -0,0 +1,31 @@ +
+
+
+
+

Scripts externes + + + + + +

+
+
+ helper::baseUrl() . 'config/script/head', + 'value' => 'Script dans head', + 'ico' => 'pencil' + ]); ?> +
+
+ helper::baseUrl() . 'config/script/body', + 'value' => 'Script dans body', + 'ico' => 'pencil' + ]); ?> +
+
+
+
+
+
diff --git a/core/module/config/view/connect/connect.php b/core/module/config/view/connect/connect.php new file mode 100644 index 0000000..e540921 --- /dev/null +++ b/core/module/config/view/connect/connect.php @@ -0,0 +1,117 @@ +
+
+
+
+

Sécurité de la connexion + + + + + +

+
+
+ $this->getData(['config', 'connect','captcha']) + ]); ?> +
+
+ $this->getData(['config', 'connect', 'captchaStrong']), + 'help' => 'Option recommandée pour sécuriser la connexion. S\'applique à tous les captchas du site. Le captcha simple se limite à une addition de nombres de 0 à 10. Le captcha complexe utilise quatre opérations de nombres de 0 à 20. Activation recommandée.' + ]); ?> +
+
+ 'Type de captcha', + 'selected' => $this->getData(['config', 'connect', 'captchaType']) + ]); ?> +
+
+ $this->getData(['config','connect', 'autoDisconnect']), + 'help' => 'Déconnecte les sessions ouvertes précédemment sur d\'autres navigateurs ou terminaux. Activation recommandée.' + ]); ?> +
+
+
+
+ 'Connexions successives', + 'selected' => $this->getData(['config', 'connect', 'attempt']) + ]); ?> +
+
+ 'Blocage après échecs', + 'selected' => $this->getData(['config', 'connect', 'timeout']) + ]); ?> +
+
+ + helper::baseUrl() . 'config/blacklistDownload', + 'value' => 'Télécharger la liste', + 'ico' => 'download' + ]); ?> +
+
+ 'buttonRed', + 'href' => helper::baseUrl() . 'config/blacklistReset', + 'value' => 'Réinitialiser la liste', + 'ico' => 'cancel' + ]); ?> +
+
+
+
+
+
+
+
+

Journalisation + + + + + +

+
+
+ $this->getData(['config', 'connect', 'log']) + ]); ?> +
+
+ 'Anonymat des adresses IP', + 'selected' => $this->getData(['config', 'connect', 'anonymousIp']), + 'help' => 'La réglementation française impose un anonymat de niveau 2' + ]); ?> +
+
+ helper::baseUrl() . 'config/logDownload', + 'value' => 'Télécharger le journal', + 'ico' => 'download' + ]); ?> +
+
+ 'buttonRed', + 'href' => helper::baseUrl() . 'config/logReset', + 'value' => 'Réinitialiser le journal', + 'ico' => 'cancel' + ]); ?> +
+
+
+
+
+
diff --git a/core/module/config/view/index/index.css b/core/module/config/view/index/index.css new file mode 100644 index 0000000..869f778 --- /dev/null +++ b/core/module/config/view/index/index.css @@ -0,0 +1,33 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ + +#setupContainer { + display: block; +} + +.activeButton { + filter : brightness(150%); +} + +.buttonNotice { + border: 2px solid red !important; + border-radius: 2px; +} \ No newline at end of file diff --git a/core/module/config/view/index/index.js.php b/core/module/config/view/index/index.js.php new file mode 100644 index 0000000..c8f8214 --- /dev/null +++ b/core/module/config/view/index/index.js.php @@ -0,0 +1,278 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +$( document).ready(function() { + + // Positionnement inital des options + //----------------------------------------------------------------------------------------------------- + + /** + * Afficher et masquer options smtp + */ + if ($("input[name=smtpEnable]").is(':checked')) { + $("#smtpParam").addClass("disabled"); + $("#smtpParam").slideDown(); + } else { + $("#smtpParam").removeClass("disabled"); + $("#smtpParam").slideUp(); + } + /** + * Afficher et masquer options Auth + */ + + if ($("select[name=smtpAuth]").val() == true) { + $("#smtpAuthParam").addClass("disabled"); + $("#smtpAuthParam").slideDown(); + } else { + $("#smtpAuthParam").removeClass("disabled"); + $("#smtpAuthParam").slideUp(); + } + + var configLayout = getCookie("configLayout"); + if (configLayout == null) { + configLayout = "setup"; + setCookie("configLayout","setup"); + } + $("#localeContainer").hide(); + $("#socialContainer").hide(); + $("#connectContainer").hide(); + $("#networkContainer").hide(); + $("#setupContainer").hide(); + $("#scriptContainer").hide(); + $("#" + configLayout + "Container" ).show(); + $("#config" + capitalizeFirstLetter(configLayout) + "Button").addClass("activeButton"); + + + // Gestion des événements + //--------------------------------------------------------------------------------------------------------------------- + + /** + * Afficher et masquer options smtp + */ + $("input[name=smtpEnable]").on("change", function() { + if ($("input[name=smtpEnable]").is(':checked')) { + $("#smtpParam").addClass("disabled"); + $("#smtpParam").slideDown(); + } else { + $("#smtpParam").removeClass("disabled"); + $("#smtpParam").slideUp(); + } + }); + + /** + * Afficher et masquer options Auth + */ + + $("select[name=smtpAuth]").on("change", function() { + if ($("select[name=smtpAuth]").val() == true) { + $("#smtpAuthParam").addClass("disabled"); + $("#smtpAuthParam").slideDown(); + } else { + $("#smtpAuthParam").removeClass("disabled"); + $("#smtpAuthParam").slideUp(); + } + }); + + /** + * Options de blocage de connexions + * Contrôle la cohérence des sélections et interdit une seule valeur Aucune + */ + $("select[name=connectAttempt]").on("change", function() { + if ($("select[name=connectAttempt]").val() === "999") { + $("select[name=connectTimeout]").val(0); + } else { + if ($("select[name=connectTimeout]").val() === "0") { + $("select[name=connectTimeout]").val(300); + } + } + }); + $("select[name=connectTimeout]").on("change", function() { + if ($("select[name=connectTimeout]").val() === "0") { + $("select[name=connectAttempt]").val(999); + } else { + if ($("select[name=connectAttempt]").val() === "999") { + $("select[name=connectAttempt]").val(3); + } + } + }); + + /** + * Captcha strong si captcha sélectionné + */ + $("input[name=connectCaptcha]").on("change", function() { + + if ($("input[name=connectCaptcha]").is(':checked')) { + $("#connectCaptchaStrongWrapper").addClass("disabled"); + $("#connectCaptchaStrongWrapper").slideDown(); + $( "#connectCaptchaStrong" ).prop( "checked", false ); + } else { + $("#connectCaptchaStrongWrapper").removeClass("disabled"); + $("#connectCaptchaStrongWrapper").slideUp(); + } + }); + + + /** + * Sélection de la page de configuration à afficher + */ + $("#configSetupButton").on("click", function() { + $("#localeContainer").hide(); + $("#socialContainer").hide(); + $("#connectContainer").hide(); + $("#networkContainer").hide(); + $("#scriptContainer").hide(); + $("#setupContainer").show(); + $("#configSetupButton").addClass("activeButton"); + $("#configLocaleButton").removeClass("activeButton"); + $("#configSocialButton").removeClass("activeButton"); + $("#configConnectButton").removeClass("activeButton"); + $("#configNetworkButton").removeClass("activeButton"); + $("#configScriptButton").removeClass("activeButton"); + setCookie("configLayout","setup"); + }); + $("#configLocaleButton").on("click", function() { + $("#setupContainer").hide(); + $("#socialContainer").hide(); + $("#connectContainer").hide(); + $("#networkContainer").hide(); + $("#scriptContainer").hide(); + $("#localeContainer").show(); + $("#configSetupButton").removeClass("activeButton"); + $("#configLocaleButton").addClass("activeButton"); + $("#configSocialButton").removeClass("activeButton"); + $("#configConnectButton").removeClass("activeButton"); + $("#configNetworkButton").removeClass("activeButton"); + $("#configScriptButton").removeClass("activeButton"); + setCookie("configLayout","locale"); + }); + $("#configSocialButton").on("click", function() { + $("#connectContainer").hide(); + $("#setupContainer").hide(); + $("#localeContainer").hide(); + $("#networkContainer").hide(); + $("#scriptContainer").hide(); + $("#socialContainer").show(); + $("#configSetupButton").removeClass("activeButton"); + $("#configLocaleButton").removeClass("activeButton"); + $("#configSocialButton").addClass("activeButton"); + $("#configConnectButton").removeClass("activeButton"); + $("#configNetworkButton").removeClass("activeButton"); + $("#configScriptButton").removeClass("activeButton"); + setCookie("configLayout","social"); + }); + $("#configConnectButton").on("click", function() { + $("#setupContainer").hide(); + $("#localeContainer").hide(); + $("#socialContainer").hide(); + $("#networkContainer").hide(); + $("#scriptContainer").hide(); + $("#connectContainer").show(); + $("#configSetupButton").removeClass("activeButton"); + $("#configLocaleButton").removeClass("activeButton"); + $("#configSocialButton").removeClass("activeButton"); + $("#configConnectButton").addClass("activeButton"); + $("#configNetworkButton").removeClass("activeButton"); + $("#configScriptButton").removeClass("activeButton"); + setCookie("configLayout","connect"); + }); + $("#configNetworkButton").on("click", function() { + $("#setupContainer").hide(); + $("#localeContainer").hide(); + $("#socialContainer").hide(); + $("#connectContainer").hide(); + $("#scriptContainer").hide(); + $("#networkContainer").show(); + $("#configSetupButton").removeClass("activeButton"); + $("#configLocaleButton").removeClass("activeButton"); + $("#configSocialButton").removeClass("activeButton"); + $("#configConnectButton").removeClass("activeButton"); + $("#configScriptButton").removeClass("activeButton"); + $("#configNetworkButton").addClass("activeButton"); + setCookie("configLayout","network"); + }); + $("#configScriptButton").on("click", function() { + $("#setupContainer").hide(); + $("#localeContainer").hide(); + $("#socialContainer").hide(); + $("#connectContainer").hide(); + $("#networkContainer").hide(); + $("#scriptContainer").show(); + $("#configSetupButton").removeClass("activeButton"); + $("#configLocaleButton").removeClass("activeButton"); + $("#configSocialButton").removeClass("activeButton"); + $("#configConnectButton").removeClass("activeButton"); + $("#configNetworkButton").removeClass("activeButton"); + $("#configScriptButton").addClass("activeButton"); + setCookie("configLayout","script"); + }); + + /** + * Aspect de la souris + */ + $("#socialMetaImage, #socialSiteMap, #configBackupCopyButton").click(function(event) { + $('body, .button').css('cursor', 'wait'); + }); + + + // Mise en évidence des erreurs de saisie dans les boutons de sélection + var containers = ["setup", "locale", "social", "connect", "network"]; + $.each( containers, function( index, value ){ + var a = $("div#" + value + "Container").find("input.notice").not(".displayNone"); + if (a.length > 0) { + $("#config" + capitalizeFirstLetter(value) + "Button").addClass("buttonNotice"); + } else { + $("#config" + capitalizeFirstLetter(value) + "Button").removeClass("buttonNotice"); + } + }); + + // Animation des boutons zwiico-help lien vers la documentation + var colorButton = getData(['admin', 'backgroundColorButtonHelp'])."'"; ?> ; + var blockButton = getData(['admin', 'backgroundBlockColor'])."'"; ?> ; + $(".helpDisplayButton").mouseenter(function(){ + $(this).css("background-color", colorButton); + }); + $(".helpDisplayButton").mouseleave(function(){ + $(this).css("background-color", blockButton); + }); + +}); + + +function setCookie(name,value,days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days*24*60*60*1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + (value || "") + expires + "; path=/; samesite=lax"; +} + +function getCookie(name) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return null; +} + +// Define function to capitalize the first letter of a string +function capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} \ No newline at end of file diff --git a/core/module/config/view/index/index.php b/core/module/config/view/index/index.php new file mode 100644 index 0000000..045c331 --- /dev/null +++ b/core/module/config/view/index/index.php @@ -0,0 +1,68 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl(false), + 'ico' => 'home', + 'value' => 'Accueil' + ]); ?> +
+
+ 'buttonHelp', + 'href' => 'https://doc.deltacms.fr/configuration-du-site', + 'target' => '_blank', + 'ico' => 'help', + 'value' => 'Aide' + ]); ?> +
+
+ +
+
+
+
+
+
+ 'Configuration' + ]); ?> +
+
+ 'Localisation' + ]); ?> +
+
+ 'Référencement' + ]); ?> +
+
+ 'Connexion' + ]); ?> +
+
+ 'Réseau' + ]); ?> +
+
+ 'Scripts' + ]); ?> +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/core/module/config/view/locale/locale.php b/core/module/config/view/locale/locale.php new file mode 100644 index 0000000..93328ba --- /dev/null +++ b/core/module/config/view/locale/locale.php @@ -0,0 +1,228 @@ +
+
+
+
+

Langues étrangères

+
+
+ $this->getData(['config', 'i18n', 'enable']), + 'help'=> 'Une nouvelle icône apparaîtra dans la barre d\'administration. Consultez l\'aide de la page concernée pour en apprendre plus.' + ]); ?> +
+
+
+
+
+
+
+
+

Identité du site + + + + + +

+
+
+ 'Titre du site' , + 'value' => $this->getData(['locale', 'title']), + 'help' => 'Il apparaît dans la barre de titre et les partages sur les réseaux sociaux.' + ]); ?> +
+
+ 'DeltaCMS Version', + 'value' => common::DELTA_VERSION, + 'readonly' => true + ]); ?> +
+
+
+
+ 'Description du site', + 'value' => $this->getData(['locale', 'metaDescription']), + 'help' => 'La description d\'une page participe à son référencement, chaque page doit disposer d\'une description différente.' + ]); ?> +
+
+
+
+
+
+
+
+

Assignation des pages spéciales + + + + + +

+
+
+ 'Accueil du site', + 'selected' =>$this->getData(['locale', 'homePageId']), + 'help' => 'La première page que vos visiteurs verront.' + ]); ?> +
+
+ 'Page par défaut'],helper::arrayCollumn($module::$orphansList, 'title', 'SORT_ASC')), [ + 'label' => 'Accès interdit, erreur 403', + 'selected' =>$this->getData(['locale', 'page403']), + 'help' => 'Cette page ne doit pas apparaître dans l\'arborescence du menu. Créez une page orpheline.' + ]); ?> +
+
+ 'Page par défaut'],helper::arrayCollumn($module::$orphansList, 'title', 'SORT_ASC')), [ + 'label' => 'Page inexistante, erreur 404', + 'selected' =>$this->getData(['locale', 'page404']), + 'help' => 'Cette page ne doit pas apparaître dans l\'arborescence du menu. Créez une page orpheline.' + ]); ?> +
+
+
+
+ 'Aucune'] , helper::arrayCollumn($module::$pagesList, 'title', 'SORT_ASC') ) , [ + 'label' => 'Mentions légales', + 'selected' => $this->getData(['locale', 'legalPageId']), + 'help' => 'Les mentions légales sont obligatoires en France. Une option du pied de page ajoute un lien discret vers cette page.' + ]); ?> +
+
+ 'Aucune'] , helper::arrayCollumn($module::$pagesList, 'title', 'SORT_ASC') ) , [ + 'label' => 'Recherche dans le site', + 'selected' => $this->getData(['locale', 'searchPageId']), + 'help' => 'Sélectionnez une page contenant le module \'Recherche\'. Une option du pied de page ajoute un lien discret vers cette page.' + ]); ?> +
+
+ 'Page par défaut'],helper::arrayCollumn($module::$orphansList, 'title', 'SORT_ASC')), [ + 'label' => 'Site en maintenance', + 'selected' =>$this->getData(['locale', 'page302']), + 'help' => 'Cette page ne doit pas apparaître dans l\'arborescence du menu. Créez une page orpheline.' + ]); ?> +
+
+
+
+
+
+
+
+

Etiquettes des pages spéciales + + + + + +

+
+
+ 'Mentions légales', + 'placeholder' => 'Mentions légales', + 'value' => $this->getData(['locale', 'legalPageLabel']) + ]); ?> +
+
+ 'Rechercher', + 'placeholder' => 'Rechercher', + 'value' => $this->getData(['locale', 'searchPageLabel']) + ]); ?> +
+
+
+
+ 'Plan du site', + 'placeholder' => 'Plan du site', + 'value' => $this->getData(['locale', 'sitemapPageLabel']), + ]); ?> +
+
+ 'Cookies', + 'value' => $this->getData(['locale', 'cookies', 'cookiesFooterText']), + 'placeHolder' => 'Cookies' + ]); ?> +
+
+
+
+
+
+
+
+

Message d'acceptation des Cookies + + + + + +

+
+
+ 'Saisissez le titre de la fenêtre de gestion des cookies.', + 'label' => 'Titre de la fenêtre', + 'value' => $this->getData(['locale', 'cookies', 'cookiesTitleText']), + 'placeHolder' => 'Gérer les cookies' + ]); ?> +
+
+
+
+ 'Saisissez le message pour les cookies déposés par DeltaCMS, nécessaires au fonctionnement et qui ne nécessitent pas de consentement.', + 'label' => 'Cookies Deltacms', + 'value' => $this->getData(['locale', 'cookies', 'cookiesDeltaText']), + 'placeHolder' => 'Ce site utilise des cookies nécessaires à son fonctionnement, ils permettent de fluidifier son fonctionnement par exemple en mémorisant les données de connexion, la langue que vous avez choisie ou la validation de ce message.' + ]); ?> +
+ +
+ 'Saisissez le texte du lien vers les mentions légales,la page doit être définie dans la configuration du site.', + 'label' => 'Lien page des mentions légales.', + 'value' => $this->getData(['locale', 'cookies', 'cookiesLinkMlText']), + 'placeHolder' => 'Consulter les mentions légales' + ]); ?> +
+
+
+
+ 'Saisissez le message pour les cookies déposés par d\'autres applications dont le consentement est requis sinon laissez vide.', + 'label' => 'Cookies tiers', + 'value' => $this->getData(['locale', 'cookies', 'cookiesExtText']) + ]); ?> +
+ +
+ 'Saisissez le texte de la case à cocher d\'acceptation des cookies tiers.', + 'label' => 'Checkbox cookies tiers', + 'value' => $this->getData(['locale', 'cookies', 'cookiesCheckboxExtText']) + ]); ?> +
+
+
+
+ 'Bouton de validation', + 'value' => $this->getData(['locale', 'cookies', 'cookiesButtonText']), + 'placeHolder' => 'J\'ai compris' + ]); ?> +
+
+
+
+
+
\ No newline at end of file diff --git a/core/module/config/view/network/network.php b/core/module/config/view/network/network.php new file mode 100644 index 0000000..52836c2 --- /dev/null +++ b/core/module/config/view/network/network.php @@ -0,0 +1,105 @@ +
+
+
+
+

Paramètres + + + + + +

+
+
+ 'Type de proxy', + 'selected' => $this->getData(['config', 'proxyType']) + ]); ?> +
+
+ 'Adresse du proxy', + 'placeholder' => 'cache.proxy.fr', + 'value' => $this->getData(['config', 'proxyUrl']) + ]); ?> +
+
+ 'Port du proxy', + 'placeholder' => '6060', + 'value' => $this->getData(['config', 'proxyPort']) + ]); ?> +
+
+
+
+
+
+
+
+

SMTP + + + + + +

+
+
+ $this->getData(['config', 'smtp','enable']), + 'help' => 'Paramètres à utiliser lorsque votre hébergeur ne propose pas la fonctionnalité d\'envoi de mail.' + ]); ?> +
+
+
+
+
+ 'Adresse SMTP', + 'placeholder' => 'smtp.fr', + 'value' => $this->getData(['config', 'smtp','host']) + ]); ?> +
+
+ 'Port SMTP', + 'placeholder' => '589', + 'value' => $this->getData(['config', 'smtp','port']) + ]); ?> +
+
+ 'Authentification', + 'selected' => $this->getData(['config', 'smtp','auth']) + ]); ?> +
+
+
+
+
+ 'Nom utilisateur', + 'value' => $this->getData(['config', 'smtp','username' ]) + ]); ?> +
+
+ 'Mot de passe', + 'autocomplete' => 'off', + 'value' => $this->getData(['config', 'smtp','username' ]) ? helper::decrypt ($this->getData(['config', 'smtp','username' ]),$this->getData(['config','smtp','password'])) : '' + ]); ?> +
+
+ 'Sécurité', + 'selected' => $this->getData(['config', 'smtp','secure']) + ]); ?> +
+
+
+
+
+
+
+
diff --git a/core/module/config/view/restore/restore.css b/core/module/config/view/restore/restore.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/config/view/restore/restore.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/config/view/restore/restore.js.php b/core/module/config/view/restore/restore.js.php new file mode 100644 index 0000000..e805dbb --- /dev/null +++ b/core/module/config/view/restore/restore.js.php @@ -0,0 +1,26 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +$( document).ready(function() { + + /** + * Aspect de la souris + */ + $("#configRestoreSubmit").click(function(event) { + $('body, .button').css('cursor', 'wait'); + }); + +}); \ No newline at end of file diff --git a/core/module/config/view/restore/restore.php b/core/module/config/view/restore/restore.php new file mode 100644 index 0000000..22a63cc --- /dev/null +++ b/core/module/config/view/restore/restore.php @@ -0,0 +1,83 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'Restaurer', + 'uniqueSubmission' => true, + ]); ?> +
+
+
+
+
+

Archive à restaurer

+
+
+
+ 'Sélectionnez une archive au format ZIP', + 'type' => 2, + 'help' => 'L\'archive a été déposée dans le gestionnaire de fichiers. Les archives inférieures à la version 9 ne sont pas acceptées.' + ]); ?> +
+
+ true + ]); ?> +
+
+
+
+
+
+
+
+
+

Conversion après la restauration

+
+
+ getData(['core', 'baseUrl'])) ) { + $baseUrlValue = 'Pas de donnée dans la sauvegarde'; + $buttonClass = 'disabled'; + } elseif ($this->getData(['core', 'baseUrl']) === '') { + $baseUrlValue = '/'; + $buttonClass = helper::baseUrl(false,false) !== $this->getData(['core', 'baseUrl']) ? '' : 'disabled'; + } else { + $baseUrlValue = str_replace('?','',$this->getData(['core', 'baseUrl'])); + $buttonClass = helper::baseUrl(false,false) !== $baseUrlValue ? '' : 'disabled'; + } + echo template::text('configRestoreBaseURLToConvert', [ + 'label' => 'Dossier de l\'archive' , + 'value' => $baseUrlValue, + 'readonly' => true, + 'help' => 'Le dossier de base du site est stockée dans la sauvegarde.' + ]); ?> +
+
+ 'Dossier du site actuel', + 'value' => helper::baseUrl(false,false), + 'readonly' => true + ]); ?> +
+
+ helper::baseUrl() . 'config/updateBaseUrl', + 'class' => $buttonClass, + 'value' => 'convertir' + ]); ?> +
+
+
+
+
+ diff --git a/core/module/config/view/script/script.css b/core/module/config/view/script/script.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/config/view/script/script.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/config/view/script/script.php b/core/module/config/view/script/script.php new file mode 100644 index 0000000..ea1ad0b --- /dev/null +++ b/core/module/config/view/script/script.php @@ -0,0 +1,38 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'Valider', + 'ico' => 'check' + ]); ?> +
+
+ geturl(2) === 'head'): ?> +
+
+ file_exists( self::DATA_DIR . 'head.inc.html') ? file_get_contents (self::DATA_DIR . 'head.inc.html') : '' , + 'class' => 'editor' + ]); ?> +
+
+ + geturl(2) === 'body'): ?> +
+
+ file_exists( self::DATA_DIR . 'body.inc.html') ? file_get_contents (self::DATA_DIR . 'body.inc.html') : '' , + 'class' => 'editor' + ]); ?> +
+
+ + \ No newline at end of file diff --git a/core/module/config/view/setup/setup.php b/core/module/config/view/setup/setup.php new file mode 100644 index 0000000..92905d6 --- /dev/null +++ b/core/module/config/view/setup/setup.php @@ -0,0 +1,142 @@ +
+
+
+
+

Paramètres + + + + + +

+
+
+ 1, + 'help' => 'Pensez à supprimer le cache de votre navigateur si la favicon ne change pas.', + 'label' => 'Favicon', + 'value' => $this->getData(['config', 'favicon']) + ]); ?> +
+
+ 1, + 'help' => 'Sélectionnez une icône adaptée à un thème sombre.
Pensez à supprimer le cache de votre navigateur si la favicon ne change pas.', + 'label' => 'Favicon thème sombre', + 'value' => $this->getData(['config', 'faviconDark']) + ]); ?> +
+
+ 'Fuseau horaire', + 'selected' => $this->getData(['config', 'timezone']), + 'help' => 'Le fuseau horaire est utile au bon référencement' + ]); ?> +
+
+
+
+ $this->getData(['config', 'cookieConsent']), + 'help' => 'Activation obligatoire selon les lois françaises sauf si vous utilisez votre propre système de consentement.' + ]); ?> +
+
+ helper::checkRewrite(), + 'help' => 'Vérifiez d\'abord que votre serveur autorise l\'URL rewriting (ce qui n\'est pas le cas chez Free).' + ]); ?> +
+
+
+
+
+
+
+
+

Mise à jour automatisée + + + + + +

+ +
+
+ $this->getData(['config', 'autoUpdate']), + 'help' => 'La vérification est quotidienne. Option désactivée si la configuration du serveur ne le permet pas.', + 'disabled' => !$updateError + ]); ?> +
+
+ $this->getData(['config', 'autoUpdateHtaccess']), + 'help' => 'Lors d\'une mise à jour automatique, conserve le fichier htaccess de la racine du site.', + 'disabled' => !$updateError + ]); ?> +
+
+ 'download-cloud', + 'href' => helper::baseUrl() . 'install/update', + 'value' => 'Réinstaller', + 'class' => 'buttonRed', + 'disabled' => !$updateError + ]); ?> +
+
+
+
+
+
+
+
+

Maintenance + + + + + +

+
+
+ $this->getData(['config', 'autoBackup']), + 'help' => 'Une archive contenant le dossier /site/data est copiée dans le dossier \'site/backup\'. La sauvegarde est conservée pendant 30 jours.

Les fichiers du site ne sont pas sauvegardés automatiquement. Activation recommandée.' + ]); ?> +

+
+ $this->getData(['config', 'maintenance']) + ]); ?> +
+
+
+
+ helper::baseUrl() . 'config/backup', + 'value' => 'Sauvegarder', + 'ico' => 'download-cloud' + ]); ?> +
+
+ helper::baseUrl() . 'config/restore', + 'value' => 'Restaurer', + 'ico' => 'upload-cloud' + ]); ?> +
+
+ helper::baseUrl() . 'config/copyBackups', + 'value' => 'Copie sauvegardes auto', + 'ico' => 'download-cloud' + ]); ?> +
+
+
+
+
+
diff --git a/core/module/config/view/social/social.php b/core/module/config/view/social/social.php new file mode 100644 index 0000000..94728aa --- /dev/null +++ b/core/module/config/view/social/social.php @@ -0,0 +1,132 @@ +
+
+
+
+

Paramètres + + + + + +

+
+
+
+
+ helper::baseUrl() . 'config/configOpenGraph', + 'value' => $texte + + ]); ?> +
+
+
+
+ helper::baseUrl() . 'config/generateFiles', + 'value' => 'Générer sitemap.xml et robots.txt' + ]); ?> +
+
+
+
+ $this->getData(['config', 'seo','robots']) + ]); ?> +
+
+
+
+ +
+
+ +
+
+ +
+
+

Image screenshot.jpg absente

L'activation du mode de capture Open Graph fait apparaître un appareil photo en bas de page. Un clic dessus génère une image screenshot.jpg à partir de la page active.

+
+
+ +
+
+
+
+
+
+
+
+

Réseaux sociaux + + + + + +

+
+
+ 'Saisissez votre ID : https://www.facebook.com/[ID].', + 'label' => 'Facebook', + 'value' => $this->getData(['config', 'social', 'facebookId']) + ]); ?> +
+
+ 'Saisissez votre ID : https://www.instagram.com/[ID].', + 'label' => 'Instagram', + 'value' => $this->getData(['config', 'social', 'instagramId']) + ]); ?> +
+
+ 'ID de la chaîne : https://www.youtube.com/channel/[ID].', + 'label' => 'Chaîne Youtube', + 'value' => $this->getData(['config', 'social', 'youtubeId']) + ]); ?> +
+
+ 'Saisissez votre ID Utilisateur : https://www.youtube.com/user/[ID].', + 'label' => 'Youtube', + 'value' => $this->getData(['config', 'social', 'youtubeUserId']) + ]); ?> +
+
+
+
+ 'Saisissez votre ID : https://twitter.com/[ID].', + 'label' => 'Twitter', + 'value' => $this->getData(['config', 'social', 'twitterId']) + ]); ?> +
+
+ 'Saisissez votre ID : https://pinterest.com/[ID].', + 'label' => 'Pinterest', + 'value' => $this->getData(['config', 'social', 'pinterestId']) + ]); ?> +
+
+ 'Saisissez votre ID Linkedin : https://fr.linkedin.com/in/[ID].', + 'label' => 'Linkedin', + 'value' => $this->getData(['config', 'social', 'linkedinId']) + ]); ?> +
+
+ 'Saisissez votre ID Github : https://github.com/[ID].', + 'label' => 'Github', + 'value' => $this->getData(['config', 'social', 'githubId']) + ]); ?> +
+
+
+
+
+
diff --git a/core/module/install/install.php b/core/module/install/install.php new file mode 100644 index 0000000..ded2c2a --- /dev/null +++ b/core/module/install/install.php @@ -0,0 +1,297 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +class install extends common { + + public static $actions = [ + 'index' => self::GROUP_VISITOR, + 'steps' => self::GROUP_ADMIN, + 'update' => self::GROUP_ADMIN + ]; + + // Thèmes proposés à l'installation + public static $themes = []; + + public static $newVersion; + + + /** + * Installation + */ + public function index() { + // Accès refusé + if($this->getData(['user']) !== []) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Accès autorisé + else { + // Soumission du formulaire + if($this->isPost()) { + $success = true; + // Double vérification pour le mot de passe + if($this->getInput('installPassword', helper::FILTER_STRING_SHORT, true) !== $this->getInput('installConfirmPassword', helper::FILTER_STRING_SHORT, true)) { + self::$inputNotices['installConfirmPassword'] = 'Incorrect'; + $success = false; + } + // Utilisateur + $userFirstname = $this->getInput('installFirstname', helper::FILTER_STRING_SHORT, true); + $userLastname = $this->getInput('installLastname', helper::FILTER_STRING_SHORT, true); + $userMail = $this->getInput('installMail', helper::FILTER_MAIL, true); + $userId = $this->getInput('installId', helper::FILTER_ID, true); + + + // Création de l'utilisateur si les données sont complétées. + // success retour de l'enregistrement des données + + $success = $this->setData([ + 'user', + $userId, + [ + 'firstname' => $userFirstname, + 'forgot' => 0, + 'group' => self::GROUP_ADMIN, + 'lastname' => $userLastname, + 'pseudo' => 'Admin', + 'signature' => 1, + 'mail' => $userMail, + 'password' => $this->getInput('installPassword', helper::FILTER_PASSWORD, true) + ] + ]); + + // Compte créé, envoi du mail et création des données du site + if ($success) { // Formulaire complété envoi du mail + // Envoie le mail + // Sent contient true si réussite sinon code erreur d'envoi en clair + $sent = $this->sendMail( + $userMail, + 'Installation de votre site', + 'Bonjour' . ' ' . $userFirstname . ' ' . $userLastname . ',

' . + 'Voici les détails de votre installation.

' . + 'URL du site : ' . helper::baseUrl(false) . '
' . + 'Identifiant du compte : ' . $this->getInput('installId') . '
', + null + ); + // Nettoyer les cookies de langue d'une précédente installation + helper::deleteCookie('DELTA_I18N_SITE'); + helper::deleteCookie('DELTA_I18N_SCRIPT'); + // Installation du site de test + if ($this->getInput('installDefaultData',helper::FILTER_BOOLEAN) === FALSE) { + $this->initData('page','fr',true); + $this->initData('module','fr',true); + } + // Images exemples livrées dans tous les cas + try { + // Décompression dans le dossier de fichier temporaires + if (file_exists(self::TEMP_DIR . 'files.tar.gz')) { + unlink(self::TEMP_DIR . 'files.tar.gz'); + } + if (file_exists(self::TEMP_DIR . 'files.tar')) { + unlink(self::TEMP_DIR . 'files.tar'); + } + copy('core/module/install/ressource/files.tar.gz', self::TEMP_DIR . 'files.tar.gz'); + $pharData = new PharData(self::TEMP_DIR . 'files.tar.gz'); + $pharData->decompress(); + // Installation + $pharData->extractTo(__DIR__ . '/../../../', null, true); + } catch (Exception $e) { + $success = $e->getMessage(); + } + unlink(self::TEMP_DIR . 'files.tar.gz'); + unlink(self::TEMP_DIR . 'files.tar'); + // Stocker le dossier d'installation + $this->setData(['core', 'baseUrl', helper::baseUrl(false,false) ]); + // Créer sitemap + $this->createSitemap(); + + // Installation du thème sélectionné + $dataThemes = file_get_contents('core/module/install/ressource/themes/themes.json'); + $dataThemes = json_decode($dataThemes, true); + $themeId = $dataThemes [$this->getInput('installTheme', helper::FILTER_STRING_SHORT)]['filename']; + if ($themeId !== 'default' ) { + $theme = new theme; + $theme->import('core/module/install/ressource/themes/' . $themeId); + } + + // Copie des thèmes dans les fichiers + if (!is_dir(self::FILE_DIR . 'source/theme' )) { + mkdir(self::FILE_DIR . 'source/theme'); + } + $this->copyDir('core/module/install/ressource/themes', self::FILE_DIR . 'source/theme'); + unlink(self::FILE_DIR . 'source/theme/themes.json'); + + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl(false), + 'notification' => $sent === true ? 'Installation terminée' : $sent, + 'state' => ($sent === true && $success === true) ? true : null + ]); + } + } + // Récupération de la liste des thèmes + $dataThemes = file_get_contents('core/module/install/ressource/themes/themes.json'); + $dataThemes = json_decode($dataThemes, true); + self::$themes = helper::arrayCollumn($dataThemes, 'name'); + + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_LAYOUT_LIGHT, + 'title' => 'Installation', + 'view' => 'index' + ]); + } + } + + /** + * Étapes de mise à jour + */ + public function steps() { + switch($this->getInput('step', helper::FILTER_INT)) { + // Préparation + case 1: + $success = true; + // RAZ la mise à jour auto + $this->setData(['core','updateAvailable', false]); + // Backup du dossier Data + helper::autoBackup(self::BACKUP_DIR,['backup','tmp','file']); + // Sauvegarde htaccess + if ($this->getData(['config','autoUpdateHtaccess'])) { + $success = copy('.htaccess', '.htaccess' . '.bak'); + } + // Nettoyage des fichiers d'installation précédents + if(file_exists(self::TEMP_DIR.'update.tar.gz') && $success) { + $success = unlink(self::TEMP_DIR.'update.tar.gz'); + } + if(file_exists(self::TEMP_DIR.'update.tar') && $success) { + $success = unlink(self::TEMP_DIR.'update.tar'); + } + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_JSON, + 'content' => [ + 'success' => $success, + 'data' => null + ] + ]); + break; + // Téléchargement + case 2: + // Téléchargement depuis le serveur de Zwii + //$success = (file_put_contents(self::TEMP_DIR.'update.tar.gz', helper::urlGetContents('https://zwiicms.fr/update/' . common::DELTA_UPDATE_CHANNEL . '/update.tar.gz')) !== false); + // URL sur le git + //$newVersion = helper::urlGetContents('https://zwiicms.fr/update/' . common::DELTA_UPDATE_CHANNEL . '/version'); + $success = (file_put_contents(self::TEMP_DIR.'update.tar.gz', helper::urlGetContents(common::DELTA_UPDATE_URL . common::DELTA_UPDATE_CHANNEL . '/update.tar.gz')) !== false); + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_JSON, + 'content' => [ + 'success' => $success, + 'data' => null + ] + ]); + break; + // Installation + case 3: + $success = true; + // Check la réécriture d'URL avant d'écraser les fichiers + $rewrite = helper::checkRewrite(); + // Décompression et installation + try { + // Décompression dans le dossier de fichier temporaires + $pharData = new PharData(self::TEMP_DIR.'update.tar.gz'); + $pharData->decompress(); + // Installation + $pharData->extractTo(__DIR__ . '/../../../', null, true); + } catch (Exception $e) { + $success = $e->getMessage(); + } + // Nettoyage du dossier + if(file_exists(self::TEMP_DIR.'update.tar.gz')) { + unlink(self::TEMP_DIR.'update.tar.gz'); + } + if(file_exists(self::TEMP_DIR.'update.tar')) { + unlink(self::TEMP_DIR.'update.tar'); + } + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_JSON, + 'content' => [ + 'success' => $success, + 'data' => $rewrite + ] + ]); + break; + // Configuration + case 4: + $success = true; + $rewrite = $this->getInput('data'); + // Réécriture d'URL + if ($rewrite === "true") { + $success = (file_put_contents( + '.htaccess', + PHP_EOL . + '' . PHP_EOL . + "\tRewriteEngine on" . PHP_EOL . + "\tRewriteBase " . helper::baseUrl(false, false) . PHP_EOL . + "\tRewriteCond %{REQUEST_FILENAME} !-f" . PHP_EOL . + "\tRewriteCond %{REQUEST_FILENAME} !-d" . PHP_EOL . + "\tRewriteRule ^(.*)$ index.php?$1 [L]" . PHP_EOL . + '', + FILE_APPEND + ) !== false); + } + // Recopie htaccess + if ($this->getData(['config','autoUpdateHtaccess']) && + $success && file_exists( '.htaccess.bak') + ) { + // L'écraser avec le backup + $success = copy( '.htaccess.bak' ,'.htaccess' ); + // Effacer l ebackup + unlink('.htaccess.bak'); + } + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_JSON, + 'content' => [ + 'success' => $success, + 'data' => null + ] + ]); + break; + } + } + + /** + * Mise à jour + */ + public function update() { + // Nouvelle version + self::$newVersion = helper::urlGetContents(common::DELTA_UPDATE_URL . common::DELTA_UPDATE_CHANNEL . '/version'); + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_LAYOUT_LIGHT, + 'title' => 'Mise à jour', + 'view' => 'update' + ]); + } + + +} \ No newline at end of file diff --git a/core/module/install/ressource/.htaccess b/core/module/install/ressource/.htaccess new file mode 100644 index 0000000..85278f8 --- /dev/null +++ b/core/module/install/ressource/.htaccess @@ -0,0 +1,10 @@ +# Bloque l'accès aux données + + Order deny,allow + Deny from all + +# Bloque l'accès htaccess + + Order deny,allow + Deny from all + \ No newline at end of file diff --git a/core/module/install/ressource/defaultdata.php b/core/module/install/ressource/defaultdata.php new file mode 100644 index 0000000..5e335c1 --- /dev/null +++ b/core/module/install/ressource/defaultdata.php @@ -0,0 +1,878 @@ + [ + 'analyticsId' => '', + 'autoBackup' => true, + 'autoUpdate' => true, + 'autoUpdateHtaccess' => false, + 'favicon' => 'favicon.ico', + 'faviconDark' => 'faviconDark.ico', + 'maintenance' => false, + 'cookieConsent' => true, + 'social' => [ + 'facebookId' => '', + 'instagramId' => '', + 'pinterestId' => '', + 'twitterId' => '', + 'youtubeId' => '', + 'youtubeUserId' => '', + 'githubId' => '' + ], + 'timezone' => 'Europe/Paris', + 'proxyUrl' => '', + 'proxyPort' => '', + 'proxyType' => 'tcp://', + 'smtp' => [ + 'enable' => false, + ], + 'seo' => [ + 'robots' => true + ], + 'connect' => [ + 'timeout' => 600, + 'attempt' => 3, + 'log' => false, + 'anonymousIp' => 2, + 'captcha' => true, + 'captchaStrong' => false, + "captchaType" => 'num', + 'autoDisconnect' => true + ], + 'i18n' => [ + 'enable'=> true, + 'scriptGoogle'=> false, + 'showCredits'=> false, + 'autoDetect'=> false, + 'admin'=> false, + 'fr'=> 'none', + 'de'=> 'none', + 'en'=> 'none', + 'es'=> 'none', + 'it'=> 'none', + 'nl'=> 'none', + 'pt'=> 'none' + ] + ], + 'core' => [ + 'dataVersion' => 3101, + 'lastBackup' => 0, + 'lastClearTmp' => 0, + 'lastAutoUpdate' => 0, + 'updateAvailable' => false, + 'baseUrl' => '' + ], + 'locale' => [ + 'homePageId' => 'accueil', + 'page302' => 'none', + 'page403' => 'none', + 'page404' => 'none', + 'legalPageId' => 'none', + 'searchPageId' => 'none', + 'searchPageLabel' => 'Rechercher', + 'sitemapPageLabel' => 'Plan du site', + 'legalPageLabel' => 'Mentions légales', + 'metaDescription' => 'DeltaCMS est un CMS sans base de données qui permet de créer et gérer facilement un site web sans aucune connaissance en programmation.', + 'title' => 'Avec DeltaCMS installez votre site en quelques clics !', + 'cookies' => [ + 'cookiesDeltaText' => 'Ce site utilise des cookies nécessaires à son fonctionnement, ils permettent de fluidifier son fonctionnement par exemple en mémorisant les données de connexion, la langue que vous avez choisie ou la validation de ce message.', + 'cookiesExtText' => '', + 'cookiesTitleText' => 'Gérer les cookies', + 'cookiesLinkMlText' => 'Consulter les mentions légales', + 'cookiesCheckboxExtText' => '', + 'cookiesFooterText' => 'Cookies', + 'cookiesButtonText' => 'J\'ai compris' + ] + ], + 'page' => [ + 'accueil' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'accueil.html', + 'hideTitle' => false, + 'homePageId' => true, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 1, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Accueil', + 'shortTitle' => 'Accueil', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ] + ], + 'module' => [], + 'user' => [], + 'theme' => [ + 'body' => [ + 'backgroundColor' => 'rgba(103, 127, 163, 1)', + 'image' => '', + 'imageAttachment' => 'scroll', + 'imageRepeat' => 'no-repeat', + 'imagePosition' => 'top center', + 'imageSize' => 'auto', + 'toTopbackgroundColor' => 'rgba(33, 34, 35, .8)', + 'toTopColor' => 'rgba(255, 255, 255, 1)' + ], + 'footer' => [ + 'backgroundColor' => 'rgba(255, 255, 255, 1)', + 'font' => 'Open+Sans', + 'fontSize' => '.8em', + 'fontWeight' => 'normal', + 'height' => '5px', + 'loginLink' => true, + 'margin' => true, + 'position' => 'site', + 'textColor' => 'rgba(33, 34, 35, 1)', + 'copyrightPosition' => 'right', + 'copyrightAlign' => 'right', + 'text' => '

Pied de page personnalisé

', + 'textPosition' => 'left', + 'textAlign' => 'left', + 'textTransform' => 'none', + 'socialsPosition' => 'center', + 'socialsAlign' => 'center', + 'displayVersion' => true, + 'displaySiteMap' => true, + 'displayCopyright' => false, + 'displayCookie' => false, + 'displayLegal' => false, + 'displaySearch' => false, + 'displayMemberBar' => false, + 'template' => '3' + ], + 'header' => [ + 'backgroundColor' => 'rgba(32, 59, 82, 1)', + 'font' => 'Oswald', + 'fontSize' => '2em', + 'fontWeight' => 'normal', + 'height' => '200px', + 'image' => 'theme/defaut/banniere_1500x200.jpg', + 'imagePosition' => 'center center', + 'imageRepeat' => 'no-repeat', + 'margin' => false, + 'position' => 'site', + 'textAlign' => 'center', + 'textColor' => 'rgba(255, 255, 255, 1)', + 'textHide' => false, + 'textTransform' => 'none', + 'linkHomePage' => true, + 'imageContainer' => 'auto', + 'tinyHidden' => true, + 'feature' => 'wallpaper', + 'featureContent' => '

Bannière vide

', + 'width' => 'container' + ], + 'menu' => [ + 'backgroundColor' => 'rgba(32, 59, 82, 0.85)', + 'backgroundColorSub' => 'rgba(32, 59, 82, 1)', + 'font' => 'Open+Sans', + 'fontSize' => '1em', + 'fontWeight' => 'normal', + 'height' => '15px 10px', + 'loginLink' => false, + 'margin' => false, + 'position' => 'top', + 'textAlign' => 'left', + 'textColor' => 'rgba(255, 255, 255, 1)', + 'textTransform' => 'none', + 'fixed' => true, + 'activeColorAuto' => true, + 'activeColor' => 'rgba(255, 255, 255, 1)', + 'activeTextColor' => 'rgba(255, 255, 255, 1)', + 'radius' => '0px', + 'memberBar' => true, + 'burgerLogo' => '', + 'burgerContent' => 'title', + 'width' => 'container' + ], + 'site' => [ + 'backgroundColor' => 'rgba(255, 255, 255, 1)', + 'radius' => '0px', + 'shadow' => '0px 0px 0px', + 'width' => '100%' + ], + 'block' => [ + 'backgroundTitleColor' => 'rgba(230, 230, 230, 1)', + 'backgroundColor' => 'rgba(241, 241, 241, 1)', + 'borderColor' => 'rgba(230, 230, 230, 1)', + 'blockBorderRadius' => '5px', + 'blockBorderShadow' => '3px 3px 6px', + ], + 'text' => [ + 'font' => 'Open+Sans', + 'fontSize' => '13px', + 'textColor' => 'rgba(33, 34, 35, 1)', + 'linkColor' => 'rgba(74, 105, 189, 1)' + ], + 'title' => [ + 'font' => 'Oswald', + 'fontWeight' => 'normal', + 'textColor' => 'rgba(74, 105, 189, 1)', + 'textTransform' => 'none' + ], + 'button' => [ + 'backgroundColor' => 'rgba(32, 59, 82, 1)' + ], + 'version' => 0 + ], + 'admin' => [ + 'backgroundColor' => 'rgba(255, 255, 255, 1)', + 'fontText' => 'open+Sans', + 'fontSize' => '13px', + 'fontTitle' => 'Oswald', + 'colorText' => 'rgba(33, 34, 35, 1)', + 'colorTitle' => 'rgba(74, 105, 189, 1)', + 'backgroundColorButton' => 'rgba(74, 105, 189, 1)', + 'backgroundColorButtonGrey' => 'rgba(170, 180, 188, 1)', + 'backgroundColorButtonRed' => 'rgba(217, 95, 78, 1)', + 'backgroundColorButtonGreen' => 'rgba(162, 223, 57, 1)', + 'backgroundColorButtonHelp' => 'rgba(255, 153, 0, 1)', + 'backgroundBlockColor' => 'rgba(236, 239, 241, 1)', + 'borderBlockColor' => 'rgba(190, 202, 209, 1)' + ], + 'blacklist' => [] + ]; + + + public static $siteData = [ + 'page' => [ + 'accueil' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'accueil.html', + 'hideTitle' => false, + 'homePageId' => true, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 1, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Accueil', + 'shortTitle' => 'Accueil', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'enfant' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'enfant.html', + 'hideTitle' => false, + 'breadCrumb' => true, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => 'accueil', + 'position' => 1, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Page Enfant', + 'shortTitle' => 'Enfant', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'privee' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'privee.html', + 'hideTitle' => false, + 'breadCrumb' => true, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'parentPageId' => '', + 'modulePosition' => 'bottom', + 'position' => 2, + 'group' => self::GROUP_MEMBER, + 'targetBlank' => false, + 'title' => 'Page privée', + 'shortTitle' => 'Privée', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'mise-en-page' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'mise-en-page.html', + 'hideTitle' => false, + 'breadCrumb' => true, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'parentPageId' => 'accueil', + 'modulePosition' => 'bottom', + 'position' => 2, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Mise en page', + 'shortTitle' => 'Mise en page', + 'block' => '4-8', + 'barLeft' => 'barre', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'menu-lateral' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'menu-lateral.html', + 'hideTitle' => false, + 'breadCrumb' => true, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'parentPageId' => 'accueil', + 'modulePosition' => 'bottom', + 'position' => 3, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Barre latérale avec menu', + 'shortTitle' => 'Menu latéral', + 'block' => '9-3', + 'barLeft' => '', + 'barRight' => 'barrelateraleavecmenu', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'blog' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'blog.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => 'blog', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 3, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Blog', + 'shortTitle' => 'Blog', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'galeries' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'galeries.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => 'gallery', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 4, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Galeries d\'images', + 'shortTitle' => 'Galeries', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'contact' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'contact.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => 'form', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 6, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Contact', + 'shortTitle' => 'Contact', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'barre' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'barre.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 0 , + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Barre latérale', + 'shortTitle' => 'Barre latérale', + 'block' => 'bar', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'barrelateraleavecmenu' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'barrelateraleavecmenu.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 0 , + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Barre latérale avec menu', + 'shortTitle' => 'Barre latérale avec menu', + 'block' => 'bar', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'parents', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'mentions-legales' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'mentions-legales.html', + 'hideTitle' => true, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => 'Mentions Légales', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 0, + 'group' => 0, + 'targetBlank' => false, + 'title' => 'Mentions légales', + 'shortTitle' => 'Mentions légales', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuHead' => false, + 'hideMenuChildren' => false + ], + 'erreur302' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'erreur302.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => '', + 'parentPageId' => '', + 'position' => 0, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Maintenance en cours', + 'shortTitle' => 'Maintenance en cours', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => true, + 'hideMenuHead' => true, + 'hideMenuChildren' => true + ], + 'erreur403' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'erreur403.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 0, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Erreur 403', + 'shortTitle' => 'Erreur 403', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' => false + ], + 'erreur404' => [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => 'erreur404.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => 'search', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 0, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Erreur 404', + 'shortTitle' => 'Erreur 404', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' =>false + ], + 'recherche' => [ + 'typeMenu' => 'icon', + 'iconUrl' => 'icones/loupe.png', + 'disable' => false, + 'content' => 'recherche.html', + 'hideTitle' => true, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => 'search', + 'modulePosition' => 'bottom', + 'parentPageId' => '', + 'position' => 7, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => 'Recherche dans le site', + 'shortTitle' => 'Rechercher', + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => 'none', + 'hideMenuSide' => false, + 'hideMenuChildren' => false + ], + ], + 'module' => [ + 'blog' => [ + 'config' => [ + 'feeds' => true, + 'feedsLabel' => 'Syndication RSS', + 'itemsperPage' => 4 + ], + 'posts' => [ + 'premier-article' => [ + 'closeComment' => false, + 'comment' => [], + 'content' => '

Et eodem impetu Domitianum praecipitem per scalas itidem funibus constrinxerunt, eosque coniunctos per ampla spatia civitatis acri raptavere discursu. iamque artuum et membrorum divulsa conpage superscandentes corpora mortuorum ad ultimam truncata deformitatem velut exsaturati mox abiecerunt in flumen.

Ex his quidam aeternitati se commendari posse per statuas aestimantes eas ardenter adfectant quasi plus praemii de figmentis aereis sensu carentibus adepturi, quam ex conscientia honeste recteque factorum, easque auro curant inbracteari, quod Acilio Glabrioni delatum est primo, cum consiliis armisque regem superasset Antiochum. quam autem sit pulchrum exigua haec spernentem et minima ad ascensus verae gloriae tendere longos et arduos, ut memorat vates Ascraeus, Censorius Cato monstravit. qui interrogatus quam ob rem inter multos... statuam non haberet malo inquit ambigere bonos quam ob rem id non meruerim, quam quod est gravius cur inpetraverim mussitare.

Latius iam disseminata licentia onerosus bonis omnibus Caesar nullum post haec adhibens modum orientis latera cuncta vexabat nec honoratis parcens nec urbium primatibus nec plebeiis.

', + 'picture' => 'galerie/toctoc/b.jpg', + 'picturePosition' => 'left', + 'hidePicture' => false, + 'pictureSize' => 20, + 'picturePosition' => 'left', + 'publishedOn' => 1639816735, + 'state' => true, + 'title' => 'Premier article', + 'userId' => '', // Géré au moment de l'installation + 'editConsent' => 'all', + 'commentMaxlength' => '500', + 'commentApproved' => false, + 'commentClose' => false, + 'commentNotification' => false, + 'commentGroupNotification' => 1 + ], + 'second-article' => [ + 'closeComment' => false, + 'comment' => [], + 'content' => '

Et prima post Osdroenam quam, ut dictum est, ab hac descriptione discrevimus, Commagena, nunc Euphratensis, clementer adsurgit, Hierapoli, vetere Nino et Samosata civitatibus amplis inlustris.

Ob haec et huius modi multa, quae cernebantur in paucis, omnibus timeri sunt coepta. et ne tot malis dissimulatis paulatimque serpentibus acervi crescerent aerumnarum, nobilitatis decreto legati mittuntur: Praetextatus ex urbi praefecto et ex vicario Venustus et ex consulari Minervius oraturi, ne delictis supplicia sint grandiora, neve senator quisquam inusitato et inlicito more tormentis exponeretur.

Sed ut tum ad senem senex de senectute, sic hoc libro ad amicum amicissimus scripsi de amicitia. Tum est Cato locutus, quo erat nemo fere senior temporibus illis, nemo prudentior; nunc Laelius et sapiens (sic enim est habitus) et amicitiae gloria excellens de amicitia loquetur. Tu velim a me animum parumper avertas, Laelium loqui ipsum putes. C. Fannius et Q. Mucius ad socerum veniunt post mortem Africani; ab his sermo oritur, respondet Laelius, cuius tota disputatio est de amicitia, quam legens te ipse cognosces.

', + 'picture' => 'galerie/grece/port.jpg', + 'hidePicture' => false, + 'picturePosition' => 'right', + 'pictureSize' => 40, + 'picturePosition' => 'right', + 'publishedOn' => 1639816823, + 'state' => true, + 'title' => 'Second article', + 'userId' => '', // Géré au moment de l'installation + 'editConsent' => 'all', + 'commentMaxlength' => '500', + 'commentApproved' => false, + 'commentClose' => false, + 'commentNotification' => false, + 'commentGroupNotification' => 1 + ], + ], + ], + 'galeries' => [ + 'content' => [ + 'toctoc' => [ + 'config' => [ + 'name' => 'Toctoc', + 'directory' => self::FILE_DIR.'source/galerie/toctoc', + 'homePicture' => 'a.jpg', + 'sort' => 'SORT_ASC', + 'position' => 0, + 'fullscreen' => false + ], + 'legend' => [ + 'a.jpg' => '', + 'b.jpg' => '', + 'c.jpg' => '' + ], + 'positions' => [] + ], + 'grece' => [ + 'config' => [ + 'name' => 'Grèce', + 'directory' => self::FILE_DIR.'source/galerie/grece', + 'homePicture' => 'philosophe.jpg', + 'sort' => 'SORT_ASC', + 'position' => 1, + 'fullscreen' => false + ], + 'legend' => [ + 'chapiteaujpg' => 'Chapiteau', + 'philosophejpg' => 'Philosophe', + 'portjpg' => 'Port' + ], + 'positions' => [] + ], + ], + 'theme' => [ + 'thumbAlign' => 'center', + 'thumbWidth' => '18em', + 'thumbHeight' => '15em', + 'thumbMargin' => '.5em', + 'thumbBorder' => '.1em', + 'thumbOpacity' => '.7', + 'thumbBorderColor' => 'rgba(221, 221, 221, 1)', + 'thumbRadius' => '.3em', + 'thumbShadows' => '1px 1px 10px', + 'thumbShadowsColor'=> 'rgba(125, 125, 125, 1)', + 'legendHeight' => '.375em', + 'legendAlign' => 'center', + 'legendTextColor' => 'rgba(255, 255, 255, 1)', + 'legendBgColor' => 'rgba(0, 0, 0, .6)', + 'style' => 'site/data/modules/gallery/galeries.css' + ], + 'config' => [ + 'versionData' => '3.0' + ], + ], + 'contact' => [ + 'config' => [ + 'button' => '', + 'captcha' => true, + 'group' => self::GROUP_ADMIN, + 'pageId' => '', + 'subject' => '' + ], + 'data' => [], + 'input' => [ + [ + 'name' => 'Adresse mail', + 'position' => 1, + 'required' => true, + 'type' => 'mail', + 'values' => '' + ], + [ + 'name' => 'Sujet', + 'position' => 2, + 'required' => true, + 'type' => 'text', + 'values' => '' + ], + [ + 'name' => 'Message', + 'position' => 3, + 'required' => true, + 'type' => 'textarea', + 'values' => '' + ] + ] + ] + ], + 'locale' => [ + 'homePageId' => 'accueil', + 'page302' => 'none', + 'page403' => 'none', + 'page404' => 'none', + 'legalPageId' => 'none', + 'searchPageId' => 'none', + 'metaDescription' => 'DeltaCMS est un CMS sans base de données qui permet de créer et gérer facilement un site web sans aucune connaissance en programmation.', + 'title' => 'Avec DeltaCMS installez votre site en quelques clics !' + ] + ]; + + public static $siteContent = [ + 'accueil' => [ + 'content' => '

Bienvenue sur votre nouveau site DeltaCMS !

+

Un email contenant le récapitulatif de votre installation vient de vous être envoyé.

+

Connectez-vous dès maintenant à votre espace membre afin de créer un site à votre image ! Vous pourrez personnaliser le thème, créer des pages, ajouter des utilisateurs et bien plus encore !

' + ], + 'enfant' => [ + 'content' => '

Vous pouvez assigner des parents à vos pages afin de mieux organiser votre menu !

+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ac dolor arcu. Cras dignissim finibus nisi, vulputate egestas mauris faucibus ultricies. Nullam ornare pretium eleifend. Donec placerat purus ut turpis dapibus condimentum. Fusce at leo pharetra nisl vestibulum fermentum. Maecenas feugiat justo at semper tincidunt. Integer in blandit lorem.

+

Ergo ego senator inimicus, si ita vultis, homini, amicus esse, sicut semper fui, rei publicae debeo. Quid? si ipsas inimicitias, depono rei publicae causa, quis me tandem iure reprehendet, praesertim cum ego omnium meorum consiliorum atque factorum exempla semper ex summorum hominum consiliis atque factis mihi censuerim petenda.

+

Principium autem unde latius se funditabat, emersit ex negotio tali. Chilo ex vicario et coniux eius Maxima nomine, questi apud Olybrium ea tempestate urbi praefectum, vitamque suam venenis petitam adseverantes inpetrarunt ut hi, quos suspectati sunt, ilico rapti conpingerentur in vincula, organarius Sericus et Asbolius palaestrita et aruspex Campensis.

+
' + ], + 'privee'=> [ + 'content' => '

Cette page n\'est visible que des membres de votre site !

+
+

Eius populus ab incunabulis primis ad usque pueritiae tempus extremum, quod annis circumcluditur fere trecentis, circummurana pertulit bella, deinde aetatem ingressus adultam post multiplices bellorum aerumnas Alpes transcendit et fretum, in iuvenem erectus et virum ex omni plaga quam orbis ambit inmensus, reportavit laureas et triumphos, iamque vergens in senium et nomine solo aliquotiens vincens ad tranquilliora vitae discessit.

+

Exsistit autem hoc loco quaedam quaestio subdifficilis, num quando amici novi, digni amicitia, veteribus sint anteponendi, ut equis vetulis teneros anteponere solemus. Indigna homine dubitatio! Non enim debent esse amicitiarum sicut aliarum rerum satietates; veterrima quaeque, ut ea vina, quae vetustatem ferunt, esse debet suavissima; verumque illud est, quod dicitur, multos modios salis simul edendos esse, ut amicitiae munus expletum sit.

+
' + ], + 'mise-en-page' => [ + 'content' => '

Vous pouvez ajouter une ou deux barres latérales aux pages de votre site. Cette mise en page se définit dans les paramètres de page et peut s\'appliquer à l\'ensemble du site ou à certaines pages en particulier, au gré de vos désirs.

+

Pour créer une barre latérale à partir d\'une "Nouvelle page" ou transformer une page existante en barre latérale, sélectionnez l\'option dans la liste des gabarits. On peut bien sûr définir autant de barres latérales qu\'on le souhaite.

+

Cette nouvelle fonctionnalité autorise toutes sortes d\'utilisations : texte, encadrés, images, vidéos... ou simple marge blanche. Seule restriction : on ne peut pas installer un module dans une barre latérale.

+

La liste des barres disponibles et leur emplacement s\'affichent en fonction du gabarit que vous aurez choisi.' + ], + 'menu-lateral' => [ + 'content' => '

Cette page illustre la possibilité d\'ajouter un menu dans les barres latérales.
+ Deux types de menus sont disponibles : l\'un reprenant les rubriques du menu principal comme celui-ci, l\'autre listant les pages d\'une même rubrique. Le choix du type de menu se fait dans la page de configuration d\'une barre latérale.

+

Pour ajouter un menu à une page, choisissez une barre latérale avec menu dans la page de configuration. Les bulles d\'aide de la rubrique "Menu" expliquent comment masquer une page.

' + ], + 'blog' => [ + 'content' => '

Cette page contient une instance du module de blog. Cliquez sur un article afin de le lire et de poster des commentaires.

' + ], + 'galeries' => [ + 'content' => '

Cette page contient une instance du module de galeries photos. Cliquez sur la galerie ci-dessous afin de voir les photos qu\'elle contient.

' + ], + 'contact' => [ + 'content' => '

Cette page contient un exemple de formulaire conçu à partir du module de génération de formulaires. Il est configuré pour envoyer les données saisies par mail aux administrateurs du site.

' + ], + 'barre' => [ + 'content' => '

DeltaCMS

Le CMS sans base de données à l\'installation simple et rapide

' + ], + 'barrelateraleavecmenu' => [ + 'content' => '

 

' + ], + 'mentions-legales' => [ + 'content' => '

Conditions générales d\'utilisation

+

En vigueur au 01/06/2020

+

AvertissementCette page fictive est donnée à titre indicatif elle a été réalisée à l\'aide d\'un générateur : https://www.legalplace.fr

+

Les présentes conditions générales d\'utilisation (dites « CGU ») ont pour objet l\'encadrement juridique des modalités de mise à disposition du site et des services par et de définir les conditions d’accès et d’utilisation des services par « l\'Utilisateur ».

+

Les présentes CGU sont accessibles sur le site à la rubrique «CGU».

+

Toute inscription ou utilisation du site implique l\'acceptation sans aucune réserve ni restriction des présentes CGU par l’utilisateur. Lors de l\'inscription sur le site via le Formulaire d’inscription, chaque utilisateur accepte expressément les présentes CGU en cochant la case précédant le texte suivant : « Je reconnais avoir lu et compris les CGU et je les accepte ».

+

En cas de non-acceptation des CGU stipulées dans le présent contrat, l\'Utilisateur se doit de renoncer à l\'accès des services proposés par le site.

+

www.site.com se réserve le droit de modifier unilatéralement et à tout moment le contenu des présentes CGU.

+

Article 1 : Les mentions légales

+

L’édition et la direction de la publication du site www.site.com est assurée par John Doe, domicilié 1 rue de Paris - 75016 PARIS.

+

Numéro de téléphone est 0102030405

+

Adresse e-mail john.doe@deltacms.fr.

+

L\'hébergeur du site www.site.com est la société Nom de l\'hébergeur, dont le siège social est situé au 12 rue de Lyon - 69001 Lyon, avec le numéro de téléphone : 0401020305.

+

ARTICLE 2 : Accès au site

+

Le site www.site.com permet à l\'Utilisateur un accès gratuit aux services suivants :

+

Le site internet propose les services suivants :

+

Publication

+

Le site est accessible gratuitement en tout lieu à tout Utilisateur ayant un accès à Internet. Tous les frais supportés par l\'Utilisateur pour accéder au service (matériel informatique, logiciels, connexion Internet, etc.) sont à sa charge.

+

L’Utilisateur non membre n\'a pas accès aux services réservés. Pour cela, il doit s’inscrire en remplissant le formulaire. En acceptant de s’inscrire aux services réservés, l’Utilisateur membre s’engage à fournir des informations sincères et exactes concernant son état civil et ses coordonnées, notamment son adresse email.

+

Pour accéder aux services, l’Utilisateur doit ensuite s\'identifier à l\'aide de son identifiant et de son mot de passe qui lui seront communiqués après son inscription.

+

Tout Utilisateur membre régulièrement inscrit pourra également solliciter sa désinscription en se rendant à la page dédiée sur son espace personnel. Celle-ci sera effective dans un délai raisonnable.

+

Tout événement dû à un cas de force majeure ayant pour conséquence un dysfonctionnement du site ou serveur et sous réserve de toute interruption ou modification en cas de maintenance, n\'engage pas la responsabilité de www.site.com. Dans ces cas, l’Utilisateur accepte ainsi ne pas tenir rigueur à l’éditeur de toute interruption ou suspension de service, même sans préavis.

+

L\'Utilisateur a la possibilité de contacter le site par messagerie électronique à l’adresse email de l’éditeur communiqué à l’ARTICLE 1.

+

ARTICLE 3 : Collecte des données

+

Le site est exempté de déclaration à la Commission Nationale Informatique et Libertés (CNIL) dans la mesure où il ne collecte aucune donnée concernant les Utilisateurs.

+

ARTICLE 4 : Propriété intellectuelle

+

Les marques, logos, signes ainsi que tous les contenus du site (textes, images, son…) font l\'objet d\'une protection par le Code de la propriété intellectuelle et plus particulièrement par le droit d\'auteur.

+

L\'Utilisateur doit solliciter l\'autorisation préalable du site pour toute reproduction, publication, copie des différents contenus. Il s\'engage à une utilisation des contenus du site dans un cadre strictement privé, toute utilisation à des fins commerciales et publicitaires est strictement interdite.

+

Toute représentation totale ou partielle de ce site par quelque procédé que ce soit, sans l’autorisation expresse de l’exploitant du site Internet constituerait une contrefaçon sanctionnée par l’article L 335-2 et suivants du Code de la propriété intellectuelle.

+

Il est rappelé conformément à l’article L122-5 du Code de propriété intellectuelle que l’Utilisateur qui reproduit, copie ou publie le contenu protégé doit citer l’auteur et sa source.

+

ARTICLE 5 : Responsabilité

+

Les sources des informations diffusées sur le site www.site.com sont réputées fiables mais le site ne garantit pas qu’il soit exempt de défauts, d’erreurs ou d’omissions.

+

Les informations communiquées sont présentées à titre indicatif et général sans valeur contractuelle. Malgré des mises à jour régulières, le site www.site.com ne peut être tenu responsable de la modification des dispositions administratives et juridiques survenant après la publication. De même, le site ne peut être tenue responsable de l’utilisation et de l’interprétation de l’information contenue dans ce site.

+

L\'Utilisateur s\'assure de garder son mot de passe secret. Toute divulgation du mot de passe, quelle que soit sa forme, est interdite. Il assume les risques liés à l\'utilisation de son identifiant et mot de passe. Le site décline toute responsabilité.

+

Le site www.site.com ne peut être tenu pour responsable d’éventuels virus qui pourraient infecter l’ordinateur ou tout matériel informatique de l’Internaute, suite à une utilisation, à l’accès, ou au téléchargement provenant de ce site.

+

La responsabilité du site ne peut être engagée en cas de force majeure ou du fait imprévisible et insurmontable d\'un tiers.

+

ARTICLE 6 : Liens hypertextes

+

Des liens hypertextes peuvent être présents sur le site. L’Utilisateur est informé qu’en cliquant sur ces liens, il sortira du site www.site.com. Ce dernier n’a pas de contrôle sur les pages web sur lesquelles aboutissent ces liens et ne saurait, en aucun cas, être responsable de leur contenu.

+

ARTICLE 7 : Cookies

+

L’Utilisateur est informé que lors de ses visites sur le site, un cookie peut s’installer automatiquement sur son logiciel de navigation.

+

Les cookies sont de petits fichiers stockés temporairement sur le disque dur de l’ordinateur de l’Utilisateur par votre navigateur et qui sont nécessaires à l’utilisation du site www.site.com. Les cookies ne contiennent pas d’information personnelle et ne peuvent pas être utilisés pour identifier quelqu’un. Un cookie contient un identifiant unique, généré aléatoirement et donc anonyme. Certains cookies expirent à la fin de la visite de l’Utilisateur, d’autres restent.

+

L’information contenue dans les cookies est utilisée pour améliorer le site www.site.com.

+

En naviguant sur le site, L’Utilisateur les accepte.

+

L’Utilisateur doit toutefois donner son consentement quant à l’utilisation de certains cookies.

+

A défaut d’acceptation, l’Utilisateur est informé que certaines fonctionnalités ou pages risquent de lui être refusées.

+

L’Utilisateur pourra désactiver ces cookies par l’intermédiaire des paramètres figurant au sein de son logiciel de navigation.

+

ARTICLE 8 : Droit applicable et juridiction compétente

+

La législation française s\'applique au présent contrat. En cas d\'absence de résolution amiable d\'un litige né entre les parties, les tribunaux français seront seuls compétents pour en connaître.

+

Pour toute question relative à l’application des présentes CGU, vous pouvez joindre l’éditeur aux coordonnées inscrites à l’ARTICLE 1.

' + ], + 'erreur302' => [ + 'content' =>'

Notre site est actuellement en maintenance. Nous sommes désolés pour la gêne occasionnée et faisons notre possible pour être rapidement de retour.

+ ' + ], + 'erreur403' => [ + 'content' => '

Vous n\'êtes pas autorisé à accéder à cette page...

Personnalisez cette page à votre convenance sans qu\'elle apparaisse dans les menus.

' + ], + 'erreur404' => [ + 'content' => '

Oups ! La page demandée est introuvable...

Personnalisez cette page à votre convenance sans qu\'elle apparaisse dans les menus.

' + ], + 'recherche' => [ + 'content' => '

Rechercher dans le site

' + ] + ]; +} diff --git a/core/module/install/ressource/files.tar.gz b/core/module/install/ressource/files.tar.gz new file mode 100644 index 0000000..753ba96 Binary files /dev/null and b/core/module/install/ressource/files.tar.gz differ diff --git a/core/module/install/ressource/themes/theme_default.zip b/core/module/install/ressource/themes/theme_default.zip new file mode 100644 index 0000000..ee7c330 Binary files /dev/null and b/core/module/install/ressource/themes/theme_default.zip differ diff --git a/core/module/install/ressource/themes/theme_hirondelles_960px.zip b/core/module/install/ressource/themes/theme_hirondelles_960px.zip new file mode 100644 index 0000000..c7d82f8 Binary files /dev/null and b/core/module/install/ressource/themes/theme_hirondelles_960px.zip differ diff --git a/core/module/install/ressource/themes/theme_montagne_960px.zip b/core/module/install/ressource/themes/theme_montagne_960px.zip new file mode 100644 index 0000000..db44f63 Binary files /dev/null and b/core/module/install/ressource/themes/theme_montagne_960px.zip differ diff --git a/core/module/install/ressource/themes/theme_sansbanniere_fluide.zip b/core/module/install/ressource/themes/theme_sansbanniere_fluide.zip new file mode 100644 index 0000000..7b57aa9 Binary files /dev/null and b/core/module/install/ressource/themes/theme_sansbanniere_fluide.zip differ diff --git a/core/module/install/ressource/themes/themes.json b/core/module/install/ressource/themes/themes.json new file mode 100644 index 0000000..14e9a12 --- /dev/null +++ b/core/module/install/ressource/themes/themes.json @@ -0,0 +1,18 @@ +{ + "defaut" : { + "name": "Le thème par défaut, ambiance bleue, largeur 100%", + "filename": "" + }, + "montagne": { + "name": "Thème avec ambiance montagne, largeur 960px", + "filename": "theme_montagne_960px.zip" + }, + "hirondelles": { + "name": "Thème avec ambiance bleue, largeur 960px", + "filename": "theme_hirondelles_960px.zip" + }, + "fluide": { + "name": "Thème sans bannière avec ambiance grise, largeur 100%", + "filename": "theme_sansbanniere_fluide.zip" + } +} \ No newline at end of file diff --git a/core/module/install/view/index/index.css b/core/module/install/view/index/index.css new file mode 100644 index 0000000..d7c543b --- /dev/null +++ b/core/module/install/view/index/index.css @@ -0,0 +1 @@ +/* Vide */ \ No newline at end of file diff --git a/core/module/install/view/index/index.js.php b/core/module/install/view/index/index.js.php new file mode 100644 index 0000000..c96e6b0 --- /dev/null +++ b/core/module/install/view/index/index.js.php @@ -0,0 +1,40 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Affichage de l'id en simulant FILTER_ID + */ +$("#installId").on("change keydown keyup", function(event) { + var userId = $(this).val(); + if( + event.keyCode !== 8 // BACKSPACE + && event.keyCode !== 37 // LEFT + && event.keyCode !== 39 // RIGHT + && event.keyCode !== 46 // DELETE + && window.getSelection().toString() !== userId // Texte sélectionné + ) { + var searchReplace = { + "á": "a", "à": "a", "â": "a", "ä": "a", "ã": "a", "å": "a", "ç": "c", "é": "e", "è": "e", "ê": "e", "ë": "e", "í": "i", "ì": "i", "î": "i", "ï": "i", "ñ": "n", "ó": "o", "ò": "o", "ô": "o", "ö": "o", "õ": "o", "ú": "u", "ù": "u", "û": "u", "ü": "u", "ý": "y", "ÿ": "y", + "Á": "A", "À": "A", "Â": "A", "Ä": "A", "Ã": "A", "Å": "A", "Ç": "C", "É": "E", "È": "E", "Ê": "E", "Ë": "E", "Í": "I", "Ì": "I", "Î": "I", "Ï": "I", "Ñ": "N", "Ó": "O", "Ò": "O", "Ô": "O", "Ö": "O", "Õ": "O", "Ú": "U", "Ù": "U", "Û": "U", "Ü": "U", "Ý": "Y", "Ÿ": "Y", + "'": "-", "\"": "-", " ": "-" + }; + userId = userId.replace(/[áàâäãåçéèêëíìîïñóòôöõúùûüýÿ'" ]/ig, function(match) { + return searchReplace[match]; + }); + userId = userId.replace(/[^a-z0-9-]/ig, ""); + $(this).val(userId); + } +}); \ No newline at end of file diff --git a/core/module/install/view/index/index.php b/core/module/install/view/index/index.php new file mode 100644 index 0000000..2fc1721 --- /dev/null +++ b/core/module/install/view/index/index.php @@ -0,0 +1,61 @@ +

Renseignez les champs ci-dessous pour finaliser l'installation.

+ + 'off', + 'label' => 'Identifiant' + ]); ?> +
+
+ 'off', + 'label' => 'Mot de passe' + ]); ?> +
+
+ 'off', + 'label' => 'Confirmation' + ]); ?> +
+
+ 'off', + 'label' => 'Adresse mail' + ]); ?> +
+
+ 'off', + 'label' => 'Prénom' + ]); ?> +
+
+ 'off', + 'label' => 'Nom' + ]); ?> +
+
+
+
+ 'Thème' + ]); ?> +
+
+
+
+ false + ]); + ?> +
+
+
+
+ 'Installer' + ]); ?> +
+
+ \ No newline at end of file diff --git a/core/module/install/view/update/update.css b/core/module/install/view/update/update.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/core/module/install/view/update/update.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/core/module/install/view/update/update.js.php b/core/module/install/view/update/update.js.php new file mode 100644 index 0000000..667764e --- /dev/null +++ b/core/module/install/view/update/update.js.php @@ -0,0 +1,63 @@ +/** + * Exécution des différentes étapes de mise à jour + */ +function step(i, data) { + // Affiche le texte de progression + $(".installUpdateProgressText").hide(); + $(".installUpdateProgressText[data-id=" + i + "]").show(); + // Requête ajax + $.ajax({ + type: "POST", + url: "?install/steps", // Ignore la réécriture d'URL + data: { + step: i, + data: data + }, + // Succès de la requête + success: function(result) { + setTimeout(function() { + // Succès + if(result.success === true) { + // Fin de la mise à jour + if(i === 4) { + // Affiche le message de succès + $("#installUpdateSuccess").show(); + // Déverrouille le bouton "Terminer" + $("#installUpdateEnd").removeClass("disabled"); + // Cache le texte de progression + $("#installUpdateProgress").hide(); + } + // Prochaine étape + else { + step(i + 1, result.data); + } + } + // Échec + else { + // Affiche le message d'erreur + $("#installUpdateErrorStep").text(i); + $("#installUpdateError").show(); + // Déverrouille le bouton "Terminer" + $("#installUpdateEnd").removeClass("disabled"); + // Cache le texte de progression + $("#installUpdateProgress").hide(); + // Affiche le résultat dans la console + console.error(result); + } + }, 2000); + }, + // Échec de la requête + error: function(xhr) { + // Affiche le message d'erreur + $("#installUpdateErrorStep").text(0); + $("#installUpdateError").show(); + // Déverrouille le bouton "Terminer" + $("#installUpdateEnd").removeClass("disabled"); + // Cache le texte de progression + $("#installUpdateProgress").hide(); + // Affiche l'erreur dans la console + console.error(xhr); + } + }); +} +$(window).on("load", step(1, null)); \ No newline at end of file diff --git a/core/module/install/view/update/update.php b/core/module/install/view/update/update.php new file mode 100644 index 0000000..53ababf --- /dev/null +++ b/core/module/install/view/update/update.php @@ -0,0 +1,29 @@ +

Mise à jour de Deltacms vers Deltacms .

+

Afin d'assurer le bon fonctionnement de Deltacms, veuillez ne pas fermer cette page avant la fin de l'opération.

+
+
+
+ + 1/4 : Préparation... + 2/4 : Téléchargement... + 3/4 : Installation... + 4/4 : Configuration... +
+
+ + Une erreur est survenue lors de l'étape . +
+
+ + Mise à jour terminée avec succès. +
+
+
+ 'Terminer', + 'href' => helper::baseUrl() . 'config', + 'ico' => 'check', + 'class' => 'disabled' + ]); ?> +
+
\ No newline at end of file diff --git a/core/module/maintenance/maintenance.php b/core/module/maintenance/maintenance.php new file mode 100644 index 0000000..e3d5f5d --- /dev/null +++ b/core/module/maintenance/maintenance.php @@ -0,0 +1,56 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class maintenance extends common { + + public static $actions = [ + 'index' => self::GROUP_VISITOR + ]; + + /** + * Maintenance + */ + public function index() { + // Redirection vers l'accueil après rafraîchissement et que la maintenance est terminée. + if($this->getData(['config', 'maintenance']) == False){ + header('Location:' . helper::baseUrl()); + exit(); + } + // Page perso définie et existante + if ($this->getData(['locale','page302']) !== 'none' + AND $this->getData(['page',$this->getData(['locale','page302'])]) ) { + $this->addOutput([ + 'display' => self::DISPLAY_LAYOUT_LIGHT, + 'title' => $this->getData(['page',$this->getData(['locale','page302']),'hideTitle']) + ? '' + : $this->getData(['page',$this->getData(['locale','page302']),'title']), + //'content' => $this->getdata(['page',$this->getData(['locale','page302']),'content']), + 'content' => $this->getPage($this->getData(['locale','page302']), self::$i18n), + 'view' => 'index' + ]); + } else { + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_LAYOUT_LIGHT, + 'title' => 'Maintenance en cours...', + 'view' => 'index' + ]); + } + } + +} \ No newline at end of file diff --git a/core/module/maintenance/view/index/index.css b/core/module/maintenance/view/index/index.css new file mode 100644 index 0000000..bb166ea --- /dev/null +++ b/core/module/maintenance/view/index/index.css @@ -0,0 +1 @@ +/* vide */ \ No newline at end of file diff --git a/core/module/maintenance/view/index/index.php b/core/module/maintenance/view/index/index.php new file mode 100644 index 0000000..de41231 --- /dev/null +++ b/core/module/maintenance/view/index/index.php @@ -0,0 +1,10 @@ +

Notre site est actuellement en maintenance. Nous sommes désolés pour la gêne occasionnée et faisons notre possible pour être rapidement de retour.

+
+
+ 'Connexion', + 'href' => helper::baseUrl() . 'user/login', + 'ico' => 'lock' + ]); ?> +
+
\ No newline at end of file diff --git a/core/module/page/page.php b/core/module/page/page.php new file mode 100644 index 0000000..fdf4280 --- /dev/null +++ b/core/module/page/page.php @@ -0,0 +1,514 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class page extends common { + + public static $actions = [ + 'add' => self::GROUP_MODERATOR, + 'delete' => self::GROUP_MODERATOR, + 'edit' => self::GROUP_MODERATOR, + 'duplicate' => self::GROUP_MODERATOR + ]; + public static $pagesNoParentId = [ + '' => 'Aucune' + ]; + public static $pagesBarId = [ + '' => 'Aucune' + ]; + public static $moduleIds = []; + + public static $typeMenu = [ + 'text' => 'Texte', + 'icon' => 'Icône', + 'icontitle' => 'Icône avec bulle de texte' + ]; + // Position du module + public static $modulePosition = [ + 'bottom' => 'En bas', + 'top' => 'En haut', + 'free' => 'Libre' + ]; + public static $pageBlocks = [ + '12' => 'Page standard', + '4-8' => 'Barre 1/3 - page 2/3', + '8-4' => 'Page 2/3 - barre 1/3', + '3-9' => 'Barre 1/4 - page 3/4', + '9-3' => 'Page 3/4 - barre 1/4', + '3-6-3' => 'Barre 1/4 - page 1/2 - barre 1/4', + '2-7-3' => 'Barre 2/12 - page 7/12 - barre 3/12 ', + '3-7-2' => 'Barre 3/12 - page 7/12 - barre 2/12 ', + 'bar' => 'Barre latérale' + ]; + public static $displayMenu = [ + 'none' => 'Aucun', + 'parents' => 'Le menu', + 'children' => 'Le sous-menu de la page parente' + ]; + + + /** + * Duplication + */ + public function duplicate() { + // Adresse sans le token + $url = explode('&',$this->getUrl(2)); + // La page n'existe pas + if($this->getData(['page', $url[0]]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } // Jeton incorrect + elseif(!isset($_GET['csrf'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Jeton invalide' + ]); + } + elseif ($_GET['csrf'] !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Suppression non autorisée' + ]); + } + // Duplication de la page + $pageTitle = $this->getData(['page',$url[0],'title']); + $pageId = helper::increment(helper::filter($pageTitle, helper::FILTER_ID), $this->getData(['page'])); + $pageId = helper::increment($pageId, self::$coreModuleIds); + $pageId = helper::increment($pageId, self::$moduleIds); + $data = $this->getData([ + 'page', + $url[0] + ]); + // Ecriture + $this->setData (['page',$pageId,$data]); + $notification = 'La page a été dupliquée'; + // Duplication du module présent + if ($this->getData(['page',$url[0],'moduleId'])) { + $data = $this->getData([ + 'module', + $url[0] + ]); + // Ecriture + $this->setData (['module',$pageId,$data]); + $notification = 'La page et son module ont été dupliqués'; + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $pageId, + 'notification' => $notification, + 'state' => true + ]); + } + + + /** + * Création + */ + public function add() { + $pageTitle = 'Nouvelle page'; + $pageId = helper::increment(helper::filter($pageTitle, helper::FILTER_ID), $this->getData(['page'])); + $this->setData([ + 'page', + $pageId, + [ + 'typeMenu' => 'text', + 'iconUrl' => '', + 'disable' => false, + 'content' => $pageId . '.html', + 'hideTitle' => false, + 'breadCrumb' => false, + 'metaDescription' => '', + 'metaTitle' => '', + 'moduleId' => '', + 'parentPageId' => '', + 'modulePosition' => 'bottom', + 'position' => 0, + 'group' => self::GROUP_VISITOR, + 'targetBlank' => false, + 'title' => $pageTitle, + 'shortTitle' => $pageTitle, + 'block' => '12', + 'barLeft' => '', + 'barRight' => '', + 'displayMenu' => '0', + 'hideMenuSide' => false, + 'hideMenuHead' => false, + 'hideMenuChildren' => false + ] + ]); + // Creation du contenu de la page + if (!is_dir(self::DATA_DIR . self::$i18n . '/content')) { + mkdir(self::DATA_DIR . self::$i18n . '/content', 0755); + } + //file_put_contents(self::DATA_DIR . self::$i18n . '/content/' . $pageId . '.html', '

Contenu de votre nouvelle page.

'); + $this->setPage($pageId, '

Contenu de votre nouvelle page.

', self::$i18n); + // Met à jour le site map + $this->createSitemap('all'); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $pageId, + 'notification' => 'Nouvelle page créée', + 'state' => true + ]); + } + + /** + * Suppression + */ + public function delete() { + // $url prend l'adresse sans le token + $url = explode('&',$this->getUrl(2)); + // La page n'existe pas + if($this->getData(['page', $url[0]]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } // Jeton incorrect + elseif(!isset($_GET['csrf'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Jeton invalide' + ]); + } + elseif ($_GET['csrf'] !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Suppression non autorisée' + ]); + } + // Impossible de supprimer la page d'accueil + elseif($url[0] === $this->getData(['locale', 'homePageId'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => 'Désactiver la page dans la configuration avant de la supprimer' + ]); + } + // Impossible de supprimer la page de recherche affectée + elseif($url[0] === $this->getData(['locale', 'searchPageId'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => 'Désactiver la page dans la configuration avant de la supprimer' + ]); + } + // Impossible de supprimer la page des mentions légales affectée + elseif($url[0] === $this->getData(['locale', 'legalPageId'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => 'Désactiver la page dans la configuration avant de la supprimer' + ]); + } + // Impossible de supprimer la page des mentions légales affectée + elseif($url[0] === $this->getData(['locale', 'page404'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => 'Désactiver la page dans la configuration avant de la supprimer' + ]); + } + // Impossible de supprimer la page des mentions légales affectée + elseif($url[0] === $this->getData(['locale', 'page403'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => 'Désactiver la page dans la configuration avant de la supprimer' + ]); + } + // Impossible de supprimer la page des mentions légales affectée + elseif($url[0] === $this->getData(['locale', 'page302'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'config', + 'notification' => 'Désactiver la page dans la configuration avant de la supprimer' + ]); + } + // Jeton incorrect + elseif(!isset($_GET['csrf'])) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Jeton invalide' + ]); + } + elseif ($_GET['csrf'] !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Suppression non autorisée' + ]); + } + // Impossible de supprimer une page contenant des enfants + elseif($this->getHierarchy($url[0],null)) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'page/edit/' . $url[0], + 'notification' => 'Impossible de supprimer une page contenant des enfants' + ]); + } + // Suppression + else { + + // Effacer le dossier du module + $moduleId = $this->getData(['page',$url[0],'moduleId']); + $modulesData = helper::getModules(); + if (is_dir($modulesData[$moduleId]['dataDirectory']. $url[0])) { + $this->removeDir( $modulesData[$moduleId]['dataDirectory']. $url[0] ); + } + // Effacer la page + $this->deleteData(['page', $url[0]]); + if (file_exists(self::DATA_DIR . self::$i18n . '/content/' . $url[0] . '.html')) { + unlink(self::DATA_DIR . self::$i18n . '/content/' . $url[0] . '.html'); + } + $this->deleteData(['module', $url[0]]); + // Met à jour le site map + $this->createSitemap('all'); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl(false), + 'notification' => 'Page supprimée', + 'state' => true + ]); + } + } + + + /** + * Édition + */ + public function edit() { + // La page n'existe pas + if($this->getData(['page', $this->getUrl(2)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // La page existe + else { + // Soumission du formulaire + if($this->isPost()) { + // Si le Title n'est pas vide, premier test pour positionner la notification du champ obligatoire + if( $this->getInput('pageEditTitle', helper::FILTER_ID, true) !== null && $this->getInput('pageEditTitle') !== '' ){ + // Génére l'ID si le titre de la page a changé + if ( $this->getInput('pageEditTitle') !== $this->getData(['page',$this->getUrl(2),'title']) ) { + $pageId = $this->getInput('pageEditTitle', helper::FILTER_ID, true); + } else { + $pageId = $this->getUrl(2); + } + // un dossier existe du même nom (erreur en cas de redirection) + if (file_exists($pageId)) { + $pageId = uniqid($pageId); + } + // Si l'id a changée + if ($pageId !== $this->getUrl(2)) { + // Incrémente le nouvel id de la page + $pageId = helper::increment($pageId, $this->getData(['page'])); + $pageId = helper::increment($pageId, self::$coreModuleIds); + $pageId = helper::increment($pageId, self::$moduleIds); + // Met à jour les enfants + foreach($this->getHierarchy($this->getUrl(2),null) as $childrenPageId) { + $this->setData(['page', $childrenPageId, 'parentPageId', $pageId]); + } + // Change l'id de page dans les données des modules + if ($this->getData(['module', $this->getUrl(2)]) !== null ) { + $this->setData(['module', $pageId, $this->getData(['module', $this->getUrl(2)])]); + $this->deleteData(['module', $this->getUrl(2)]); + // Renommer le dossier du module + $moduleId = $this->getData(['page',$this->getUrl(2),'moduleId']); + $modulesData = helper::getModules(); + if (is_dir($modulesData[$moduleId]['dataDirectory']. $this->getUrl(2))) { + // Placer la feuille de style dans un dossier au nom de la nouvelle instance + mkdir( $modulesData[$moduleId]['dataDirectory']. $pageId, 0755 ); + copy( $modulesData[$moduleId]['dataDirectory']. $this->getUrl(2), $modulesData[$moduleId]['dataDirectory']. $pageId); + $this->removeDir($modulesData[$moduleId]['dataDirectory']. $this->getUrl(2)); + // Mettre à jour le nom de la feuille de style + $this->setData(['module',$pageId,'theme','style', $modulesData[$moduleId]['dataDirectory']. $pageId]); + } + } + // Si la page correspond à la page d'accueil, change l'id dans la configuration du site + if($this->getData(['locale', 'homePageId']) === $this->getUrl(2)) { + $this->setData(['locale', 'homePageId', $pageId]); + } + } + // Supprime les données du module en cas de changement de module + if($this->getInput('pageEditModuleId') !== $this->getData(['page', $this->getUrl(2), 'moduleId'])) { + $this->deleteData(['module', $pageId]); + } + // Supprime l'ancienne page si l'id a changée + if($pageId !== $this->getUrl(2)) { + $this->deleteData(['page', $this->getUrl(2)]); + unlink (self::DATA_DIR . self::$i18n . '/content/' . $this->getUrl(2) . '.html'); + } + // Traitement des pages spéciales affectées dans la config : + if ($this->getUrl(2) === $this->getData(['locale', 'legalPageId']) ) { + $this->setData(['locale','legalPageId', $pageId]); + } + if ($this->getUrl(2) === $this->getData(['locale', 'searchPageId']) ) { + $this->setData(['locale','searchPageId', $pageId]); + } + if ($this->getUrl(2) === $this->getData(['locale', 'page404']) ) { + $this->setData(['locale','page404', $pageId]); + } + if ($this->getUrl(2) === $this->getData(['locale', 'page403']) ) { + $this->setData(['locale','page403', $pageId]); + } + if ($this->getUrl(2) === $this->getData(['locale', 'page302']) ) { + $this->setData(['locale','page302', $pageId]); + } + // Si la page est une page enfant, actualise les positions des autres enfants du parent, sinon actualise les pages sans parents + $lastPosition = 1; + $hierarchy = $this->getInput('pageEditParentPageId') ? $this->getHierarchy($this->getInput('pageEditParentPageId')) : array_keys($this->getHierarchy()); + $position = $this->getInput('pageEditPosition', helper::FILTER_INT); + foreach($hierarchy as $hierarchyPageId) { + // Ignore la page en cours de modification + if($hierarchyPageId === $this->getUrl(2)) { + continue; + } + // Incrémente de +1 pour laisser la place à la position de la page en cours de modification + if($lastPosition === $position) { + $lastPosition++; + } + // Change la position + $this->setData(['page', $hierarchyPageId, 'position', $lastPosition]); + // Incrémente pour la prochaine position + $lastPosition++; + } + if ($this->getinput('pageEditBlock') !== 'bar') { + $barLeft = $this->getinput('pageEditBarLeft'); + $barRight = $this->getinput('pageEditBarRight'); + $hideTitle = $this->getInput('pageEditHideTitle', helper::FILTER_BOOLEAN); + + } else { + // Une barre ne peut pas avoir de barres + $barLeft = ""; + $barRight = ""; + // Une barre est masquée + $position = 0; + $hideTitle = true; + } + // Modifie la page ou en crée une nouvelle si l'id a changé + $this->setData([ + 'page', + $pageId, + [ + 'typeMenu' => $this->getinput('pageTypeMenu'), + 'iconUrl' => $this->getinput('pageIconUrl'), + 'disable'=> $this->getinput('pageEditDisable', helper::FILTER_BOOLEAN), + 'content' => $pageId . '.html', + 'hideTitle' => $hideTitle, + 'breadCrumb' => $this->getInput('pageEditbreadCrumb', helper::FILTER_BOOLEAN), + 'metaDescription' => $this->getInput('pageEditMetaDescription', helper::FILTER_STRING_LONG), + 'metaTitle' => $this->getInput('pageEditMetaTitle'), + 'moduleId' => $this->getInput('pageEditModuleId'), + 'modulePosition' => $this->getInput('configModulePosition'), + 'parentPageId' => $this->getInput('pageEditParentPageId'), + 'position' => $position, + 'group' => $this->getinput('pageEditBlock') !== 'bar' ? $this->getInput('pageEditGroup', helper::FILTER_INT) : 0, + 'targetBlank' => $this->getInput('pageEditTargetBlank', helper::FILTER_BOOLEAN), + 'title' => $this->getInput('pageEditTitle', helper::FILTER_STRING_SHORT), + 'shortTitle' => $this->getInput('pageEditShortTitle', helper::FILTER_STRING_SHORT, true), + 'block' => $this->getinput('pageEditBlock'), + 'barLeft' => $barLeft, + 'barRight' => $barRight, + 'displayMenu' => $this->getinput('pageEditDisplayMenu'), + 'hideMenuSide' => $this->getinput('pageEditHideMenuSide', helper::FILTER_BOOLEAN), + 'hideMenuHead' => $this->getinput('pageEditHideMenuHead', helper::FILTER_BOOLEAN), + 'hideMenuChildren' => $this->getinput('pageEditHideMenuChildren', helper::FILTER_BOOLEAN), + ] + ]); + // Creation du contenu de la page + if (!is_dir(self::DATA_DIR . self::$i18n . '/content')) { + mkdir(self::DATA_DIR . self::$i18n . '/content', 0755); + } + $content = empty($this->getInput('pageEditContent', null)) ? '

' : str_replace('

', '

 

', $this->getInput('pageEditContent', null)); + //file_put_contents( self::DATA_DIR . self::$i18n . '/content/' . $pageId . '.html' , $content ); + $this->setPage($pageId , $content, self::$i18n); + // Barre renommée : changement le nom de la barre dans les pages mères + if ($this->getinput('pageEditBlock') === 'bar') { + foreach ($this->getHierarchy() as $eachPageId=>$parentId) { + if ($this->getData(['page',$eachPageId,'barRight']) === $this->getUrl(2)) { + $this->setData(['page',$eachPageId,'barRight',$pageId]); + } + if ($this->getData(['page',$eachPageId,'barLeft']) === $this->getUrl(2)) { + $this->setData(['page',$eachPageId,'barLeft',$pageId]); + } + foreach ($parentId as $childId) { + if ($this->getData(['page',$childId,'barRight']) === $this->getUrl(2)) { + $this->setData(['page',$childId,'barRight',$pageId]); + } + if ($this->getData(['page',$childId,'barLeft']) === $this->getUrl(2)) { + $this->setData(['page',$childId,'barLeft',$pageId]); + } + } + } + } + // Met à jour le site map + $this->createSitemap('all'); + // Redirection vers la configuration + if($this->getInput('pageEditModuleRedirect', helper::FILTER_BOOLEAN)) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $pageId . '/config', + 'state' => true + ]); + } + // Redirection vers la page + else { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $pageId, + 'notification' => 'Modifications enregistrées', + 'state' => true + ]); + } + } + } + self::$moduleIds = array_merge( ['' => 'Aucun'] , helper::arrayCollumn(helper::getModules(),'realName','SORT_ASC')); // Pages sans parent + foreach($this->getHierarchy() as $parentPageId => $childrenPageIds) { + if($parentPageId !== $this->getUrl(2)) { + self::$pagesNoParentId[$parentPageId] = $this->getData(['page', $parentPageId, 'title']); + } + } + // Pages barre latérales + foreach($this->getHierarchy(null,false,true) as $parentPageId => $childrenPageIds) { + if($parentPageId !== $this->getUrl(2) && + $this->getData(['page', $parentPageId, 'block']) === 'bar') { + self::$pagesBarId[$parentPageId] = $this->getData(['page', $parentPageId, 'title']); + } + } + // Mise à jour de la liste des pages pour TinyMCE + $this->pages2Json(); + // Valeurs en sortie + $this->addOutput([ + 'title' => $this->getData(['page', $this->getUrl(2), 'title']), + 'vendor' => [ + 'tinymce' + ], + 'view' => 'edit' + ]); + } + } +} diff --git a/core/module/page/view/edit/edit.css b/core/module/page/view/edit/edit.css new file mode 100644 index 0000000..0797b4e --- /dev/null +++ b/core/module/page/view/edit/edit.css @@ -0,0 +1,19 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** NE PAS EFFACER +* admin.css +*/ diff --git a/core/module/page/view/edit/edit.js.php b/core/module/page/view/edit/edit.js.php new file mode 100644 index 0000000..4807a72 --- /dev/null +++ b/core/module/page/view/edit/edit.js.php @@ -0,0 +1,590 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** + * Confirmation de suppression + */ +$("#pageEditDelete").on("click", function() { + var _this = $(this); + return core.confirm("Confirmez-vous la suppression de cette page ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); + +$("#pageEditModuleId").on("click", function() { + protectModule(); +}); + +function protectModule() { + var oldModule = $("#pageEditModuleIdOld").val(); + var oldModuleText = $("#pageEditModuleIdOldText").val(); + var newModule = $("#pageEditModuleId").val(); + if ( oldModule !== "" && + oldModule !== newModule) { + var _this = $(this); + core.confirm("Les données du module " + oldModuleText + " seront effacées. Confirmez-vous ?", + function() { + $(location).attr("href", _this.attr("href")); + return true; + }, + function() { + $("#pageEditModuleId").val(oldModule); + return false; + } + ); + } +} + +/** +* Paramètres par défaut au chargement +*/ +$( document ).ready(function() { + + /* + * Enleve le menu fixe en édition de page + */ + $("nav").removeAttr('id'); + + /** + * Bloque/Débloque le bouton de configuration au changement de module + * Affiche ou masque la position du module selon le call_user_func + */ + if($("#pageEditModuleId").val() === "") { + $("#pageEditModuleConfig").addClass("disabled"); + $("#pageEditContentContainer").hide(); + } + else { + $("#pageEditModuleConfig").removeClass("disabled"); + $("#pageEditContentContainer").hide(); + $("#pageEditBlock option[value='bar']").remove(); + } + + /** + * Masquer et affiche la sélection de position du module + */ + if( $("#pageEditModuleId").val() === "redirection" || + $("#pageEditModuleId").val() === "" ) { + $("#configModulePositionWrapper").removeClass("disabled"); + $("#configModulePositionWrapper").slideUp(); + } + else { + $("#configModulePositionWrapper").addClass("disabled"); + $("#configModulePositionWrapper").slideDown(); + } + + + /** + * Masquer et démasquer le contenu pour les modules code et redirection + */ + if( $("#pageEditModuleId").val() === "redirection") { + $("#pageEditContentWrapper").removeClass("disabled"); + $("#pageEditContentWrapper").slideUp(); + } else { + $("#pageEditContentWrapper").addClass("disabled"); + $("#pageEditContentWrapper").slideDown(); + } + /** + * Masquer et démasquer le masquage du titre pour le module redirection + */ + if( $("#pageEditModuleId").val() === "redirection" ) { + $("#pageEditHideTitleWrapper").removeClass("disabled"); + $("#pageEditHideTitleWrapper").hide(); + $("#pageEditBlockLayout").removeClass("disabled"); + $("#pageEditBlockLayout").hide(); + + } else { + $("#pageEditHideTitleWrapper").addClass("disabled"); + $("#pageEditHideTitleWrapper").show(); + $("#pageEditBlockLayout").addClass("disabled"); + $("#pageEditBlockLayout").show(); + } + /** + * Masquer et démasquer la sélection des barres + */ + switch ($("#pageEditBlock").val()) { + case "bar": + case "12": + $("#pageEditBarLeftWrapper").removeClass("disabled"); + $("#pageEditBarLeftWrapper").slideUp(); + $("#pageEditBarRightWrapper").removeClass("disabled"); + $("#pageEditBarRightWrapper").slideUp(); + break; + case "3-9": + case "4-8": + $("#pageEditBarLeftWrapper").addClass("disabled"); + $("#pageEditBarLeftWrapper").slideDown(); + $("#pageEditBarRightWrapper").removeClass("disabled"); + $("#pageEditBarRightWrapper").slideUp(); + break; + case "9-3": + case "8-4": + $("#pageEditBarLeftWrapper").removeClass("disabled"); + $("#pageEditBarLeftWrapper").slideUp(); + $("#pageEditBarRightWrapper").addClass("disabled"); + $("#pageEditBarRightWrapper").slideDown(); + break; + case "3-6-3": + case "2-7-3": + case "3-7-2": + $("#pageEditBarLeftWrapper").addClass("disabled"); + $("#pageEditBarLeftWrapper").slideDown(); + $("#pageEditBarRightWrapper").addClass("disabled"); + $("#pageEditBarRightWrapper").slideDown(); + break; + }; + if ($("#pageEditBlock").val() === "bar") { + $("#pageEditMenu").removeClass("disabled"); + $("#pageEditMenu").hide(); + $("#pageEditHideTitleWrapper").removeClass("disabled"); + $("#pageEditHideTitleWrapper").slideUp(); + $("#pageEditbreadCrumbWrapper").removeClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideUp(); + $("#pageEditModuleIdWrapper").removeClass("disabled"); + $("#pageEditModuleIdWrapper").slideUp(); + $("#pageEditModuleConfig").removeClass("disabled"); + $("#pageEditModuleConfig").slideUp(); + $("#pageEditDisplayMenuWrapper").addClass("disabled"); + $("#pageEditDisplayMenuWrapper").slideDown(); + $("#pageTypeMenuWrapper").removeClass("disabled"); + $("#pageTypeMenuWrapper").slideUp(); + $("#pageEditSeoWrapper").removeClass("disabled"); + $("#pageEditSeoWrapper").slideUp(); + $("#pageEditAdvancedWrapper").removeClass("disabled"); + $("#pageEditAdvancedWrapper").slideUp(); + /* + $("#pageEditBlockLayout").removeClass("col6"); + $("#pageEditBlockLayout").addClass("col12"); + */ + + } else { + $("#pageEditDisplayMenuWrapper").removeClass("disabled"); + $("#pageEditDisplayMenuWrapper").slideUp(); + } + + /** + * Masquer ou afficher le chemin de fer + * Quand le titre est masqué + */ + if ($("input[name=pageEditHideTitle]").is(':checked') || + $("#pageEditParentPageId").val() === "" ) { + + $("#pageEditbreadCrumbWrapper").removeClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideUp(); + } else { + if ($("#pageEditParentPageId").val() !== "") { + $("#pageEditbreadCrumbWrapper").addClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideDown(); + } + } + + /** + * Masquer ou afficher la sélection de l'icône + */ + if ($("#pageTypeMenu").val() !== "text") { + $("#pageIconUrlWrapper").addClass("disabled"); + $("#pageIconUrlWrapper").slideDown(); + } else { + $("#pageIconUrlWrapper").removeClass("disabled"); + $("#pageIconUrlWrapper").slideUp(); + } + + /** + * Cache les options de masquage dans les menus quand la page n'est pas affichée. + */ + if ($("#pageEditPosition").val() === "0" ) { + $("#pageEditHideMenuSideWrapper").removeClass("disabled"); + $("#pageEditHideMenuSideWrapper").slideUp(); + } else { + $("#pageEditHideMenuSideWrapper").addClass("disabled"); + $("#pageEditHideMenuSideWrapper").slideDown(); + } + + /** + * Cache l'option de masquage des pages enfants + */ + if ($("#pageEditParentPageId").val() !== "") { + $("#pageEditHideMenuChildrenWrapper").removeClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideUp(); + } else { + $("#pageEditHideMenuChildrenWrapper").addClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideDown(); + } + + /** + * Cache le l'option "ne pas afficher les pages enfants dans le menu horizontal" lorsque la page est désactivée + */ + if ($("#pageEditDisable").is(':checked') ) { + $("#pageEditHideMenuChildrenWrapper").removeClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideUp(); + } else { + $("#pageEditHideMenuChildrenWrapper").addClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideDown(); + } + + // Animation des boutons zwiico-help lien vers la documentation + var colorButton = getData(['admin', 'backgroundColorButtonHelp'])."'"; ?> ; + var blockButton = getData(['admin', 'backgroundBlockColor'])."'"; ?> ; + $(".helpDisplayButton").mouseenter(function(){ + $(this).css("background-color", colorButton); + }); + $(".helpDisplayButton").mouseleave(function(){ + $(this).css("background-color", blockButton); + }); + + +}); + + + +/** + * Cache le l'option "ne pas afficher les pages enfants dans le menu horizontal" lorsque la page est désactivée + */ +var pageEditDisableDOM = $("#pageEditDisable"); +pageEditDisableDOM.on("change", function() { + if ($(this).is(':checked') ) { + $("#pageEditHideMenuChildrenWrapper").removeClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideUp(); + $("#pageEditHideMenuChildren").prop("checked", false); + } else { + $("#pageEditHideMenuChildrenWrapper").addClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideDown(); + } +}); + + +/** +* Cache les options de masquage dans les menus quand la page n'est pas affichée. +*/ +var pageEditPositionDOM = $("#pageEditPosition"); +pageEditPositionDOM.on("change", function() { + if ($(this).val() === "0" ) { + $("#pageEditHideMenuSideWrapper").removeClass("disabled"); + $("#pageEditHideMenuSideWrapper").slideUp(); + } else { + $("#pageEditHideMenuSideWrapper").addClass("disabled"); + $("#pageEditHideMenuSideWrapper").slideDown(); + } +}); + +/** + * Bloque/Débloque le bouton de configuration au changement de module + * Affiche ou masque la position du module selon le call_user_func + */ +var pageEditModuleIdDOM = $("#pageEditModuleId"); +pageEditModuleIdDOM.on("change", function() { + if($(this).val() === "") { + $("#pageEditModuleConfig").addClass("disabled"); + $("#pageEditContentContainer").slideDown(); + $("#pageEditBlock").append(''); + } + else { + $("#pageEditModuleConfig").removeClass("disabled"); + $("#pageEditContentContainer").slideUp(); + $("#pageEditBlock option[value='bar']").remove(); + } +}); + + + +/** + * Masquer et affiche la sélection de position du module + * + * */ +var pageEditModuleIdDOM = $("#pageEditModuleId"); +pageEditModuleIdDOM.on("change", function() { + if( $(this).val() === "redirection" || + $(this).val() === "") { + $("#configModulePositionWrapper").removeClass("disabled"); + $("#configModulePositionWrapper").slideUp(); + } + else { + $("#configModulePositionWrapper").addClass("disabled"); + $("#configModulePositionWrapper").slideDown(); + } +}); + + + + +/** + * Masquer et démasquer le contenu pour les modules code et redirection + */ +var pageEditModuleIdDOM = $("#pageEditModuleId"); +pageEditModuleIdDOM.on("change", function() { + if( $(this).val() === "redirection") { + $("#pageEditContentWrapper").removeClass("disabled"); + $("#pageEditContentWrapper").slideUp(); + } + else { + $("#pageEditContentWrapper").addClass("disabled"); + $("#pageEditContentWrapper").slideDown(); + } +}); + + + +/** + * Masquer et démasquer le masquage du titre pour le module redirection + */ +var pageEditModuleIdDOM = $("#pageEditModuleId"); +pageEditModuleIdDOM.on("change", function() { + if( $(this).val() === "redirection") { + $("#pageEditHideTitleWrapper").removeClass("disabled"); + $("#pageEditHideTitleWrapper").slideUp(); + $("#pageEditBlockLayout").removeClass("disabled"); + $("#pageEditBlockLayout").slideUp(); + } + else { + $("#pageEditHideTitleWrapper").addClass("disabled"); + $("#pageEditHideTitleWrapper").slideDown(); + $("#pageEditBlockLayout").addClass("disabled"); + $("#pageEditBlockLayout").slideDown(); + } +}); + + +/** + * Masquer et démasquer la sélection des barres + */ +var pageEditBlockDOM = $("#pageEditBlock"); +pageEditBlockDOM.on("change", function() { + switch ($(this).val()) { + case "bar": + case "12": + $("#pageEditBarLeftWrapper").removeClass("disabled"); + $("#pageEditBarLeftWrapper").slideUp(); + $("#pageEditBarRightWrapper").removeClass("disabled"); + $("#pageEditBarRightWrapper").slideUp(); + break; + case "3-9": + case "4-8": + $("#pageEditBarLeftWrapper").addClass("disabled"); + $("#pageEditBarLeftWrapper").slideDown(); + $("#pageEditBarRightWrapper").removeClass("disabled"); + $("#pageEditBarRightWrapper").slideUp(); + break; + case "9-3": + case "8-4": + $("#pageEditBarLeftWrapper").removeClass("disabled"); + $("#pageEditBarLeftWrapper").slideUp(); + $("#pageEditBarRightWrapper").addClass("disabled"); + $("#pageEditBarRightWrapper").slideDown(); + break; + case "3-6-3": + case "2-7-3": + case "3-7-2": + $("#pageEditBarLeftWrapper").addClass("disabled"); + $("#pageEditBarLeftWrapper").slideDown(); + $("#pageEditBarRightWrapper").addClass("disabled"); + $("#pageEditBarRightWrapper").slideDown(); + break; + } + if ($(this).val() === "bar") { + $("#pageEditMenu").removeClass("disabled"); + $("#pageEditMenu").hide(); + $("#pageEditHideTitleWrapper").removeClass("disabled"); + $("#pageEditHideTitleWrapper").slideUp(); + $("#pageTypeMenuWrapper").removeClass("disabled"); + $("#pageTypeMenuWrapper").slideUp(); + $("#pageEditSeoWrapper").removeClass("disabled"); + $("#pageEditSeoWrapper").slideUp(); + $("#pageEditAdvancedWrapper").removeClass("disabled"); + $("#pageEditAdvancedWrapper").slideUp(); + $("#pageEditbreadCrumbWrapper").removeClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideUp(); + $("#pageEditModuleIdWrapper").removeClass("disabled"); + $("#pageEditModuleIdWrapper").slideUp(); + $("#pageEditModuleConfig").removeClass("disabled"); + $("#pageEditModuleConfig").slideUp(); + $("#pageEditDisplayMenuWrapper").addClass("disabled"); + $("#pageEditDisplayMenuWrapper").slideDown(); + /* + $("#pageEditBlockLayout").removeClass("col6"); + $("#pageEditBlockLayout").addClass("col12"); + */ + } else { + $("#pageEditMenu").addClass("disabled"); + $("#pageEditMenu").show(); + $("#pageEditHideTitleWrapper").addClass("disabled"); + $("#pageEditHideTitleWrapper").slideDown(); + $("#pageTypeMenuWrapper").addClass("disabled"); + $("#pageTypeMenuWrapper").slideDown(); + $("#pageEditSeoWrapper").addClass("disabled"); + $("#pageEditSeoWrapper").slideDown(); + $("#pageEditAdvancedWrapper").addClass("disabled"); + $("#pageEditAdvancedWrapper").slideDown(); + $("#pageEditModuleIdWrapper").addClass("disabled"); + $("#pageEditModuleIdWrapper").slideDown(); + $("#pageEditModuleConfig").slideDown(); + $("#pageEditDisplayMenuWrapper").removeClass("disabled"); + $("#pageEditDisplayMenuWrapper").slideUp(); + if ($("#pageEditParentPageId").val() !== "") { + $("#pageEditbreadCrumbWrapper").addClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideDown(); + } + if ($("#pageEditModuleId").val() === "") { + $("#pageEditModuleConfig").addClass("disabled"); + } else { + $("#pageEditModuleConfig").removeClass("disabled"); + } + /* + $("#pageEditBlockLayout").removeClass("col12"); + $("#pageEditBlockLayout").addClass("col6"); + */ + } +}); + + + + +/** + * Masquer ou afficher le chemin de fer + * Quand le titre est masqué + */ +var pageEditHideTitleDOM = $("#pageEditHideTitle"); +pageEditHideTitleDOM.on("change", function() { + if ($("input[name=pageEditHideTitle]").is(':checked')) { + $("#pageEditbreadCrumbWrapper").removeClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideUp(); + } else { + if ($("#pageEditParentPageId").val() !== "") { + $("#pageEditbreadCrumbWrapper").addClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideDown(); + } + } +}); + + +/** + * Masquer ou afficher le chemin de fer + * Quand la page n'est pas mère et que le menu n'est pas masqué + */ +var pageEditParentPageIdDOM = $("#pageEditParentPageId"); +pageEditParentPageIdDOM.on("change", function() { + if ($(this).val() === "" && + !$('input[name=pageEditHideTitle]').is(':checked') ) { + $("#pageEditbreadCrumbWrapper").removeClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideUp(); + } else { + $("#pageEditbreadCrumbWrapper").addClass("disabled"); + $("#pageEditbreadCrumbWrapper").slideDown(); + + } + if ($(this).val() !== "") { + $("#pageEditHideMenuChildrenWrapper").removeClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideUp(); + } else { + $("#pageEditHideMenuChildrenWrapper").addClass("disabled"); + $("#pageEditHideMenuChildrenWrapper").slideDown(); + } +}); + + + +/** + * Masquer ou afficher la sélection de l'icône + */ +var pageTypeMenuDOM = $("#pageTypeMenu"); +pageTypeMenuDOM.on("change", function() { + if ($(this).val() !== "text") { + $("#pageIconUrlWrapper").addClass("disabled"); + $("#pageIconUrlWrapper").slideDown(); + } else { + $("#pageIconUrlWrapper").removeClass("disabled"); + $("#pageIconUrlWrapper").slideUp(); + } +}); + + + + +/** + * Soumission du formulaire pour éditer le module + */ +$("#pageEditModuleConfig").on("click", function() { + $("#pageEditModuleRedirect").val(1); + $("#pageEditForm").trigger("submit"); +}); + +/** + * Affiche les pages en fonction de la page parent dans le choix de la position + */ +var hierarchy = getHierarchy()); ?>; + +var pages = getData(['page'])); ?>; + + +// 9.0.07 corrige une mauvaise sélection d'une page orpheline avec enfant +var positionInitial = getData(['page',$this->getUrl(2),"position"]); ?>; +// 9.0.07 +$("#pageEditParentPageId").on("change", function() { + var positionDOM = $("#pageEditPosition"); + positionDOM.empty().append( + $("
"; diff --git a/core/module/theme/resource/custom.css b/core/module/theme/resource/custom.css new file mode 100644 index 0000000..d1d4a08 --- /dev/null +++ b/core/module/theme/resource/custom.css @@ -0,0 +1,224 @@ +/** + * Voici une feuille de style type, bien entendu vous pouvez ajouter + * ou supprimer des propriétés CSS en fonction de vos besoins. + */ + +/** + * Éléments principaux + */ + + +/* +* Grille du site +* Barres et page +*/ +#contentLeft { +} +#contentRight { +} +#contentSite { +} + +/* Bannière */ +header { +} + +/* Titre de la bannière */ +header span { +} + +/* Menu */ +nav { +} + +/* Items du menu */ +nav a { +} + +/* Items au survol du menu */ +nav a:hover { +} + +/* Item courant du menu */ +nav a.active { +} + +/* Menu latéral */ +/* aspect des puces */ +ul #menuSide { +} +/* Block menu à droite */ +#menuSideRight { +} +/* Block menu à gauche */ +#menuSideLeft { +} + + +/* Bas de page */ +footer { +} + +footer #footersite, #footerbody { +} + +/* Liens du bas de page */ +footer #footersite, #footerbody a { +} + +/* footer bloc gauche */ +footer #footersiteLeft, #footerbodyLeft { +} + +/* footer bloc central */ +footer #footersiteCenter, #footerbodyCenter { +} + +/* footer bloc droite */ +footer #footersiteRight, #footerbodyRight { +} + + + +/** + * Éléments de contenu + */ + +/* Titres */ +h1, +h2, +h3, +h4 { +} + +/* Liens */ +a { +} + +/* Liens au survol */ +a:hover { +} + +/* Liens au clic */ +a:active { +} + +/* Boutons */ +.button, +button[type='submit'], +.pagination a { +} + +/* Boutons au survol */ +.button:hover, +button[type='submit']:hover, +.pagination a:hover { +} + +/* Boutons au clic */ +.button:active, +button[type='submit']:active, +.pagination a:active { +} + +/* Cases à cocher */ +input[type='checkbox']:checked + label:before { +} + +/* Cases à cocher au survol */ +input[type='checkbox']:not(:active):checked:hover + label:before, +input[type='checkbox']:active + label:before { +} + +/* Champs de formulaire */ +input[type='text'], +input[type='password'], +.inputFile, +select, +textarea { +} + +/* Champs de formulaire au survol */ +input[type='text']:hover, +input[type='password']:hover, +.inputFile:hover, +select:hover, +textarea:hover { +} + +/* Modules News et Blog */ + +.blogDate { +} + +.blogPicture { +} + +.blogPicture img { +} + +.blogComment { +} + +.blogTitle { +} + +.blogContent { +} + +.newsTitle { + /*background-color: grey;*/ +} + +.newsContent { +} + +.newsSignature { + color: #404040; +} + +/* Consentement aux cookies */ + +/* + +#cookieConsent { + + width: 80%; + + color: #FFF; + + background: #212223; + + opacity: 0.9; + +} + +#cookieConsent a{ + + color : yellow; + +} + +#cookieConsent h3{ + + color : red; + +} + +#cookieConsentConfirm { + + background: rgba(0,0,255,1); + + color: rgba(255,255,255,1); + +} + +#cookieConsentConfirm:hover { + + background: rgba(0,50,255,1); + + color: rgba(255,255,255,1); + +} + +*/ diff --git a/core/module/theme/theme.php b/core/module/theme/theme.php new file mode 100644 index 0000000..0b81ffc --- /dev/null +++ b/core/module/theme/theme.php @@ -0,0 +1,906 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class theme extends common { + + public static $actions = [ + 'advanced' => self::GROUP_ADMIN, + 'body' => self::GROUP_ADMIN, + 'footer' => self::GROUP_ADMIN, + 'header' => self::GROUP_ADMIN, + 'index' => self::GROUP_ADMIN, + 'menu' => self::GROUP_ADMIN, + 'reset' => self::GROUP_ADMIN, + 'site' => self::GROUP_ADMIN, + 'admin' => self::GROUP_ADMIN, + 'manage' => self::GROUP_ADMIN, + 'export' => self::GROUP_ADMIN, + 'import' => self::GROUP_ADMIN, + 'save' => self::GROUP_ADMIN + ]; + public static $aligns = [ + 'left' => 'À gauche', + 'center' => 'Au centre', + 'right' => 'À droite' + ]; + public static $attachments = [ + 'scroll' => 'Standard', + 'fixed' => 'Fixe' + ]; + public static $fonts = [ + 'Abril+Fatface' => 'Abril Fatface', + 'Arimo' => 'Arimo', + 'Arvo' => 'Arvo', + 'Berkshire+Swash' => 'Berkshire Swash', + 'Cabin' => 'Cabin', + 'Dancing+Script' => 'Dancing Script', + 'Droid+Sans' => 'Droid Sans', + 'Droid+Serif' => 'Droid Serif', + 'Fira+Sans' => 'Fira Sans', + 'Inconsolata' => 'Inconsolata', + 'Indie+Flower' => 'Indie Flower', + 'Josefin+Slab' => 'Josefin Slab', + 'Lobster' => 'Lobster', + 'Lora' => 'Lora', + 'Lato' => 'Lato', + 'Marvel' => 'Marvel', + 'Old+Standard+TT' => 'Old Standard TT', + 'Open+Sans' => 'Open Sans', + 'Oswald' => 'Oswald', + 'PT+Mono' => 'PT Mono', + 'PT+Serif' => 'PT Serif', + 'Raleway' => 'Raleway', + 'Rancho' => 'Rancho', + 'Roboto' => 'Roboto', + 'Signika' => 'Signika', + 'Ubuntu' => 'Ubuntu', + 'Vollkorn' => 'Vollkorn' + ]; + public static $containerWides = [ + 'container' => 'Limitée au site', + 'none' => 'Etendue sur la page' + ]; + public static $footerblocks = [ + 1 => [ + 'hide' => 'Masqué', + 'center' => 'Affiché' ], + 2 => [ + 'hide' => 'Masqué', + 'left' => 'À gauche', + 'right' => 'À droite' ], + 3 => [ + 'hide' => 'Masqué', + 'left' => 'À gauche', + 'center' => 'Au centre', + 'right' => 'À droite' ], + 4 => [ + 'hide' => 'Masqué', + 'left' => 'En haut', + 'center' => 'Au milieu', + 'right' => 'En bas' ] + ]; + + public static $fontWeights = [ + 'normal' => 'Maigre', + 'bold' => 'Gras' + ]; + public static $footerHeights = [ + '0px' => 'Nulles (0px)', + '5px' => 'Très petites (5px)', + '10px' => 'Petites (10px)', + '15px' => 'Moyennes (15px)', + '20px' => 'Grandes (20px)' + ]; + public static $footerPositions = [ + 'hide' => 'Caché', + 'site' => 'Dans le site', + 'body' => 'En dessous du site' + ]; + public static $footerFontSizes = [ + '.8em' => 'Très petite (80%)', + '.9em' => 'Petite (90%)', + '1em' => 'Standard (100%)', + '1.1em' => 'Moyenne (110%)', + '1.2em' => 'Grande (120%)', + '1.3em' => 'Très grande (130%)' + ]; + public static $headerFontSizes = [ + '1.6em' => 'Très petite (160%)', + '1.8em' => 'Petite (180%)', + '2em' => 'Moyenne (200%)', + '2.2em' => 'Grande (220%)', + '2.4vmax' => 'Très grande (240%)' + ]; + public static $headerHeights = [ + 'unset' => 'Libre', // texte dynamique cf header.js.php + '100px' => 'Très petite (100px) ', + '150px' => 'Petite (150px)', + '200px' => 'Moyenne (200px)', + '300px' => 'Grande (300px)', + '400px' => 'Très grande (400px)', + ]; + public static $headerPositions = [ + 'body' => 'Au dessus du site', + 'site' => 'Dans le site', + 'hide' => 'Cachée' + ]; + public static $headerFeatures = [ + 'wallpaper' => 'Couleur unie ou papier-peint', + 'feature' => 'Contenu personnalisé' + ]; + public static $imagePositions = [ + 'top left' => 'En haut à gauche', + 'top center' => 'En haut au centre', + 'top right' => 'En haut à droite', + 'center left' => 'Au milieu à gauche', + 'center center' => 'Au milieu au centre', + 'center right' => 'Au milieu à droite', + 'bottom left' => 'En bas à gauche', + 'bottom center' => 'En bas au centre', + 'bottom right' => 'En bas à droite' + ]; + public static $menuFontSizes = [ + '.8em' => 'Très petite (80%)', + '.9em' => 'Petite (90%)', + '1em' => 'Standard (100%)', + '1.1em' => 'Moyenne (110%)', + '1.2em' => 'Grande (120%)', + '1.3em' => 'Très grande (130%)' + ]; + public static $menuHeights = [ + '5px 10px' => 'Très petite', + '10px' => 'Petite', + '15px 10px' => 'Moyenne', + '20px 15px' => 'Grande', + '25px 15px' => 'Très grande' + ]; + public static $menuPositionsSite = [ + 'top' => 'En-dehors du site', + 'site-first' => 'Avant la bannière', + 'site-second' => 'Après la bannière', + 'hide' => 'Caché' + ]; + public static $menuPositionsBody = [ + 'top' => 'En-dehors du site', + 'body-first' => 'Avant la bannière', + 'body-second' => 'Après la bannière', + 'site' => 'Dans le site', + 'hide' => 'Caché' + ]; + public static $menuRadius = [ + '0px' => 'Aucun', + '3px 3px 0px 0px' => 'Très léger', + '6px 6px 0px 0px' => 'Léger', + '9px 9px 0px 0px' => 'Moyen', + '12px 12px 0px 0px' => 'Important', + '15px 15px 0px 0px' => 'Très important' + ]; + public static $radius = [ + '0px' => 'Aucun', + '5px' => 'Très léger', + '10px' => 'Léger', + '15px' => 'Moyen', + '25px' => 'Important', + '50px' => 'Très important' + ]; + public static $repeats = [ + 'no-repeat' => 'Ne pas répéter', + 'repeat-x' => 'Sur l\'axe horizontal', + 'repeat-y' => 'Sur l\'axe vertical', + 'repeat' => 'Sur les deux axes' + ]; + public static $shadows = [ + '0px 0px 0px' => 'Aucune', + '1px 1px 5px' => 'Très légère', + '1px 1px 10px' => 'Légère', + '1px 1px 15px' => 'Moyenne', + '1px 1px 25px' => 'Importante', + '1px 1px 50px' => 'Très importante' + ]; + public static $blockShadows = [ + '0px 0px 0px' => 'Aucune', + '1px 1px 2px' => 'Très légère', + '2px 2px 4px' => 'Légère', + '3px 3px 6px' => 'Moyenne', + '5px 5px 10px' => 'Importante', + '10px 10px 20px' => 'Très importante' + ]; + public static $siteFontSizes = [ + '12px' => '12 pixels', + '13px' => '13 pixels', + '14px' => '14 pixels', + '15px' => '15 pixels', + '16px' => '16 pixels', + '18px' => '18 pixels', + '20px' => '20 pixels' + ]; + public static $bodySizes = [ + 'auto' => 'Automatique', + '100% 100%' => 'Image étirée (100% 100%)', + 'cover' => 'Responsive (cover)', + 'contain' => 'Responsive (contain)' + ]; + public static $textTransforms = [ + 'none' => 'Standard', + 'lowercase' => 'Minuscules', + 'uppercase' => 'Majuscules', + 'capitalize' => 'Majuscule à chaque mot' + ]; + public static $siteWidths = [ + '750px' => 'Petite (750 pixels)', + '960px' => 'Moyenne (960 pixels)', + '1170px' => 'Grande (1170 pixels)', + '100%' => 'Fluide (100%)' + ]; + public static $headerWide = [ + 'auto auto' => 'Automatique', + '100% 100%' => 'Image étirée (100% 100%)', + 'cover' => 'Responsive (cover)', + 'contain' => 'Responsive (contain)' + ]; + public static $footerTemplate = [ + '1' => 'Une seule colonne', + '2' => 'Deux colonnes : 1/2 - 1/2', + '3' => 'Trois colonnes : 1/3 - 1/3 - 1/3', + '4' => 'Trois lignes superposées' + ]; + public static $burgerContent = [ + 'none' => 'Aucun', + 'title' => 'Titre du site', + 'logo' => 'Logo du site' + ]; + + // Variable pour construire la liste des pages du site + public static $pagesList = []; + + /** + * Thème des écrans d'administration + */ + public function admin() { + // Soumission du formulaire + if($this->isPost()) { + $this->setData(['admin', [ + 'backgroundColor' => $this->getInput('adminBackgroundColor'), + 'colorTitle' => $this->getInput('adminColorTitle'), + 'colorText' => $this->getInput('adminColorText'), + 'colorButtonText' => $this->getInput('adminColorButtonText'), + 'backgroundColorButton' => $this->getInput('adminColorButton'), + 'backgroundColorButtonGrey' => $this->getInput('adminColorGrey'), + 'backgroundColorButtonRed' => $this->getInput('adminColorRed'), + 'backgroundColorButtonGreen'=> $this->getInput('adminColorGreen'), + 'backgroundColorButtonHelp'=> $this->getInput('adminColorHelp'), + 'fontText' => $this->getInput('adminFontText'), + 'fontSize' => $this->getInput('adminFontTextSize'), + 'fontTitle' => $this->getInput('adminFontTitle'), + 'backgroundBlockColor' => $this->getInput('adminBackGroundBlockColor'), + 'borderBlockColor' => $this->getInput('adminBorderBlockColor'), + ]]); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme/admin', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Administration', + 'view' => 'admin', + 'vendor' => [ + 'tinycolorpicker' + ], + ]); + } + + /** + * Mode avancé + */ + public function advanced() { + // Soumission du formulaire + if($this->isPost()) { + // Enregistre le CSS + file_put_contents(self::DATA_DIR.'custom.css', $this->getInput('themeAdvancedCss', null)); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme/advanced', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Éditeur CSS', + 'vendor' => [ + 'codemirror' + ], + 'view' => 'advanced' + ]); + } + + /** + * Options de l'arrière plan + */ + public function body() { + // Soumission du formulaire + if($this->isPost()) { + $this->setData(['theme', 'body', [ + 'backgroundColor' => $this->getInput('themeBodyBackgroundColor'), + 'image' => $this->getInput('themeBodyImage'), + 'imageAttachment' => $this->getInput('themeBodyImageAttachment'), + 'imagePosition' => $this->getInput('themeBodyImagePosition'), + 'imageRepeat' => $this->getInput('themeBodyImageRepeat'), + 'imageSize' => $this->getInput('themeBodyImageSize'), + 'toTopbackgroundColor' => $this->getInput('themeBodyToTopBackground'), + 'toTopColor' => $this->getInput('themeBodyToTopColor') + ]]); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Personnalisation de l\'arrière plan', + 'vendor' => [ + 'tinycolorpicker' + ], + 'view' => 'body' + ]); + } + + /** + * Options du pied de page + */ + public function footer() { + // Soumission du formulaire + if($this->isPost()) { + if ( $this->getInput('themeFooterCopyrightPosition') === 'hide' && + $this->getInput('themeFooterSocialsPosition') === 'hide' && + $this->getInput('themeFooterTextPosition') === 'hide' ) { + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Sélectionnez au moins un contenu à afficher', + 'redirect' => helper::baseUrl() . 'theme/footer', + 'state' => false + ]); + } else { + $this->setData(['theme', 'footer', [ + 'backgroundColor' => $this->getInput('themeFooterBackgroundColor'), + 'copyrightAlign' => $this->getInput('themeFooterCopyrightAlign'), + 'height' => $this->getInput('themeFooterHeight'), + 'loginLink' => $this->getInput('themeFooterLoginLink'), + 'margin' => $this->getInput('themeFooterMargin', helper::FILTER_BOOLEAN), + 'position' => $this->getInput('themeFooterPosition'), + 'fixed' => $this->getInput('themeFooterFixed', helper::FILTER_BOOLEAN), + 'socialsAlign' => $this->getInput('themeFooterSocialsAlign'), + 'text' => $this->getInput('themeFooterText', null), + 'textAlign' => $this->getInput('themeFooterTextAlign'), + 'textColor' => $this->getInput('themeFooterTextColor'), + 'copyrightPosition' => $this->getInput('themeFooterCopyrightPosition'), + 'textPosition' => $this->getInput('themeFooterTextPosition'), + 'socialsPosition' => $this->getInput('themeFooterSocialsPosition'), + 'textTransform' => $this->getInput('themeFooterTextTransform'), + 'font' => $this->getInput('themeFooterFont'), + 'fontSize' => $this->getInput('themeFooterFontSize'), + 'fontWeight' => $this->getInput('themeFooterFontWeight'), + 'displayVersion' => $this->getInput('themefooterDisplayVersion', helper::FILTER_BOOLEAN), + 'displaySiteMap' => $this->getInput('themefooterDisplaySiteMap', helper::FILTER_BOOLEAN), + 'displayCopyright' => $this->getInput('themefooterDisplayCopyright', helper::FILTER_BOOLEAN), + 'displayCookie' => $this->getInput('themefooterDisplayCookie', helper::FILTER_BOOLEAN), + 'displayLegal' => $this->getInput('themeFooterDisplayLegal', helper::FILTER_BOOLEAN), + 'displaySearch' => $this->getInput('themeFooterDisplaySearch', helper::FILTER_BOOLEAN), + 'displayMemberBar'=> $this->getInput('themeFooterDisplayMemberBar', helper::FILTER_BOOLEAN), + 'template' => $this->getInput('themeFooterTemplate') + ]]); + + // Sauvegarder la configuration localisée + $this->setData(['locale','legalPageId', $this->getInput('configLegalPageId')]); + $this->setData(['locale','searchPageId', $this->getInput('configSearchPageId')]); + + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme', + 'state' => true + ]); + } + } + + // Liste des pages + self::$pagesList = $this->getData(['page']); + foreach(self::$pagesList as $page => $pageId) { + if ($this->getData(['page',$page,'block']) === 'bar' || + $this->getData(['page',$page,'disable']) === true) { + unset(self::$pagesList[$page]); + } + } + + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Personnalisation du pied de page', + 'vendor' => [ + 'tinycolorpicker', + 'tinymce' + ], + 'view' => 'footer' + ]); + } + + /** + * Options de la bannière + */ + public function header() { + // Soumission du formulaire + if($this->isPost()) { + // Modification des URL des images dans la bannière perso + $featureContent = $this->getInput('themeHeaderText', null); + $featureContent = str_replace(helper::baseUrl(false,false), './', $featureContent); + // Si une image est positionnée, l'arrière en transparent. + $this->setData(['theme', 'header', [ + 'backgroundColor' => $this->getInput('themeHeaderBackgroundColor'), + 'font' => $this->getInput('themeHeaderFont'), + 'fontSize' => $this->getInput('themeHeaderFontSize'), + 'fontWeight' => $this->getInput('themeHeaderFontWeight'), + 'height' => $this->getInput('themeHeaderHeight'), + 'wide' => $this->getInput('themeHeaderWide'), + 'image' => $this->getInput('themeHeaderImage'), + 'imagePosition' => $this->getInput('themeHeaderImagePosition'), + 'imageRepeat' => $this->getInput('themeHeaderImageRepeat'), + 'margin' => $this->getInput('themeHeaderMargin', helper::FILTER_BOOLEAN), + 'position' => $this->getInput('themeHeaderPosition'), + 'textAlign' => $this->getInput('themeHeaderTextAlign'), + 'textColor' => $this->getInput('themeHeaderTextColor'), + 'textHide' => $this->getInput('themeHeaderTextHide', helper::FILTER_BOOLEAN), + 'textTransform' => $this->getInput('themeHeaderTextTransform'), + 'linkHomePage' => $this->getInput('themeHeaderlinkHomePage',helper::FILTER_BOOLEAN), + 'imageContainer' => $this->getInput('themeHeaderImageContainer'), + 'tinyHidden' => $this->getInput('themeHeaderTinyHidden', helper::FILTER_BOOLEAN), + 'feature' => $this->getInput('themeHeaderFeature'), + 'featureContent' => $featureContent + ]]); + // Modification de la position du menu selon la position de la bannière + if ( $this->getData(['theme','header','position']) == 'site' ) + { + $this->setData(['theme', 'menu', 'position',str_replace ('body-','site-',$this->getData(['theme','menu','position']))]); + } + if ( $this->getData(['theme','header','position']) == 'body') + { + $this->setData(['theme', 'menu', 'position',str_replace ('site-','body-',$this->getData(['theme','menu','position']))]); + } + // Menu accroché à la bannière qui devient cachée + if ( $this->getData(['theme','header','position']) == 'hide' && + in_array( $this->getData(['theme','menu','position']) , ['body-first', 'site-first', 'body-first' , 'site-second']) + ) { + $this->setData(['theme', 'menu', 'position','site']); + } + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Personnalisation de la bannière', + 'vendor' => [ + 'tinycolorpicker', + 'tinymce' + ], + 'view' => 'header' + ]); + } + + /** + * Accueil de la personnalisation + */ + public function index() { + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Personnalisation des thèmes', + 'view' => 'index' + ]); + } + + /** + * Options du menu + */ + public function menu() { + // Soumission du formulaire + if($this->isPost()) { + $this->setData(['theme', 'menu', [ + 'backgroundColor' => $this->getInput('themeMenuBackgroundColor'), + 'backgroundColorSub' => $this->getInput('themeMenuBackgroundColorSub'), + 'font' => $this->getInput('themeMenuFont'), + 'fontSize' => $this->getInput('themeMenuFontSize'), + 'fontWeight' => $this->getInput('themeMenuFontWeight'), + 'height' => $this->getInput('themeMenuHeight'), + 'wide' => $this->getInput('themeMenuWide'), + 'loginLink' => $this->getInput('themeMenuLoginLink', helper::FILTER_BOOLEAN), + 'margin' => $this->getInput('themeMenuMargin', helper::FILTER_BOOLEAN), + 'position' => $this->getInput('themeMenuPosition'), + 'textAlign' => $this->getInput('themeMenuTextAlign'), + 'textColor' => $this->getInput('themeMenuTextColor'), + 'textTransform' => $this->getInput('themeMenuTextTransform'), + 'fixed' => $this->getInput('themeMenuFixed', helper::FILTER_BOOLEAN), + 'activeColorAuto' => $this->getInput('themeMenuActiveColorAuto', helper::FILTER_BOOLEAN), + 'activeColor' => $this->getInput('themeMenuActiveColor'), + 'activeTextColor' => $this->getInput('themeMenuActiveTextColor'), + 'radius' => $this->getInput('themeMenuRadius'), + 'burgerTitle' => $this->getInput('themeMenuBurgerTitle', helper::FILTER_BOOLEAN), + 'memberBar' => $this->getInput('themeMenuMemberBar', helper::FILTER_BOOLEAN), + 'burgerLogo' => $this->getInput('themeMenuBurgerLogo'), + 'burgerContent' => $this->getInput('themeMenuBurgerContent') + ]]); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Personnalisation du menu', + 'vendor' => [ + 'tinycolorpicker' + ], + 'view' => 'menu' + ]); + } + + /** + * Réinitialisation de la personnalisation avancée + */ + public function reset() { + // $url prend l'adresse sans le token + $url = explode('&',$this->getUrl(2)); + + if ( isset($_GET['csrf']) + AND $_GET['csrf'] === $_SESSION['csrf'] + ) { + // Réinitialisation + $redirect =''; + switch ($url[0]) { + case 'admin': + $this->initData('admin'); + $redirect = helper::baseUrl() . 'theme/admin'; + break; + case 'manage': + $this->initData('theme'); + $redirect = helper::baseUrl() . 'theme/manage'; + break; + case 'custom': + unlink(self::DATA_DIR.'custom.css'); + $redirect = helper::baseUrl() . 'theme/advanced'; + break; + default : + $redirect = helper::baseUrl() . 'theme'; + } + + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Réinitialisation effectuée', + 'redirect' => $redirect, + 'state' => true + ]); + } else { + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Jeton incorrect' + ]); + } + } + + + /** + * Options du site + */ + public function site() { + // Soumission du formulaire + if($this->isPost()) { + $this->setData(['theme', 'title', [ + 'font' => $this->getInput('themeTitleFont'), + 'textColor' => $this->getInput('themeTitleTextColor'), + 'fontWeight' => $this->getInput('themeTitleFontWeight'), + 'textTransform' => $this->getInput('themeTitleTextTransform') + ]]); + $this->setData(['theme', 'text', [ + 'font' => $this->getInput('themeTextFont'), + 'fontSize' => $this->getInput('themeTextFontSize'), + 'textColor' => $this->getInput('themeTextTextColor'), + 'linkColor'=> $this->getInput('themeTextLinkColor') + ]]); + $this->setData(['theme', 'site', [ + 'backgroundColor' => $this->getInput('themeSiteBackgroundColor'), + 'radius' => $this->getInput('themeSiteRadius'), + 'shadow' => $this->getInput('themeSiteShadow'), + 'width' => $this->getInput('themeSiteWidth'), + 'margin' => $this->getInput('themeSiteMargin',helper::FILTER_BOOLEAN) + ]]); + $this->setData(['theme', 'button', [ + 'backgroundColor' => $this->getInput('themeButtonBackgroundColor') + ]]); + $this->setData(['theme', 'block', [ + 'backgroundColor' => $this->getInput('themeBlockBackgroundColor'), + 'borderColor' => $this->getInput('themeBlockBorderColor'), + 'backgroundTitleColor' => $this->getInput('themeBlockBackgroundTitleColor'), + 'blockBorderRadius' => $this->getInput('themeBlockBorderRadius'), + 'blockBorderShadow' => $this->getInput('themeBlockBorderShadow') + ]]); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . 'theme', + 'state' => true + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Personnalisation du site', + 'vendor' => [ + 'tinycolorpicker', + 'tinymce' + ], + 'view' => 'site' + ]); + } + + /** + * Import du thème + */ + public function manage() { + if($this->isPost() ) { + + $zipFilename = $this->getInput('themeManageImport', helper::FILTER_STRING_SHORT, true); + $data = $this->import(self::FILE_DIR.'source/' . $zipFilename); + if ($data['success']) { + header("Refresh:0"); + } else { + // Valeurs en sortie + $this->addOutput([ + 'notification' => $data['notification'], + 'state' => $data['success'], + 'title' => 'Gestion des thèmes', + 'view' => 'manage' + ]);; + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Gestion des thèmes', + 'view' => 'manage' + ]); + } + + /** + * Importe un thème + * @param string Url du thème à télécharger + * @param @return array contenant $success = true ou false ; $ notification string message à afficher + */ + + public function import($zipName = '') { + + if ($zipName !== '' && + file_exists($zipName)) { + // Init variables de retour + $success = false; + $notification = ''; + // Dossier temporaire + $tempFolder = uniqid(); + // Ouvrir le zip + $zip = new ZipArchive(); + if ($zip->open($zipName) === TRUE) { + mkdir (self::TEMP_DIR . $tempFolder, 0755); + $zip->extractTo(self::TEMP_DIR . $tempFolder ); + $modele = ''; + // Archive de thème ? + if ( + file_exists(self::TEMP_DIR . $tempFolder . '/site/data/custom.css') + AND file_exists(self::TEMP_DIR . $tempFolder . '/site/data/theme.css') + AND file_exists(self::TEMP_DIR . $tempFolder . '/site/data/theme.json') + ) { + $modele = 'theme'; + $this->sauve( $modele ); + } + if( + file_exists(self::TEMP_DIR . $tempFolder . '/site/data/admin.json') + AND file_exists(self::TEMP_DIR . $tempFolder . '/site/data/admin.css') + ) { + $modele = 'admin'; + $this->sauve( $modele ); + } + if (!empty($modele) + ) { + // traiter l'archive + $importFolder = './site/file/import'; + mkdir ($importFolder, 0755); + $success = $zip->extractTo($importFolder); + // $path = $importFolder.'/site/file/source/'; + // Modifie le nom de tous les fichiers de $path et leur nom dans theme.json + // $this->changeName($path); + $this->copyDir( $importFolder, './'); + $this->removeDir($importFolder); + // traitement de l'erreur + $notification = $success ? 'Le thème a été importé' : 'Erreur lors de l\'extraction, vérifiez les permissions.'; + + + } else { + // pas une archive de thème + $success = false; + $notification = 'Ce n\'est pas l\'archive d\'un thème !'; + } + // Supprimer le dossier temporaire même si le thème est invalide + $this->removeDir(self::TEMP_DIR . $tempFolder); + $zip->close(); + } else { + // erreur à l'ouverture + $success = false; + $notification = 'Impossible d\'ouvrir l\'archive'; + } + return (['success' => $success, 'notification' => $notification]); + } + + return (['success' => false, 'notification' => 'Archive non spécifiée ou introuvable']); + } + + + + /** + * Export du thème + */ + public function export() { + // Make zip + $zipFilename = $this->zipTheme($this->getUrl(2)); + // Téléchargement du ZIP + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header('Content-Transfer-Encoding: binary'); + header('Content-Disposition: attachment; filename="' . $zipFilename . '"'); + header('Content-Length: ' . filesize(self::TEMP_DIR . $zipFilename)); + readfile(self::TEMP_DIR . $zipFilename); + // Nettoyage du dossier + unlink (self::TEMP_DIR . $zipFilename); + exit(); + } + + /** + * Export du thème + */ + public function save() { + // Make zip + $zipFilename = $this->zipTheme($this->getUrl(2)); + // Téléchargement du ZIP + if (!is_dir(self::FILE_DIR.'source/theme')) { + mkdir(self::FILE_DIR.'source/theme', 0755); + } + copy (self::TEMP_DIR . $zipFilename , self::FILE_DIR.'source/theme/' . $zipFilename); + // Nettoyage du dossier + unlink (self::TEMP_DIR . $zipFilename); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Archive '.$zipFilename.' sauvegardée avec succès', + 'redirect' => helper::baseUrl() . 'theme/manage', + 'state' => true + ]); + } + + /** + * Sauvegarde du thème du site ou de l'administration avant application d'un nouveau thème + */ + public function sauve( $type ) { + // Make zip + $zipFilename = $this->zipTheme( $type ); + // Téléchargement du ZIP + if (!is_dir(self::FILE_DIR.'source/theme')) { + mkdir(self::FILE_DIR.'source/theme', 0755); + } + copy (self::TEMP_DIR . $zipFilename , self::FILE_DIR.'source/theme/' . $zipFilename); + // Nettoyage du dossier + unlink (self::TEMP_DIR . $zipFilename); + return; + } + + /** + * construction du zip + * @param string $modele theme ou admin + */ + private function zipTheme($modele) { + // Creation du dossier + $zipFilename = $modele . date('Y-m-d-H-i-s', time()) . '.zip'; + $zip = new ZipArchive(); + if ($zip->open(self::TEMP_DIR . $zipFilename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) === TRUE) { + switch ($modele) { + case 'admin': + $zip->addFile(self::DATA_DIR.'admin.json',self::DATA_DIR.'admin.json'); + $zip->addFile(self::DATA_DIR.'admin.css',self::DATA_DIR.'admin.css'); + break; + case 'theme': + $zip->addFile(self::DATA_DIR.'theme.json',self::DATA_DIR.'theme.json'); + $zip->addFile(self::DATA_DIR.'theme.css',self::DATA_DIR.'theme.css'); + $zip->addFile(self::DATA_DIR.'custom.css',self::DATA_DIR.'custom.css'); + if ($this->getData(['theme','body','image']) !== '' ) { + $zip->addFile(self::FILE_DIR.'source/'.$this->getData(['theme','body','image']), + self::FILE_DIR.'source/'.$this->getData(['theme','body','image']) + ); + } + if ($this->getData(['theme','header','image']) !== '' ) { + $zip->addFile(self::FILE_DIR.'source/'.$this->getData(['theme','header','image']), + self::FILE_DIR.'source/'.$this->getData(['theme','header','image']) + ); + } + // Extraction des images de la bannière personnalisée + $images=[]; + $ii=0; + if( $this->getData(['theme','header','feature'])=== 'feature') { + $tab = str_word_count($this->getData(['theme','header','featureContent']), 1, './' ); + foreach( $tab as $key=>$value ){ + if( $value ==='src'){ + $images[$ii] = $tab [$key + 1]; + $ii++; + } + } + // ajout des images dans le zip + foreach( $images as $key=>$value){ + $value = str_replace( './site', 'site' , $value); + $zip->addFile( $value, $value ); + } + } + break; + } + $ret = $zip->close(); + } + return ($zipFilename); + } + + /* + * + */ + /* + private function changeName($path = '.') + { + $ignore = array('cgi-bin', '.', '..'); + if(is_dir($path)){ + if($dir = opendir($path)){ + while(false !== ($file = readdir($dir))) { + if(!in_array($file, $ignore)) { + if(is_dir("$path$file")) { + $this->changeName( "$path$file/"); + } + else { + $oldName = $file; + $newName = date("YmdHi").$file; + rename("$path$oldName", "$path$newName"); + // Modification du nom de fichier dans .../import/site/data/theme.json + $fileTheme = './site/file/import/site/data/theme.json'; + if(file_exists($fileTheme)){ + $json = file_get_contents($fileTheme); + $json = str_replace($oldName, $newName, $json); + file_put_contents($fileTheme,$json); + } + } + } + } + closedir($dir); + } + } + } + */ +} diff --git a/core/module/theme/view/admin/admin.css b/core/module/theme/view/admin/admin.css new file mode 100644 index 0000000..0797b4e --- /dev/null +++ b/core/module/theme/view/admin/admin.css @@ -0,0 +1,19 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** NE PAS EFFACER +* admin.css +*/ diff --git a/core/module/theme/view/admin/admin.js.php b/core/module/theme/view/admin/admin.js.php new file mode 100644 index 0000000..3c87778 --- /dev/null +++ b/core/module/theme/view/admin/admin.js.php @@ -0,0 +1,62 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + /** + * Aperçu en direct + */ +$("input, select").on("change", function() { + + var titleFont = $("#adminFontTitle").val(); + var textFont = $("#adminFontText").val(); + var css = "@import url('https://fonts.googleapis.com/css?family=" + titleFont + "|" + textFont + "');"; + var colors = core.colorVariants($("#adminBackgroundColor").val()); + var css = "#site{background-color:" + colors.normal + ";}"; + css += "body, .row > div {font:" + $("#adminFontTextSize").val() + " '" + textFont + "', sans-serif;}"; + css += "body h1, h2, h3, h4, h5, h6 {font-family:'" + titleFont + "', sans-serif; color:" + $("#adminColorTitle").val() + ";}"; + css += "body:not(.editorWysiwyg),span .zwiico-help {color:" + $("#adminColorText").val() + ";}"; + var colors = core.colorVariants($("#adminColorButton").val()); + css += "input[type='checkbox']:checked + label::before,.speechBubble{ background-color:" + colors.normal + "; color:" + $("#adminColorButtonText").val() + ";}"; + css += ".speechBubble::before {border-color:" + colors.normal + " transparent transparent transparent;}"; + css += ".button {background-color:" + colors.normal + ";color:" + colors.text + ";}.button:hover {background-color:" + colors.darken + ";color:" + colors.text + ";}.button:active {background-color:" + colors.veryDarken + ";color:" + colors.text + ";}"; + var colors = core.colorVariants($("#adminColorGrey").val()); + css += ".button.buttonGrey {background-color: " + colors.normal + ";color:" + colors.text + ";}.button.buttonGrey:hover {background-color:" + colors.darken + ";color:" + colors.text + "}.button.buttonGrey:active {background-color:" + colors.veryDarken + ";color:" + colors.text + ";}"; + var colors = core.colorVariants($("#adminColorRed").val()); + css += ".button.buttonRed {background-color: " + colors.normal + ";color:" + colors.text + ";}.button.buttonRed:hover {background-color:" + colors.darken + ";color:" + colors.text + "}.button.buttonRed:active {background-color:" + colors.veryDarken + ";color:" + colors.text + "}"; + var colors = core.colorVariants($("#adminColorGreen").val()); + css += ".button.buttonGreen, button[type=submit] {background-color: " + colors.normal + ";color: " + ";color:" + colors.text + "}.button.buttonGreen:hover, button[type=submit]:hover {background-color: " + colors.darken + ";color:" + colors.text + ";}.button.buttonGreen:active, button[type=submit]:active {background-color:" + colors.veryDarken + ";color:" + colors.text + "}"; + var colors = core.colorVariants($("#adminBackGroundBlockColor").val()); + css += ".block {border: 1px solid " + $("#adminBorderBlockColor").val() + ";}.block h4 {background-color: " + colors.normal + ";color:" + colors.text + ";}"; + css += "input[type=email],input[type=text],input[type=password],select:not(#barSelectPage),textarea:not(.editorWysiwyg),.inputFile{background-color: " + colors.normal + ";color:" + colors.text + ";border: 1px solid " + $("#adminBorderBlockColor").val() + ";}"; + + // Ajout du css au DOM + $("#themePreview").remove(); + $(" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + " /> + " /> + " /> + " /> + + + + +
+
+
+ +
+
+
+
+ +
+
+ + +
+
+ +
+ +
+
+
+ +
+
+
+ + + + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ + + +
+ +scanDir($config['ftp_base_folder'] . $config['upload_dir'] . $rfm_subfolder . $subdir); + if (!$ftp->isDir($config['ftp_base_folder'] . $config['ftp_thumbs_dir'] . $rfm_subfolder . $subdir)) { + create_folder(false, $config['ftp_base_folder'] . $config['ftp_thumbs_dir'] . $rfm_subfolder . $subdir, $ftp, $config); + } + } catch (FtpClient\FtpException $e) { + echo "Error: "; + echo $e->getMessage(); + echo "
Please check configurations"; + die(); + } +} else { + $files = scandir($config['current_path'] . $rfm_subfolder . $subdir); +} + +$n_files = count($files); + +//php sorting +$sorted = array(); +//$current_folder=array(); +//$prev_folder=array(); +$current_files_number = 0; +$current_folders_number = 0; + +foreach ($files as $k => $file) { + if ($ftp) { + $date = strtotime($file['day'] . " " . $file['month'] . " " . date('Y') . " " . $file['time']); + $size = $file['size']; + if ($file['type'] == 'file') { + $current_files_number++; + $file_ext = substr(strrchr($file['name'], '.'), 1); + $is_dir = false; + } else { + $current_folders_number++; + $file_ext = trans('Type_dir'); + $is_dir = true; + } + $sorted[$k] = array( + 'is_dir' => $is_dir, + 'file' => $file['name'], + 'file_lcase' => strtolower($file['name']), + 'date' => $date, + 'size' => $size, + 'permissions' => $file['permissions'], + 'extension' => fix_strtolower($file_ext) + ); + } else { + + + if ($file != "." && $file != "..") { + if (is_dir($config['current_path'] . $rfm_subfolder . $subdir . $file)) { + $date = filemtime($config['current_path'] . $rfm_subfolder . $subdir . $file); + $current_folders_number++; + if ($config['show_folder_size']) { + list($size, $nfiles, $nfolders) = folder_info($config['current_path'] . $rfm_subfolder . $subdir . $file, false); + } else { + $size = 0; + } + $file_ext = trans('Type_dir'); + $sorted[$k] = array( + 'is_dir' => true, + 'file' => $file, + 'file_lcase' => strtolower($file), + 'date' => $date, + 'size' => $size, + 'permissions' => '', + 'extension' => fix_strtolower($file_ext) + ); + + if ($config['show_folder_size']) { + $sorted[$k]['nfiles'] = $nfiles; + $sorted[$k]['nfolders'] = $nfolders; + } + } else { + $current_files_number++; + $file_path = $config['current_path'] . $rfm_subfolder . $subdir . $file; + $date = filemtime($file_path); + $size = filesize($file_path); + $file_ext = substr(strrchr($file, '.'), 1); + $sorted[$k] = array( + 'is_dir' => false, + 'file' => $file, + 'file_lcase' => strtolower($file), + 'date' => $date, + 'size' => $size, + 'permissions' => '', + 'extension' => strtolower($file_ext) + ); + } + } + } +} + +function filenameSort($x, $y) +{ + global $descending; + + if ($x['is_dir'] !== $y['is_dir']) { + return $y['is_dir']; + } else { + return ($descending) + ? $x['file_lcase'] < $y['file_lcase'] + : $x['file_lcase'] >= $y['file_lcase']; + } +} + +function dateSort($x, $y) +{ + global $descending; + + if ($x['is_dir'] !== $y['is_dir']) { + return $y['is_dir']; + } else { + return ($descending) + ? $x['date'] < $y['date'] + : $x['date'] >= $y['date']; + } +} + +function sizeSort($x, $y) +{ + global $descending; + + if ($x['is_dir'] !== $y['is_dir']) { + return $y['is_dir']; + } else { + return ($descending) + ? $x['size'] < $y['size'] + : $x['size'] >= $y['size']; + } +} + +function extensionSort($x, $y) +{ + global $descending; + + if ($x['is_dir'] !== $y['is_dir']) { + return $y['is_dir']; + } else { + return ($descending) + ? $x['extension'] < $y['extension'] + : $x['extension'] >= $y['extension']; + } +} + +switch ($sort_by) { + case 'date': + usort($sorted, 'dateSort'); + break; + case 'size': + usort($sorted, 'sizeSort'); + break; + case 'extension': + usort($sorted, 'extensionSort'); + break; + default: + usort($sorted, 'filenameSort'); + break; +} + +if ($subdir != "") { + $sorted = array_merge(array(array('file' => '..')), $sorted); +} + +$files = $sorted; +?> + + + + + + + +
+ + +
+ +
+
+ isDir($config['ftp_base_folder'].$config['upload_dir'].$rfm_subfolder.$subdir)) || (!$ftp && @opendir($config['current_path'].$rfm_subfolder.$subdir)===FALSE)){ ?> +
+
There is an error! The upload folder there isn't. Check your config.php file.
+ +

+ +
+ + + + + + + + +
    " id="main-item-container"> + $config['file_number_limit_js'] && $file!=".." && stripos($file,$filter)===false)){ + continue; + } + $new_name=fix_filename($file,$config); + if($ftp && $file!='..' && $file!=$new_name){ + //rename + rename_folder($config['current_path'].$subdir.$file,$new_name,$ftp,$config); + $file=$new_name; + } + //add in thumbs folder if not exist + if($file!='..'){ + if(!$ftp && !file_exists($thumbs_path.$file)){ + create_folder(false,$thumbs_path.$file,$ftp,$config); + } + } + + $class_ext = 3; + if($file=='..' && trim($subdir) != '' ){ + $src = explode("/",$subdir); + unset($src[count($src)-2]); + $src=implode("/",$src); + if($src=='') $src="/"; + } + elseif ($file!='..') { + $src = $subdir . $file."/"; + } + + ?> + + $file_array) { + $file=$file_array['file']; + + if($file == '.' || $file == '..' || $file_array['extension']==fix_strtolower(trans('Type_dir')) || !check_extension($file_array['extension'],$config) || ($filter!='' && $n_files>$config['file_number_limit_js'] && stripos($file,$filter)===false)) + continue; + foreach ( $config['hidden_files'] as $hidden_file ) { + if ( fnmatch($hidden_file, $file, FNM_PATHNAME) ) { + continue 2; + } + } + $filename=substr($file, 0, '-' . (strlen($file_array['extension']) + 1)); + if(strlen($file_array['extension'])===0){ + $filename = $file; + } + if(!$ftp){ + $file_path=$config['current_path'].$rfm_subfolder.$subdir.$file; + //check if file have illegal caracter + + if($file!=fix_filename($file,$config)){ + $file1=fix_filename($file,$config); + $file_path1=($config['current_path'].$rfm_subfolder.$subdir.$file1); + if(file_exists($file_path1)){ + $i = 1; + $info=pathinfo($file1); + while(file_exists($config['current_path'].$rfm_subfolder.$subdir.$info['filename'].".[".$i."].".$info['extension'])) { + $i++; + } + $file1=$info['filename'].".[".$i."].".$info['extension']; + $file_path1=($config['current_path'].$rfm_subfolder.$subdir.$file1); + } + + $filename=substr($file1, 0, '-' . (strlen($file_array['extension']) + 1)); + if(strlen($file_array['extension'])===0){ + $filename = $file1; + } + rename_file($file_path,fix_filename($filename,$config),$ftp,$config); + $file=$file1; + $file_array['extension']=fix_filename($file_array['extension'],$config); + $file_path=$file_path1; + } + }else{ + $file_path = $config['ftp_base_url'].$config['upload_dir'].$rfm_subfolder.$subdir.$file; + } + + $is_img=false; + $is_video=false; + $is_audio=false; + $show_original=false; + $show_original_mini=false; + $mini_src=""; + $src_thumb=""; + if(in_array($file_array['extension'], $config['ext_img'])){ + $src = $file_path; + $is_img=true; + + $img_width = $img_height = ""; + if($ftp){ + $mini_src = $src_thumb = $config['ftp_base_url'].$config['ftp_thumbs_dir'].$subdir. $file; + $creation_thumb_path = "/".$config['ftp_base_folder'].$config['ftp_thumbs_dir'].$subdir. $file; + }else{ + + $creation_thumb_path = $mini_src = $src_thumb = $thumbs_path. $file; + + if (!file_exists($src_thumb)) { + if (!create_img($file_path, $creation_thumb_path, 122, 91, 'crop', $config)) { + $src_thumb = $mini_src = ""; + } + } + //check if is smaller than thumb + list($img_width, $img_height, $img_type, $attr)=@getimagesize($file_path); + if($img_width<122 && $img_height<91){ + $src_thumb=$file_path; + $show_original=true; + } + + if($img_width<45 && $img_height<38){ + $mini_src=$config['current_path'].$rfm_subfolder.$subdir.$file; + $show_original_mini=true; + } + } + } + $is_icon_thumb=false; + $is_icon_thumb_mini=false; + $no_thumb=false; + if($src_thumb==""){ + $no_thumb=true; + if(file_exists('img/'.$config['icon_theme'].'/'.$file_array['extension'].".jpg")){ + $src_thumb ='img/'.$config['icon_theme'].'/'.$file_array['extension'].".jpg"; + }else{ + $src_thumb = "img/".$config['icon_theme']."/default.jpg"; + } + $is_icon_thumb=true; + } + if($mini_src==""){ + $is_icon_thumb_mini=false; + } + + $class_ext=0; + if (in_array($file_array['extension'], $config['ext_video'])) { + $class_ext = 4; + $is_video=true; + }elseif (in_array($file_array['extension'], $config['ext_img'])) { + $class_ext = 2; + }elseif (in_array($file_array['extension'], $config['ext_music'])) { + $class_ext = 5; + $is_audio=true; + }elseif (in_array($file_array['extension'], $config['ext_misc'])) { + $class_ext = 3; + }else{ + $class_ext = 1; + } + if((!($_GET['type']==1 && !$is_img) && !(($_GET['type']==3 && !$is_video) && ($_GET['type']==3 && !$is_audio))) && $class_ext>0){ +?> + +
+ + +
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + diff --git a/core/vendor/filemanager/execute.php b/core/vendor/filemanager/execute.php new file mode 100644 index 0000000..0321bfa --- /dev/null +++ b/core/vendor/filemanager/execute.php @@ -0,0 +1,505 @@ +send(); + exit; +} + +if (!checkRelativePath($_POST['path'])) { + response(trans('wrong path') . AddErrorLocation())->send(); + exit; +} + +if (isset($_SESSION['RF']['language']) && file_exists('lang/' . basename($_SESSION['RF']['language']) . '.php')) { + $languages = include 'lang/languages.php'; + if (array_key_exists($_SESSION['RF']['language'], $languages)) { + include 'lang/' . basename($_SESSION['RF']['language']) . '.php'; + } else { + response(trans('Lang_Not_Found') . AddErrorLocation())->send(); + exit; + } +} else { + response(trans('Lang_Not_Found') . AddErrorLocation())->send(); + exit; +} + +$ftp = ftp_con($config); + +$base = $config['current_path']; +$path = $base . $_POST['path']; +$cycle = true; +$max_cycles = 50; +$i = 0; + +while ($cycle && $i < $max_cycles) { + $i++; + if ($path == $base) { + $cycle = false; + } + + if (file_exists($path . "config.php")) { + require_once $path . "config.php"; + $cycle = false; + } + $path = fix_dirname($path) . "/"; +} + +function returnPaths($_path, $_name, $config) +{ + global $ftp; + $path = $config['current_path'] . $_path; + $path_thumb = $config['thumbs_base_path'] . $_path; + $name = null; + if ($ftp) { + $path = $config['ftp_base_folder'] . $config['upload_dir'] . $_path; + $path_thumb = $config['ftp_base_folder'] . $config['ftp_thumbs_dir'] . $_path; + } + if ($_name) { + $name = fix_filename($_name, $config); + if (strpos($name, '../') !== false || strpos($name, '..\\') !== false) { + response(trans('wrong name') . AddErrorLocation())->send(); + exit; + } + } + return array($path, $path_thumb, $name); +} + +if(isset($_POST['paths'])){ + $paths = $paths_thumb = $names = array(); + foreach ($_POST['paths'] as $key => $path) { + if (!checkRelativePath($path)) + { + response(trans('wrong path').AddErrorLocation())->send(); + exit; + } + $name = null; + if(isset($_POST['names'][$key])){ + $name = $_POST['names'][$key]; + } + list($path,$path_thumb,$name) = returnPaths($path,$name,$config); + $paths[] = $path; + $paths_thumb[] = $path_thumb; + $names = $name; + } +} else { + $name = null; + if(isset($_POST['name'])){ + $name = $_POST['name']; + } + list($path,$path_thumb,$name) = returnPaths($_POST['path'],$name,$config); + +} + +$info = pathinfo($path); +if (isset($info['extension']) && !(isset($_GET['action']) && $_GET['action'] == 'delete_folder') && + !check_extension($info['extension'], $config) + && $_GET['action'] != 'create_file') { + response(trans('wrong extension') . AddErrorLocation())->send(); + exit; +} + +if (isset($_GET['action'])) { + switch ($_GET['action']) { + case 'delete_file': + + deleteFile($path, $path_thumb, $config); + + break; + + case 'delete_files': + foreach ($paths as $key => $p) { + deleteFile($p, $paths_thumb[$key], $config); + } + + break; + case 'delete_folder': + if ($config['delete_folders']){ + + if($ftp){ + deleteDir($path,$ftp,$config); + deleteDir($path_thumb,$ftp,$config); + }else{ + if (is_dir($path_thumb)) + { + deleteDir($path_thumb,NULL,$config); + } + + if (is_dir($path)) + { + deleteDir($path,NULL,$config); + if ($config['fixed_image_creation']) + { + foreach($config['fixed_path_from_filemanager'] as $k=>$paths){ + if ($paths!="" && $paths[strlen($paths)-1] != "/") $paths.="/"; + + $base_dir=$paths.substr_replace($path, '', 0, strlen($config['current_path'])); + if (is_dir($base_dir)) deleteDir($base_dir,NULL,$config); + } + } + } + } + } + break; + case 'create_folder': + if ($config['create_folders']) + { + + $name = fix_filename($_POST['name'],$config); + $path .= $name; + $path_thumb .= $name; + $res = create_folder(fix_path($path,$config),fix_path($path_thumb,$config),$ftp,$config); + if(!$res){ + response(trans('Rename_existing_folder').AddErrorLocation())->send(); + } + } + break; + case 'rename_folder': + if ($config['rename_folders']){ + if(!is_dir($path)) { + response(trans('wrong path').AddErrorLocation())->send(); + exit; + } + $name = fix_filename($name, $config); + $name = str_replace('.', '', $name); + + if (!empty($name)) { + if (!rename_folder($path, $name, $ftp, $config)) { + response(trans('Rename_existing_folder') . AddErrorLocation())->send(); + exit; + } + rename_folder($path_thumb, $name, $ftp, $config); + if (!$ftp && $config['fixed_image_creation']) { + foreach ($config['fixed_path_from_filemanager'] as $k => $paths) { + if ($paths != "" && $paths[strlen($paths) - 1] != "/") { + $paths .= "/"; + } + + $base_dir = $paths . substr_replace($path, '', 0, strlen($config['current_path'])); + rename_folder($base_dir, $name, $ftp, $config); + } + } + } else { + response(trans('Empty_name') . AddErrorLocation())->send(); + exit; + } + } + break; + + case 'create_file': + if ($config['create_text_files'] === false) { + response(sprintf(trans('File_Open_Edit_Not_Allowed'), strtolower(trans('Edit'))) . AddErrorLocation())->send(); + exit; + } + + if (!isset($config['editable_text_file_exts']) || !is_array($config['editable_text_file_exts'])) { + $config['editable_text_file_exts'] = array(); + } + + // check if user supplied extension + if (strpos($name, '.') === false) { + response(trans('No_Extension') . ' ' . sprintf(trans('Valid_Extensions'), implode(', ', $config['editable_text_file_exts'])) . AddErrorLocation())->send(); + exit; + } + + // correct name + $old_name = $name; + $name = fix_filename($name, $config); + if (empty($name)) { + response(trans('Empty_name') . AddErrorLocation())->send(); + exit; + } + + // check extension + $parts = explode('.', $name); + if (!in_array(end($parts), $config['editable_text_file_exts'])) { + response(trans('Error_extension') . ' ' . sprintf(trans('Valid_Extensions'), implode(', ', $config['editable_text_file_exts'])) . AddErrorLocation(), 400)->send(); + exit; + } + + $content = $_POST['new_content']; + + if ($ftp) { + $temp = tempnam('/tmp', 'RF'); + file_put_contents($temp, $content); + $ftp->put("/" . $path . $name, $temp, FTP_BINARY); + unlink($temp); + response(trans('File_Save_OK'))->send(); + } else { + if (!checkresultingsize(strlen($content))) { + response(sprintf(trans('max_size_reached'), $config['MaxSizeTotal']) . AddErrorLocation())->send(); + exit; + } + // file already exists + if (file_exists($path . $name)) { + response(trans('Rename_existing_file') . AddErrorLocation())->send(); + exit; + } + + if (@file_put_contents($path . $name, $content) === false) { + response(trans('File_Save_Error') . AddErrorLocation())->send(); + exit; + } else { + if (is_function_callable('chmod') !== false) { + chmod($path . $name, 0644); + } + response(trans('File_Save_OK'))->send(); + exit; + } + } + + break; + + case 'rename_file': + if ($config['rename_files']) { + $name = fix_filename($name, $config); + if (!empty($name)) { + if (!rename_file($path, $name, $ftp, $config)) { + response(trans('Rename_existing_file') . AddErrorLocation())->send(); + exit; + } + + rename_file($path_thumb, $name, $ftp, $config); + + if ($config['fixed_image_creation']) { + $info = pathinfo($path); + + foreach ($config['fixed_path_from_filemanager'] as $k => $paths) { + if ($paths != "" && $paths[strlen($paths) - 1] != "/") { + $paths .= "/"; + } + + $base_dir = $paths . substr_replace($info['dirname'] . "/", '', 0, strlen($config['current_path'])); + if (file_exists($base_dir . $config['fixed_image_creation_name_to_prepend'][$k] . $info['filename'] . $config['fixed_image_creation_to_append'][$k] . "." . $info['extension'])) { + rename_file($base_dir . $config['fixed_image_creation_name_to_prepend'][$k] . $info['filename'] . $config['fixed_image_creation_to_append'][$k] . "." . $info['extension'], $config['fixed_image_creation_name_to_prepend'][$k] . $name . $config['fixed_image_creation_to_append'][$k], $ftp, $config); + } + } + } + } else { + response(trans('Empty_name') . AddErrorLocation())->send(); + exit; + } + } + break; + + case 'duplicate_file': + if ($config['duplicate_files']) { + $name = fix_filename($name, $config); + if (!empty($name)) { + if (!$ftp && !checkresultingsize(filesize($path))) { + response(sprintf(trans('max_size_reached'), $config['MaxSizeTotal']) . AddErrorLocation())->send(); + exit; + } + if (!duplicate_file($path, $name, $ftp, $config)) { + response(trans('Rename_existing_file') . AddErrorLocation())->send(); + exit; + } + + duplicate_file($path_thumb, $name, $ftp, $config); + + if (!$ftp && $config['fixed_image_creation']) { + $info = pathinfo($path); + foreach ($config['fixed_path_from_filemanager'] as $k => $paths) { + if ($paths != "" && $paths[strlen($paths) - 1] != "/") { + $paths .= "/"; + } + + $base_dir = $paths . substr_replace($info['dirname'] . "/", '', 0, strlen($config['current_path'])); + + if (file_exists($base_dir . $config['fixed_image_creation_name_to_prepend'][$k] . $info['filename'] . $config['fixed_image_creation_to_append'][$k] . "." . $info['extension'])) { + duplicate_file($base_dir . $config['fixed_image_creation_name_to_prepend'][$k] . $info['filename'] . $config['fixed_image_creation_to_append'][$k] . "." . $info['extension'], $config['fixed_image_creation_name_to_prepend'][$k] . $name . $config['fixed_image_creation_to_append'][$k]); + } + } + } + } else { + response(trans('Empty_name') . AddErrorLocation())->send(); + exit; + } + } + break; + + case 'paste_clipboard': + if (!isset($_SESSION['RF']['clipboard_action'], $_SESSION['RF']['clipboard']['path']) + || $_SESSION['RF']['clipboard_action'] == '' + || $_SESSION['RF']['clipboard']['path'] == '') { + response()->send(); + exit; + } + + $action = $_SESSION['RF']['clipboard_action']; + $data = $_SESSION['RF']['clipboard']; + + + if ($ftp) { + if ($_POST['path'] != "") { + $path .= DIRECTORY_SEPARATOR; + $path_thumb .= DIRECTORY_SEPARATOR; + } + $path_thumb .= basename($data['path']); + $path .= basename($data['path']); + $data['path_thumb'] = DIRECTORY_SEPARATOR . $config['ftp_base_folder'] . $config['ftp_thumbs_dir'] . $data['path']; + $data['path'] = DIRECTORY_SEPARATOR . $config['ftp_base_folder'] . $config['upload_dir'] . $data['path']; + } else { + $data['path_thumb'] = $config['thumbs_base_path'] . $data['path']; + $data['path'] = $config['current_path'] . $data['path']; + } + + $pinfo = pathinfo($data['path']); + + // user wants to paste to the same dir. nothing to do here... + if ($pinfo['dirname'] == rtrim($path, DIRECTORY_SEPARATOR)) { + response()->send(); + exit; + } + + // user wants to paste folder to it's own sub folder.. baaaah. + if (is_dir($data['path']) && strpos($path, $data['path']) !== false) { + response()->send(); + exit; + } + + // something terribly gone wrong + if ($action != 'copy' && $action != 'cut') { + response(trans('wrong action') . AddErrorLocation())->send(); + exit; + } + if ($ftp) { + if ($action == 'copy') { + $tmp = time() . basename($data['path']); + $ftp->get($tmp, $data['path'], FTP_BINARY); + $ftp->put(DIRECTORY_SEPARATOR . $path, $tmp, FTP_BINARY); + unlink($tmp); + + if (url_exists($data['path_thumb'])) { + $tmp = time() . basename($data['path_thumb']); + @$ftp->get($tmp, $data['path_thumb'], FTP_BINARY); + @$ftp->put(DIRECTORY_SEPARATOR . $path_thumb, $tmp, FTP_BINARY); + unlink($tmp); + } + } elseif ($action == 'cut') { + $ftp->rename($data['path'], DIRECTORY_SEPARATOR . $path); + if (url_exists($data['path_thumb'])) { + @$ftp->rename($data['path_thumb'], DIRECTORY_SEPARATOR . $path_thumb); + } + } + } else { + // check for writability + if (is_really_writable($path) === false || is_really_writable($path_thumb) === false) { + response(trans('Dir_No_Write') . '
' . str_replace('../', '', $path) . '
' . str_replace('../', '', $path_thumb) . AddErrorLocation())->send(); + exit; + } + + // check if server disables copy or rename + if (is_function_callable(($action == 'copy' ? 'copy' : 'rename')) === false) { + response(sprintf(trans('Function_Disabled'), ($action == 'copy' ? (trans('Copy')) : (trans('Cut')))) . AddErrorLocation())->send(); + exit; + } + if ($action == 'copy') { + list($sizeFolderToCopy, $fileNum, $foldersCount) = folder_info($path, false); + if (!checkresultingsize($sizeFolderToCopy)) { + response(sprintf(trans('max_size_reached'), $config['MaxSizeTotal']) . AddErrorLocation())->send(); + exit; + } + rcopy($data['path'], $path); + rcopy($data['path_thumb'], $path_thumb); + } elseif ($action == 'cut') { + rrename($data['path'], $path); + rrename($data['path_thumb'], $path_thumb); + + // cleanup + if (is_dir($data['path']) === TRUE){ + rrename_after_cleaner($data['path']); + rrename_after_cleaner($data['path_thumb']); + } + } + } + + // cleanup + $_SESSION['RF']['clipboard']['path'] = null; + $_SESSION['RF']['clipboard_action'] = null; + + break; + + case 'chmod': + $mode = $_POST['new_mode']; + $rec_option = $_POST['is_recursive']; + $valid_options = array('none', 'files', 'folders', 'both'); + $chmod_perm = ($_POST['folder'] ? $config['chmod_dirs'] : $config['chmod_files']); + + // check perm + if ($chmod_perm === false) { + response(sprintf(trans('File_Permission_Not_Allowed'), (is_dir($path) ? (trans('Folders')) : (trans('Files')))) . AddErrorLocation())->send(); + exit; + } + // check mode + if (!preg_match("/^[0-7]{3}$/", $mode)) { + response(trans('File_Permission_Wrong_Mode') . AddErrorLocation())->send(); + exit; + } + // check recursive option + if (!in_array($rec_option, $valid_options)) { + response(trans("wrong option") . AddErrorLocation())->send(); + exit; + } + // check if server disabled chmod + if (!$ftp && is_function_callable('chmod') === false) { + response(sprintf(trans('Function_Disabled'), 'chmod') . AddErrorLocation())->send(); + exit; + } + + $mode = "0" . $mode; + $mode = octdec($mode); + if ($ftp) { + $ftp->chmod($mode, "/" . $path); + } else { + rchmod($path, $mode, $rec_option); + } + + break; + + case 'save_text_file': + $content = $_POST['new_content']; + // $content = htmlspecialchars($content); not needed + // $content = stripslashes($content); + + if ($ftp) { + $tmp = time(); + file_put_contents($tmp, $content); + $ftp->put("/" . $path, $tmp, FTP_BINARY); + unlink($tmp); + response(trans('File_Save_OK'))->send(); + } else { + // no file + if (!file_exists($path)) { + response(trans('File_Not_Found') . AddErrorLocation())->send(); + exit; + } + + // not writable or edit not allowed + if (!is_writable($path) || $config['edit_text_files'] === false) { + response(sprintf(trans('File_Open_Edit_Not_Allowed'), strtolower(trans('Edit'))) . AddErrorLocation())->send(); + exit; + } + + if (!checkresultingsize(strlen($content))) { + response(sprintf(trans('max_size_reached'), $config['MaxSizeTotal']) . AddErrorLocation())->send(); + exit; + } + if (@file_put_contents($path, $content) === false) { + response(trans('File_Save_Error') . AddErrorLocation())->send(); + exit; + } else { + response(trans('File_Save_OK'))->send(); + exit; + } + } + + break; + + default: + response(trans('wrong action') . AddErrorLocation())->send(); + exit; + } +} diff --git a/core/vendor/filemanager/force_download.php b/core/vendor/filemanager/force_download.php new file mode 100644 index 0000000..42f1666 --- /dev/null +++ b/core/vendor/filemanager/force_download.php @@ -0,0 +1,127 @@ +send(); + exit; +} + +if (!checkRelativePath($_POST['path']) || strpos($_POST['path'], '/') === 0) { + response(trans('wrong path') . AddErrorLocation(), 400)->send(); + exit; +} + +if (strpos($_POST['name'], '/') !== false) { + response(trans('wrong path') . AddErrorLocation(), 400)->send(); + exit; +} + +$ftp = ftp_con($config); + +if ($ftp) { + $path = $config['ftp_base_url'] . $config['upload_dir'] . $_POST['path']; +} else { + $path = $config['current_path'] . $_POST['path']; +} + +$name = $_POST['name']; +$info = pathinfo($name); + +if (!check_extension($info['extension'], $config)) { + response(trans('wrong extension') . AddErrorLocation(), 400)->send(); + exit; +} + +$file_name = $info['basename']; +$file_ext = $info['extension']; +$file_path = $path . $name; + + +// make sure the file exists +if ($ftp) { + header('Content-Type: application/octet-stream'); + header("Content-Transfer-Encoding: Binary"); + header("Content-disposition: attachment; filename=\"" . $file_name . "\""); + readfile($file_path); +} elseif (is_file($file_path) && is_readable($file_path)) { + if (!file_exists($path . $name)) { + response(trans('File_Not_Found') . AddErrorLocation(), 404)->send(); + exit; + } + + $size = filesize($file_path); + $file_name = rawurldecode($file_name); + + + if (function_exists('mime_content_type')) { + $mime_type = mime_content_type($file_path); + } elseif (function_exists('finfo_open')) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime_type = finfo_file($finfo, $file_path); + } else { + $mime_type = get_file_mime_type($file_path); + } + + + @ob_end_clean(); + if (ini_get('zlib.output_compression')) { + ini_set('zlib.output_compression', 'Off'); + } + header('Content-Type: ' . $mime_type); + header('Content-Disposition: attachment; filename="' . $file_name . '"'); + header("Content-Transfer-Encoding: binary"); + header('Accept-Ranges: bytes'); + + if (isset($_SERVER['HTTP_RANGE'])) { + list($a, $range) = explode("=", $_SERVER['HTTP_RANGE'], 2); + list($range) = explode(",", $range, 2); + list($range, $range_end) = explode("-", $range); + $range = intval($range); + if (!$range_end) { + $range_end = $size - 1; + } else { + $range_end = intval($range_end); + } + + $new_length = $range_end - $range + 1; + header("HTTP/1.1 206 Partial Content"); + header("Content-Length: $new_length"); + header("Content-Range: bytes $range-$range_end/$size"); + } else { + $new_length = $size; + header("Content-Length: " . $size); + } + + $chunksize = 1 * (1024 * 1024); + $bytes_send = 0; + + if ($file = fopen($file_path, 'r')) { + if (isset($_SERVER['HTTP_RANGE'])) { + fseek($file, $range); + } + + while (!feof($file) && + (!connection_aborted()) && + ($bytes_send < $new_length) + ) { + $buffer = fread($file, $chunksize); + echo $buffer; + flush(); + $bytes_send += strlen($buffer); + } + fclose($file); + } else { + die('Error - can not open file.'); + } + + die(); +} else { + // file does not exist + header("HTTP/1.0 404 Not Found"); +} + +exit; diff --git a/core/vendor/filemanager/img/clipboard_apply.png b/core/vendor/filemanager/img/clipboard_apply.png new file mode 100644 index 0000000..d470c44 Binary files /dev/null and b/core/vendor/filemanager/img/clipboard_apply.png differ diff --git a/core/vendor/filemanager/img/clipboard_clear.png b/core/vendor/filemanager/img/clipboard_clear.png new file mode 100644 index 0000000..e7fb903 Binary files /dev/null and b/core/vendor/filemanager/img/clipboard_clear.png differ diff --git a/core/vendor/filemanager/img/copy.png b/core/vendor/filemanager/img/copy.png new file mode 100644 index 0000000..e1d8911 Binary files /dev/null and b/core/vendor/filemanager/img/copy.png differ diff --git a/core/vendor/filemanager/img/cut.png b/core/vendor/filemanager/img/cut.png new file mode 100644 index 0000000..a139267 Binary files /dev/null and b/core/vendor/filemanager/img/cut.png differ diff --git a/core/vendor/filemanager/img/date.png b/core/vendor/filemanager/img/date.png new file mode 100644 index 0000000..ef310f4 Binary files /dev/null and b/core/vendor/filemanager/img/date.png differ diff --git a/core/vendor/filemanager/img/dimension.png b/core/vendor/filemanager/img/dimension.png new file mode 100644 index 0000000..43dcc10 Binary files /dev/null and b/core/vendor/filemanager/img/dimension.png differ diff --git a/core/vendor/filemanager/img/down.png b/core/vendor/filemanager/img/down.png new file mode 100644 index 0000000..7651122 Binary files /dev/null and b/core/vendor/filemanager/img/down.png differ diff --git a/core/vendor/filemanager/img/download.png b/core/vendor/filemanager/img/download.png new file mode 100644 index 0000000..76125f2 Binary files /dev/null and b/core/vendor/filemanager/img/download.png differ diff --git a/core/vendor/filemanager/img/duplicate.png b/core/vendor/filemanager/img/duplicate.png new file mode 100644 index 0000000..faa0f97 Binary files /dev/null and b/core/vendor/filemanager/img/duplicate.png differ diff --git a/core/vendor/filemanager/img/edit_img.png b/core/vendor/filemanager/img/edit_img.png new file mode 100644 index 0000000..ca55e58 Binary files /dev/null and b/core/vendor/filemanager/img/edit_img.png differ diff --git a/core/vendor/filemanager/img/file_edit.png b/core/vendor/filemanager/img/file_edit.png new file mode 100644 index 0000000..4bcd072 Binary files /dev/null and b/core/vendor/filemanager/img/file_edit.png differ diff --git a/core/vendor/filemanager/img/glyphicons-halflings-white.png b/core/vendor/filemanager/img/glyphicons-halflings-white.png new file mode 100644 index 0000000..a92969a Binary files /dev/null and b/core/vendor/filemanager/img/glyphicons-halflings-white.png differ diff --git a/core/vendor/filemanager/img/glyphicons-halflings.png b/core/vendor/filemanager/img/glyphicons-halflings.png new file mode 100644 index 0000000..f43139a Binary files /dev/null and b/core/vendor/filemanager/img/glyphicons-halflings.png differ diff --git a/core/vendor/filemanager/img/ico/ac3.jpg b/core/vendor/filemanager/img/ico/ac3.jpg new file mode 100644 index 0000000..3ce97c7 Binary files /dev/null and b/core/vendor/filemanager/img/ico/ac3.jpg differ diff --git a/core/vendor/filemanager/img/ico/accdb.jpg b/core/vendor/filemanager/img/ico/accdb.jpg new file mode 100644 index 0000000..4581e5e Binary files /dev/null and b/core/vendor/filemanager/img/ico/accdb.jpg differ diff --git a/core/vendor/filemanager/img/ico/ade.jpg b/core/vendor/filemanager/img/ico/ade.jpg new file mode 100644 index 0000000..b653b3d Binary files /dev/null and b/core/vendor/filemanager/img/ico/ade.jpg differ diff --git a/core/vendor/filemanager/img/ico/adp.jpg b/core/vendor/filemanager/img/ico/adp.jpg new file mode 100644 index 0000000..b653b3d Binary files /dev/null and b/core/vendor/filemanager/img/ico/adp.jpg differ diff --git a/core/vendor/filemanager/img/ico/ai.jpg b/core/vendor/filemanager/img/ico/ai.jpg new file mode 100644 index 0000000..e469be3 Binary files /dev/null and b/core/vendor/filemanager/img/ico/ai.jpg differ diff --git a/core/vendor/filemanager/img/ico/aiff.jpg b/core/vendor/filemanager/img/ico/aiff.jpg new file mode 100644 index 0000000..053ff30 Binary files /dev/null and b/core/vendor/filemanager/img/ico/aiff.jpg differ diff --git a/core/vendor/filemanager/img/ico/avi.jpg b/core/vendor/filemanager/img/ico/avi.jpg new file mode 100644 index 0000000..b9ddc2d Binary files /dev/null and b/core/vendor/filemanager/img/ico/avi.jpg differ diff --git a/core/vendor/filemanager/img/ico/bmp.jpg b/core/vendor/filemanager/img/ico/bmp.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/core/vendor/filemanager/img/ico/bmp.jpg differ diff --git a/core/vendor/filemanager/img/ico/c4d.jpg b/core/vendor/filemanager/img/ico/c4d.jpg new file mode 100644 index 0000000..62994da Binary files /dev/null and b/core/vendor/filemanager/img/ico/c4d.jpg differ diff --git a/core/vendor/filemanager/img/ico/css.jpg b/core/vendor/filemanager/img/ico/css.jpg new file mode 100644 index 0000000..e1673b0 Binary files /dev/null and b/core/vendor/filemanager/img/ico/css.jpg differ diff --git a/core/vendor/filemanager/img/ico/csv.jpg b/core/vendor/filemanager/img/ico/csv.jpg new file mode 100644 index 0000000..ca93201 Binary files /dev/null and b/core/vendor/filemanager/img/ico/csv.jpg differ diff --git a/core/vendor/filemanager/img/ico/default.jpg b/core/vendor/filemanager/img/ico/default.jpg new file mode 100644 index 0000000..94ca669 Binary files /dev/null and b/core/vendor/filemanager/img/ico/default.jpg differ diff --git a/core/vendor/filemanager/img/ico/dmg.jpg b/core/vendor/filemanager/img/ico/dmg.jpg new file mode 100644 index 0000000..2494e87 Binary files /dev/null and b/core/vendor/filemanager/img/ico/dmg.jpg differ diff --git a/core/vendor/filemanager/img/ico/doc.jpg b/core/vendor/filemanager/img/ico/doc.jpg new file mode 100644 index 0000000..c0e14b3 Binary files /dev/null and b/core/vendor/filemanager/img/ico/doc.jpg differ diff --git a/core/vendor/filemanager/img/ico/docx.jpg b/core/vendor/filemanager/img/ico/docx.jpg new file mode 100644 index 0000000..c0e14b3 Binary files /dev/null and b/core/vendor/filemanager/img/ico/docx.jpg differ diff --git a/core/vendor/filemanager/img/ico/dwg.jpg b/core/vendor/filemanager/img/ico/dwg.jpg new file mode 100644 index 0000000..bf5d63f Binary files /dev/null and b/core/vendor/filemanager/img/ico/dwg.jpg differ diff --git a/core/vendor/filemanager/img/ico/dxf.jpg b/core/vendor/filemanager/img/ico/dxf.jpg new file mode 100644 index 0000000..7041cbe Binary files /dev/null and b/core/vendor/filemanager/img/ico/dxf.jpg differ diff --git a/core/vendor/filemanager/img/ico/favicon.ico b/core/vendor/filemanager/img/ico/favicon.ico new file mode 100644 index 0000000..7383707 Binary files /dev/null and b/core/vendor/filemanager/img/ico/favicon.ico differ diff --git a/core/vendor/filemanager/img/ico/fla.jpg b/core/vendor/filemanager/img/ico/fla.jpg new file mode 100644 index 0000000..41bd2ec Binary files /dev/null and b/core/vendor/filemanager/img/ico/fla.jpg differ diff --git a/core/vendor/filemanager/img/ico/flv.jpg b/core/vendor/filemanager/img/ico/flv.jpg new file mode 100644 index 0000000..75aff12 Binary files /dev/null and b/core/vendor/filemanager/img/ico/flv.jpg differ diff --git a/core/vendor/filemanager/img/ico/folder.png b/core/vendor/filemanager/img/ico/folder.png new file mode 100644 index 0000000..d56b85e Binary files /dev/null and b/core/vendor/filemanager/img/ico/folder.png differ diff --git a/core/vendor/filemanager/img/ico/folder_back.png b/core/vendor/filemanager/img/ico/folder_back.png new file mode 100644 index 0000000..dc0786e Binary files /dev/null and b/core/vendor/filemanager/img/ico/folder_back.png differ diff --git a/core/vendor/filemanager/img/ico/gif.jpg b/core/vendor/filemanager/img/ico/gif.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/core/vendor/filemanager/img/ico/gif.jpg differ diff --git a/core/vendor/filemanager/img/ico/gz.jpg b/core/vendor/filemanager/img/ico/gz.jpg new file mode 100644 index 0000000..36d1591 Binary files /dev/null and b/core/vendor/filemanager/img/ico/gz.jpg differ diff --git a/core/vendor/filemanager/img/ico/html.jpg b/core/vendor/filemanager/img/ico/html.jpg new file mode 100644 index 0000000..a1af20d Binary files /dev/null and b/core/vendor/filemanager/img/ico/html.jpg differ diff --git a/core/vendor/filemanager/img/ico/iso.jpg b/core/vendor/filemanager/img/ico/iso.jpg new file mode 100644 index 0000000..379f506 Binary files /dev/null and b/core/vendor/filemanager/img/ico/iso.jpg differ diff --git a/core/vendor/filemanager/img/ico/jpeg.jpg b/core/vendor/filemanager/img/ico/jpeg.jpg new file mode 100644 index 0000000..0be5ac5 Binary files /dev/null and b/core/vendor/filemanager/img/ico/jpeg.jpg differ diff --git a/core/vendor/filemanager/img/ico/jpg.jpg b/core/vendor/filemanager/img/ico/jpg.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/core/vendor/filemanager/img/ico/jpg.jpg differ diff --git a/core/vendor/filemanager/img/ico/log.jpg b/core/vendor/filemanager/img/ico/log.jpg new file mode 100644 index 0000000..ec6d3e3 Binary files /dev/null and b/core/vendor/filemanager/img/ico/log.jpg differ diff --git a/core/vendor/filemanager/img/ico/m4a.jpg b/core/vendor/filemanager/img/ico/m4a.jpg new file mode 100644 index 0000000..5c6417c Binary files /dev/null and b/core/vendor/filemanager/img/ico/m4a.jpg differ diff --git a/core/vendor/filemanager/img/ico/mdb.jpg b/core/vendor/filemanager/img/ico/mdb.jpg new file mode 100644 index 0000000..4581e5e Binary files /dev/null and b/core/vendor/filemanager/img/ico/mdb.jpg differ diff --git a/core/vendor/filemanager/img/ico/mid.jpg b/core/vendor/filemanager/img/ico/mid.jpg new file mode 100644 index 0000000..176cd71 Binary files /dev/null and b/core/vendor/filemanager/img/ico/mid.jpg differ diff --git a/core/vendor/filemanager/img/ico/mov.jpg b/core/vendor/filemanager/img/ico/mov.jpg new file mode 100644 index 0000000..78a2ffa Binary files /dev/null and b/core/vendor/filemanager/img/ico/mov.jpg differ diff --git a/core/vendor/filemanager/img/ico/mp3.jpg b/core/vendor/filemanager/img/ico/mp3.jpg new file mode 100644 index 0000000..b79ba99 Binary files /dev/null and b/core/vendor/filemanager/img/ico/mp3.jpg differ diff --git a/core/vendor/filemanager/img/ico/mp4.jpg b/core/vendor/filemanager/img/ico/mp4.jpg new file mode 100644 index 0000000..50184ef Binary files /dev/null and b/core/vendor/filemanager/img/ico/mp4.jpg differ diff --git a/core/vendor/filemanager/img/ico/mpeg.jpg b/core/vendor/filemanager/img/ico/mpeg.jpg new file mode 100644 index 0000000..50f99ff Binary files /dev/null and b/core/vendor/filemanager/img/ico/mpeg.jpg differ diff --git a/core/vendor/filemanager/img/ico/mpg.jpg b/core/vendor/filemanager/img/ico/mpg.jpg new file mode 100644 index 0000000..daa2bc4 Binary files /dev/null and b/core/vendor/filemanager/img/ico/mpg.jpg differ diff --git a/core/vendor/filemanager/img/ico/odb.jpg b/core/vendor/filemanager/img/ico/odb.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/core/vendor/filemanager/img/ico/odb.jpg differ diff --git a/core/vendor/filemanager/img/ico/odf.jpg b/core/vendor/filemanager/img/ico/odf.jpg new file mode 100644 index 0000000..da290dc Binary files /dev/null and b/core/vendor/filemanager/img/ico/odf.jpg differ diff --git a/core/vendor/filemanager/img/ico/odg.jpg b/core/vendor/filemanager/img/ico/odg.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/core/vendor/filemanager/img/ico/odg.jpg differ diff --git a/core/vendor/filemanager/img/ico/odp.jpg b/core/vendor/filemanager/img/ico/odp.jpg new file mode 100644 index 0000000..da290dc Binary files /dev/null and b/core/vendor/filemanager/img/ico/odp.jpg differ diff --git a/core/vendor/filemanager/img/ico/ods.jpg b/core/vendor/filemanager/img/ico/ods.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/core/vendor/filemanager/img/ico/ods.jpg differ diff --git a/core/vendor/filemanager/img/ico/odt.jpg b/core/vendor/filemanager/img/ico/odt.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/core/vendor/filemanager/img/ico/odt.jpg differ diff --git a/core/vendor/filemanager/img/ico/ogg.jpg b/core/vendor/filemanager/img/ico/ogg.jpg new file mode 100644 index 0000000..e20ab2f Binary files /dev/null and b/core/vendor/filemanager/img/ico/ogg.jpg differ diff --git a/core/vendor/filemanager/img/ico/otg.jpg b/core/vendor/filemanager/img/ico/otg.jpg new file mode 100644 index 0000000..af44482 Binary files /dev/null and b/core/vendor/filemanager/img/ico/otg.jpg differ diff --git a/core/vendor/filemanager/img/ico/otp.jpg b/core/vendor/filemanager/img/ico/otp.jpg new file mode 100644 index 0000000..802e78c Binary files /dev/null and b/core/vendor/filemanager/img/ico/otp.jpg differ diff --git a/core/vendor/filemanager/img/ico/ots.jpg b/core/vendor/filemanager/img/ico/ots.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/core/vendor/filemanager/img/ico/ots.jpg differ diff --git a/core/vendor/filemanager/img/ico/ott.jpg b/core/vendor/filemanager/img/ico/ott.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/core/vendor/filemanager/img/ico/ott.jpg differ diff --git a/core/vendor/filemanager/img/ico/pdf.jpg b/core/vendor/filemanager/img/ico/pdf.jpg new file mode 100644 index 0000000..2080921 Binary files /dev/null and b/core/vendor/filemanager/img/ico/pdf.jpg differ diff --git a/core/vendor/filemanager/img/ico/png.jpg b/core/vendor/filemanager/img/ico/png.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/core/vendor/filemanager/img/ico/png.jpg differ diff --git a/core/vendor/filemanager/img/ico/ppt.jpg b/core/vendor/filemanager/img/ico/ppt.jpg new file mode 100644 index 0000000..aa13f73 Binary files /dev/null and b/core/vendor/filemanager/img/ico/ppt.jpg differ diff --git a/core/vendor/filemanager/img/ico/pptx.jpg b/core/vendor/filemanager/img/ico/pptx.jpg new file mode 100644 index 0000000..8504969 Binary files /dev/null and b/core/vendor/filemanager/img/ico/pptx.jpg differ diff --git a/core/vendor/filemanager/img/ico/psd.jpg b/core/vendor/filemanager/img/ico/psd.jpg new file mode 100644 index 0000000..53028c5 Binary files /dev/null and b/core/vendor/filemanager/img/ico/psd.jpg differ diff --git a/core/vendor/filemanager/img/ico/rar.jpg b/core/vendor/filemanager/img/ico/rar.jpg new file mode 100644 index 0000000..36d1591 Binary files /dev/null and b/core/vendor/filemanager/img/ico/rar.jpg differ diff --git a/core/vendor/filemanager/img/ico/rtf.jpg b/core/vendor/filemanager/img/ico/rtf.jpg new file mode 100644 index 0000000..c0e14b3 Binary files /dev/null and b/core/vendor/filemanager/img/ico/rtf.jpg differ diff --git a/core/vendor/filemanager/img/ico/skp.jpg b/core/vendor/filemanager/img/ico/skp.jpg new file mode 100644 index 0000000..6ebcde0 Binary files /dev/null and b/core/vendor/filemanager/img/ico/skp.jpg differ diff --git a/core/vendor/filemanager/img/ico/sql.jpg b/core/vendor/filemanager/img/ico/sql.jpg new file mode 100644 index 0000000..46794ad Binary files /dev/null and b/core/vendor/filemanager/img/ico/sql.jpg differ diff --git a/core/vendor/filemanager/img/ico/stp.jpg b/core/vendor/filemanager/img/ico/stp.jpg new file mode 100644 index 0000000..cab6077 Binary files /dev/null and b/core/vendor/filemanager/img/ico/stp.jpg differ diff --git a/core/vendor/filemanager/img/ico/svg.jpg b/core/vendor/filemanager/img/ico/svg.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/core/vendor/filemanager/img/ico/svg.jpg differ diff --git a/core/vendor/filemanager/img/ico/tar.jpg b/core/vendor/filemanager/img/ico/tar.jpg new file mode 100644 index 0000000..665cd03 Binary files /dev/null and b/core/vendor/filemanager/img/ico/tar.jpg differ diff --git a/core/vendor/filemanager/img/ico/tiff.jpg b/core/vendor/filemanager/img/ico/tiff.jpg new file mode 100644 index 0000000..afe2cde Binary files /dev/null and b/core/vendor/filemanager/img/ico/tiff.jpg differ diff --git a/core/vendor/filemanager/img/ico/txt.jpg b/core/vendor/filemanager/img/ico/txt.jpg new file mode 100644 index 0000000..ec6d3e3 Binary files /dev/null and b/core/vendor/filemanager/img/ico/txt.jpg differ diff --git a/core/vendor/filemanager/img/ico/vwx.jpg b/core/vendor/filemanager/img/ico/vwx.jpg new file mode 100644 index 0000000..c56cad7 Binary files /dev/null and b/core/vendor/filemanager/img/ico/vwx.jpg differ diff --git a/core/vendor/filemanager/img/ico/wav.jpg b/core/vendor/filemanager/img/ico/wav.jpg new file mode 100644 index 0000000..e20ab2f Binary files /dev/null and b/core/vendor/filemanager/img/ico/wav.jpg differ diff --git a/core/vendor/filemanager/img/ico/webm.jpg b/core/vendor/filemanager/img/ico/webm.jpg new file mode 100644 index 0000000..b9ddc2d Binary files /dev/null and b/core/vendor/filemanager/img/ico/webm.jpg differ diff --git a/core/vendor/filemanager/img/ico/wma.jpg b/core/vendor/filemanager/img/ico/wma.jpg new file mode 100644 index 0000000..b9ddc2d Binary files /dev/null and b/core/vendor/filemanager/img/ico/wma.jpg differ diff --git a/core/vendor/filemanager/img/ico/xhtml.jpg b/core/vendor/filemanager/img/ico/xhtml.jpg new file mode 100644 index 0000000..5979c91 Binary files /dev/null and b/core/vendor/filemanager/img/ico/xhtml.jpg differ diff --git a/core/vendor/filemanager/img/ico/xls.jpg b/core/vendor/filemanager/img/ico/xls.jpg new file mode 100644 index 0000000..4f656f7 Binary files /dev/null and b/core/vendor/filemanager/img/ico/xls.jpg differ diff --git a/core/vendor/filemanager/img/ico/xlsx.jpg b/core/vendor/filemanager/img/ico/xlsx.jpg new file mode 100644 index 0000000..b4c33ff Binary files /dev/null and b/core/vendor/filemanager/img/ico/xlsx.jpg differ diff --git a/core/vendor/filemanager/img/ico/xml.jpg b/core/vendor/filemanager/img/ico/xml.jpg new file mode 100644 index 0000000..73301e7 Binary files /dev/null and b/core/vendor/filemanager/img/ico/xml.jpg differ diff --git a/core/vendor/filemanager/img/ico/zip.jpg b/core/vendor/filemanager/img/ico/zip.jpg new file mode 100644 index 0000000..36d1591 Binary files /dev/null and b/core/vendor/filemanager/img/ico/zip.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ac3.jpg b/core/vendor/filemanager/img/ico_dark/ac3.jpg new file mode 100644 index 0000000..0530f28 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ac3.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/accdb.jpg b/core/vendor/filemanager/img/ico_dark/accdb.jpg new file mode 100644 index 0000000..13607b1 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/accdb.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ade.jpg b/core/vendor/filemanager/img/ico_dark/ade.jpg new file mode 100644 index 0000000..92d67d9 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ade.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/adp.jpg b/core/vendor/filemanager/img/ico_dark/adp.jpg new file mode 100644 index 0000000..92d67d9 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/adp.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ai.jpg b/core/vendor/filemanager/img/ico_dark/ai.jpg new file mode 100644 index 0000000..b7c353b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ai.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/aiff.jpg b/core/vendor/filemanager/img/ico_dark/aiff.jpg new file mode 100644 index 0000000..f0422da Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/aiff.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/avi.jpg b/core/vendor/filemanager/img/ico_dark/avi.jpg new file mode 100644 index 0000000..9dfa9fe Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/avi.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/bmp.jpg b/core/vendor/filemanager/img/ico_dark/bmp.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/bmp.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/css.jpg b/core/vendor/filemanager/img/ico_dark/css.jpg new file mode 100644 index 0000000..8c80e15 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/css.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/csv.jpg b/core/vendor/filemanager/img/ico_dark/csv.jpg new file mode 100644 index 0000000..b81a32b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/csv.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/default.jpg b/core/vendor/filemanager/img/ico_dark/default.jpg new file mode 100644 index 0000000..433adcf Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/default.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/dmg.jpg b/core/vendor/filemanager/img/ico_dark/dmg.jpg new file mode 100644 index 0000000..509039e Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/dmg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/doc.jpg b/core/vendor/filemanager/img/ico_dark/doc.jpg new file mode 100644 index 0000000..122d382 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/doc.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/docx.jpg b/core/vendor/filemanager/img/ico_dark/docx.jpg new file mode 100644 index 0000000..9b0bc4b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/docx.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/favicon.ico b/core/vendor/filemanager/img/ico_dark/favicon.ico new file mode 100644 index 0000000..7383707 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/favicon.ico differ diff --git a/core/vendor/filemanager/img/ico_dark/fla.jpg b/core/vendor/filemanager/img/ico_dark/fla.jpg new file mode 100644 index 0000000..e8757e6 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/fla.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/flv.jpg b/core/vendor/filemanager/img/ico_dark/flv.jpg new file mode 100644 index 0000000..c53f135 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/flv.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/folder.png b/core/vendor/filemanager/img/ico_dark/folder.png new file mode 100644 index 0000000..a5fac96 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/folder.png differ diff --git a/core/vendor/filemanager/img/ico_dark/folder_back.png b/core/vendor/filemanager/img/ico_dark/folder_back.png new file mode 100644 index 0000000..dc0786e Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/folder_back.png differ diff --git a/core/vendor/filemanager/img/ico_dark/gif.jpg b/core/vendor/filemanager/img/ico_dark/gif.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/gif.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/gz.jpg b/core/vendor/filemanager/img/ico_dark/gz.jpg new file mode 100644 index 0000000..414d5da Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/gz.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/html.jpg b/core/vendor/filemanager/img/ico_dark/html.jpg new file mode 100644 index 0000000..6bb6743 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/html.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/iso.jpg b/core/vendor/filemanager/img/ico_dark/iso.jpg new file mode 100644 index 0000000..aaf5d5b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/iso.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/jpeg.jpg b/core/vendor/filemanager/img/ico_dark/jpeg.jpg new file mode 100644 index 0000000..b4f258a Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/jpeg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/jpg.jpg b/core/vendor/filemanager/img/ico_dark/jpg.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/jpg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/log.jpg b/core/vendor/filemanager/img/ico_dark/log.jpg new file mode 100644 index 0000000..c2fe0e7 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/log.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/m4a.jpg b/core/vendor/filemanager/img/ico_dark/m4a.jpg new file mode 100644 index 0000000..f3997e6 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/m4a.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mdb.jpg b/core/vendor/filemanager/img/ico_dark/mdb.jpg new file mode 100644 index 0000000..13607b1 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mdb.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mid.jpg b/core/vendor/filemanager/img/ico_dark/mid.jpg new file mode 100644 index 0000000..966b39c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mid.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mov.jpg b/core/vendor/filemanager/img/ico_dark/mov.jpg new file mode 100644 index 0000000..2e98f5e Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mov.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mp3.jpg b/core/vendor/filemanager/img/ico_dark/mp3.jpg new file mode 100644 index 0000000..fd66149 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mp3.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mp4.jpg b/core/vendor/filemanager/img/ico_dark/mp4.jpg new file mode 100644 index 0000000..0b045ed Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mp4.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mpeg.jpg b/core/vendor/filemanager/img/ico_dark/mpeg.jpg new file mode 100644 index 0000000..f075cc3 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mpeg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/mpg.jpg b/core/vendor/filemanager/img/ico_dark/mpg.jpg new file mode 100644 index 0000000..0da2aad Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/mpg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/odb.jpg b/core/vendor/filemanager/img/ico_dark/odb.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/odb.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/odf.jpg b/core/vendor/filemanager/img/ico_dark/odf.jpg new file mode 100644 index 0000000..8f40c74 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/odf.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/odg.jpg b/core/vendor/filemanager/img/ico_dark/odg.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/odg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/odp.jpg b/core/vendor/filemanager/img/ico_dark/odp.jpg new file mode 100644 index 0000000..8f40c74 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/odp.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ods.jpg b/core/vendor/filemanager/img/ico_dark/ods.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ods.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/odt.jpg b/core/vendor/filemanager/img/ico_dark/odt.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/odt.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ogg.jpg b/core/vendor/filemanager/img/ico_dark/ogg.jpg new file mode 100644 index 0000000..23ed22b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ogg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/otg.jpg b/core/vendor/filemanager/img/ico_dark/otg.jpg new file mode 100644 index 0000000..b36cae5 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/otg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/otp.jpg b/core/vendor/filemanager/img/ico_dark/otp.jpg new file mode 100644 index 0000000..fc995f5 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/otp.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ots.jpg b/core/vendor/filemanager/img/ico_dark/ots.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ots.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ott.jpg b/core/vendor/filemanager/img/ico_dark/ott.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ott.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/pdf.jpg b/core/vendor/filemanager/img/ico_dark/pdf.jpg new file mode 100644 index 0000000..809b5e6 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/pdf.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/png.jpg b/core/vendor/filemanager/img/ico_dark/png.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/png.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/ppt.jpg b/core/vendor/filemanager/img/ico_dark/ppt.jpg new file mode 100644 index 0000000..b87590a Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/ppt.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/pptx.jpg b/core/vendor/filemanager/img/ico_dark/pptx.jpg new file mode 100644 index 0000000..62cbe2f Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/pptx.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/psd.jpg b/core/vendor/filemanager/img/ico_dark/psd.jpg new file mode 100644 index 0000000..312af5c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/psd.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/rar.jpg b/core/vendor/filemanager/img/ico_dark/rar.jpg new file mode 100644 index 0000000..6057cbc Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/rar.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/rtf.jpg b/core/vendor/filemanager/img/ico_dark/rtf.jpg new file mode 100644 index 0000000..122d382 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/rtf.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/sql.jpg b/core/vendor/filemanager/img/ico_dark/sql.jpg new file mode 100644 index 0000000..73485f1 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/sql.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/svg.jpg b/core/vendor/filemanager/img/ico_dark/svg.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/svg.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/tar.jpg b/core/vendor/filemanager/img/ico_dark/tar.jpg new file mode 100644 index 0000000..bb5adaf Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/tar.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/tiff.jpg b/core/vendor/filemanager/img/ico_dark/tiff.jpg new file mode 100644 index 0000000..e25985d Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/tiff.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/txt.jpg b/core/vendor/filemanager/img/ico_dark/txt.jpg new file mode 100644 index 0000000..c2fe0e7 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/txt.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/wav.jpg b/core/vendor/filemanager/img/ico_dark/wav.jpg new file mode 100644 index 0000000..23ed22b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/wav.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/webm.jpg b/core/vendor/filemanager/img/ico_dark/webm.jpg new file mode 100644 index 0000000..b6c568c Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/webm.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/wma.jpg b/core/vendor/filemanager/img/ico_dark/wma.jpg new file mode 100644 index 0000000..9dfa9fe Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/wma.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/xhtml.jpg b/core/vendor/filemanager/img/ico_dark/xhtml.jpg new file mode 100644 index 0000000..3420b33 Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/xhtml.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/xls.jpg b/core/vendor/filemanager/img/ico_dark/xls.jpg new file mode 100644 index 0000000..62e21dd Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/xls.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/xlsx.jpg b/core/vendor/filemanager/img/ico_dark/xlsx.jpg new file mode 100644 index 0000000..c1728bc Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/xlsx.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/xml.jpg b/core/vendor/filemanager/img/ico_dark/xml.jpg new file mode 100644 index 0000000..7b2d75b Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/xml.jpg differ diff --git a/core/vendor/filemanager/img/ico_dark/zip.jpg b/core/vendor/filemanager/img/ico_dark/zip.jpg new file mode 100644 index 0000000..414d5da Binary files /dev/null and b/core/vendor/filemanager/img/ico_dark/zip.jpg differ diff --git a/core/vendor/filemanager/img/info.png b/core/vendor/filemanager/img/info.png new file mode 100644 index 0000000..6baffc3 Binary files /dev/null and b/core/vendor/filemanager/img/info.png differ diff --git a/core/vendor/filemanager/img/key.png b/core/vendor/filemanager/img/key.png new file mode 100644 index 0000000..463d082 Binary files /dev/null and b/core/vendor/filemanager/img/key.png differ diff --git a/core/vendor/filemanager/img/label.png b/core/vendor/filemanager/img/label.png new file mode 100644 index 0000000..fa65317 Binary files /dev/null and b/core/vendor/filemanager/img/label.png differ diff --git a/core/vendor/filemanager/img/loading.gif b/core/vendor/filemanager/img/loading.gif new file mode 100644 index 0000000..6fba776 Binary files /dev/null and b/core/vendor/filemanager/img/loading.gif differ diff --git a/core/vendor/filemanager/img/logo.png b/core/vendor/filemanager/img/logo.png new file mode 100644 index 0000000..2d2c0b2 Binary files /dev/null and b/core/vendor/filemanager/img/logo.png differ diff --git a/core/vendor/filemanager/img/preview.png b/core/vendor/filemanager/img/preview.png new file mode 100644 index 0000000..b124752 Binary files /dev/null and b/core/vendor/filemanager/img/preview.png differ diff --git a/core/vendor/filemanager/img/processing.gif b/core/vendor/filemanager/img/processing.gif new file mode 100644 index 0000000..7c99504 Binary files /dev/null and b/core/vendor/filemanager/img/processing.gif differ diff --git a/core/vendor/filemanager/img/rename.png b/core/vendor/filemanager/img/rename.png new file mode 100644 index 0000000..a425ee7 Binary files /dev/null and b/core/vendor/filemanager/img/rename.png differ diff --git a/core/vendor/filemanager/img/size.png b/core/vendor/filemanager/img/size.png new file mode 100644 index 0000000..fcc302f Binary files /dev/null and b/core/vendor/filemanager/img/size.png differ diff --git a/core/vendor/filemanager/img/sort.png b/core/vendor/filemanager/img/sort.png new file mode 100644 index 0000000..0a029be Binary files /dev/null and b/core/vendor/filemanager/img/sort.png differ diff --git a/core/vendor/filemanager/img/storing_animation.gif b/core/vendor/filemanager/img/storing_animation.gif new file mode 100644 index 0000000..eca3a53 Binary files /dev/null and b/core/vendor/filemanager/img/storing_animation.gif differ diff --git a/core/vendor/filemanager/img/trans.jpg b/core/vendor/filemanager/img/trans.jpg new file mode 100644 index 0000000..147175e Binary files /dev/null and b/core/vendor/filemanager/img/trans.jpg differ diff --git a/core/vendor/filemanager/img/up.png b/core/vendor/filemanager/img/up.png new file mode 100644 index 0000000..680dade Binary files /dev/null and b/core/vendor/filemanager/img/up.png differ diff --git a/core/vendor/filemanager/img/upload.png b/core/vendor/filemanager/img/upload.png new file mode 100644 index 0000000..7380e8f Binary files /dev/null and b/core/vendor/filemanager/img/upload.png differ diff --git a/core/vendor/filemanager/img/url.png b/core/vendor/filemanager/img/url.png new file mode 100644 index 0000000..f18499a Binary files /dev/null and b/core/vendor/filemanager/img/url.png differ diff --git a/core/vendor/filemanager/img/zip.png b/core/vendor/filemanager/img/zip.png new file mode 100644 index 0000000..9ef55c7 Binary files /dev/null and b/core/vendor/filemanager/img/zip.png differ diff --git a/core/vendor/filemanager/inc.json b/core/vendor/filemanager/inc.json new file mode 100644 index 0000000..e9a0bcc --- /dev/null +++ b/core/vendor/filemanager/inc.json @@ -0,0 +1,3 @@ +[ + "callback.js" +] \ No newline at end of file diff --git a/core/vendor/filemanager/include/FtpClient.php b/core/vendor/filemanager/include/FtpClient.php new file mode 100644 index 0000000..4283b8a --- /dev/null +++ b/core/vendor/filemanager/include/FtpClient.php @@ -0,0 +1,884 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @copyright Nicolas Tallefourtane http://nicolab.net + */ +namespace FtpClient; + +use \Countable; + +/** + * The FTP and SSL-FTP client for PHP. + * + * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded + * @method bool cdup() cdup() Changes to the parent directory + * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server + * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP + * @method bool delete() delete(string $path) Deletes a file on the FTP server + * @method bool exec() exec(string $command) Requests execution of a command on the FTP server + * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file + * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server + * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream + * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server + * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file + * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking) + * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking) + * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking) + * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking) + * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking) + * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off + * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server + * @method string pwd() pwd() Returns the current directory name + * @method bool quit() quit() Closes an FTP connection + * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server + * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server + * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options + * @method bool site() site(string $command) Sends a SITE command to the server + * @method int size() size(string $remote_file) Returns the size of the given file + * @method string systype() systype() Returns the system type identifier of the remote FTP server + * + * @author Nicolas Tallefourtane + */ +class FtpClient implements Countable +{ + /** + * The connection with the server. + * + * @var resource + */ + protected $conn; + + /** + * PHP FTP functions wrapper. + * + * @var FtpWrapper + */ + private $ftp; + + /** + * Constructor. + * + * @param resource|null $connection + * @throws FtpException If FTP extension is not loaded. + */ + public function __construct($connection = null) + { + if (!extension_loaded('ftp')) { + throw new FtpException('FTP extension is not loaded!'); + } + + if ($connection) { + $this->conn = $connection; + } + + $this->setWrapper(new FtpWrapper($this->conn)); + } + + /** + * Close the connection when the object is destroyed. + */ + public function __destruct() + { + if ($this->conn) { + $this->ftp->close(); + } + } + + /** + * Call an internal method or a FTP method handled by the wrapper. + * + * Wrap the FTP PHP functions to call as method of FtpClient object. + * The connection is automaticaly passed to the FTP PHP functions. + * + * @param string $method + * @param array $arguments + * @return mixed + * @throws FtpException When the function is not valid + */ + public function __call($method, array $arguments) + { + return $this->ftp->__call($method, $arguments); + } + + /** + * Overwrites the PHP limit + * + * @param string|null $memory The memory limit, if null is not modified + * @param int $time_limit The max execution time, unlimited by default + * @param bool $ignore_user_abort Ignore user abort, true by default + * @return FtpClient + */ + public function setPhpLimit($memory = null, $time_limit = 0, $ignore_user_abort = true) + { + if (null !== $memory) { + ini_set('memory_limit', $memory); + } + + ignore_user_abort(true); + set_time_limit($time_limit); + + return $this; + } + + /** + * Get the help information of the remote FTP server. + * + * @return array + */ + public function help() + { + return $this->ftp->raw('help'); + } + + /** + * Open a FTP connection. + * + * @param string $host + * @param bool $ssl + * @param int $port + * @param int $timeout + * + * @return FTPClient + * @throws FtpException If unable to connect + */ + public function connect($host, $ssl = false, $port = 21, $timeout = 90) + { + if ($ssl) { + $this->conn = @$this->ftp->ssl_connect($host, $port, $timeout); + } else { + $this->conn = @$this->ftp->connect($host, $port, $timeout); + } + + if (!$this->conn) { + throw new FtpException('Unable to connect'); + } + + return $this; + } + + /** + * Closes the current FTP connection. + * + * @return bool + */ + public function close() + { + if ($this->conn) { + $this->ftp->close(); + $this->conn = null; + } + } + + /** + * Get the connection with the server. + * + * @return resource + */ + public function getConnection() + { + return $this->conn; + } + + /** + * Get the wrapper. + * + * @return FtpWrapper + */ + public function getWrapper() + { + return $this->ftp; + } + + /** + * Logs in to an FTP connection. + * + * @param string $username + * @param string $password + * + * @return FtpClient + * @throws FtpException If the login is incorrect + */ + public function login($username = 'anonymous', $password = '') + { + $result = $this->ftp->login($username, $password); + + if ($result === false) { + throw new FtpException('Login incorrect'); + } + + return $this; + } + + /** + * Returns the last modified time of the given file. + * Return -1 on error + * + * @param string $remoteFile + * @param string|null $format + * + * @return int + */ + public function modifiedTime($remoteFile, $format = null) + { + $time = $this->ftp->mdtm($remoteFile); + + if ($time !== -1 && $format !== null) { + return date($format, $time); + } + + return $time; + } + + /** + * Changes to the parent directory. + * + * @throws FtpException + * @return FtpClient + */ + public function up() + { + $result = @$this->ftp->cdup(); + + if ($result === false) { + throw new FtpException('Unable to get parent folder'); + } + + return $this; + } + + /** + * Returns a list of files in the given directory. + * + * @param string $directory The directory, by default is "." the current directory + * @param bool $recursive + * @param callable $filter A callable to filter the result, by default is asort() PHP function. + * The result is passed in array argument, + * must take the argument by reference ! + * The callable should proceed with the reference array + * because is the behavior of several PHP sorting + * functions (by reference ensure directly the compatibility + * with all PHP sorting functions). + * + * @return array + * @throws FtpException If unable to list the directory + */ + public function nlist($directory = '.', $recursive = false, $filter = 'sort') + { + if (!$this->isDir($directory)) { + throw new FtpException('"'.$directory.'" is not a directory'); + } + + $files = $this->ftp->nlist($directory); + + if ($files === false) { + throw new FtpException('Unable to list directory'); + } + + $result = array(); + $dir_len = strlen($directory); + + // if it's the current + if (false !== ($kdot = array_search('.', $files))) { + unset($files[$kdot]); + } + + // if it's the parent + if(false !== ($kdot = array_search('..', $files))) { + unset($files[$kdot]); + } + + if (!$recursive) { + foreach ($files as $file) { + $result[] = $directory.'/'.$file; + } + + // working with the reference (behavior of several PHP sorting functions) + $filter($result); + + return $result; + } + + // utils for recursion + $flatten = function (array $arr) use (&$flatten) { + + $flat = []; + + foreach ($arr as $k => $v) { + if (is_array($v)) { + $flat = array_merge($flat, $flatten($v)); + } else { + $flat[] = $v; + } + } + + return $flat; + }; + + foreach ($files as $file) { + $file = $directory.'/'.$file; + + // if contains the root path (behavior of the recursivity) + if (0 === strpos($file, $directory, $dir_len)) { + $file = substr($file, $dir_len); + } + + if ($this->isDir($file)) { + $result[] = $file; + $items = $flatten($this->nlist($file, true, $filter)); + + foreach ($items as $item) { + $result[] = $item; + } + + } else { + $result[] = $file; + } + } + + $result = array_unique($result); + + $filter($result); + + return $result; + } + + /** + * Creates a directory. + * + * @see FtpClient::rmdir() + * @see FtpClient::remove() + * @see FtpClient::put() + * @see FtpClient::putAll() + * + * @param string $directory The directory + * @param bool $recursive + * @return array + */ + public function mkdir($directory, $recursive = false) + { + if (!$recursive or $this->isDir($directory)) { + return $this->ftp->mkdir($directory); + } + + $result = false; + $pwd = $this->ftp->pwd(); + $parts = explode('/', $directory); + + foreach ($parts as $part) { + + if (!@$this->ftp->chdir($part)) { + $result = $this->ftp->mkdir($part); + $this->ftp->chdir($part); + } + } + + $this->ftp->chdir($pwd); + + return $result; + } + + /** + * Remove a directory. + * + * @see FtpClient::mkdir() + * @see FtpClient::cleanDir() + * @see FtpClient::remove() + * @see FtpClient::delete() + * @param string $directory + * @param bool $recursive Forces deletion if the directory is not empty + * @return bool + * @throws FtpException If unable to list the directory to remove + */ + public function rmdir($directory, $recursive = true) + { + if ($recursive) { + $files = $this->nlist($directory, false, 'rsort'); + + // remove children + foreach ($files as $file) { + $this->remove($file, true); + } + } + + // remove the directory + return $this->ftp->rmdir($directory); + } + + /** + * Empty directory. + * + * @see FtpClient::remove() + * @see FtpClient::delete() + * @see FtpClient::rmdir() + * + * @param string $directory + * @return bool + */ + public function cleanDir($directory) + { + if(!$files = $this->nlist($directory)) { + return $this->isEmpty($directory); + } + + // remove children + foreach ($files as $file) { + $this->remove($file, true); + } + + return $this->isEmpty($directory); + } + + /** + * Remove a file or a directory. + * + * @see FtpClient::rmdir() + * @see FtpClient::cleanDir() + * @see FtpClient::delete() + * @param string $path The path of the file or directory to remove + * @param bool $recursive Is effective only if $path is a directory, {@see FtpClient::rmdir()} + * @return bool + */ + public function remove($path, $recursive = false) + { + try { + if (@$this->ftp->delete($path) + or ($this->isDir($path) and @$this->rmdir($path, $recursive))) { + return true; + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Check if a directory exist. + * + * @param string $directory + * @return bool + * @throws FtpException + */ + public function isDir($directory) + { + $pwd = $this->ftp->pwd(); + + if ($pwd === false) { + throw new FtpException('Unable to resolve the current directory'); + } + + if (@$this->ftp->chdir($directory)) { + $this->ftp->chdir($pwd); + return true; + } + + $this->ftp->chdir($pwd); + + return false; + } + + /** + * Check if a directory is empty. + * + * @param string $directory + * @return bool + */ + public function isEmpty($directory) + { + return $this->count($directory, null, false) === 0 ? true : false; + } + + /** + * Scan a directory and returns the details of each item. + * + * @see FtpClient::nlist() + * @see FtpClient::rawlist() + * @see FtpClient::parseRawList() + * @see FtpClient::dirSize() + * @param string $directory + * @param bool $recursive + * @return array + */ + public function scanDir($directory = '.', $recursive = false) + { + return $this->parseRawList($this->rawlist($directory, $recursive)); + } + + /** + * Returns the total size of the given directory in bytes. + * + * @param string $directory The directory, by default is the current directory. + * @param bool $recursive true by default + * @return int The size in bytes. + */ + public function dirSize($directory = '.', $recursive = true) + { + $items = $this->scanDir($directory, $recursive); + $size = 0; + + foreach ($items as $item) { + $size += (int) $item['size']; + } + + return $size; + } + + /** + * Count the items (file, directory, link, unknown). + * + * @param string $directory The directory, by default is the current directory. + * @param string|null $type The type of item to count (file, directory, link, unknown) + * @param bool $recursive true by default + * @return int + */ + public function count($directory = '.', $type = null, $recursive = true) + { + $items = (null === $type ? $this->nlist($directory, $recursive) + : $this->scanDir($directory, $recursive)); + + $count = 0; + foreach ($items as $item) { + if (null === $type or $item['type'] == $type) { + $count++; + } + } + + return $count; + } + + /** + * Uploads a file to the server from a string. + * + * @param string $remote_file + * @param string $content + * @return FtpClient + * @throws FtpException When the transfer fails + */ + public function putFromString($remote_file, $content) + { + $handle = fopen('php://temp', 'w'); + + fwrite($handle, $content); + rewind($handle); + + if ($this->ftp->fput($remote_file, $handle, FTP_BINARY)) { + return $this; + } + + throw new FtpException('Unable to put the file "'.$remote_file.'"'); + } + + /** + * Uploads a file to the server. + * + * @param string $local_file + * @return FtpClient + * @throws FtpException When the transfer fails + */ + public function putFromPath($local_file) + { + $remote_file = basename($local_file); + $handle = fopen($local_file, 'r'); + + if ($this->ftp->fput($remote_file, $handle, FTP_BINARY)) { + rewind($handle); + return $this; + } + + throw new FtpException( + 'Unable to put the remote file from the local file "'.$local_file.'"' + ); + } + + /** + * Upload files. + * + * @param string $source_directory + * @param string $target_directory + * @param int $mode + * @return FtpClient + */ + public function putAll($source_directory, $target_directory, $mode = FTP_BINARY) + { + $d = dir($source_directory); + + // do this for each file in the directory + while ($file = $d->read()) { + + // to prevent an infinite loop + if ($file != "." && $file != "..") { + + // do the following if it is a directory + if (is_dir($source_directory.'/'.$file)) { + + if (!$this->isDir($target_directory.'/'.$file)) { + + // create directories that do not yet exist + $this->ftp->mkdir($target_directory.'/'.$file); + } + + // recursive part + $this->putAll( + $source_directory.'/'.$file, $target_directory.'/'.$file, + $mode + ); + } else { + + // put the files + $this->ftp->put( + $target_directory.'/'.$file, $source_directory.'/'.$file, + $mode + ); + } + } + } + + return $this; + } + + /** + * Returns a detailed list of files in the given directory. + * + * @see FtpClient::nlist() + * @see FtpClient::scanDir() + * @see FtpClient::dirSize() + * @param string $directory The directory, by default is the current directory + * @param bool $recursive + * @return array + * @throws FtpException + */ + public function rawlist($directory = '.', $recursive = false) + { + if (!$this->isDir($directory)) { + throw new FtpException('"'.$directory.'" is not a directory.'); + } + + $list = $this->ftp->rawlist($directory); + $items = array(); + + if (!$list) { + return $items; + } + + if (false == $recursive) { + + foreach ($list as $path => $item) { + $chunks = preg_split("/\s+/", $item); + + // if not "name" + if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') { + continue; + } + + $path = $directory.'/'.$chunks[8]; + + if (isset($chunks[9])) { + $nbChunks = count($chunks); + + for ($i = 9; $i < $nbChunks; $i++) { + $path .= ' '.$chunks[$i]; + } + } + + + if (substr($path, 0, 2) == './') { + $path = substr($path, 2); + } + + $items[ $this->rawToType($item).'#'.$path ] = $item; + } + + return $items; + } + + $path = ''; + + foreach ($list as $item) { + $len = strlen($item); + + if (!$len + + // "." + || ($item[$len-1] == '.' && $item[$len-2] == ' ' + + // ".." + or $item[$len-1] == '.' && $item[$len-2] == '.' && $item[$len-3] == ' ') + ){ + + continue; + } + + $chunks = preg_split("/\s+/", $item); + + // if not "name" + if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') { + continue; + } + + $path = $directory.'/'.$chunks[8]; + + if (isset($chunks[9])) { + $nbChunks = count($chunks); + + for ($i = 9; $i < $nbChunks; $i++) { + $path .= ' '.$chunks[$i]; + } + } + + if (substr($path, 0, 2) == './') { + $path = substr($path, 2); + } + + $items[$this->rawToType($item).'#'.$path] = $item; + + if ($item[0] == 'd') { + $sublist = $this->rawlist($path, true); + + foreach ($sublist as $subpath => $subitem) { + $items[$subpath] = $subitem; + } + } + } + + return $items; + } + + /** + * Parse raw list. + * + * @see FtpClient::rawlist() + * @see FtpClient::scanDir() + * @see FtpClient::dirSize() + * @param array $rawlist + * @return array + */ + public function parseRawList(array $rawlist) + { + $items = array(); + $path = ''; + + foreach ($rawlist as $key => $child) { + $chunks = preg_split("/\s+/", $child); + + if (isset($chunks[8]) && ($chunks[8] == '.' or $chunks[8] == '..')) { + continue; + } + + if (count($chunks) === 1) { + $len = strlen($chunks[0]); + + if ($len && $chunks[0][$len-1] == ':') { + $path = substr($chunks[0], 0, -1); + } + + continue; + } + + $item = [ + 'permissions' => $chunks[0], + 'number' => $chunks[1], + 'owner' => $chunks[2], + 'group' => $chunks[3], + 'size' => $chunks[4], + 'month' => $chunks[5], + 'day' => $chunks[6], + 'time' => $chunks[7], + 'name' => $chunks[8], + 'type' => $this->rawToType($chunks[0]), + ]; + + unset($chunks[0]); + unset($chunks[1]); + unset($chunks[2]); + unset($chunks[3]); + unset($chunks[4]); + unset($chunks[5]); + unset($chunks[6]); + unset($chunks[7]); + $item['name'] = implode(' ', $chunks); + + if ($item['type'] == 'link') { + $item['target'] = $chunks[10]; // 9 is "->" + } + + // if the key is not the path, behavior of ftp_rawlist() PHP function + if (is_int($key) || false === strpos($key, $item['name'])) { + array_splice($chunks, 0, 8); + + $key = $item['type'].'#' + .($path ? $path.'/' : '') + .implode(" ", $chunks); + + if ($item['type'] == 'link') { + + // get the first part of 'link#the-link.ext -> /path/of/the/source.ext' + $exp = explode(' ->', $key); + $key = rtrim($exp[0]); + } + + $items[$key] = $item; + + } else { + + // the key is the path, behavior of FtpClient::rawlist() method() + $items[$key] = $item; + } + } + + return $items; + } + + /** + * Convert raw info (drwx---r-x ...) to type (file, directory, link, unknown). + * Only the first char is used for resolving. + * + * @param string $permission Example : drwx---r-x + * + * @return string The file type (file, directory, link, unknown) + * @throws FtpException + */ + public function rawToType($permission) + { + if (!is_string($permission)) { + throw new FtpException('The "$permission" argument must be a string, "' + .gettype($permission).'" given.'); + } + + if (empty($permission[0])) { + return 'unknown'; + } + + switch ($permission[0]) { + case '-': + return 'file'; + + case 'd': + return 'directory'; + + case 'l': + return 'link'; + + default: + return 'unknown'; + } + } + + /** + * Set the wrapper which forward the PHP FTP functions to use in FtpClient instance. + * + * @param FtpWrapper $wrapper + * @return FtpClient + */ + protected function setWrapper(FtpWrapper $wrapper) + { + $this->ftp = $wrapper; + + return $this; + } +} diff --git a/core/vendor/filemanager/include/FtpException.php b/core/vendor/filemanager/include/FtpException.php new file mode 100644 index 0000000..f17ed7f --- /dev/null +++ b/core/vendor/filemanager/include/FtpException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @copyright Nicolas Tallefourtane http://nicolab.net + */ +namespace FtpClient; + +/** + * The FtpException class. + * Exception thrown if an error on runtime of the FTP client occurs. + * @inheritDoc + * @author Nicolas Tallefourtane + */ +class FtpException extends \Exception {} diff --git a/core/vendor/filemanager/include/FtpWrapper.php b/core/vendor/filemanager/include/FtpWrapper.php new file mode 100644 index 0000000..cd12de0 --- /dev/null +++ b/core/vendor/filemanager/include/FtpWrapper.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @copyright Nicolas Tallefourtane http://nicolab.net + */ +namespace FtpClient; + +/** + * Wrap the PHP FTP functions + * + * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded + * @method bool cdup() cdup() Changes to the parent directory + * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server + * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP + * @method bool close() close() Closes an FTP connection + * @method bool delete() delete(string $path) Deletes a file on the FTP server + * @method bool exec() exec(string $command) Requests execution of a command on the FTP server + * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file + * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server + * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream + * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server + * @method bool login() login(string $username, string $password) Logs in to an FTP connection + * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file + * @method string mkdir() mkdir(string $directory) Creates a directory + * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking) + * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking) + * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking) + * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking) + * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking) + * @method array nlist() nlist(string $directory) Returns a list of files in the given directory + * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off + * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server + * @method string pwd() pwd() Returns the current directory name + * @method bool quit() quit() Closes an FTP connection + * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server + * @method array rawlist() rawlist(string $directory, bool $recursive = false) Returns a detailed list of files in the given directory + * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server + * @method bool rmdir() rmdir(string $directory) Removes a directory + * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options + * @method bool site() site(string $command) Sends a SITE command to the server + * @method int size() size(string $remote_file) Returns the size of the given file + * @method string systype() systype() Returns the system type identifier of the remote FTP server + * + * @author Nicolas Tallefourtane + */ +class FtpWrapper +{ + /** + * The connection with the server + * + * @var resource + */ + protected $conn; + + /** + * Constructor. + * + * @param resource &$connection The FTP (or SSL-FTP) connection (takes by reference). + */ + public function __construct(&$connection) + { + $this->conn = &$connection; + } + + /** + * Forward the method call to FTP functions + * + * @param string $function + * @param array $arguments + * @return mixed + * @throws FtpException When the function is not valid + */ + public function __call($function, array $arguments) + { + $function = 'ftp_' . $function; + + if (function_exists($function)) { + array_unshift($arguments, $this->conn); + return call_user_func_array($function, $arguments); + } + + throw new FtpException("{$function} is not a valid FTP function"); + } + + /** + * Opens a FTP connection + * + * @param string $host + * @param int $port + * @param int $timeout + * @return resource + */ + public function connect($host, $port = 21, $timeout = 90) + { + return ftp_connect($host, $port, $timeout); + } + + /** + * Opens a Secure SSL-FTP connection + * @param string $host + * @param int $port + * @param int $timeout + * @return resource + */ + public function ssl_connect($host, $port = 21, $timeout = 90) + { + return ftp_ssl_connect($host, $port, $timeout); + } +} diff --git a/core/vendor/filemanager/include/Response.php b/core/vendor/filemanager/include/Response.php new file mode 100644 index 0000000..ae9f10c --- /dev/null +++ b/core/vendor/filemanager/include/Response.php @@ -0,0 +1,365 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ); + + /** + * @var string + */ + protected $content; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var array + */ + public $headers; + + /** + * @var string + */ + protected $version; + + /** + * Construct the response + * + * @param mixed $content + * @param int $statusCode + * @param array $headers + */ + public function __construct($content = '', $statusCode = 200, $headers = array()) + { + $this->setContent($content); + $this->setStatusCode($statusCode); + $this->headers = $headers; + $this->version = '1.1'; + } + + /** + * Set the content on the response. + * + * @param mixed $content + * @return $this + */ + public function setContent($content) + { + if ($content instanceof ArrayObject || is_array($content)) + { + $this->headers['Content-Type'] = array('application/json'); + + $content = json_encode($content); + } + + $this->content = $content; + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Sets the response status code. + * + * @param int $code HTTP status code + * @param mixed $text HTTP status text + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return Response + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @api + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : ''; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + /** + * Is response invalid? + * + * @return bool + * + * @api + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Set a header on the Response. + * + * @param string $key + * @param string $value + * @param bool $replace + * @return $this + */ + public function header($key, $value, $replace = true) + { + if (empty($this->headers[$key])) + { + $this->headers[$key] = array(); + } + if ($replace) + { + $this->headers[$key] = array($value); + } + else + { + $this->headers[$key][] = $value; + } + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return Response + * + * @api + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return Response + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return Response + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + // headers + foreach ($this->headers as $name => $values) { + if (is_array($values)) + { + foreach ($values as $value) + { + header($name . ': ' . $value, false, $this->statusCode); + } + } + else + { + header($name . ': ' . $values, false, $this->statusCode); + } + } + + return $this; + } +} \ No newline at end of file diff --git a/core/vendor/filemanager/include/ftp_class.php b/core/vendor/filemanager/include/ftp_class.php new file mode 100644 index 0000000..9c2ae22 --- /dev/null +++ b/core/vendor/filemanager/include/ftp_class.php @@ -0,0 +1,82 @@ +messageArray[] = $message; + } + + public function getMessages() + { + return $this->messageArray; + } + + public function connect ($server, $ftpUser, $ftpPassword, $isPassive = false) + { + + // *** Set up basic connection + $this->connectionId = ftp_connect($server); + + // *** Login with username and password + $loginResult = ftp_login($this->connectionId, $ftpUser, $ftpPassword); + + // *** Sets passive mode on/off (default off) + ftp_pasv($this->connectionId, $isPassive); + + // *** Check connection + if ((!$this->connectionId) || (!$loginResult)) { + $this->logMessage('FTP connection has failed!'); + $this->logMessage('Attempted to connect to ' . $server . ' for user ' . $ftpUser, true); + return false; + } else { + $this->logMessage('Connected to ' . $server . ', for user ' . $ftpUser); + $this->loginOk = true; + return true; + } + } + public function makeDir($directory) + { + // *** If creating a directory is successful... + if (ftp_mkdir($this->connectionId, $directory)) { + + $this->logMessage('Directory "' . $directory . '" created successfully'); + return true; + + } else { + + // *** ...Else, FAIL. + $this->logMessage('Failed creating directory "' . $directory . '"'); + return false; + } + } + + public function changeDir($directory) + { + if (ftp_chdir($this->connectionId, $directory)) { + $this->logMessage('Current directory is now: ' . ftp_pwd($this->connectionId)); + return true; + } else { + $this->logMessage('Couldn\'t change directory'); + return false; + } + } + + public function getDirListing($directory = '.', $parameters = '-la') + { + echo shell_exec('whoami')." is who i am
"; + echo "Current directory is now: " . ftp_pwd($this->connectionId) . "
"; + + // get contents of the current directory + $contentsArray = ftp_rawlist($this->connectionId, $parameters . ' ' . $directory); + echo error_get_last(); + return $contentsArray; + } +} \ No newline at end of file diff --git a/core/vendor/filemanager/include/mime_type_lib.php b/core/vendor/filemanager/include/mime_type_lib.php new file mode 100644 index 0000000..182f645 --- /dev/null +++ b/core/vendor/filemanager/include/mime_type_lib.php @@ -0,0 +1,267 @@ + "ps", + "audio/x-aiff" => "aiff", + "text/plain" => "txt", + "video/x-ms-asf" => "asx", + "audio/basic" => "snd", + "video/x-msvideo" => "avi", + "application/x-bcpio" => "bcpio", + "application/octet-stream" => "so", + "image/bmp" => "bmp", + "application/x-rar" => "rar", + "application/x-bzip2" => "bz2", + "application/x-netcdf" => "nc", + "application/x-kchart" => "chrt", + "application/x-cpio" => "cpio", + "application/mac-compactpro" => "cpt", + "application/x-csh" => "csh", + "text/css" => "css", + "application/x-director" => "dxr", + "image/vnd.djvu" => "djvu", + "application/x-dvi" => "dvi", + "image/vnd.dwg" => "dwg", + "application/epub" => "epub", + "application/epub+zip" => "epub", + "text/x-setext" => "etx", + "application/andrew-inset" => "ez", + "video/x-flv" => "flv", + "image/gif" => "gif", + "application/x-gtar" => "gtar", + "application/x-gzip" => "tgz", + "application/x-hdf" => "hdf", + "application/mac-binhex40" => "hqx", + "text/html" => "html", + "text/htm" => "htm", + "x-conference/x-cooltalk" => "ice", + "image/ief" => "ief", + "model/iges" => "igs", + "text/vnd.sun.j2me.app-descriptor" => "jad", + "application/x-java-archive" => "jar", + "application/x-java-jnlp-file" => "jnlp", + "image/jpeg" => "jpg", + "application/x-javascript" => "js", + "audio/midi" => "midi", + "application/x-killustrator" => "kil", + "application/x-kpresenter" => "kpt", + "application/x-kspread" => "ksp", + "application/x-kword" => "kwt", + "application/vnd.google-earth.kml+xml" => "kml", + "application/vnd.google-earth.kmz" => "kmz", + "application/x-latex" => "latex", + "audio/x-mpegurl" => "m3u", + "application/x-troff-man" => "man", + "application/x-troff-me" => "me", + "model/mesh" => "silo", + "application/vnd.mif" => "mif", + "video/quicktime" => "mov", + "video/x-sgi-movie" => "movie", + "audio/mpeg" => "mp3", + "video/mp4" => "mp4", + "video/mpeg" => "mpeg", + "application/x-troff-ms" => "ms", + "video/vnd.mpegurl" => "mxu", + "application/vnd.oasis.opendocument.database" => "odb", + "application/vnd.oasis.opendocument.chart" => "odc", + "application/vnd.oasis.opendocument.formula" => "odf", + "application/vnd.oasis.opendocument.graphics" => "odg", + "application/vnd.oasis.opendocument.image" => "odi", + "application/vnd.oasis.opendocument.text-master" => "odm", + "application/vnd.oasis.opendocument.presentation" => "odp", + "application/vnd.oasis.opendocument.spreadsheet" => "ods", + "application/vnd.oasis.opendocument.text" => "odt", + "application/ogg" => "ogg", + "video/ogg" => "ogv", + "application/vnd.oasis.opendocument.graphics-template" => "otg", + "application/vnd.oasis.opendocument.text-web" => "oth", + "application/vnd.oasis.opendocument.presentation-template" => "otp", + "application/vnd.oasis.opendocument.spreadsheet-template" => "ots", + "application/vnd.oasis.opendocument.text-template" => "ott", + "image/x-portable-bitmap" => "pbm", + "chemical/x-pdb" => "pdb", + "application/pdf" => "pdf", + "image/x-portable-graymap" => "pgm", + "application/x-chess-pgn" => "pgn", + "text/x-php" => "php", + "image/png" => "png", + "image/x-portable-anymap" => "pnm", + "image/x-portable-pixmap" => "ppm", + "application/vnd.ms-powerpoint" => "ppt", + "audio/x-realaudio" => "ra", + "audio/x-pn-realaudio" => "rm", + "image/x-cmu-raster" => "ras", + "image/x-rgb" => "rgb", + "application/x-troff" => "tr", + "application/x-rpm" => "rpm", + "text/rtf" => "rtf", + "text/richtext" => "rtx", + "text/sgml" => "sgml", + "application/x-sh" => "sh", + "application/x-shar" => "shar", + "application/vnd.symbian.install" => "sis", + "application/x-stuffit" => "sit", + "application/x-koan" => "skt", + "application/smil" => "smil", + "image/svg+xml" => "svg", + "application/x-futuresplash" => "spl", + "application/x-wais-source" => "src", + "application/vnd.sun.xml.calc.template" => "stc", + "application/vnd.sun.xml.draw.template" => "std", + "application/vnd.sun.xml.impress.template" => "sti", + "application/vnd.sun.xml.writer.template" => "stw", + "application/x-sv4cpio" => "sv4cpio", + "application/x-sv4crc" => "sv4crc", + "application/x-shockwave-flash" => "swf", + "application/vnd.sun.xml.calc" => "sxc", + "application/vnd.sun.xml.draw" => "sxd", + "application/vnd.sun.xml.writer.global" => "sxg", + "application/vnd.sun.xml.impress" => "sxi", + "application/vnd.sun.xml.math" => "sxm", + "application/vnd.sun.xml.writer" => "sxw", + "application/x-tar" => "tar", + "application/x-tcl" => "tcl", + "application/x-tex" => "tex", + "application/x-texinfo" => "texinfo", + "image/tiff" => "tiff", + "image/tiff-fx" => "tiff", + "application/x-bittorrent" => "torrent", + "text/tab-separated-values" => "tsv", + "application/x-ustar" => "ustar", + "application/x-cdlink" => "vcd", + "model/vrml" => "wrl", + "audio/x-wav" => "wav", + "audio/x-ms-wax" => "wax", + "image/vnd.wap.wbmp" => "wbmp", + "application/vnd.wap.wbxml" => "wbxml", + "video/x-ms-wm" => "wm", + "audio/x-ms-wma" => "wma", + "text/vnd.wap.wml" => "wml", + "application/vnd.wap.wmlc" => "wmlc", + "text/vnd.wap.wmlscript" => "wmls", + "application/vnd.wap.wmlscriptc" => "wmlsc", + "video/x-ms-wmv" => "wmv", + "video/x-ms-wmx" => "wmx", + "video/x-ms-wvx" => "wvx", + "image/x-xbitmap" => "xbm", + "application/xhtml+xml" => "xhtml", + "application/xml" => "xml", + "image/x-xpixmap" => "xpm", + "text/xsl" => "xsl", + "image/x-xwindowdump" => "xwd", + "chemical/x-xyz" => "xyz", + "application/zip" => "zip", + "application/msword" => "doc", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => "docx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template" => "dotx", + "application/vnd.ms-word.document.macroEnabled.12" => "docm", + "application/vnd.ms-excel" => "xls", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => "xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template" => "xltx", + "application/vnd.ms-excel.sheet.macroEnabled.12" => "xlsm", + "application/vnd.ms-excel.template.macroEnabled.12" => "xltm", + "application/vnd.ms-excel.addin.macroEnabled.12" => "xlam", + "application/vnd.ms-excel.sheet.binary.macroEnabled.12" => "xlsb", + "application/vnd.openxmlformats-officedocument.presentationml.presentation" => "pptx", + "application/vnd.openxmlformats-officedocument.presentationml.template" => "potx", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow" => "ppsx", + "application/vnd.ms-powerpoint.addin.macroEnabled.12" => "ppam", + "application/vnd.ms-powerpoint.presentation.macroEnabled.12" => "pptm", + "application/vnd.ms-powerpoint.template.macroEnabled.12" => "potm", + "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" => "ppsm", +); + + +if ( ! function_exists('get_extension_from_mime')) +{ + function get_extension_from_mime($mime){ + global $mime_types; + if(strpos($mime, ';')!==FALSE){ + $mime = substr($mime, 0,strpos($mime, ';')); + } + if(isset($mime_types[$mime])){ + return $mime_types[$mime]; + } + return ''; + } +} + +if ( ! function_exists('get_file_mime_type')) +{ + function get_file_mime_type($filename, $debug = false) + { + if (function_exists('finfo_open') && function_exists('finfo_file') && function_exists('finfo_close')) + { + $fileinfo = finfo_open(FILEINFO_MIME_TYPE); + $mime_type = finfo_file($fileinfo, $filename); + finfo_close($fileinfo); + + if ( ! empty($mime_type)) + { + if (true === $debug) + { + return array( 'mime_type' => $mime_type, 'method' => 'fileinfo' ); + } + + return $mime_type; + } + } + + if (function_exists('mime_content_type')) + { + $mime_type = mime_content_type($filename); + + if ( ! empty($mime_type)) + { + if (true === $debug) + { + return array( 'mime_type' => $mime_type, 'method' => 'mime_content_type' ); + } + + return $mime_type; + } + } + + global $mime_types; + $mime_types = array_flip($mime_types); + + $tmp_array = explode('.', $filename); + $ext = strtolower(array_pop($tmp_array)); + + if ( ! empty($mime_types[ $ext ])) + { + if (true === $debug) + { + return array( 'mime_type' => $mime_types[ $ext ], 'method' => 'from_array' ); + } + + return $mime_types[ $ext ]; + } + + if (true === $debug) + { + return array( 'mime_type' => 'application/octet-stream', 'method' => 'last_resort' ); + } + + return 'application/octet-stream'; + } +} + + +/******************** + * The following code can be used to test the function. + * First put a plain text file named "test.txt" and a + * JPEG image file named "image.jpg" in the same folder + * as this file. + * + * Simply remove the "REMOVE ME TO TEST" lines below to have + * the code run when this file runs. + * + * Run the code with this command: + * php mime_type_lib.php + ********************/ + + +/* REMOVE ME TO TEST +echo get_file_mime_type( 'test.txt' ) . "\n"; +echo print_r( get_file_mime_type( 'image.jpg', true ), true ) . "\n"; +REMOVE ME TO TEST */ diff --git a/core/vendor/filemanager/include/php_image_magician.php b/core/vendor/filemanager/include/php_image_magician.php new file mode 100644 index 0000000..8dbc5e7 --- /dev/null +++ b/core/vendor/filemanager/include/php_image_magician.php @@ -0,0 +1,3785 @@ + resizeImage(150, 100, 0); +# $magicianObj -> saveImage('images/car_small.jpg', 100); +# +# - See end of doc for more examples - +# +# Supported file types include: jpg, png, gif, bmp, psd (read) +# +# +# +# The following functions are taken from phpThumb() [available from +# http://phpthumb.sourceforge.net], and are used with written permission +# from James Heinrich. +# - GD2BMPstring +# - GetPixelColor +# - LittleEndian2String +# +# The following functions are from Marc Hibbins and are used with written +# permission (are also under the Attribution-ShareAlike +# [http://creativecommons.org/licenses/by-sa/3.0/] license. +# - +# +# PhpPsdReader is used with written permission from Tim de Koning. +# [http://www.kingsquare.nl/phppsdreader] +# +# +# +# Modificatoin history +# Date Initials Ver Description +# 10-05-11 J.C.O 0.0 Initial build +# 01-06-11 J.C.O 0.1.1 * Added reflections +# * Added Rounded corners +# * You can now use PNG interlacing +# * Added shadow +# * Added caption box +# * Added vintage filter +# * Added dynamic image resizing (resize on the fly) +# * minor bug fixes +# 05-06-11 J.C.O 0.1.1.1 * Fixed undefined variables +# 17-06-11 J.C.O 0.1.2 * Added image_batch_class.php class +# * Minor bug fixes +# 26-07-11 J.C.O 0.1.4 * Added support for external images +# * Can now set the crop poisition +# 03-08-11 J.C.O 0.1.5 * Added reset() method to reset resource to +# original input file. +# * Added method addTextToCaptionBox() to +# simplify adding text to a caption box. +# * Added experimental writeIPTC. (not finished) +# * Added experimental readIPTC. (not finished) +# 11-08-11 J.C.O * Added initial border presets. +# 30-08-11 J.C.O * Added 'auto' crop option to crop portrait +# images near the top. +# 08-09-11 J.C.O * Added cropImage() method to allow standalone +# cropping. +# 17-09-11 J.C.O * Added setCropFromTop() set method - set the +# percentage to crop from the top when using +# crop 'auto' option. +# * Added setTransparency() set method - allows you +# to turn transparency off (like when saving +# as a jpg). +# * Added setFillColor() set method - set the +# background color to use instead of transparency. +# 05-11-11 J.C.O 0.1.5.1 * Fixed interlacing option +# 0-07-12 J.C.O 1.0 +# +# Known issues & Limitations: +# ------------------------------- +# Not so much an issue, the image is destroyed on the deconstruct rather than +# when we have finished with it. The reason for this is that we don't know +# when we're finished with it as you can both save the image and display +# it directly to the screen (imagedestroy($this->imageResized)) +# +# Opening BMP files is slow. A test with 884 bmp files processed in a loop +# takes forever - over 5 min. This test inlcuded opening the file, then +# getting and displaying its width and height. +# +# $forceStretch: +# ------------------------------- +# On by default. +# $forceStretch can be disabled by calling method setForceStretch with false +# parameter. If disabled, if an images original size is smaller than the size +# specified by the user, the original size will be used. This is useful when +# dealing with small images. +# +# If enabled, images smaller than the size specified will be stretched to +# that size. +# +# Tips: +# ------------------------------- +# * If you're resizing a transparent png and saving it as a jpg, set +# $keepTransparency to false with: $magicianObj->setTransparency(false); +# +# FEATURES: +# * EASY TO USE +# * BMP SUPPORT (read & write) +# * PSD (photoshop) support (read) +# * RESIZE IMAGES +# - Preserve transparency (png, gif) +# - Apply sharpening (jpg) (requires PHP >= 5.1.0) +# - Set image quality (jpg, png) +# - Resize modes: +# - exact size +# - resize by width (auto height) +# - resize by height (auto width) +# - auto (automatically determine the best of the above modes to use) +# - crop - resize as best as it can then crop the rest +# - Force stretching of smaller images (upscale) +# * APPLY FILTERS +# - Convert to grey scale +# - Convert to black and white +# - Convert to sepia +# - Convert to negative +# * ROTATE IMAGES +# - Rotate using predefined "left", "right", or "180"; or any custom degree amount +# * EXTRACT EXIF DATA (requires exif module) +# - make +# - model +# - date +# - exposure +# - aperture +# - f-stop +# - iso +# - focal length +# - exposure program +# - metering mode +# - flash status +# - creator +# - copyright +# * ADD WATERMARK +# - Specify exact x, y placement +# - Or, specify using one of the 9 pre-defined placements such as "tl" +# (for top left), "m" (for middle), "br" (for bottom right) +# - also specify padding from edge amount (optional). +# - Set opacity of watermark (png). +# * ADD BORDER +# * USE HEX WHEN SPECIFYING COLORS (eg: #ffffff) +# * SAVE IMAGE OR OUTPUT TO SCREEN +# +# +# ========================================================================# + + +class imageLib { + + private $fileName; + private $image; + protected $imageResized; + private $widthOriginal; # Always be the original width + private $heightOriginal; + private $width; # Current width (width after resize) + private $height; + private $imageSize; + private $fileExtension; + + private $debug = true; + private $errorArray = array(); + + private $forceStretch = true; + private $aggresiveSharpening = false; + + private $transparentArray = array( '.png', '.gif' ); + private $keepTransparency = true; + private $fillColorArray = array( 'r' => 255, 'g' => 255, 'b' => 255 ); + + private $sharpenArray = array( 'jpg' ); + + private $psdReaderPath; + private $filterOverlayPath; + + private $isInterlace; + + private $captionBoxPositionArray = array(); + + private $fontDir = 'fonts'; + + private $cropFromTopPercent = 10; + + +## -------------------------------------------------------- + + function __construct($fileName) + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: Constructor + # Param in: $fileName: File name and path. + # Param out: n/a + # Reference: + # Notes: + # + { + if ( ! $this->testGDInstalled()) + { + if ($this->debug) + { + throw new Exception('The GD Library is not installed.'); + } + else + { + throw new Exception(); + } + }; + + $this->initialise(); + + // *** Save the image file name. Only store this incase you want to display it + $this->fileName = $fileName; + $this->fileExtension = fix_strtolower(strrchr($fileName, '.')); + + // *** Open up the file + $this->image = $this->openImage($fileName); + + + // *** Assign here so we don't modify the original + $this->imageResized = $this->image; + + // *** If file is an image + if ($this->testIsImage($this->image)) + { + // *** Get width and height + $this->width = imagesx($this->image); + $this->widthOriginal = imagesx($this->image); + $this->height = imagesy($this->image); + $this->heightOriginal = imagesy($this->image); + + + /* Added 15-09-08 + * Get the filesize using this build in method. + * Stores an array of size + * + * $this->imageSize[1] = width + * $this->imageSize[2] = height + * $this->imageSize[3] = width x height + * + */ + $this->imageSize = getimagesize($this->fileName); + + } + else + { + $this->errorArray[] = 'File is not an image'; + } + } + +## -------------------------------------------------------- + + private function initialise() + { + + $this->psdReaderPath = dirname(__FILE__) . '/classPhpPsdReader.php'; + $this->filterOverlayPath = dirname(__FILE__) . '/filters'; + + // *** Set if image should be interlaced or not. + $this->isInterlace = false; + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Resize +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + + public function resizeImage($newWidth, $newHeight, $option = 0, $sharpen = false, $autoRotate = false) + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: Resizes the image + # Param in: $newWidth: + # $newHeight: + # $option: 0 / exact = defined size; + # 1 / portrait = keep aspect set height; + # 2 / landscape = keep aspect set width; + # 3 / auto = auto; + # 4 / crop= resize and crop; + # + # $option can also be an array containing options for + # cropping. E.G., array('crop', 'r') + # + # This array only applies to 'crop' and the 'r' refers to + # "crop right". Other value include; tl, t, tr, l, m (default), + # r, bl, b, br, or you can specify your own co-ords (which + # isn't recommended. + # + # $sharpen: true: sharpen (jpg only); + # false: don't sharpen + # Param out: n/a + # Reference: + # Notes: To clarify the $option input: + # 0 = The exact height and width dimensions you set. + # 1 = Whatever height is passed in will be the height that + # is set. The width will be calculated and set automatically + # to a the value that keeps the original aspect ratio. + # 2 = The same but based on the width. We try make the image the + # biggest size we can while stil fitting inside the box size + # 3 = Depending whether the image is landscape or portrait, this + # will automatically determine whether to resize via + # dimension 1,2 or 0 + # 4 = Will resize and then crop the image for best fit + # + # forceStretch can be applied to options 1,2,3 and 4 + # + { + + // *** We can pass in an array of options to change the crop position + $cropPos = 'm'; + if (is_array($option) && fix_strtolower($option[0]) == 'crop') + { + $cropPos = $option[1]; # get the crop option + } + else + { + if (strpos($option, '-') !== false) + { + // *** Or pass in a hyphen seperated option + $optionPiecesArray = explode('-', $option); + $cropPos = end($optionPiecesArray); + } + } + + // *** Check the option is valid + $option = $this->prepOption($option); + + // *** Make sure the file passed in is valid + if ( ! $this->image) + { + if ($this->debug) + { + throw new Exception('file ' . $this->getFileName() . ' is missing or invalid'); + } + else + { + throw new Exception(); + } + }; + + // *** Get optimal width and height - based on $option + $dimensionsArray = $this->getDimensions($newWidth, $newHeight, $option); + + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + + // *** Resample - create image canvas of x, y size + $this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight); + $this->keepTransparancy($optimalWidth, $optimalHeight, $this->imageResized); + imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height); + + + // *** If '4', then crop too + if ($option == 4 || $option == 'crop') + { + + if (($optimalWidth >= $newWidth && $optimalHeight >= $newHeight)) + { + $this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight, $cropPos); + } + } + + // *** If Rotate. + if ($autoRotate) + { + + $exifData = $this->getExif(false); + if (count($exifData) > 0) + { + + switch ($exifData['orientation']) + { + case 8: + $this->imageResized = imagerotate($this->imageResized, 90, 0); + break; + case 3: + $this->imageResized = imagerotate($this->imageResized, 180, 0); + break; + case 6: + $this->imageResized = imagerotate($this->imageResized, -90, 0); + break; + } + } + } + + // *** Sharpen image (if jpg and the user wishes to do so) + if ($sharpen && in_array($this->fileExtension, $this->sharpenArray)) + { + + // *** Sharpen + $this->sharpen(); + } + } + +## -------------------------------------------------------- + + public function cropImage($newWidth, $newHeight, $cropPos = 'm') + # Author: Jarrod Oberto + # Date: 08-09-11 + # Purpose: Crops the image + # Param in: $newWidth: crop with + # $newHeight: crop height + # $cropPos: Can be any of the following: + # tl, t, tr, l, m, r, bl, b, br, auto + # Or: + # a custom position such as '30x50' + # Param out: n/a + # Reference: + # Notes: + # + { + + // *** Make sure the file passed in is valid + if ( ! $this->image) + { + if ($this->debug) + { + throw new Exception('file ' . $this->getFileName() . ' is missing or invalid'); + } + else + { + throw new Exception(); + } + }; + + $this->imageResized = $this->image; + $this->crop($this->width, $this->height, $newWidth, $newHeight, $cropPos); + + } + +## -------------------------------------------------------- + + private function keepTransparancy($width, $height, $im) + # Author: Jarrod Oberto + # Date: 08-04-11 + # Purpose: Keep transparency for png and gif image + # Param in: + # Param out: n/a + # Reference: + # Notes: + # + { + // *** If PNG, perform some transparency retention actions (gif untested) + if (in_array($this->fileExtension, $this->transparentArray) && $this->keepTransparency) + { + imagealphablending($im, false); + imagesavealpha($im, true); + $transparent = imagecolorallocatealpha($im, 255, 255, 255, 127); + imagefilledrectangle($im, 0, 0, $width, $height, $transparent); + } + else + { + $color = imagecolorallocate($im, $this->fillColorArray['r'], $this->fillColorArray['g'], $this->fillColorArray['b']); + imagefilledrectangle($im, 0, 0, $width, $height, $color); + } + } + +## -------------------------------------------------------- + + private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight, $cropPos) + # Author: Jarrod Oberto + # Date: 15-09-08 + # Purpose: Crops the image + # Param in: $newWidth: + # $newHeight: + # Param out: n/a + # Reference: + # Notes: + # + { + + // *** Get cropping co-ordinates + $cropArray = $this->getCropPlacing($optimalWidth, $optimalHeight, $newWidth, $newHeight, $cropPos); + $cropStartX = $cropArray['x']; + $cropStartY = $cropArray['y']; + + // *** Crop this bad boy + $crop = imagecreatetruecolor($newWidth, $newHeight); + $this->keepTransparancy($optimalWidth, $optimalHeight, $crop); + imagecopyresampled($crop, $this->imageResized, 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight, $newWidth, $newHeight); + + $this->imageResized = $crop; + + // *** Set new width and height to our variables + $this->width = $newWidth; + $this->height = $newHeight; + + } + +## -------------------------------------------------------- + + private function getCropPlacing($optimalWidth, $optimalHeight, $newWidth, $newHeight, $pos = 'm') + # + # Author: Jarrod Oberto + # Date: July 11 + # Purpose: Set the cropping area. + # Params in: + # Params out: (array) the crop x and y co-ordinates. + # Notes: When specifying the exact pixel crop position (eg 10x15), be + # very careful as it's easy to crop out of the image leaving + # black borders. + # + { + $pos = fix_strtolower($pos); + + // *** If co-ords have been entered + if (strstr($pos, 'x')) + { + $pos = str_replace(' ', '', $pos); + + $xyArray = explode('x', $pos); + list($cropStartX, $cropStartY) = $xyArray; + + } + else + { + + switch ($pos) + { + case 'tl': + $cropStartX = 0; + $cropStartY = 0; + break; + + case 't': + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = 0; + break; + + case 'tr': + $cropStartX = $optimalWidth - $newWidth; + $cropStartY = 0; + break; + + case 'l': + $cropStartX = 0; + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + + case 'm': + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + + case 'r': + $cropStartX = $optimalWidth - $newWidth; + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + + case 'bl': + $cropStartX = 0; + $cropStartY = $optimalHeight - $newHeight; + break; + + case 'b': + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = $optimalHeight - $newHeight; + break; + + case 'br': + $cropStartX = $optimalWidth - $newWidth; + $cropStartY = $optimalHeight - $newHeight; + break; + + case 'auto': + // *** If image is a portrait crop from top, not center. v1.5 + if ($optimalHeight > $optimalWidth) + { + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($this->cropFromTopPercent / 100) * $optimalHeight; + } + else + { + + // *** Else crop from the center + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + } + break; + + default: + // *** Default to center + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + } + } + + return array( 'x' => $cropStartX, 'y' => $cropStartY ); + } + +## -------------------------------------------------------- + + private function getDimensions($newWidth, $newHeight, $option) + # Author: Jarrod Oberto + # Date: 17-11-09 + # Purpose: Get new image dimensions based on user specificaions + # Param in: $newWidth: + # $newHeight: + # Param out: Array of new width and height values + # Reference: + # Notes: If $option = 3 then this function is call recursivly + # + # To clarify the $option input: + # 0 = The exact height and width dimensions you set. + # 1 = Whatever height is passed in will be the height that + # is set. The width will be calculated and set automatically + # to a the value that keeps the original aspect ratio. + # 2 = The same but based on the width. + # 3 = Depending whether the image is landscape or portrait, this + # will automatically determine whether to resize via + # dimension 1,2 or 0. + # 4 = Resize the image as much as possible, then crop the + # remainder. + { + + switch (strval($option)) + { + case '0': + case 'exact': + $optimalWidth = $newWidth; + $optimalHeight = $newHeight; + break; + case '1': + case 'portrait': + $dimensionsArray = $this->getSizeByFixedHeight($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + case '2': + case 'landscape': + $dimensionsArray = $this->getSizeByFixedWidth($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + case '3': + case 'auto': + $dimensionsArray = $this->getSizeByAuto($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + case '4': + case 'crop': + $dimensionsArray = $this->getOptimalCrop($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + } + + return array( 'optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight ); + } + +## -------------------------------------------------------- + + private function getSizeByFixedHeight($newWidth, $newHeight) + { + // *** If forcing is off... + if ( ! $this->forceStretch) + { + + // *** ...check if actual height is less than target height + if ($this->height < $newHeight) + { + return array( 'optimalWidth' => $this->width, 'optimalHeight' => $this->height ); + } + } + + $ratio = $this->width / $this->height; + + $newWidth = $newHeight * $ratio; + + //return $newWidth; + return array( 'optimalWidth' => $newWidth, 'optimalHeight' => $newHeight ); + } + +## -------------------------------------------------------- + + private function getSizeByFixedWidth($newWidth, $newHeight) + { + // *** If forcing is off... + if ( ! $this->forceStretch) + { + + // *** ...check if actual width is less than target width + if ($this->width < $newWidth) + { + return array( 'optimalWidth' => $this->width, 'optimalHeight' => $this->height ); + } + } + + $ratio = $this->height / $this->width; + + $newHeight = $newWidth * $ratio; + + //return $newHeight; + return array( 'optimalWidth' => $newWidth, 'optimalHeight' => $newHeight ); + } + +## -------------------------------------------------------- + + private function getSizeByAuto($newWidth, $newHeight) + # Author: Jarrod Oberto + # Date: 19-08-08 + # Purpose: Depending on the height, choose to resize by 0, 1, or 2 + # Param in: The new height and new width + # Notes: + # + { + // *** If forcing is off... + if ( ! $this->forceStretch) + { + + // *** ...check if actual size is less than target size + if ($this->width < $newWidth && $this->height < $newHeight) + { + return array( 'optimalWidth' => $this->width, 'optimalHeight' => $this->height ); + } + } + + if ($this->height < $this->width) + // *** Image to be resized is wider (landscape) + { + //$optimalWidth = $newWidth; + //$optimalHeight= $this->getSizeByFixedWidth($newWidth); + + $dimensionsArray = $this->getSizeByFixedWidth($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } + elseif ($this->height > $this->width) + // *** Image to be resized is taller (portrait) + { + //$optimalWidth = $this->getSizeByFixedHeight($newHeight); + //$optimalHeight= $newHeight; + + $dimensionsArray = $this->getSizeByFixedHeight($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } + else + // *** Image to be resizerd is a square + { + + if ($newHeight < $newWidth) + { + //$optimalWidth = $newWidth; + //$optimalHeight= $this->getSizeByFixedWidth($newWidth); + $dimensionsArray = $this->getSizeByFixedWidth($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } + else + { + if ($newHeight > $newWidth) + { + //$optimalWidth = $this->getSizeByFixedHeight($newHeight); + //$optimalHeight= $newHeight; + $dimensionsArray = $this->getSizeByFixedHeight($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } + else + { + // *** Sqaure being resized to a square + $optimalWidth = $newWidth; + $optimalHeight = $newHeight; + } + } + } + + return array( 'optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight ); + } + +## -------------------------------------------------------- + + private function getOptimalCrop($newWidth, $newHeight) + # Author: Jarrod Oberto + # Date: 17-11-09 + # Purpose: Get optimal crop dimensions + # Param in: width and height as requested by user (fig 3) + # Param out: Array of optimal width and height (fig 2) + # Reference: + # Notes: The optimal width and height return are not the same as the + # same as the width and height passed in. For example: + # + # + # |-----------------| |------------| |-------| + # | | => |**| |**| => | | + # | | |**| |**| | | + # | | |------------| |-------| + # |-----------------| + # original optimal crop + # size size size + # Fig 1 2 3 + # + # 300 x 250 150 x 125 150 x 100 + # + # The optimal size is the smallest size (that is closest to the crop size) + # while retaining proportion/ratio. + # + # The crop size is the optimal size that has been cropped on one axis to + # make the image the exact size specified by the user. + # + # * represent cropped area + # + { + + // *** If forcing is off... + if ( ! $this->forceStretch) + { + + // *** ...check if actual size is less than target size + if ($this->width < $newWidth && $this->height < $newHeight) + { + return array( 'optimalWidth' => $this->width, 'optimalHeight' => $this->height ); + } + } + + $heightRatio = $this->height / $newHeight; + $widthRatio = $this->width / $newWidth; + + if ($heightRatio < $widthRatio) + { + $optimalRatio = $heightRatio; + } + else + { + $optimalRatio = $widthRatio; + } + + $optimalHeight = round($this->height / $optimalRatio); + $optimalWidth = round($this->width / $optimalRatio); + + return array( 'optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight ); + } + +## -------------------------------------------------------- + + private function sharpen() + # Author: Jarrod Oberto + # Date: 08 04 2011 + # Purpose: Sharpen image + # Param in: n/a + # Param out: n/a + # Reference: + # Notes: + # Credit: Incorporates Joe Lencioni (August 6, 2008) code + { + + if (version_compare(PHP_VERSION, '5.1.0') >= 0) + { + + // *** + if ($this->aggresiveSharpening) + { # A more aggressive sharpening solution + + $sharpenMatrix = array( array( -1, -1, -1 ), + array( -1, 16, -1 ), + array( -1, -1, -1 ) ); + $divisor = 8; + $offset = 0; + + imageconvolution($this->imageResized, $sharpenMatrix, $divisor, $offset); + } + else # More subtle and personally more desirable + { + $sharpness = $this->findSharp($this->widthOriginal, $this->width); + + $sharpenMatrix = array( + array( -1, -2, -1 ), + array( -2, $sharpness + 12, -2 ), //Lessen the effect of a filter by increasing the value in the center cell + array( -1, -2, -1 ) + ); + $divisor = $sharpness; // adjusts brightness + $offset = 0; + imageconvolution($this->imageResized, $sharpenMatrix, $divisor, $offset); + } + } + else + { + if ($this->debug) + { + throw new Exception('Sharpening required PHP 5.1.0 or greater.'); + } + } + } + + ## -------------------------------------------------------- + + private function sharpen2($level) + { + $sharpenMatrix = array( + array( $level, $level, $level ), + array( $level, (8 * $level) + 1, $level ), //Lessen the effect of a filter by increasing the value in the center cell + array( $level, $level, $level ) + ); + + } + +## -------------------------------------------------------- + + private function findSharp($orig, $final) + # Author: Ryan Rud (http://adryrun.com) + # Purpose: Find optimal sharpness + # Param in: n/a + # Param out: n/a + # Reference: + # Notes: + # + { + $final = $final * (750.0 / $orig); + $a = 52; + $b = -0.27810650887573124; + $c = .00047337278106508946; + + $result = $a + $b * $final + $c * $final * $final; + + return max(round($result), 0); + } + +## -------------------------------------------------------- + + private function prepOption($option) + # Author: Jarrod Oberto + # Purpose: Prep option like change the passed in option to lowercase + # Param in: (str/int) $option: eg. 'exact', 'crop'. 0, 4 + # Param out: lowercase string + # Reference: + # Notes: + # + { + if (is_array($option)) + { + if (fix_strtolower($option[0]) == 'crop' && count($option) == 2) + { + return 'crop'; + } + else + { + throw new Exception('Crop resize option array is badly formatted.'); + } + } + else + { + if (strpos($option, 'crop') !== false) + { + return 'crop'; + } + } + + if (is_string($option)) + { + return fix_strtolower($option); + } + + return $option; + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Presets +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + +# +# Preset are pre-defined templates you can apply to your image. +# +# These are inteded to be applied to thumbnail images. +# + + + public function borderPreset($preset) + { + switch ($preset) + { + + case 'simple': + $this->addBorder(7, '#fff'); + $this->addBorder(6, '#f2f1f0'); + $this->addBorder(2, '#fff'); + $this->addBorder(1, '#ccc'); + break; + default: + break; + } + + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Draw border +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addBorder($thickness = 1, $rgbArray = array( 255, 255, 255 )) + # Author: Jarrod Oberto + # Date: 05-05-11 + # Purpose: Add a border to the image + # Param in: + # Param out: + # Reference: + # Notes: This border is added to the INSIDE of the image + # + { + if ($this->imageResized) + { + + $rgbArray = $this->formatColor($rgbArray); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + + $x1 = 0; + $y1 = 0; + $x2 = ImageSX($this->imageResized) - 1; + $y2 = ImageSY($this->imageResized) - 1; + + $rgbArray = ImageColorAllocate($this->imageResized, $r, $g, $b); + + + for ($i = 0; $i < $thickness; $i++) + { + ImageRectangle($this->imageResized, $x1++, $y1++, $x2--, $y2--, $rgbArray); + } + } + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Gray Scale +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function greyScale() + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Make image greyscale + # Param in: n/a + # Param out: + # Reference: + # Notes: + # + { + if ($this->imageResized) + { + imagefilter($this->imageResized, IMG_FILTER_GRAYSCALE); + } + + } + + ## -------------------------------------------------------- + + public function greyScaleEnhanced() + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Make image greyscale + # Param in: n/a + # Param out: + # Reference: + # Notes: + # + { + if ($this->imageResized) + { + imagefilter($this->imageResized, IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, IMG_FILTER_CONTRAST, -15); + imagefilter($this->imageResized, IMG_FILTER_BRIGHTNESS, 2); + $this->sharpen($this->width); + } + } + + ## -------------------------------------------------------- + + public function greyScaleDramatic() + # Alias of gd_filter_monopin + { + $this->gd_filter_monopin(); + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Black 'n White +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function blackAndWhite() + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Make image black and white + # Param in: n/a + # Param out: + # Reference: + # Notes: + # + { + if ($this->imageResized) + { + + imagefilter($this->imageResized, IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, IMG_FILTER_CONTRAST, -1000); + } + + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Negative +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function negative() + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Make image negative + # Param in: n/a + # Param out: + # Reference: + # Notes: + # + { + if ($this->imageResized) + { + + imagefilter($this->imageResized, IMG_FILTER_NEGATE); + } + + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Sepia +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function sepia() + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Make image sepia + # Param in: n/a + # Param out: + # Reference: + # Notes: + # + { + if ($this->imageResized) + { + imagefilter($this->imageResized, IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, IMG_FILTER_BRIGHTNESS, -10); + imagefilter($this->imageResized, IMG_FILTER_CONTRAST, -20); + imagefilter($this->imageResized, IMG_FILTER_COLORIZE, 60, 30, -15); + } + } + + ## -------------------------------------------------------- + + public function sepia2() + + { + if ($this->imageResized) + { + + $total = imagecolorstotal($this->imageResized); + for ($i = 0; $i < $total; $i++) + { + $index = imagecolorsforindex($this->imageResized, $i); + $red = ($index["red"] * 0.393 + $index["green"] * 0.769 + $index["blue"] * 0.189) / 1.351; + $green = ($index["red"] * 0.349 + $index["green"] * 0.686 + $index["blue"] * 0.168) / 1.203; + $blue = ($index["red"] * 0.272 + $index["green"] * 0.534 + $index["blue"] * 0.131) / 2.140; + imagecolorset($this->imageResized, $i, $red, $green, $blue); + } + + + } + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Vintage +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function vintage() + # Alias of gd_filter_monopin + { + $this->gd_filter_vintage(); + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Presets By Marc Hibbins +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + + /** Apply 'Monopin' preset */ + public function gd_filter_monopin() + { + + if ($this->imageResized) + { + imagefilter($this->imageResized, IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, IMG_FILTER_BRIGHTNESS, -15); + imagefilter($this->imageResized, IMG_FILTER_CONTRAST, -15); + $this->imageResized = $this->gd_apply_overlay($this->imageResized, 'vignette', 100); + } + } + + ## -------------------------------------------------------- + + public function gd_filter_vintage() + { + if ($this->imageResized) + { + $this->imageResized = $this->gd_apply_overlay($this->imageResized, 'vignette', 45); + imagefilter($this->imageResized, IMG_FILTER_BRIGHTNESS, 20); + imagefilter($this->imageResized, IMG_FILTER_CONTRAST, -35); + imagefilter($this->imageResized, IMG_FILTER_COLORIZE, 60, -10, 35); + imagefilter($this->imageResized, IMG_FILTER_SMOOTH, 7); + $this->imageResized = $this->gd_apply_overlay($this->imageResized, 'scratch', 10); + } + } + + ## -------------------------------------------------------- + + /** Apply a PNG overlay */ + private function gd_apply_overlay($im, $type, $amount) + # + # Original Author: Marc Hibbins + # License: Attribution-ShareAlike 3.0 + # Purpose: + # Params in: + # Params out: + # Notes: + # + { + $width = imagesx($im); + $height = imagesy($im); + $filter = imagecreatetruecolor($width, $height); + + imagealphablending($filter, false); + imagesavealpha($filter, true); + + $transparent = imagecolorallocatealpha($filter, 255, 255, 255, 127); + imagefilledrectangle($filter, 0, 0, $width, $height, $transparent); + + // *** Resize overlay + $overlay = $this->filterOverlayPath . '/' . $type . '.png'; + $png = imagecreatefrompng($overlay); + imagecopyresampled($filter, $png, 0, 0, 0, 0, $width, $height, imagesx($png), imagesy($png)); + + $comp = imagecreatetruecolor($width, $height); + imagecopy($comp, $im, 0, 0, 0, 0, $width, $height); + imagecopy($comp, $filter, 0, 0, 0, 0, $width, $height); + imagecopymerge($im, $comp, 0, 0, 0, 0, $width, $height, $amount); + + imagedestroy($comp); + + return $im; + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Colorise +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function image_colorize($rgb) + { + imageTrueColorToPalette($this->imageResized, true, 256); + $numColors = imageColorsTotal($this->imageResized); + + for ($x = 0; $x < $numColors; $x++) + { + list($r, $g, $b) = array_values(imageColorsForIndex($this->imageResized, $x)); + + // calculate grayscale in percent + $grayscale = ($r + $g + $b) / 3 / 0xff; + + imageColorSet($this->imageResized, $x, + $grayscale * $rgb[0], + $grayscale * $rgb[1], + $grayscale * $rgb[2] + ); + + } + + return true; + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Reflection +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addReflection($reflectionHeight = 50, $startingTransparency = 30, $inside = false, $bgColor = '#fff', $stretch = false, $divider = 0) + { + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + $im = $this->imageResized; + $li = imagecreatetruecolor($this->width, 1); + + $bgc = imagecolorallocate($li, $r, $g, $b); + imagefilledrectangle($li, 0, 0, $this->width, 1, $bgc); + + $bg = imagecreatetruecolor($this->width, $reflectionHeight); + $wh = imagecolorallocate($im, 255, 255, 255); + + $im = imagerotate($im, -180, $wh); + imagecopyresampled($bg, $im, 0, 0, 0, 0, $this->width, $this->height, $this->width, $this->height); + + $im = $bg; + + $bg = imagecreatetruecolor($this->width, $reflectionHeight); + + for ($x = 0; $x < $this->width; $x++) + { + imagecopy($bg, $im, $x, 0, $this->width - $x - 1, 0, 1, $reflectionHeight); + } + $im = $bg; + + $transaprencyAmount = $this->invertTransparency($startingTransparency, 100); + + + // *** Fade + if ($stretch) + { + $step = 100 / ($reflectionHeight + $startingTransparency); + } + else + { + $step = 100 / $reflectionHeight; + } + for ($i = 0; $i <= $reflectionHeight; $i++) + { + + if ($startingTransparency > 100) + { + $startingTransparency = 100; + } + if ($startingTransparency < 1) + { + $startingTransparency = 1; + } + imagecopymerge($bg, $li, 0, $i, 0, 0, $this->width, 1, $startingTransparency); + $startingTransparency += $step; + } + + // *** Apply fade + imagecopymerge($im, $li, 0, 0, 0, 0, $this->width, $divider, 100); // Divider + + + // *** width, height of reflection. + $x = imagesx($im); + $y = imagesy($im); + + + // *** Determines if the reflection should be displayed inside or outside the image + if ($inside) + { + + // Create new blank image with sizes. + $final = imagecreatetruecolor($this->width, $this->height); + + imagecopymerge($final, $this->imageResized, 0, 0, 0, $reflectionHeight, $this->width, $this->height - $reflectionHeight, 100); + imagecopymerge($final, $im, 0, $this->height - $reflectionHeight, 0, 0, $x, $y, 100); + + } + else + { + + // Create new blank image with sizes. + $final = imagecreatetruecolor($this->width, $this->height + $y); + + imagecopymerge($final, $this->imageResized, 0, 0, 0, 0, $this->width, $this->height, 100); + imagecopymerge($final, $im, 0, $this->height, 0, 0, $x, $y, 100); + } + + $this->imageResized = $final; + + imagedestroy($li); + imagedestroy($im); + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Rotate +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function rotate($value = 90, $bgColor = 'transparent') + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Rotate image + # Param in: (mixed) $degrees: (int) number of degress to rotate image + # (str) param "left": rotate left + # (str) param "right": rotate right + # (str) param "upside": upside-down image + # Param out: + # Reference: + # Notes: The default direction of imageRotate() is counter clockwise. + # + { + if ($this->imageResized) + { + + if (is_integer($value)) + { + $degrees = $value; + } + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + if (isset($rgbArray['a'])) + { + $a = $rgbArray['a']; + } + + if (is_string($value)) + { + + $value = fix_strtolower($value); + + switch ($value) + { + case 'left': + $degrees = 90; + break; + case 'right': + $degrees = 270; + break; + case 'upside': + $degrees = 180; + break; + default: + break; + } + + } + + // *** The default direction of imageRotate() is counter clockwise + // * This makes it clockwise + $degrees = 360 - $degrees; + + // *** Create background color + $bg = ImageColorAllocateAlpha($this->imageResized, $r, $g, $b, $a); + + // *** Fill with background + ImageFill($this->imageResized, 0, 0, $bg); + + // *** Rotate + $this->imageResized = imagerotate($this->imageResized, $degrees, $bg); // Rotate 45 degrees and allocated the transparent colour as the one to make transparent (obviously) + + // Ensure alpha transparency + ImageSaveAlpha($this->imageResized, true); + + } + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Round corners +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function roundCorners($radius = 5, $bgColor = 'transparent') + # Author: Jarrod Oberto + # Date: 19-05-2011 + # Purpose: Create rounded corners on your image + # Param in: (int) radius = the amount of curvature + # (mixed) $bgColor = the corner background color + # Param out: n/a + # Reference: + # Notes: + # + { + + // *** Check if the user wants transparency + $isTransparent = false; + if ( ! is_array($bgColor)) + { + if (fix_strtolower($bgColor) == 'transparent') + { + $isTransparent = true; + } + } + + + // *** If we use transparency, we need to color our curved mask with a unique color + if ($isTransparent) + { + $bgColor = $this->findUnusedGreen(); + } + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + if (isset($rgbArray['a'])) + { + $a = $rgbArray['a']; + } + + + // *** Create top-left corner mask (square) + $cornerImg = imagecreatetruecolor($radius, $radius); + //$cornerImg = imagecreate($radius, $radius); + + //imagealphablending($cornerImg, true); + //imagesavealpha($cornerImg, true); + + //imagealphablending($this->imageResized, false); + //imagesavealpha($this->imageResized, true); + + // *** Give it a color + $maskColor = imagecolorallocate($cornerImg, 0, 0, 0); + + + // *** Replace the mask color (black) to transparent + imagecolortransparent($cornerImg, $maskColor); + + + // *** Create the image background color + $imagebgColor = imagecolorallocate($cornerImg, $r, $g, $b); + + + // *** Fill the corner area to the user defined color + imagefill($cornerImg, 0, 0, $imagebgColor); + + + imagefilledellipse($cornerImg, $radius, $radius, $radius * 2, $radius * 2, $maskColor); + + + // *** Map to top left corner + imagecopymerge($this->imageResized, $cornerImg, 0, 0, 0, 0, $radius, $radius, 100); #tl + + // *** Map rounded corner to other corners by rotating and applying the mask + $cornerImg = imagerotate($cornerImg, 90, 0); + imagecopymerge($this->imageResized, $cornerImg, 0, $this->height - $radius, 0, 0, $radius, $radius, 100); #bl + + $cornerImg = imagerotate($cornerImg, 90, 0); + imagecopymerge($this->imageResized, $cornerImg, $this->width - $radius, $this->height - $radius, 0, 0, $radius, $radius, 100); #br + + $cornerImg = imagerotate($cornerImg, 90, 0); + imagecopymerge($this->imageResized, $cornerImg, $this->width - $radius, 0, 0, 0, $radius, $radius, 100); #tr + + + // *** If corners are to be transparent, we fill our chromakey color as transparent. + if ($isTransparent) + { + //imagecolortransparent($this->imageResized, $imagebgColor); + $this->imageResized = $this->transparentImage($this->imageResized); + imagesavealpha($this->imageResized, true); + } + + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Shadow +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addShadow($shadowAngle = 45, $blur = 15, $bgColor = 'transparent') + # + # Author: Jarrod Oberto (Adapted from Pascal Naidon) + # Ref: http://www.les-stooges.org/pascal/webdesign/vignettes/index.php?la=en + # Purpose: Add a drop shadow to your image + # Params in: (int) $angle: the angle of the shadow + # (int) $blur: the blur distance + # (mixed) $bgColor: the color of the background + # Params out: + # Notes: + # + { + // *** A higher number results in a smoother shadow + define('STEPS', $blur * 2); + + // *** Set the shadow distance + $shadowDistance = $blur * 0.25; + + // *** Set blur width and height + $blurWidth = $blurHeight = $blur; + + + if ($shadowAngle == 0) + { + $distWidth = 0; + $distHeight = 0; + } + else + { + $distWidth = $shadowDistance * cos(deg2rad($shadowAngle)); + $distHeight = $shadowDistance * sin(deg2rad($shadowAngle)); + } + + + // *** Convert color + if (fix_strtolower($bgColor) != 'transparent') + { + $rgbArray = $this->formatColor($bgColor); + $r0 = $rgbArray['r']; + $g0 = $rgbArray['g']; + $b0 = $rgbArray['b']; + } + + + $image = $this->imageResized; + $width = $this->width; + $height = $this->height; + + + $newImage = imagecreatetruecolor($width, $height); + imagecopyresampled($newImage, $image, 0, 0, 0, 0, $width, $height, $width, $height); + + + // *** RGB + $rgb = imagecreatetruecolor($width + $blurWidth, $height + $blurHeight); + $colour = imagecolorallocate($rgb, 0, 0, 0); + imagefilledrectangle($rgb, 0, 0, $width + $blurWidth, $height + $blurHeight, $colour); + $colour = imagecolorallocate($rgb, 255, 255, 255); + //imagefilledrectangle($rgb, $blurWidth*0.5-$distWidth, $blurHeight*0.5-$distHeight, $width+$blurWidth*0.5-$distWidth, $height+$blurWidth*0.5-$distHeight, $colour); + imagefilledrectangle($rgb, $blurWidth * 0.5 - $distWidth, $blurHeight * 0.5 - $distHeight, $width + $blurWidth * 0.5 - $distWidth, $height + $blurWidth * 0.5 - $distHeight, $colour); + //imagecopymerge($rgb, $newImage, 1+$blurWidth*0.5-$distWidth, 1+$blurHeight*0.5-$distHeight, 0,0, $width, $height, 100); + imagecopymerge($rgb, $newImage, $blurWidth * 0.5 - $distWidth, $blurHeight * 0.5 - $distHeight, 0, 0, $width + $blurWidth, $height + $blurHeight, 100); + + + // *** Shadow (alpha) + $shadow = imagecreatetruecolor($width + $blurWidth, $height + $blurHeight); + imagealphablending($shadow, false); + $colour = imagecolorallocate($shadow, 0, 0, 0); + imagefilledrectangle($shadow, 0, 0, $width + $blurWidth, $height + $blurHeight, $colour); + + + for ($i = 0; $i <= STEPS; $i++) + { + + $t = ((1.0 * $i) / STEPS); + $intensity = 255 * $t * $t; + + $colour = imagecolorallocate($shadow, $intensity, $intensity, $intensity); + $points = array( + $blurWidth * $t, $blurHeight, // Point 1 (x, y) + $blurWidth, $blurHeight * $t, // Point 2 (x, y) + $width, $blurHeight * $t, // Point 3 (x, y) + $width + $blurWidth * (1 - $t), $blurHeight, // Point 4 (x, y) + $width + $blurWidth * (1 - $t), $height, // Point 5 (x, y) + $width, $height + $blurHeight * (1 - $t), // Point 6 (x, y) + $blurWidth, $height + $blurHeight * (1 - $t), // Point 7 (x, y) + $blurWidth * $t, $height // Point 8 (x, y) + ); + imagepolygon($shadow, $points, 8, $colour); + } + + for ($i = 0; $i <= STEPS; $i++) + { + + $t = ((1.0 * $i) / STEPS); + $intensity = 255 * $t * $t; + + $colour = imagecolorallocate($shadow, $intensity, $intensity, $intensity); + imagefilledarc($shadow, $blurWidth - 1, $blurHeight - 1, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 180, 268, $colour, IMG_ARC_PIE); + imagefilledarc($shadow, $width, $blurHeight - 1, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 270, 358, $colour, IMG_ARC_PIE); + imagefilledarc($shadow, $width, $height, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 0, 90, $colour, IMG_ARC_PIE); + imagefilledarc($shadow, $blurWidth - 1, $height, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 90, 180, $colour, IMG_ARC_PIE); + } + + + $colour = imagecolorallocate($shadow, 255, 255, 255); + imagefilledrectangle($shadow, $blurWidth, $blurHeight, $width, $height, $colour); + imagefilledrectangle($shadow, $blurWidth * 0.5 - $distWidth, $blurHeight * 0.5 - $distHeight, $width + $blurWidth * 0.5 - 1 - $distWidth, $height + $blurHeight * 0.5 - 1 - $distHeight, $colour); + + + // *** The magic + imagealphablending($rgb, false); + + for ($theX = 0; $theX < imagesx($rgb); $theX++) + { + for ($theY = 0; $theY < imagesy($rgb); $theY++) + { + + // *** Get the RGB values for every pixel of the RGB image + $colArray = imagecolorat($rgb, $theX, $theY); + $r = ($colArray >> 16) & 0xFF; + $g = ($colArray >> 8) & 0xFF; + $b = $colArray & 0xFF; + + // *** Get the alpha value for every pixel of the shadow image + $colArray = imagecolorat($shadow, $theX, $theY); + $a = $colArray & 0xFF; + $a = 127 - floor($a / 2); + $t = $a / 128.0; + + // *** Create color + if (fix_strtolower($bgColor) == 'transparent') + { + $myColour = imagecolorallocatealpha($rgb, $r, $g, $b, $a); + } + else + { + $myColour = imagecolorallocate($rgb, $r * (1.0 - $t) + $r0 * $t, $g * (1.0 - $t) + $g0 * $t, $b * (1.0 - $t) + $b0 * $t); + } + + // *** Add color to new rgb image + imagesetpixel($rgb, $theX, $theY, $myColour); + } + } + + imagealphablending($rgb, true); + imagesavealpha($rgb, true); + + $this->imageResized = $rgb; + + imagedestroy($image); + imagedestroy($newImage); + imagedestroy($shadow); + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Add Caption Box +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addCaptionBox($side = 'b', $thickness = 50, $padding = 0, $bgColor = '#000', $transaprencyAmount = 30) + # + # Author: Jarrod Oberto + # Date: 26 May 2011 + # Purpose: Add a caption box + # Params in: (str) $side: the side to add the caption box (t, r, b, or l). + # (int) $thickness: how thick you want the caption box to be. + # (mixed) $bgColor: The color of the caption box. + # (int) $transaprencyAmount: The amount of transparency to be + # applied. + # Params out: n/a + # Notes: + # + { + $side = fix_strtolower($side); + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + $positionArray = $this->calculateCaptionBoxPosition($side, $thickness, $padding); + + // *** Store incase we want to use method addTextToCaptionBox() + $this->captionBoxPositionArray = $positionArray; + + + $transaprencyAmount = $this->invertTransparency($transaprencyAmount, 127, false); + $transparent = imagecolorallocatealpha($this->imageResized, $r, $g, $b, $transaprencyAmount); + imagefilledrectangle($this->imageResized, $positionArray['x1'], $positionArray['y1'], $positionArray['x2'], $positionArray['y2'], $transparent); + } + + ## -------------------------------------------------------- + + public function addTextToCaptionBox($text, $fontColor = '#fff', $fontSize = 12, $angle = 0, $font = null) + # + # Author: Jarrod Oberto + # Date: 03 Aug 11 + # Purpose: Simplify adding text to a caption box by automatically + # locating the center of the caption box + # Params in: The usually text paams (less a couple) + # Params out: n/a + # Notes: + # + { + + // *** Get the caption box measurements + if (count($this->captionBoxPositionArray) == 4) + { + $x1 = $this->captionBoxPositionArray['x1']; + $x2 = $this->captionBoxPositionArray['x2']; + $y1 = $this->captionBoxPositionArray['y1']; + $y2 = $this->captionBoxPositionArray['y2']; + } + else + { + if ($this->debug) + { + throw new Exception('No caption box found.'); + } + else + { + return false; + } + } + + + // *** Get text font + $font = $this->getTextFont($font); + + // *** Get text size + $textSizeArray = $this->getTextSize($fontSize, $angle, $font, $text); + $textWidth = $textSizeArray['width']; + $textHeight = $textSizeArray['height']; + + // *** Find the width/height middle points + $boxXMiddle = (($x2 - $x1) / 2); + $boxYMiddle = (($y2 - $y1) / 2); + + // *** Box middle - half the text width/height + $xPos = ($x1 + $boxXMiddle) - ($textWidth / 2); + $yPos = ($y1 + $boxYMiddle) - ($textHeight / 2); + + $pos = $xPos . 'x' . $yPos; + + $this->addText($text, $pos, $padding = 0, $fontColor, $fontSize, $angle, $font); + + } + + ## -------------------------------------------------------- + + private function calculateCaptionBoxPosition($side, $thickness, $padding) + { + $positionArray = array(); + + switch ($side) + { + case 't': + $positionArray['x1'] = 0; + $positionArray['y1'] = $padding; + $positionArray['x2'] = $this->width; + $positionArray['y2'] = $thickness + $padding; + break; + case 'r': + $positionArray['x1'] = $this->width - $thickness - $padding; + $positionArray['y1'] = 0; + $positionArray['x2'] = $this->width - $padding; + $positionArray['y2'] = $this->height; + break; + case 'b': + $positionArray['x1'] = 0; + $positionArray['y1'] = $this->height - $thickness - $padding; + $positionArray['x2'] = $this->width; + $positionArray['y2'] = $this->height - $padding; + break; + case 'l': + $positionArray['x1'] = $padding; + $positionArray['y1'] = 0; + $positionArray['x2'] = $thickness + $padding; + $positionArray['y2'] = $this->height; + break; + + default: + break; + } + + return $positionArray; + + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Get EXIF Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function getExif($debug = false) + # Author: Jarrod Oberto + # Date: 07-05-2011 + # Purpose: Get image EXIF data + # Param in: n/a + # Param out: An associate array of EXIF data + # Reference: + # Notes: + # 23 May 13 : added orientation flag -jco + # + { + + if ( ! $this->debug || ! $debug) + { + $debug = false; + } + + // *** Check all is good - check the EXIF library exists and the file exists, too. + if ( ! $this->testEXIFInstalled()) + { + if ($debug) + { + throw new Exception('The EXIF Library is not installed.'); + } + else + { + return array(); + } + }; + if ( ! file_exists($this->fileName)) + { + if ($debug) + { + throw new Exception('Image not found.'); + } + else + { + return array(); + } + }; + if ($this->fileExtension != '.jpg') + { + if ($debug) + { + throw new Exception('Metadata not supported for this image type.'); + } + else + { + return array(); + } + }; + $exifData = exif_read_data($this->fileName, 'IFD0'); + + // *** Format the apperture value + $ev = $exifData['ApertureValue']; + $apPeicesArray = explode('/', $ev); + if (count($apPeicesArray) == 2) + { + $apertureValue = round($apPeicesArray[0] / $apPeicesArray[1], 2, PHP_ROUND_HALF_DOWN) . ' EV'; + } + else + { + $apertureValue = ''; + } + + // *** Format the focal length + $focalLength = $exifData['FocalLength']; + $flPeicesArray = explode('/', $focalLength); + if (count($flPeicesArray) == 2) + { + $focalLength = $flPeicesArray[0] / $flPeicesArray[1] . '.0 mm'; + } + else + { + $focalLength = ''; + } + + // *** Format fNumber + $fNumber = $exifData['FNumber']; + $fnPeicesArray = explode('/', $fNumber); + if (count($fnPeicesArray) == 2) + { + $fNumber = $fnPeicesArray[0] / $fnPeicesArray[1]; + } + else + { + $fNumber = ''; + } + + // *** Resolve ExposureProgram + if (isset($exifData['ExposureProgram'])) + { + $ep = $exifData['ExposureProgram']; + } + if (isset($ep)) + { + $ep = $this->resolveExposureProgram($ep); + } + + + // *** Resolve MeteringMode + $mm = $exifData['MeteringMode']; + $mm = $this->resolveMeteringMode($mm); + + // *** Resolve Flash + $flash = $exifData['Flash']; + $flash = $this->resolveFlash($flash); + + + if (isset($exifData['Make'])) + { + $exifDataArray['make'] = $exifData['Make']; + } + else + { + $exifDataArray['make'] = ''; + } + + if (isset($exifData['Model'])) + { + $exifDataArray['model'] = $exifData['Model']; + } + else + { + $exifDataArray['model'] = ''; + } + + if (isset($exifData['DateTime'])) + { + $exifDataArray['date'] = $exifData['DateTime']; + } + else + { + $exifDataArray['date'] = ''; + } + + if (isset($exifData['ExposureTime'])) + { + $exifDataArray['exposure time'] = $exifData['ExposureTime'] . ' sec.'; + } + else + { + $exifDataArray['exposure time'] = ''; + } + + if ($apertureValue != '') + { + $exifDataArray['aperture value'] = $apertureValue; + } + else + { + $exifDataArray['aperture value'] = ''; + } + + if (isset($exifData['COMPUTED']['ApertureFNumber'])) + { + $exifDataArray['f-stop'] = $exifData['COMPUTED']['ApertureFNumber']; + } + else + { + $exifDataArray['f-stop'] = ''; + } + + if (isset($exifData['FNumber'])) + { + $exifDataArray['fnumber'] = $exifData['FNumber']; + } + else + { + $exifDataArray['fnumber'] = ''; + } + + if ($fNumber != '') + { + $exifDataArray['fnumber value'] = $fNumber; + } + else + { + $exifDataArray['fnumber value'] = ''; + } + + if (isset($exifData['ISOSpeedRatings'])) + { + $exifDataArray['iso'] = $exifData['ISOSpeedRatings']; + } + else + { + $exifDataArray['iso'] = ''; + } + + if ($focalLength != '') + { + $exifDataArray['focal length'] = $focalLength; + } + else + { + $exifDataArray['focal length'] = ''; + } + + if (isset($ep)) + { + $exifDataArray['exposure program'] = $ep; + } + else + { + $exifDataArray['exposure program'] = ''; + } + + if ($mm != '') + { + $exifDataArray['metering mode'] = $mm; + } + else + { + $exifDataArray['metering mode'] = ''; + } + + if ($flash != '') + { + $exifDataArray['flash status'] = $flash; + } + else + { + $exifDataArray['flash status'] = ''; + } + + if (isset($exifData['Artist'])) + { + $exifDataArray['creator'] = $exifData['Artist']; + } + else + { + $exifDataArray['creator'] = ''; + } + + if (isset($exifData['Copyright'])) + { + $exifDataArray['copyright'] = $exifData['Copyright']; + } + else + { + $exifDataArray['copyright'] = ''; + } + + // *** Orientation + if (isset($exifData['Orientation'])) + { + $exifDataArray['orientation'] = $exifData['Orientation']; + } + else + { + $exifDataArray['orientation'] = ''; + } + + return $exifDataArray; + } + + ## -------------------------------------------------------- + + private function resolveExposureProgram($ep) + { + switch ($ep) + { + case 0: + $ep = ''; + break; + case 1: + $ep = 'manual'; + break; + case 2: + $ep = 'normal program'; + break; + case 3: + $ep = 'aperture priority'; + break; + case 4: + $ep = 'shutter priority'; + break; + case 5: + $ep = 'creative program'; + break; + case 6: + $ep = 'action program'; + break; + case 7: + $ep = 'portrait mode'; + break; + case 8: + $ep = 'landscape mode'; + break; + + default: + break; + } + + return $ep; + } + + ## -------------------------------------------------------- + + private function resolveMeteringMode($mm) + { + switch ($mm) + { + case 0: + $mm = 'unknown'; + break; + case 1: + $mm = 'average'; + break; + case 2: + $mm = 'center weighted average'; + break; + case 3: + $mm = 'spot'; + break; + case 4: + $mm = 'multi spot'; + break; + case 5: + $mm = 'pattern'; + break; + case 6: + $mm = 'partial'; + break; + case 255: + $mm = 'other'; + break; + + default: + break; + } + + return $mm; + } + + ## -------------------------------------------------------- + + private function resolveFlash($flash) + { + switch ($flash) + { + case 0: + $flash = 'flash did not fire'; + break; + case 1: + $flash = 'flash fired'; + break; + case 5: + $flash = 'strobe return light not detected'; + break; + case 7: + $flash = 'strobe return light detected'; + break; + case 9: + $flash = 'flash fired, compulsory flash mode'; + break; + case 13: + $flash = 'flash fired, compulsory flash mode, return light not detected'; + break; + case 15: + $flash = 'flash fired, compulsory flash mode, return light detected'; + break; + case 16: + $flash = 'flash did not fire, compulsory flash mode'; + break; + case 24: + $flash = 'flash did not fire, auto mode'; + break; + case 25: + $flash = 'flash fired, auto mode'; + break; + case 29: + $flash = 'flash fired, auto mode, return light not detected'; + break; + case 31: + $flash = 'flash fired, auto mode, return light detected'; + break; + case 32: + $flash = 'no flash function'; + break; + case 65: + $flash = 'flash fired, red-eye reduction mode'; + break; + case 69: + $flash = 'flash fired, red-eye reduction mode, return light not detected'; + break; + case 71: + $flash = 'flash fired, red-eye reduction mode, return light detected'; + break; + case 73: + $flash = 'flash fired, compulsory flash mode, red-eye reduction mode'; + break; + case 77: + $flash = 'flash fired, compulsory flash mode, red-eye reduction mode, return light not detected'; + break; + case 79: + $flash = 'flash fired, compulsory flash mode, red-eye reduction mode, return light detected'; + break; + case 89: + $flash = 'flash fired, auto mode, red-eye reduction mode'; + break; + case 93: + $flash = 'flash fired, auto mode, return light not detected, red-eye reduction mode'; + break; + case 95: + $flash = 'flash fired, auto mode, return light detected, red-eye reduction mode'; + break; + + default: + break; + } + + return $flash; + + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Get IPTC Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Write IPTC Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function writeIPTCcaption($value) + # Caption + { + $this->writeIPTC(120, $value); + } + + ## -------------------------------------------------------- + + public function writeIPTCwriter($value) + { + //$this->writeIPTC(65, $value); + } + + ## -------------------------------------------------------- + + private function writeIPTC($dat, $value) + { + + # LIMIT TO JPG + + $caption_block = $this->iptc_maketag(2, $dat, $value); + $image_string = iptcembed($caption_block, $this->fileName); + file_put_contents('iptc.jpg', $image_string); + } + +## -------------------------------------------------------- + + private function iptc_maketag($rec, $dat, $val) + # Author: Thies C. Arntzen + # Purpose: Function to format the new IPTC text + # Param in: $rec: Application record. (We’re working with #2) + # $dat: Index. (120 for caption, 118 for contact. See the IPTC IIM + # specification: + # http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf + # $val: Value/data/text. Make sure this is within the length + # constraints of the IPTC IIM specification + # Ref: http://blog.peterhaza.no/working-with-image-meta-data-in-exif-and-iptc-headers-from-php/ + # http://php.net/manual/en/function.iptcembed.php + # + { + $len = strlen($val); + if ($len < 0x8000) + { + return chr(0x1c) . chr($rec) . chr($dat) . + chr($len >> 8) . + chr($len & 0xff) . + $val; + } + else + { + return chr(0x1c) . chr($rec) . chr($dat) . + chr(0x80) . chr(0x04) . + chr(($len >> 24) & 0xff) . + chr(($len >> 16) & 0xff) . + chr(($len >> 8) & 0xff) . + chr(($len) & 0xff) . + $val; + } + } + + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Write XMP Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + //http://xmpphptoolkit.sourceforge.net/ + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Add Text +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addText($text, $pos = '20x20', $padding = 0, $fontColor = '#fff', $fontSize = 12, $angle = 0, $font = null) + # Author: Jarrod Oberto + # Date: 18-11-09 + # Purpose: Add text to an image + # Param in: + # Param out: + # Reference: http://php.net/manual/en/function.imagettftext.php + # Notes: Make sure you supply the font. + # + { + + // *** Convert color + $rgbArray = $this->formatColor($fontColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + // *** Get text font + $font = $this->getTextFont($font); + + // *** Get text size + $textSizeArray = $this->getTextSize($fontSize, $angle, $font, $text); + $textWidth = $textSizeArray['width']; + $textHeight = $textSizeArray['height']; + + // *** Find co-ords to place text + $posArray = $this->calculatePosition($pos, $padding, $textWidth, $textHeight, false); + $x = $posArray['width']; + $y = $posArray['height']; + + $fontColor = imagecolorallocate($this->imageResized, $r, $g, $b); + + // *** Add text + imagettftext($this->imageResized, $fontSize, $angle, $x, $y, $fontColor, $font, $text); + } + + ## -------------------------------------------------------- + + private function getTextFont($font) + { + // *** Font path (shou + $fontPath = dirname(__FILE__) . '/' . $this->fontDir; + + + // *** The below is/may be needed depending on your version (see ref) + putenv('GDFONTPATH=' . realpath('.')); + + // *** Check if the passed in font exsits... + if ($font == null || ! file_exists($font)) + { + + // *** ...If not, default to this font. + $font = $fontPath . '/arimo.ttf'; + + // *** Check our default font exists... + if ( ! file_exists($font)) + { + + // *** If not, return false + if ($this->debug) + { + throw new Exception('Font not found'); + } + else + { + return false; + } + } + } + + return $font; + + } + + ## -------------------------------------------------------- + + private function getTextSize($fontSize, $angle, $font, $text) + { + + // *** Define box (so we can get the width) + $box = @imageTTFBbox($fontSize, $angle, $font, $text); + + // *** Get width of text from dimensions + $textWidth = abs($box[4] - $box[0]); + + // *** Get height of text from dimensions (should also be same as $fontSize) + $textHeight = abs($box[5] - $box[1]); + + return array( 'height' => $textHeight, 'width' => $textWidth ); + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Add Watermark +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addWatermark($watermarkImage, $pos, $padding = 0, $opacity = 0) + # Author: Jarrod Oberto + # Date: 18-11-09 + # Purpose: Add watermark image + # Param in: (str) $watermark: The watermark image + # (str) $pos: Could be a pre-determined position such as: + # tl = top left, + # t = top (middle), + # tr = top right, + # l = left, + # m = middle, + # r = right, + # bl = bottom left, + # b = bottom (middle), + # br = bottom right + # Or, it could be a co-ordinate position such as: 50x100 + # + # (int) $padding: If using a pre-determined position you can + # adjust the padding from the edges by passing an amount + # in pixels. If using co-ordinates, this value is ignored. + # Param out: + # Reference: http://www.php.net/manual/en/image.examples-watermark.php + # Notes: Based on example in reference. + # + # + { + + // Load the stamp and the photo to apply the watermark to + $stamp = $this->openImage($watermarkImage); # stamp + $im = $this->imageResized; # photo + + // *** Get stamps width and height + $sx = imagesx($stamp); + $sy = imagesy($stamp); + + // *** Find co-ords to place image + $posArray = $this->calculatePosition($pos, $padding, $sx, $sy); + $x = $posArray['width']; + $y = $posArray['height']; + + // *** Set watermark opacity + if (fix_strtolower(strrchr($watermarkImage, '.')) == '.png') + { + + $opacity = $this->invertTransparency($opacity, 100); + $this->filterOpacity($stamp, $opacity); + } + + // Copy the watermark image onto our photo + imagecopy($im, $stamp, $x, $y, 0, 0, imagesx($stamp), imagesy($stamp)); + + } + + ## -------------------------------------------------------- + + private function calculatePosition($pos, $padding, $assetWidth, $assetHeight, $upperLeft = true) + # + # Author: Jarrod Oberto + # Date: 08-05-11 + # Purpose: Calculate the x, y pixel cordinates of the asset to place + # Params in: (str) $pos: Either something like: "tl", "l", "br" or an + # exact position like: "100x50" + # (int) $padding: The amount of padding from the edge. Only + # used for the predefined $pos. + # (int) $assetWidth: The width of the asset to add to the image + # (int) $assetHeight: The height of the asset to add to the image + # (bol) $upperLeft: if true, the asset will be positioned based + # on the upper left x, y coords. If false, it means you're + # using the lower left as the basepoint and this will + # convert it to the upper left position + # Params out: + # NOTE: this is done from the UPPER left corner!! But will convert lower + # left basepoints to upper left if $upperleft is set to false + # + # + { + $pos = fix_strtolower($pos); + + // *** If co-ords have been entered + if (strstr($pos, 'x')) + { + $pos = str_replace(' ', '', $pos); + + $xyArray = explode('x', $pos); + list($width, $height) = $xyArray; + + } + else + { + + switch ($pos) + { + case 'tl': + $width = 0 + $padding; + $height = 0 + $padding; + break; + + case 't': + $width = ($this->width / 2) - ($assetWidth / 2); + $height = 0 + $padding; + break; + + case 'tr': + $width = $this->width - $assetWidth - $padding; + $height = 0 + $padding;; + break; + + case 'l': + $width = 0 + $padding; + $height = ($this->height / 2) - ($assetHeight / 2); + break; + + case 'm': + $width = ($this->width / 2) - ($assetWidth / 2); + $height = ($this->height / 2) - ($assetHeight / 2); + break; + + case 'r': + $width = $this->width - $assetWidth - $padding; + $height = ($this->height / 2) - ($assetHeight / 2); + break; + + case 'bl': + $width = 0 + $padding; + $height = $this->height - $assetHeight - $padding; + break; + + case 'b': + $width = ($this->width / 2) - ($assetWidth / 2); + $height = $this->height - $assetHeight - $padding; + break; + + case 'br': + $width = $this->width - $assetWidth - $padding; + $height = $this->height - $assetHeight - $padding; + break; + + default: + $width = 0; + $height = 0; + break; + } + } + + if ( ! $upperLeft) + { + $height = $height + $assetHeight; + } + + return array( 'width' => $width, 'height' => $height ); + } + + + ## -------------------------------------------------------- + + private function filterOpacity(&$img, $opacity = 75) + # + # Author: aiden dot mail at freemail dot hu + # Author date: 29-03-08 08:16 + # Date added: 08-05-11 + # Purpose: Change opacity of image + # Params in: $img: Image resource id + # (int) $opacity: the opacity amount: 0-100, 100 being not opaque. + # Params out: (bool) true on success, else false + # Ref: http://www.php.net/manual/en/function.imagefilter.php#82162 + # Notes: png only + # + { + + if ( ! isset($opacity)) + { + return false; + } + + if ($opacity == 100) + { + return true; + } + + $opacity /= 100; + + //get image width and height + $w = imagesx($img); + $h = imagesy($img); + + //turn alpha blending off + imagealphablending($img, false); + + //find the most opaque pixel in the image (the one with the smallest alpha value) + $minalpha = 127; + for ($x = 0; $x < $w; $x++) + { + for ($y = 0; $y < $h; $y++) + { + $alpha = (imagecolorat($img, $x, $y) >> 24) & 0xFF; + if ($alpha < $minalpha) + { + $minalpha = $alpha; + } + } + } + + //loop through image pixels and modify alpha for each + for ($x = 0; $x < $w; $x++) + { + for ($y = 0; $y < $h; $y++) + { + //get current alpha value (represents the TANSPARENCY!) + $colorxy = imagecolorat($img, $x, $y); + $alpha = ($colorxy >> 24) & 0xFF; + //calculate new alpha + if ($minalpha !== 127) + { + $alpha = 127 + 127 * $opacity * ($alpha - 127) / (127 - $minalpha); + } + else + { + $alpha += 127 * $opacity; + } + //get the color index with new alpha + $alphacolorxy = imagecolorallocatealpha($img, ($colorxy >> 16) & 0xFF, ($colorxy >> 8) & 0xFF, $colorxy & 0xFF, $alpha); + //set pixel with the new color + opacity + if ( ! imagesetpixel($img, $x, $y, $alphacolorxy)) + { + + return false; + } + } + } + + return true; + } + +## -------------------------------------------------------- + + private function openImage($file) + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: + # Param in: + # Param out: n/a + # Reference: + # Notes: + # + { + + if ( ! file_exists($file) && ! $this->checkStringStartsWith('http://', $file) && ! $this->checkStringStartsWith('https://', $file) ) + { + if ($this->debug) + { + throw new Exception('Image not found.'); + } + else + { + throw new Exception(); + } + }; + + // *** Get extension / image type + $extension = mime_content_type($file); + $extension = fix_strtolower($extension); + $extension = str_replace('image/', '', $extension); + switch ($extension) + { + case 'jpg': + case 'jpeg': + $img = @imagecreatefromjpeg($file); + break; + case 'gif': + $img = @imagecreatefromgif($file); + break; + case 'png': + $img = @imagecreatefrompng($file); + break; + case 'bmp': + $img = @$this->imagecreatefrombmp($file); + break; + case 'psd': + case 'vnd.adobe.photoshop': + $img = @$this->imagecreatefrompsd($file); + break; + + + // ... etc + + default: + $img = false; + break; + } + + return $img; + } + +## -------------------------------------------------------- + + public function reset() + # + # Author: Jarrod Oberto + # Date: 30-08-11 + # Purpose: Reset the resource (allow further editing) + # Params in: + # Params out: + # Notes: + # + { + $this->__construct($this->fileName); + } + +## -------------------------------------------------------- + + public function saveImage($savePath, $imageQuality = "100") + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: Saves the image + # Param in: $savePath: Where to save the image including filename: + # $imageQuality: image quality you want the image saved at 0-100 + # Param out: n/a + # Reference: + # Notes: * gif doesn't have a quality parameter + # * jpg has a quality setting 0-100 (100 being the best) + # * png has a quality setting 0-9 (0 being the best) + # + # * bmp files have no native support for bmp files. We use a + # third party class to save as bmp. + { + + // *** Perform a check or two. + if ( ! is_resource($this->imageResized)) + { + if ($this->debug) + { + throw new Exception('saveImage: This is not a resource.'); + } + else + { + throw new Exception(); + } + } + $fileInfoArray = pathInfo($savePath); + clearstatcache(); + if ( ! is_writable($fileInfoArray['dirname'])) + { + if ($this->debug) + { + throw new Exception('The path is not writable. Please check your permissions.'); + } + else + { + throw new Exception(); + } + } + + // *** Get extension + $extension = strrchr($savePath, '.'); + $extension = fix_strtolower($extension); + + $error = ''; + + switch ($extension) + { + case '.jpg': + case '.jpeg': + $this->checkInterlaceImage($this->isInterlace); + if (imagetypes() & IMG_JPG) + { + imagejpeg($this->imageResized, $savePath, $imageQuality); + } + else + { + $error = 'jpg'; + } + break; + + case '.gif': + $this->checkInterlaceImage($this->isInterlace); + if (imagetypes() & IMG_GIF) + { + imagegif($this->imageResized, $savePath); + } + else + { + $error = 'gif'; + } + break; + + case '.png': + // *** Scale quality from 0-100 to 0-9 + $scaleQuality = round(($imageQuality / 100) * 9); + + // *** Invert qualit setting as 0 is best, not 9 + $invertScaleQuality = 9 - $scaleQuality; + + $this->checkInterlaceImage($this->isInterlace); + if (imagetypes() & IMG_PNG) + { + imagepng($this->imageResized, $savePath, $invertScaleQuality); + } + else + { + $error = 'png'; + } + break; + + case '.bmp': + file_put_contents($savePath, $this->GD2BMPstring($this->imageResized)); + break; + + + // ... etc + + default: + // *** No extension - No save. + $this->errorArray[] = 'This file type (' . $extension . ') is not supported. File not saved.'; + break; + } + + //imagedestroy($this->imageResized); + + // *** Display error if a file type is not supported. + if ($error != '') + { + $this->errorArray[] = $error . ' support is NOT enabled. File not saved.'; + } + } + +## -------------------------------------------------------- + + public function displayImage($fileType = 'jpg', $imageQuality = "100") + # Author: Jarrod Oberto + # Date: 18-11-09 + # Purpose: Display images directly to the browser + # Param in: The image type you want to display + # Param out: + # Reference: + # Notes: + # + { + + if ( ! is_resource($this->imageResized)) + { + if ($this->debug) + { + throw new Exception('saveImage: This is not a resource.'); + } + else + { + throw new Exception(); + } + } + + switch ($fileType) + { + case 'jpg': + case 'jpeg': + header('Content-type: image/jpeg'); + imagejpeg($this->imageResized, '', $imageQuality); + break; + case 'gif': + header('Content-type: image/gif'); + imagegif($this->imageResized); + break; + case 'png': + header('Content-type: image/png'); + + // *** Scale quality from 0-100 to 0-9 + $scaleQuality = round(($imageQuality / 100) * 9); + + // *** Invert qualit setting as 0 is best, not 9 + $invertScaleQuality = 9 - $scaleQuality; + + imagepng($this->imageResized, '', $invertScaleQuality); + break; + case 'bmp': + echo 'bmp file format is not supported.'; + break; + + // ... etc + + default: + // *** No extension - No save. + break; + } + + + //imagedestroy($this->imageResized); + } + +## -------------------------------------------------------- + + public function setTransparency($bool) + # Sep 2011 + { + $this->keepTransparency = $bool; + } + +## -------------------------------------------------------- + + public function setFillColor($value) + # Sep 2011 + # Param in: (mixed) $value: (array) Could be an array of RGB + # (str) Could be hex #ffffff or #fff, fff, ffffff + # + # If the keepTransparency is set to false, then no transparency is to be used. + # This is ideal when you want to save as jpg. + # + # this method allows you to set the background color to use instead of + # transparency. + # + { + $colorArray = $this->formatColor($value); + $this->fillColorArray = $colorArray; + } + +## -------------------------------------------------------- + + public function setCropFromTop($value) + # Sep 2011 + { + $this->cropFromTopPercent = $value; + } + +## -------------------------------------------------------- + + public function testGDInstalled() + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: Test to see if GD is installed + # Param in: n/a + # Param out: (bool) True is gd extension loaded otherwise false + # Reference: + # Notes: + # + { + if (extension_loaded('gd') && function_exists('gd_info')) + { + $gdInstalled = true; + } + else + { + $gdInstalled = false; + } + + return $gdInstalled; + } + +## -------------------------------------------------------- + + public function testEXIFInstalled() + # Author: Jarrod Oberto + # Date: 08-05-11 + # Purpose: Test to see if EXIF is installed + # Param in: n/a + # Param out: (bool) True is exif extension loaded otherwise false + # Reference: + # Notes: + # + { + if (extension_loaded('exif')) + { + $exifInstalled = true; + } + else + { + $exifInstalled = false; + } + + return $exifInstalled; + } + +## -------------------------------------------------------- + + public function testIsImage($image) + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: Test if file is an image + # Param in: n/a + # Param out: n/a + # Reference: + # Notes: + # + { + if ($image) + { + $fileIsImage = true; + } + else + { + $fileIsImage = false; + } + + return $fileIsImage; + } + +## -------------------------------------------------------- + + public function testFunct() + # Author: Jarrod Oberto + # Date: 27-02-08 + # Purpose: Test Function + # Param in: n/a + # Param out: n/a + # Reference: + # Notes: + # + { + echo $this->height; + } + +## -------------------------------------------------------- + + public function setForceStretch($value) + # Author: Jarrod Oberto + # Date: 23-12-10 + # Purpose: + # Param in: (bool) $value + # Param out: n/a + # Reference: + # Notes: + # + { + $this->forceStretch = $value; + } + +## -------------------------------------------------------- + + public function setFile($fileName) + # Author: Jarrod Oberto + # Date: 28-02-08 + # Purpose: + # Param in: n/a + # Param out: n/a + # Reference: + # Notes: + # + { + self::__construct($fileName); + } + +## -------------------------------------------------------- + + public function getFileName() + # Author: Jarrod Oberto + # Date: 10-09-08 + # Purpose: + # Param in: n/a + # Param out: n/a + # Reference: + # Notes: + # + { + return $this->fileName; + } + +## -------------------------------------------------------- + + public function getHeight() + { + return $this->height; + } + +## -------------------------------------------------------- + + public function getWidth() + { + return $this->width; + } + +## -------------------------------------------------------- + + public function getOriginalHeight() + { + return $this->heightOriginal; + } + +## -------------------------------------------------------- + + public function getOriginalWidth() + { + return $this->widthOriginal; + } + +## -------------------------------------------------------- + + public function getErrors() + # Author: Jarrod Oberto + # Date: 19-11-09 + # Purpose: Returns the error array + # Param in: n/a + # Param out: Array of errors + # Reference: + # Notes: + # + { + return $this->errorArray; + } + +## -------------------------------------------------------- + + private function checkInterlaceImage($isEnabled) + # jpg will use progressive (they don't use interace) + { + if ($isEnabled) + { + imageinterlace($this->imageResized, $isEnabled); + } + } + +## -------------------------------------------------------- + + protected function formatColor($value) + # Author: Jarrod Oberto + # Date: 09-05-11 + # Purpose: Determine color method passed in and return color as RGB + # Param in: (mixed) $value: (array) Could be an array of RGB + # (str) Could be hex #ffffff or #fff, fff, ffffff + # Param out: + # Reference: + # Notes: + # + { + $rgbArray = array(); + + // *** If it's an array it should be R, G, B + if (is_array($value)) + { + + if (key($value) == 0 && count($value) == 3) + { + + $rgbArray['r'] = $value[0]; + $rgbArray['g'] = $value[1]; + $rgbArray['b'] = $value[2]; + + } + else + { + $rgbArray = $value; + } + } + else + { + if (fix_strtolower($value) == 'transparent') + { + + $rgbArray = array( + 'r' => 255, + 'g' => 255, + 'b' => 255, + 'a' => 127 + ); + + } + else + { + + // *** ...Else it should be hex. Let's make it RGB + $rgbArray = $this->hex2dec($value); + } + } + + return $rgbArray; + } + + ## -------------------------------------------------------- + + function hex2dec($hex) + # Purpose: Convert #hex color to RGB + { + $color = str_replace('#', '', $hex); + + if (strlen($color) == 3) + { + $color = $color . $color; + } + + $rgb = array( + 'r' => hexdec(substr($color, 0, 2)), + 'g' => hexdec(substr($color, 2, 2)), + 'b' => hexdec(substr($color, 4, 2)), + 'a' => 0 + ); + + return $rgb; + } + + ## -------------------------------------------------------- + + private function createImageColor($colorArray) + { + $r = $colorArray['r']; + $g = $colorArray['g']; + $b = $colorArray['b']; + + return imagecolorallocate($this->imageResized, $r, $g, $b); + } + + ## -------------------------------------------------------- + + private function testColorExists($colorArray) + { + $r = $colorArray['r']; + $g = $colorArray['g']; + $b = $colorArray['b']; + + if (imagecolorexact($this->imageResized, $r, $g, $b) == -1) + { + return false; + } + else + { + return true; + } + } + + ## -------------------------------------------------------- + + private function findUnusedGreen() + # Purpose: We find a green color suitable to use like green-screen effect. + # Therefore, the color must not exist in the image. + { + $green = 255; + + do + { + + $greenChroma = array( 0, $green, 0 ); + $colorArray = $this->formatColor($greenChroma); + $match = $this->testColorExists($colorArray); + $green--; + + } while ($match == false && $green > 0); + + // *** If no match, just bite the bullet and use green value of 255 + if ( ! $match) + { + $greenChroma = array( 0, $green, 0 ); + } + + return $greenChroma; + } + + ## -------------------------------------------------------- + + private function findUnusedBlue() + # Purpose: We find a green color suitable to use like green-screen effect. + # Therefore, the color must not exist in the image. + { + $blue = 255; + + do + { + + $blueChroma = array( 0, 0, $blue ); + $colorArray = $this->formatColor($blueChroma); + $match = $this->testColorExists($colorArray); + $blue--; + + } while ($match == false && $blue > 0); + + // *** If no match, just bite the bullet and use blue value of 255 + if ( ! $match) + { + $blueChroma = array( 0, 0, $blue ); + } + + return $blueChroma; + } + + ## -------------------------------------------------------- + + private function invertTransparency($value, $originalMax, $invert = true) + # Purpose: This does two things: + # 1) Convert the range from 0-127 to 0-100 + # 2) Inverts value to 100 is not transparent while 0 is fully + # transparent (like Photoshop) + { + // *** Test max range + if ($value > $originalMax) + { + $value = $originalMax; + } + + // *** Test min range + if ($value < 0) + { + $value = 0; + } + + if ($invert) + { + return $originalMax - (($value / 100) * $originalMax); + } + else + { + return ($value / 100) * $originalMax; + } + } + + ## -------------------------------------------------------- + + private function transparentImage($src) + { + // *** making images with white bg transparent + $r1 = 0; + $g1 = 255; + $b1 = 0; + for ($x = 0; $x < imagesx($src); ++$x) + { + for ($y = 0; $y < imagesy($src); ++$y) + { + $color = imagecolorat($src, $x, $y); + $r = ($color >> 16) & 0xFF; + $g = ($color >> 8) & 0xFF; + $b = $color & 0xFF; + for ($i = 0; $i < 270; $i++) + { + //if ($r . $g . $b == ($r1 + $i) . ($g1 + $i) . ($b1 + $i)) { + if ($r == 0 && $g == 255 && $b == 0) + { + //if ($g == 255) { + $trans_colour = imagecolorallocatealpha($src, 0, 0, 0, 127); + imagefill($src, $x, $y, $trans_colour); + } + } + } + } + + return $src; + } + + ## -------------------------------------------------------- + + function checkStringStartsWith($needle, $haystack) + # Check if a string starts with a specific pattern + { + return (substr($haystack, 0, strlen($needle)) == $needle); + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + BMP SUPPORT (SAVING) - James Heinrich +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + private function GD2BMPstring(&$gd_image) + # Author: James Heinrich + # Purpose: Save file as type bmp + # Param in: The image canvas (passed as ref) + # Param out: + # Reference: + # Notes: This code was stripped out of two external files + # (phpthumb.bmp.php,phpthumb.functions.php) and added below to + # avoid dependancies. + # + { + $imageX = ImageSX($gd_image); + $imageY = ImageSY($gd_image); + + $BMP = ''; + for ($y = ($imageY - 1); $y >= 0; $y--) + { + $thisline = ''; + for ($x = 0; $x < $imageX; $x++) + { + $argb = $this->GetPixelColor($gd_image, $x, $y); + $thisline .= chr($argb['blue']) . chr($argb['green']) . chr($argb['red']); + } + while (strlen($thisline) % 4) + { + $thisline .= "\x00"; + } + $BMP .= $thisline; + } + + $bmpSize = strlen($BMP) + 14 + 40; + // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp + $BITMAPFILEHEADER = 'BM'; // WORD bfType; + $BITMAPFILEHEADER .= $this->LittleEndian2String($bmpSize, 4); // DWORD bfSize; + $BITMAPFILEHEADER .= $this->LittleEndian2String(0, 2); // WORD bfReserved1; + $BITMAPFILEHEADER .= $this->LittleEndian2String(0, 2); // WORD bfReserved2; + $BITMAPFILEHEADER .= $this->LittleEndian2String(54, 4); // DWORD bfOffBits; + + // BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp + $BITMAPINFOHEADER = $this->LittleEndian2String(40, 4); // DWORD biSize; + $BITMAPINFOHEADER .= $this->LittleEndian2String($imageX, 4); // LONG biWidth; + $BITMAPINFOHEADER .= $this->LittleEndian2String($imageY, 4); // LONG biHeight; + $BITMAPINFOHEADER .= $this->LittleEndian2String(1, 2); // WORD biPlanes; + $BITMAPINFOHEADER .= $this->LittleEndian2String(24, 2); // WORD biBitCount; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biCompression; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biSizeImage; + $BITMAPINFOHEADER .= $this->LittleEndian2String(2835, 4); // LONG biXPelsPerMeter; + $BITMAPINFOHEADER .= $this->LittleEndian2String(2835, 4); // LONG biYPelsPerMeter; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biClrUsed; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biClrImportant; + + return $BITMAPFILEHEADER . $BITMAPINFOHEADER . $BMP; + } + +## -------------------------------------------------------- + + private function GetPixelColor(&$img, $x, $y) + # Author: James Heinrich + # Purpose: + # Param in: + # Param out: + # Reference: + # Notes: + # + { + if ( ! is_resource($img)) + { + return false; + } + + return @ImageColorsForIndex($img, @ImageColorAt($img, $x, $y)); + } + +## -------------------------------------------------------- + + private function LittleEndian2String($number, $minbytes = 1) + # Author: James Heinrich + # Purpose: BMP SUPPORT (SAVING) + # Param in: + # Param out: + # Reference: + # Notes: + # + { + $intstring = ''; + while ($number > 0) + { + $intstring = $intstring . chr($number & 255); + $number >>= 8; + } + + return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + BMP SUPPORT (READING) +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + private function ImageCreateFromBMP($filename) + # Author: DHKold + # Date: The 15th of June 2005 + # Version: 2.0B + # Purpose: To create an image from a BMP file. + # Param in: BMP file to open. + # Param out: Return a resource like the other ImageCreateFrom functions + # Reference: http://us3.php.net/manual/en/function.imagecreate.php#53879 + # Bug fix: Author: domelca at terra dot es + # Date: 06 March 2008 + # Fix: Correct 16bit BMP support + # Notes: + # + { + + //Ouverture du fichier en mode binaire + if ( ! $f1 = fopen($filename, "rb")) + { + return false; + } + + //1 : Chargement des ent�tes FICHIER + $FILE = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1, 14)); + if ($FILE['file_type'] != 19778) + { + return false; + } + + //2 : Chargement des ent�tes BMP + $BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel' . + '/Vcompression/Vsize_bitmap/Vhoriz_resolution' . + '/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1, 40)); + $BMP['colors'] = pow(2, $BMP['bits_per_pixel']); + + if ($BMP['size_bitmap'] == 0) + { + $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset']; + } + + $BMP['bytes_per_pixel'] = $BMP['bits_per_pixel'] / 8; + $BMP['bytes_per_pixel2'] = ceil($BMP['bytes_per_pixel']); + $BMP['decal'] = ($BMP['width'] * $BMP['bytes_per_pixel'] / 4); + $BMP['decal'] -= floor($BMP['width'] * $BMP['bytes_per_pixel'] / 4); + $BMP['decal'] = 4 - (4 * $BMP['decal']); + + if ($BMP['decal'] == 4) + { + $BMP['decal'] = 0; + } + + //3 : Chargement des couleurs de la palette + $PALETTE = array(); + if ($BMP['colors'] < 16777216) + { + $PALETTE = unpack('V' . $BMP['colors'], fread($f1, $BMP['colors'] * 4)); + } + + //4 : Cr�ation de l'image + $IMG = fread($f1, $BMP['size_bitmap']); + $VIDE = chr(0); + + $res = imagecreatetruecolor($BMP['width'], $BMP['height']); + $P = 0; + $Y = $BMP['height'] - 1; + while ($Y >= 0) + { + $X = 0; + while ($X < $BMP['width']) + { + if ($BMP['bits_per_pixel'] == 24) + { + $COLOR = unpack("V", substr($IMG, $P, 3) . $VIDE); + } + elseif ($BMP['bits_per_pixel'] == 16) + { + + /* + * BMP 16bit fix + * ================= + * + * Ref: http://us3.php.net/manual/en/function.imagecreate.php#81604 + * + * Notes: + * "don't work with bmp 16 bits_per_pixel. change pixel + * generator for this." + * + */ + + // *** Original code (don't work) + //$COLOR = unpack("n",substr($IMG,$P,2)); + //$COLOR[1] = $PALETTE[$COLOR[1]+1]; + + $COLOR = unpack("v", substr($IMG, $P, 2)); + $blue = ($COLOR[1] & 0x001f) << 3; + $green = ($COLOR[1] & 0x07e0) >> 3; + $red = ($COLOR[1] & 0xf800) >> 8; + $COLOR[1] = $red * 65536 + $green * 256 + $blue; + + } + elseif ($BMP['bits_per_pixel'] == 8) + { + $COLOR = unpack("n", $VIDE . substr($IMG, $P, 1)); + $COLOR[1] = $PALETTE[ $COLOR[1] + 1 ]; + } + elseif ($BMP['bits_per_pixel'] == 4) + { + $COLOR = unpack("n", $VIDE . substr($IMG, floor($P), 1)); + if (($P * 2) % 2 == 0) + { + $COLOR[1] = ($COLOR[1] >> 4); + } + else + { + $COLOR[1] = ($COLOR[1] & 0x0F); + } + $COLOR[1] = $PALETTE[ $COLOR[1] + 1 ]; + } + elseif ($BMP['bits_per_pixel'] == 1) + { + $COLOR = unpack("n", $VIDE . substr($IMG, floor($P), 1)); + if (($P * 8) % 8 == 0) + { + $COLOR[1] = $COLOR[1] >> 7; + } + elseif (($P * 8) % 8 == 1) + { + $COLOR[1] = ($COLOR[1] & 0x40) >> 6; + } + elseif (($P * 8) % 8 == 2) + { + $COLOR[1] = ($COLOR[1] & 0x20) >> 5; + } + elseif (($P * 8) % 8 == 3) + { + $COLOR[1] = ($COLOR[1] & 0x10) >> 4; + } + elseif (($P * 8) % 8 == 4) + { + $COLOR[1] = ($COLOR[1] & 0x8) >> 3; + } + elseif (($P * 8) % 8 == 5) + { + $COLOR[1] = ($COLOR[1] & 0x4) >> 2; + } + elseif (($P * 8) % 8 == 6) + { + $COLOR[1] = ($COLOR[1] & 0x2) >> 1; + } + elseif (($P * 8) % 8 == 7) + { + $COLOR[1] = ($COLOR[1] & 0x1); + } + $COLOR[1] = $PALETTE[ $COLOR[1] + 1 ]; + } + else + { + return false; + } + + imagesetpixel($res, $X, $Y, $COLOR[1]); + $X++; + $P += $BMP['bytes_per_pixel']; + } + + $Y--; + $P += $BMP['decal']; + } + //Fermeture du fichier + fclose($f1); + + return $res; + } + + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + PSD SUPPORT (READING) +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + private function imagecreatefrompsd($fileName) + # Author: Tim de Koning + # Version: 1.3 + # Purpose: To create an image from a PSD file. + # Param in: PSD file to open. + # Param out: Return a resource like the other ImageCreateFrom functions + # Reference: http://www.kingsquare.nl/phppsdreader + # Notes: + # + { + if (file_exists($this->psdReaderPath)) + { + + + include_once($this->psdReaderPath); + + $psdReader = new PhpPsdReader($fileName); + + if (isset($psdReader->infoArray['error'])) + { + return ''; + } + else + { + return $psdReader->getImage(); + } + } + else + { + return false; + } + } + +## -------------------------------------------------------- + + public function __destruct() + { + if (is_resource($this->imageResized)) + { + imagedestroy($this->imageResized); + } + } + +## -------------------------------------------------------- + +} + + +/* + * Example with some API calls (outdated): + * + * + * =============================== + * Compulsary + * =============================== + * + * include("classes/resize_class.php"); + * + * // *** Initialise object + * $magicianObj = new resize('images/cars/large/a.jpg'); + * + * // *** Turn off stretching (optional) + * $magicianObj -> setForceStretch(false); + * + * // *** Resize object + * $magicianObj -> resizeImage(150, 100, 0); + * + * =============================== + * Image options - can run none, one, or all. + * =============================== + * + * // *** Add watermark + * $magicianObj -> addWatermark('stamp.png'); + * + * // *** Add text + * $magicianObj -> addText('testing...'); + * + * =============================== + * Output options - can run one, or the other, or both. + * =============================== + * + * // *** Save image to disk + * $magicianObj -> saveImage('images/cars/large/b.jpg', 100); + * + * // *** Or output to screen (params in can be jpg, gif, png) + * $magicianObj -> displayImage('png'); + * + * =============================== + * Return options - return errors. nice for debuggin. + * =============================== + * + * // *** Return error array + * $errorArray = $magicianObj -> getErrors(); + * + * + * =============================== + * Cleanup options - not really neccessary, but good practice + * =============================== + * + * // *** Free used memory + * $magicianObj -> __destruct(); + */ diff --git a/core/vendor/filemanager/include/utils.php b/core/vendor/filemanager/include/utils.php new file mode 100644 index 0000000..8cda960 --- /dev/null +++ b/core/vendor/filemanager/include/utils.php @@ -0,0 +1,1345 @@ +send(); + exit; + } + + } + if(file_exists('lang/' . $lang . '.php')){ + $lang_vars = include 'lang/' . $lang . '.php'; + }else{ + $lang_vars = include '../lang/' . $lang . '.php'; + } + + if ( ! is_array($lang_vars)) + { + $lang_vars = array(); + } +} + + +function checkRelativePathPartial($path){ + if (strpos($path, '../') !== false + || strpos($path, './') !== false + || strpos($path, '/..') !== false + || strpos($path, '..\\') !== false + || strpos($path, '\\..') !== false + || strpos($path, '.\\') !== false + || $path === ".." + ){ + return false; + } + return true; +} + +/** +* Check relative path +* +* @param string $path +* +* @return boolean is it correct? +*/ +function checkRelativePath($path){ + $path_correct = checkRelativePathPartial($path); + if($path_correct){ + $path_decoded = rawurldecode($path); + $path_correct = checkRelativePathPartial($path_decoded); + } + return $path_correct; +} + +/** +* Check if the given path is an upload dir based on config +* +* @param string $path +* @param array $config +* +* @return boolean is it an upload dir? +*/ +function isUploadDir($path, $config){ + $upload_dir = $config['current_path']; + $thumbs_dir = $config['thumbs_base_path']; + if (realpath($path) === realpath($upload_dir) || realpath($path) === realpath($thumbs_dir)) + { + return true; + } + return false; +} + +/** +* Delete file +* +* @param string $path +* @param string $path_thumb +* @param array $config +* +* @return null +*/ +function deleteFile($path,$path_thumb,$config){ + if ($config['delete_files']){ + $ftp = ftp_con($config); + if($ftp){ + try{ + $ftp->delete("/".$path); + @$ftp->delete("/".$path_thumb); + }catch(FtpClient\FtpException $e){ + return; + } + }else{ + if (file_exists($path)){ + unlink($path); + } + if (file_exists($path_thumb)){ + unlink($path_thumb); + } + } + + $info=pathinfo($path); + if (!$ftp && $config['relative_image_creation']){ + foreach($config['relative_path_from_current_pos'] as $k=>$path) + { + if ($path!="" && $path[strlen($path)-1]!="/") $path.="/"; + + if (file_exists($info['dirname']."/".$path.$config['relative_image_creation_name_to_prepend'][$k].$info['filename'].$config['relative_image_creation_name_to_append'][$k].".".$info['extension'])) + { + unlink($info['dirname']."/".$path.$config['relative_image_creation_name_to_prepend'][$k].$info['filename'].$config['relative_image_creation_name_to_append'][$k].".".$info['extension']); + } + } + } + + if (!$ftp && $config['fixed_image_creation']) + { + foreach($config['fixed_path_from_filemanager'] as $k=>$path) + { + if ($path!="" && $path[strlen($path)-1] != "/") $path.="/"; + + $base_dir=$path.substr_replace($info['dirname']."/", '', 0, strlen($config['current_path'])); + if (file_exists($base_dir.$config['fixed_image_creation_name_to_prepend'][$k].$info['filename'].$config['fixed_image_creation_to_append'][$k].".".$info['extension'])) + { + unlink($base_dir.$config['fixed_image_creation_name_to_prepend'][$k].$info['filename'].$config['fixed_image_creation_to_append'][$k].".".$info['extension']); + } + } + } + } +} + +/** +* Delete directory +* +* @param string $dir +* +* @return bool +*/ +function deleteDir($dir,$ftp = null, $config = null) +{ + if($ftp){ + + try{ + $ftp->rmdir($dir); + return true; + + }catch(FtpClient\FtpException $e){ + return null; + } + + }else{ + if ( ! file_exists($dir) || isUploadDir($dir, $config)) + { + return false; + } + if ( ! is_dir($dir)) + { + return unlink($dir); + } + foreach (scandir($dir) as $item) + { + if ($item == '.' || $item == '..') + { + continue; + } + if ( ! deleteDir($dir . DIRECTORY_SEPARATOR . $item)) + { + return false; + } + } + } + + return rmdir($dir); +} + +/** +* Make a file copy +* +* @param string $old_path +* @param string $name New file name without extension +* +* @return bool +*/ +function duplicate_file( $old_path, $name, $ftp = null, $config = null ) +{ + $info = pathinfo($old_path); + $new_path = $info['dirname'] . "/" . $name . "." . $info['extension']; + if($ftp){ + try{ + $tmp = time().$name . "." . $info['extension']; + $ftp->get($tmp, "/".$old_path, FTP_BINARY); + $ftp->put("/".$new_path, $tmp, FTP_BINARY); + unlink($tmp); + return true; + + }catch(FtpClient\FtpException $e){ + return null; + } + }else{ + if (file_exists($old_path) && is_file($old_path)) + { + if (file_exists($new_path) && $old_path == $new_path) + { + return false; + } + + return copy($old_path, $new_path); + } + } +} + + +/** +* Rename file +* +* @param string $old_path File to rename +* @param string $name New file name without extension +* @param bool $transliteration +* +* @return bool +*/ +function rename_file($old_path, $name, $ftp = null, $config = null) +{ + $name = fix_filename($name, $config); + $info = pathinfo($old_path); + $new_path = $info['dirname'] . "/" . $name . "." . $info['extension']; + if($ftp){ + try{ + return $ftp->rename("/".$old_path, "/".$new_path); + }catch(FtpClient\FtpException $e){ + return false; + } + }else{ + if (file_exists($old_path) && is_file($old_path)) + { + $new_path = $info['dirname'] . "/" . $name . "." . $info['extension']; + if (file_exists($new_path) && $old_path == $new_path) + { + return false; + } + + return rename($old_path, $new_path); + } + } +} + + +function url_exists($url){ + if (!$fp = curl_init($url)) return false; + return true; +} + + +function tempdir() { + $tempfile=tempnam(sys_get_temp_dir(),''); + if (file_exists($tempfile)) { unlink($tempfile); } + mkdir($tempfile); + if (is_dir($tempfile)) { return $tempfile; } +} + + +/** +* Rename directory +* +* @param string $old_path Directory to rename +* @param string $name New directory name +* @param bool $transliteration +* +* @return bool +*/ +function rename_folder($old_path, $name, $ftp = null, $config = null) +{ + $name = fix_filename($name, $config, true); + $new_path = fix_dirname($old_path) . "/" . $name; + if($ftp){ + if($ftp->chdir("/".$old_path)){ + if(@$ftp->chdir($new_path)){ + return false; + } + return $ftp->rename("/".$old_path, "/".$new_path); + } + }else{ + if (file_exists($old_path) && is_dir($old_path) && !isUploadDir($old_path, $config)) + { + if (file_exists($new_path) && $old_path == $new_path) + { + return false; + } + return rename($old_path, $new_path); + } + } +} + +function ftp_con($config){ + if(isset($config['ftp_host']) && $config['ftp_host']){ + // *** Include the class + include('include/FtpClient.php'); + include('include/FtpException.php'); + include('include/FtpWrapper.php'); + + $ftp = new \FtpClient\FtpClient(); + try{ + $ftp->connect($config['ftp_host'],$config['ftp_ssl'],$config['ftp_port']); + $ftp->login($config['ftp_user'], $config['ftp_pass']); + $ftp->pasv(true); + return $ftp; + }catch(FtpClient\FtpException $e){ + echo "Error: "; + echo $e->getMessage(); + echo " to server "; + $tmp = $e->getTrace(); + echo $tmp[0]['args'][0]; + echo "
Please check configurations"; + die(); + } + }else{ + return false; + } +} + +/** +* Create new image from existing file +* +* @param string $imgfile Source image file name +* @param string $imgthumb Thumbnail file name +* @param int $newwidth Thumbnail width +* @param int $newheight Optional thumbnail height +* @param string $option Type of resize +* +* @return bool +* @throws \Exception +*/ +function create_img($imgfile, $imgthumb, $newwidth, $newheight = null, $option = "crop",$config = array()) +{ + $result = false; + if(isset($config['ftp_host']) && $config['ftp_host']){ + if(url_exists($imgfile)){ + $temp = tempnam('/tmp','RF'); + unlink($temp); + $temp .=".".substr(strrchr($imgfile,'.'),1); + $handle = fopen($temp, "w"); + fwrite($handle, file_get_contents($imgfile)); + fclose($handle); + $imgfile= $temp; + $save_ftp = $imgthumb; + $imgthumb = $temp; + } + } + if(file_exists($imgfile) || strpos($imgfile,'http')===0){ + if (strpos($imgfile,'http')===0 || image_check_memory_usage($imgfile, $newwidth, $newheight)) + { + require_once('php_image_magician.php'); + try{ + $magicianObj = new imageLib($imgfile); + $magicianObj->resizeImage($newwidth, $newheight, $option); + $magicianObj->saveImage($imgthumb, 80); + }catch (Exception $e){ + return $e->getMessage(); + } + $result = true; + } + } + if($result && isset($config['ftp_host']) && $config['ftp_host'] ){ + $ftp->put($save_ftp, $imgthumb, FTP_BINARY); + unlink($imgthumb); + } + + return $result; +} + +/** +* Convert convert size in bytes to human readable +* +* @param int $size +* +* @return string +*/ +function makeSize($size) +{ + $units = array( 'B', 'KB', 'MB', 'GB', 'TB' ); + $u = 0; + while ((round($size / 1024) > 0) && ($u < 4)) + { + $size = $size / 1024; + $u++; + } + + return (number_format($size, 0) . " " . trans($units[ $u ])); +} + +/** +* Determine directory size +* +* @param string $path +* +* @return int +*/ +function folder_info($path,$count_hidden=true) +{ + global $config; + $total_size = 0; + $files = scandir($path); + $cleanPath = rtrim($path, '/') . '/'; + $files_count = 0; + $folders_count = 0; + foreach ($files as $t) + { + if ($t != "." && $t != "..") + { + if ($count_hidden or !(in_array($t,$config['hidden_folders']) or in_array($t,$config['hidden_files']))) + { + $currentFile = $cleanPath . $t; + if (is_dir($currentFile)) + { + list($size,$tmp,$tmp1) = folder_info($currentFile); + $total_size += $size; + $folders_count ++; + } + else + { + $size = filesize($currentFile); + $total_size += $size; + $files_count++; + } + } + } + } + + return array($total_size,$files_count,$folders_count); +} +/** +* Get number of files in a directory +* +* @param string $path +* +* @return int +*/ +function filescount($path,$count_hidden=true) +{ + global $config; + $total_count = 0; + $files = scandir($path); + $cleanPath = rtrim($path, '/') . '/'; + + foreach ($files as $t) + { + if ($t != "." && $t != "..") + { + if ($count_hidden or !(in_array($t,$config['hidden_folders']) or in_array($t,$config['hidden_files']))) + { + $currentFile = $cleanPath . $t; + if (is_dir($currentFile)) + { + $size = filescount($currentFile); + $total_count += $size; + } + else + { + $total_count += 1; + } + } + } + } + + return $total_count; +} +/** +* check if the current folder size plus the added size is over the overall size limite +* +* @param int $sizeAdded +* +* @return bool +*/ +function checkresultingsize($sizeAdded) +{ + global $config; + + if ($config['MaxSizeTotal'] !== false && is_int($config['MaxSizeTotal'])) { + list($sizeCurrentFolder,$fileCurrentNum,$foldersCurrentCount) = folder_info($config['current_path'],false); + // overall size over limit + if (($config['MaxSizeTotal'] * 1024 * 1024) < ($sizeCurrentFolder + $sizeAdded)) { + return false; + } + } + return true; +} + +/** +* Create directory for images and/or thumbnails +* +* @param string $path +* @param string $path_thumbs +*/ +function create_folder($path = null, $path_thumbs = null,$ftp = null,$config = null) +{ + if($ftp){ + $ftp->mkdir($path); + $ftp->mkdir($path_thumbs); + }else{ + if(file_exists($path) || file_exists($path_thumbs)){ + return false; + } + $oldumask = umask(0); + $permission = 0755; + if(isset($config['folderPermission'])){ + $permission = $config['folderPermission']; + } + if ($path && !file_exists($path)) + { + mkdir($path, $permission, true); + } // or even 01777 so you get the sticky bit set + if ($path_thumbs) + { + mkdir($path_thumbs, $permission, true) or die("$path_thumbs cannot be found"); + } // or even 01777 so you get the sticky bit set + umask($oldumask); + return true; + } +} + +/** +* Get file extension present in directory +* +* @param string $path +* @param string $ext +*/ +function check_files_extensions_on_path($path, $ext) +{ + if ( ! is_dir($path)) + { + $fileinfo = pathinfo($path); + if ( ! in_array(mb_strtolower($fileinfo['extension']), $ext)) + { + unlink($path); + } + } + else + { + $files = scandir($path); + foreach ($files as $file) + { + check_files_extensions_on_path(trim($path, '/') . "/" . $file, $ext); + } + } +} + + +/** +* Check file extension +* +* @param string $extension +* @param array $config +*/ + +function check_file_extension($extension,$config){ + $check = false; + if (!$config['ext_blacklist']) { + if(in_array(mb_strtolower($extension), $conf['ext'])){ + $check = true; + } + } else { + if(!in_array(mb_strtolower($extension), $conf['ext_blacklist'])){ + $check = true; + } + } + + if($config['files_without_extension'] && $extension == ''){ + $check = true; + } + + return $check; +} + + +/** +* Get file extension present in PHAR file +* +* @param string $phar +* @param array $files +* @param string $basepath +* @param string $ext +*/ +function check_files_extensions_on_phar($phar, &$files, $basepath, $config) +{ + foreach ($phar as $file) + { + if ($file->isFile()) + { + if (check_file_extension($file->getExtension())) + { + $files[] = $basepath . $file->getFileName(); + } + } + else + { + if ($file->isDir()) + { + $iterator = new DirectoryIterator($file); + check_files_extensions_on_phar($iterator, $files, $basepath . $file->getFileName() . '/', $config); + } + } + } +} + +/** +* Cleanup input +* +* @param string $str +* +* @return string +*/ +function fix_get_params($str) +{ + return strip_tags(preg_replace("/[^a-zA-Z0-9\.\[\]_| -]/", '', $str)); +} + + +/** +* Check extension +* +* @param string $extension +* @param array $config +* +* @return bool +*/ +function check_extension($extension,$config){ + $extension = fix_strtolower($extension); + if((!$config['ext_blacklist'] && !in_array($extension, $config['ext'])) || ($config['ext_blacklist'] && in_array($extension, $config['ext_blacklist']))){ + return false; + } + return true; +} + + + + +/** +* Sanitize filename +* +* @param string $str +* +* @return string +*/ +function sanitize($str) +{ + return strip_tags(htmlspecialchars($str)); +} + +/** +* Cleanup filename +* +* @param string $str +* @param bool $transliteration +* @param bool $convert_spaces +* @param string $replace_with +* @param bool $is_folder +* +* @return string +*/ +function fix_filename($str, $config, $is_folder = false) +{ + $str = sanitize($str); + if ($config['convert_spaces']) + { + $str = str_replace(' ', $config['replace_with'], $str); + } + + if ($config['transliteration']) + { + if (!mb_detect_encoding($str, 'UTF-8', true)) + { + $str = utf8_encode($str); + } + if (function_exists('transliterator_transliterate')) + { + $str = transliterator_transliterate('Any-Latin; Latin-ASCII', $str); + } + else + { + $str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str); + } + + $str = preg_replace("/[^a-zA-Z0-9\.\[\]_| -]/", '', $str); + } + + $str = str_replace(array( '"', "'", "/", "\\" ), "", $str); + $str = strip_tags($str); + + // Empty or incorrectly transliterated filename. + // Here is a point: a good file UNKNOWN_LANGUAGE.jpg could become .jpg in previous code. + // So we add that default 'file' name to fix that issue. + if (!$config['empty_filename'] && strpos($str, '.') === 0 && $is_folder === false) + { + $str = 'file' . $str; + } + + return trim($str); +} + +/** +* Cleanup directory name +* +* @param string $str +* +* @return string +*/ +function fix_dirname($str) +{ + return str_replace('~', ' ', dirname(str_replace(' ', '~', $str))); +} + +/** +* Correct strtoupper handling +* +* @param string $str +* +* @return string +*/ +function fix_strtoupper($str) +{ + if (function_exists('mb_strtoupper')) + { + return mb_strtoupper($str); + } + else + { + return strtoupper($str); + } +} + +/** +* Correct strtolower handling +* +* @param string $str +* +* @return string +*/ +function fix_strtolower($str) +{ + if (function_exists('mb_strtoupper')) + { + return mb_strtolower($str); + } + else + { + return strtolower($str); + } +} + +function fix_path($path, $config) +{ + $info = pathinfo($path); + $tmp_path = $info['dirname']; + $str = fix_filename($info['filename'], $config); + if ($tmp_path != "") + { + return $tmp_path . DIRECTORY_SEPARATOR . $str; + } + else + { + return $str; + } +} + +/** +* @param $current_path +* @param $fld +* +* @return bool +*/ +function config_loading($current_path, $fld) +{ + if (file_exists($current_path . $fld . ".config")) + { + require_once($current_path . $fld . ".config"); + + return true; + } + echo "!!!!" . $parent = fix_dirname($fld); + if ($parent != "." && ! empty($parent)) + { + config_loading($current_path, $parent); + } + + return false; +} + +/** +* Check if memory is enough to process image +* +* @param string $img +* @param int $max_breedte +* @param int $max_hoogte +* +* @return bool +*/ +function image_check_memory_usage($img, $max_breedte, $max_hoogte) +{ + if (file_exists($img)) + { + $K64 = 65536; // number of bytes in 64K + $memory_usage = memory_get_usage(); + if(ini_get('memory_limit') > 0 ){ + + $mem = ini_get('memory_limit'); + $memory_limit = 0; + if (strpos($mem, 'M') !== false) $memory_limit = abs(intval(str_replace(array('M'), '', $mem) * 1024 * 1024)); + if (strpos($mem, 'G') !== false) $memory_limit = abs(intval(str_replace(array('G'), '', $mem) * 1024 * 1024 * 1024)); + + $image_properties = getimagesize($img); + $image_width = $image_properties[0]; + $image_height = $image_properties[1]; + if (isset($image_properties['bits'])) + $image_bits = $image_properties['bits']; + else + $image_bits = 0; + $image_memory_usage = $K64 + ($image_width * $image_height * ($image_bits >> 3) * 2); + $thumb_memory_usage = $K64 + ($max_breedte * $max_hoogte * ($image_bits >> 3) * 2); + $memory_needed = abs(intval($memory_usage + $image_memory_usage + $thumb_memory_usage)); + + if ($memory_needed > $memory_limit) + { + return false; + } + } + return true; + } + return false; +} + +/** +* Check is string is ended with needle +* +* @param string $haystack +* @param string $needle +* +* @return bool +*/ +if(!function_exists('ends_with')){ + function ends_with($haystack, $needle) + { + return $needle === "" || substr($haystack, -strlen($needle)) === $needle; + } +} + +/** +* TODO REFACTOR THIS! +* +* @param $targetPath +* @param $targetFile +* @param $name +* @param $current_path +* @param $config +* relative_image_creation +* relative_path_from_current_pos +* relative_image_creation_name_to_prepend +* relative_image_creation_name_to_append +* relative_image_creation_width +* relative_image_creation_height +* relative_image_creation_option +* fixed_image_creation +* fixed_path_from_filemanager +* fixed_image_creation_name_to_prepend +* fixed_image_creation_to_append +* fixed_image_creation_width +* fixed_image_creation_height +* fixed_image_creation_option +* +* @return bool +*/ +function new_thumbnails_creation($targetPath, $targetFile, $name, $current_path, $config) +{ + //create relative thumbs + $all_ok = true; + + $info = pathinfo($name); + $info['filename'] = fix_filename($info['filename'],$config); + if ($config['relative_image_creation']) + { + foreach ($config['relative_path_from_current_pos'] as $k => $path) + { + if ($path != "" && $path[ strlen($path) - 1 ] != "/") + { + $path .= "/"; + } + if ( ! file_exists($targetPath . $path)) + { + create_folder($targetPath . $path, false); + } + if ( ! ends_with($targetPath, $path)) + { + if ( ! create_img($targetFile, $targetPath . $path . $config['relative_image_creation_name_to_prepend'][ $k ] . $info['filename'] . $config['relative_image_creation_name_to_append'][ $k ] . "." . $info['extension'], $config['relative_image_creation_width'][ $k ], $config['relative_image_creation_height'][ $k ], $config['relative_image_creation_option'][ $k ])) + { + $all_ok = false; + } + } + } + } + + //create fixed thumbs + if ($config['fixed_image_creation']) + { + foreach ($config['fixed_path_from_filemanager'] as $k => $path) + { + if ($path != "" && $path[ strlen($path) - 1 ] != "/") + { + $path .= "/"; + } + $base_dir = $path . substr_replace($targetPath, '', 0, strlen($current_path)); + if ( ! file_exists($base_dir)) + { + create_folder($base_dir, false); + } + if ( ! create_img($targetFile, $base_dir . $config['fixed_image_creation_name_to_prepend'][ $k ] . $info['filename'] . $config['fixed_image_creation_to_append'][ $k ] . "." . $info['extension'], $config['fixed_image_creation_width'][ $k ], $config['fixed_image_creation_height'][ $k ], $config['fixed_image_creation_option'][ $k ])) + { + $all_ok = false; + } + } + } + + return $all_ok; +} + + +/** +* Get a remote file, using whichever mechanism is enabled +* +* @param string $url +* +* @return bool|mixed|string +*/ +function get_file_by_url($url) +{ + if (ini_get('allow_url_fopen')) + { + $arrContextOptions=array( + "ssl"=>array( + "verify_peer"=>false, + "verify_peer_name"=>false, + ), + ); + return file_get_contents($url, false, stream_context_create($arrContextOptions)); + } + if ( ! function_exists('curl_version')) + { + return false; + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_URL, $url); + + $data = curl_exec($ch); + curl_close($ch); + + return $data; +} + +/** +* test for dir/file writability properly +* +* @param string $dir +* +* @return bool +*/ +function is_really_writable($dir) +{ + $dir = rtrim($dir, '/'); + // linux, safe off + if (DIRECTORY_SEPARATOR == '/' && @ini_get("safe_mode") == false) + { + return is_writable($dir); + } + + // Windows, safe ON. (have to write a file :S) + if (is_dir($dir)) + { + $dir = $dir . '/' . md5(mt_rand(1, 1000) . mt_rand(1, 1000)); + + if (($fp = @fopen($dir, 'ab')) === false) + { + return false; + } + + fclose($fp); + @chmod($dir, 0755); + @unlink($dir); + + return true; + } + elseif ( ! is_file($dir) || ($fp = @fopen($dir, 'ab')) === false) + { + return false; + } + + fclose($fp); + + return true; +} + +/** +* Check if a function is callable. +* Some servers disable copy,rename etc. +* +* @parm string $name +* +* @return bool +*/ +function is_function_callable($name) +{ + if (function_exists($name) === false) + { + return false; + } + $disabled = explode(',', ini_get('disable_functions')); + + return ! in_array($name, $disabled); +} + +/** +* recursivly copies everything +* +* @param string $source +* @param string $destination +* @param bool $is_rec +*/ +function rcopy($source, $destination, $is_rec = false) +{ + if (is_dir($source)) + { + if ($is_rec === false) + { + $pinfo = pathinfo($source); + $destination = rtrim($destination, '/') . DIRECTORY_SEPARATOR . $pinfo['basename']; + } + if (is_dir($destination) === false) + { + mkdir($destination, 0755, true); + } + + $files = scandir($source); + foreach ($files as $file) + { + if ($file != "." && $file != "..") + { + rcopy($source . DIRECTORY_SEPARATOR . $file, rtrim($destination, '/') . DIRECTORY_SEPARATOR . $file, true); + } + } + } + else + { + if (file_exists($source)) + { + if (is_dir($destination) === true) + { + $pinfo = pathinfo($source); + $dest2 = rtrim($destination, '/') . DIRECTORY_SEPARATOR . $pinfo['basename']; + } + else + { + $dest2 = $destination; + } + + copy($source, $dest2); + } + } +} + + + + +/** +* recursivly renames everything +* +* I know copy and rename could be done with just one function +* but i split the 2 because sometimes rename fails on windows +* Need more feedback from users and refactor if needed +* +* @param string $source +* @param string $destination +* @param bool $is_rec +*/ +function rrename($source, $destination, $is_rec = false) +{ + if (is_dir($source)) + { + if ($is_rec === false) + { + $pinfo = pathinfo($source); + $destination = rtrim($destination, '/') . DIRECTORY_SEPARATOR . $pinfo['basename']; + } + if (is_dir($destination) === false) + { + mkdir($destination, 0755, true); + } + + $files = scandir($source); + foreach ($files as $file) + { + if ($file != "." && $file != "..") + { + rrename($source . DIRECTORY_SEPARATOR . $file, rtrim($destination, '/') . DIRECTORY_SEPARATOR . $file, true); + } + } + } + else + { + if (file_exists($source)) + { + if (is_dir($destination) === true) + { + $pinfo = pathinfo($source); + $dest2 = rtrim($destination, '/') . DIRECTORY_SEPARATOR . $pinfo['basename']; + } + else + { + $dest2 = $destination; + } + + rename($source, $dest2); + } + } +} + +// On windows rename leaves folders sometime +// This will clear leftover folders +// After more feedback will merge it with rrename +function rrename_after_cleaner($source) +{ + $files = scandir($source); + + foreach ($files as $file) + { + if ($file != "." && $file != "..") + { + if (is_dir($source . DIRECTORY_SEPARATOR . $file)) + { + rrename_after_cleaner($source . DIRECTORY_SEPARATOR . $file); + } + else + { + unlink($source . DIRECTORY_SEPARATOR . $file); + } + } + } + + return rmdir($source); +} + +/** +* Recursive chmod +* @param string $source +* @param int $mode +* @param string $rec_option +* @param bool $is_rec +*/ +function rchmod($source, $mode, $rec_option = "none", $is_rec = false) +{ + if ($rec_option == "none") + { + chmod($source, $mode); + } + else + { + if ($is_rec === false) + { + chmod($source, $mode); + } + + $files = scandir($source); + + foreach ($files as $file) + { + if ($file != "." && $file != "..") + { + if (is_dir($source . DIRECTORY_SEPARATOR . $file)) + { + if ($rec_option == "folders" || $rec_option == "both") + { + chmod($source . DIRECTORY_SEPARATOR . $file, $mode); + } + rchmod($source . DIRECTORY_SEPARATOR . $file, $mode, $rec_option, true); + } + else + { + if ($rec_option == "files" || $rec_option == "both") + { + chmod($source . DIRECTORY_SEPARATOR . $file, $mode); + } + } + } + } + } +} + +/** +* @param string $input +* @param bool $trace +* @param bool $halt +*/ +function debugger($input, $trace = false, $halt = false) +{ + ob_start(); + + echo "
----- DEBUG DUMP -----"; + echo "
";
+	var_dump($input);
+	echo "
"; + + if ($trace) + { + if (is_php('5.3.6')) + { + $debug = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + else + { + $debug = debug_backtrace(false); + } + + echo "
-----STACK TRACE-----"; + echo "
";
+		var_dump($debug);
+		echo "
"; + } + + echo ""; + echo "---------------------------
"; + + $ret = ob_get_contents(); + ob_end_clean(); + + echo $ret; + + if ($halt == true) + { + exit(); + } +} + +/** +* @param string $version +* +* @return bool +*/ +function is_php($version = '5.0.0') +{ + static $phpVer; + $version = (string) $version; + + if ( ! isset($phpVer[ $version ])) + { + $phpVer[ $version ] = (version_compare(PHP_VERSION, $version) < 0) ? false : true; + } + + return $phpVer[ $version ]; +} + +/** +* Return the caller location if set in config.php +* @param string $version +* +* @return bool +*/ +function AddErrorLocation() +{ + if (defined('DEBUG_ERROR_MESSAGE') and DEBUG_ERROR_MESSAGE) { + $pile=debug_backtrace(); + return " (@".$pile[0]["file"]."#".$pile[0]["line"].")"; + } + return ""; +} diff --git a/core/vendor/filemanager/index.php b/core/vendor/filemanager/index.php new file mode 100644 index 0000000..c30c28f --- /dev/null +++ b/core/vendor/filemanager/index.php @@ -0,0 +1,10 @@ +').prop('href', options.postMessage)[0], + target = loc.protocol + '//' + loc.host, + xhrUpload = options.xhr().upload; + // IE always includes the port for the host property of a link + // element, but not in the location.host or origin property for the + // default http port 80 and https port 443, so we strip it: + if (/^(http:\/\/.+:80)|(https:\/\/.+:443)$/.test(target)) { + target = target.replace(/:(80|443)$/, ''); + } + return { + send: function (_, completeCallback) { + counter += 1; + var message = { + id: 'postmessage-transport-' + counter + }, + eventName = 'message.' + message.id; + iframe = $( + '' + ).bind('load', function () { + $.each(names, function (i, name) { + message[name] = options[name]; + }); + message.dataType = message.dataType.replace('postmessage ', ''); + $(window).bind(eventName, function (e) { + e = e.originalEvent; + var data = e.data, + ev; + if (e.origin === target && data.id === message.id) { + if (data.type === 'progress') { + ev = document.createEvent('Event'); + ev.initEvent(data.type, false, true); + $.extend(ev, data); + xhrUpload.dispatchEvent(ev); + } else { + completeCallback( + data.status, + data.statusText, + {postmessage: data.result}, + data.headers + ); + iframe.remove(); + $(window).unbind(eventName); + } + } + }); + iframe[0].contentWindow.postMessage( + message, + target + ); + }).appendTo(document.body); + }, + abort: function () { + if (iframe) { + iframe.remove(); + } + } + }; + } + }); + +})); diff --git a/core/vendor/filemanager/js/cors/jquery.xdr-transport.js b/core/vendor/filemanager/js/cors/jquery.xdr-transport.js new file mode 100644 index 0000000..a4e2699 --- /dev/null +++ b/core/vendor/filemanager/js/cors/jquery.xdr-transport.js @@ -0,0 +1,89 @@ +/* + * jQuery XDomainRequest Transport Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + * + * Based on Julian Aubourg's ajaxHooks xdr.js: + * https://github.com/jaubourg/ajaxHooks/ + */ + +/* global define, require, window, XDomainRequest */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + if (window.XDomainRequest && !$.support.cors) { + $.ajaxTransport(function (s) { + if (s.crossDomain && s.async) { + if (s.timeout) { + s.xdrTimeout = s.timeout; + delete s.timeout; + } + var xdr; + return { + send: function (headers, completeCallback) { + var addParamChar = /\?/.test(s.url) ? '&' : '?'; + function callback(status, statusText, responses, responseHeaders) { + xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; + xdr = null; + completeCallback(status, statusText, responses, responseHeaders); + } + xdr = new XDomainRequest(); + // XDomainRequest only supports GET and POST: + if (s.type === 'DELETE') { + s.url = s.url + addParamChar + '_method=DELETE'; + s.type = 'POST'; + } else if (s.type === 'PUT') { + s.url = s.url + addParamChar + '_method=PUT'; + s.type = 'POST'; + } else if (s.type === 'PATCH') { + s.url = s.url + addParamChar + '_method=PATCH'; + s.type = 'POST'; + } + xdr.open(s.type, s.url); + xdr.onload = function () { + callback( + 200, + 'OK', + {text: xdr.responseText}, + 'Content-Type: ' + xdr.contentType + ); + }; + xdr.onerror = function () { + callback(404, 'Not Found'); + }; + if (s.xdrTimeout) { + xdr.ontimeout = function () { + callback(0, 'timeout'); + }; + xdr.timeout = s.xdrTimeout; + } + xdr.send((s.hasContent && s.data) || null); + }, + abort: function () { + if (xdr) { + xdr.onerror = $.noop(); + xdr.abort(); + } + } + }; + } + }); + } +})); diff --git a/core/vendor/filemanager/js/include.js b/core/vendor/filemanager/js/include.js new file mode 100644 index 0000000..d1b8c0a --- /dev/null +++ b/core/vendor/filemanager/js/include.js @@ -0,0 +1 @@ +var encodeURL,show_animation,hide_animation,apply,apply_none,apply_img,apply_any,apply_video,apply_link,apply_file_rename,apply_file_duplicate,apply_folder_rename;!function(y,n,a){"use strict";var t,i=null,c=0,r=(t=0,function(e,a){clearTimeout(t),t=setTimeout(e,a)}),l=function(e){if(1==jQuery("#ftp").val())var a=jQuery("#ftp_base_url").val()+jQuery("#upload_dir").val()+jQuery("#fldr_value").val();else a=jQuery("#base_url").val()+jQuery("#cur_dir").val();var t=e.find("a.link").attr("data-file");return""!=t&&null!=t&&(a+=t),""!=(t=e.find("h4 a.folder-link").attr("data-file"))&&null!=t&&(a+=t),a},s={contextActions:{copy_url:function(e){var a=l(e);bootbox.alert('URL:
")},unzip:function(e){var a=jQuery("#sub_folder").val()+jQuery("#fldr_value").val()+e.find("a.link").attr("data-file");show_animation(),y.ajax({type:"POST",url:"ajax_calls.php?action=extract",data:{path:a}}).done(function(e){hide_animation(),""!=e?bootbox.alert(e):window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()})},edit_img:function(e){var a=e.attr("data-name"),t=e.attr("data-path");if(1==jQuery("#ftp").val())var r=jQuery("#ftp_base_url").val()+jQuery("#upload_dir").val()+jQuery("#fldr_value").val()+a;else r=jQuery("#base_url").val()+jQuery("#upload_dir").val()+t;var i,n=jQuery("#tui-image-editor");n.attr("data-name",a),n.attr("data-path",r),show_animation(),n.attr("id"),i=r,imageEditor.loadImageFromURL(i,"SampleImage").then(function(e){imageEditor.ui.resizeEditor({imageSize:{oldWidth:e.oldWidth,oldHeight:e.oldHeight,newWidth:e.newWidth,newHeight:e.newHeight}})}).catch(function(e){bootbox.alert("Something went wrong: "+e)}),hide_animation(),n.removeClass("hide")},duplicate:function(a){var t=a.find("h4").text().trim();bootbox.prompt(jQuery("#lang_duplicate").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(e){null!==e&&(e=Q(e))!=t&&g("duplicate_file",a.attr("data-path"),e,a,"apply_file_duplicate")},t+" - copy")},select:function(e){var a,t=l(e),r=jQuery("#field_id").val();(1==jQuery("#return_relative_url").val()&&(t=(t=t.replace(jQuery("#base_url").val(),"")).replace(jQuery("#cur_dir").val(),"")),a=1==jQuery("#popup").val()?window.opener:window.parent,""!=r)?1==jQuery("#crossdomain").val()?a.postMessage({sender:"responsivefilemanager",url:t,field_id:r},"*"):(jQuery("#"+r,a.document).val(t).trigger("change"),"function"==typeof a.responsive_filemanager_callback&&a.responsive_filemanager_callback(r),j()):apply_any(t)},copy:function(e){u(e,"copy")},cut:function(e){u(e,"cut")},paste:function(){d()},chmod:function(e){!function(e){jQuery("#files_permission_start").parent().parent().remove();var r=e.find(".rename-file-paths"),i=e.closest("figure").attr("data-path"),a=r.attr("data-permissions"),n=r.attr("data-folder");y.ajax({type:"POST",url:"ajax_calls.php?action=chmod",data:{path:i,permissions:a,folder:n}}).done(function(e){bootbox.dialog(e,[{label:jQuery("#cancel").val(),class:"btn"},{label:jQuery("#ok").val(),class:"btn-inverse",callback:function(){var a="-";jQuery("#u_4").is(":checked")?a+="r":a+="-",jQuery("#u_2").is(":checked")?a+="w":a+="-",jQuery("#u_1").is(":checked")?a+="x":a+="-",jQuery("#g_4").is(":checked")?a+="r":a+="-",jQuery("#g_2").is(":checked")?a+="w":a+="-",jQuery("#g_1").is(":checked")?a+="x":a+="-",jQuery("#a_4").is(":checked")?a+="r":a+="-",jQuery("#a_2").is(":checked")?a+="w":a+="-",jQuery("#a_1").is(":checked")?a+="x":a+="-";var e=jQuery("#chmod_form #chmod_value").val();if(""!=e&&void 0!==e){var t=jQuery("#chmod_form input[name=apply_recursive]:checked").val();""!=t&&void 0!==t||(t="none"),y.ajax({type:"POST",url:"execute.php?action=chmod",data:{path:i,new_mode:e,is_recursive:t,folder:n}}).done(function(e){""!=e?bootbox.alert(e):r.attr("data-permissions",a)})}}}],{header:jQuery("#lang_file_permission").val()}),setTimeout(function(){o(!1)},100)})}(e)},edit_text_file:function(e){!function(a){jQuery("#textfile_edit_area").parent().parent().remove();var t=a.closest("figure").attr("data-path");y.ajax({type:"POST",url:"ajax_calls.php?action=get_file&sub_action=edit&preview_mode=text",data:{path:t}}).done(function(e){bootbox.dialog(e,[{label:jQuery("#cancel").val(),class:"btn"},{label:jQuery("#ok").val(),class:"btn-inverse",callback:function(){var e=jQuery("#textfile_edit_area").val();window.editor&&"function"==typeof window.editor.getData&&(e=window.editor.getData()),y.ajax({type:"POST",url:"execute.php?action=save_text_file",data:{path:t,new_content:e}}).done(function(e){""!=e&&bootbox.alert(e)})}}],{header:a.find(".name_download").val()})})}(e)}},makeContextMenu:function(){var r=this;y.contextMenu({selector:"figure:not(.back-directory), .list-view2 figure:not(.back-directory)",autoHide:!0,build:function(t){t.addClass("selected");var e={callback:function(e,a){r.contextActions[e](t)},items:{}};return(t.find(".img-precontainer-mini .filetype").hasClass("png")||t.find(".img-precontainer-mini .filetype").hasClass("jpg")||t.find(".img-precontainer-mini .filetype").hasClass("jpeg"))&&a&&(e.items.edit_img={name:jQuery("#lang_edit_image").val(),icon:"edit_img",disabled:!1}),t.hasClass("directory")&&0!=jQuery("#type_param").val()&&(e.items.select={name:jQuery("#lang_select").val(),icon:"",disabled:!1}),e.items.copy_url={name:jQuery("#lang_show_url").val(),icon:"url",disabled:!1},(t.find(".img-precontainer-mini .filetype").hasClass("zip")||t.find(".img-precontainer-mini .filetype").hasClass("tar")||t.find(".img-precontainer-mini .filetype").hasClass("gz"))&&1==jQuery("#extract_files").val()&&(e.items.unzip={name:jQuery("#lang_extract").val(),icon:"extract",disabled:!1}),t.find(".img-precontainer-mini .filetype").hasClass("edit-text-file-allowed")&&(e.items.edit_text_file={name:jQuery("#lang_edit_file").val(),icon:"edit",disabled:!1}),t.hasClass("directory")||1!=jQuery("#duplicate").val()||(e.items.duplicate={name:jQuery("#lang_duplicate").val(),icon:"duplicate",disabled:!1}),t.hasClass("directory")||1!=jQuery("#copy_cut_files_allowed").val()?t.hasClass("directory")&&1==jQuery("#copy_cut_dirs_allowed").val()&&(e.items.copy={name:jQuery("#lang_copy").val(),icon:"copy",disabled:!1},e.items.cut={name:jQuery("#lang_cut").val(),icon:"cut",disabled:!1}):(e.items.copy={name:jQuery("#lang_copy").val(),icon:"copy",disabled:!1},e.items.cut={name:jQuery("#lang_cut").val(),icon:"cut",disabled:!1}),0==jQuery("#clipboard").val()||t.hasClass("directory")||(e.items.paste={name:jQuery("#lang_paste_here").val(),icon:"clipboard-apply",disabled:!1}),t.hasClass("directory")||1!=jQuery("#chmod_files_allowed").val()?t.hasClass("directory")&&1==jQuery("#chmod_dirs_allowed").val()&&(e.items.chmod={name:jQuery("#lang_file_permission").val(),icon:"key",disabled:!1}):e.items.chmod={name:jQuery("#lang_file_permission").val(),icon:"key",disabled:!1},e.items.sep="----",e.items.info={type:"html",html:""+jQuery("#lang_file_info").val()+""},e.items.name={name:t.attr("data-name"),icon:"label",disabled:!0},"img"==t.attr("data-type")&&(e.items.dimension={name:t.find(".img-dimension").html(),icon:"dimension",disabled:!0}),"true"!==jQuery("#show_folder_size").val()&&"true"!==jQuery("#show_folder_size").val()||(t.hasClass("directory")?e.items.size={name:t.find(".file-size").html()+" - "+t.find(".nfiles").val()+" "+jQuery("#lang_files").val()+" - "+t.find(".nfolders").val()+" "+jQuery("#lang_folders").val(),icon:"size",disabled:!0}:e.items.size={name:t.find(".file-size").html(),icon:"size",disabled:!0}),e.items.date={name:t.find(".file-date").html(),icon:"date",disabled:!0},e},events:{hide:function(){jQuery("figure").removeClass("selected")}}}),jQuery(document).on("contextmenu",function(e){if(!jQuery(e.target).is("figure"))return!1})},updateMultipleSelectionButtons:function(){0 div > div.fileupload-buttonbar > div.text-center > button").click()},200)}),jQuery("#fileupload").bind("fileuploadsubmit",function(e,a){a.formData={fldr:jQuery("#sub_folder").val()+jQuery("#fldr_value").val()+(a.files[0].relativePath||a.files[0].webkitRelativePath||"")}}),jQuery("#fileupload").addClass("fileupload-processing"),y.ajax({url:jQuery("#fileupload").fileupload("option","url"),dataType:"json",context:jQuery("#fileupload")[0]}).always(function(){jQuery(this).removeClass("fileupload-processing")}),jQuery(".upload-btn").on("click",function(){jQuery(".uploader").show(200)}),jQuery(".close-uploader").on("click",function(){jQuery(".uploader").hide(200),setTimeout(function(){window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()},420)})},uploadURL:function(){jQuery("#uploadURL").on("click",function(e){e.preventDefault();var a=jQuery("#url").val(),t=jQuery("#fldr_value").val();show_animation(),y.ajax({type:"POST",url:"upload.php",data:{fldr:t,url:a}}).done(function(e){hide_animation(),jQuery("#url").val("")}).fail(function(e){bootbox.alert(jQuery("#lang_error_upload").val()),hide_animation(),jQuery("#url").val("")})})},makeSort:function(r){jQuery("input[name=radio-sort]").on("click",function(){var e=jQuery(this).attr("data-item"),a=jQuery("#"+e),t=jQuery(".filters label");c=0,y(".selection:checkbox").removeAttr("checked"),s.updateMultipleSelectionButtons(),t.removeClass("btn-inverse"),t.find("i").removeClass("icon-white"),jQuery("#filter-input").val(""),a.addClass("btn-inverse"),a.find("i").addClass("icon-white"),"ff-item-type-all"==e?(r?jQuery(".grid li").show(300):window.location.href=jQuery("#current_url").val()+"&sort_by="+jQuery("#sort_by").val()+"&descending="+(i?1:0),"undefined"!=typeof Storage&&localStorage.setItem("sort","")):jQuery(this).is(":checked")&&(jQuery(".grid li").not("."+e).hide(300),jQuery(".grid li."+e).show(300),"undefined"!=typeof Storage&&localStorage.setItem("sort",e)),b()});var i=jQuery("#descending").val();jQuery(".sorter").on("click",function(){var e=jQuery(this);i=jQuery("#sort_by").val()!==e.attr("data-sort")||0==i,r?(y.ajax({url:"ajax_calls.php?action=sort&sort_by="+e.attr("data-sort")+"&descending="+(i?1:0)}),_(i,"."+e.attr("data-sort")),jQuery(" a.sorter").removeClass("descending").removeClass("ascending"),i?jQuery(".sort-"+e.attr("data-sort")).addClass("descending"):jQuery(".sort-"+e.attr("data-sort")).addClass("ascending"),jQuery("#sort_by").val(e.attr("data-sort")),jQuery("#descending").val(i?1:0),b()):window.location.href=jQuery("#current_url").val()+"&sort_by="+e.attr("data-sort")+"&descending="+(i?1:0)})}};function o(e){var n=[];if(n.user=0,n.group=0,void(n.all=0)!==e&&1==e){var a=jQuery("#chmod_form #chmod_value").val();n.user=a.substr(0,1),n.group=a.substr(1,1),n.all=a.substr(2,1),y.each(n,function(e){(""==n[e]||0==y.isNumeric(n[e])||parseInt(n[e])<0||7]+(>|$)/g,""),y.trim(e)):null}function g(e,a,t,r,i){null!==t&&(t=Q(t),y.ajax({type:"POST",url:"execute.php?action="+e,data:{path:a,name:t.replace("/","")}}).done(function(e){return""!=e?(bootbox.alert(e),!1):(""!=i&&window[i](r,t),!0)}))}function _(e,t){var a=jQuery("li.dir","ul.grid").filter(":visible"),r=jQuery("li.file","ul.grid").filter(":visible");c=0,y(".selection:checkbox").removeAttr("checked"),s.updateMultipleSelectionButtons();var i=[],n=[],l=[],o=[];a.each(function(){var e=jQuery(this),a=e.find(t).val();if(y.isNumeric(a))for(a=parseFloat(a);void 0!==i[a]&&i[a];)a=parseFloat(parseFloat(a)+parseFloat(.001));else a=a+"a"+e.find("h4 a").attr("data-file");i[a]=e.html(),n.push(a)}),r.each(function(){var e=jQuery(this),a=e.find(t).val();if(y.isNumeric(a))for(a=parseFloat(a);void 0!==l[a]&&l[a];)a=parseFloat(parseFloat(a)+parseFloat(.001));else a=a+"a"+e.find("h4 a").attr("data-file");l[a]=e.html(),o.push(a)}),y.isNumeric(n[0])?n.sort(function(e,a){return parseFloat(e)-parseFloat(a)}):n.sort(),y.isNumeric(o[0])?o.sort(function(e,a){return parseFloat(e)-parseFloat(a)}):o.sort(),e&&(n.reverse(),o.reverse()),a.each(function(e){jQuery(this).html(i[n[e]])}),r.each(function(e){jQuery(this).html(l[o[e]]),jQuery(this).attr("data-name",jQuery(this).children().attr("data-name"))})}function b(){i.update()}jQuery(document).ready(function(){if(s.makeContextMenu(),"undefined"!=typeof Storage&&1!=jQuery("#type_param").val()&&3!=jQuery("#type_param").val()){var e=localStorage.getItem("sort");if(e){var a=jQuery("#"+e);a.addClass("btn-inverse"),a.find("i").addClass("icon-white"),jQuery(".grid li").not("."+e).hide(300),jQuery(".grid li."+e).show(300)}}if(jQuery(".ff-container").on("click",".checkmark",function(e){e.stopPropagation(),jQuery(this).parent().find("input").is(":checked")?c--:c++,s.updateMultipleSelectionButtons()}),jQuery("#full-img").on("click",function(){jQuery("#previewLightbox").lightbox("hide")}),jQuery("body").on("click",function(){jQuery(".tip-right").tooltip("hide")}),s.bindGridEvents(),parseInt(jQuery("#file_number").val())>parseInt(jQuery("#file_number_limit_js").val()))var t=!1;else t=!0;s.makeSort(t),s.makeFilters(t),s.uploadURL(),jQuery("#info").on("click",function(){bootbox.alert('

responsive filemanager

RESPONSIVE filemanager v.9.14.0
responsivefilemanager.com


Copyright © Tecrail - Alberto Peripolli. All rights reserved.


License
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License.

')}),jQuery("#change_lang_btn").on("click",function(){y.ajax({type:"POST",url:"ajax_calls.php?action=get_lang",data:{}}).done(function(e){bootbox.dialog(e,[{label:jQuery("#cancel").val(),class:"btn"},{label:jQuery("#ok").val(),class:"btn-inverse",callback:function(){var a=jQuery("#new_lang_select").val();y.ajax({type:"POST",url:"ajax_calls.php?action=change_lang",data:{choosen_lang:a}}).done(function(e){""!=e?bootbox.alert(e):setTimeout(function(){window.location.href=jQuery("#refresh").attr("href").replace(/lang=[\w]*&/i,"lang="+a+"&")+"&"+(new Date).getTime()},100)})}}],{header:jQuery("#lang_lang_change").val()})})}),s.makeUploader(),jQuery("body").on("keypress",function(e){var a=String.fromCharCode(e.which);if("'"==a||'"'==a||"\\"==a||"/"==a)return!1}),jQuery("ul.grid li figcaption").on("click",'a[data-toggle="lightbox"]',function(){!function(e){show_animation();var a=new Image;a.src=e,jQuery(a).on("load",function(){hide_animation()})}(decodeURIComponent(jQuery(this).attr("data-url")))}),jQuery(".create-file-btn").on("click",function(){jQuery("#textfile_create_area").parent().parent().remove(),y.ajax({type:"GET",url:"ajax_calls.php?action=new_file_form"}).done(function(e){bootbox.dialog(e,[{label:jQuery("#cancel").val(),class:"btn"},{label:jQuery("#ok").val(),class:"btn-inverse",callback:function(){var e=jQuery("#create_text_file_name").val()+jQuery("#create_text_file_extension").val(),a=jQuery("#textfile_create_area").val();if(null!==e){e=Q(e);var t=jQuery("#sub_folder").val()+jQuery("#fldr_value").val();y.ajax({type:"POST",url:"execute.php?action=create_file",data:{path:t,name:e,new_content:a}}).done(function(e){""!=e&&bootbox.alert(e,function(){setTimeout(function(){window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()},500)})})}}}],{header:jQuery("#lang_new_file").val()})})}),jQuery(".new-folder").on("click",function(){bootbox.prompt(jQuery("#insert_folder_name").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(e){if(null!==e){e=Q(e).replace(".","");var a=jQuery("#sub_folder").val()+jQuery("#fldr_value").val();y.ajax({type:"POST",url:"execute.php?action=create_folder",data:{path:a,name:e}}).done(function(e){e?bootbox.alert(jQuery("#rename_existing_folder").val()):setTimeout(function(){window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()},300)})}})}),jQuery(".view-controller button").on("click",function(){var e=jQuery(this);jQuery(".view-controller button").removeClass("btn-inverse"),jQuery(".view-controller i").removeClass("icon-white"),e.addClass("btn-inverse"),e.find("i").addClass("icon-white"),y.ajax({url:"ajax_calls.php?action=view&type="+e.attr("data-value")}).done(function(e){""!=e&&bootbox.alert(e)}),void 0!==jQuery("ul.grid")[0]&&jQuery("ul.grid")[0]&&(jQuery("ul.grid")[0].className=jQuery("ul.grid")[0].className.replace(/\blist-view.*?\b/g,"")),void 0!==jQuery(".sorter-container")[0]&&jQuery(".sorter-container")[0]&&(jQuery(".sorter-container")[0].className=jQuery(".sorter-container")[0].className.replace(/\blist-view.*?\b/g,""));var a=e.attr("data-value");jQuery("#view").val(a),jQuery("ul.grid").addClass("list-view"+a),jQuery(".sorter-container").addClass("list-view"+a),1<=e.attr("data-value")?p(14):(jQuery("ul.grid li").css("width",126),jQuery("ul.grid figure").css("width",122)),b()}),n.touch?(jQuery("#help").show(),jQuery(".box:not(.no-effect)").swipe({swipeLeft:v,swipeRight:v,threshold:30})):(jQuery(".tip").tooltip({placement:"bottom"}),jQuery(".tip-top").tooltip({placement:"top"}),jQuery(".tip-left").tooltip({placement:"left"}),jQuery(".tip-right").tooltip({placement:"right"}),jQuery("body").addClass("no-touch")),jQuery(".paste-here-btn").on("click",function(){0==jQuery(this).hasClass("disabled")&&d()}),jQuery(".clear-clipboard-btn").on("click",function(){0==jQuery(this).hasClass("disabled")&&bootbox.confirm(jQuery("#lang_clear_clipboard_confirm").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(e){1==e&&y.ajax({type:"POST",url:"ajax_calls.php?action=clear_clipboard",data:{}}).done(function(e){""!=e?bootbox.alert(e):jQuery("#clipboard").val("0"),f(!1)})})});var u=function(a){var t=[];return jQuery(".selection:checkbox:checked:visible").each(function(){var e=jQuery(this).val();a&&(e=jQuery(this).closest("figure").attr("data-path")),t.push(e)}),t};if(jQuery(".multiple-action-btn").on("click",function(){var e=u();window[jQuery(this).attr("data-function")](e,jQuery("#field_id").val())}),jQuery(".multiple-deselect-btn").on("click",function(){y(".selection:checkbox").removeAttr("checked"),jQuery("#multiple-selection").hide(300)}),jQuery(".multiple-select-btn").on("click",function(){y(".selection:checkbox:visible").prop("checked",!0)}),jQuery(".multiple-delete-btn").on("click",function(){if(0!=jQuery(".selection:checkbox:checked:visible").length){var e=jQuery(this);bootbox.confirm(e.attr("data-confirm"),jQuery("#cancel").val(),jQuery("#ok").val(),function(e){if(1==e){var a=u(!0);r="delete_files",i=a,o=l=n="",null!==name&&(name=Q(name),y.ajax({type:"POST",url:"execute.php?action="+r,data:{path:i[0],paths:i,names:n}}).done(function(e){return""!=e?(bootbox.alert(e),!1):(""!=o&&window[o](l,name),!0)}));var t=jQuery("#files_number");t.text(parseInt(t.text())-a.length),jQuery(".selection:checkbox:checked:visible").each(function(){jQuery(this).closest("li").remove()}),jQuery("#multiple-selection").hide(300)}var r,i,n,l,o})}}),!n.csstransforms){var r=jQuery("figure");r.on("mouseover",function(){0==jQuery("#view").val()&&!1===jQuery("#main-item-container").hasClass("no-effect-slide")&&jQuery(this).find(".box:not(.no-effect)").animate({top:"-26px"},{queue:!1,duration:300})}),r.on("mouseout",function(){0==jQuery("#view").val()&&jQuery(this).find(".box:not(.no-effect)").animate({top:"0px"},{queue:!1,duration:300})})}jQuery(window).resize(function(){p(28)}),p(14),1==jQuery("#clipboard").val()?f(!0):f(!1),jQuery("li.dir, li.file").draggable({distance:20,cursor:"move",helper:function(){jQuery(this).find("figure").find(".box").css("top","0px");var e=jQuery(this).clone().css("z-index",1e3).find(".box").css("box-shadow","none").css("-webkit-box-shadow","none").parent().parent();return jQuery(this).addClass("selected"),e},start:function(e,a){jQuery(a.helper).addClass("ui-draggable-helper"),0==jQuery("#view").val()&&jQuery("#main-item-container").addClass("no-effect-slide")},stop:function(){jQuery(this).removeClass("selected"),0==jQuery("#view").val()&&jQuery("#main-item-container").removeClass("no-effect-slide")}}),jQuery("li.dir,li.back").droppable({accept:"ul.grid li",activeClass:"ui-state-highlight",hoverClass:"ui-state-hover",drop:function(e,a){!function(t,r){t.hasClass("directory")?t.find(".rename-folder"):t.find(".rename-file");var e=t.closest("figure").attr("data-path");t.parent().hide(100),y.ajax({type:"POST",url:"ajax_calls.php?action=copy_cut",data:{path:e,sub_action:"cut"}}).done(function(e){var a;""!=e?bootbox.alert(e):(a=void 0!==r?r.hasClass("back-directory")?r.find(".path").val():r.closest("figure").attr("data-path"):jQuery("#sub_folder").val()+jQuery("#fldr_value").val(),y.ajax({type:"POST",url:"execute.php?action=paste_clipboard",data:{path:a}}).done(function(e){""!=e?(bootbox.alert(e),t.parent().show(100)):(jQuery("#clipboard").val("0"),f(!1),t.parent().remove())}))}).error(function(){t.parent().show(100)})}(a.draggable.find("figure"),jQuery(this).find("figure"))}}),jQuery(document).on("keyup","#chmod_form #chmod_value",function(){o(!0)}),jQuery(document).on("change","#chmod_form input",function(){o(!1)}),jQuery(document).on("focusout","#chmod_form #chmod_value",function(){var e=jQuery("#chmod_form #chmod_value");null==e.val().match(/^[0-7]{3}$/)&&(e.val(e.attr("data-def-value")),o(!0))}),i=new LazyLoad,new Clipboard(".btn")}),encodeURL=function(e){for(var a=e.split("/"),t=3;t '):-1'+d+" ":-1'+d+" "):i+=''+d+" "}1==jQuery("#crossdomain").val()?t.postMessage({sender:"responsivefilemanager",url:u,field_id:null,html:i},"*"):parent.tinymce.majorVersion<4?(parent.tinymce.activeEditor.execCommand("mceInsertContent",!1,i),parent.tinymce.activeEditor.windowManager.close(parent.tinymce.activeEditor.windowManager.params.mce_window_id)):(parent.tinymce.activeEditor.insertContent(i),parent.tinymce.activeEditor.windowManager.close())}},apply_link=function(e,a){var t=h(),r=jQuery("#callback").val();Array.isArray(e)||(e=new Array(e));var i=m(e),n=JSON.stringify(i);(1==i.length&&(n=i[0]),""!=a)?1==jQuery("#crossdomain").val()?t.postMessage({sender:"responsivefilemanager",url:i[0],field_id:a},"*"):(jQuery("#"+a,t.document).val(n).trigger("change"),0==r?"function"==typeof t.responsive_filemanager_callback&&t.responsive_filemanager_callback(a):"function"==typeof t[r]&&t[r](a),j()):apply_any(i[0])},apply_img=function(e,a){var t=h(),r=jQuery("#callback").val();Array.isArray(e)||(e=new Array(e));var i=m(e),n=JSON.stringify(i);if(1==i.length&&(n=i[0]),""!=a){if(1==jQuery("#crossdomain").val())t.postMessage({sender:"responsivefilemanager",url:i[0],field_id:a},"*");else jQuery("#"+a,t.document).val(n).trigger("change"),0==r?"function"==typeof t.responsive_filemanager_callback&&t.responsive_filemanager_callback(a):"function"==typeof t[r]&&t[r](a),j()}else{if(jQuery("#add_time_to_img").val())var l=i[0]+"?"+(new Date).getTime();else l=i[0];apply_any(l)}},apply_video=function(e,a){var t=h(),r=jQuery("#callback").val();Array.isArray(e)||(e=new Array(e));var i=m(e),n=JSON.stringify(i);(1==i.length&&(n=i[0]),""!=a)?1==jQuery("#crossdomain").val()?t.postMessage({sender:"responsivefilemanager",url:i[0],field_id:a},"*"):(jQuery("#"+a,t.document).val(n).trigger("change"),0==r?"function"==typeof t.responsive_filemanager_callback&&t.responsive_filemanager_callback(a):"function"==typeof t[r]&&t[r](a),j()):apply_any(i[0])},apply_none=function(e,a,t){t.parent().find("form a")[1].click(),jQuery(".tip-right").tooltip("hide")},apply_any=function(e){if(1==jQuery("#crossdomain").val())window.parent.postMessage({sender:"responsivefilemanager",url:e,field_id:null},"*");else if("ckeditor"===jQuery("#editor").val()){var a=(t=new RegExp("(?:[?&]|&)"+"CKEditorFuncNum"+"=([^&]+)","i"),(r=window.location.search.match(t))&&1"+t.html()+"");var r=t.next();apply_file_rename(r.find("figure"),a);var i=r.find(".download-form"),n="form"+(new Date).getTime();i.attr("id",n),i.find(".tip-right").first().attr("onclick","jQuery('#"+n+"').submit();")},apply_file_rename=function(e,a){var t;e.attr("data-name",a),e.parent().attr("data-name",a),e.find("h4").text(a);var r=e.find("a.link"),i=(t=r.attr("data-file")).substring(t.lastIndexOf("/")+1),n=t.substring(t.lastIndexOf(".")+1);n=n?"."+n:"",r.each(function(){jQuery(this).attr("data-file",encodeURIComponent(a+n))}),e.find("img").each(function(){if(e=jQuery(this).attr("src"))jQuery(this).attr("src",e.replace(i,a+n)+"?time="+(new Date).getTime());else{var e=jQuery(this).attr("data-src");jQuery(this).attr("data-src",e.replace(i,a+n)+"?time="+(new Date).getTime())}jQuery(this).attr("alt",a+" thumbnails")});var l=e.find("a.preview");void 0!==(t=l.attr("data-url"))&&t&&l.attr("data-url",t.replace(encodeURIComponent(i),encodeURIComponent(a+n))),e.parent().attr("data-name",a+n),e.attr("data-name",a+n),e.find(".name_download").val(a+n);var o=e.attr("data-path").replace(i,a+n);e.attr("data-path",o)},apply_folder_rename=function(e,a){e.attr("data-name",a),e.find("figure").attr("data-name",a);var t=e.find("h4").find("a").text();e.find("h4 > a").text(a);var r=e.find(".folder-link"),i=r.attr("href"),n=jQuery("#fldr_value").val(),l=i.replace("fldr="+n+encodeURIComponent(t),"fldr="+n+encodeURIComponent(a));r.each(function(){jQuery(this).attr("href",l)});var o=e.attr("data-path"),u=o.lastIndexOf("/"),c=o.substr(0,u+1)+a;e.attr("data-path",c)},show_animation=function(){jQuery("#loading_container").css("display","block"),jQuery("#loading").css("opacity",".7")},hide_animation=function(){jQuery("#loading_container").fadeOut()}}(jQuery,Modernizr,image_editor),function(){if("function"==typeof window.CustomEvent)return;function e(e,a){a=a||{bubbles:!1,cancelable:!1,detail:void 0};var t=document.createEvent("CustomEvent");return t.initCustomEvent(e,a.bubbles,a.cancelable,a.detail),t}e.prototype=window.Event.prototype,window.CustomEvent=e}(); \ No newline at end of file diff --git a/core/vendor/filemanager/js/jquery.fileupload-angular.js b/core/vendor/filemanager/js/jquery.fileupload-angular.js new file mode 100644 index 0000000..185907d --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-angular.js @@ -0,0 +1,438 @@ +/* + * jQuery File Upload AngularJS Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, angular, require */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'angular', + './jquery.fileupload-image', + './jquery.fileupload-audio', + './jquery.fileupload-video', + './jquery.fileupload-validate' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('angular'), + require('./jquery.fileupload-image'), + require('./jquery.fileupload-audio'), + require('./jquery.fileupload-video'), + require('./jquery.fileupload-validate') + ); + } else { + factory(); + } +}(function () { + 'use strict'; + + angular.module('blueimp.fileupload', []) + + // The fileUpload service provides configuration options + // for the fileUpload directive and default handlers for + // File Upload events: + .provider('fileUpload', function () { + var scopeEvalAsync = function (expression) { + var scope = angular.element(this) + .fileupload('option', 'scope'); + // Schedule a new $digest cycle if not already inside of one + // and evaluate the given expression: + scope.$evalAsync(expression); + }, + addFileMethods = function (scope, data) { + var files = data.files, + file = files[0]; + angular.forEach(files, function (file, index) { + file._index = index; + file.$state = function () { + return data.state(); + }; + file.$processing = function () { + return data.processing(); + }; + file.$progress = function () { + return data.progress(); + }; + file.$response = function () { + return data.response(); + }; + }); + file.$submit = function () { + if (!file.error) { + return data.submit(); + } + }; + file.$cancel = function () { + return data.abort(); + }; + }, + $config; + $config = this.defaults = { + handleResponse: function (e, data) { + var files = data.result && data.result.files; + if (files) { + data.scope.replace(data.files, files); + } else if (data.errorThrown || + data.textStatus === 'error') { + data.files[0].error = data.errorThrown || + data.textStatus; + } + }, + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var scope = data.scope, + filesCopy = []; + angular.forEach(data.files, function (file) { + filesCopy.push(file); + }); + scope.$parent.$applyAsync(function () { + addFileMethods(scope, data); + var method = scope.option('prependFiles') ? + 'unshift' : 'push'; + Array.prototype[method].apply(scope.queue, data.files); + }); + data.process(function () { + return scope.process(data); + }).always(function () { + scope.$parent.$applyAsync(function () { + addFileMethods(scope, data); + scope.replace(filesCopy, data.files); + }); + }).then(function () { + if ((scope.option('autoUpload') || + data.autoUpload) && + data.autoUpload !== false) { + data.submit(); + } + }); + }, + done: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = this; + data.scope.$apply(function () { + data.handleResponse.call(that, e, data); + }); + }, + fail: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = this, + scope = data.scope; + if (data.errorThrown === 'abort') { + scope.clear(data.files); + return; + } + scope.$apply(function () { + data.handleResponse.call(that, e, data); + }); + }, + stop: scopeEvalAsync, + processstart: scopeEvalAsync, + processstop: scopeEvalAsync, + getNumberOfFiles: function () { + var scope = this.scope; + return scope.queue.length - scope.processing(); + }, + dataType: 'json', + autoUpload: false + }; + this.$get = [ + function () { + return { + defaults: $config + }; + } + ]; + }) + + // Format byte numbers to readable presentations: + .provider('formatFileSizeFilter', function () { + var $config = { + // Byte units following the IEC format + // http://en.wikipedia.org/wiki/Kilobyte + units: [ + {size: 1000000000, suffix: ' GB'}, + {size: 1000000, suffix: ' MB'}, + {size: 1000, suffix: ' KB'} + ] + }; + this.defaults = $config; + this.$get = function () { + return function (bytes) { + if (!angular.isNumber(bytes)) { + return ''; + } + var unit = true, + i = 0, + prefix, + suffix; + while (unit) { + unit = $config.units[i]; + prefix = unit.prefix || ''; + suffix = unit.suffix || ''; + if (i === $config.units.length - 1 || bytes >= unit.size) { + return prefix + (bytes / unit.size).toFixed(2) + suffix; + } + i += 1; + } + }; + }; + }) + + // The FileUploadController initializes the fileupload widget and + // provides scope methods to control the File Upload functionality: + .controller('FileUploadController', [ + '$scope', '$element', '$attrs', '$window', 'fileUpload','$q', + function ($scope, $element, $attrs, $window, fileUpload, $q) { + var uploadMethods = { + progress: function () { + return $element.fileupload('progress'); + }, + active: function () { + return $element.fileupload('active'); + }, + option: function (option, data) { + if (arguments.length === 1) { + return $element.fileupload('option', option); + } + $element.fileupload('option', option, data); + }, + add: function (data) { + return $element.fileupload('add', data); + }, + send: function (data) { + return $element.fileupload('send', data); + }, + process: function (data) { + return $element.fileupload('process', data); + }, + processing: function (data) { + return $element.fileupload('processing', data); + } + }; + $scope.disabled = !$window.jQuery.support.fileInput; + $scope.queue = $scope.queue || []; + $scope.clear = function (files) { + var queue = this.queue, + i = queue.length, + file = files, + length = 1; + if (angular.isArray(files)) { + file = files[0]; + length = files.length; + } + while (i) { + i -= 1; + if (queue[i] === file) { + return queue.splice(i, length); + } + } + }; + $scope.replace = function (oldFiles, newFiles) { + var queue = this.queue, + file = oldFiles[0], + i, + j; + for (i = 0; i < queue.length; i += 1) { + if (queue[i] === file) { + for (j = 0; j < newFiles.length; j += 1) { + queue[i + j] = newFiles[j]; + } + return; + } + } + }; + $scope.applyOnQueue = function (method) { + var list = this.queue.slice(0), + i, + file, + promises = []; + for (i = 0; i < list.length; i += 1) { + file = list[i]; + if (file[method]) { + promises.push(file[method]()); + } + } + return $q.all(promises); + }; + $scope.submit = function () { + return this.applyOnQueue('$submit'); + }; + $scope.cancel = function () { + return this.applyOnQueue('$cancel'); + }; + // Add upload methods to the scope: + angular.extend($scope, uploadMethods); + // The fileupload widget will initialize with + // the options provided via "data-"-parameters, + // as well as those given via options object: + $element.fileupload(angular.extend( + {scope: $scope}, + fileUpload.defaults + )).on('fileuploadadd', function (e, data) { + data.scope = $scope; + }).on('fileuploadfail', function (e, data) { + if (data.errorThrown === 'abort') { + return; + } + if (data.dataType && + data.dataType.indexOf('json') === data.dataType.length - 4) { + try { + data.result = angular.fromJson(data.jqXHR.responseText); + } catch (ignore) {} + } + }).on([ + 'fileuploadadd', + 'fileuploadsubmit', + 'fileuploadsend', + 'fileuploaddone', + 'fileuploadfail', + 'fileuploadalways', + 'fileuploadprogress', + 'fileuploadprogressall', + 'fileuploadstart', + 'fileuploadstop', + 'fileuploadchange', + 'fileuploadpaste', + 'fileuploaddrop', + 'fileuploaddragover', + 'fileuploadchunkbeforesend', + 'fileuploadchunksend', + 'fileuploadchunkdone', + 'fileuploadchunkfail', + 'fileuploadchunkalways', + 'fileuploadprocessstart', + 'fileuploadprocess', + 'fileuploadprocessdone', + 'fileuploadprocessfail', + 'fileuploadprocessalways', + 'fileuploadprocessstop' + ].join(' '), function (e, data) { + $scope.$parent.$applyAsync(function () { + if ($scope.$emit(e.type, data).defaultPrevented) { + e.preventDefault(); + } + }); + }).on('remove', function () { + // Remove upload methods from the scope, + // when the widget is removed: + var method; + for (method in uploadMethods) { + if (uploadMethods.hasOwnProperty(method)) { + delete $scope[method]; + } + } + }); + // Observe option changes: + $scope.$watch( + $attrs.fileUpload, + function (newOptions) { + if (newOptions) { + $element.fileupload('option', newOptions); + } + } + ); + } + ]) + + // Provide File Upload progress feedback: + .controller('FileUploadProgressController', [ + '$scope', '$attrs', '$parse', + function ($scope, $attrs, $parse) { + var fn = $parse($attrs.fileUploadProgress), + update = function () { + var progress = fn($scope); + if (!progress || !progress.total) { + return; + } + $scope.num = Math.floor( + progress.loaded / progress.total * 100 + ); + }; + update(); + $scope.$watch( + $attrs.fileUploadProgress + '.loaded', + function (newValue, oldValue) { + if (newValue !== oldValue) { + update(); + } + } + ); + } + ]) + + // Display File Upload previews: + .controller('FileUploadPreviewController', [ + '$scope', '$element', '$attrs', + function ($scope, $element, $attrs) { + $scope.$watch( + $attrs.fileUploadPreview + '.preview', + function (preview) { + $element.empty(); + if (preview) { + $element.append(preview); + } + } + ); + } + ]) + + .directive('fileUpload', function () { + return { + controller: 'FileUploadController', + scope: true + }; + }) + + .directive('fileUploadProgress', function () { + return { + controller: 'FileUploadProgressController', + scope: true + }; + }) + + .directive('fileUploadPreview', function () { + return { + controller: 'FileUploadPreviewController' + }; + }) + + // Enhance the HTML5 download attribute to + // allow drag&drop of files to the desktop: + .directive('download', function () { + return function (scope, elm) { + elm.on('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [ + 'application/octet-stream', + elm.prop('download'), + elm.prop('href') + ].join(':') + ); + } catch (ignore) {} + }); + }; + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-audio.js b/core/vendor/filemanager/js/jquery.fileupload-audio.js new file mode 100644 index 0000000..a253776 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-audio.js @@ -0,0 +1,113 @@ +/* + * jQuery File Upload Audio Preview Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('blueimp-load-image/js/load-image'), + require('./jquery.fileupload-process') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadAudio', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + disabled: '@disableAudioPreview' + }, + { + action: 'setAudio', + name: '@audioPreviewName', + disabled: '@disableAudioPreview' + } + ); + + // The File Upload Audio Preview plugin extends the fileupload widget + // with audio preview functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of audio files to load, + // matched against the file type: + loadAudioFileTypes: /^audio\/.*$/ + }, + + _audioElement: document.createElement('audio'), + + processActions: { + + // Loads the audio file given via data.files and data.index + // as audio element if the browser supports playing it. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadAudio: function (data, options) { + if (options.disabled) { + return data; + } + var file = data.files[data.index], + url, + audio; + if (this._audioElement.canPlayType && + this._audioElement.canPlayType(file.type) && + ($.type(options.maxFileSize) !== 'number' || + file.size <= options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + url = loadImage.createObjectURL(file); + if (url) { + audio = this._audioElement.cloneNode(false); + audio.src = url; + audio.controls = true; + data.audio = audio; + return data; + } + } + return data; + }, + + // Sets the audio element as a property of the file object: + setAudio: function (data, options) { + if (data.audio && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.audio; + } + return data; + } + + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-image.js b/core/vendor/filemanager/js/jquery.fileupload-image.js new file mode 100644 index 0000000..65fc6d7 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-image.js @@ -0,0 +1,326 @@ +/* + * jQuery File Upload Image Preview & Resize Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, Blob */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'load-image-meta', + 'load-image-scale', + 'load-image-exif', + 'canvas-to-blob', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('blueimp-load-image/js/load-image'), + require('blueimp-load-image/js/load-image-meta'), + require('blueimp-load-image/js/load-image-scale'), + require('blueimp-load-image/js/load-image-exif'), + require('blueimp-canvas-to-blob'), + require('./jquery.fileupload-process') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadImageMetaData', + disableImageHead: '@', + disableExif: '@', + disableExifThumbnail: '@', + disableExifSub: '@', + disableExifGps: '@', + disabled: '@disableImageMetaDataLoad' + }, + { + action: 'loadImage', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + noRevoke: '@', + disabled: '@disableImageLoad' + }, + { + action: 'resizeImage', + // Use "image" as prefix for the "@" options: + prefix: 'image', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + forceResize: '@', + disabled: '@disableImageResize' + }, + { + action: 'saveImage', + quality: '@imageQuality', + type: '@imageType', + disabled: '@disableImageResize' + }, + { + action: 'saveImageMetaData', + disabled: '@disableImageMetaDataSave' + }, + { + action: 'resizeImage', + // Use "preview" as prefix for the "@" options: + prefix: 'preview', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + thumbnail: '@', + canvas: '@', + disabled: '@disableImagePreview' + }, + { + action: 'setImage', + name: '@imagePreviewName', + disabled: '@disableImagePreview' + }, + { + action: 'deleteImageReferences', + disabled: '@disableImageReferencesDeletion' + } + ); + + // The File Upload Resize plugin extends the fileupload widget + // with image resize functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of images to load: + // matched against the file type: + loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/, + // The maximum file size of images to load: + loadImageMaxFileSize: 10000000, // 10MB + // The maximum width of resized images: + imageMaxWidth: 1920, + // The maximum height of resized images: + imageMaxHeight: 1080, + // Defines the image orientation (1-8) or takes the orientation + // value from Exif data if set to true: + imageOrientation: false, + // Define if resized images should be cropped or only scaled: + imageCrop: false, + // Disable the resize image functionality by default: + disableImageResize: true, + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // Defines the preview orientation (1-8) or takes the orientation + // value from Exif data if set to true: + previewOrientation: true, + // Create the preview using the Exif data thumbnail: + previewThumbnail: true, + // Define if preview images should be cropped or only scaled: + previewCrop: false, + // Define if preview images should be resized as canvas elements: + previewCanvas: true + }, + + processActions: { + + // Loads the image given via data.files and data.index + // as img element, if the browser supports the File API. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadImage: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (($.type(options.maxFileSize) === 'number' && + file.size > options.maxFileSize) || + (options.fileTypes && + !options.fileTypes.test(file.type)) || + !loadImage( + file, + function (img) { + if (img.src) { + data.img = img; + } + dfd.resolveWith(that, [data]); + }, + options + )) { + return data; + } + return dfd.promise(); + }, + + // Resizes the image given as data.canvas or data.img + // and updates data.canvas or data.img with the resized image. + // Also stores the resized image as preview property. + // Accepts the options maxWidth, maxHeight, minWidth, + // minHeight, canvas and crop: + resizeImage: function (data, options) { + if (options.disabled || !(data.canvas || data.img)) { + return data; + } + options = $.extend({canvas: true}, options); + var that = this, + dfd = $.Deferred(), + img = (options.canvas && data.canvas) || data.img, + resolve = function (newImg) { + if (newImg && (newImg.width !== img.width || + newImg.height !== img.height || + options.forceResize)) { + data[newImg.getContext ? 'canvas' : 'img'] = newImg; + } + data.preview = newImg; + dfd.resolveWith(that, [data]); + }, + thumbnail; + if (data.exif) { + if (options.orientation === true) { + options.orientation = data.exif.get('Orientation'); + } + if (options.thumbnail) { + thumbnail = data.exif.get('Thumbnail'); + if (thumbnail) { + loadImage(thumbnail, resolve, options); + return dfd.promise(); + } + } + // Prevent orienting the same image twice: + if (data.orientation) { + delete options.orientation; + } else { + data.orientation = options.orientation; + } + } + if (img) { + resolve(loadImage.scale(img, options)); + return dfd.promise(); + } + return data; + }, + + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + saveImage: function (data, options) { + if (!data.canvas || options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (data.canvas.toBlob) { + data.canvas.toBlob( + function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\.\w+$/, + '.' + blob.type.substr(6) + ); + } + } + // Don't restore invalid meta data: + if (file.type !== blob.type) { + delete data.imageHead; + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }, + options.type || file.type, + options.quality + ); + } else { + return data; + } + return dfd.promise(); + }, + + loadImageMetaData: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + dfd = $.Deferred(); + loadImage.parseMetaData(data.files[data.index], function (result) { + $.extend(data, result); + dfd.resolveWith(that, [data]); + }, options); + return dfd.promise(); + }, + + saveImageMetaData: function (data, options) { + if (!(data.imageHead && data.canvas && + data.canvas.toBlob && !options.disabled)) { + return data; + } + var file = data.files[data.index], + blob = new Blob([ + data.imageHead, + // Resized images always have a head size of 20 bytes, + // including the JPEG marker and a minimal JFIF header: + this._blobSlice.call(file, 20) + ], {type: file.type}); + blob.name = file.name; + data.files[data.index] = blob; + return data; + }, + + // Sets the resized version of the image as a property of the + // file object, must be called after "saveImage": + setImage: function (data, options) { + if (data.preview && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.preview; + } + return data; + }, + + deleteImageReferences: function (data, options) { + if (!options.disabled) { + delete data.img; + delete data.canvas; + delete data.preview; + delete data.imageHead; + } + return data; + } + + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-jquery-ui.js b/core/vendor/filemanager/js/jquery.fileupload-jquery-ui.js new file mode 100644 index 0000000..7b136b3 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-jquery-ui.js @@ -0,0 +1,161 @@ +/* + * jQuery File Upload jQuery UI Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload-ui' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./jquery.fileupload-ui') + ); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + processdone: function (e, data) { + data.context.find('.start').button('enable'); + }, + progress: function (e, data) { + if (data.context) { + data.context.find('.progress').progressbar( + 'option', + 'value', + parseInt(data.loaded / data.total * 100, 10) + ); + } + }, + progressall: function (e, data) { + var $this = $(this); + $this.find('.fileupload-progress') + .find('.progress').progressbar( + 'option', + 'value', + parseInt(data.loaded / data.total * 100, 10) + ).end() + .find('.progress-extended').each(function () { + $(this).html( + ($this.data('blueimp-fileupload') || + $this.data('fileupload')) + ._renderExtendedProgress(data) + ); + }); + } + }, + + _renderUpload: function (func, files) { + var node = this._super(func, files), + showIconText = $(window).width() > 480; + node.find('.progress').empty().progressbar(); + node.find('.start').button({ + icons: {primary: 'ui-icon-circle-arrow-e'}, + text: showIconText + }); + node.find('.cancel').button({ + icons: {primary: 'ui-icon-cancel'}, + text: showIconText + }); + if (node.hasClass('fade')) { + node.hide(); + } + return node; + }, + + _renderDownload: function (func, files) { + var node = this._super(func, files), + showIconText = $(window).width() > 480; + node.find('.delete').button({ + icons: {primary: 'ui-icon-trash'}, + text: showIconText + }); + if (node.hasClass('fade')) { + node.hide(); + } + return node; + }, + + _startHandler: function (e) { + $(e.currentTarget).button('disable'); + this._super(e); + }, + + _transition: function (node) { + var deferred = $.Deferred(); + if (node.hasClass('fade')) { + node.fadeToggle( + this.options.transitionDuration, + this.options.transitionEasing, + function () { + deferred.resolveWith(node); + } + ); + } else { + deferred.resolveWith(node); + } + return deferred; + }, + + _create: function () { + this._super(); + this.element + .find('.fileupload-buttonbar') + .find('.fileinput-button').each(function () { + var input = $(this).find('input:file').detach(); + $(this) + .button({icons: {primary: 'ui-icon-plusthick'}}) + .append(input); + }) + .end().find('.start') + .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) + .end().find('.cancel') + .button({icons: {primary: 'ui-icon-cancel'}}) + .end().find('.delete') + .button({icons: {primary: 'ui-icon-trash'}}) + .end().find('.progress').progressbar(); + }, + + _destroy: function () { + this.element + .find('.fileupload-buttonbar') + .find('.fileinput-button').each(function () { + var input = $(this).find('input:file').detach(); + $(this) + .button('destroy') + .append(input); + }) + .end().find('.start') + .button('destroy') + .end().find('.cancel') + .button('destroy') + .end().find('.delete') + .button('destroy') + .end().find('.progress').progressbar('destroy'); + this._super(); + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-process.js b/core/vendor/filemanager/js/jquery.fileupload-process.js new file mode 100644 index 0000000..638f0d2 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-process.js @@ -0,0 +1,178 @@ +/* + * jQuery File Upload Processing Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./jquery.fileupload') + ); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + var originalAdd = $.blueimp.fileupload.prototype.options.add; + + // The File Upload Processing plugin extends the fileupload widget + // with file processing functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The list of processing actions: + processQueue: [ + /* + { + action: 'log', + type: 'debug' + } + */ + ], + add: function (e, data) { + var $this = $(this); + data.process(function () { + return $this.fileupload('process', data); + }); + originalAdd.call(this, e, data); + } + }, + + processActions: { + /* + log: function (data, options) { + console[options.type]( + 'Processing "' + data.files[data.index].name + '"' + ); + } + */ + }, + + _processFile: function (data, originalData) { + var that = this, + dfd = $.Deferred().resolveWith(that, [data]), + chain = dfd.promise(); + this._trigger('process', null, data); + $.each(data.processQueue, function (i, settings) { + var func = function (data) { + if (originalData.errorThrown) { + return $.Deferred() + .rejectWith(that, [originalData]).promise(); + } + return that.processActions[settings.action].call( + that, + data, + settings + ); + }; + chain = chain.then(func, settings.always && func); + }); + chain + .done(function () { + that._trigger('processdone', null, data); + that._trigger('processalways', null, data); + }) + .fail(function () { + that._trigger('processfail', null, data); + that._trigger('processalways', null, data); + }); + return chain; + }, + + // Replaces the settings of each processQueue item that + // are strings starting with an "@", using the remaining + // substring as key for the option map, + // e.g. "@autoUpload" is replaced with options.autoUpload: + _transformProcessQueue: function (options) { + var processQueue = []; + $.each(options.processQueue, function () { + var settings = {}, + action = this.action, + prefix = this.prefix === true ? action : this.prefix; + $.each(this, function (key, value) { + if ($.type(value) === 'string' && + value.charAt(0) === '@') { + settings[key] = options[ + value.slice(1) || (prefix ? prefix + + key.charAt(0).toUpperCase() + key.slice(1) : key) + ]; + } else { + settings[key] = value; + } + + }); + processQueue.push(settings); + }); + options.processQueue = processQueue; + }, + + // Returns the number of files currently in the processsing queue: + processing: function () { + return this._processing; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows to bind callbacks: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.processQueue && options.processQueue.length) { + this._transformProcessQueue(options); + if (this._processing === 0) { + this._trigger('processstart'); + } + $.each(data.files, function (index) { + var opts = index ? $.extend({}, options) : options, + func = function () { + if (data.errorThrown) { + return $.Deferred() + .rejectWith(that, [data]).promise(); + } + return that._processFile(opts, data); + }; + opts.index = index; + that._processing += 1; + that._processingQueue = that._processingQueue.then(func, func) + .always(function () { + that._processing -= 1; + if (that._processing === 0) { + that._trigger('processstop'); + } + }); + }); + } + return this._processingQueue; + }, + + _create: function () { + this._super(); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-ui.js b/core/vendor/filemanager/js/jquery.fileupload-ui.js new file mode 100644 index 0000000..5058084 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-ui.js @@ -0,0 +1,714 @@ +/* + * jQuery File Upload User Interface Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'blueimp-tmpl', + './jquery.fileupload-image', + './jquery.fileupload-audio', + './jquery.fileupload-video', + './jquery.fileupload-validate' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('blueimp-tmpl'), + require('./jquery.fileupload-image'), + require('./jquery.fileupload-audio'), + require('./jquery.fileupload-video'), + require('./jquery.fileupload-validate') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.tmpl + ); + } +}(function ($, tmpl) { + 'use strict'; + + $.blueimp.fileupload.prototype._specialOptions.push( + 'filesContainer', + 'uploadTemplateId', + 'downloadTemplateId' + ); + + // The UI version extends the file upload widget + // and adds complete user interface interaction: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // By default, files added to the widget are uploaded as soon + // as the user clicks on the start buttons. To enable automatic + // uploads, set the following option to true: + autoUpload: false, + // The ID of the upload template: + uploadTemplateId: 'template-upload', + // The ID of the download template: + downloadTemplateId: 'template-download', + // The container for the list of files. If undefined, it is set to + // an element with class "files" inside of the widget element: + filesContainer: undefined, + // By default, files are appended to the files container. + // Set the following option to true, to prepend files instead: + prependFiles: false, + // The expected data type of the upload response, sets the dataType + // option of the $.ajax upload requests: + dataType: 'json', + + // Error and info messages: + messages: { + unknownError: 'Unknown error' + }, + + // Function returning the current number of files, + // used by the maxNumberOfFiles validation: + getNumberOfFiles: function () { + return this.filesContainer.children() + .not('.processing').length; + }, + + // Callback to retrieve the list of files from the server response: + getFilesFromResponse: function (data) { + if (data.result && $.isArray(data.result.files)) { + return data.result.files; + } + return []; + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop or add API call). + // See the basic file upload widget for more information: + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var $this = $(this), + that = $this.data('blueimp-fileupload') || + $this.data('fileupload'), + options = that.options; + data.context = that._renderUpload(data.files) + .data('data', data) + .addClass('processing'); + options.filesContainer[ + options.prependFiles ? 'prepend' : 'append' + ](data.context); + that._forceReflow(data.context); + that._transition(data.context); + data.process(function () { + return $this.fileupload('process', data); + }).always(function () { + data.context.each(function (index) { + $(this).find('.size').text( + that._formatFileSize(data.files[index].size) + ); + }).removeClass('processing'); + that._renderPreviews(data); + }).done(function () { + data.context.find('.start').prop('disabled', false); + if ((that._trigger('added', e, data) !== false) && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false) { + data.submit(); + } + }).fail(function () { + if (data.files.error) { + data.context.each(function (index) { + var error = data.files[index].error; + if (error) { + $(this).find('.error').text(error); + } + }); + } + }); + }, + // Callback for the start of each file upload request: + send: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'); + if (data.context && data.dataType && + data.dataType.substr(0, 6) === 'iframe') { + // Iframe Transport does not support progress events. + // In lack of an indeterminate progress bar, we set + // the progress to 100%, showing the full animated bar: + data.context + .find('.progress').addClass( + !$.support.transition && 'progress-animated' + ) + .attr('aria-valuenow', 100) + .children().first().css( + 'width', + '100%' + ); + } + return that._trigger('sent', e, data); + }, + // Callback for successful uploads: + done: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + getFilesFromResponse = data.getFilesFromResponse || + that.options.getFilesFromResponse, + files = getFilesFromResponse(data), + template, + deferred; + if (data.context) { + data.context.each(function (index) { + var file = files[index] || + {error: 'Empty file upload result'}; + deferred = that._addFinishedDeferreds(); + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + ); + }); + } else { + template = that._renderDownload(files)[ + that.options.prependFiles ? 'prependTo' : 'appendTo' + ](that.options.filesContainer); + that._forceReflow(template); + deferred = that._addFinishedDeferreds(); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + }, + // Callback for failed (abort or error) uploads: + fail: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + template, + deferred; + if (data.context) { + data.context.each(function (index) { + if (data.errorThrown !== 'abort') { + var file = data.files[index]; + file.error = file.error || data.errorThrown || + data.i18n('unknownError'); + deferred = that._addFinishedDeferreds(); + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + ); + } else { + deferred = that._addFinishedDeferreds(); + that._transition($(this)).done( + function () { + $(this).remove(); + that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + }); + } else if (data.errorThrown !== 'abort') { + data.context = that._renderUpload(data.files)[ + that.options.prependFiles ? 'prependTo' : 'appendTo' + ](that.options.filesContainer) + .data('data', data); + that._forceReflow(data.context); + deferred = that._addFinishedDeferreds(); + that._transition(data.context).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } else { + that._trigger('failed', e, data); + that._trigger('finished', e, data); + that._addFinishedDeferreds().resolve(); + } + }, + // Callback for upload progress events: + progress: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var progress = Math.floor(data.loaded / data.total * 100); + if (data.context) { + data.context.each(function () { + $(this).find('.progress') + .attr('aria-valuenow', progress) + .children().first().css( + 'width', + progress + '%' + ); + }); + } + }, + // Callback for global upload progress events: + progressall: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var $this = $(this), + progress = Math.floor(data.loaded / data.total * 100), + globalProgressNode = $this.find('.fileupload-progress'), + extendedProgressNode = globalProgressNode + .find('.progress-extended'); + if (extendedProgressNode.length) { + extendedProgressNode.html( + ($this.data('blueimp-fileupload') || $this.data('fileupload')) + ._renderExtendedProgress(data) + ); + } + globalProgressNode + .find('.progress') + .attr('aria-valuenow', progress) + .children().first().css( + 'width', + progress + '%' + ); + }, + // Callback for uploads start, equivalent to the global ajaxStart event: + start: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'); + that._resetFinishedDeferreds(); + that._transition($(this).find('.fileupload-progress')).done( + function () { + that._trigger('started', e); + } + ); + }, + // Callback for uploads stop, equivalent to the global ajaxStop event: + stop: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + deferred = that._addFinishedDeferreds(); + $.when.apply($, that._getFinishedDeferreds()) + .done(function () { + that._trigger('stopped', e); + }); + that._transition($(this).find('.fileupload-progress')).done( + function () { + $(this).find('.progress') + .attr('aria-valuenow', '0') + .children().first().css('width', '0%'); + $(this).find('.progress-extended').html(' '); + deferred.resolve(); + } + ); + }, + processstart: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + $(this).addClass('fileupload-processing'); + }, + processstop: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + $(this).removeClass('fileupload-processing'); + }, + // Callback for file deletion: + destroy: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + removeNode = function () { + that._transition(data.context).done( + function () { + $(this).remove(); + that._trigger('destroyed', e, data); + } + ); + }; + if (data.url) { + data.dataType = data.dataType || that.options.dataType; + $.ajax(data).done(removeNode).fail(function () { + that._trigger('destroyfailed', e, data); + }); + } else { + removeNode(); + } + } + }, + + _resetFinishedDeferreds: function () { + this._finishedUploads = []; + }, + + _addFinishedDeferreds: function (deferred) { + if (!deferred) { + deferred = $.Deferred(); + } + this._finishedUploads.push(deferred); + return deferred; + }, + + _getFinishedDeferreds: function () { + return this._finishedUploads; + }, + + // Link handler, that allows to download files + // by drag & drop of the links to the desktop: + _enableDragToDesktop: function () { + var link = $(this), + url = link.prop('href'), + name = link.prop('download'), + type = 'application/octet-stream'; + link.bind('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [type, name, url].join(':') + ); + } catch (ignore) {} + }); + }, + + _formatFileSize: function (bytes) { + if (typeof bytes !== 'number') { + return ''; + } + if (bytes >= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; + }, + + _formatBitrate: function (bits) { + if (typeof bits !== 'number') { + return ''; + } + if (bits >= 1000000000) { + return (bits / 1000000000).toFixed(2) + ' Gbit/s'; + } + if (bits >= 1000000) { + return (bits / 1000000).toFixed(2) + ' Mbit/s'; + } + if (bits >= 1000) { + return (bits / 1000).toFixed(2) + ' kbit/s'; + } + return bits.toFixed(2) + ' bit/s'; + }, + + _formatTime: function (seconds) { + var date = new Date(seconds * 1000), + days = Math.floor(seconds / 86400); + days = days ? days + 'd ' : ''; + return days + + ('0' + date.getUTCHours()).slice(-2) + ':' + + ('0' + date.getUTCMinutes()).slice(-2) + ':' + + ('0' + date.getUTCSeconds()).slice(-2); + }, + + _formatPercentage: function (floatValue) { + return (floatValue * 100).toFixed(2) + ' %'; + }, + + _renderExtendedProgress: function (data) { + return this._formatBitrate(data.bitrate) + ' | ' + + this._formatTime( + (data.total - data.loaded) * 8 / data.bitrate + ) + ' | ' + + this._formatPercentage( + data.loaded / data.total + ) + ' | ' + + this._formatFileSize(data.loaded) + ' / ' + + this._formatFileSize(data.total); + }, + + _renderTemplate: function (func, files) { + if (!func) { + return $(); + } + var result = func({ + files: files, + formatFileSize: this._formatFileSize, + options: this.options + }); + if (result instanceof $) { + return result; + } + return $(this.options.templatesContainer).html(result).children(); + }, + + _renderPreviews: function (data) { + data.context.find('.preview').each(function (index, elm) { + $(elm).append(data.files[index].preview); + }); + }, + + _renderUpload: function (files) { + return this._renderTemplate( + this.options.uploadTemplate, + files + ); + }, + + _renderDownload: function (files) { + return this._renderTemplate( + this.options.downloadTemplate, + files + ).find('a[download]').each(this._enableDragToDesktop).end(); + }, + + _startHandler: function (e) { + e.preventDefault(); + var button = $(e.currentTarget), + template = button.closest('.template-upload'), + data = template.data('data'); + button.prop('disabled', true); + if (data && data.submit) { + data.submit(); + } + }, + + _cancelHandler: function (e) { + e.preventDefault(); + var template = $(e.currentTarget) + .closest('.template-upload,.template-download'), + data = template.data('data') || {}; + data.context = data.context || template; + if (data.abort) { + data.abort(); + } else { + data.errorThrown = 'abort'; + this._trigger('fail', e, data); + } + }, + + _deleteHandler: function (e) { + e.preventDefault(); + var button = $(e.currentTarget); + this._trigger('destroy', e, $.extend({ + context: button.closest('.template-download'), + type: 'DELETE' + }, button.data())); + }, + + _forceReflow: function (node) { + return $.support.transition && node.length && + node[0].offsetWidth; + }, + + _transition: function (node) { + var dfd = $.Deferred(); + if ($.support.transition && node.hasClass('fade') && node.is(':visible')) { + node.bind( + $.support.transition.end, + function (e) { + // Make sure we don't respond to other transitions events + // in the container element, e.g. from button elements: + if (e.target === node[0]) { + node.unbind($.support.transition.end); + dfd.resolveWith(node); + } + } + ).toggleClass('in'); + } else { + node.toggleClass('in'); + dfd.resolveWith(node); + } + return dfd; + }, + + _initButtonBarEventHandlers: function () { + var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), + filesList = this.options.filesContainer; + this._on(fileUploadButtonBar.find('.start'), { + click: function (e) { + e.preventDefault(); + filesList.find('.start').click(); + } + }); + this._on(fileUploadButtonBar.find('.cancel'), { + click: function (e) { + e.preventDefault(); + filesList.find('.cancel').click(); + } + }); + this._on(fileUploadButtonBar.find('.delete'), { + click: function (e) { + e.preventDefault(); + filesList.find('.toggle:checked') + .closest('.template-download') + .find('.delete').click(); + fileUploadButtonBar.find('.toggle') + .prop('checked', false); + } + }); + this._on(fileUploadButtonBar.find('.toggle'), { + change: function (e) { + filesList.find('.toggle').prop( + 'checked', + $(e.currentTarget).is(':checked') + ); + } + }); + }, + + _destroyButtonBarEventHandlers: function () { + this._off( + this.element.find('.fileupload-buttonbar') + .find('.start, .cancel, .delete'), + 'click' + ); + this._off( + this.element.find('.fileupload-buttonbar .toggle'), + 'change.' + ); + }, + + _initEventHandlers: function () { + this._super(); + this._on(this.options.filesContainer, { + 'click .start': this._startHandler, + 'click .cancel': this._cancelHandler, + 'click .delete': this._deleteHandler + }); + this._initButtonBarEventHandlers(); + }, + + _destroyEventHandlers: function () { + this._destroyButtonBarEventHandlers(); + this._off(this.options.filesContainer, 'click'); + this._super(); + }, + + _enableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', false) + .parent().removeClass('disabled'); + }, + + _disableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', true) + .parent().addClass('disabled'); + }, + + _initTemplates: function () { + var options = this.options; + options.templatesContainer = this.document[0].createElement( + options.filesContainer.prop('nodeName') + ); + if (tmpl) { + if (options.uploadTemplateId) { + options.uploadTemplate = tmpl(options.uploadTemplateId); + } + if (options.downloadTemplateId) { + options.downloadTemplate = tmpl(options.downloadTemplateId); + } + } + }, + + _initFilesContainer: function () { + var options = this.options; + if (options.filesContainer === undefined) { + options.filesContainer = this.element.find('.files'); + } else if (!(options.filesContainer instanceof $)) { + options.filesContainer = $(options.filesContainer); + } + }, + + _initSpecialOptions: function () { + this._super(); + this._initFilesContainer(); + this._initTemplates(); + }, + + _create: function () { + this._super(); + this._resetFinishedDeferreds(); + if (!$.support.fileInput) { + this._disableFileInputButton(); + } + }, + + enable: function () { + var wasDisabled = false; + if (this.options.disabled) { + wasDisabled = true; + } + this._super(); + if (wasDisabled) { + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); + } + }, + + disable: function () { + if (!this.options.disabled) { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + } + this._super(); + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-validate.js b/core/vendor/filemanager/js/jquery.fileupload-validate.js new file mode 100644 index 0000000..eebeb37 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-validate.js @@ -0,0 +1,125 @@ +/* + * jQuery File Upload Validation Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* global define, require, window */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./jquery.fileupload-process') + ); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + // Append to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.push( + { + action: 'validate', + // Always trigger this action, + // even if the previous action was rejected: + always: true, + // Options taken from the global options map: + acceptFileTypes: '@', + maxFileSize: '@', + minFileSize: '@', + maxNumberOfFiles: '@', + disabled: '@disableValidation' + } + ); + + // The File Upload Validation plugin extends the fileupload widget + // with file validation functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + /* + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + // The maximum allowed file size in bytes: + maxFileSize: 10000000, // 10 MB + // The minimum allowed file size in bytes: + minFileSize: undefined, // No minimal file size + // The limit of files to be uploaded: + maxNumberOfFiles: 10, + */ + + // Function returning the current number of files, + // has to be overriden for maxNumberOfFiles validation: + getNumberOfFiles: $.noop, + + // Error and info messages: + messages: { + maxNumberOfFiles: 'Maximum number of files exceeded', + acceptFileTypes: 'File type not allowed', + maxFileSize: 'File is too large', + minFileSize: 'File is too small' + } + }, + + processActions: { + + validate: function (data, options) { + if (options.disabled) { + return data; + } + var dfd = $.Deferred(), + settings = this.options, + file = data.files[data.index], + fileSize; + if (options.minFileSize || options.maxFileSize) { + fileSize = file.size; + } + if ($.type(options.maxNumberOfFiles) === 'number' && + (settings.getNumberOfFiles() || 0) + data.files.length > + options.maxNumberOfFiles) { + file.error = settings.i18n('maxNumberOfFiles'); + } else if (options.acceptFileTypes && + !(options.acceptFileTypes.test(file.type) || + options.acceptFileTypes.test(file.name))) { + file.error = settings.i18n('acceptFileTypes'); + } else if (fileSize > options.maxFileSize) { + file.error = settings.i18n('maxFileSize'); + } else if ($.type(fileSize) === 'number' && + fileSize < options.minFileSize) { + file.error = settings.i18n('minFileSize'); + } else { + delete file.error; + } + if (file.error || data.files.error) { + data.files.error = true; + dfd.rejectWith(this, [data]); + } else { + dfd.resolveWith(this, [data]); + } + return dfd.promise(); + } + + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload-video.js b/core/vendor/filemanager/js/jquery.fileupload-video.js new file mode 100644 index 0000000..aedcec2 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload-video.js @@ -0,0 +1,113 @@ +/* + * jQuery File Upload Video Preview Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('blueimp-load-image/js/load-image'), + require('./jquery.fileupload-process') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadVideo', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + disabled: '@disableVideoPreview' + }, + { + action: 'setVideo', + name: '@videoPreviewName', + disabled: '@disableVideoPreview' + } + ); + + // The File Upload Video Preview plugin extends the fileupload widget + // with video preview functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of video files to load, + // matched against the file type: + loadVideoFileTypes: /^video\/.*$/ + }, + + _videoElement: document.createElement('video'), + + processActions: { + + // Loads the video file given via data.files and data.index + // as video element if the browser supports playing it. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadVideo: function (data, options) { + if (options.disabled) { + return data; + } + var file = data.files[data.index], + url, + video; + if (this._videoElement.canPlayType && + this._videoElement.canPlayType(file.type) && + ($.type(options.maxFileSize) !== 'number' || + file.size <= options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + url = loadImage.createObjectURL(file); + if (url) { + video = this._videoElement.cloneNode(false); + video.src = url; + video.controls = true; + data.video = video; + return data; + } + } + return data; + }, + + // Sets the video element as a property of the file object: + setVideo: function (data, options) { + if (data.video && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.video; + } + return data; + } + + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.fileupload.js b/core/vendor/filemanager/js/jquery.fileupload.js new file mode 100644 index 0000000..700f901 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.fileupload.js @@ -0,0 +1,1502 @@ +/* + * jQuery File Upload Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document, location, Blob, FormData */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery-ui/ui/widget' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./vendor/jquery.ui.widget') + ); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Detect file input support, based on + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/ + $.support.fileInput = !(new RegExp( + // Handle devices which give false positives for the feature detection: + '(Android (1\\.[0156]|2\\.[01]))' + + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + + '|(w(eb)?OSBrowser)|(webOS)' + + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' + ).test(window.navigator.userAgent) || + // Feature detection for all other devices: + $('').prop('disabled')); + + // The FileReader API is not actually used, but works as feature detection, + // as some Safari versions (5?) support XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads. + // window.XMLHttpRequestUpload is not available on IE10, so we check for + // window.ProgressEvent instead to detect XHR2 file upload capability: + $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // Detect support for Blob slicing (required for chunked uploads): + $.support.blobSlice = window.Blob && (Blob.prototype.slice || + Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + + // Helper function to create drag handlers for dragover/dragenter/dragleave: + function getDragHandler(type) { + var isDragOver = type === 'dragover'; + return function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && + this._trigger( + type, + $.Event(type, {delegatedEvent: e}) + ) !== false) { + e.preventDefault(); + if (isDragOver) { + dataTransfer.dropEffect = 'copy'; + } + } + }; + } + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: + dropZone: $(document), + // The paste target element(s), by the default undefined. + // Set to a DOM node or jQuery object to enable file pasting: + pasteZone: undefined, + // The file input field(s), that are listened to for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // The following option limits the number of files uploaded with one + // XHR request to keep the request size under or equal to the defined + // limit in bytes: + limitMultiFileUploadSize: undefined, + // Multipart file uploads add a number of bytes to each uploaded file, + // therefore the following option adds an overhead for each file used + // in the limitMultiFileUploadSize configuration: + limitMultiFileUploadSizeOverhead: 512, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + // By default, uploads are started automatically when adding files: + autoUpload: true, + + // Error and info messages: + messages: { + uploadedBytes: 'Uploaded bytes exceed file size' + }, + + // Translation function, gets the message key to be translated + // and an object with context specific data as arguments: + i18n: function (message, context) { + message = this.messages[message] || message.toString(); + if (context) { + $.each(context, function (key, value) { + message = message.replace('{' + key + '}', value); + }); + } + return message; + }, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows you to override plugin options as well as define ajax settings. + // + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + if (data.autoUpload || (data.autoUpload !== false && + $(this).fileupload('option', 'autoUpload'))) { + data.process().done(function () { + data.submit(); + }); + } + }, + + // Other callbacks: + + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + + // Callback for change events of the fileInput(s): + // change: function (e, data) {}, // .bind('fileuploadchange', func); + + // Callback for paste events to the pasteZone(s): + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + + // Callback for drop events of the dropZone(s): + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + + // Callback for dragover events of the dropZone(s): + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // Callback before the start of each chunk upload request (before form data initialization): + // chunkbeforesend: function (e, data) {}, // .bind('fileuploadchunkbeforesend', func); + + // Callback for the start of each chunk upload request: + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); + + // Callback for successful chunk uploads: + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); + + // Callback for failed (abort or error) chunk uploads: + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); + + // Callback for completed (success, abort or error) chunk upload requests: + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false, + timeout: 0 + }, + + // A list of options that require reinitializing event listeners and/or + // special initialization code: + _specialOptions: [ + 'fileInput', + 'dropZone', + 'pasteZone', + 'multipart', + 'forceIframeTransport' + ], + + _blobSlice: $.support.blobSlice && function () { + var slice = this.slice || this.webkitSlice || this.mozSlice; + return slice.apply(this, arguments); + }, + + _BitrateTimer: function () { + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if ($.type(options.formData) === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if ($.type(options.formData) === 'object') { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _initProgressObject: function (obj) { + var progress = { + loaded: 0, + total: 0, + bitrate: 0 + }; + if (obj._progress) { + $.extend(obj._progress, progress); + } else { + obj._progress = progress; + } + }, + + _initResponseObject: function (obj) { + var prop; + if (obj._response) { + for (prop in obj._response) { + if (obj._response.hasOwnProperty(prop)) { + delete obj._response[prop]; + } + } + } else { + obj._response = {}; + } + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = ((Date.now) ? Date.now() : (new Date()).getTime()), + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + loaded = Math.floor( + e.loaded / e.total * (data.chunkSize || data._progress.total) + ) + (data.uploadedBytes || 0); + // Add the difference from the previously loaded state + // to the global loaded counter: + this._progress.loaded += (loaded - data._progress.loaded); + this._progress.bitrate = this._bitrateTimer.getBitrate( + now, + this._progress.loaded, + data.bitrateInterval + ); + data._progress.loaded = data.loaded = loaded; + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger( + 'progress', + $.Event('progress', {delegatedEvent: e}), + data + ); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger( + 'progressall', + $.Event('progressall', {delegatedEvent: e}), + this._progress + ); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _deinitProgressListener: function (options) { + var xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + if (xhr.upload) { + $(xhr.upload).unbind('progress'); + } + }, + + _isInstanceOf: function (type, obj) { + // Cross-frame instanceof check + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }, + + _initXHRData: function (options) { + var that = this, + formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = $.type(options.paramName) === 'array' ? + options.paramName[0] : options.paramName; + options.headers = $.extend({}, options.headers); + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; + } + if (!multipart || options.blob || !this._isInstanceOf('File', file)) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.uploadName || file.name) + '"'; + } + if (!multipart) { + options.contentType = file.type || 'application/octet-stream'; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: ($.type(options.paramName) === 'array' && + options.paramName[index]) || paramName, + value: file + }); + }); + } + } else { + if (that._isInstanceOf('FormData', options.formData)) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append( + paramName, + options.blob, + file.uploadName || file.name + ); + } else { + $.each(options.files, function (index, file) { + // This check allows the tests to run with + // dummy objects: + if (that._isInstanceOf('File', file) || + that._isInstanceOf('Blob', file)) { + formData.append( + ($.type(options.paramName) === 'array' && + options.paramName[index]) || paramName, + file, + file.uploadName || file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + var targetHost = $('').prop('href', options.url).prop('host'); + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && targetHost && targetHost !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || + ($.type(options.form.prop('method')) === 'string' && + options.form.prop('method')) || '' + ).toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { + options.type = 'POST'; + } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected() and .isResolved(): + _getDeferredState: function (deferred) { + if (deferred.state) { + return deferred.state(); + } + if (deferred.isResolved()) { + return 'resolved'; + } + if (deferred.isRejected()) { + return 'rejected'; + } + return 'pending'; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Adds convenience methods to the data callback argument: + _addConvenienceMethods: function (e, data) { + var that = this, + getPromise = function (args) { + return $.Deferred().resolveWith(that, args).promise(); + }; + data.process = function (resolveFunc, rejectFunc) { + if (resolveFunc || rejectFunc) { + data._processQueue = this._processQueue = + (this._processQueue || getPromise([this])).then( + function () { + if (data.errorThrown) { + return $.Deferred() + .rejectWith(that, [data]).promise(); + } + return getPromise(arguments); + } + ).then(resolveFunc, rejectFunc); + } + return this._processQueue || getPromise([this]); + }; + data.submit = function () { + if (this.state() !== 'pending') { + data.jqXHR = this.jqXHR = + (that._trigger( + 'submit', + $.Event('submit', {delegatedEvent: e}), + this + ) !== false) && that._onSend(e, this); + } + return this.jqXHR || that._getXHRPromise(); + }; + data.abort = function () { + if (this.jqXHR) { + return this.jqXHR.abort(); + } + this.errorThrown = 'abort'; + that._trigger('fail', null, this); + return that._getXHRPromise(false); + }; + data.state = function () { + if (this.jqXHR) { + return that._getDeferredState(this.jqXHR); + } + if (this._processQueue) { + return that._getDeferredState(this._processQueue); + } + }; + data.processing = function () { + return !this.jqXHR && this._processQueue && that + ._getDeferredState(this._processQueue) === 'pending'; + }; + data.progress = function () { + return this._progress; + }; + data.response = function () { + return this._response; + }; + }, + + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + options.uploadedBytes = options.uploadedBytes || 0; + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes, + mcs = options.maxChunkSize || fs, + slice = this._blobSlice, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + upload; + if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = options.i18n('uploadedBytes'); + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // The chunk upload method: + upload = function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options), + currentLoaded = o._progress.loaded; + o.blob = slice.call( + file, + ub, + ub + ($.type(mcs) === 'function' ? mcs(o) : mcs), + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Trigger chunkbeforesend to allow form data to be updated for this chunk + that._trigger('chunkbeforesend', null, o); + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || + that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if no final progress event + // with loaded equaling total has been triggered + // for this chunk: + if (currentLoaded + o.chunkSize - o._progress.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + o.result = result; + o.textStatus = textStatus; + o.jqXHR = jqXHR; + that._trigger('chunkdone', null, o); + that._trigger('chunkalways', null, o); + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + o.jqXHR = jqXHR; + o.textStatus = textStatus; + o.errorThrown = errorThrown; + that._trigger('chunkfail', null, o); + that._trigger('chunkalways', null, o); + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }) + .always(function () { + that._deinitProgressListener(o); + }); + }; + this._enhancePromise(promise); + promise.abort = function () { + return jqXHR.abort(); + }; + upload(); + return promise; + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + // Reset the global progress values: + this._progress.loaded = this._progress.total = 0; + this._progress.bitrate = 0; + } + // Make sure the container objects for the .response() and + // .progress() methods on the data object are available + // and reset to their initial state: + this._initResponseObject(data); + this._initProgressObject(data); + data._progress.loaded = data.loaded = data.uploadedBytes || 0; + data._progress.total = data.total = this._getTotal(data.files) || 1; + data._progress.bitrate = data.bitrate = 0; + this._active += 1; + // Initialize the global progress values: + this._progress.loaded += data.loaded; + this._progress.total += data.total; + }, + + _onDone: function (result, textStatus, jqXHR, options) { + var total = options._progress.total, + response = options._response; + if (options._progress.loaded < total) { + // Create a progress event if no final progress event + // with loaded equaling total has been triggered: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: total, + total: total + }), options); + } + response.result = options.result = result; + response.textStatus = options.textStatus = textStatus; + response.jqXHR = options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + var response = options._response; + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._progress.loaded -= options._progress.loaded; + this._progress.total -= options._progress.total; + } + response.jqXHR = options.jqXHR = jqXHR; + response.textStatus = options.textStatus = textStatus; + response.errorThrown = options.errorThrown = errorThrown; + this._trigger('fail', null, options); + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + // jqXHRorResult, textStatus and jqXHRorError are added to the + // options object via done and fail callbacks + this._trigger('always', null, options); + }, + + _onSend: function (e, data) { + if (!data.submit) { + this._addConvenienceMethods(e, data); + } + var that = this, + jqXHR, + aborted, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function () { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + ((aborted || that._trigger( + 'send', + $.Event('send', {delegatedEvent: e}), + options + ) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._deinitProgressListener(options); + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + that._sending -= 1; + that._active -= 1; + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (that._getDeferredState(nextSlot) === 'pending') { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + if (that._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + that._trigger('stop'); + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.then(send); + } else { + this._sequence = this._sequence.then(send, send); + pipe = this._sequence; + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + aborted = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(options.context, aborted); + } + return send(); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + files = data.files, + filesLength = files.length, + limit = options.limitMultiFileUploads, + limitSize = options.limitMultiFileUploadSize, + overhead = options.limitMultiFileUploadSizeOverhead, + batchSize = 0, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i, + j = 0; + if (!filesLength) { + return false; + } + if (limitSize && files[0].size === undefined) { + limitSize = undefined; + } + if (!(options.singleFileUploads || limit || limitSize) || + !this._isXHRUpload(options)) { + fileSet = [files]; + paramNameSet = [paramName]; + } else if (!(options.singleFileUploads || limitSize) && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < filesLength; i += limit) { + fileSet.push(files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else if (!options.singleFileUploads && limitSize) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < filesLength; i = i + 1) { + batchSize += files[i].size + overhead; + if (i + 1 === filesLength || + ((batchSize + files[i + 1].size + overhead) > limitSize) || + (limit && i + 1 - j >= limit)) { + fileSet.push(files.slice(j, i + 1)); + paramNameSlice = paramName.slice(j, i + 1); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + j = i + 1; + batchSize = 0; + } + } + } else { + paramNameSet = paramName; + } + data.originalFiles = files; + $.each(fileSet || files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + that._initResponseObject(newData); + that._initProgressObject(newData); + that._addConvenienceMethods(e, newData); + result = that._trigger( + 'add', + $.Event('add', {delegatedEvent: e}), + newData + ); + return result; + }); + return result; + }, + + _replaceFileInput: function (data) { + var input = data.fileInput, + inputClone = input.clone(true), + restoreFocus = input.is(document.activeElement); + // Add a reference for the new cloned file input to the data argument: + data.fileInputClone = inputClone; + $('
').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // If the fileInput had focus before it was detached, + // restore focus to the inputClone. + if (restoreFocus) { + inputClone.focus(); + } + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // elements set with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + entries = [], + dirReader, + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + successHandler = function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, + readEntries = function () { + dirReader.readEntries(function (results) { + if (!results.length) { + successHandler(entries); + } else { + entries = entries.concat(results); + readEntries(); + } + }, errorHandler); + }; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + readEntries(); + } else { + // Return an empty list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).then(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); + } + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); + } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).then(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data); + } + if (that._trigger( + 'change', + $.Event('change', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + }, + + _onPaste: function (e) { + var items = e.originalEvent && e.originalEvent.clipboardData && + e.originalEvent.clipboardData.items, + data = {files: []}; + if (items && items.length) { + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (this._trigger( + 'paste', + $.Event('paste', {delegatedEvent: e}), + data + ) !== false) { + this._onAdd(e, data); + } + } + }, + + _onDrop: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var that = this, + dataTransfer = e.dataTransfer, + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger( + 'drop', + $.Event('drop', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + } + }, + + _onDragOver: getDragHandler('dragover'), + + _onDragEnter: getDragHandler('dragenter'), + + _onDragLeave: getDragHandler('dragleave'), + + _initEventHandlers: function () { + if (this._isXHRUpload(this.options)) { + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop, + // event.preventDefault() on dragenter is required for IE10+: + dragenter: this._onDragEnter, + // dragleave is not required, but added for completeness: + dragleave: this._onDragLeave + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); + } + if ($.support.fileInput) { + this._on(this.options.fileInput, { + change: this._onChange + }); + } + }, + + _destroyEventHandlers: function () { + this._off(this.options.dropZone, 'dragenter dragleave dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); + }, + + _destroy: function () { + this._destroyEventHandlers(); + }, + + _setOption: function (key, value) { + var reinit = $.inArray(key, this._specialOptions) !== -1; + if (reinit) { + this._destroyEventHandlers(); + } + this._super(key, value); + if (reinit) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } + }, + + _getRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _isRegExpOption: function (key, value) { + return key !== 'url' && $.type(value) === 'string' && + /^\/.*\/[igm]{0,3}$/.test(value); + }, + + _initDataAttributes: function () { + var that = this, + options = this.options, + data = this.element.data(); + // Initialize options set via HTML5 data-attributes: + $.each( + this.element[0].attributes, + function (index, attr) { + var key = attr.name.toLowerCase(), + value; + if (/^data-/.test(key)) { + // Convert hyphen-ated key to camelCase: + key = key.slice(5).replace(/-[a-z]/g, function (str) { + return str.charAt(1).toUpperCase(); + }); + value = data[key]; + if (that._isRegExpOption(key, value)) { + value = that._getRegExp(value); + } + options[key] = value; + } + } + ); + }, + + _create: function () { + this._initDataAttributes(); + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = 0; + this._initProgressObject(this); + this._initEventHandlers(); + }, + + // This method is exposed to the widget API and allows to query + // the number of active uploads: + active: function () { + return this._active; + }, + + // This method is exposed to the widget API and allows to query + // the widget upload progress. + // It returns an object with loaded, total and bitrate properties + // for the running uploads: + progress: function () { + return this._progress; + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + var that = this; + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files or fileInput property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + if (!files.length) { + dfd.reject(); + return; + } + data.files = files; + jqXHR = that._onSend(null, data); + jqXHR.then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff --git a/core/vendor/filemanager/js/jquery.iframe-transport.js b/core/vendor/filemanager/js/jquery.iframe-transport.js new file mode 100644 index 0000000..8d25c46 --- /dev/null +++ b/core/vendor/filemanager/js/jquery.iframe-transport.js @@ -0,0 +1,224 @@ +/* + * jQuery Iframe Transport Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + */ + +/* global define, require, window, document, JSON */ + +;(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0, + jsonAPI = $, + jsonParse = 'parseJSON'; + + if ('JSON' in window && 'parse' in JSON) { + jsonAPI = JSON; + jsonParse = 'parse'; + } + + // The iframe transport accepts four additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + // options.initialIframeSrc: the URL of the initial iframe src, + // by default set to "javascript:false;" + $.ajaxTransport('iframe', function (options) { + if (options.async) { + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6: + /*jshint scripturl: true */ + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', + /*jshint scripturl: false */ + form, + iframe, + addParamChar; + return { + send: function (_, completeCallback) { + form = $('
'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + counter += 1; + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + // Remove the HTML5 form attribute from the input(s): + options.fileInput.removeAttr('form'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + // Restore the original name and form properties: + $(input) + .prop('name', clone.prop('name')) + .attr('form', clone.attr('form')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', initialIframeSrc); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return iframe && $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return iframe && jsonAPI[jsonParse]($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); + }, + 'iframe script': function (iframe) { + return iframe && $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff --git a/core/vendor/filemanager/js/modernizr.custom.js b/core/vendor/filemanager/js/modernizr.custom.js new file mode 100644 index 0000000..2ec5cd3 --- /dev/null +++ b/core/vendor/filemanager/js/modernizr.custom.js @@ -0,0 +1,4 @@ +/* Modernizr 2.7.1 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-flexboxlegacy-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-shiv-cssclasses-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load + */ +;window.Modernizr=function(a,b,c){function B(a){j.cssText=a}function C(a,b){return B(n.join(a+";")+(b||""))}function D(a,b){return typeof a===b}function E(a,b){return!!~(""+a).indexOf(b)}function F(a,b){for(var d in a){var e=a[d];if(!E(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function G(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:D(f,"function")?f.bind(d||b):f}return!1}function H(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return D(b,"string")||D(b,"undefined")?F(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),G(e,b,c))}function I(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},y=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=D(e[d],"function"),D(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),z={}.hasOwnProperty,A;!D(z,"undefined")&&!D(z.call,"undefined")?A=function(a,b){return z.call(a,b)}:A=function(a,b){return b in a&&D(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=v.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(v.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(v.call(arguments)))};return e}),r.flexbox=function(){return H("flexWrap")},r.flexboxlegacy=function(){return H("boxDirection")},r.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},r.canvastext=function(){return!!e.canvas&&!!D(b.createElement("canvas").getContext("2d").fillText,"function")},r.postmessage=function(){return!!a.postMessage},r.websqldatabase=function(){return!!a.openDatabase},r.indexedDB=function(){return!!H("indexedDB",a)},r.hashchange=function(){return y("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},r.history=function(){return!!a.history&&!!history.pushState},r.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},r.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},r.rgba=function(){return B("background-color:rgba(150,255,150,.5)"),E(j.backgroundColor,"rgba")},r.hsla=function(){return B("background-color:hsla(120,40%,100%,.5)"),E(j.backgroundColor,"rgba")||E(j.backgroundColor,"hsla")},r.multiplebgs=function(){return B("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},r.backgroundsize=function(){return H("backgroundSize")},r.borderimage=function(){return H("borderImage")},r.borderradius=function(){return H("borderRadius")},r.boxshadow=function(){return H("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){return C("opacity:.55"),/^0.55$/.test(j.opacity)},r.cssanimations=function(){return H("animationName")},r.csscolumns=function(){return H("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return B((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),E(j.backgroundImage,"gradient")},r.cssreflections=function(){return H("boxReflect")},r.csstransforms=function(){return!!H("transform")},r.csstransforms3d=function(){var a=!!H("perspective");return a&&"webkitPerspective"in g.style&&x("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},r.csstransitions=function(){return H("transition")},r.fontface=function(){var a;return x('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},r.generatedcontent=function(){var a;return x(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},r.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},r.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},r.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},r.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},r.webworkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache};for(var J in r)A(r,J)&&(w=J.toLowerCase(),e[w]=r[J](),u.push((e[w]?"":"no-")+w));return e.input||I(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)A(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},B(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.hasEvent=y,e.testProp=function(a){return F([a])},e.testAllProps=H,e.testStyles=x,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+u.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f=n-r?"bottom":null!=s&&o<=s&&"top"),this.affixed!==t&&(this.affixed=t,this.unpin="bottom"==t?i.top-o:null,this.$element.removeClass(c).addClass("affix"+(t?"-"+t:"")))}};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var o=e(this),i=o.data("affix"),a="object"==typeof n&&n;i||o.data("affix",i=new t(this,a)),"string"==typeof n&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery),!function(e){"use strict";function t(){e(".dropdown-backdrop").remove(),e(o).each(function(){n(e(this)).removeClass("open")})}function n(t){var n,o=t.attr("data-target");return o||(o=t.attr("href"),o=o&&/#/.test(o)&&o.replace(/.*(?=#[^\s]*$)/,"")),n=o&&e(o),n&&n.length||(n=t.parent()),n}var o="[data-toggle=dropdown]",i=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};i.prototype={constructor:i,toggle:function(o){var i,a,r=e(this);if(!r.is(".disabled, :disabled"))return i=n(r),a=i.hasClass("open"),t(),a||("ontouchstart"in document.documentElement&&e(' +
+

Vous n'avez pas accès à la création d'évènements, connectez-vous.

+
+ + + + +
Version n° + +
\ No newline at end of file diff --git a/module/agenda/view/edition/edition.css b/module/agenda/view/edition/edition.css new file mode 100644 index 0000000..929b036 --- /dev/null +++ b/module/agenda/view/edition/edition.css @@ -0,0 +1,12 @@ +/** NE PAS EFFACER +* admin.css +*/ +.button, +button[id="edition_supprimer"]{ + background-color : rgba(255,0,0,0.7); +} + +.button:hover, +button[id="edition_supprimer"]:hover{ + background-color : rgba(255,0,0,1); +} diff --git a/module/agenda/view/edition/edition.php b/module/agenda/view/edition/edition.php new file mode 100644 index 0000000..327b15a --- /dev/null +++ b/module/agenda/view/edition/edition.php @@ -0,0 +1,145 @@ + + +getUser('group') >= $module::$evenement['groupe_mod'] ){ + echo ''; + echo ''; + if( $this->getUser('group') >= 2){ + echo ''; + } + else{ + echo ''; + } + echo ''; +} +?> + + + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . $this->getUrl(0), + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+getUser('group') >= $module::$evenement['groupe_mod'] ){?> +
+ 'buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/deleteEvent/' .$module::$evenement['id'], + 'value' => 'Supprimer', + 'ico' => 'cancel' + ]); ?> +
+
+ 'check' + ]); ?> +
+ +
+ +
+

+ +
'.$module::$evenement['texte'].'
';} + else{ + ?> +
+
+ 'Evènement', + 'class' => 'editorWysiwyg', + 'value' => $module::$evenement['texte'] + ]); ?> +
+
+ + +
+
+ 'Date de début', + 'label' => 'Date de début', + 'disabled' => $readonly, + 'value' => $module::$evenement['datedebut'], + 'vendor' => 'flatpickr' + ]); ?> +
+ +
+ 'Date de fin', + 'label' => 'Date de fin', + 'disabled' => $readonly, + 'value' => $module::$evenement['datefin'], + 'vendor' => 'flatpickr' + ]); ?> +
+
+ +
+ +
+ 'Choix de la catégorie d\'évènement.', + 'label' => 'Catégorie d\'évènement', + 'selected' => $module::$evenement['categorie'] + ]); ?> +
+ +
+ 'Choix de la couleur du bandeau dans lequel le texte apparaît.', + 'label' => 'Couleur de fond', + 'disabled' => $readonly, + 'selected' => $module::$evenement['couleurfond'] + ]); ?> +
+
+ 'Choix de la couleur du texte.', + 'label' => 'Couleur du texte', + 'disabled' => $readonly, + 'selected' => $module::$evenement['couleurtexte'] + ]); ?> +
+ + + +
+ +
+
+ 'Choix du groupe minimal qui pourra voir et lire cet évènement', + 'label' => 'Accès en lecture', + 'disabled' => $readonly, + 'selected' => $module::$evenement['groupe_lire'] + ]); ?> +
+
+ 'Choix du groupe minimal qui pourra modifier ou supprimer cet évènement', + 'label' => 'Accès en modification', + 'disabled' => $readonly, + 'selected' => $module::$evenement['groupe_mod'] + ]); ?> +
+
+
+ + +
Version n° + +
\ No newline at end of file diff --git a/module/agenda/view/index/index.css b/module/agenda/view/index/index.css new file mode 100644 index 0000000..6284707 --- /dev/null +++ b/module/agenda/view/index/index.css @@ -0,0 +1,138 @@ +/* Personnalisation des couleurs de l'agenda associer avec votre thme*/ + +/**/ +/*Bordures */ +/**/ + +.fc-unthemed th, +.fc-unthemed td, +.fc-unthemed thead, +.fc-unthemed tbody, +.fc-unthemed .fc-divider, +.fc-unthemed .fc-row, +.fc-unthemed .fc-content, +.fc-unthemed .fc-popover, +.fc-unthemed .fc-list-view, +.fc-unthemed .fc-list-heading td, +.fc .fc-row .fc-content-skeleton td { + border-color: #ddd; +} + +/**/ +/* Background */ +/**/ + +/* Fond du bandeau boutons de l'agenda, comment pour conserver la couleur du thme*/ +/* +.fc-toolbar { + background-color : #ffffff; +} +*/ + +/* Fond des cellules de la grille, comment pour conserver la couleur du thme */ +/* +.fc-day { + background-color : #ffffff; +} +*/ + +/* Fond de la cellule slectionne (clic) */ +.fc-highlight { + background: #ffff54; + opacity: 0.3; +} + +/* Fond de la cellule aujourd'hui */ +.fc-unthemed td .fc-today{ + background-color : #F6F7F8; +} + +/* Fond du bandeau suprieur des jours, comment pour conserver la couleur du thme*/ + +.fc th { + background-color : #F6F7F8; + +} + + +/**/ +/* Textes */ +/**/ + +/* Couleur de la valeur des jours, comment pour conserver la couleur du thme*/ +/*table td { + color: rgba(33, 34, 35, 1); +}*/ + +/* Opacit pour les jours du mois pass ou futur*/ +.fc-day-top.fc-other-month{ + opacity: 0.3; +} + + +/* Nom des jours dans la ligne suprieure, comment pour conserver la couleur du thme*/ +table th{ + color: rgba(33, 34, 35, 1); + /*font-weight: normal;*/ + font-size: 1em; +} + +/* font-size des jours dans la ligne suprieure et du mois dans le titre en petit cran */ +@media (max-width: 768px) { + .fc-center h2{ + font-size: 1.2em; + text-align: center; + } + table th{ + font-size: 8px; + } + .fc-button { + font-size: 10px !important; + } +} + +/**/ +/* Boutons */ +/**/ + +/* Couleurs bouton et texte non actif*/ +.fc-button-primary { + color: #fff; + background-color: #2C3E50; + border-color: #2C3E50; +} + +/* Couleurs bouton et texte (non actif) au survol */ +.fc-button-primary:hover { + color: #fff; + background-color: #1a252f; + border-color: #1a252f; +} + +/* Contour des boutons 'Aujourd'hui' et dfilement aprs slection*/ +.fc-button-primary:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); +} + +/* Bouton 'Aujourd'hui' quand ce jour est affich dans la grille */ +.fc-today-button.fc-button.fc-button-primary:disabled { +/* color: #0000ff; /* color et background-color inoprant cause de !important sur ces paramtres pour button::disabled dans common.css*/ +/* background-color: #ff0000; */ + border-color: #ff0000; +} + +/* Bouton mois ou semaine actif (slectionn) */ +.fc-button-primary:not(:disabled):active, +.fc-button-primary:not(:disabled).fc-button-active { + color: #fff; + background-color: #151e27; + border-color: #151e27; +} + +/* Contour des boutons mois et semaine*/ +.fc-button-primary:not(:disabled):active:focus, +.fc-button-primary:not(:disabled).fc-button-active:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); +} diff --git a/module/agenda/view/index/index.js.php b/module/agenda/view/index/index.js.php new file mode 100644 index 0000000..a62f1e6 --- /dev/null +++ b/module/agenda/view/index/index.js.php @@ -0,0 +1,116 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + * + * Module agenda développé par Sylvain Lelièvre + * Utilise le package Fullcalendar + * FullCalendar Core Package v4.3.1 + * Docs & License: https://fullcalendar.io/ + * (c) 2019 Adam Shaw + **/ + + $(document).ready(function() { + + //Fullcalendar : instanciation, initialisations + var calendarEl = document.getElementById('calendar'); + var calendar = new FullCalendar.Calendar(calendarEl, { + header: { + left: 'dayGridMonth,dayGridWeek', + center: 'title', + right: 'today,prev,next' + }, + titleFormat: { + month: 'long', + year: 'numeric' + }, + columnHeaderFormat: { + weekday: 'long' + }, + views: { + dayGridWeek: { + titleFormat: { month: 'long', day: '2-digit' } + } + }, + plugins: [ 'dayGrid', 'interaction' ], + locale : 'fr', + defaultView: 'getData(['module', $this->getUrl(0), 'vue', 'vueagenda']) ;?>', + defaultDate: 'getData(['module', $this->getUrl(0), 'vue', 'debagenda']) ;?>', + selectable: true, + editable: true, + //afficher les évènements à partir d'un fichier JSON + events : 'getUrl(0); ?>'+'_affiche/events.json', + //créer un évènement + dateClick: function(info) { + window.open('getUrl(0); ?>'+ '/da:'+ info.dateStr + 'vue:' + info.view.type + 'deb:' + calendar.formatIso(info.view.currentStart),'_self'); + }, + //Lire, modifier, supprimer un évènement + eventClick: function(info) { + window.open('getUrl(0); ?>'+'/id:'+ info.event.id + 'vue:' + info.view.type + 'deb:' + calendar.formatIso(info.view.currentStart),'_self'); + } + }); + + //Déclaration de la fonction wrapper pour déterminer la largeur du div qui contient l'agenda et le bouton gérer : index_wrapper + $.wrapper = function(){ + // Adaptation de la largeur du wrapper en fonction de la largeur de la page client et de la largeur du site + // 10000 pour la sélection 100% + if(maxwidth != 10000){ + var wclient = document.body.clientWidth, + largeur_pour_cent, + largeur, + largeur_section, + wsection = getComputedStyle(site).width, + wcalcul; + switch (wsection) + { + case '750px': + largeur_section = 750; + break; + case '960px': + largeur_section = 960; + break; + case '1170px': + largeur_section = 1170; + break; + default: + largeur_section = wclient; + } + + // 20 pour les margin du body / html, 40 pour le padding intérieur dans section + if(wclient > largeur_section + 20) {wcalcul = largeur_section-40} else {wcalcul = wclient-40}; + largeur_pour_cent = Math.floor(100*(maxwidth/wcalcul)); + if(largeur_pour_cent > 100) { largeur_pour_cent=100;} + largeur=largeur_pour_cent.toString() + "%"; + $("#index_wrapper").css('width',largeur); + } + else + { + $("#index_wrapper").css('width',"100%"); + } + //La taille du wrapper étant défini on peut l'afficher + $("#index_wrapper").css('visibility', "visible"); + }; + + $.wrapper(); + calendar.render(); + + $(window).resize(function(){ + $.wrapper(); + calendar.render(); + }); +}); + + + + + \ No newline at end of file diff --git a/module/agenda/view/index/index.php b/module/agenda/view/index/index.php new file mode 100644 index 0000000..ccc8272 --- /dev/null +++ b/module/agenda/view/index/index.php @@ -0,0 +1,17 @@ + + +
+ +
+
+
+ + + + +
+ + diff --git a/module/agenda/view/vuesimple/vuesimple.css b/module/agenda/view/vuesimple/vuesimple.css new file mode 100644 index 0000000..f0be081 --- /dev/null +++ b/module/agenda/view/vuesimple/vuesimple.css @@ -0,0 +1,18 @@ +/** NE PAS EFFACER +* theme.css +*/ + +/* Commentez ces lignes pour conserver les valeurs du thme*/ + +/* Couleurs du bouton retour*/ +.button { + background-color: rgba(176,176,176); + color: white; +} +.button:hover { + background-color: rgba(192,192,192); +} +/* Alignement du texte dans l'entte de la zone d'affichage*/ +.block h4 { + text-align: center; +} \ No newline at end of file diff --git a/module/agenda/view/vuesimple/vuesimple.php b/module/agenda/view/vuesimple/vuesimple.php new file mode 100644 index 0000000..ac4ebf6 --- /dev/null +++ b/module/agenda/view/vuesimple/vuesimple.php @@ -0,0 +1,36 @@ +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . $this->getUrl(0), + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ +
+
+
+

Début : - Fin :

+

+ +
+
+
+ + + + + + + + diff --git a/module/blog/blog.php b/module/blog/blog.php new file mode 100644 index 0000000..900b8cc --- /dev/null +++ b/module/blog/blog.php @@ -0,0 +1,801 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class blog extends common { + + const VERSION = '5.1'; + const REALNAME = 'Blog'; + const DELETE = true; + const UPDATE = '0.0'; + const DATADIRECTORY = ''; // Contenu localisé inclus par défaut (page.json et module.json) + + const EDIT_OWNER = 'owner'; + const EDIT_GROUP = 'group'; + const EDIT_ALL = 'all'; + + public static $actions = [ + 'add' => self::GROUP_MODERATOR, + 'comment' => self::GROUP_MODERATOR, + 'commentApprove' => self::GROUP_MODERATOR, + 'commentDelete' => self::GROUP_MODERATOR, + 'commentDeleteAll' => self::GROUP_MODERATOR, + 'config' => self::GROUP_MODERATOR, + 'delete' => self::GROUP_MODERATOR, + 'edit' => self::GROUP_MODERATOR, + 'index' => self::GROUP_VISITOR, + 'rss' => self::GROUP_VISITOR + ]; + + public static $articles = []; + + // Signature de l'article + public static $articleSignature = ''; + + // Signature du commentaire + public static $editCommentSignature = ''; + + public static $comments = []; + + public static $nbCommentsApproved = 0; + + public static $commentsDelete; + + // Signatures des commentaires déjà saisis + public static $commentsSignature = []; + + public static $pages; + + public static $states = [ + false => 'Brouillon', + true => 'Publié' + ]; + + public static $pictureSizes = [ + '20' => 'Très petite', + '30' => 'Petite', + '40' => 'Grande', + '50' => 'Très Grande', + '100' => 'Pleine largeur', + ]; + + public static $picturePositions = [ + 'left' => 'À gauche', + 'right' => 'À droite ', + ]; + + // Nombre d'objets par page + public static $ItemsList = [ + 4 => '4 articles', + 8 => '8 articles', + 12 => '12 articles', + 16 => '16 articles', + 22 => '22 articles' + ]; + + //Paramètre longueur maximale des commentaires en nb de caractères + public static $commentLength = [ + '500' => '500', + '1000' => '1000', + '2000' => '2000', + '5000' => '5000', + '10000' => '10000' + ]; + + // Permissions d'un article + public static $articleConsent = [ + self::EDIT_ALL => 'Tous les groupes', + self::EDIT_GROUP => 'Groupe du propriétaire', + self::EDIT_OWNER => 'Propriétaire' + ]; + + + public static $users = []; + + + + /** + * Mise à jour du module + * Appelée par les fonctions index et config + */ + private function update() { + // Version 5.0 + if (version_compare($this->getData(['module', $this->getUrl(0), 'config', 'versionData']), '5.0', '<') ) { + $this->setData(['module', $this->getUrl(0), 'config', 'itemsperPage', 6]); + $this->setData(['module', $this->getUrl(0), 'config', 'versionData','5.0']); + } + } + + + + /** + * Flux RSS + */ + public function rss() { + // Inclure les classes + include_once 'module/blog/vendor/FeedWriter/Item.php'; + include_once 'module/blog/vendor/FeedWriter/Feed.php'; + include_once 'module/blog/vendor/FeedWriter/RSS2.php'; + include_once 'module/blog/vendor/FeedWriter/InvalidOperationException.php'; + + date_default_timezone_set('UTC'); + $feeds = new \FeedWriter\RSS2(); + + // En-tête + $feeds->setTitle($this->getData (['page', $this->getUrl(0), 'title'])); + $feeds->setLink(helper::baseUrl() . $this->getUrl(0)); + $feeds->setDescription($this->getData (['page', $this->getUrl(0), 'metaDescription'])); + $feeds->setChannelElement('language', 'fr-FR'); + $feeds->setDate(date('r',time())); + $feeds->addGenerator(); + // Corps des articles + $articleIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'publishedOn', 'SORT_DESC'); + $articleIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0),'posts']), 'state', 'SORT_DESC'); + foreach( $articleIdsPublishedOns as $articleId => $articlePublishedOn ) { + if( $articlePublishedOn <= time() AND $articleIdsStates[$articleId] ) { + // Miniature + $parts = explode('/',$this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'picture'])); + $thumb = str_replace ($parts[(count($parts)-1)],'mini_' . $parts[(count($parts)-1)], $this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'picture'])); + // Créer les articles du flux + $newsArticle = $feeds->createNewItem(); + // Signature de l'article + $author = $this->signature($this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'userId'])); + $newsArticle->addElementArray([ + 'title' => $this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'title']), + 'link' => helper::baseUrl() .$this->getUrl(0) . '/' . $articleId, + 'description' => '' . $this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'title'])
+									 . '' . + $this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'content']), + ]); + $newsArticle->setAuthor($author,'no@mail.com'); + $newsArticle->setId(helper::baseUrl() .$this->getUrl(0) . '/' . $articleId); + $newsArticle->setDate(date('r', $this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'publishedOn']))); + if ( file_exists($this->getData(['module', $this->getUrl(0), 'posts', $articleId, 'picture'])) ) { + $imageData = getimagesize(helper::baseUrl(false) . self::FILE_DIR . 'thumb/' . $thumb); + $newsArticle->addEnclosure( helper::baseUrl(false) . self::FILE_DIR . 'thumb/' . $thumb, + $imageData[0] * $imageData[1], + $imageData['mime'] + ); + } + $feeds->addItem($newsArticle); + } + } + + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_RSS, + 'content' => $feeds->generateFeed(), + 'view' => 'rss' + ]); + } + + /** + * Édition + */ + public function add() { + // Soumission du formulaire + if($this->isPost()) { + // Modification de l'userId + if($this->getUser('group') === self::GROUP_ADMIN){ + $newuserid = $this->getInput('blogAddUserId', helper::FILTER_STRING_SHORT, true); + } + else{ + $newuserid = $this->getUser('id'); + } + // Incrémente l'id de l'article + $articleId = helper::increment($this->getInput('blogAddTitle', helper::FILTER_ID), $this->getData(['page'])); + $articleId = helper::increment($articleId, (array) $this->getData(['module', $this->getUrl(0)])); + $articleId = helper::increment($articleId, array_keys(self::$actions)); + // Crée l'article + $this->setData(['module', + $this->getUrl(0), + 'posts', + $articleId, [ + 'comment' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment']), + 'content' => $this->getInput('blogAddContent', null), + 'picture' => $this->getInput('blogAddPicture', helper::FILTER_STRING_SHORT, true), + 'hidePicture' => $this->getInput('blogAddHidePicture', helper::FILTER_BOOLEAN), + 'pictureSize' => $this->getInput('blogAddPictureSize', helper::FILTER_STRING_SHORT), + 'picturePosition' => $this->getInput('blogAddPicturePosition', helper::FILTER_STRING_SHORT), + 'publishedOn' => $this->getInput('blogAddPublishedOn', helper::FILTER_DATETIME, true), + 'state' => $this->getInput('blogAddState', helper::FILTER_BOOLEAN), + 'title' => $this->getInput('blogAddTitle', helper::FILTER_STRING_SHORT, true), + 'userId' => $newuserid, + 'editConsent' => $this->getInput('blogAddConsent') === self::EDIT_GROUP ? $this->getUser('group') : $this->getInput('blogAddConsent'), + 'commentMaxlength' => $this->getInput('blogAddCommentMaxlength'), + 'commentApproved' => $this->getInput('blogAddCommentApproved', helper::FILTER_BOOLEAN), + 'commentClose' => $this->getInput('blogAddCommentClose', helper::FILTER_BOOLEAN), + 'commentNotification' => $this->getInput('blogAddCommentNotification', helper::FILTER_BOOLEAN), + 'commentGroupNotification' => $this->getInput('blogAddCommentGroupNotification', helper::FILTER_INT), + ] + ]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Nouvel article créé', + 'state' => true + ]); + } + // Liste des utilisateurs + self::$users = helper::arrayCollumn($this->getData(['user']), 'firstname'); + ksort(self::$users); + foreach(self::$users as $userId => &$userFirstname) { + $userFirstname = $userFirstname . ' ' . $this->getData(['user', $userId, 'lastname']); + } + unset($userFirstname); + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Nouvel article', + 'vendor' => [ + 'flatpickr', + 'tinymce' + ], + 'view' => 'add' + ]); + } + + /** + * Liste des commentaires + */ + public function comment() { + $comments = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2),'comment']); + self::$commentsDelete = template::button('blogCommentDeleteAll', [ + 'class' => 'blogCommentDeleteAll buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentDeleteAll/' . $this->getUrl(2).'/' . $_SESSION['csrf'] , + 'ico' => 'cancel', + 'value' => 'Tout effacer' + ]); + // Ids des commentaires par ordre de création + $commentIds = array_keys(helper::arrayCollumn($comments, 'createdOn', 'SORT_DESC')); + // Pagination + $pagination = helper::pagination($commentIds, $this->getUrl(),$this->getData(['module', $this->getUrl(0), 'config', 'itemsperPage']) ); + // Liste des pages + self::$pages = $pagination['pages']; + // Commentaires en fonction de la pagination + for($i = $pagination['first']; $i < $pagination['last']; $i++) { + // Met en forme le tableau + $comment = $comments[$commentIds[$i]]; + // Bouton d'approbation + $buttonApproval = ''; + // Compatibilité avec les commentaires des versions précédentes, les valider + $comment['approval'] = array_key_exists('approval', $comment) === false ? true : $comment['approval'] ; + if ( $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2),'commentApproved']) === true) { + $buttonApproval = template::button('blogCommentApproved' . $commentIds[$i], [ + 'class' => $comment['approval'] === true ? 'blogCommentRejected buttonGreen' : 'blogCommentApproved buttonRed' , + 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentApprove/' . $this->getUrl(2) . '/' . $commentIds[$i] . '/' . $_SESSION['csrf'] , + 'value' => $comment['approval'] === true ? 'A' : 'R' + ]); + } + self::$comments[] = [ + mb_detect_encoding(strftime('%d %B %Y - %H:%M', $comment['createdOn']), 'UTF-8', true) + ? strftime('%d %B %Y - %H:%M', $comment['createdOn']) + : utf8_encode(strftime('%d %B %Y - %H:%M', $comment['createdOn'])), + $comment['content'], + $comment['userId'] ? $this->getData(['user', $comment['userId'], 'firstname']) . ' ' . $this->getData(['user', $comment['userId'], 'lastname']) : $comment['author'], + $buttonApproval, + template::button('blogCommentDelete' . $commentIds[$i], [ + 'class' => 'blogCommentDelete buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentDelete/' . $this->getUrl(2) . '/' . $commentIds[$i] . '/' . $_SESSION['csrf'] , + 'value' => template::ico('cancel') + ]) + ]; + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Gestion des commentaires : '. $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']), + 'view' => 'comment' + ]); + } + + /** + * Suppression de commentaire + */ + public function commentDelete() { + // Le commentaire n'existe pas + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Jeton incorrect + elseif ($this->getUrl(4) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Suppression + else { + $this->deleteData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3)]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment/'.$this->getUrl(2), + 'notification' => 'Commentaire supprimé', + 'state' => true + ]); + } + } + + /** + * Suppression de tous les commentaires de l'article $this->getUrl(2) + */ + public function commentDeleteAll() { + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Suppression + else { + $this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment',[] ]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment', + 'notification' => 'Commentaires supprimés', + 'state' => true + ]); + } + } + + /** + * Approbation oou désapprobation de commentaire + */ + public function commentApprove() { + // Le commentaire n'existe pas + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Jeton incorrect + elseif ($this->getUrl(4) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Inversion du statut + else { + $approved = !$this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'approval']) ; + $this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), [ + 'author' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'author']), + 'content' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'content']), + 'createdOn' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'createdOn']), + 'userId' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment', $this->getUrl(3), 'userId']), + 'approval' => $approved + ]]); + + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment/'.$this->getUrl(2), + 'notification' => $approved ? 'Commentaire approuvé' : 'Commentaire rejeté', + 'state' => $approved + ]); + } + } + + /** + * Configuration + */ + public function config() { + // Mise à jour des données de module + $this->update(); + // Soumission du formulaire + if($this->isPost()) { + $this->setData(['module', $this->getUrl(0), 'config',[ + 'feeds' => $this->getInput('blogConfigShowFeeds',helper::FILTER_BOOLEAN), + 'feedsLabel' => $this->getInput('blogConfigFeedslabel',helper::FILTER_STRING_SHORT), + 'itemsperPage' => $this->getInput('blogConfigItemsperPage', helper::FILTER_INT,true), + 'versionData' => $this->getData(['module', $this->getUrl(0), 'config', 'versionData']) + ]]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Modifications enregistrées', + 'state' => true + ]); + } else { + // Ids des articles par ordre de publication + $articleIds = array_keys(helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'publishedOn', 'SORT_DESC')); + // Gestion des droits d'accès + $filterData=[]; + foreach ($articleIds as $key => $value) { + if ( + ( // Propriétaire + $this->getData(['module', $this->getUrl(0), 'posts', $value,'editConsent']) === self::EDIT_OWNER + AND ( $this->getData(['module', $this->getUrl(0), 'posts', $value,'userId']) === $this->getUser('id') + OR $this->getUser('group') === self::GROUP_ADMIN ) + ) + + OR ( + // Groupe + $this->getData(['module', $this->getUrl(0), 'posts', $value,'editConsent']) !== self::EDIT_OWNER + AND $this->getUser('group') >= $this->getData(['module',$this->getUrl(0), 'posts', $value,'editConsent']) + ) + OR ( + // Tout le monde + $this->getData(['module', $this->getUrl(0), 'posts', $value,'editConsent']) === self::EDIT_ALL + ) + ) { + $filterData[] = $value; + } + } + $articleIds = $filterData; + // Pagination + $pagination = helper::pagination($articleIds, $this->getUrl(),$this->getData(['module', $this->getUrl(0),'config', 'itemsperPage'])); + // Liste des pages + self::$pages = $pagination['pages']; + // Articles en fonction de la pagination + for($i = $pagination['first']; $i < $pagination['last']; $i++) { + // Nombre de commentaires à approuver et approuvés + $approvals = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'comment' ]),'approval', 'SORT_DESC'); + if ( is_array($approvals) ) { + $a = array_values($approvals); + $toApprove = count(array_keys($a,false)); + $approved = count(array_keys($a,true)); + } else { + $toApprove = 0; + $approved = count($this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i],'comment'])); + } + // Met en forme le tableau + $date = mb_detect_encoding(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])), 'UTF-8', true) + ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])) + : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn']))); + $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])), 'UTF-8', true) + ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn'])) + : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'publishedOn']))); + self::$articles[] = [ + '' . + $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'title']) . + '', + $date .' à '. $heure, + self::$states[$this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i], 'state'])], + // Bouton pour afficher les commentaires de l'article + template::button('blogConfigComment' . $articleIds[$i], [ + 'class' => ($toApprove || $approved ) > 0 ? '' : 'buttonGrey' , + 'href' => ($toApprove || $approved ) > 0 ? helper::baseUrl() . $this->getUrl(0) . '/comment/' . $articleIds[$i] : '', + 'value' => $toApprove > 0 ? $toApprove . '/' . $approved : $approved + ]), + template::button('blogConfigEdit' . $articleIds[$i], [ + 'href' => helper::baseUrl() . $this->getUrl(0) . '/edit/' . $articleIds[$i] . '/' . $_SESSION['csrf'], + 'value' => template::ico('pencil') + ]), + template::button('blogConfigDelete' . $articleIds[$i], [ + 'class' => 'blogConfigDelete buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $articleIds[$i] . '/' . $_SESSION['csrf'], + 'value' => template::ico('cancel') + ]) + ]; + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration du module', + 'view' => 'config' + ]); + } + } + + /** + * Suppression + */ + public function delete() { + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Jeton incorrect + elseif ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Suppression + else { + $this->deleteData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Article supprimé', + 'state' => true + ]); + } + } + + /** + * Édition + */ + public function edit() { + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // L'article n'existe pas + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // L'article existe + else { + // Soumission du formulaire + if($this->isPost()) { + if($this->getUser('group') === self::GROUP_ADMIN){ + $newuserid = $this->getInput('blogEditUserId', helper::FILTER_STRING_SHORT, true); + } + else{ + $newuserid = $this->getUser('id'); + } + $articleId = $this->getInput('blogEditTitle', helper::FILTER_ID, true); + // Incrémente le nouvel id de l'article + if($articleId !== $this->getUrl(2)) { + $articleId = helper::increment($articleId, $this->getData(['page'])); + $articleId = helper::increment($articleId, $this->getData(['module', $this->getUrl(0),'posts'])); + $articleId = helper::increment($articleId, array_keys(self::$actions)); + } + $this->setData(['module', + $this->getUrl(0), + 'posts', + $articleId, [ + 'comment' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'comment']), + 'content' => $this->getInput('blogEditContent', null), + 'picture' => $this->getInput('blogEditPicture', helper::FILTER_STRING_SHORT, true), + 'hidePicture' => $this->getInput('blogEditHidePicture', helper::FILTER_BOOLEAN), + 'pictureSize' => $this->getInput('blogEditPictureSize', helper::FILTER_STRING_SHORT), + 'picturePosition' => $this->getInput('blogEditPicturePosition', helper::FILTER_STRING_SHORT), + 'publishedOn' => $this->getInput('blogEditPublishedOn', helper::FILTER_DATETIME, true), + 'state' => $this->getInput('blogEditState', helper::FILTER_BOOLEAN), + 'title' => $this->getInput('blogEditTitle', helper::FILTER_STRING_SHORT, true), + 'userId' => $newuserid, + 'editConsent' => $this->getInput('blogEditConsent') === self::EDIT_GROUP ? $this->getUser('group') : $this->getInput('blogEditConsent'), + 'commentMaxlength' => $this->getInput('blogEditCommentMaxlength'), + 'commentApproved' => $this->getInput('blogEditCommentApproved', helper::FILTER_BOOLEAN), + 'commentClose' => $this->getInput('blogEditCommentClose', helper::FILTER_BOOLEAN), + 'commentNotification' => $this->getInput('blogEditCommentNotification', helper::FILTER_BOOLEAN), + 'commentGroupNotification' => $this->getInput('blogEditCommentGroupNotification', helper::FILTER_INT) + ] + ]); + // Supprime l'ancien article + if($articleId !== $this->getUrl(2)) { + $this->deleteData(['module', $this->getUrl(0), 'posts', $this->getUrl(2)]); + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Modifications enregistrées', + 'state' => true + ]); + } + // Liste des utilisateurs + self::$users = helper::arrayCollumn($this->getData(['user']), 'firstname'); + ksort(self::$users); + foreach(self::$users as $userId => &$userFirstname) { + // Les membres ne sont pas éditeurs, les exclure de la liste + if ( $this->getData(['user', $userId, 'group']) < self::GROUP_MODERATOR) { + unset(self::$users[$userId]); + } + $userFirstname = $userFirstname . ' ' . $this->getData(['user', $userId, 'lastname']) . ' (' . self::$groupEdits[$this->getData(['user', $userId, 'group'])] . ')'; + } + unset($userFirstname); + // Valeurs en sortie + $this->addOutput([ + 'title' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']), + 'vendor' => [ + 'flatpickr', + 'tinymce' + ], + 'view' => 'edit' + ]); + } + } + + /** + * Accueil (deux affichages en un pour éviter une url à rallonge) + */ + public function index() { + // Mise à jour des données de module + $this->update(); + // Affichage d'un article + if( + $this->getUrl(1) + // Protection pour la pagination, un ID ne peut pas être un entier, une page oui + AND intval($this->getUrl(1)) === 0 + ) { + // L'article n'existe pas + if($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // L'article existe + else { + // Soumission du formulaire + if($this->isPost()) { + // Check la captcha + if( + $this->getUser('password') !== $this->getInput('DELTA_USER_PASSWORD') + //AND $this->getInput('blogArticlecaptcha', helper::FILTER_INT) !== $this->getInput('blogArticlecaptchaFirstNumber', helper::FILTER_INT) + $this->getInput('blogArticlecaptchaSecondNumber', helper::FILTER_INT)) + AND password_verify($this->getInput('blogArticleCaptcha', helper::FILTER_INT), $this->getInput('blogArticleCaptchaResult') ) === false ) + { + self::$inputNotices['blogArticleCaptcha'] = 'Incorrect'; + } + // Crée le commentaire + $commentId = helper::increment(uniqid(), $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment'])); + $content = $this->getInput('blogArticleContent', false); + $this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentId, [ + 'author' => $this->getInput('blogArticleAuthor', helper::FILTER_STRING_SHORT, empty($this->getInput('blogArticleUserId')) ? TRUE : FALSE), + 'content' => $content, + 'createdOn' => time(), + 'userId' => $this->getInput('blogArticleUserId'), + 'approval' => !$this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentApproved']) // true commentaire publié false en attente de publication + ]]); + // Envoi d'une notification aux administrateurs + // Init tableau + $to = []; + // Liste des destinataires + foreach($this->getData(['user']) as $userId => $user) { + if ($user['group'] >= $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentGroupNotification']) ) { + $to[] = $user['mail']; + $firstname[] = $user['firstname']; + $lastname[] = $user['lastname']; + } + } + // Envoi du mail $sent code d'erreur ou de réussite + $notification = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentApproved']) === true ? 'Commentaire déposé en attente d\'approbation': 'Commentaire déposé'; + if ($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentNotification']) === true) { + $error = 0; + foreach($to as $key => $adress){ + $sent = $this->sendMail( + $adress, + 'Nouveau commentaire déposé', + 'Bonjour' . ' ' . $firstname[$key] . ' ' . $lastname[$key] . ',

' . + 'L\'article ' . $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'title']) . ' a reçu un nouveau commentaire.

', + '' + ); + if( $sent === false) $error++; + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl() . '#comment', + 'notification' => ($error === 0 ? $notification . '
Une notification a été envoyée.' : $notification . '
Erreur de notification : ' . $sent), + 'state' => ($sent === true ? true : null) + ]); + + } else { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl() . '#comment', + 'notification' => $notification, + 'state' => true + ]); + } + + } + // Ids des commentaires approuvés par ordre de publication + $commentsApproved = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment']); + if ($commentsApproved) { + foreach( $commentsApproved as $key => $value){ + if($value['approval']===false) unset($commentsApproved[$key]); + } + // Ligne suivante si affichage du nombre total de commentaires approuvés sous l'article + self::$nbCommentsApproved = count($commentsApproved); + } + $commentIds = array_keys(helper::arrayCollumn($commentsApproved, 'createdOn', 'SORT_DESC')); + // Pagination + $pagination = helper::pagination($commentIds, $this->getUrl(), $this->getData(['module', $this->getUrl(0),'config', 'itemsperPage']),'#comment'); + // Liste des pages + self::$pages = $pagination['pages']; + // Signature de l'article + self::$articleSignature = $this->signature($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'userId'])); + // Signature du commentaire édité + if($this->getUser('password') === $this->getInput('DELTA_USER_PASSWORD')) { + self::$editCommentSignature = $this->signature($this->getUser('id')); + } + // Commentaires en fonction de la pagination + for($i = $pagination['first']; $i < $pagination['last']; $i++) { + // Signatures des commentaires + $e = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentIds[$i],'userId']); + if ($e) { + self::$commentsSignature[$commentIds[$i]] = $this->signature($e); + } else { + self::$commentsSignature[$commentIds[$i]] = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentIds[$i],'author']); + } + // Données du commentaire si approuvé + if ($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentIds[$i],'approval']) === true ) { + self::$comments[$commentIds[$i]] = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentIds[$i]]); + } + } + // Valeurs en sortie + $this->addOutput([ + 'showBarEditButton' => true, + 'title' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'title']), + 'vendor' => [ + 'tinymce' + ], + 'view' => 'article' + ]); + } + + } + // Liste des articles + else { + // Ids des articles par ordre de publication + $articleIdsPublishedOns = helper::arrayCollumn($this->getData(['module', $this->getUrl(0),'posts']), 'publishedOn', 'SORT_DESC'); + $articleIdsStates = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), 'posts']), 'state', 'SORT_DESC'); + $articleIds = []; + foreach($articleIdsPublishedOns as $articleId => $articlePublishedOn) { + if($articlePublishedOn <= time() AND $articleIdsStates[$articleId]) { + $articleIds[] = $articleId; + } + } + // Pagination + $pagination = helper::pagination($articleIds, $this->getUrl(), $this->getData(['module', $this->getUrl(0),'config', 'itemsperPage'])); + // Liste des pages + self::$pages = $pagination['pages']; + // Articles en fonction de la pagination + for($i = $pagination['first']; $i < $pagination['last']; $i++) { + self::$articles[$articleIds[$i]] = $this->getData(['module', $this->getUrl(0), 'posts', $articleIds[$i]]); + } + // Valeurs en sortie + $this->addOutput([ + 'showBarEditButton' => true, + 'showPageContent' => true, + 'view' => 'index' + ]); + } + } + + /** + * Retourne la signature d'un utilisateur + */ + private function signature($userId) { + switch ($this->getData(['user', $userId, 'signature'])){ + case 1: + return $userId; + break; + case 2: + return $this->getData(['user', $userId, 'pseudo']); + break; + case 3: + return $this->getData(['user', $userId, 'firstname']) . ' ' . $this->getData(['user', $userId, 'lastname']); + break; + case 4: + return $this->getData(['user', $userId, 'lastname']) . ' ' . $this->getData(['user', $userId, 'firstname']); + break; + default: + return $this->getData(['user', $userId, 'firstname']); + } + } +} diff --git a/module/blog/ressource/feed-icon-16.gif b/module/blog/ressource/feed-icon-16.gif new file mode 100644 index 0000000..26fa274 Binary files /dev/null and b/module/blog/ressource/feed-icon-16.gif differ diff --git a/module/blog/vendor/FeedWriter/ATOM.php b/module/blog/vendor/FeedWriter/ATOM.php new file mode 100644 index 0000000..2849450 --- /dev/null +++ b/module/blog/vendor/FeedWriter/ATOM.php @@ -0,0 +1,38 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating ATOM feeds + * + * @package UniversalFeedWriter + */ +class ATOM extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::ATOM); + } + +} diff --git a/module/blog/vendor/FeedWriter/Feed.php b/module/blog/vendor/FeedWriter/Feed.php new file mode 100644 index 0000000..9e0650a --- /dev/null +++ b/module/blog/vendor/FeedWriter/Feed.php @@ -0,0 +1,1017 @@ + + * Copyright (C) 2010-2016 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer class + * + * Generate RSS 1.0, RSS2.0 and ATOM Feeds + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +abstract class Feed +{ + // RSS 0.90 Officially obsoleted by 1.0 + // RSS 0.91, 0.92, 0.93 and 0.94 Officially obsoleted by 2.0 + // So, define constants for RSS 1.0, RSS 2.0 and ATOM + + const RSS1 = 'RSS 1.0'; + const RSS2 = 'RSS 2.0'; + const ATOM = 'ATOM'; + + const VERSION = '1.1.0'; + + /** + * Collection of all channel elements + */ + private $channels = array(); + + /** + * Collection of items as object of \FeedWriter\Item class. + */ + private $items = array(); + + /** + * Collection of other version wise data. + * + * Currently used to store the 'rdf:about' attribute and image element of the channel (both RSS1 only). + */ + private $data = array(); + + /** + * The tag names which have to encoded as CDATA + */ + private $CDATAEncoding = array(); + + /** + * Collection of XML namespaces + */ + private $namespaces = array(); + + /** + * Contains the format of this feed. + */ + private $version = null; + + /** + * Constructor + * + * If no version is given, a feed in RSS 2.0 format will be generated. + * + * @param string $version the version constant (RSS1/RSS2/ATOM). + */ + protected function __construct($version = Feed::RSS2) + { + $this->version = $version; + + // Setting default encoding + $this->encoding = 'utf-8'; + + // Setting default value for essential channel element + $this->setTitle($version . ' Feed'); + + // Add some default XML namespaces + $this->namespaces['content'] = 'http://purl.org/rss/1.0/modules/content/'; + $this->namespaces['wfw'] = 'http://wellformedweb.org/CommentAPI/'; + $this->namespaces['atom'] = 'http://www.w3.org/2005/Atom'; + $this->namespaces['rdf'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->namespaces['rss1'] = 'http://purl.org/rss/1.0/'; + $this->namespaces['dc'] = 'http://purl.org/dc/elements/1.1/'; + $this->namespaces['sy'] = 'http://purl.org/rss/1.0/modules/syndication/'; + + // Tag names to encode in CDATA + $this->addCDATAEncoding(array('description', 'content:encoded', 'summary')); + } + + // Start # public functions --------------------------------------------- + + /** + * Set the URLs for feed pagination. + * + * See RFC 5005, chapter 3. At least one page URL must be specified. + * + * @param string $nextURL The URL to the next page of this feed. Optional. + * @param string $previousURL The URL to the previous page of this feed. Optional. + * @param string $firstURL The URL to the first page of this feed. Optional. + * @param string $lastURL The URL to the last page of this feed. Optional. + * @link http://tools.ietf.org/html/rfc5005#section-3 + * @return self + * @throws \LogicException if none of the parameters are set. + */ + public function setPagination($nextURL = null, $previousURL = null, $firstURL = null, $lastURL = null) + { + if (empty($nextURL) && empty($previousURL) && empty($firstURL) && empty($lastURL)) + throw new \LogicException('At least one URL must be specified for pagination to work.'); + + if (!empty($nextURL)) + $this->setAtomLink($nextURL, 'next'); + + if (!empty($previousURL)) + $this->setAtomLink($previousURL, 'previous'); + + if (!empty($firstURL)) + $this->setAtomLink($firstURL, 'first'); + + if (!empty($lastURL)) + $this->setAtomLink($lastURL, 'last'); + + return $this; + } + + /** + * Add a channel element indicating the program used to generate the feed. + * + * @return self + * @throws InvalidOperationException if this method is called on an RSS1 feed. + */ + public function addGenerator() + { + if ($this->version == Feed::ATOM) + $this->setChannelElement('atom:generator', 'FeedWriter', array('uri' => 'https://github.com/mibe/FeedWriter')); + else if ($this->version == Feed::RSS2) + $this->setChannelElement('generator', 'FeedWriter'); + else + throw new InvalidOperationException('The generator element is not supported in RSS1 feeds.'); + + return $this; + } + + /** + * Add a XML namespace to the internal list of namespaces. After that, + * custom channel elements can be used properly to generate a valid feed. + * + * @access public + * @param string $prefix namespace prefix + * @param string $uri namespace name (URI) + * @return self + * @link http://www.w3.org/TR/REC-xml-names/ + * @throws \InvalidArgumentException if the prefix or uri is empty or NULL. + */ + public function addNamespace($prefix, $uri) + { + if (empty($prefix)) + throw new \InvalidArgumentException('The prefix may not be emtpy or NULL.'); + if (empty($uri)) + throw new \InvalidArgumentException('The uri may not be empty or NULL.'); + + $this->namespaces[$prefix] = $uri; + + return $this; + } + + /** + * Add a channel element to the feed. + * + * @access public + * @param string $elementName name of the channel tag + * @param string $content content of the channel tag + * @param array array of element attributes with attribute name as array key + * @param bool TRUE if this element can appear multiple times + * @return self + * @throws \InvalidArgumentException if the element name is not a string, empty or NULL. + */ + public function setChannelElement($elementName, $content, array $attributes = null, $multiple = false) + { + if (empty($elementName)) + throw new \InvalidArgumentException('The element name may not be empty or NULL.'); + if (!is_string($elementName)) + throw new \InvalidArgumentException('The element name must be a string.'); + + $entity['content'] = $content; + $entity['attributes'] = $attributes; + + if ($multiple === TRUE) + $this->channels[$elementName][] = $entity; + else + $this->channels[$elementName] = $entity; + + return $this; + } + + /** + * Set multiple channel elements from an array. Array elements + * should be 'channelName' => 'channelContent' format. + * + * @access public + * @param array array of channels + * @return self + */ + public function setChannelElementsFromArray(array $elementArray) + { + foreach ($elementArray as $elementName => $content) { + $this->setChannelElement($elementName, $content); + } + + return $this; + } + + /** + * Get the appropriate MIME type string for the current feed. + * + * @access public + * @return string The MIME type string. + */ + public function getMIMEType() + { + switch ($this->version) { + case Feed::RSS2 : $mimeType = "application/rss+xml"; + break; + case Feed::RSS1 : $mimeType = "application/rdf+xml"; + break; + case Feed::ATOM : $mimeType = "application/atom+xml"; + break; + default : $mimeType = "text/xml"; + } + + return $mimeType; + } + + /** + * Print the actual RSS/ATOM file + * + * Sets a Content-Type header and echoes the contents of the feed. + * Should only be used in situations where direct output is desired; + * if you need to pass a string around, use generateFeed() instead. + * + * @access public + * @param bool FALSE if the specific feed media type should be sent. + * @return void + * @throws \InvalidArgumentException if the useGenericContentType parameter is not boolean. + */ + public function printFeed($useGenericContentType = false) + { + if (!is_bool($useGenericContentType)) + throw new \InvalidArgumentException('The useGenericContentType parameter must be boolean.'); + + $contentType = "text/xml"; + + if (!$useGenericContentType) { + $contentType = $this->getMIMEType(); + } + + // Generate the feed before setting the header, so Exceptions will be nicely visible. + $feed = $this->generateFeed(); + header("Content-Type: " . $contentType . "; charset=" . $this->encoding); + echo $feed; + } + + /** + * Generate the feed. + * + * @access public + * @return string The complete feed XML. + * @throws InvalidOperationException if the link element of the feed is not set. + */ + public function generateFeed() + { + if ($this->version != Feed::ATOM && !array_key_exists('link', $this->channels)) + throw new InvalidOperationException('RSS1 & RSS2 feeds need a link element. Call the setLink method before this method.'); + + return $this->makeHeader() + . $this->makeChannels() + . $this->makeItems() + . $this->makeFooter(); + } + + /** + * Create a new Item. + * + * @access public + * @return Item instance of Item class + */ + public function createNewItem() + { + $Item = new Item($this->version); + + return $Item; + } + + /** + * Add one or more tags to the list of CDATA encoded tags + * + * @access public + * @param array $tags An array of tag names that are merged into the list of tags which should be encoded as CDATA + * @return self + */ + public function addCDATAEncoding(array $tags) + { + $this->CDATAEncoding = array_merge($this->CDATAEncoding, $tags); + + return $this; + } + + /** + * Get list of CDATA encoded properties + * + * @access public + * @return array Return an array of CDATA properties that are to be encoded as CDATA + */ + public function getCDATAEncoding() + { + return $this->CDATAEncoding; + } + + /** + * Remove tags from the list of CDATA encoded tags + * + * @access public + * @param array $tags An array of tag names that should be removed. + * @return void + */ + public function removeCDATAEncoding(array $tags) + { + // Call array_values to re-index the array. + $this->CDATAEncoding = array_values(array_diff($this->CDATAEncoding, $tags)); + } + + /** + * Add a FeedItem to the main class + * + * @access public + * @param Item $feedItem instance of Item class + * @return self + * @throws \InvalidArgumentException if the given item version mismatches. + */ + public function addItem(Item $feedItem) + { + if ($feedItem->getVersion() != $this->version) + { + $msg = sprintf('Feed type mismatch: This instance can handle %s feeds only, but item for %s feeds given.', $this->version, $feedItem->getVersion()); + throw new \InvalidArgumentException($msg); + } + + $this->items[] = $feedItem; + + return $this; + } + + // Wrapper functions ------------------------------------------------------------------- + + /** + * Set the 'encoding' attribute in the XML prolog. + * + * @access public + * @param string $encoding value of 'encoding' attribute + * @return self + * @throws \InvalidArgumentException if the encoding is not a string, empty or NULL. + */ + public function setEncoding($encoding) + { + if (empty($encoding)) + throw new \InvalidArgumentException('The encoding may not be empty or NULL.'); + if (!is_string($encoding)) + throw new \InvalidArgumentException('The encoding must be a string.'); + + $this->encoding = $encoding; + + return $this; + } + + /** + * Set the 'title' channel element + * + * @access public + * @param string $title value of 'title' channel tag + * @return self + */ + public function setTitle($title) + { + return $this->setChannelElement('title', $title); + } + + /** + * Set the date when the feed was lastly updated. + * + * This adds the 'updated' element to the feed. The value of the date parameter + * can be either an instance of the DateTime class, an integer containing a UNIX + * timestamp or a string which is parseable by PHP's 'strtotime' function. + * + * Not supported in RSS1 feeds. + * + * @access public + * @param DateTime|int|string Date which should be used. + * @return self + * @throws \InvalidArgumentException if the given date is not an instance of DateTime, a UNIX timestamp or a date string. + * @throws InvalidOperationException if this method is called on an RSS1 feed. + */ + public function setDate($date) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('The publication date is not supported in RSS1 feeds.'); + + // The feeds have different date formats. + $format = $this->version == Feed::ATOM ? \DATE_ATOM : \DATE_RSS; + + if ($date instanceof DateTime) + $date = $date->format($format); + else if(is_numeric($date) && $date >= 0) + $date = date($format, $date); + else if (is_string($date)) + { + $timestamp = strtotime($date); + if ($timestamp === FALSE) + throw new \InvalidArgumentException('The given date was not parseable.'); + + $date = date($format, $timestamp); + } + else + throw new \InvalidArgumentException('The given date is not an instance of DateTime, a UNIX timestamp or a date string.'); + + if ($this->version == Feed::ATOM) + $this->setChannelElement('updated', $date); + else + $this->setChannelElement('lastBuildDate', $date); + + return $this; + } + + /** + * Set a phrase or sentence describing the feed. + * + * @access public + * @param string $description Description of the feed. + * @return self + */ + public function setDescription($description) + { + if ($this->version != Feed::ATOM) + $this->setChannelElement('description', $description); + else + $this->setChannelElement('subtitle', $description); + + return $this; + } + + /** + * Set the 'link' channel element + * + * @access public + * @param string $link value of 'link' channel tag + * @return self + */ + public function setLink($link) + { + if ($this->version == Feed::ATOM) + $this->setAtomLink($link); + else + $this->setChannelElement('link', $link); + + return $this; + } + + /** + * Set custom 'link' channel elements. + * + * In ATOM feeds, only one link with alternate relation and the same combination of + * type and hreflang values. + * + * @access public + * @param string $href URI of this link + * @param string $rel relation type of the resource + * @param string $type MIME type of the target resource + * @param string $hreflang language of the resource + * @param string $title human-readable information about the resource + * @param int $length length of the resource in bytes + * @link https://www.iana.org/assignments/link-relations/link-relations.xml + * @link https://tools.ietf.org/html/rfc4287#section-4.2.7 + * @return self + * @throws \InvalidArgumentException on multiple occasions. + * @throws InvalidOperationException if the same link with the same attributes was already added to the feed. + */ + public function setAtomLink($href, $rel = null, $type = null, $hreflang = null, $title = null, $length = null) + { + $data = array('href' => $href); + + if ($rel != null) { + if (!is_string($rel) || empty($rel)) + throw new \InvalidArgumentException('rel parameter must be a string and a valid relation identifier.'); + + $data['rel'] = $rel; + } + if ($type != null) { + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + throw new \InvalidArgumentException('type parameter must be a string and a MIME type.'); + + $data['type'] = $type; + } + if ($hreflang != null) { + // Regex used from RFC 4287, page 41 + if (!is_string($hreflang) || preg_match('/[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*/', $hreflang) != 1) + throw new \InvalidArgumentException('hreflang parameter must be a string and a valid language code.'); + + $data['hreflang'] = $hreflang; + } + if ($title != null) { + if (!is_string($title) || empty($title)) + throw new \InvalidArgumentException('title parameter must be a string and not empty.'); + + $data['title'] = $title; + } + if ($length != null) { + if (!is_int($length) || $length < 0) + throw new \InvalidArgumentException('length parameter must be a positive integer.'); + + $data['length'] = (string) $length; + } + + // ATOM spec. has some restrictions on atom:link usage + // See RFC 4287, page 12 (4.1.1) + if ($this->version == Feed::ATOM) { + foreach ($this->channels as $key => $value) { + if ($key != 'atom:link') + continue; + + // $value is an array , so check every element + foreach ($value as $linkItem) { + $attrib = $linkItem['attributes']; + // Only one link with relation alternate and same hreflang & type is allowed. + if (@$attrib['rel'] == 'alternate' && @$attrib['hreflang'] == $hreflang && @$attrib['type'] == $type) + throw new InvalidOperationException('The feed must not contain more than one link element with a' + . ' relation of "alternate" that has the same combination of type and hreflang attribute values.'); + } + } + } + + return $this->setChannelElement('atom:link', '', $data, true); + } + + /** + * Set an 'atom:link' channel element with relation=self attribute. + * Needs the full URL to this feed. + * + * @link http://www.rssboard.org/rss-profile#namespace-elements-atom-link + * @access public + * @param string $url URL to this feed + * @return self + */ + public function setSelfLink($url) + { + return $this->setAtomLink($url, 'self', $this->getMIMEType()); + } + + /** + * Set the 'image' channel element + * + * @access public + * @param string $url URL of the image + * @param string $title Title of the image. RSS only. + * @param string $link Link target URL of the image. RSS only. + * @return self + * @throws \InvalidArgumentException if the url is invalid. + * @throws \InvalidArgumentException if the title and link parameter are not a string or empty. + */ + public function setImage($url, $title = null, $link = null) + { + if (!is_string($url) || empty($url)) + throw new \InvalidArgumentException('url parameter must be a string and may not be empty or NULL.'); + + // RSS feeds have support for a title & link element. + if ($this->version != Feed::ATOM) + { + if (!is_string($title) || empty($title)) + throw new \InvalidArgumentException('title parameter must be a string and may not be empty or NULL.'); + if (!is_string($link) || empty($link)) + throw new \InvalidArgumentException('link parameter must be a string and may not be empty or NULL.'); + + $data = array('title'=>$title, 'link'=>$link, 'url'=>$url); + $name = 'image'; + } + else + { + $name = 'logo'; + $data = $url; + } + + // Special handling for RSS1 again (since RSS1 is a bit strange...) + if ($this->version == Feed::RSS1) + { + $this->data['Image'] = $data; + return $this->setChannelElement($name, '', array('rdf:resource' => $url), false); + } + else + return $this->setChannelElement($name, $data); + } + + /** + * Set the channel 'rdf:about' attribute, which is used in RSS1 feeds only. + * + * @access public + * @param string $url value of 'rdf:about' attribute of the channel element + * @return self + * @throws InvalidOperationException if this method is called and the feed is not of type RSS1. + * @throws \InvalidArgumentException if the given URL is invalid. + */ + public function setChannelAbout($url) + { + if ($this->version != Feed::RSS1) + throw new InvalidOperationException("This method is only supported in RSS1 feeds."); + if (empty($url)) + throw new \InvalidArgumentException('The about URL may not be empty or NULL.'); + if (!is_string($url)) + throw new \InvalidArgumentException('The about URL must be a string.'); + + $this->data['ChannelAbout'] = $url; + + return $this; + } + + /** + * Generate an UUID. + * + * The UUID is based on an MD5 hash. If no key is given, a unique ID as the input + * for the MD5 hash is generated. + * + * @author Anis uddin Ahmad + * @access public + * @param string $key optional key on which the UUID is generated + * @param string $prefix an optional prefix + * @return string the formatted UUID + */ + public static function uuid($key = null, $prefix = '') + { + $key = ($key == null) ? uniqid(rand()) : $key; + $chars = md5($key); + $uuid = substr($chars,0,8) . '-'; + $uuid .= substr($chars,8,4) . '-'; + $uuid .= substr($chars,12,4) . '-'; + $uuid .= substr($chars,16,4) . '-'; + $uuid .= substr($chars,20,12); + + return $prefix . $uuid; + } + + /** + * Replace invalid XML characters. + * + * @link http://www.phpwact.org/php/i18n/charsets#xml See utf8_for_xml() function + * @link http://www.w3.org/TR/REC-xml/#charsets + * @link https://github.com/mibe/FeedWriter/issues/30 + * + * @access public + * @param string $string string which should be filtered + * @param string $replacement replace invalid characters with this string + * @return string the filtered string + */ + public static function filterInvalidXMLChars($string, $replacement = '_') // default to '\x{FFFD}' ??? + { + $result = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', $replacement, $string); + + // Did the PCRE replace failed because of bad UTF-8 data? + // If yes, try a non-multibyte regex and without the UTF-8 mode enabled. + if ($result == NULL && preg_last_error() == PREG_BAD_UTF8_ERROR) + $result = preg_replace('/[^\x09\x0a\x0d\x20-\xFF]+/', $replacement, $string); + + // In case the regex replacing failed completely, return the whole unfiltered string. + if ($result == NULL) + $result = $string; + + return $result; + } + // End # public functions ---------------------------------------------- + + // Start # private functions ---------------------------------------------- + + /** + * Returns all used XML namespace prefixes in this instance. + * This includes all channel elements and feed items. + * Unfortunately some namespace prefixes are not included, + * because they are hardcoded, e.g. rdf. + * + * @access private + * @return array Array with namespace prefix as value. + */ + private function getNamespacePrefixes() + { + $prefixes = array(); + + // Get all tag names from channel elements... + $tags = array_keys($this->channels); + + // ... and now all names from feed items + foreach ($this->items as $item) { + foreach (array_keys($item->getElements()) as $key) { + if (!in_array($key, $tags)) { + $tags[] = $key; + } + } + } + + // Look for prefixes in those tag names + foreach ($tags as $tag) { + $elements = explode(':', $tag); + + if (count($elements) != 2) + continue; + + $prefixes[] = $elements[0]; + } + + return array_unique($prefixes); + } + + /** + * Returns the XML header and root element, depending on the feed type. + * + * @access private + * @return string The XML header of the feed. + * @throws InvalidOperationException if an unknown XML namespace prefix is encountered. + */ + private function makeHeader() + { + $out = 'encoding.'" ?>' . PHP_EOL; + + $prefixes = $this->getNamespacePrefixes(); + $attributes = array(); + $tagName = ''; + $defaultNamespace = ''; + + if ($this->version == Feed::RSS2) { + $tagName = 'rss'; + $attributes['version'] = '2.0'; + } elseif ($this->version == Feed::RSS1) { + $tagName = 'rdf:RDF'; + $prefixes[] = 'rdf'; + $defaultNamespace = $this->namespaces['rss1']; + } elseif ($this->version == Feed::ATOM) { + $tagName = 'feed'; + $defaultNamespace = $this->namespaces['atom']; + + // Ugly hack to remove the 'atom' value from the prefixes array. + $prefixes = array_flip($prefixes); + unset($prefixes['atom']); + $prefixes = array_flip($prefixes); + } + + // Iterate through every namespace prefix and add it to the element attributes. + foreach ($prefixes as $prefix) { + if (!isset($this->namespaces[$prefix])) + throw new InvalidOperationException('Unknown XML namespace prefix: \'' . $prefix . '\'.' + . ' Use the addNamespace method to add support for this prefix.'); + else + $attributes['xmlns:' . $prefix] = $this->namespaces[$prefix]; + } + + // Include default namepsace, if required + if (!empty($defaultNamespace)) + $attributes['xmlns'] = $defaultNamespace; + + $out .= $this->makeNode($tagName, '', $attributes, true); + + return $out; + } + + /** + * Closes the open tags at the end of file + * + * @access private + * @return string The XML footer of the feed. + */ + private function makeFooter() + { + if ($this->version == Feed::RSS2) { + return '' . PHP_EOL . ''; + } elseif ($this->version == Feed::RSS1) { + return ''; + } elseif ($this->version == Feed::ATOM) { + return ''; + } + } + + /** + * Creates a single node in XML format + * + * @access private + * @param string $tagName name of the tag + * @param mixed $tagContent tag value as string or array of nested tags in 'tagName' => 'tagValue' format + * @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format + * @param bool $omitEndTag True if the end tag should be omitted. Defaults to false. + * @return string formatted xml tag + * @throws \InvalidArgumentException if the tagContent is not an array and not a string. + */ + private function makeNode($tagName, $tagContent, array $attributes = null, $omitEndTag = false) + { + $nodeText = ''; + $attrText = ''; + + if ($attributes != null) { + foreach ($attributes as $key => $value) { + $value = self::filterInvalidXMLChars($value); + $value = htmlspecialchars($value); + $attrText .= " $key=\"$value\""; + } + } + + $attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == Feed::ATOM) ? ' type="html"' : ''; + $nodeText .= "<{$tagName}{$attrText}>"; + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ' $value) { + if (is_array($value)) { + $nodeText .= PHP_EOL; + foreach ($value as $subValue) { + $nodeText .= $this->makeNode($key, $subValue); + } + } else if (is_string($value)) { + $nodeText .= $this->makeNode($key, $value); + } else { + throw new \InvalidArgumentException("Unknown node-value type for $key"); + } + } + } else { + $tagContent = self::filterInvalidXMLChars($tagContent); + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $this->sanitizeCDATA($tagContent) : htmlspecialchars($tagContent); + } + + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ']]>' : ''; + + if (!$omitEndTag) + $nodeText .= ""; + + $nodeText .= PHP_EOL; + + return $nodeText; + } + + /** + * Make the channels. + * + * @access private + * @return string The feed header as XML containing all the feed metadata. + */ + private function makeChannels() + { + $out = ''; + + //Start channel tag + switch ($this->version) { + case Feed::RSS2: + $out .= '' . PHP_EOL; + break; + case Feed::RSS1: + $out .= (isset($this->data['ChannelAbout']))? "data['ChannelAbout']}\">" : "channels['link']['content']}\">"; + break; + } + + //Print Items of channel + foreach ($this->channels as $key => $value) { + // In ATOM feeds, strip all ATOM namespace prefixes from the tag name. They are not needed here, + // because the ATOM namespace name is set as default namespace. + if ($this->version == Feed::ATOM && strncmp($key, 'atom', 4) == 0) { + $key = substr($key, 5); + } + + // The channel element can occur multiple times, when the key 'content' is not in the array. + if (!array_key_exists('content', $value)) { + // If this is the case, iterate through the array with the multiple elements. + foreach ($value as $singleElement) { + $out .= $this->makeNode($key, $singleElement['content'], $singleElement['attributes']); + } + } else { + $out .= $this->makeNode($key, $value['content'], $value['attributes']); + } + } + + if ($this->version == Feed::RSS1) { + //RSS 1.0 have special tag with channel + $out .= "" . PHP_EOL . "" . PHP_EOL; + foreach ($this->items as $item) { + $thisItems = $item->getElements(); + $out .= "" . PHP_EOL; + } + $out .= "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL; + + // An image has its own element after the channel elements. + if (array_key_exists('image', $this->data)) + $out .= $this->makeNode('image', $this->data['Image'], array('rdf:about' => $this->data['Image']['url'])); + } else if ($this->version == Feed::ATOM) { + // ATOM feeds have a unique feed ID. Use the title channel element as key. + $out .= $this->makeNode('id', Feed::uuid($this->channels['title']['content'], 'urn:uuid:')); + } + + return $out; + } + + /** + * Prints formatted feed items + * + * @access private + * @return string The XML of every feed item. + */ + private function makeItems() + { + $out = ''; + + foreach ($this->items as $item) { + $thisItems = $item->getElements(); + + // The argument is printed as rdf:about attribute of item in RSS 1.0 + // We're using the link set in the item (which is mandatory) as the about attribute. + if ($this->version == Feed::RSS1) + $out .= $this->startItem($thisItems['link']['content']); + else + $out .= $this->startItem(); + + foreach ($thisItems as $feedItem) { + $name = $feedItem['name']; + + // Strip all ATOM namespace prefixes from tags when feed is an ATOM feed. + // Not needed here, because the ATOM namespace name is used as default namespace. + if ($this->version == Feed::ATOM && strncmp($name, 'atom', 4) == 0) + $name = substr($name, 5); + + $out .= $this->makeNode($name, $feedItem['content'], $feedItem['attributes']); + } + $out .= $this->endItem(); + } + + return $out; + } + + /** + * Make the starting tag of channels + * + * @access private + * @param string $about The value of about attribute which is used for RSS 1.0 only. + * @return string The starting XML tag of an feed item. + * @throws InvalidOperationException if this object misses the data for the about attribute. + */ + private function startItem($about = false) + { + $out = ''; + + if ($this->version == Feed::RSS2) { + $out .= '' . PHP_EOL; + } elseif ($this->version == Feed::RSS1) { + if ($about) { + $out .= "" . PHP_EOL; + } else { + throw new InvalidOperationException("Missing data for about attribute. Call setChannelAbout method."); + } + } elseif ($this->version == Feed::ATOM) { + $out .= "" . PHP_EOL; + } + + return $out; + } + + /** + * Closes feed item tag + * + * @access private + * @return string The ending XML tag of an feed item. + */ + private function endItem() + { + if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) { + return '' . PHP_EOL; + } elseif ($this->version == Feed::ATOM) { + return '' . PHP_EOL; + } + } + + /** + * Sanitizes data which will be later on returned as CDATA in the feed. + * + * A "]]>" respectively "", "]]>", $text); + $text = str_replace(" + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * The exception that is thrown when an invalid operation is performed on + * the object. + * + * @package UniversalFeedWriter + */ +class InvalidOperationException extends LogicException +{ +} diff --git a/module/blog/vendor/FeedWriter/Item.php b/module/blog/vendor/FeedWriter/Item.php new file mode 100644 index 0000000..695afe4 --- /dev/null +++ b/module/blog/vendor/FeedWriter/Item.php @@ -0,0 +1,413 @@ + + * Copyright (C) 2010-2013, 2015-2016 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer + * + * Item class - Used as feed element in Feed class + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +class Item +{ + /** + * Collection of feed item elements + */ + private $elements = array(); + + /** + * Contains the format of this feed. + */ + private $version; + + /** + * Is used as a suffix when multiple elements have the same name. + **/ + private $_cpt = 0; + + /** + * Constructor + * + * @param string $version constant (RSS1/RSS2/ATOM) RSS2 is default. + */ + public function __construct($version = Feed::RSS2) + { + $this->version = $version; + } + + /** + * Return an unique number + * + * @access private + * @return int + **/ + private function cpt() + { + return $this->_cpt++; + } + + /** + * Add an element to elements array + * + * @access public + * @param string $elementName The tag name of an element + * @param string $content The content of tag + * @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format + * @param boolean $overwrite Specifies if an already existing element is overwritten. + * @param boolean $allowMultiple Specifies if multiple elements of the same name are allowed. + * @return self + * @throws \InvalidArgumentException if the element name is not a string, empty or NULL. + */ + public function addElement($elementName, $content, array $attributes = null, $overwrite = FALSE, $allowMultiple = FALSE) + { + if (empty($elementName)) + throw new \InvalidArgumentException('The element name may not be empty or NULL.'); + if (!is_string($elementName)) + throw new \InvalidArgumentException('The element name must be a string.'); + + $key = $elementName; + + // return if element already exists & if overwriting is disabled + // & if multiple elements are not allowed. + if (isset($this->elements[$elementName]) && !$overwrite) { + if (!$allowMultiple) + return $this; + + $key .= '-' . $this->cpt(); + } + + $this->elements[$key]['name'] = $elementName; + $this->elements[$key]['content'] = $content; + $this->elements[$key]['attributes'] = $attributes; + + return $this; + } + + /** + * Set multiple feed elements from an array. + * Elements which have attributes cannot be added by this method + * + * @access public + * @param array array of elements in 'tagName' => 'tagContent' format. + * @return self + */ + public function addElementArray(array $elementArray) + { + foreach ($elementArray as $elementName => $content) { + $this->addElement($elementName, $content); + } + + return $this; + } + + /** + * Return the collection of elements in this feed item + * + * @access public + * @return array All elements of this item. + * @throws InvalidOperationException on ATOM feeds if either a content or link element is missing. + * @throws InvalidOperationException on RSS1 feeds if a title or link element is missing. + */ + public function getElements() + { + // ATOM feeds have some specific requirements... + if ($this->version == Feed::ATOM) + { + // Add an 'id' element, if it was not added by calling the setLink method. + // Use the value of the title element as key, since no link element was specified. + if (!array_key_exists('id', $this->elements)) + $this->setId(Feed::uuid($this->elements['title']['content'], 'urn:uuid:')); + + // Either a 'link' or 'content' element is needed. + if (!array_key_exists('content', $this->elements) && !array_key_exists('link', $this->elements)) + throw new InvalidOperationException('ATOM feed entries need a link or a content element. Call the setLink or setContent method.'); + } + // ...same with RSS1 feeds. + else if ($this->version == Feed::RSS1) + { + if (!array_key_exists('title', $this->elements)) + throw new InvalidOperationException('RSS1 feed entries need a title element. Call the setTitle method.'); + if (!array_key_exists('link', $this->elements)) + throw new InvalidOperationException('RSS1 feed entries need a link element. Call the setLink method.'); + } + + return $this->elements; + } + + /** + * Return the type of this feed item + * + * @access public + * @return string The feed type, as defined in Feed.php + */ + public function getVersion() + { + return $this->version; + } + + // Wrapper functions ------------------------------------------------------ + + /** + * Set the 'description' element of feed item + * + * @access public + * @param string $description The content of the 'description' or 'summary' element + * @return self + */ + public function setDescription($description) + { + $tag = ($this->version == Feed::ATOM) ? 'summary' : 'description'; + + return $this->addElement($tag, $description); + } + + /** + * Set the 'content' element of the feed item + * For ATOM feeds only + * + * @access public + * @param string $content Content for the item (i.e., the body of a blog post). + * @return self + * @throws InvalidOperationException if this method is called on non-ATOM feeds. + */ + public function setContent($content) + { + if ($this->version != Feed::ATOM) + throw new InvalidOperationException('The content element is supported in ATOM feeds only.'); + + return $this->addElement('content', $content, array('type' => 'html')); + } + + /** + * Set the 'title' element of feed item + * + * @access public + * @param string $title The content of 'title' element + * @return self + */ + public function setTitle($title) + { + return $this->addElement('title', $title); + } + + /** + * Set the 'date' element of the feed item. + * + * The value of the date parameter can be either an instance of the + * DateTime class, an integer containing a UNIX timestamp or a string + * which is parseable by PHP's 'strtotime' function. + * + * @access public + * @param DateTime|int|string $date Date which should be used. + * @return self + * @throws \InvalidArgumentException if the given date was not parseable. + */ + public function setDate($date) + { + if (!is_numeric($date)) { + if ($date instanceof DateTime) + $date = $date->getTimestamp(); + else { + $date = strtotime($date); + + if ($date === FALSE) + throw new \InvalidArgumentException('The given date string was not parseable.'); + } + } elseif ($date < 0) + throw new \InvalidArgumentException('The given date is not an UNIX timestamp.'); + + if ($this->version == Feed::ATOM) { + $tag = 'updated'; + $value = date(\DATE_ATOM, $date); + } elseif ($this->version == Feed::RSS2) { + $tag = 'pubDate'; + $value = date(\DATE_RSS, $date); + } else { + $tag = 'dc:date'; + $value = date("Y-m-d", $date); + } + + return $this->addElement($tag, $value); + } + + /** + * Set the 'link' element of feed item + * + * @access public + * @param string $link The content of 'link' element + * @return self + */ + public function setLink($link) + { + if ($this->version == Feed::RSS2 || $this->version == Feed::RSS1) { + $this->addElement('link', $link); + } else { + $this->addElement('link','',array('href'=>$link)); + $this->setId(Feed::uuid($link,'urn:uuid:')); + } + + return $this; + } + + /** + * Attach a external media to the feed item. + * Not supported in RSS 1.0 feeds. + * + * See RFC 4288 for syntactical correct MIME types. + * + * Note that you should avoid the use of more than one enclosure in one item, + * since some RSS aggregators don't support it. + * + * @access public + * @param string $url The URL of the media. + * @param integer $length The length of the media. + * @param string $type The MIME type attribute of the media. + * @param boolean $multiple Specifies if multiple enclosures are allowed + * @return self + * @link https://tools.ietf.org/html/rfc4288 + * @throws \InvalidArgumentException if the length or type parameter is invalid. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function addEnclosure($url, $length, $type, $multiple = TRUE) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('Media attachment is not supported in RSS1 feeds.'); + + // the length parameter should be set to 0 if it can't be determined + // see http://www.rssboard.org/rss-profile#element-channel-item-enclosure + if (!is_numeric($length) || $length < 0) + throw new \InvalidArgumentException('The length parameter must be an integer and greater or equals to zero.'); + + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + throw new \InvalidArgumentException('type parameter must be a string and a MIME type.'); + + $attributes = array('length' => $length, 'type' => $type); + + if ($this->version == Feed::RSS2) { + $attributes['url'] = $url; + $this->addElement('enclosure', '', $attributes, FALSE, $multiple); + } else { + $attributes['href'] = $url; + $attributes['rel'] = 'enclosure'; + $this->addElement('atom:link', '', $attributes, FALSE, $multiple); + } + + return $this; + } + + /** + * Set the 'author' element of feed item. + * Not supported in RSS 1.0 feeds. + * + * @access public + * @param string $author The author of this item + * @param string|null $email Optional email address of the author + * @param string|null $uri Optional URI related to the author + * @return self + * @throws \InvalidArgumentException if the provided email address is syntactically incorrect. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function setAuthor($author, $email = null, $uri = null) + { + if ($this->version == Feed::RSS1) + throw new InvalidOperationException('The author element is not supported in RSS1 feeds.'); + + // Regex from RFC 4287 page 41 + if ($email != null && preg_match('/.+@.+/', $email) != 1) + throw new \InvalidArgumentException('The email address is syntactically incorrect.'); + + if ($this->version == Feed::RSS2) + { + if ($email != null) + $author = $email . ' (' . $author . ')'; + + $this->addElement('author', $author); + } + else + { + $elements = array('name' => $author); + + if ($email != null) + $elements['email'] = $email; + + if ($uri != null) + $elements['uri'] = $uri; + + $this->addElement('author', $elements); + } + + return $this; + } + + /** + * Set the unique identifier of the feed item + * + * On ATOM feeds, the identifier must begin with an valid URI scheme. + * + * @access public + * @param string $id The unique identifier of this item + * @param boolean $permaLink The value of the 'isPermaLink' attribute in RSS 2 feeds. + * @return self + * @throws \InvalidArgumentException if the permaLink parameter is not boolean. + * @throws InvalidOperationException if this method is called on RSS1 feeds. + */ + public function setId($id, $permaLink = false) + { + if ($this->version == Feed::RSS2) { + if (!is_bool($permaLink)) + throw new \InvalidArgumentException('The permaLink parameter must be boolean.'); + + $permaLink = $permaLink ? 'true' : 'false'; + + $this->addElement('guid', $id, array('isPermaLink' => $permaLink)); + } elseif ($this->version == Feed::ATOM) { + // Check if the given ID is an valid URI scheme (see RFC 4287 4.2.6) + // The list of valid schemes was generated from http://www.iana.org/assignments/uri-schemes + // by using only permanent or historical schemes. + $validSchemes = array('aaa', 'aaas', 'about', 'acap', 'acct', 'cap', 'cid', 'coap', 'coaps', 'crid', 'data', 'dav', 'dict', 'dns', 'example', 'fax', 'file', 'filesystem', 'ftp', 'geo', 'go', 'gopher', 'h323', 'http', 'https', 'iax', 'icap', 'im', 'imap', 'info', 'ipp', 'ipps', 'iris', 'iris.beep', 'iris.lwz', 'iris.xpc', 'iris.xpcs', 'jabber', 'ldap', 'mailserver', 'mailto', 'mid', 'modem', 'msrp', 'msrps', 'mtqp', 'mupdate', 'news', 'nfs', 'ni', 'nih', 'nntp', 'opaquelocktoken', 'pack', 'pkcs11', 'pop', 'pres', 'prospero', 'reload', 'rtsp', 'rtsps', 'rtspu', 'service', 'session', 'shttp', 'sieve', 'sip', 'sips', 'sms', 'snews', 'snmp', 'soap.beep', 'soap.beeps', 'stun', 'stuns', 'tag', 'tel', 'telnet', 'tftp', 'thismessage', 'tip', 'tn3270', 'turn', 'turns', 'tv', 'urn', 'vemmi', 'videotex', 'vnc', 'wais', 'ws', 'wss', 'xcon', 'xcon-userid', 'xmlrpc.beep', 'xmlrpc.beeps', 'xmpp', 'z39.50', 'z39.50r', 'z39.50s'); + $found = FALSE; + $checkId = strtolower($id); + + foreach($validSchemes as $scheme) + if (strrpos($checkId, $scheme . ':', -strlen($checkId)) !== FALSE) + { + $found = TRUE; + break; + } + + if (!$found) + throw new \InvalidArgumentException("The ID must begin with an IANA-registered URI scheme."); + + $this->addElement('id', $id, NULL, TRUE); + } else + throw new InvalidOperationException('A unique ID is not supported in RSS1 feeds.'); + + return $this; + } + + } // end of class Item diff --git a/module/blog/vendor/FeedWriter/README.md b/module/blog/vendor/FeedWriter/README.md new file mode 100644 index 0000000..f630af9 --- /dev/null +++ b/module/blog/vendor/FeedWriter/README.md @@ -0,0 +1,42 @@ +# Generate **RSS 1.0**, **RSS 2.0** or **ATOM** Formatted Feeds + +This package can be used to generate feeds in either **RSS 1.0**, **RSS 2.0** or **ATOM** format. + +Applications can create a feed object, several feed item objects, set several types of properties of either feed and feed items, and add items to the feed. + +Once a feed is fully composed with its items, the feed class can generate the necessary XML structure to describe the feed in **RSS** or **ATOM** format. This structure can be directly sent to the browser, or just returned as string. + +## Requirements + +- PHP 5.3 or higher + +If you don't have **PHP 5.3** available on your system there is a version supporting **PHP 5.0** and above. See the `legacy-php-5.0` branch. + +## Documentation + +The documentation can be found in the `gh-pages` branch, or on [GitHub Pages](https://mibe.github.io/FeedWriter/). + +See the `/examples` directory for usage examples. + +See the `CHANGELOG.md` file for changes between the different versions. + +## Authors + +In chronological order: + +- [Anis uddin Ahmad](https://github.com/ajaxray) +- [Michael Bemmerl](https://github.com/mibe) +- Phil Freo +- Paul Ferrett +- Brennen Bearnes +- Michael Robinson +- Baptiste Fontaine +- Kristián Valentín +- Brandtley McMinn +- Julian Bogdani +- Cedric Gampert +- Yamek +- Thielj +- Pavel Khakhlou +- Daniel +- Tino Goratsch diff --git a/module/blog/vendor/FeedWriter/RSS1.php b/module/blog/vendor/FeedWriter/RSS1.php new file mode 100644 index 0000000..a0465cf --- /dev/null +++ b/module/blog/vendor/FeedWriter/RSS1.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS1 feeds + * + * @package UniversalFeedWriter + */ +class RSS1 extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::RSS1); + } +} diff --git a/module/blog/vendor/FeedWriter/RSS2.php b/module/blog/vendor/FeedWriter/RSS2.php new file mode 100644 index 0000000..9e36a72 --- /dev/null +++ b/module/blog/vendor/FeedWriter/RSS2.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS2 feeds + * + * @package UniversalFeedWriter + */ +class RSS2 extends Feed +{ + /** + * {@inheritdoc} + */ + public function __construct() + { + parent::__construct(Feed::RSS2); + } +} diff --git a/module/blog/view/add/add.css b/module/blog/view/add/add.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/module/blog/view/add/add.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/module/blog/view/add/add.js.php b/module/blog/view/add/add.js.php new file mode 100644 index 0000000..4220c28 --- /dev/null +++ b/module/blog/view/add/add.js.php @@ -0,0 +1,58 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Soumission du formulaire pour enregistrer en brouillon + */ +$("#blogAddDraft").on("click", function() { + $("#blogAddState").val(0); + $("#blogAddForm").trigger("submit"); +}); + +/** + * Options de commentaires + */ +$("#blogAddCommentClose").on("change", function() { + if ($(this).is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } +}); + +$("#blogAddCommentNotification").on("change", function() { + if ($(this).is(':checked') ) { + $("#blogAddCommentGroupNotification").slideDown(); + } else { + $("#blogAddCommentGroupNotification").slideUp(); + } +}); + + +$( document).ready(function() { + + if ($("#blogAddCloseComment").is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } + + if ($("#blogAddCommentNotification").is(':checked') ) { + $("#blogAddCommentGroupNotification").slideDown(); + } else { + $("#blogAddCommentGroupNotification").slideUp(); + } +}); \ No newline at end of file diff --git a/module/blog/view/add/add.php b/module/blog/view/add/add.php new file mode 100644 index 0000000..7f50b45 --- /dev/null +++ b/module/blog/view/add/add.php @@ -0,0 +1,130 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ true, + 'value' => 'Enregistrer en brouillon' + ]); ?> + true + ]); ?> +
+
+ 'Publier', + 'uniqueSubmission' => true + ]); ?> +
+
+
+
+
+

Informations générales

+
+
+ 'Titre' + ]); ?> +
+
+
+
+ 'Taille optimale de l\'image de couverture : ' . ((int) substr($this->getData(['theme', 'site', 'width']), 0, -2) - (20 * 2)) . ' x 350 pixels.', + 'label' => 'Image de couverture', + 'type' => 1 + ]); ?> +
+
+ 'Largeur de l\'image' + ]); ?> +
+
+ 'Position', + 'help' => 'Le texte de l\'article est adapté autour de l\'image' + ]); ?> +
+
+
+
+ $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'hidePicture']) + ]); ?> +
+
+
+
+
+ 'editorWysiwyg' + ]); ?> +
+
+
+

Options de publication

+
+
+ 'Auteur', + 'selected' => $this->getUser('id'), + 'disabled' => $this->getUser('group') !== self::GROUP_ADMIN ? true : false + ]); ?> +
+
+ 'L\'article n\'est visible qu\'après la date de publication prévue.', + 'label' => 'Date de publication', + 'value' => time() + ]); ?> +
+
+ 'Edition / Suppression', + 'selected' => $module::EDIT_ALL, + 'help' => 'Les utilisateurs des groupes supérieurs accèdent à l\'article sans restriction' + ]); ?> +
+
+
+
+
+
+
+
+

Commentaires

+
+
+ +
+
+ +
+
+ 'Choix du nombre maximum de caractères pour chaque commentaire de l\'article, mise en forme html comprise.', + 'label' => 'Caractères par commentaire' + ]); ?> +
+
+
+
+ +
+
+ +
+
+
+
+
+ diff --git a/module/blog/view/article/article.css b/module/blog/view/article/article.css new file mode 100644 index 0000000..00b1246 --- /dev/null +++ b/module/blog/view/article/article.css @@ -0,0 +1,63 @@ + +#sectionTitle { + margin-top: 0; + margin-bottom: 5px; +} +.blogArticlePicture { + height: auto; + border:1px solid lightgray; + box-shadow: 1px 1px 5px; +} +.blogArticlePictureleft { + float: left; + margin: 15px 10px 5px 0 ; +} +.blogArticlePictureright { + float: right; + margin: 15px 0 5px 10px ; +} + + +.pict20{ + width: 20%; +} +.pict30{ + width: 30%; +} +.pict40{ + width: 40%; +} +.pict50{ + width: 50%; +} +.pict100{ + width: 100%; + margin: 15px 0 20px 0 ; +} + +#blogArticleCommentShow { + cursor: text; +} +#blogArticleOr { + padding: 10px; +} +.blogDate { + font-style: italic; + color: grey; + height: 100%; +} +@media (max-width: 767px) { + .blogArticlePicture { + height:auto; + max-width: 100%;} + } + + +#rssFeed { + text-align: right; + float: right; +} +#rssFeed p { + display: inline; + vertical-align: top; +} diff --git a/module/blog/view/article/article.js.php b/module/blog/view/article/article.js.php new file mode 100644 index 0000000..945e62a --- /dev/null +++ b/module/blog/view/article/article.js.php @@ -0,0 +1,49 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Affiche le bloc pour rédiger un commentaire + */ +var commentShowDOM = $("#blogArticleCommentShow"); +commentShowDOM.on("click focus", function() { + $("#blogArticleCommentShowWrapper").fadeOut(function() { + $("#blogArticleCommentWrapper").fadeIn(); + $("#blogArticleCommentContent").trigger("focus"); + }); +}); + +if($("#blogArticleCommentWrapper").find("textarea.notice,input.notice").length) { + commentShowDOM.trigger("click"); +} + +/** + * Cache le bloc pour rédiger un commentaire + */ +$("#blogArticleCommentHide").on("click focus", function() { + $("#blogArticleCommentWrapper").fadeOut(function() { + $("#blogArticleCommentShowWrapper").fadeIn(); + $("#blogArticleCommentContent").val(""); + $("#blogArticleCommentAuthor").val(""); + }); +}); + +/** + * Force le scroll vers les commentaires en cas d'erreur + */ +$("#blogArticleCommentForm").on("submit", function() { + $(location).attr("href", "#comment"); +}); + diff --git a/module/blog/view/article/article.php b/module/blog/view/article/article.php new file mode 100644 index 0000000..69fbfd3 --- /dev/null +++ b/module/blog/view/article/article.php @@ -0,0 +1,157 @@ +
+
+ getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'pictureSize']) === null ? '100' : $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'pictureSize']); ?> + getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'hidePicture']) == false) { + echo '' . $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'picture']) . ''; + } ?> + getData(['module', $this->getUrl(0),'posts', $this->getUrl(1), 'content']); ?> +
+
+
+
+ + + + getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])), 'UTF-8', true) + ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])) + : utf8_encode(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn']))); + $heure = mb_detect_encoding(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])), 'UTF-8', true) + ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn'])) + : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'publishedOn']))); + echo $date . ' à ' . $heure; + ?> + + getUser('password') === $this->getInput('DELTA_USER_PASSWORD') + AND + ( // Propriétaire + ( + $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1),'editConsent']) === $module::EDIT_OWNER + AND ( $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1),'userId']) === $this->getUser('id') + OR $this->getUser('group') === self::GROUP_ADMIN ) + ) + OR ( + // Groupe + ( $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1),'editConsent']) === self::GROUP_ADMIN + OR $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1),'editConsent']) === self::GROUP_MODERATOR) + AND $this->getUser('group') >= $this->getData(['module',$this->getUrl(0), 'posts', $this->getUrl(1),'editConsent']) + ) + OR ( + // Tout le monde + $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1),'editConsent']) === $module::EDIT_ALL + AND $this->getUser('group') >= $module::$actions['config'] + ) + ) + ): ?> + + Editer + + + + getData(['module',$this->getUrl(0), 'config', 'feeds'])): ?> + + +
+
+getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentClose'])): ?> +

Cet article ne reçoit pas de commentaire.

+ +

+ + + + 0 ? $commentsNb . ' ' . 'commentaire' . $s : 'Pas encore de commentaire'; ?> +

+ + 'Rédiger un commentaire...', + 'readonly' => true + ]); ?> +
+ getUser('password') === $this->getInput('DELTA_USER_PASSWORD')): ?> + 'Nom', + 'readonly' => true, + 'value' => $module::$editCommentSignature + ]); ?> + $this->getUser('id') + ]); ?> + +
+
+ 'Nom' + ]); ?> +
+
+
Ou
+
+
+ helper::baseUrl() . 'user/login/' . str_replace('/', '_', $this->getUrl()) . '__comment', + 'value' => 'Connexion' + ]); ?> +
+
+ + 'Commentaire avec maximum '.$this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentMaxlength']).' caractères', + 'class' => 'editorWysiwygComment', + 'noDirty' => true, + 'maxlength' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentMaxlength']) + ]); ?> +
+ getUser('password') !== $this->getInput('DELTA_USER_PASSWORD')): ?> +
+
+ $this->getData(['config','connect', 'captchaStrong']), + 'type' => $this->getData(['config','connect', 'captchaType']) + ]); ?> +
+
+ +
+
+ 'buttonGrey', + 'value' => 'Annuler' + ]); ?> +
+
+ 'Envoyer', + 'ico' => '' + ]); ?> +
+
+
+ + +
+
+ $comment): ?> +
+

+ le +

+ +
+ +
+
+ \ No newline at end of file diff --git a/module/blog/view/comment/comment.css b/module/blog/view/comment/comment.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/module/blog/view/comment/comment.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/module/blog/view/comment/comment.php b/module/blog/view/comment/comment.php new file mode 100644 index 0000000..08d5e9e --- /dev/null +++ b/module/blog/view/comment/comment.php @@ -0,0 +1,22 @@ +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+ + +
+ +
+ +
+ + '; ?> + +
+ + diff --git a/module/blog/view/config/config.css b/module/blog/view/config/config.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/module/blog/view/config/config.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/module/blog/view/config/config.js.php b/module/blog/view/config/config.js.php new file mode 100644 index 0000000..77b643c --- /dev/null +++ b/module/blog/view/config/config.js.php @@ -0,0 +1,24 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ +/** + * Confirmation de suppression + */ +$(".blogConfigDelete").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir supprimer cet article ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); \ No newline at end of file diff --git a/module/blog/view/config/config.php b/module/blog/view/config/config.php new file mode 100644 index 0000000..736151c --- /dev/null +++ b/module/blog/view/config/config.php @@ -0,0 +1,60 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'page/edit/' . $this->getUrl(0), 'posts', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ helper::baseUrl() . $this->getUrl(0) . '/add', + 'ico' => 'plus', + 'value' => 'Article' + ]); ?> +
+
+ +
+
+
+
+
+

Paramètres du module

+
+
+ $this->getData(['module', $this->getUrl(0), 'config', 'feeds']), + ]); ?> +
+
+ 'Texte de l\'étiquette', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'feedsLabel']) + ]); ?> +
+
+
+
+ 'Articles par page', + 'selected' => $this->getData(['module', $this->getUrl(0),'config', 'itemsperPage']) + ]); ?> +
+
+
+
+
+ + + + + + + +
Version n° + +
+ diff --git a/module/blog/view/edit/edit.css b/module/blog/view/edit/edit.css new file mode 100644 index 0000000..9572399 --- /dev/null +++ b/module/blog/view/edit/edit.css @@ -0,0 +1,19 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/module/blog/view/edit/edit.js.php b/module/blog/view/edit/edit.js.php new file mode 100644 index 0000000..4efaa14 --- /dev/null +++ b/module/blog/view/edit/edit.js.php @@ -0,0 +1,70 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +// Lien de connexion +$("#blogEditMailNotification").on("change", function() { + if($(this).is(":checked")) { + $("#formConfigGroup").show(); + } + else { + $("#formConfigGroup").hide(); + } +}).trigger("change"); + + +/** + * Soumission du formulaire pour enregistrer en brouillon + */ +$("#blogEditDraft").on("click", function() { + $("#blogEditState").val(0); + $("#blogEditForm").trigger("submit"); +}); + +/** + * Options de commentaires + */ +$("#blogEditCommentClose").on("change", function() { + if ($(this).is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } +}); + +$("#blogEditCommentNotification").on("change", function() { + if ($(this).is(':checked') ) { + $("#blogEditCommentGroupNotification").slideDown(); + } else { + $("#blogEditCommentGroupNotification").slideUp(); + } +}); + + +$( document).ready(function() { + + if ($("#blogEditCloseComment").is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } + + if ($("#blogEditCommentNotification").is(':checked') ) { + $("#blogEditCommentGroupNotification").slideDown(); + } else { + $("#blogEditCommentGroupNotification").slideUp(); + } +}); \ No newline at end of file diff --git a/module/blog/view/edit/edit.php b/module/blog/view/edit/edit.php new file mode 100644 index 0000000..404467b --- /dev/null +++ b/module/blog/view/edit/edit.php @@ -0,0 +1,147 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ true, + 'value' => 'Enregistrer en brouillon' + ]); ?> + true + ]); ?> +
+
+ 'Publier', + 'uniqueSubmission' => true, + ]); ?> +
+
+
+
+
+

Informations générales

+
+
+ 'Titre', + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']) + ]); ?> +
+
+
+
+ 'Taille optimale de l\'image de couverture : ' . ((int) substr($this->getData(['theme', 'site', 'width']), 0, -2) - (20 * 2)) . ' x 350 pixels.', + 'label' => 'Image de couverture', + 'type' => 1, + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'picture']) + ]); ?> +
+
+ 'Largeur de l\'image', + 'selected' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'pictureSize']) + ]); ?> +
+
+ 'Position', + 'selected' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'picturePosition']), + 'help' => 'Le texte de l\'article est adapté autour de l\'image' + ]); ?> +
+
+
+
+ $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'hidePicture']) + ]); ?> +
+
+
+
+
+ 'editorWysiwyg', + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'content']) + ]); ?> +
+
+
+

Options de publication

+
+
+ 'Auteur', + 'selected' => $this->getUser('id'), + 'disabled' => $this->getUser('group') !== self::GROUP_ADMIN ? true : false + ]); ?> +
+
+ 'L\'article n\'est visible qu\'après la date de publication prévue.', + 'label' => 'Date de publication', + 'value' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'publishedOn']) + ]); ?> +
+
+ 'Edition / Suppression', + 'selected' => is_numeric($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'editConsent'])) ? $module::EDIT_GROUP : $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'editConsent']), + 'help' => 'Les utilisateurs des groupes supérieurs accèdent à l\'article sans restriction' + ]); ?> +
+
+
+
+
+
+
+
+

Commentaires

+
+
+ $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'commentClose']) + ]); ?> +
+
+ $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'commentApproved']), + '' + ]); ?> +
+
+ 'Choix du nombre maximum de caractères pour chaque commentaire de l\'article, mise en forme html comprise.', + 'label' => 'Caractères par commentaire', + 'selected' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'commentMaxlength']) + ]); ?> +
+ +
+
+
+ $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'commentNotification']), + ]); ?> +
+
+ $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'commentGroupNotification']), + 'help' => 'Editeurs = éditeurs + administrateurs
Membres = membres + éditeurs + administrateurs' + ]); ?> +
+
+
+
+
+ diff --git a/module/blog/view/index/index.css b/module/blog/view/index/index.css new file mode 100644 index 0000000..474b1a8 --- /dev/null +++ b/module/blog/view/index/index.css @@ -0,0 +1,68 @@ +.rowArticle { + margin-bottom: 10px !important; +} +.blogPicture { + float: none; + border: 1px; +} +.blogPicture img { + width: 100%; + height: auto; + /* + border:1px solid lightgray; + box-shadow: 1px 1px 5px darkgray; + */ +} + +.blogPicture:hover { + opacity: .7; +} +.row:after { + content: " "; + display: table; + clear: both; +} +.blogComment { + padding-right: 10px; + float: right; +} +.blogTitle { + /*background-color: #ECEFF1;*/ + padding: 0px; + margin-bottom: 5px; +} +.blogContent { + float: left; + margin-top: 5px; +} +.blogDate { + font-size:0.8em; + font-style: italic; + /* + color: grey; + */ +} +@media (max-width: 768px) { + .blogContent { + display: none; + } + + .blogPicture img { + width: 50% ; + display: block; + margin-left: auto; + margin-right: auto; + } +} + +/* +* Flux RSS +*/ +#rssFeed { + text-align: right; + float: right; +} +#rssFeed p { + display: inline; + vertical-align: top; +} \ No newline at end of file diff --git a/module/blog/view/index/index.php b/module/blog/view/index/index.php new file mode 100644 index 0000000..30baca8 --- /dev/null +++ b/module/blog/view/index/index.php @@ -0,0 +1,65 @@ + +
+
+ $article): ?> +
+
+ + makeThumb( self::FILE_DIR . 'source/' . $article['picture'], + self::FILE_DIR . 'thumb/' . $thumb, + self::THUMBS_WIDTH); + } + ?> + + <?php echo $article['picture']; ?> + + +
+
+

+ + + +

+
+ + + + + + +
+
+ + +
+

+ ... + Lire la suite +

+
+
+ +
+
+ + getData(['module',$this->getUrl(0), 'config', 'feeds'])): ?> + + + + + diff --git a/module/blog/view/rss/rss.php b/module/blog/view/rss/rss.php new file mode 100644 index 0000000..4b8f6d3 --- /dev/null +++ b/module/blog/view/rss/rss.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/module/form/form.php b/module/form/form.php new file mode 100644 index 0000000..9770c26 --- /dev/null +++ b/module/form/form.php @@ -0,0 +1,421 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class form extends common { + + const VERSION = '2.11'; + const REALNAME = 'Formulaire'; + const DELETE = true; + const UPDATE = '0.0'; + const DATADIRECTORY = ''; // Contenu localisé inclus par défaut (page.json et module.json) + + public static $actions = [ + 'config' => self::GROUP_MODERATOR, + 'data' => self::GROUP_MODERATOR, + 'delete' => self::GROUP_MODERATOR, + 'deleteall' => self::GROUP_MODERATOR, + 'index' => self::GROUP_VISITOR, + 'export2csv' => self::GROUP_MODERATOR, + 'output2csv' => self::GROUP_MODERATOR + ]; + + public static $data = []; + + public static $pages = []; + + public static $pagination; + + + // Objets + const TYPE_MAIL = 'mail'; + const TYPE_SELECT = 'select'; + const TYPE_TEXT = 'text'; + const TYPE_TEXTAREA = 'textarea'; + const TYPE_DATETIME = 'date'; + const TYPE_CHECKBOX = 'checkbox'; + const TYPE_LABEL = 'label'; + const ITEMSPAGE = 10; + + + public static $types = [ + self::TYPE_LABEL => 'Etiquette', + self::TYPE_TEXT => 'Champ texte', + self::TYPE_TEXTAREA => 'Grand champ texte', + self::TYPE_MAIL => 'Champ mail', + self::TYPE_SELECT => 'Sélection', + self::TYPE_CHECKBOX => 'Case à cocher', + self::TYPE_DATETIME => 'Date' + ]; + + public static $listUsers = [ + ]; + + public static $signature = [ + 'text' => 'Nom du site', + 'logo' => 'Logo du site' + ]; + + public static $logoWidth = [ + '40' => '40%', + '60' => '60%', + '80' => '80%', + '100' => '100%' + ]; + + /** + * Configuration + */ + public function config() { + // Liste des utilisateurs + $userIdsFirstnames = helper::arrayCollumn($this->getData(['user']), 'firstname'); + ksort($userIdsFirstnames); + self::$listUsers [] = ''; + foreach($userIdsFirstnames as $userId => $userFirstname) { + self::$listUsers [] = $userId; + } + // Soumission du formulaire + if($this->isPost()) { + // Configuration + $this->setData([ + 'module', + $this->getUrl(0), + 'config', + [ + 'button' => $this->getInput('formConfigButton'), + 'captcha' => $this->getInput('formConfigCaptcha', helper::FILTER_BOOLEAN), + 'group' => $this->getInput('formConfigGroup', helper::FILTER_INT), + 'user' => self::$listUsers [$this->getInput('formConfigUser', helper::FILTER_INT)], + 'mail' => $this->getInput('formConfigMail') , + 'pageId' => $this->getInput('formConfigPageIdToggle', helper::FILTER_BOOLEAN) === true ? $this->getInput('formConfigPageId', helper::FILTER_ID) : '', + 'subject' => $this->getInput('formConfigSubject'), + 'replyto' => $this->getInput('formConfigMailReplyTo', helper::FILTER_BOOLEAN), + 'signature' => $this->getInput('formConfigSignature'), + 'logoUrl' => $this->getInput('formConfigLogo'), + 'logoWidth' => $this->getInput('formConfigLogoWidth') + ] + ]); + // Génération des données vides + if ($this->getData(['module', $this->getUrl(0), 'data']) === null) { + $this->setData(['module', $this->getUrl(0), 'data', []]); + } + // Génération des champs + $inputs = []; + foreach($this->getInput('formConfigPosition', null) as $index => $position) { + $inputs[] = [ + 'name' => htmlspecialchars_decode($this->getInput('formConfigName[' . $index . ']'),ENT_QUOTES), + 'position' => helper::filter($position, helper::FILTER_INT), + 'required' => $this->getInput('formConfigRequired[' . $index . ']', helper::FILTER_BOOLEAN), + 'type' => $this->getInput('formConfigType[' . $index . ']'), + 'values' => $this->getInput('formConfigValues[' . $index . ']') + ]; + } + $this->setData(['module', $this->getUrl(0), 'input', $inputs]); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Modifications enregistrées', + 'redirect' => helper::baseUrl() . $this->getUrl(), + 'state' => true + ]); + } + // Liste des pages + foreach($this->getHierarchy(null, false) as $parentPageId => $childrenPageIds) { + self::$pages[$parentPageId] = $this->getData(['page', $parentPageId, 'title']); + foreach($childrenPageIds as $childKey) { + self::$pages[$childKey] = '    ' . $this->getData(['page', $childKey, 'title']); + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration du module', + 'vendor' => [ + 'html-sortable', + 'flatpickr' + ], + 'view' => 'config' + ]); + } + + /** + * Données enregistrées + */ + public function data() { + $data = $this->getData(['module', $this->getUrl(0), 'data']); + if($data) { + // Pagination + $pagination = helper::pagination($data, $this->getUrl(),self::ITEMSPAGE); + // Liste des pages + self::$pages = $pagination['pages']; + // Inverse l'ordre du tableau + $dataIds = array_reverse(array_keys($data)); + $data = array_reverse($data); + // Données en fonction de la pagination + for($i = $pagination['first']; $i < $pagination['last']; $i++) { + $content = ''; + foreach($data[$i] as $input => $value) { + $content .= $input . ' : ' . $value . '
'; + } + self::$data[] = [ + $content, + template::button('formDataDelete' . $dataIds[$i], [ + 'class' => 'formDataDelete buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $dataIds[$i] . '/' . $_SESSION['csrf'], + 'value' => template::ico('cancel') + ]) + ]; + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Données enregistrées', + 'view' => 'data' + ]); + } + + /** + * Export CSV + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + public function export2csv() { + // Jeton incorrect + if ($this->getUrl(2) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/data', + 'notification' => 'Action non autorisée' + ]); + } else { + $data = $this->getData(['module', $this->getUrl(0), 'data']); + if ($data !== []) { + $csvfilename = 'data-'.date('dmY').'-'.date('hm').'-'.rand(10,99).'.csv'; + if (!file_exists(self::FILE_DIR.'source/data')) { + mkdir(self::FILE_DIR.'source/data', 0755); + } + $fp = fopen(self::FILE_DIR.'source/data/'.$csvfilename, 'w'); + fputcsv($fp, array_keys($data[1]), ';','"'); + foreach ($data as $fields) { + fputcsv($fp, $fields, ';','"'); + } + fclose($fp); + // Valeurs en sortie + $this->addOutput([ + 'notification' => 'Export CSV effectué dans le gestionnaire de fichiers
sous le nom '.$csvfilename, + 'redirect' => helper::baseUrl() . $this->getUrl(0) .'/data', + 'state' => true + ]); + } else { + $this->addOutput([ + 'notification' => 'Aucune donnée à exporter', + 'redirect' => helper::baseUrl() . $this->getUrl(0) .'/data' + ]); + } + } + } + + + /** + * Suppression + */ + public function deleteall() { + // Jeton incorrect + if ($this->getUrl(2) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/data', + 'notification' => 'Action non autorisée' + ]); + } else { + $data = ($this->getData(['module', $this->getUrl(0), 'data'])); + if (count($data) > 0 ) { + // Suppression multiple + for ($i = 1; $i <= count($data) ; $i++) { + echo $this->deleteData(['module', $this->getUrl(0), 'data', $i]); + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/data', + 'notification' => 'Données supprimées', + 'state' => true + ]); + } else { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/data', + 'notification' => 'Aucune donnée à supprimer' + ]); + } + } + } + + + /** + * Suppression + */ + public function delete() { + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/data', + 'notification' => 'Action non autorisée' + ]); + } else { + // La donnée n'existe pas + if($this->getData(['module', $this->getUrl(0), 'data', $this->getUrl(2)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Suppression + else { + $this->deleteData(['module', $this->getUrl(0), 'data', $this->getUrl(2)]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/data', + 'notification' => 'Donnée supprimée', + 'state' => true + ]); + } + } + } + + + + + /** + * Accueil + */ + public function index() { + // Soumission du formulaire + if($this->isPost()) { + // Check la captcha + if( + $this->getData(['module', $this->getUrl(0), 'config', 'captcha']) + // AND $this->getInput('formcaptcha', helper::FILTER_INT) !== $this->getInput('formcaptchaFirstNumber', helper::FILTER_INT) + $this->getInput('formcaptchaSecondNumber', helper::FILTER_INT)) + AND password_verify($this->getInput('formCaptcha', helper::FILTER_INT), $this->getInput('formCaptchaResult') ) === false ) + { + self::$inputNotices['formCaptcha'] = 'Incorrect'; + + } + // Préparation le contenu du mail + $data = []; + $replyTo = null; + $content = ''; + foreach($this->getData(['module', $this->getUrl(0), 'input']) as $index => $input) { + // Filtre la valeur + switch($input['type']) { + case self::TYPE_MAIL: + $filter = helper::FILTER_MAIL; + break; + case self::TYPE_TEXTAREA: + $filter = helper::FILTER_STRING_LONG; + break; + case self::TYPE_DATETIME: + $filter = helper::FILTER_STRING_SHORT; // Mettre TYPE_DATETIME pour récupérer un TIMESTAMP + break; + case self::TYPE_CHECKBOX: + $filter = helper::FILTER_BOOLEAN; + break; + default: + $filter = helper::FILTER_STRING_SHORT; + } + $value = $this->getInput('formInput[' . $index . ']', $filter, $input['required']) === true ? 'X' : $this->getInput('formInput[' . $index . ']', $filter, $input['required']); + // premier champ email ajouté au mail en reply si option active + if ($this->getData(['module', $this->getUrl(0), 'config', 'replyto']) === true && + $input['type'] === 'mail') { + $replyTo = $value; + } + // Préparation des données pour la création dans la base + $data[$this->getData(['module', $this->getUrl(0), 'input', $index, 'name'])] = $value; + // Préparation des données pour le mail + $content .= '' . $this->getData(['module', $this->getUrl(0), 'input', $index, 'name']) . ' : ' . $value . '
'; + } + // Crée les données + $this->setData(['module', $this->getUrl(0), 'data', helper::increment(1, $this->getData(['module', $this->getUrl(0), 'data'])), $data]); + // Envoi du mail + // Rechercher l'adresse en fonction du mail + $sent = true; + $singleuser = $this->getData(['user', + $this->getData(['module', $this->getUrl(0), 'config', 'user']), + 'mail']); + $singlemail = $this->getData(['module', $this->getUrl(0), 'config', 'mail']); + $group = $this->getData(['module', $this->getUrl(0), 'config', 'group']); + // Verification si le mail peut être envoyé + if( + self::$inputNotices === [] && ( + $group > 0 || + $singleuser !== '' || + $singlemail !== '' ) + ) { + // Utilisateurs dans le groupe + $to = []; + if ($group > 0){ + foreach($this->getData(['user']) as $userId => $user) { + if($user['group'] >= $group) { + $to[] = $user['mail']; + } + } + } + // Utilisateur désigné + if (!empty($singleuser)) { + $to[] = $singleuser; + } + // Mail désigné + if (!empty($singlemail)) { + $to[] = $singlemail; + } + if($to) { + // Sujet du mail + $subject = $this->getData(['module', $this->getUrl(0), 'config', 'subject']); + if($subject === '') { + $subject = 'Nouveau message en provenance de votre site'; + } + // Envoi le mail + $sent = $this->sendMail( + $to, + $subject, + 'Nouveau message en provenance de la page "' . $this->getData(['page', $this->getUrl(0), 'title']) . '" :

' . + $content, + $replyTo + ); + } + } + // Redirection + $redirect = $this->getData(['module', $this->getUrl(0), 'config', 'pageId']); + // Valeurs en sortie + $this->addOutput([ + 'notification' => ($sent === true ? 'Formulaire soumis' : $sent), + 'redirect' => $redirect ? helper::baseUrl() . $redirect : '', + 'state' => ($sent === true ? true : null), + 'vendor' => [ + 'flatpickr' + ], + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'showBarEditButton' => true, + 'showPageContent' => true, + 'view' => 'index', + 'vendor' => [ + 'flatpickr' + ], + ]); + } +} diff --git a/module/form/vendor/html-sortable/html-sortable.min.js b/module/form/vendor/html-sortable/html-sortable.min.js new file mode 100644 index 0000000..9a96985 --- /dev/null +++ b/module/form/vendor/html-sortable/html-sortable.min.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.sortable=t()}(this,function(){"use strict";function e(e,t,n){var r=null;return 0===t?e:function(){var a=n||this,o=arguments;clearTimeout(r),r=setTimeout(function(){e.apply(a,o)},t)}}var t,n,r,a=[],o=[],i=function(e,t,n){return void 0===n?e&&e.h5s&&e.h5s.data&&e.h5s.data[t]:(e.h5s=e.h5s||{},e.h5s.data=e.h5s.data||{},e.h5s.data[t]=n,void 0)},s=function(e){e.h5s&&delete e.h5s.data};switch(!0){case"matches"in window.Element.prototype:r="matches";break;case"mozMatchesSelector"in window.Element.prototype:r="mozMatchesSelector";break;case"msMatchesSelector"in window.Element.prototype:r="msMatchesSelector";break;case"webkitMatchesSelector"in window.Element.prototype:r="webkitMatchesSelector"}var l=function(e,t){if(!t)return Array.prototype.slice.call(e);for(var n=[],a=0;an){var c=o-n,f=p(e).top;if(id&&r>f+o-c)return}void 0===t.oldDisplay&&(t.oldDisplay=t.style.display),t.style.display="none",i + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** NE PAS EFFACER +* admin.css +*/ + diff --git a/module/form/view/config/config.js.php b/module/form/view/config/config.js.php new file mode 100644 index 0000000..b2dbb59 --- /dev/null +++ b/module/form/view/config/config.js.php @@ -0,0 +1,218 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Ajout d'un champ + */ +function add(inputUid, input) { + // Nouveau champ + var newInput = $($("#formConfigCopy").html()); + // Ajout de l'ID unique aux champs + newInput.find("a, input, select").each(function() { + var _this = $(this); + _this.attr({ + id: _this.attr("id").replace("[]", "[" + inputUid + "]"), + name: _this.attr("name").replace("[]", "[" + inputUid + "]") + }); + }); + newInput.find("label").each(function() { + var _this = $(this); + _this.attr("for", _this.attr("for").replace("[]", "[" + inputUid + "]")); + }); + // Attribue les bonnes valeurs + if(input) { + // Nom du champ + newInput.find("[name='formConfigName[" + inputUid + "]']").val(input.name); + // Type de champ + newInput.find("[name='formConfigType[" + inputUid + "]']").val(input.type); + // Largeur du champ + newInput.find("[name='formConfigWidth[" + inputUid + "]']").val(input.width); + // Valeurs du champ + newInput.find("[name='formConfigValues[" + inputUid + "]']").val(input.values); + // Champ obligatoire + newInput.find("[name='formConfigRequired[" + inputUid + "]']").prop("checked", input.required); + } + // Ajout du nouveau champ au DOM + $("#formConfigInputs") + .append(newInput.hide()) + .find(".formConfigInput").last().show(); + // Cache le texte d'absence de champ + $("#formConfigNoInput:visible").hide(); + // Check le type + $(".formConfigType").trigger("change"); + // Actualise les positions + position(); +} + +/** + * Calcul des positions + */ +function position() { + $("#formConfigInputs").find(".formConfigPosition").each(function(i) { + $(this).val(i + 1); + }); +} + +/** + * Ajout des champs déjà existant + */ +var inputUid = 0; +var inputs = getData(['module', $this->getUrl(0), 'input'])); ?>; +if(inputs) { + var inputsPerPosition = getData(['module', $this->getUrl(0), 'input']), 'position', 'SORT_ASC')); ?>; + $.each(inputsPerPosition, function(id) { + add(inputUid, inputs[id]); + inputUid++; + }); +} + +/** + * Afficher/cacher les options supplémentaires + */ +$(document).on("click", ".formConfigMoreToggle", function() { + + $(this).parents(".formConfigInput").find(".formConfigMore").slideToggle(); + $(this).parents(".formConfigInput").find(".formConfigMoreLabel").slideToggle(); +}); + +/** + * Crée un nouveau champ à partir des champs cachés + */ +$("#formConfigAdd").on("click", function() { + add(inputUid); + inputUid++; +}); + +/** + * Actions sur les champs + */ +// Tri entre les champs +sortable("#formConfigInputs", { + forcePlaceholderSize: true, + containment: "#formConfigInputs", + handle: ".formConfigMove" +}); +$("#formConfigInputs") + // Actualise les positions + .on("sortupdate", function() { + position(); + }) + // Suppression du champ + .on("click", ".formConfigDelete", function() { + var inputDOM = $(this).parents(".formConfigInput"); + // Cache le champ + inputDOM.hide(); + // Supprime le champ + inputDOM.remove(); + // Affiche le texte d'absence de champ + if($("#formConfigInputs").find(".formConfigInput").length === 0) { + $("#formConfigNoInput").show(); + } + // Actualise les positions + position(); + }) + // Affiche/cache le champ "Valeurs" en fonction des champs cachés + .on("change", ".formConfigType", function() { + var _this = $(this); + switch (_this.val()) { + case "select": + _this.parents(".formConfigInput").find("label[for*=formConfigRequired]").show(); + _this.parents(".formConfigInput").find(".formConfigValuesWrapper").slideDown(); + _this.parents(".formConfigInput").find(".formConfigLabelWrapper").slideUp(); + break; + case "label": + _this.parents(".formConfigInput").find("label[for*=formConfigRequired]").hide(); + _this.parents(".formConfigInput").find(".formConfigLabelWrapper").slideDown(); + _this.parents(".formConfigInput").find(".formConfigValuesWrapper").slideUp(); + break; + default: + _this.parents(".formConfigInput").find("label[for*=formConfigRequired]").show(); + _this.parents(".formConfigInput").find(".formConfigValuesWrapper").slideUp(); + _this.parents(".formConfigInput").find(".formConfigLabelWrapper").slideUp(); + } + }); +// Simule un changement de type au chargement de la page +$(".formConfigType").trigger("change"); + +/** + * Affiche/cache les options de la case à cocher du mail + */ +$("#formConfigMailOptionsToggle").on("change", function() { + if($(this).is(":checked")) { + $("#formConfigMailOptions").slideDown(); + } + else { + $("#formConfigMailOptions").slideUp(function() { + $("#formConfigGroup").val(""); + $("#formConfigSubject").val(""); + $("#formConfigMail").val(""); + $("#formConfigUser").val(""); + }); + } +}).trigger("change"); + +/** + * Affiche/cache les options de la case à cocher de la redirection + */ +$("#formConfigPageIdToggle").on("change", function() { + if($(this).is(":checked")) { + $("#formConfigPageIdWrapper").slideDown(); + } + else { + $("#formConfigPageIdWrapper").slideUp(function() { + $("#formConfigPageId").val(""); + }); + } +}).trigger("change"); + +/** +* Paramètres par défaut au chargement +*/ +$( document ).ready(function() { + + /** + * Masquer ou afficher la sélection du logo + */ + if ($("#formConfigSignature").val() !== "text") { + $("#formConfigLogoWrapper").addClass("disabled"); + $("#formConfigLogoWrapper").slideDown(); + $("#formConfigLogoWidthWrapper").addClass("disabled"); + $("#formConfigLogoWidthWrapper").slideDown(); + } else { + $("#formConfigLogoWrapper").removeClass("disabled"); + $("#formConfigLogoWrapper").slideUp(); + $("#formConfigLogoWidthWrapper").removeClass("disabled"); + $("#formConfigLogoWidthWrapper").slideUp(); + } +}); + +/** + * Masquer ou afficher la sélection du logo + */ +var formConfigSignatureDOM = $("#formConfigSignature"); +formConfigSignatureDOM.on("change", function() { + if ($(this).val() !== "text") { + $("#formConfigLogoWrapper").addClass("disabled"); + $("#formConfigLogoWrapper").slideDown(); + $("#formConfigLogoWidthWrapper").addClass("disabled"); + $("#formConfigLogoWidthWrapper").slideDown(); + } else { + $("#formConfigLogoWrapper").removeClass("disabled"); + $("#formConfigLogoWrapper").slideUp(); + $("#formConfigLogoWidthWrapper").removeClass("disabled"); + $("#formConfigLogoWidthWrapper").slideUp(); + } +}); diff --git a/module/form/view/config/config.php b/module/form/view/config/config.php new file mode 100644 index 0000000..6631a20 --- /dev/null +++ b/module/form/view/config/config.php @@ -0,0 +1,189 @@ +
+
+ 'formConfigPosition' + ]); ?> +
+
+ template::ico('sort'), + 'class' => 'formConfigMove' + ]); ?> +
+
+ 'Intitulé' + ]); ?> +
+
+ 'formConfigType' + ]); ?> +
+
+ template::ico('gear'), + 'class' => 'formConfigMoreToggle' + ]); ?> +
+
+ template::ico('minus'), + 'class' => 'formConfigDelete' + ]); ?> +
+
+
+ 'displayNone formConfigLabelWrapper' + ]); ?> +
+
+ 'Liste des valeurs séparées par des virgules (valeur1,valeur2,...)', + 'class' => 'formConfigValues', + 'classWrapper' => 'displayNone formConfigValuesWrapper' + ]); ?> + +
+
+
+ +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'page/edit/' . $this->getUrl(0), + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ helper::baseUrl() . $this->getUrl(0) . '/data', + 'value' => 'Gérer les données' + ]); ?> +
+
+ +
+
+
+
+
+

Configuration

+ 'Laissez vide afin de conserver le texte par défaut.', + 'label' => 'Texte du bouton de soumission', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'button']) + ]); ?> + (bool) $this->getData(['module', $this->getUrl(0), 'config', 'group']) || + !empty($this->getData(['module', $this->getUrl(0), 'config', 'user'])) || + !empty($this->getData(['module', $this->getUrl(0), 'config', 'mail'])), + 'help' => 'Sélectionnez au moins un groupe, un utilisateur ou saississez un email. Votre serveur doit autoriser les envois de mail.' + ]); ?> +
+
+
+ 'Laissez vide afin de conserver le texte par défaut.', + 'label' => 'Sujet du mail', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'subject']) + ]); ?> +
+
+ +
+
+ 'Aux groupes à partir de', + 'selected' => $this->getData(['module', $this->getUrl(0), 'config', 'group']), + 'help' => 'Editeurs = éditeurs + administrateurs
Membres = membres + éditeurs + administrateurs' + ]); ?> +
+
+ 'A un membre', + 'selected' => array_search($this->getData(['module', $this->getUrl(0), 'config', 'user']),$module::$listUsers) + ]); ?> +
+
+ 'A une adresse email', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'mail']), + 'help' => 'Un email ou une liste de diffusion' + ]); ?> +
+
+
+
+ (bool) $this->getData(['module', $this->getUrl(0), 'config', 'replyto']), + 'help' => 'Cette option permet de réponse drectement à l\'expéditeur du message si celui-ci a indiqué un email valide.' + ]); ?> +
+
+
+
+
+ 'Sélectionner le type de signature', + 'selected' => $this->getData(['module', $this->getUrl(0), 'config', 'signature']) + ]); ?> +
+
+ 'Sélectionnez le logo du site', + 'label' => 'Logo', + 'value' => $this->getData(['module', $this->getUrl(0), 'config', 'logoUrl']) + ]); ?> +
+
+ 'Sélectionner la largeur du logo', + 'selected' => $this->getData(['module', $this->getUrl(0), 'config', 'logoWidth']) + ]); ?> +
+
+
+
+ (bool) $this->getData(['module', $this->getUrl(0), 'config', 'pageId']) + ]); ?> +
+
+ 'displayNone', + 'label' => 'Sélectionner une page du site :', + 'selected' => $this->getData(['module', $this->getUrl(0), 'config', 'pageId']) + ]); ?> +
+
+ $this->getData(['module', $this->getUrl(0), 'config', 'captcha']) + ]); ?> +
+
+

Liste des champs

+
+ +
+
+
+
+ template::ico('plus') + ]); ?> +
+
+
+
+
+ +
Version n° + +
diff --git a/module/form/view/data/data.css b/module/form/view/data/data.css new file mode 100644 index 0000000..7a3bf64 --- /dev/null +++ b/module/form/view/data/data.css @@ -0,0 +1,20 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + +/** NE PAS EFFACER +* admin.css +*/ \ No newline at end of file diff --git a/module/form/view/data/data.js.php b/module/form/view/data/data.js.php new file mode 100644 index 0000000..15edac9 --- /dev/null +++ b/module/form/view/data/data.js.php @@ -0,0 +1,35 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Confirmation de suppression + */ +$(".formDataDelete").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir supprimer cette donnée ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); + +/** + * Confirmation de suppression de toutes les donénes + */ +$(".formDataDeleteAll").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir supprimer toutes les données ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); \ No newline at end of file diff --git a/module/form/view/data/data.php b/module/form/view/data/data.php new file mode 100644 index 0000000..d0a2286 --- /dev/null +++ b/module/form/view/data/data.php @@ -0,0 +1,34 @@ +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'formDataDeleteAll buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/deleteall' . '/' . $_SESSION['csrf'], + 'ico' => 'cancel', + 'value' => 'Tout effacer' + ]); ?> +
+
+ helper::baseUrl() . $this->getUrl(0) . '/export2csv' . '/' . $_SESSION['csrf'], + 'ico' => 'download', + 'value' => 'Export CSV' + ]); ?> +
+
+ + + + + + +
Version n° + +
\ No newline at end of file diff --git a/module/form/view/index/index.js.php b/module/form/view/index/index.js.php new file mode 100644 index 0000000..9d3d762 --- /dev/null +++ b/module/form/view/index/index.js.php @@ -0,0 +1,28 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +/** + * Paramétrage du format de date + */ +$(function() { + $(".datepicker").flatpickr({ + altInput: true, + altFormat: "d/m/Y", + enableTime: false, + locale: "fr", + dateFormat: "d/m/Y" + }); +}); diff --git a/module/form/view/index/index.php b/module/form/view/index/index.php new file mode 100644 index 0000000..6eba5cc --- /dev/null +++ b/module/form/view/index/index.php @@ -0,0 +1,67 @@ +getData(['module', $this->getUrl(0), 'input'])): ?> + + getData(['module', $this->getUrl(0), 'input']) as $index => $input): ?> + + 'formInput_' . $index, + 'label' => $input['name'] + ]); ?> + + $key) { + $values[$value] = trim($value); + } + ?> + 'formInput_' . $index, + 'label' => $input['name'] + ]); ?> + + 'formInput_' . $index, + 'label' => $input['name'] + ]); ?> + + 'formInput_' . $index, + 'label' => $input['name'] + ]); ?> + + 'formInput_' . $index, + 'label' => $input['name'], + 'vendor' => 'flatpickr' + ]); ?> + + + +

+ +
+

+ + + getData(['module', $this->getUrl(0), 'config', 'captcha'])): ?> +
+
+ $this->getData(['config','connect', 'captchaStrong']), + 'type' => $this->getData(['config','connect', 'captchaType']) + ]); ?> +
+
+ +
+
+ $this->getData(['module', $this->getUrl(0), 'config', 'button']) ? $this->getData(['module', $this->getUrl(0), 'config', 'button']) : 'Envoyer', + 'ico' => '' + ]); ?> +
+
+ + + + \ No newline at end of file diff --git a/module/gallery/gallery.php b/module/gallery/gallery.php new file mode 100644 index 0000000..4264293 --- /dev/null +++ b/module/gallery/gallery.php @@ -0,0 +1,823 @@ + + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +class gallery extends common { + + + const VERSION = '3.3'; + const REALNAME = 'Galerie'; + const DELETE = true; + const UPDATE = '0.0'; + const DATADIRECTORY = self::DATA_DIR . 'gallery/'; + + const SORT_ASC = 'SORT_ASC'; + const SORT_DSC = 'SORT_DSC'; + const SORT_HAND = 'SORT_HAND'; + + + public static $directories = []; + + public static $firstPictures = []; + + public static $galleries = []; + + public static $galleriesId = []; + + public static $pictures = []; + + public static $picturesId = []; + + public static $thumbs = []; + + public static $actions = [ + 'config' => self::GROUP_MODERATOR, + 'delete' => self::GROUP_MODERATOR, + 'dirs' => self::GROUP_MODERATOR, + 'sortGalleries' => self::GROUP_MODERATOR, + 'sortPictures' => self::GROUP_MODERATOR, + 'edit' => self::GROUP_MODERATOR, + 'theme' => self::GROUP_MODERATOR, + 'index' => self::GROUP_VISITOR + ]; + + public static $sort = [ + self::SORT_ASC => 'Alphabétique ', + self::SORT_DSC => 'Alphabétique inverse', + self::SORT_HAND => 'Manuel' + ]; + + public static $galleryThemeFlexAlign = [ + 'flex-start' => 'À gauche', + 'center' => 'Au centre', + 'flex-end' => 'À droite', + 'space-around' => 'Distribué avec marges', + 'space-between' => 'Distribué sans marge', + ]; + + public static $galleryThemeAlign = [ + 'left' => 'À gauche', + 'center' => 'Au centre', + 'right' => 'À droite' + ]; + + public static $galleryThemeSizeWidth = [ + '9em' => 'Très petite', + '12em' => 'Petite', + '15em' => 'Moyenne', + '18em' => 'Grande', + '21em' => 'Très grande', + '100%' => 'Proportionnelle' + ]; + + public static $galleryThemeSizeHeight = [ + '9em' => 'Très petite', + '12em' => 'Petite', + '15em' => 'Moyenne', + '18em' => 'Grande', + '21em' => 'Très grande' + ]; + + public static $galleryThemeLegendHeight = [ + '.125em' => 'Très petite', + '.25em' => 'Petite', + '.375em' => 'Moyenne', + '.5em' => 'Grande', + '.625em' => 'Très grande' + ]; + + public static $galleryThemeBorder = [ + '0em' => 'Aucune', + '.1em' => 'Très fine', + '.3em' => 'Fine', + '.5em' => 'Moyenne', + '.7em' => 'Epaisse', + '.9em' => 'Très épaisse' + ]; + + public static $galleryThemeOpacity = [ + '1' => 'Aucun ', + '.9' => 'Très Discrète', + '.8' => 'Discrète', + '.7' => 'Moyenne', + '.6' => 'Forte', + '.5' => 'Très forte' + ]; + + public static $galleryThemeMargin = [ + '0em' => 'Aucune', + '.1em' => 'Très petite', + '.3em' => 'Petite', + '.5em' => 'Moyenne', + '.7em' => 'Grande', + '.9em' => 'Très grande' + ]; + + public static $galleryThemeRadius = [ + '0em' => 'Aucun', + '.3em' => 'Très léger', + '.6em' => 'Léger', + '.9em' => 'Moyen', + '1.2em' => 'Important', + '1.5em' => 'Très important' + ]; + + public static $galleryThemeShadows = [ + '0px' => 'Aucune', + '1px 1px 5px' => 'Très légère', + '1px 1px 10px' => 'Légère', + '1px 1px 15px' => 'Moyenne', + '1px 1px 25px' => 'Importante', + '1px 1px 50px' => 'Très importante' + ]; + + /** + * Mise à jour du module + * Appelée par les fonctions index et config + */ + private function update() { + + + // Initialisation du module, créer les données si elles sont manquantes. + $this->init(); + + $versionData = $this->getData(['module',$this->getUrl(0),'config', 'versionData' ]); + // Mise à jour 3.1 + if (version_compare($versionData, '3.1', '<') ) { + if (is_dir(self::DATADIRECTORY . 'pages/')) { + // Déplacer les données du dossier Pages + $this->copyDir(self::DATADIRECTORY . 'pages/' . $this->getUrl(0), self::DATADIRECTORY . $this->getUrl(0)); + $this->removeDir(self::DATADIRECTORY . 'pages/'); + $style = $this->getData(['module', $this->getUrl(0), 'theme', 'style']); + $this->setData(['module', $this->getUrl(0), 'theme', 'style', str_replace('pages/', '', $style)]); + } + // Mettre à jour la version + $this->setData(['module',$this->getUrl(0),'config', 'versionData', '3.1' ]); + } + } + + /** + * Initialisation séparée des éléments absents + * Thème + * Config + * Content + */ + private function init() { + + // Mise à jour d'une version inférieure, la gallery existe mais pas la variable content + if ($this->getData(['module', $this->getUrl(0)]) && + $this->getData(['module', $this->getUrl(0), 'content']) === NULL ) { + + // Changement de l'arborescence dans module.json + $data = $this->getData(['module', $this->getUrl(0)]); + $this->deleteData(['module', $this->getUrl(0)]); + $this->setData(['module', $this->getUrl(0), 'content', $data]); + + // Effacer les fichiers CSS de l'ancienne version + if (file_exists('module/gallery/view/index/index.css')) { + unlink('module/gallery/view/index/index.css'); + } + if (file_exists('module/gallery/view/gallery/gallery.css')) { + unlink('module/gallery/view/gallery/gallery.css'); + } + // Stockage des données du thème de la gallery existant + if (is_array($this->getData(['theme','gallery']))) { + $data = $this->getData(['theme','gallery']); + $this->deleteData(['theme','gallery']); + $this->setData(['module', $this->getUrl(0), 'theme', $data]); + // Nom de la feuille de style + $this->setData(['module', $this->getUrl(0), 'theme', 'style', self::DATADIRECTORY . $this->getUrl(0) . '/theme.css']); + } + } + + // Variable commune + $fileCSS = self::DATADIRECTORY . $this->getUrl(0) . '/theme.css' ; + + // Check la présence des données de thème + if ( $this->getData(['module', $this->getUrl(0), 'theme']) === null ) { + require_once('module/gallery/ressource/defaultdata.php'); + $this->setData(['module', $this->getUrl(0), 'theme', theme::$defaultTheme]); + // Nom de la feuille de style + $this->setData(['module', $this->getUrl(0), 'theme', 'style', $fileCSS]); + } + + // Check la présence de la feuille de style + if ( !file_exists(self::DATADIRECTORY . $this->getUrl(0) . '/theme.css')) { + // Dossier de l'instance + if (!is_dir(self::DATADIRECTORY . $this->getUrl(0) )) { + mkdir (self::DATADIRECTORY . $this->getUrl(0), 0755, true); + } + // Générer la feuille de CSS + $content = file_get_contents('module/gallery/ressource/vartheme.css'); + $themeCss = file_get_contents('module/gallery/ressource/theme.css'); + + // Injection des variables + $content = str_replace('#thumbAlign#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbAlign']),$content ); + $content = str_replace('#thumbWidth#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbWidth']),$content ); + $content = str_replace('#thumbHeight#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbHeight']),$content ); + $content = str_replace('#thumbMargin#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbMargin']),$content ); + $content = str_replace('#thumbBorder#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbBorder']),$content ); + $content = str_replace('#thumbBorderColor#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbBorderColor']),$content ); + $content = str_replace('#thumbOpacity#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbOpacity']),$content ); + $content = str_replace('#thumbShadows#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbShadows']),$content ); + $content = str_replace('#thumbShadowsColor#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbShadowsColor']),$content ); + $content = str_replace('#thumbRadius#',$this->getData(['module', $this->getUrl(0), 'theme', 'thumbRadius']),$content ); + $content = str_replace('#legendAlign#',$this->getData(['module', $this->getUrl(0), 'theme', 'legendAlign']),$content ); + $content = str_replace('#legendHeight#',$this->getData(['module', $this->getUrl(0), 'theme', 'legendHeight']),$content ); + $content = str_replace('#legendTextColor#',$this->getData(['module', $this->getUrl(0), 'theme', 'legendTextColor']),$content ); + $content = str_replace('#legendBgColor#',$this->getData(['module', $this->getUrl(0), 'theme', 'legendBgColor']),$content ); + // Ecriture de la feuille de style + file_put_contents(self::DATADIRECTORY . $this->getUrl(0) . '/theme.css' , $content . $themeCss); + // Nom de la feuille de style + $this->setData(['module', $this->getUrl(0), 'theme', 'style', $fileCSS]); + } + + // Check la présence de la config + if ( $this->getData(['module', $this->getUrl(0), 'config']) === null ) { + require_once('module/gallery/ressource/defaultdata.php'); + $this->setData(['module', $this->getUrl(0), 'config', theme::$defaultData]); + } + + // Contenu vide de la galerie + if (!is_array($this->getData(['module', $this->getUrl(0), 'content'])) ) { + $this->setData(['module', $this->getUrl(0), 'content', array() ]); + } + } + + + /** + * Tri de la liste des galeries + * + */ + public function sortGalleries() { + if($_POST['response']) { + $data = explode('&',$_POST['response']); + $data = str_replace('galleryTable%5B%5D=','',$data); + for($i=0;$isetData(['module', $this->getUrl(0), 'content', $data[$i], [ + 'config' => [ + 'name' => $this->getData(['module',$this->getUrl(0), 'content', $data[$i],'config','name']), + 'directory' => $this->getData(['module',$this->getUrl(0), 'content', $data[$i],'config','directory']), + 'homePicture' => $this->getData(['module',$this->getUrl(0), 'content', $data[$i],'config','homePicture']), + 'sort' => $this->getData(['module',$this->getUrl(0), 'content', $data[$i],'config','sort']), + 'position'=> $i, + 'fullScreen' => $this->getData(['module',$this->getUrl(0), 'content',$data[$i],'config','fullScreen']) + + ], + 'legend' => $this->getData(['module',$this->getUrl(0), 'content', $data[$i],'legend']), + 'positions' => $this->getData(['module',$this->getUrl(0), 'content', $data[$i],'positions']) + ]]); + } + } + } + + /** + * Tri de la liste des images + * + */ + public function sortPictures() { + if($_POST['response']) { + $galleryName = $_POST['gallery']; + $data = explode('&',$_POST['response']); + $data = str_replace('galleryTable%5B%5D=','',$data); + // Sauvegarder + $this->setData(['module', $this->getUrl(0), 'content', $galleryName, [ + 'config' => [ + 'name' => $this->getData(['module',$this->getUrl(0),$galleryName,'config','name']), + 'directory' => $this->getData(['module',$this->getUrl(0),$galleryName,'config','directory']), + 'homePicture' => $this->getData(['module',$this->getUrl(0),$galleryName,'config','homePicture']), + 'sort' => $this->getData(['module',$this->getUrl(0),$galleryName,'config','sort']), + 'position' => $this->getData(['module',$this->getUrl(0),$galleryName,'config','position']), + 'fullScreen' => $this->getData(['module',$this->getUrl(0),$galleryName,'config','fullScreen']) + + ], + 'legend' => $this->getData(['module',$this->getUrl(0),$galleryName,'legend']), + 'positions' => array_flip($data) + ]]); + } + } + + + /** + * Configuration + */ + public function config() { + + // Mise à jour des données de module + $this->update(); + + //Affichage de la galerie triée + $g = $this->getData(['module', $this->getUrl(0), 'content']); + $p = helper::arrayCollumn(helper::arrayCollumn($g,'config'),'position'); + asort($p,SORT_NUMERIC); + $galleries = []; + foreach ($p as $positionId => $item) { + $galleries [$positionId] = $g[$positionId]; + } + // Traitement de l'affichage + if($galleries) { + foreach($galleries as $galleryId => $gallery) { + // Erreur dossier vide + if(is_dir($gallery['config']['directory'])) { + if(count(scandir($gallery['config']['directory'])) === 2) { + $gallery['config']['directory'] = '' . $gallery['config']['directory'] . ' (dossier vide)'; + } + } + // Erreur dossier supprimé + else { + $gallery['config']['directory'] = '' . $gallery['config']['directory'] . ' (dossier introuvable)'; + } + // Met en forme le tableau + self::$galleries[] = [ + template::ico('sort'), + $gallery['config']['name'], + $gallery['config']['directory'], + template::button('galleryConfigEdit' . $galleryId , [ + 'href' => helper::baseUrl() . $this->getUrl(0) . '/edit/' . $galleryId . '/' . $_SESSION['csrf'], + 'value' => template::ico('pencil') + ]), + template::button('galleryConfigDelete' . $galleryId, [ + 'class' => 'galleryConfigDelete buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $galleryId . '/' . $_SESSION['csrf'], + 'value' => template::ico('cancel') + ]) + ]; + // Tableau des id des galleries pour le drag and drop + self::$galleriesId[] = $galleryId; + } + } + // Soumission du formulaire d'ajout d'une galerie + if($this->isPost()) { + if (!$this->getInput('galleryConfigFilterResponse')) { + $galleryId = helper::increment($this->getInput('galleryConfigName', helper::FILTER_ID, true), (array) $this->getData(['module', $this->getUrl(0), 'content'])); + // définir une vignette par défaut + $directory = $this->getInput('galleryConfigDirectory', helper::FILTER_STRING_SHORT, true); + $iterator = new DirectoryIterator($directory); + foreach($iterator as $fileInfos) { + if($fileInfos->isDot() === false AND $fileInfos->isFile() AND @getimagesize($fileInfos->getPathname())) { + // Créer la miniature si manquante + if (!file_exists( str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()))) { + $this->makeThumb($fileInfos->getPathname(), + str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()), + self::THUMBS_WIDTH); + } + // Miniatures + $homePicture = strtolower($fileInfos->getFilename()); + break; + } + } + $this->setData(['module', $this->getUrl(0), 'content', $galleryId, [ + 'config' => [ + 'name' => $this->getInput('galleryConfigName'), + 'directory' => $this->getInput('galleryConfigDirectory', helper::FILTER_STRING_SHORT, true), + 'homePicture' => $homePicture, + 'sort' => self::SORT_ASC, + 'position' => $this->getData(['module', $this->getUrl(0), 'content', $galleryId,'config','position']), + 'fullScreen' => false + ], + 'legend' => [], + 'positions' => [] + ]]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl() /*. '#galleryConfigForm'*/, + 'notification' => 'Modifications enregistrées', + 'state' => true + ]); + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Configuration du module', + 'view' => 'config', + 'vendor' => [ + 'tablednd' + ] + ]); + } + + /** + * Suppression + */ + public function delete() { + // $url prend l'adresse sans le token + // La galerie n'existe pas + if($this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Suppression non autorisée' + ]); + } + // Suppression + else { + $this->deleteData(['module', $this->getUrl(0), 'content', $this->getUrl(2)]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Galerie supprimée', + 'state' => true + ]); + } + } + + /** + * Liste des dossiers + */ + public function dirs() { + // Valeurs en sortie + $this->addOutput([ + 'display' => self::DISPLAY_JSON, + 'content' => galleriesHelper::scanDir(self::FILE_DIR.'source') + ]); + } + + /** + * Édition + */ + public function edit() { + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // La galerie n'existe pas + if($this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // La galerie existe + else { + // Soumission du formulaire + if($this->isPost()) { + // Si l'id a changée + $galleryId = !empty($this->getInput('galleryEditName')) ? $this->getInput('galleryEditName', helper::FILTER_ID, true) : $this->getUrl(2); + if($galleryId !== $this->getUrl(2)) { + // Incrémente le nouvel id de la galerie + $galleryId = helper::increment($galleryId, $this->getData(['module', $this->getUrl(0), 'content'])); + // Transférer la position des images + $oldPositions = $this->getData(['module',$this->getUrl(0), $this->getUrl(2),'positions']); + // Supprime l'ancienne galerie + $this->deleteData(['module', $this->getUrl(0), 'content', $this->getUrl(2)]); + } + // légendes + $legends = []; + foreach((array) $this->getInput('legend', null) as $file => $legend) { + // Image de couverture par défaut si non définie + $homePicture = $file; + $file = str_replace('.','',$file); + $legends[$file] = helper::filter($legend, helper::FILTER_STRING_SHORT); + + } + // Photo de la page de garde de l'album définie dans form + if (is_array($this->getInput('homePicture', null)) ) { + $d = array_keys($this->getInput('homePicture', null)); + $homePicture = $d[0]; + } + // Sauvegarder + if ($this->getInput('galleryEditName')) { + $this->setData(['module', $this->getUrl(0), 'content', $galleryId, [ + 'config' => [ + 'name' => $this->getInput('galleryEditName', helper::FILTER_STRING_SHORT, true), + 'directory' => $this->getInput('galleryEditDirectory', helper::FILTER_STRING_SHORT, true), + 'homePicture' => $homePicture, + // pas de positions, on active le tri alpha + 'sort' => $this->getInput('galleryEditSort'), + 'position' => $this->getData(['module', $this->getUrl(0), 'content', $galleryId,'config','position']), + 'fullScreen' => $this->getInput('galleryEditFullscreen', helper::FILTER_BOOLEAN) + + ], + 'legend' => $legends, + 'positions' => empty($oldPositions) ? $this->getdata(['module', $this->getUrl(0), 'content', $galleryId, 'positions']) : $oldPositions + ]]); + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/edit/' . $galleryId . '/' . $_SESSION['csrf'] , + 'notification' => 'Modifications enregistrées', + 'state' => true + ]); + } + // Met en forme le tableau + $directory = $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2), 'config', 'directory']); + if(is_dir($directory)) { + $iterator = new DirectoryIterator($directory); + + foreach($iterator as $fileInfos) { + if($fileInfos->isDot() === false AND $fileInfos->isFile() AND @getimagesize($fileInfos->getPathname())) { + // Créer la miniature RFM si manquante + if (!file_exists( str_replace('source','thumb',$fileInfos->getPath()) . '/' . strtolower($fileInfos->getFilename()))) { + $this->makeThumb($fileInfos->getPathname(), + str_replace('source','thumb',$fileInfos->getPath()) . '/' . strtolower($fileInfos->getFilename()), + 122); + } + self::$pictures[str_replace('.','',$fileInfos->getFilename())] = [ + template::ico('sort'), + $fileInfos->getFilename(), + template::checkbox( 'homePicture[' . $fileInfos->getFilename() . ']', true, '', [ + 'checked' => $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2),'config', 'homePicture']) === $fileInfos->getFilename() ? true : false, + 'class' => 'homePicture' + ]), + template::text('legend[' . $fileInfos->getFilename() . ']', [ + 'value' => $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2), 'legend', str_replace('.','',$fileInfos->getFilename())]) + ]), + '' + ]; + self::$picturesId [] = str_replace('.','',$fileInfos->getFilename()); + } + } + // Tri des images + switch ($this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2), 'config', 'sort'])) { + case self::SORT_HAND: + $positions = $this->getdata(['module',$this->getUrl(0), $this->getUrl(2),'positions']); + if ($positions) { + foreach ($positions as $key => $value) { + if (array_key_exists($key,self::$pictures)) { + $tempPictures[$key] = self::$pictures[$key]; + $tempPicturesId [] = $key; + } + } + // Images ayant été ajoutées dans le dossier mais non triées + foreach (self::$pictures as $key => $value) { + if (!array_key_exists($key,$tempPictures)) { + $tempPictures[$key] = self::$pictures[$key]; + $tempPicturesId [] = $key; + } + } + self::$pictures = $tempPictures; + self::$picturesId = $tempPicturesId; + } + break; + case self::SORT_ASC: + ksort(self::$pictures,SORT_NATURAL); + sort(self::$picturesId,SORT_NATURAL); + break; + case self::SORT_DSC: + krsort(self::$pictures,SORT_NATURAL); + rsort(self::$picturesId,SORT_NATURAL); + break; + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(2), 'config', 'name']), + 'view' => 'edit', + 'vendor' => [ + 'tablednd' + ] + ]); + } + } + + /** + * Accueil (deux affichages en un pour éviter une url à rallonge) + */ + public function index() { + + // Mise à jour des données de module + $this->update(); + + // Images d'une galerie + if($this->getUrl(1)) { + // La galerie n'existe pas + if($this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(1)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // La galerie existe + else { + // Images de la galerie + $directory = $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(1), 'config', 'directory']); + if(is_dir($directory)) { + $iterator = new DirectoryIterator($directory); + foreach($iterator as $fileInfos) { + if($fileInfos->isDot() === false AND $fileInfos->isFile() AND @getimagesize($fileInfos->getPathname())) { + self::$pictures[$directory . '/' . $fileInfos->getFilename()] = $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(1), 'legend', str_replace('.','',$fileInfos->getFilename())]); + $picturesSort[$directory . '/' . $fileInfos->getFilename()] = $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(1), 'positions', str_replace('.','',$fileInfos->getFilename())]); + // Créer la miniature si manquante + if (!file_exists( str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()))) { + $this->makeThumb($fileInfos->getPathname(), + str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()), + self::THUMBS_WIDTH); + } + // Définir la Miniature + self::$thumbs[$directory . '/' . $fileInfos->getFilename()] = file_exists( str_replace('source','thumb',$directory) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename())) + ? str_replace('source','thumb',$directory) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()) + : str_replace('source','thumb',$directory) . '/' . strtolower($fileInfos->getFilename()); + } + } + // Tri des images par ordre alphabétique + switch ($this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(1), 'config', 'sort'])) { + case self::SORT_HAND: + asort($picturesSort); + if ($picturesSort) { + foreach ($picturesSort as $name => $position) { + $temp[$name] = self::$pictures[$name]; + } + self::$pictures = $temp; + break; + } + case self::SORT_DSC: + krsort(self::$pictures,SORT_NATURAL); + break; + case self::SORT_ASC: + default: + ksort(self::$pictures,SORT_NATURAL); + break; + } + } + // Affichage du template + if(self::$pictures) { + // Valeurs en sortie + $this->addOutput([ + 'showBarEditButton' => true, + 'title' => $this->getData(['module', $this->getUrl(0), 'content', $this->getUrl(1), 'config', 'name']), + 'view' => 'gallery', + 'style' => $this->getData(['module', $this->getUrl(0), 'theme', 'style']) + ]); + } + // Pas d'image dans la galerie + else { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + } + + } + // Liste des galeries + else { + // Tri des galeries suivant l'ordre défini + $g = $this->getData(['module', $this->getUrl(0), 'content']); + $p = helper::arrayCollumn(helper::arrayCollumn($g,'config'),'position'); + asort($p,SORT_NUMERIC); + $galleries = []; + foreach ($p as $positionId => $item) { + $galleries [$positionId] = $g[$positionId]; + } + // Construire le tableau + foreach((array) $galleries as $galleryId => $gallery) { + if(is_dir($gallery['config']['directory'])) { + $iterator = new DirectoryIterator($gallery['config']['directory']); + foreach($iterator as $fileInfos) { + if($fileInfos->isDot() === false AND $fileInfos->isFile() AND @getimagesize($fileInfos->getPathname())) { + + self::$galleries[$galleryId] = $gallery; + // L'image de couverture est-elle supprimée ? + if (file_exists( $gallery['config']['directory'] . '/' . $gallery['config']['homePicture'])) { + // Créer la miniature si manquante + if (!file_exists( str_replace('source','thumb',$gallery['config']['directory']) . '/' . self::THUMBS_SEPARATOR . strtolower($gallery['config']['homePicture']))) { + $this->makeThumb($gallery['config']['directory'] . '/' . str_replace(self::THUMBS_SEPARATOR ,'',$gallery['config']['homePicture']), + str_replace('source','thumb',$gallery['config']['directory']) . '/' . self::THUMBS_SEPARATOR . strtolower($gallery['config']['homePicture']), + self::THUMBS_WIDTH); + } + // Définir l'image de couverture + self::$firstPictures[$galleryId] = file_exists( str_replace('source','thumb',$gallery['config']['directory']) . '/' . self::THUMBS_SEPARATOR . strtolower($gallery['config']['homePicture'])) + ? str_replace('source','thumb',$gallery['config']['directory']) . '/' . self::THUMBS_SEPARATOR . strtolower($gallery['config']['homePicture']) + : str_replace('source','thumb',$gallery['config']['directory']) . '/' . strtolower($gallery['config']['homePicture']); + } else { + // homePicture contient une image invalide, supprimée ou déplacée + // Définir l'image de couverture, première image disponible + $this->makeThumb($fileInfos->getPath() . '/' . $fileInfos->getFilename(), + str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()), + self::THUMBS_WIDTH); + self::$firstPictures[$galleryId] = file_exists( str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename())) + ? str_replace('source','thumb',$fileInfos->getPath()) . '/' . self::THUMBS_SEPARATOR . strtolower($fileInfos->getFilename()) + : str_replace('source','thumb',$fileInfos->getPath()) . '/' . strtolower($fileInfos->getFilename()); + } + } + continue(1); + } + } + } + // Valeurs en sortie + $this->addOutput([ + 'showBarEditButton' => true, + 'showPageContent' => true, + 'view' => 'index', + 'style' => $this->getData(['module', $this->getUrl(0), 'theme', 'style']) + ]); + } + } + + /** + * Thème de la galerie + */ + public function theme() { + // Jeton incorrect + if ($this->getUrl(2) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Soumission du formulaire + if($this->isPost()) { + // Dossier de l'instance + if (!is_dir(self::DATADIRECTORY . $this->getUrl(0) )) { + mkdir (self::DATADIRECTORY . $this->getUrl(0), 0755, true); + } + $this->setData(['module', $this->getUrl(0), 'theme', [ + 'thumbAlign' => $this->getinput('galleryThemeThumbAlign', helper::FILTER_STRING_SHORT), + 'thumbWidth' => $this->getinput('galleryThemeThumbWidth', helper::FILTER_STRING_SHORT), + 'thumbHeight' => $this->getinput('galleryThemeThumbHeight', helper::FILTER_STRING_SHORT), + 'thumbMargin' => $this->getinput('galleryThemeThumbMargin', helper::FILTER_STRING_SHORT), + 'thumbBorder' => $this->getinput('galleryThemeThumbBorder', helper::FILTER_STRING_SHORT), + 'thumbBorderColor' => $this->getinput('galleryThemeThumbBorderColor', helper::FILTER_STRING_SHORT), + 'thumbOpacity' => $this->getinput('galleryThemeThumbOpacity', helper::FILTER_STRING_SHORT), + 'thumbShadows' => $this->getinput('galleryThemeThumbShadows', helper::FILTER_STRING_SHORT), + 'thumbShadowsColor' => $this->getinput('galleryThemeThumbShadowsColor', helper::FILTER_STRING_SHORT), + 'thumbRadius' => $this->getinput('galleryThemeThumbRadius', helper::FILTER_STRING_SHORT), + 'legendHeight' => $this->getinput('galleryThemeLegendHeight', helper::FILTER_STRING_SHORT), + 'legendAlign' => $this->getinput('galleryThemeLegendAlign', helper::FILTER_STRING_SHORT), + 'legendTextColor' => $this->getinput('galleryThemeLegendTextColor', helper::FILTER_STRING_SHORT), + 'legendBgColor' => $this->getinput('galleryThemeLegendBgColor', helper::FILTER_STRING_SHORT), + 'style' => self::DATADIRECTORY . $this->getUrl(0) . '/theme.css' + ]]); + // Création des fichiers CSS + $content = file_get_contents('module/gallery/ressource/vartheme.css'); + $themeCss = file_get_contents('module/gallery/ressource/theme.css'); + // Injection des variables + $content = str_replace('#thumbAlign#',$this->getinput('galleryThemeThumbAlign'),$content ); + $content = str_replace('#thumbWidth#',$this->getinput('galleryThemeThumbWidth'),$content ); + $content = str_replace('#thumbHeight#',$this->getinput('galleryThemeThumbHeight'),$content ); + $content = str_replace('#thumbMargin#',$this->getinput('galleryThemeThumbMargin'),$content ); + $content = str_replace('#thumbBorder#',$this->getinput('galleryThemeThumbBorder'),$content ); + $content = str_replace('#thumbBorderColor#',$this->getinput('galleryThemeThumbBorderColor'),$content ); + $content = str_replace('#thumbOpacity#',$this->getinput('galleryThemeThumbOpacity'),$content ); + $content = str_replace('#thumbShadows#',$this->getinput('galleryThemeThumbShadows'),$content ); + $content = str_replace('#thumbShadowsColor#',$this->getinput('galleryThemeThumbShadowsColor'),$content ); + $content = str_replace('#thumbRadius#',$this->getinput('galleryThemeThumbRadius'),$content ); + $content = str_replace('#legendAlign#',$this->getinput('galleryThemeLegendAlign'),$content ); + $content = str_replace('#legendHeight#',$this->getinput('galleryThemeLegendHeight'),$content ); + $content = str_replace('#legendTextColor#',$this->getinput('galleryThemeLegendTextColor'),$content ); + $content = str_replace('#legendBgColor#',$this->getinput('galleryThemeLegendBgColor'),$content ); + $success = file_put_contents(self::DATADIRECTORY . $this->getUrl(0) . '/theme.css', $content . $themeCss); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl() . '/theme', + 'notification' => $success !== FALSE ? 'Modifications enregistrées' : 'Modifications non enregistrées !', + 'state' => $success !== FALSE + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => "Thème", + 'view' => 'theme', + 'vendor' => [ + 'tinycolorpicker' + ] + ]); + } + +} + +class galleriesHelper extends helper { + + /** + * Scan le contenu d'un dossier et de ses sous-dossiers + * @param string $dir Dossier à scanner + * @return array + */ + public static function scanDir($dir) { + $dirContent = []; + $iterator = new DirectoryIterator($dir); + foreach($iterator as $fileInfos) { + if($fileInfos->isDot() === false AND $fileInfos->isDir()) { + $dirContent[] = $dir . '/' . $fileInfos->getBasename(); + $dirContent = array_merge($dirContent, self::scanDir($dir . '/' . $fileInfos->getBasename())); + } + } + return $dirContent; + } +} diff --git a/module/gallery/ressource/defaultdata.php b/module/gallery/ressource/defaultdata.php new file mode 100644 index 0000000..737999c --- /dev/null +++ b/module/gallery/ressource/defaultdata.php @@ -0,0 +1,22 @@ + 'center', + 'thumbWidth' => '18em', + 'thumbHeight' => '15em', + 'thumbMargin' => '.5em', + 'thumbBorder' => '.1em', + 'thumbOpacity' => '.7', + 'thumbBorderColor' => 'rgba(221, 221, 221, 1)', + 'thumbRadius' => '.3em', + 'thumbShadows' => '1px 1px 10px', + 'thumbShadowsColor'=> 'rgba(125, 125, 125, 1)', + 'legendHeight' => '.375em', + 'legendAlign' => 'center', + 'legendTextColor' => 'rgba(255, 255, 255, 1)', + 'legendBgColor' => 'rgba(0, 0, 0, .6)' + ]; + public static $defaultData = [ + 'versionData' => '3.0' + ]; +} diff --git a/module/gallery/ressource/theme.css b/module/gallery/ressource/theme.css new file mode 100644 index 0000000..c0608f9 --- /dev/null +++ b/module/gallery/ressource/theme.css @@ -0,0 +1,52 @@ +.galleryPicture, +.galleryGalleryPicture { + display: block; + border: var(--thumbBorder) solid var(--thumbBorderColor); + height: var(--thumbHeight); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + position: relative; + -webkit-transition: opacity .3s ease-out; + transition: opacity .3s ease-out; + border-radius: var(--thumbRadius); + box-shadow: var(--thumbShadows) var(--thumbShadowsColor); + -webkit-box-shadow: var(--thumbShadows) var(--thumbShadowsColor); + -moz-box-shadow: var(--thumbShadows) var(--thumbShadowsColor); +} +.galleryPicture:hover, +.galleryGalleryPicture:hover { + opacity: var(--thumbOpacity); +} +.galleryName, +.galleryGalleryName { + position: absolute; + left: 0; + right: 0; + bottom: 0; + border-radius: 0 0 calc(var(--thumbRadius)/2) calc(var(--thumbRadius)/2); + padding: var(--legendHeight); + background: var(--legendBgColor); + color: var(--legendTextColor); + text-align: var(--legendAlign); +} + +.galleryRow { + display: flex; + flex-wrap: wrap; + justify-content: var(--thumbAlign); +} + +.colPicture { + width : var(--thumbWidth); + max-width: 50%; + padding: var(--thumbMargin); + } + + @media (max-width: 432px) { + .colPicture { + width: 90%; + max-width: 90%; + margin: 0.5em; + } + } \ No newline at end of file diff --git a/module/gallery/ressource/vartheme.css b/module/gallery/ressource/vartheme.css new file mode 100644 index 0000000..bd665d4 --- /dev/null +++ b/module/gallery/ressource/vartheme.css @@ -0,0 +1,27 @@ +.galleryRow { + --thumbAlign: #thumbAlign#; +} +.colPicture { + --thumbWidth: #thumbWidth#; + --thumbMargin: #thumbMargin#; +} +.galleryPicture, +.galleryGalleryPicture { + --thumbHeight: #thumbHeight#; + --thumbBorder: #thumbBorder#; + --thumbBorderColor: #thumbBorderColor#; + --thumbRadius: #thumbRadius#; + --thumbShadows: #thumbShadows#; + --thumbShadowsColor: #thumbShadowsColor#; +} +.galleryName, +.galleryGalleryName { + --legendHeight: #legendHeight#; + --legendAlign: #legendAlign#; + --legendTextColor: #legendTextColor#; + --legendBgColor: #legendBgColor#; +} +.galleryPicture:hover, +.galleryGalleryPicture:hover { + --thumbOpacity: #thumbOpacity#; +} diff --git a/module/gallery/vendor/tablednd/inc.json b/module/gallery/vendor/tablednd/inc.json new file mode 100644 index 0000000..aea2b72 --- /dev/null +++ b/module/gallery/vendor/tablednd/inc.json @@ -0,0 +1,3 @@ +[ + "tablednd.min.js" +] \ No newline at end of file diff --git a/module/gallery/vendor/tablednd/lisez-moi.txt b/module/gallery/vendor/tablednd/lisez-moi.txt new file mode 100644 index 0000000..dfa8616 --- /dev/null +++ b/module/gallery/vendor/tablednd/lisez-moi.txt @@ -0,0 +1 @@ +https://github.com/isocra/TableDnD \ No newline at end of file diff --git a/module/gallery/vendor/tablednd/tablednd.min.js b/module/gallery/vendor/tablednd/tablednd.min.js new file mode 100644 index 0000000..225739f --- /dev/null +++ b/module/gallery/vendor/tablednd/tablednd.min.js @@ -0,0 +1,2 @@ +/*! jquery.tablednd.js 30-12-2017 */ +!function(a,b,c,d){var e="touchstart mousedown",f="touchmove mousemove",g="touchend mouseup";a(c).ready(function(){function b(a){for(var b={},c=a.match(/([^;:]+)/g)||[];c.length;)b[c.shift()]=c.shift().trim();return b}a("table").each(function(){"dnd"===a(this).data("table")&&a(this).tableDnD({onDragStyle:a(this).data("ondragstyle")&&b(a(this).data("ondragstyle"))||null,onDropStyle:a(this).data("ondropstyle")&&b(a(this).data("ondropstyle"))||null,onDragClass:a(this).data("ondragclass")===d&&"tDnD_whileDrag"||a(this).data("ondragclass"),onDrop:a(this).data("ondrop")&&new Function("table","row",a(this).data("ondrop")),onDragStart:a(this).data("ondragstart")&&new Function("table","row",a(this).data("ondragstart")),onDragStop:a(this).data("ondragstop")&&new Function("table","row",a(this).data("ondragstop")),scrollAmount:a(this).data("scrollamount")||5,sensitivity:a(this).data("sensitivity")||10,hierarchyLevel:a(this).data("hierarchylevel")||0,indentArtifact:a(this).data("indentartifact")||'
 
',autoWidthAdjust:a(this).data("autowidthadjust")||!0,autoCleanRelations:a(this).data("autocleanrelations")||!0,jsonPretifySeparator:a(this).data("jsonpretifyseparator")||"\t",serializeRegexp:a(this).data("serializeregexp")&&new RegExp(a(this).data("serializeregexp"))||/[^\-]*$/,serializeParamName:a(this).data("serializeparamname")||!1,dragHandle:a(this).data("draghandle")||null})})}),jQuery.tableDnD={currentTable:null,dragObject:null,mouseOffset:null,oldX:0,oldY:0,build:function(b){return this.each(function(){this.tableDnDConfig=a.extend({onDragStyle:null,onDropStyle:null,onDragClass:"tDnD_whileDrag",onDrop:null,onDragStart:null,onDragStop:null,scrollAmount:5,sensitivity:10,hierarchyLevel:0,indentArtifact:'
 
',autoWidthAdjust:!0,autoCleanRelations:!0,jsonPretifySeparator:"\t",serializeRegexp:/[^\-]*$/,serializeParamName:!1,dragHandle:null},b||{}),a.tableDnD.makeDraggable(this),this.tableDnDConfig.hierarchyLevel&&a.tableDnD.makeIndented(this)}),this},makeIndented:function(b){var c,d,e=b.tableDnDConfig,f=b.rows,g=a(f).first().find("td:first")[0],h=0,i=0;if(a(b).hasClass("indtd"))return null;d=a(b).addClass("indtd").attr("style"),a(b).css({whiteSpace:"nowrap"});for(var j=0;ja.vertical&&this.dragObject.parentNode.insertBefore(this.dragObject,b.nextSibling)||00&&a(c).find("td:first").children(":first").remove()&&a(c).data("level",--d),0>b.horizontal&&d=d&&a(c).children(":first").prepend(e.indentArtifact)&&a(c).data("level",++d)},mousemove:function(b){var c,d,e,f,g,h=a(a.tableDnD.dragObject),i=a.tableDnD.currentTable.tableDnDConfig;return b&&b.preventDefault(),!!a.tableDnD.dragObject&&("touchmove"===b.type&&event.preventDefault(),i.onDragClass&&h.addClass(i.onDragClass)||h.css(i.onDragStyle),d=a.tableDnD.mouseCoords(b),f=d.x-a.tableDnD.mouseOffset.x,g=d.y-a.tableDnD.mouseOffset.y,a.tableDnD.autoScroll(d),c=a.tableDnD.findDropTargetRow(h,g),e=a.tableDnD.findDragDirection(f,g),a.tableDnD.moveVerticle(e,c),a.tableDnD.moveHorizontal(e,c),!1)},findDragDirection:function(a,b){var c=this.currentTable.tableDnDConfig.sensitivity,d=this.oldX,e=this.oldY,f=d-c,g=d+c,h=e-c,i=e+c,j={horizontal:a>=f&&a<=g?0:a>d?-1:1,vertical:b>=h&&b<=i?0:b>e?-1:1};return 0!==j.horizontal&&(this.oldX=a),0!==j.vertical&&(this.oldY=b),j},findDropTargetRow:function(b,c){for(var d=0,e=this.currentTable.rows,f=this.currentTable.tableDnDConfig,g=0,h=null,i=0;ig-d&&c1&&a(this.currentTable.rows).each(function(){if((h=a(this).data("level"))>1)for(e=a(this).prev().data("level");h>e+1;)a(this).find("td:first").children(":first").remove(),a(this).data("level",--h)}),b.onDragClass&&a(d).removeClass(b.onDragClass)||a(d).css(b.onDropStyle),this.dragObject=null,b.onDrop&&this.originalOrder!==this.currentOrder()&&a(d).hide().fadeIn("fast")&&b.onDrop(this.currentTable,d),b.onDragStop&&b.onDragStop(this.currentTable,d),this.currentTable=null},mouseup:function(b){return b&&b.preventDefault(),a.tableDnD.processMouseup(),!1},jsonize:function(a){var b=this.currentTable;return a?JSON.stringify(this.tableData(b),null,b.tableDnDConfig.jsonPretifySeparator):JSON.stringify(this.tableData(b))},serialize:function(){return a.param(this.tableData(this.currentTable))},serializeTable:function(a){for(var b="",c=a.tableDnDConfig.serializeParamName||a.id,d=a.rows,e=0;e0&&(b+="&");var f=d[e].id;f&&a.tableDnDConfig&&a.tableDnDConfig.serializeRegexp&&(f=f.match(a.tableDnDConfig.serializeRegexp)[0],b+=c+"[]="+f)}return b},serializeTables:function(){var b=[];return a("table").each(function(){this.id&&b.push(a.param(a.tableDnD.tableData(this)))}),b.join("&")},tableData:function(b){var c,d,e,f,g=b.tableDnDConfig,h=[],i=0,j=0,k=null,l={};if(b||(b=this.currentTable),!b||!b.rows||!b.rows.length)return{error:{code:500,message:"Not a valid table."}};if(!b.id&&!g.serializeParamName)return{error:{code:500,message:"No serializable unique id provided."}};f=g.autoCleanRelations&&b.rows||a.makeArray(b.rows),d=g.serializeParamName||b.id,e=d,c=function(a){return a&&g&&g.serializeRegexp?a.match(g.serializeRegexp)[0]:a},l[e]=[],!g.autoCleanRelations&&a(f[0]).data("level")&&f.unshift({id:"undefined"});for(var m=0;mi)h.push([e,i]),e=c(f[m-1].id);else if(j=i&&(h[n][1]=0);i=j,a.isArray(l[e])||(l[e]=[]),k=c(f[m].id),k&&l[e].push(k)}else(k=c(f[m].id))&&l[e].push(k);return l}},jQuery.fn.extend({tableDnD:a.tableDnD.build,tableDnDUpdate:a.tableDnD.updateTables,tableDnDSerialize:a.proxy(a.tableDnD.serialize,a.tableDnD),tableDnDSerializeAll:a.tableDnD.serializeTables,tableDnDData:a.proxy(a.tableDnD.tableData,a.tableDnD)})}(jQuery,window,window.document); \ No newline at end of file diff --git a/module/gallery/view/config/config.css b/module/gallery/view/config/config.css new file mode 100644 index 0000000..5a29939 --- /dev/null +++ b/module/gallery/view/config/config.css @@ -0,0 +1,26 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + + + +/** NE PAS EFFACER +* admin.css +*/ + +.galleryConfigError { + color: #F3674A; + font-weight: bold; +} \ No newline at end of file diff --git a/module/gallery/view/config/config.js.php b/module/gallery/view/config/config.js.php new file mode 100644 index 0000000..9d4953d --- /dev/null +++ b/module/gallery/view/config/config.js.php @@ -0,0 +1,106 @@ +/** + * This file is part of DeltaCMS. + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * @author Sylvain Lelièvre + * @copyright Copyright (C) 2021-2022, Sylvain Lelièvre + * @license GNU General Public License, version 3 + * @link https://deltacms.fr/ + * + * Delta was created from version 11.2.00.24 of ZwiiCMS + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2021, Frédéric Tempez + */ + +$( document ).ready(function() { + + + /** + * Tri de la galerie avec drag and drop + */ + $("#galleryTable").tableDnD({ + onDrop: function(table, row) { + $("#galleryConfigFilterResponse").val($.tableDnD.serialize()); + }, + onDragStop : function(table, row) { + // Affiche le bouton de tri après un déplacement + //$(":input[type='submit']").prop('disabled', false); + // Sauvegarde le tri + sortGalleries(); + }, + // Supprime le tiret des séparateurs + serializeRegexp: "" + }); + + + + /** + * Confirmation de suppression + */ + $(".galleryConfigDelete").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir supprimer cette galerie ?", function() { + $(location).attr("href", _this.attr("href")); + }); + }); + +}); + +/** + * Liste des dossiers + */ +var oldResult = []; +var directoryDOM = $("#galleryConfigDirectory"); +var directoryOldDOM = $("#galleryConfigDirectoryOld"); +function dirs() { + $.ajax({ + type: "POST", + url: "getUrl(0); ?>/dirs", + success: function(result) { + if($(result).not(oldResult).length !== 0 || $(oldResult).not(result).length !== 0) { + directoryDOM.empty(); + for(var i = 0; i < result.length; i++) { + directoryDOM.append(function(i) { + var option = $("