<?php

namespace FOP;

class Util {
   // --- Konstanten --- 
  
    const KEY_DAY = 0b1;
  

    // --- Statische Eigenschaften ---
  
  static private $counter = 0;
  static private $versionAppendix ;

    // --- Statische Methoden ---
  
  public static function array_value($arr, $idx) { 
    
    
    // --- BLOCK: main ---
    return $arr[$idx];
    
  }

  public static function parse_qs($s) { 
    
    
    // --- BLOCK: main ---
    $s = preg_replace_callback('/(^|(?<=&))[^=[&]+/',function($k) { return bin2hex(urldecode($k[0])); },$s);parse_str($s, $p);return array_combine(array_map('hex2bin', array_keys($p)), $p);
    
  }

  public static function expandUrl($url, $params) { 
    
    
    // --- BLOCK: main ---
    
	 	$args = array();
		$qm = strpos($url, '?');
		if ($qm !== false) {
			foreach(explode('&', substr($url, $qm + 1)) AS $line) {
				$line = explode('=', $line);
				$args[$line[0]] = $line[1];
			}
			$url = substr($url, 0, $qm);
		}
		foreach($params AS $key => $val) {
			if ($val === false) unset($args[$key]);
			else $args[$key] = urlencode($val);
		}
		array_walk($args, function (&$val, $key) {
			$val = $key . '=' . $val;
		});
		return $url.'?'.implode('&', $args);
	
    
  }

  public static function getBackUrl() { 
    
    
    // --- BLOCK: main ---
    
		  if (isset($_GET['parent_request'])) {
			  $req = $_GET['parent_request'] . '?';
			  $isFirst = true;
			  foreach($_GET AS $k => $v) {
				  if ($k != 'parent_request' && $k != '_q') {
					  if ($isFirst) $isFirst = false; else $req .= '&';
					  $req .= $k . '=' . urlencode($v);
				  }
			  }
		  } else {
			  $req = $_SERVER['REQUEST_URI'];
		  }
		  return $req;
		  
    
  }

  public static function getVersionAppendix() { 
    
    
    // --- BLOCK: main ---
    
		return self::$versionAppendix;
	
    
  }

  public static function ensureIsLoaded() { 
    
    
    // --- BLOCK: main ---
    
  // Can be called to ensure class is loaded and initialized

    
  }

  public static function getPrintUserErrors() { 
    
    
    // --- BLOCK: main ---
    return \FOP\Configuration::getFOPUtilPrintUserErrors();
    
  }

  public static function getCurrentBaseurl() { 
    
    
    // --- BLOCK: main ---
    
		/* ToDo: Alternative für HTTTP-basierte Crons entwicklen */
		if (!isset($_SERVER['SERVER_NAME'])) return \FOP\Configuration::getBaseurl();
		$pageURL = 'http';
		if (array_key_exists('HTTPS', $_SERVER)){
			if(isset($_SERVER['HTTPS'])) {$pageURL .= 's';}
		}
		$pageURL .= '://';
		if ($_SERVER['SERVER_PORT'] != '443' && $_SERVER['SERVER_PORT'] != '80') {
			$pageURL .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI'];
		} else {
			$pageURL .= $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
		}
		$pageURL = strtok($pageURL, '?');
		global $_global_q;
		$i = strpos($pageURL, '/' . ($_global_q ?? '§§' ?: '§§'));
		if ($i) {
			// Page-URL croppen (TODO: Corner Cases und escaping testen!)
			$pageURL = substr($pageURL, 0, $i + 1);
		} else if (isset($_global_q) && $_global_q == '') {
		} else if (!preg_match('/\/$/', $pageURL)) {
			$pageURL = substr($pageURL, 0, strlen($pageURL) - strlen(basename($pageURL)));
		}
		return $pageURL;

    
  }

  public static function getNextCounter() { 
    
    
    // --- BLOCK: main ---
    return self::$counter++;
    
  }

  public static function getEnableRewrite() { 
    
    
    // --- BLOCK: main ---
    return \FOP\Configuration::getFOPUtilEnableRewrite();
    
  }

  public static function urlencode($url) { 
    
    
    // --- BLOCK: main ---
    
		if (is_array($url)) {
			$url = new \FOP\IDList($url);
		}
		if ($url instanceof \FOP\IDList) return $url->toString();
		return urlencode($url);
	
    
  }

  public static function convertIniBytes($value) { 
    
    
    // --- BLOCK: main ---
    
	if ( is_numeric($value) ) {
		return $value;
	} else {
		$value_length = strlen($value);
		$qty = substr ($value, 0, $value_length -1  );
		$unit = strtolower( substr( $value, $value_length - 1 ) );
			switch($unit) {
			case 'k':
				$qty *= 1024;
				break;
			case 'm':
				$qty *= 1048576;
				break;
			case 'g':
				$qty *= 1073741824;
				break;
			}
			return $qty;
	}

	
    
  }

  public static function IntStringNull($x) { 
    
    
    // --- BLOCK: main ---
    
		return $x === null ? null : (string)$x; // workaround IFNULL(FETCH dropdown_field FROM ..., ...)
	
    
  }

  public static function BoolSanitizer($x) { 
    
    
    // --- BLOCK: main ---
    return $x === null ? null : (bool)$x;
    
  }

  public static function intbytestoreadable($x) { 
    
    
    // --- BLOCK: main ---
    
	if(!$x <= 0) {
		$size = array('Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB');
		$factor = min(floor((strlen($x) - 1) / 3), 6);
		return sprintf("%.1f", $x / pow(1024, $factor)) .' '. $size[$factor];
	}
    
  }

  public static function FalseToNull($x) { 
    
    
    // --- BLOCK: main ---
    return $x === false ? null : $x;
    
  }

  public static function getIDArray($tbl, $col) { 
    
    
    // --- BLOCK: main ---
    
		$rows = \FOP\Database::query('SELECT ' . qi($col) . ' FROM ' .  qi($tbl))->fetchAll();
		$ids = array();

		foreach($rows as $row){
			array_push($ids, intval($row[$col]));
		}

		return $ids;
	
    
  }

  public static function removeFromArray($from, $what) { 
    
    
    // --- BLOCK: main ---
    
		for($i = 0; $i < count($from); $i++){
			if(in_array($from[$i], $what)){
				array_splice($from, $i, 1);
				$i--;
			}
		}
		return $from;
	
    
  }

  public static function escapeSEPA($x, $maxlen = 0 ) { 
    
    
    // --- BLOCK: main ---
    
	$search = array('Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'ß', '´');
	$replace = array('Ae', 'Oe', 'Ue', 'ae', 'oe', 'ue', 'ss', '');
	$x = str_replace($search, $replace, $x);
	$x = preg_replace('/[^ a-zA-Z0-9\/?:().,\'+-]/', '-', $x);
	if ($maxlen) {
		$x = substr($x, 0, $maxlen);
	}
	return $x;

    
  }

  public static function aggregateJoin($array, $groupfields, $itemname) { 
    
    
    // --- BLOCK: main ---
    
		 $result = [];
		 foreach($array AS $item) {
			  if (!count($result) || $result[count($result)-1][$groupfields[0]] != $item[$groupfields[0]]) {
				  $group = [];
				  $group[$itemname] = [$item];
				  foreach ($groupfields AS $field) $group[$field] = $item[$field];
						array_push($result, $group);
			  } else {
				  array_push($result[count($result)-1][$itemname], $item);
			  }
		 }
		 return $result;
    
  }

  public static function concurrentArrayWalk($iterator, &$inputs, $callback) { 
    
    
    // --- BLOCK: main ---
    
		// diese Funktion läuft gleichzeitig durch mehrere Arrays
		// als Schrittgeber dient ein $iterator (1. Parameter), z.B. range($start,$stop,$step)
		// $inputs ist eine Liste von Objekten, je eines pro Array, das durchlaufen wird, mit den Feldern:
		//  	'data': das Array selbst; jedes Element besteht i.A. wiederum aus einem Assoziativ-Array
		//		'key' (TODO: optional): wird mit dem Iterator verglichen.
		//			Das aktuelle Element ($cur) ist immer das letzte mit $cur['$input['key']] <= iteator
		// 			Das Array sollte nach dem key sortiert sein.
		//			(TODO: Wenn key nicht angegeben: Das Array wird bei jedem Iteratorschritt inkrementiert)
		//		'onChange' (optional): eine Callback-Funktion, die bei jeder Veränderung des aktuellen Elements aufgerufen wird (2 Parameter: $iterator->current(), neues &$cur)
		//		'flags' (optional):
		//			\FOP\Util::KEY_DAY: Key ist ein tagesgenauer Timestamp (wird auf 0:00:00 normalisiert)
		// $callback: Callback-Funktion mit zwei Parametern ($iterator->current(), array der current-Elemente)
		//		wird bei jedem Iteratorschritt aufgerufen
		//
		// Beispiel für die Verwendung: backends/easyeditor/businesslogic/personal/zeiterfassung-wochenstunden.fop
		
		// key-Normalizer
		function normalize($key, $flags = null) {
			if (isset($flags)) {
				if ($flags & \FOP\Util::KEY_DAY) return strtotime('0:00:00', $key);
				else return $key;
			} else return $key;
		}
		
		// initialize
		$cur = [];
		foreach ($inputs as $which => &$input) {
			reset($input['data']);
			$cur[$which] = null;
		}
		// step through iterator
		for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
			// benutzt die PHP-Array-Funktionen current() und next()
			// current($input['data']) soll (trotz des Namens) immer auf das _nächste_ Element zeigen,
			// also das erste mit $key > $time
			// das aktuelle Element wird in $cur gespeichert und an den Callback übergeben
			foreach ($inputs as $which => &$input) {
				while (current($input['data']) && normalize(current($input['data'])[$input['key']], $input['flags']) <= $iterator->current()) {
					$cur[$which] = current($input['data']);
					next($input['data']);
					if (isset($input['onChange'])) call_user_func($input['onChange'], $iterator->current(), $cur[$which]);
				}
			}

			/* Das Array-Unpacking mit ... gibt es erst seit PHP 7.4, aber 7.3 wird noch gepflegt und ist noch auf manchen Servern
			call_user_func_array($callback, array($iterator->current(), ...array_values($cur))); */
		}
	
    
  }

  public static function SanitizeUserHtml($userinput, $email = false , $placeholders = [] ) { 
    
    
    // --- BLOCK: main ---
    
	if (strlen($userinput) < 0) {
			return false;
		} else {
			if ($email) {
			$userinput = \FOP\Sanitizers::innerHTML_parse($userinput);
			}
		$doc = new \DOMDocument();
		$allowed_tags = array('b', 'i', 'u', 'strong', 'br', 'hr', 'img', 'span', 'h1', 'h2', 'h3', 'h4', 'pre', 'sup', 'sub', 'center', 'code', 'blockquote', 'div', '#text', 'table', 'tbody', 'thead', 'tr', 'td', 'a', 'em', 'p', 'font', 'h', 'li', 'ul', 'ol', 'pagebreak');
		$remove_tags = array('script', 'style');
		$allowed_attr = array('src', 'href', 'class', 'id', 'style', 'title', 'target', 'spellcheck', 'alt', 'face', 'width', 'height');
		@$doc->loadHTML('<?xml encoding="utf-8" ?><span>' . $userinput . '</span>', LIBXML_HTML_NOIMPLIED);
		\FOP\Util::visitNode($doc->documentElement, $allowed_tags, $remove_tags, $allowed_attr, $placeholders);
		return $doc->saveHTML($doc->documentElement);
	}
	
    
  }

  public static function visitNode($domNode, $allowed_tags, $remove_tags, $allowed_attr, $placeholders) { 
    
    
    // --- BLOCK: main ---
    
		if (!$domNode) return;
		$nextSibling = $domNode->nextSibling;
		if ($domNode instanceof \DOMElement && $domNode->hasAttribute('data-if') && !empty($placeholders) && (!isset($placeholders[$domNode->getAttribute('data-if')]) || !$placeholders[$domNode->getAttribute('data-if')])) {
			// remove data-if nodes which are not present in placeholders
			$domNode->parentNode->removeChild($domNode);
		} else if ($domNode instanceof \DOMElement && $domNode->hasAttribute('data-field') && isset($placeholders[$domNode->getAttribute('data-field')])) {
			$newdom = new \DOMDocument;
			// prevent warning
			if (!empty($placeholders[$domNode->getAttribute('data-field')]))
				$newdom->loadHTML($placeholders[$domNode->getAttribute('data-field')]);
			// can still generate an error, f.ex. if placeholder == ' '
			if ($newdom->documentElement) {
				$newdom = $domNode->ownerDocument->importNode($newdom->documentElement->firstChild->firstChild, true);
				while ($newdom->firstChild) $domNode->parentNode->insertBefore($newdom->firstChild, $domNode);
			}
			$domNode->parentNode->removeChild($domNode);
		} else if (in_array($domNode->nodeName, $remove_tags)) {
				// remove certain tags
				$domNode->parentNode->removeChild($domNode);
		} else {
			// replace unallowed tags with a fresh span
			if (!in_array($domNode->nodeName, $allowed_tags) && !substr($domNode->nodeName, 0, 5) != 'data-') {
				//$domNode->parentNode->removeChild($domNode);
				$newspan = $domNode->ownerDocument->createElement('span');
				while ($domNode->firstChild) {
					$newspan->appendChild($domNode->firstChild);
				}
				$domNode->parentNode->replaceChild($newspan, $domNode);
				$newspan->setAttribute('data-old-tag', $domNode->nodeName);
				$domNode = $newspan;
			}

			// visit node
			\FOP\Util::visitNode($domNode->firstChild, $allowed_tags, $remove_tags, $allowed_attr, $placeholders);
			if ($domNode->hasAttributes()) {
				foreach($domNode->attributes as $attr) {
					if (substr($attr->nodeName, 0, 5) != 'data-' && !in_array($attr->nodeName, $allowed_attr)) {
						$domNode->removeAttribute($attr->nodeName);
					}
				}
			}
		}
		\FOP\Util::visitNode($nextSibling, $allowed_tags, $remove_tags, $allowed_attr, $placeholders);
	
    
  }

  public static function initialize() { 
    
    
    // --- BLOCK: parse_qs ---
    $_GET = self::parse_qs(isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '');
    // --- BLOCK: version ---
    self::$versionAppendix = @file_get_contents('version.txt') ?: 'unknown';
    
  }

  public static function exception_handler($exception) { 
    
    
    // --- BLOCK: handler ---
    
				if (!headers_sent()) {
					header('HTTP/1.0 500 Internal Server Error');
				}
				$msg = $exception->getMessage() . "\n\n" . $exception->getTraceAsString() . "\n\n" . print_r($exception, TRUE);
				error_log($msg);
            echo htmlentities(_('InternalError'));
				if (\FOP\Configuration::getFOPUtilPrintUserErrors() == 'true' || \FOP\Configuration::getFOPUtilPrintUserErrors() == 1) {
					echo '<pre>' . htmlentities($msg) . '</pre>';
				}
    
    // --- BLOCK: nloghookfop_exceptions ---
    
    $logdata = array('namespace' => 'FOP', 'class' => 'Util',
        'method' => 'exception_handler');

    $logdata['user'] = \FOP\User::getSessionUserID();

    $logdata['description'] = $exception->getMessage();
		$logdata['param1'] = 'trace'; $logdata['param1value'] = $exception->getTraceAsString(); $logdata['param2value'] = json_encode($exception->getTrace());
    

    \FOP\LogFop_exceptions::create($logdata);
    
    // --- BLOCK: zquit ---
    die();
    
  }

  public static function getUniqueName($tbl, $col, $prefix) { 
    
    
    // --- BLOCK: main ---
    

		return $prefix.\FOP\Database::query('SELECT MAX(CONVERT(SUBSTR(`' . str_replace('`', '``', $col) . '`, ' . (strlen($prefix) + 1) . ', CHAR_LENGTH(`' . str_replace('`', '``', $col) . '`)), SIGNED INTEGER)) + 1 AS result  FROM `' . str_replace('`', '``', $tbl) . '` WHERE `' . str_replace('`', '``', $col) . '` LIKE ' . \FOP\Database::quote(str_replace('%', '%%', $prefix) . '%'))->fetchColumn();
	 
    
  }


  
  

  
  
}

\FOP\Util::initialize(); 

