<?php
/*******************************************************************************
 * Copyright (C) 2007 Easter-eggs
 * https://ldapsaisie.org
 *
 * Author: See AUTHORS file in top-level directory.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

******************************************************************************/

/*
 * LSFormat : %{[key name][:A][:B][-][! ou _][~][%}}
 *
 * Extracted fields
 * - 0 : full string '%{...}'
 * - 1/key : key name
 * - 2 : :A
 * - 3/substr_a : A
 * - 4 : :B
 * - 5/substr_b : B
 * - 6/modifiers : "!" / "_" / "~" / "%"
 */
define(
  'GETFDATA_REGEX',
  "/%[{(](?P<key>[A-Za-z0-9]+)(\:(?P<substr_a>-?[0-9])+)?(\:(?P<substr_b>[0-9]+))?(?P<modifiers>[\!\_~%]*)[})]/"
);

/**
 * Build LSformat string
 *
 * This function build and return a LSformat string from specified format and provided data.
 *
 * @author Benjamin Renard <brenard@easter-eggs.com>
 *
 * @param string $format The LSformat string
 * @param string|object|array<string|object> $data Data to build the string
 *        This parameter could be a string, an object or an array of string or object.
 * @param string $meth The name to the method of objet(s) to call to get the replacement value for a
 *                     specified key
 *
 * @return string The builded LSformat string
 */
function getFData($format, $data, $meth=NULL) {
  $unique = false;
  if(!is_array($format)) {
    $format = array($format);
    $unique = true;
  }
  for($i=0; $i<count($format); $i++) {
    if(is_array($data)) {
      if (is_null($meth)) {
        while (preg_match(GETFDATA_REGEX, $format[$i], $ch)) {
          if (!isset($data[$ch['key']])) {
            $val = '';
          }
          elseif (is_array($data[$ch['key']])) {
            $val = $data[$ch['key']][0];
          }
          else {
            $val = $data[$ch['key']];
          }
          $val = _getFData_extractAndModify($val, $ch);
          $format[$i] = str_replace($ch[0], $val, $format[$i]);
        }
      }
      else {
        while (preg_match(GETFDATA_REGEX, $format[$i], $ch)) {
          if (method_exists($data[$ch['key']], $meth)) {
            $value = $data[$ch['key']] -> $meth();
            if (is_array($value)) {
              $value = $value[0];
            }
            $value = _getFData_extractAndModify($value, $ch);
            $format[$i] = str_replace($ch[0], $value, $format[$i]);
          }
          else {
            LSerror :: addErrorCode(
              'fct_getFData_01',
              array('meth' => $meth, 'obj' => $ch['key'])
            );
            break;
          }
        }
      }
    }
    elseif (is_object($data)) {
      if (is_null($meth)) {
        while (preg_match(GETFDATA_REGEX, $format[$i], $ch)) {
          $value = $data -> $ch['key'];
          if (is_array($value)) {
            $value = $value[0];
          }
          $value = _getFData_extractAndModify($value, $ch);
          $format[$i] = str_replace($ch[0], $value, $format[$i]);
        }
      }
      else {
        while (preg_match(GETFDATA_REGEX, $format[$i], $ch)) {
          if (method_exists($data, $meth)) {
            $value = $data -> $meth($ch['key']);
            if (is_array($value)) {
              $value = $value[0];
            }
            $value = _getFData_extractAndModify($value, $ch);
            $format[$i] = str_replace($ch[0], $value, $format[$i]);
          }
          else {
            LSerror :: addErrorCode(
              0,
              getFData(
                _("Function 'getFData' : The method %{meth} of the object %{obj} doesn't exist."),
                array('meth' => $meth, 'obj' => get_class($data))
              )
            );
            break;
          }
        }
      }
    }
    else {
      while (preg_match(GETFDATA_REGEX, $format[$i], $ch)) {
        $val = _getFData_extractAndModify($data, $ch);
        $format[$i] = str_replace($ch[0], $val, $format[$i]);
      }
    }
  }
  if($unique) {
    return $format[0];
  }
  return $format;
}

function _getFData_extractAndModify($data, $ch) {
  /*
   * Handle substring parameters (substr_a & substr_b)
   */

  // If A
  if($ch['substr_a']) {
    // If A and B
    if ($ch['substr_b']) {
      // If A and B=0
      if ($ch['substr_b'] == 0) {
        // If A<0 and B=0
        if ($ch['substr_a'] < 0) {
          $s = mb_strlen(strval($data)) - (-1 * $ch['substr_a']);
          $l = mb_strlen(strval($data));
        }
        // If A >= 0 and B
        else {
          $s = $ch['substr_a'];
          $l = mb_strlen(strval($data));
        }
      }
      // If A and B > 0
      elseif ($ch['substr_b'] > 0) {
        // If A < 0 and B > 0 or A >= 0 and B > 0
        $s = $ch['substr_a'];
        $l = $ch['substr_b'];
      }
      // If A and B < 0
      else {
        // If A < 0 and B < 0
        if ($ch['substr_a'] < 0) {
          $s = $ch['substr_b'];
          $l = false;
        }
        // If A >= 0 and B < 0
        else {
          $s = $ch['substr_a'] + $ch['substr_b'];
          $l = abs($ch['substr_b']);
        }
      }
    }
    // If only A
    else {
      if ($ch['substr_a'] < 0) {
        $s = $ch['substr_a'];
        $l = false;
      }
      else {
        $s = 0;
        $l = $ch['substr_a'];
      }
    }

    if ($l == false) {
      $val = mb_substr(strval($data), $s);
    }
    else {
      $val = mb_substr(strval($data), $s, abs($l));
    }
  }
  else {
    try {
      $val = strval($data);
    }
    catch (Exception $e) {
      $val = _('[not string value]');
    }
  }

  /*
   * Handle modifiers
   */
  if ($ch['modifiers']) {
    # Without Accent
    if (strpos($ch['modifiers'], '~') !== false) {
      $val = withoutAccents($val);
    }

    # Upper / Lower case
    if (strpos($ch['modifiers'], '!') !== false) {
      $val = mb_strtoupper($val);
    }
    elseif (strpos($ch['modifiers'], '_') !== false) {
      $val = mb_strtolower($val);
    }

    # Escape HTML entities
    if (strpos($ch['modifiers'], '%') !== false) {
      $val = htmlentities($val);
    }
  }

  return $val;
}

function getFieldInFormat($format) {
  $fields = array();
  while (preg_match(GETFDATA_REGEX, $format, $ch)) {
    $fields[] = $ch['key'];
    $format = str_replace($ch[0], '', $format);
  }
  return $fields;
}

function loadDir($dir,$regexpr='/^.*\.php$/') {
  if ($handle = opendir($dir)) {
    while (false !== ($file = readdir($handle))) {
      if (preg_match($regexpr,$file)) {
        require_once($dir.'/'.$file);
      }
    }
  }
  else {
    die(_('Folder not found').' : '.$dir);
  }
  return true;
}


function valid($obj) {
  LSdebug('function valid() : ok');
  return true;
}

function validPas($obj=null) {
  LSdebug('function valid() : nok');
  return false;
}

function return_data($data) {
  return $data;
}

function varDump($data) {
  ob_start();
  var_dump($data);
  $data=ob_get_contents();
  ob_end_clean();
  return $data;
}

/*
 * LSdebug
 */
$GLOBALS['LSdebug_fields'] = array();
function LSdebug($data, $dump=false) {
  if ($dump)
    $data = varDump($data);

  if (class_exists('LSlog'))
    LSlog :: debug($data);

  $GLOBALS['LSdebug_fields'][] = htmlentities(strval($data));
  return true;
}

function LSdebug_print($return=false) {
  $result = array();
  if (LSdebugDefined())
    $result = $GLOBALS['LSdebug_fields'];

  // Reset
  $GLOBALS['LSdebug_fields'] = array();
  if ($return)
    return $result;
    LStemplate :: assign(
      'LSdebug_content',
      base64_encode(
        json_encode(
          $result
        )
      )
    );
  return;
}

function LSdebugDefined() {
  if (!LSdebug)
    return;
  return (
    isset($GLOBALS['LSdebug_fields']) &&
    is_array($GLOBALS['LSdebug_fields']) &&
    !empty($GLOBALS['LSdebug_fields'])
  );
}

/**
 * Vérifie la compatibilite des DN
 *
 * Vérifie que les DNs sont dans la même branche de l'annuaire.
 *
 * @param string $dn1 Un premier DN.
 * @param string $dn2 Un deuxième DN.
 *
 * @author Benjamin Renard <brenard@easter-eggs.com>
 *
 * @return boolean true si les DN sont compatibles, false sinon.
 */
function isCompatibleDNs($dn1,$dn2) {
  $infos1=ldap_explode_dn($dn1,0);
  if(!$infos1)
    return false;
  $infos2=ldap_explode_dn($dn2,0);
  if(!$infos2)
    return false;
  if($infos2['count']>$infos1['count']) {
    $tmp=$infos1;
    $infos1=$infos2;
    $infos2=$tmp;
  }
  $infos1=array_reverse($infos1);
  $infos2=array_reverse($infos2);

  for($i=0;$i<$infos1['count'];$i++) {
    if (!isset($infos2[$i])) continue;
    if($infos1[$i]==$infos2[$i]) continue;
    return false;
  }
  return true;
}

/**
 * Fait la somme de DN
 *
 * Retourne un DN qui correspond au point de séparation des DN si les DN
 * ne sont pas dans la meme dans la meme branche ou le dn le plus long sinon.
 *
 * @param string $dn1 Un premier DN.
 * @param string $dn2 Un deuxième DN.
 *
 * @author Benjamin Renard <brenard@easter-eggs.com>
 *
 * @return string|false Un DN (ou false si les DN ne sont pas valide)
 */
function sumDn($dn1,$dn2) {
  $infos1=ldap_explode_dn($dn1,0);
  if(!$infos1)
    return false;
  $infos2=ldap_explode_dn($dn2,0);
  if(!$infos2)
    return false;
  if($infos2['count']>$infos1['count']) {
    $tmp=$infos1;
    $infos1=$infos2;
    $infos2=$tmp;
  }
  $infos1=array_reverse($infos1);
  $infos2=array_reverse($infos2);

  $first=true;
  $basedn='';
  for($i=0;$i<$infos1['count'];$i++) {
    if(($infos1[$i]==$infos2[$i])||(!isset($infos2[$i]))) {
      if($first) {
        $basedn=$infos1[$i];
        $first=false;
      }
      else
        $basedn=$infos1[$i].','.$basedn;
    }
    else {
      return $basedn;
    }
  }
  return $basedn;
}

/**
 * Extract and retreive RDN from DN
 * @param string $dn
 * @param bool $with_attr If true, include the RDN attribute name (optional, default: true)
 * @return string|null|false The parent object DN, null if no parent or false in case of error
 */
function getRdn($dn, $with_attr=true) {
  $parts = ldap_explode_dn($dn, 0);
  if (!is_array($parts) || !isset($parts['count'])) return false;
  if ($with_attr)
    return $parts[0];
  $equal_pos = strpos($parts[0], '=');
  if ($equal_pos !== false)
    return substr($parts[0], $equal_pos+1);
  return $parts[0];
}

/**
 * Retreive parent object DN
 * @param string $dn
 * @return string|null|false The parent object DN, null if no parent or false in case of error
 */
function parentDn($dn) {
  $parts = ldap_explode_dn($dn, 0);
  if (!is_array($parts) || !isset($parts['count'])) return false;
  if ($parts['count'] == 1) return null;
  return implode(',', array_slice($parts, 2));
}

function checkEmail($value,$domain=NULL,$checkDns=true) {
  $log = LSlog :: get_logger('checkEmail');
  $regex = '/^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/';

  if (!preg_match($regex, $value)) {
    $log -> debug("'$value': regex fail");
    return false;
  }

  $nd = explode('@', $value);
  $nd=$nd[1];

  if ($domain) {
    if(is_array($domain)) {
      if (!in_array($nd,$domain)) {
        $log -> debug("'$value': domain '$nd' not authorized. Allowed domains: '".implode("', '", $domain)."'");
        return false;
      }
    }
    else {
      if($nd!=$domain) {
        $log -> debug("'$value': domain '$nd' not authorized. Allowed domains: '$domain'");
        return false;
      }
    }
  }

  if ($checkDns && function_exists('checkdnsrr')) {
    if (!(checkdnsrr($nd, 'MX') || checkdnsrr($nd, 'A'))) {
      $log -> debug("'$value': DNS check fail");
      return false;
    }
  }

  $log -> debug("'$value': validated");
  return true;
}

function generatePassword($chars=NULL,$length=NULL) {
  if (!$length) {
      $length=8;
  }
  if (is_array($chars)) {
    $retval='';
    foreach($chars as $chs) {
      if (!is_array($chs)) {
        $chs=array('chars' => $chs);
      }
      if (!isset($chs['nb']) || !is_int($chs['nb'])) {
        $chs['nb']=1;
      }
      $retval.=aleaChar($chs['chars'],$chs['nb']);
    }
    $add = ($length-strlen($retval));
    if ($add > 0) {
      $retval .= aleaChar($chars,$add);
    }
    return str_shuffle($retval);
  } else {
    return aleaChar($chars,$length);
  }
}

function aleaChar($chars=NULL,$length=1) {
  if (is_array($chars)) {
    $nchars="";
    foreach($chars as $chs) {
      if (is_string($chs)) {
        $nchars.=$chs;
      }
      else if (is_string($chs['chars'])) {
        $nchars.=$chs['chars'];
      }
    }
    if(is_string($chars) && strlen($chars)>0) {
      $chars=$nchars;
    }
    else {
      $chars=NULL;
    }
  }
  if (!$chars) {
    $chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
  }
  $nbChars=strlen($chars);
  $retval="";
  if(is_int($length)) {
    for ($i=0;$i<$length;$i++) {
      $retval.=$chars[rand(0,$nbChars-1)];
    }
  }
  return $retval;
}

function compareDn($a,$b) {
  if (substr_count($a,',') > substr_count($b,','))
    return -1;
  else
    return 1;
}

/**
 * Translate message by using LSlang or Gettext methods
 *
 * @param string $msg The message
 *
 * @return string The translated message if translation available, the original message otherwise
 **/
function __($msg) {
  if (empty($msg)) return $msg;
  if (isset($GLOBALS['LSlang'][$msg])) {
    return $GLOBALS['LSlang'][$msg];
  }
  return _($msg);
}

/**
 * Non-translate message
 *
 * Just-return the input message. This function permit the detection of message
 * that will be translated only at display time and not at declare time.
 *
 * @param string $msg The message
 *
 * @return string The message (unchanged)
 **/
function ___($msg) {
  return $msg;
}

// Try to load unidecode library
// Note: unidecode lib use mb_ord function only available since PHP 7.2.
if (!function_exists('unidecode') && function_exists('mb_ord')) {
  if (file_exists(LS_LIB_DIR."/unidecode/unidecode.php"))
    @include(LS_LIB_DIR."/unidecode/unidecode.php");
  if (!function_exists('unidecode') && stream_resolve_include_path("unidecode/unidecode.php"))
    @include("unidecode/unidecode.php");
}

 /**
  * Supprime les accents d'une chaine
  *
  * @param string $string La chaine originale
  *
  * @return string La chaine sans les accents
  */
function withoutAccents($string){
  // Use unidecode lib if available
  if (function_exists('unidecode'))
    return unidecode($string);

  // Otherwise, use historical method
  $replaceAccent = array(
    "à" => "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", "Ç" => "C", "È" => "E", "É" => "E", "Ê" => "E", "Ë" => "E",
    "Ì" => "I", "Í" => "I", "Î" => "I", "Ï" => "I", "Ñ" => "N", "Ò" => "O",
    "Ó" => "O", "Ô" => "O", "Õ" => "O", "Ö" => "O", "Ù" => "U", "Ú" => "U",
    "Û" => "U", "Ü" => "U", "Ý" => "Y"
  );
  return strtr($string, $replaceAccent);
}


 /**
  * Supprime les espaces d'une chaine en les remplacant par un motif ou non
  *
  * @param string $str La chaine originale
  * @param string $to Le motif de remplacement. S'il n'est pas spécifié, les espaces seront simplement supprimés
  *
  * @return string La chaine sans les espaces
  **/
  function replaceSpaces($str,$to='') {
    return strtr($str,array (
               ' ' => $to,
               "\t" => $to
             )
           );
  }

 /**
  * List files in a directory corresponding to a regex
  *
  * @param string $dir The path of the directory
  * @param string $regex The regex apply on filename
  *
  * @return array() List of file name
  **/
  function listFiles($dir,$regex) {
    $retval=array();
    if (is_dir($dir)) {
      $d = dir($dir);
      while (false !== ($file = $d->read())) {
        if (is_file("$dir/$file")) {
          if (preg_match($regex, $file, $m)) {
            $retval[]=((is_array($m) && count($m)>1)?$m:$file);
          }
        }
      }
    }
    return $retval;
  }

 /**
  * Return current date in LDAP format
  *
  * @param mixed $mixed Anything (to permit using as generated function)
  *
  * @return string The current date in LDAP format (YYYYMMDDHHMMSSZ)
  **/
  function now($mixed=Null) {
    return date ('YmdHis').'Z';
  }

/**
 * Check if a path is absolute
 *
 * @param string $path The path
 *
 * @return boolean True if path is absolute, False otherwise
 */
function isAbsolutePath($path) {
  return strStartWith($path, '/') || strStartWith($path, './') || strStartWith($path, '../');
}

/**
 * Check if a string start with another specified string
 *
 * @param string $string The string to search in
 * @param string $start_string The starting string to check
 *
 * @return boolean True if string start by specified one, False otherwise
 */
function strStartWith($string, $start_string) {
  if (strlen($start_string) > strlen($string))
    return false;
  return substr($string, 0, strlen($start_string)) === $start_string;
}

/**
 * Dump file content
 *
 * @param string $file_path The file path to dump
 * @param string|null $mime_type The MIME type return as Content-type (optional, default: auto-detected)
 * @param int $max_age The cache max_age value, as return in Cache-Control HTTP header
 *                             (optional, default: 3600)
 * @param boolean $force_download Set to true to force download (optional, default: false)
 * @param string|null $filename Specific filename in case of force download (optional, default: orignal filename)
 *
 * @return void
 **/
function dumpFile($file_path, $mime_type=null, $max_age=3600, $force_download=false, $filename=null) {
  if (is_file($file_path)) {
    header('Content-Type: '.(is_null($mime_type)?mime_content_type($file_path):$mime_type));
    if ($force_download)
      header("Content-disposition: attachment; filename=\"".($filename?$filename:basename($file_path))."\"");
    $last_modified_time = filemtime($file_path);
    $etag = md5_file($file_path);
    header("Cache-Control: max-age=$max_age, must-revalidate");
    header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
    header("Etag: $etag");

    if (
      (
        isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
        @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time
      )
      ||
      (
        isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
        trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag
      )
    ) {
            header("HTTP/1.1 304 Not Modified");
            exit();
    }

    header('Pragma: public');
    header('Content-Length: ' . filesize($file_path));
    readfile($file_path);
    exit();
  }

  // File not found, Trigger error 404 (via LSurl if defined)
  if (class_exists('LSurl')) {
    LSurl :: error_404();
  }
  else {
    header("HTTP/1.1 404 Not found");
    exit();
  }
}

/**
 * Format a callable object for logging
 * @param  callable $callable The callable object
 * @return string The callable object string representation
 */
function format_callable($callable) {
  if (is_string($callable))
    return $callable."()";
  if (is_array($callable) && count($callable)==2)
    if (is_string($callable[0]))
      return $callable[0]."::".$callable[1]."()";
    elseif (is_object($callable[0]))
      return get_class($callable[0])."->".$callable[1]."()";
    else
      return "Unkown->".$callable[1]."()";
  return varDump($callable);
}

function is_empty($val) {
  switch(gettype($val)) {
    case "boolean":
    case "integer":
    case "double":
    case "object":
    case "resource":
            return False;
    case "array":
    case "string":
      if ($val == "0") return false;
      return empty($val);
    case "NULL":
      return True;
  }
  return empty($val);
}

function ensureIsArray($value) {
  if (is_array($value))
    return $value;
  if (is_empty($value))
    return array();
  return array($value);
}

function ldapDate2DateTime($value, $naive=False, $format=null) {
  if (is_null($format))
    $format = ($naive?'YmdHis*':'YmdHisO');
  $datetime = date_create_from_format($format, $value);
  if ($datetime instanceof DateTime)
    return $datetime;
  return False;
}

function ldapDate2Timestamp($value, $naive=False, $format=null) {
  $datetime = ldapDate2DateTime($value, $naive, $format);
  if ($datetime instanceof DateTime)
    return $datetime -> format('U');
  return False;
}

function dateTime2LdapDate($datetime, $timezone=null, $format=null) {
  if ($timezone != 'naive' && $timezone != 'local') {
    $datetime -> setTimezone(timezone_open(is_null($timezone)?'UTC':$timezone));
  }
  if (is_null($format))
    $format = ($timezone == 'naive'?'YmdHis':'YmdHisO');
  $datetime_string = $datetime -> format($format);

  // Replace +0000 or -0000 end by Z
  $datetime_string = preg_replace('/[\+\-]0000$/', 'Z', $datetime_string);

  return $datetime_string;
}

function timestamp2LdapDate($value, $timezone=null, $format=null) {
  $datetime = date_create("@$value");
  if ($datetime instanceof DateTime)
    return dateTime2LdapDate($datetime, $timezone, $format);
  return false;
}

/**
 * Generate an UUID
 * @return string
 */
function generate_uuid() {
  return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
    // 32 bits for "time_low"
    mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),

    // 16 bits for "time_mid"
    mt_rand( 0, 0xffff ),

    // 16 bits for "time_hi_and_version",
    // four most significant bits holds version number 4
    mt_rand( 0, 0x0fff ) | 0x4000,

    // 16 bits, 8 bits for "clk_seq_hi_res",
    // 8 bits for "clk_seq_low",
    // two most significant bits holds zero and one for variant DCE1.1
    mt_rand( 0, 0x3fff ) | 0x8000,

    // 48 bits for "node"
    mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
  );
}

/**
 * Format a number
 * @param float|int $number
 * @return string
 */
function format_number($number) {
  if ((int)$number == $number) return strval($number);
  return number_format($number, 2, ',', '.');
}

/**
 * Format a size (in bytes)
 * @param int  $value
 * @param boolean|string $unit   Unit of the provided value (optional, default=bytes)
 * @return string|false
 */
function format_size($value, $unit=false) {
  $units = array(
    ___('TB') => 1099511627776,
    ___('GB') => 1073741824,
    ___('MB') => 1048576,
    ___('KB') => 1024,
    ___('B') => 1,
  );
  if (!$unit) $unit = 'B';
  if (!array_key_exists($unit, $units)) return false;
  $value = $value * $units[$unit];
  foreach ($units as $unit => $factor) {
    if ($value >= $factor)
      return format_number($value / $factor)._($unit);
  }
  // 0 ?
  return strval($value);
}

/**
 * Convert PHP ini value with unit as bytes
 * @param string $value
 * @return int
 */
function php_ini_unit_to_bytes($value) {
  if ($value == "-1") return -1;
  return (int)preg_replace_callback('/(\-?\d+)(.?)/', function ($m) {
    $factor = $m[2]?strpos('BKMG', $m[2]):null;
    return $factor?intval($m[1]) * pow(1024, $factor):$m[0];
  }, strtoupper($value));
}
