Functions imported, some dev-scripts added
[mailer.git] / inc / functions.php
index c5db673..ef6cbde 100644 (file)
@@ -139,7 +139,7 @@ function OUTPUT_HTML ($HTML, $newLine = true) {
                while (strpos($OUTPUT, '{!') > 0) {
                        // Prepare the content and eval() it...
                        $newContent = "";
-                       $eval = "\$newContent = \"".COMPILE_CODE(addslashes($OUTPUT))."\";";
+                       $eval = "\$newContent = \"".COMPILE_CODE(SQL_ESCAPE($OUTPUT))."\";";
                        @eval($eval);
 
                        // Was that eval okay?
@@ -160,7 +160,7 @@ function OUTPUT_HTML ($HTML, $newLine = true) {
 
                // Compile and run finished rendered HTML code
                while (strpos($OUTPUT, '{!') > 0) {
-                       $eval = "\$OUTPUT = \"".COMPILE_CODE(addslashes($OUTPUT))."\";";
+                       $eval = "\$OUTPUT = \"".COMPILE_CODE(SQL_ESCAPE($OUTPUT))."\";";
                        eval($eval);
                } // END - while
 
@@ -251,7 +251,9 @@ function LOAD_TEMPLATE ($template, $return=false, $content=array()) {
                        // Translate gender
                        $content['gender'] = TRANSLATE_GENDER($content['gender']);
                } else {
-                       // DEPRECATED: Load data in direct variables
+                       // @DEPRECATED
+                       // @TODO Fine all templates which are using these direct variables and rewrite them.
+                       // @TODO After this step is done, this else-block is history
                        list($gender, $surname, $family, $email) = SQL_FETCHROW($result);
 
                        // Translate gender
@@ -337,7 +339,7 @@ function LOAD_TEMPLATE ($template, $return=false, $content=array()) {
                $ret = "";
                if ((strpos($tmpl_file, "\$") !== false) || (strpos($tmpl_file, '{--') !== false) || (strpos($tmpl_file, '--}') > 0)) {
                        // Okay, compile it!
-                       $tmpl_file = "\$ret=\"".COMPILE_CODE(addslashes($tmpl_file))."\";";
+                       $tmpl_file = "\$ret=\"".COMPILE_CODE(SQL_ESCAPE($tmpl_file))."\";";
                        eval($tmpl_file);
                } else {
                        // Simply return loaded code
@@ -383,7 +385,7 @@ function SEND_EMAIL($TO, $SUBJECT, $MSG, $HTML = "N", $FROM = "") {
        //* DEBUG: */ print __FUNCTION__."(<font color=\"#0000aa\">".__LINE__."</font>):TO={$TO},SUBJECT={$SUBJECT}<br />\n";
 
        // Compile subject line (for POINTS constant etc.)
-       $eval = "\$SUBJECT = html_entity_decode(\"".COMPILE_CODE(addslashes($SUBJECT))."\");";
+       $eval = "\$SUBJECT = decodeEntities(\"".COMPILE_CODE(SQL_ESCAPE($SUBJECT))."\");";
        eval($eval);
 
        // Set from header
@@ -436,11 +438,11 @@ function SEND_EMAIL($TO, $SUBJECT, $MSG, $HTML = "N", $FROM = "") {
        }
 
        // Compile "TO"
-       $eval = "\$TO = \"".COMPILE_CODE(addslashes($TO))."\";";
+       $eval = "\$TO = \"".COMPILE_CODE(SQL_ESCAPE($TO))."\";";
        eval($eval);
 
        // Compile "MSG"
-       $eval = "\$MSG = \"".COMPILE_CODE(addslashes($MSG))."\";";
+       $eval = "\$MSG = \"".COMPILE_CODE(SQL_ESCAPE($MSG))."\";";
        eval($eval);
 
        // Fix HTML parameter (default is no!)
@@ -504,7 +506,7 @@ function SEND_RAW_EMAIL ($to, $subject, $msg, $from) {
                        $mail->WordWrap   = 70;
                        $mail->IsHTML(true);
                } else {
-                       $mail->Body       = html_entity_decode($msg);
+                       $mail->Body       = decodeEntities($msg);
                }
                $mail->AddAddress($to, "");
                $mail->AddReplyTo(constant('WEBMASTER'), constant('MAIN_TITLE'));
@@ -513,7 +515,7 @@ function SEND_RAW_EMAIL ($to, $subject, $msg, $from) {
                $mail->Send();
        } else {
                // Use legacy mail() command
-               @mail($to, $subject, html_entity_decode($msg), $from);
+               @mail($to, $subject, decodeEntities($msg), $from);
        }
 }
 //
@@ -624,35 +626,45 @@ function DEREFERER ($URL) {
        // Don't de-refer our own links!
        if (substr($URL, 0, strlen(URL)) != URL) {
                // De-refer this link
-               $URL = "modules.php?module=loader&amp;url=".urlencode(base64_encode(gzcompress($URL)));
+               $URL = "modules.php?module=loader&amp;url=".encodeString(compileUriCode($URL));
        } // END - if
 
        // Return link
        return $URL;
 }
 
-//
+// Translate Uni*-like gender to human-readable
 function TRANSLATE_GENDER ($gender) {
-       switch ($gender)
-       {
-               case "M": $ret = GENDER_M; break;
-               case "F": $ret = GENDER_F; break;
-               case "C": $ret = GENDER_C; break;
-               default : $ret = $gender; break;
+       // Default
+       $ret = "!{$gender}!";
+
+       // Male/female or company?
+       switch ($gender) {
+               case "M": $ret = getMessage('GENDER_M'); break;
+               case "F": $ret = getMessage('GENDER_F'); break;
+               case "C": $ret = getMessage('GENDER_C'); break;
+               default:
+                       // Log unknown gender
+                       DEBUG_LOG(__FUNCTION__, __LINE__, sprintf("Unknown gender %s detected.", $gender));
+                       break;
        }
+
+       // Return translated gender
        return $ret;
 }
+
 //
-function FRAMETESTER($URL) {
+function FRAMETESTER ($URL) {
        // Prepare frametester URL
        $frametesterUrl = sprintf("%s/modules.php?module=frametester&amp;url=%s",
                URL,
-               urlencode(base64_encode(gzcompress(COMPILE_CODE($URL))))
+               encodeString(compileUriCode($URL))
        );
        return $frametesterUrl;
 }
+
 //
-function SELECTION_COUNT($array) {
+function SELECTION_COUNT ($array) {
        $ret = 0;
        if (is_array($array)) {
                foreach ($array as $key => $sel) {
@@ -666,31 +678,27 @@ function IMG_CODE ($code, $type, $DATA, $uid) {
        return "<IMG border=\"0\" alt=\"Code\" src=\"{!URL!}/mailid_top.php?uid=".$uid."&amp;".$type."=".$DATA."&amp;mode=img&amp;code=".$code."\">";
 }
 //
-function TRANSLATE_STATUS($status) {
+function TRANSLATE_STATUS ($status) {
        switch ($status)
        {
        case "UNCONFIRMED":
-               $ret = ACCOUNT_UNCONFIRMED;
-               break;
-
        case "CONFIRMED":
-               $ret = ACCOUNT_CONFIRMED;
-               break;
-
        case "LOCKED":
-               $ret = ACCOUNT_LOCKED;
+               $ret = getMessage(sprintf("ACCOUNT_%s", $status));
                break;
 
        case "":
        case null:
-               $ret = ACCOUNT_DELETED;
+               $ret = getMessage('ACCOUNT_DELETED');
                break;
 
        default:
                DEBUG_LOG(__FUNCTION__, __LINE__, sprintf("Unknown status %s detected.", $status));
-               $ret = UNKNOWN_STATUS_1.$status.UNKNOWN_STATUS_2;
+               $ret = sprintf(getMessage('UNKNOWN_STATUS"'), $status);
                break;
        }
+
+       // Return it
        return $ret;
 }
 //
@@ -770,7 +778,7 @@ function LOAD_EMAIL_TEMPLATE($template, $content=array(), $UID="0") {
        // Expiration in a nice output format
        if (getConfig('auto_purge') == 0) {
                // Will never expire!
-               $EXPIRATION = MAIL_WILL_NEVER_EXPIRE;
+               $EXPIRATION = getMessage('MAIL_WILL_NEVER_EXPIRE');
        } else {
                // Create nice date string
                $EXPIRATION = CREATE_FANCY_TIME(getConfig('auto_purge'));
@@ -852,10 +860,10 @@ function LOAD_EMAIL_TEMPLATE($template, $content=array(), $UID="0") {
        if (FILE_READABLE($file)) {
                // The local file does exists so we load it. :)
                $tmpl_file = READ_FILE($file);
-               $tmpl_file = addslashes($tmpl_file);
+               $tmpl_file = SQL_ESCAPE($tmpl_file);
 
                // Run code
-               $tmpl_file = "\$newContent = html_entity_decode(\"".COMPILE_CODE($tmpl_file)."\");";
+               $tmpl_file = "\$newContent = decodeEntities(\"".COMPILE_CODE($tmpl_file)."\");";
                @eval($tmpl_file);
        } elseif (!empty($template)) {
                // Template file not found!
@@ -903,7 +911,7 @@ function LOAD_URL($URL, $addUrlData=true) {
        global $CSS, $footer;
 
        // Compile out URI codes
-       $URL = COMPILE_CODE($URL);
+       $URL = compileUriCode($URL);
 
        // Check if http(s):// is there
        if ((substr($URL, 0, 7) != "http://") && (substr($URL, 0, 8) != "https://")) {
@@ -1210,19 +1218,25 @@ function ADD_SELECTION($type, $DEFAULT, $prefix="", $id="0") {
 //
 function TRANSLATE_YESNO($yn)
 {
-       switch ($yn)
-       {
-               case "Y": $yn = YES; break;
-               case "N": $yn = NO; break;
-               default : $yn = "??? (".$yn.")"; break;
+       // Default
+       $yn = "??? (".$yn.")";
+       switch ($yn) {
+               case "Y": $yn = getMessage('YES'); break;
+               case "N": $yn = getMessage('NO'); break;
+               default:
+                       // Log unknown value
+                       DEBUG_LOG(__FUNCTION__, __LINE__, sprintf("Unknown value %s. Expected Y/N!", $yn));
+                       break;
        }
+
+       // Return it
        return $yn;
 }
 //
 // Deprecated : $length
 // Optional   : $DATA
 //
-function GEN_RANDOM_CODE($length, $code, $uid, $DATA="") {
+function GEN_RANDOM_CODE ($length, $code, $uid, $DATA="") {
        // Fix missing _MAX constant
        if (!defined('_MAX')) define('_MAX', 15235);
 
@@ -1230,7 +1244,7 @@ function GEN_RANDOM_CODE($length, $code, $uid, $DATA="") {
        $server = $_SERVER['PHP_SELF'].":".GET_USER_AGENT().":".getenv('SERVER_SOFTWARE').":".GET_REMOTE_ADDR().":".":".filemtime(constant('PATH')."inc/databases.php");
 
        // Build key string
-       $keys   = SITE_KEY.":".DATE_KEY;
+       $keys   = constant('SITE_KEY').":".constant('DATE_KEY');
        if (getConfig('secret_key') != null)  $keys .= ":".getConfig('secret_key');
        if (getConfig('file_hash') != null)   $keys .= ":".getConfig('file_hash');
        $keys .= ":".date("d-m-Y (l-F-T)", bigintval(getConfig('patch_ctime')));
@@ -1254,13 +1268,13 @@ function GEN_RANDOM_CODE($length, $code, $uid, $DATA="") {
                $saltedHash = generateHash(($a % constant('_PRIME')).":".$server.":".$keys.":".$data.":".date("d-m-Y (l-F-T)", time()).":".$a, getConfig('master_salt'));
 
                // Create number from hash
-               $rcode = hexdec(substr($saltedHash, strlen(getConfig('master_salt')), 9)) / abs(_MAX - $a + sqrt(_ADD)) / pi();
+               $rcode = hexdec(substr($saltedHash, strlen(getConfig('master_salt')), 9)) / abs(constant('_MAX') - $a + sqrt(constant('_ADD'))) / pi();
        } else {
                // Generate hash with "hash of site key" from modula of number with the prime number and other data
                $saltedHash = generateHash(($a % constant('_PRIME')).":".$server.":".$keys.":".$data.":".date("d-m-Y (l-F-T)", time()).":".$a, substr(sha1(SITE_KEY), 0, 8));
 
                // Create number from hash
-               $rcode = hexdec(substr($saltedHash, 8, 9)) / abs(_MAX - $a + sqrt(_ADD)) / pi();
+               $rcode = hexdec(substr($saltedHash, 8, 9)) / abs(constant('_MAX') - $a + sqrt(constant('_ADD'))) / pi();
        }
 
        // At least 10 numbers shall be secure enought!
@@ -1274,6 +1288,7 @@ function GEN_RANDOM_CODE($length, $code, $uid, $DATA="") {
        // Done building code
        return $return;
 }
+
 // Does only allow numbers
 function bigintval($num, $castValue = true) {
        // Filter all numbers out
@@ -1286,20 +1301,21 @@ function bigintval($num, $castValue = true) {
        // @TODO Remove this if() block if all is working fine
        if ("".$ret."" != "".$num."") {
                // Log the values
-               debug_report_bug();
+               debug_report_bug("{$ret}<>{$num}");
        } // END - if
 
        // Return result
        return $ret;
 }
+
 // Insert the code in $img_code into jpeg or PNG image
-function GENERATE_IMAGE($img_code, $header=true) {
+function GENERATE_IMAGE ($img_code, $header=true) {
        if ((strlen($img_code) > 6) || (empty($img_code)) || (getConfig('code_length') == 0)) {
                // Stop execution of function here because of over-sized code length
                return;
        } elseif (!$header) {
                // Return in an HTML code code
-               return "<IMG src=\"{!URL!}/img.php?code=".$img_code."\">\n";
+               return "<img src=\"{!URL!}/img.php?code=".$img_code."\" alt=\"Image\" />\n";
        }
 
        // Load image
@@ -1320,6 +1336,7 @@ function GENERATE_IMAGE($img_code, $header=true) {
                }
        } else {
                // Exit function here
+               DEBUG_LOG(__FUNCTION__, __LINE__, sprintf("File for image type %s not found.", getConfig('img_type')));
                return;
        }
 
@@ -1635,7 +1652,7 @@ function CREATE_FANCY_TIME ($stamp) {
        foreach($data as $k => $v) {
                if ($v > 0) {
                        // Value is greater than 0 "eval" data to return string
-                       $eval = "\$ret .= \", \".\$v.\" \"._".strtoupper($k).";";
+                       $eval = "\$ret .= \", \".\$v.\" {--_".strtoupper($k)."--}\";";
                        eval($eval);
                        break;
                } // END - if
@@ -1647,7 +1664,7 @@ function CREATE_FANCY_TIME ($stamp) {
                $ret = substr($ret, 2);
        } else {
                // Zero seconds
-               $ret = "0 "._SECONDS;
+               $ret = "0 {--_SECONDS--}";
        }
 
        // Return fancy time string
@@ -1713,7 +1730,7 @@ function ADD_EMAIL_NAV($PAGES, $offset, $show_form, $colspan, $return=false) {
 // Extract host from script name
 function EXTRACT_HOST (&$script) {
        // Use default SERVER_URL by default... ;) So?
-       $url = SERVER_URL;
+       $url = constant('SERVER_URL');
 
        // Is this URL valid?
        if (substr($script, 0, 7) == "http://") {
@@ -1756,8 +1773,8 @@ function GET_URL ($script) {
        // Generate GET request header
        $request  = "GET /" . trim($script) . " HTTP/1.1\r\n";
        $request .= "Host: " . $host . "\r\n";
-       $request .= "Referer: " . URL . "/admin.php\r\n";
-       $request .= "User-Agent: " . TITLE . "/" . FULL_VERSION . "\r\n";
+       $request .= "Referer: " . constant('URL') . "/admin.php\r\n";
+       $request .= "User-Agent: " . constant('TITLE') . "/" . constant('FULL_VERSION') . "\r\n";
        $request .= "Content-Type: text/plain\r\n";
        $request .= "Cache-Control: no-cache\r\n";
        $request .= "Connection: Close\r\n\r\n";
@@ -1790,8 +1807,8 @@ function POST_URL ($script, $postData) {
        // Generate POST request header
        $request  = "POST /" . trim($script) . " HTTP/1.1\r\n";
        $request .= "Host: " . $host . "\r\n";
-       $request .= "Referer: " . URL . "/admin.php\r\n";
-       $request .= "User-Agent: " . TITLE . "/" . FULL_VERSION . "\r\n";
+       $request .= "Referer: " . constant('URL') . "/admin.php\r\n";
+       $request .= "User-Agent: " . constant('TITLE') . "/" . constant('FULL_VERSION') . "\r\n";
        $request .= "Content-type: application/x-www-form-urlencoded\r\n";
        $request .= "Content-length: " . strlen($data) . "\r\n";
        $request .= "Cache-Control: no-cache\r\n";
@@ -1930,6 +1947,7 @@ function VALIDATE_EMAIL($email) {
        // Return check result
        return eregi($regex, $email);
 }
+
 // Function taken from user comments on www.php.net / function eregi()
 function VALIDATE_URL ($URL, $compile=true) {
        // Trim URL a little
@@ -1937,7 +1955,7 @@ function VALIDATE_URL ($URL, $compile=true) {
        //* DEBUG: */ echo $URL."<br />";
 
        // Compile some chars out...
-       if ($compile) $URL = COMPILE_CODE($URL, false, false, false);
+       if ($compile) $URL = compileUriCode($URL, false, false, false);
        //* DEBUG: */ echo $URL."<br />";
 
        // Check for the extension filter
@@ -1948,8 +1966,9 @@ function VALIDATE_URL ($URL, $compile=true) {
 
        // If not installed, perform a simple test. Just make it sure there is always a http:// or
        // https:// in front of the URLs
-       return (((substr($URL, 0, 7) == "http://") || (substr($URL, 0, 8) == "https://")) && (strlen($URL) >= 12));
+       return isUrlValid($URL);
 }
+
 //
 function MEMBER_ACTION_LINKS ($uid, $status = "") {
        // Define all main targets
@@ -1986,11 +2005,14 @@ function MEMBER_ACTION_LINKS ($uid, $status = "") {
        // Return string
        return $OUT;
 }
+
 // Function for backward-compatiblity
-function ADD_CATEGORY_table ($MODE, $return=false) {
+// @TODO Can this function be deprecated?
+function ADD_CATEGORY_TABLE ($MODE, $return=false) {
        // Load it from the register extension
-       return REGISTER_ADD_CATEGORY_table ($MODE, $return);
+       return REGISTER_ADD_CATEGORY_TABLE ($MODE, $return);
 }
+
 // Generate an email link
 function CREATE_EMAIL_LINK ($email, $table = "admins") {
        // Default email link (INSECURE! Spammer can read this by harvester programs)
@@ -3295,6 +3317,124 @@ function INCLUDE_READABLE ($INC) {
        return FILE_READABLE($FQFN);
 }
 
+// Encode strings
+// @TODO Implement $compress
+function encodeString ($str, $compress=true) {
+       $str = urlencode(base64_encode(compileUriCode($str)));
+       return $str;
+}
+
+// Decode strings encoded with encodeString()
+// @TODO Implement $decompress
+function decodeString ($str, $decompress=true) {
+       $str = compileUriCode(base64_decode(urldecode(compileUriCode($str))));
+       return $str;
+}
+
+// Compile characters which are allowed in URLs
+function compileUriCode ($code, $simple=true) {
+       // Compile constants
+       if (!$simple) $code = str_replace("{--", '".', str_replace("--}", '."', $code));
+
+       // Compile QUOT and other non-HTML codes
+       $code = str_replace("{DOT}", ".",
+               str_replace("{SLASH}", "/",
+               str_replace("{QUOT}", "'",
+               str_replace("{DOLLAR}", "$",
+               str_replace("{OPEN_ANCHOR}", "(",
+               str_replace("{CLOSE_ANCHOR}", ")",
+               str_replace("{OPEN_SQR}", "[",
+               str_replace("{CLOSE_SQR}", "]",
+               str_replace("{PER}", "%",
+               $code
+       )))))))));
+
+       // Return compiled code
+       return $code;
+}
+
+// Function taken from user comments on www.php.net / function eregi()
+function isUrlValid ($url) {
+       // Prepare URL
+       $url = strip_tags(str_replace("\\", "", compileUriCode(urldecode($url))));
+
+       // Allows http and https
+       $http      = "(http|https)+(:\/\/)";
+       // Test domain
+       $domain1   = "([[:alnum:]]([-[:alnum:]])*\.)?([[:alnum:]][-[:alnum:]\.]*[[:alnum:]])(\.[[:alpha:]]{2,5})?";
+       // Test double-domains (e.g. .de.vu)
+       $domain2   = "([-[:alnum:]])?(\.[[:alnum:]][-[:alnum:]\.]*[[:alnum:]])(\.[[:alpha:]]{2,5})(\.[[:alpha:]]{2,5})?";
+       // Test IP number
+       $ip        = "([[:digit:]]{1,3})\.([[:digit:]]{1,3})\.([[:digit:]]{1,3})\.([[:digit:]]{1,3})";
+       // ... directory
+       $dir       = "((/)+([-_\.[:alnum:]])+)*";
+       // ... page
+       $page      = "/([-_[:alnum:]][-\._[:alnum:]]*\.[[:alnum:]]{2,5})?";
+       // ... and the string after and including question character
+       $getstring1 = "([\?/]([[:alnum:]][-\._%[:alnum:]]*(=)?([-\@\._:%[:alnum:]])+)(&([[:alnum:]]([-_%[:alnum:]])*(=)?([-\@\[\._:%[:alnum:]])+(\])*))*)?";
+       // Pattern for URLs like http://url/dir/doc.html?var=value
+       $pattern['d1dpg1']  = $http.$domain1.$dir.$page.$getstring1;
+       $pattern['d2dpg1']  = $http.$domain2.$dir.$page.$getstring1;
+       $pattern['ipdpg1']  = $http.$ip.$dir.$page.$getstring1;
+       // Pattern for URLs like http://url/dir/?var=value
+       $pattern['d1dg1']  = $http.$domain1.$dir."/".$getstring1;
+       $pattern['d2dg1']  = $http.$domain2.$dir."/".$getstring1;
+       $pattern['ipdg1']  = $http.$ip.$dir."/".$getstring1;
+       // Pattern for URLs like http://url/dir/page.ext
+       $pattern['d1dp']  = $http.$domain1.$dir.$page;
+       $pattern['d1dp']  = $http.$domain2.$dir.$page;
+       $pattern['ipdp']  = $http.$ip.$dir.$page;
+       // Pattern for URLs like http://url/dir
+       $pattern['d1d']  = $http.$domain1.$dir;
+       $pattern['d2d']  = $http.$domain2.$dir;
+       $pattern['ipd']  = $http.$ip.$dir;
+       // Pattern for URLs like http://url/?var=value
+       $pattern['d1g1']  = $http.$domain1."/".$getstring1;
+       $pattern['d2g1']  = $http.$domain2."/".$getstring1;
+       $pattern['ipg1']  = $http.$ip."/".$getstring1;
+       // Pattern for URLs like http://url?var=value
+       $pattern['d1g12']  = $http.$domain1.$getstring1;
+       $pattern['d2g12']  = $http.$domain2.$getstring1;
+       $pattern['ipg12']  = $http.$ip.$getstring1;
+       // Test all patterns
+       $reg = false;
+       foreach ($pattern as $key=>$pat) {
+               // Debug regex?
+               if (defined('DEBUG_REGEX')) {
+                       $pat = str_replace("[:alnum:]", "0-9a-zA-Z", $pat);
+                       $pat = str_replace("[:alpha:]", "a-zA-Z", $pat);
+                       $pat = str_replace("[:digit:]", "0-9", $pat);
+                       $pat = str_replace(".", "\.", $pat);
+                       $pat = str_replace("@", "\@", $pat);
+                       echo $key."=&nbsp;".$pat."<br />";
+               }
+
+               // Check if expression matches
+               $reg = ($reg || preg_match(("^".$pat."^"), $url));
+
+               // Does it match?
+               if ($reg === true) break;
+       }
+
+       // Return true/false
+       return $reg;
+}
+
+// Smartly adds slashes
+function smartAddSlashes ($unquoted) {
+       $unquoted = str_replace("\\", "", $unquoted);
+       return addslashes($unquoted);
+}
+
+// Decode entities in a nicer way
+function decodeEntities ($str) {
+       // @TODO We may want to switch over to UTF-8 here!
+       $decodedString = html_entity_decode($str, ENT_NOQUOTES, "ISO-8859-15");
+
+       // Return decoded string
+       return $decodedString;
+}
+
 //////////////////////////////////////////////////
 // AUTOMATICALLY RE-GENERATED MISSING FUNCTIONS //
 //////////////////////////////////////////////////