Subversion Repositories oidplus

Rev

Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. # todo: "recursive" functions private machen, damit params nicht manipuliert werden können...
  4. # todo: return false -> exception
  5.  
  6. # TODO false vs null ausgaben
  7. # TODO signed openssl output
  8.  
  9. # TODO: oid definieren für name based
  10. # QUE: geht extention von macros?
  11.  
  12. # TODO: "server time" am ende von Âquery() anzeigen?
  13.  
  14. require_once __DIR__ . '/../includes/uuid_utils.inc.php';
  15. require_once __DIR__ . '/../includes/oid_utils.inc.php';
  16.  
  17. define('UUID_NAMEBASED_NS_OidPlusMisc',   'ad1654e6-7e15-11e4-9ef6-78e3b5fc7f22');
  18. define('UUID_NAMEBASED_NS_OidPlusNSOnly', '0943e3ce-4b79-11e5-b742-78e3b5fc7f22');
  19.  
  20. define('GENERATION_ROOT_DEFAULT', '.2.25.<SYSID>');
  21.  
  22. class VolcanoDB {
  23.         protected $macro_data = array(); // TODO: use with caution!
  24.         protected $oid_data = array(); // TODO: use with caution!
  25.         protected $authTokens = array();
  26.         protected $configuration = array();
  27.         protected $configuration_file = null;
  28.         protected $configuration_may_create = false;
  29.  
  30.         protected $redactedMessage = '# Information redacted. Please append a correct auth token with your request.';
  31. #       protected $redactedMessage = null;
  32.  
  33.         public function filterRedactedEntries($output) {
  34.                 $out = '';
  35.  
  36.                 $lines = explode("\n", $output);
  37.                 foreach ($lines as $line) {
  38.                         $line = trim($line);
  39.                         if ($line == '') continue;
  40.                         $name_val = explode(':', $line, 2);
  41.                         if (count($name_val) != 2) continue;
  42.                         if (trim($name_val[1]) != trim($this->redactedMessage)) {
  43.                                 $out .= $line."\n";
  44.                         }
  45.  
  46.                 }
  47.  
  48.                 return $out;
  49.         }
  50.  
  51.         public function getOIDData() {
  52.                 return $this->oid_data;
  53.         }
  54.  
  55.         public function getMacroData() {
  56.                 return $this->macro_data;
  57.         }
  58.  
  59.         public function __construct($conf_file, $may_create) {
  60.                 $this->configuration_file = $conf_file;
  61.                 $this->configuration_may_create = $may_create;
  62.                 $this->readConfiguration();
  63.         }
  64.  
  65.         public function getConfigValue($name) {
  66.                 if (!isset($this->configuration[$name])) return false;
  67.                 return $this->configuration[$name];
  68.         }
  69.  
  70.         public function setConfigValue($name, $value) {
  71.                 $this->configuration[$name] = $value;
  72.                 $this->saveConfiguration();
  73.         }
  74.  
  75.         protected function readConfiguration() {
  76.                 if (is_null($this->configuration_file)) return;
  77.                 $this->configuration = array();
  78.                 if (!file_exists($this->configuration_file)) {
  79.                         if (!$this->configuration_may_create) {
  80.                                 $metadata = array();
  81.                                 throw new VolcanoException("Configuration file '$conf_file' not found", $metadata);
  82.                         }
  83.                 } else {
  84.                         $lines = file($this->configuration_file);
  85.                         foreach ($lines as &$line) {
  86.                                 $line = trim($line);
  87.                                 if (empty($line)) continue;
  88.                                 if ($line[0] == '#') continue;
  89.                                 $ary = explode('=', $line, 2);
  90.                                 if (count($ary) < 2) continue;
  91.                                 $this->configuration[$ary[0]] = $ary[1];
  92.                         }
  93.                 }
  94.  
  95.                 if ($this->getConfigValue('system_unique_id') === false) {
  96.                         $this->setConfigValue('system_unique_id', gen_uuid());
  97.                 }
  98.         }
  99.  
  100.         public function getSystemID() {
  101.                 return $this->getConfigValue('system_unique_id');
  102.         }
  103.  
  104.         public function getSystemIDInteger() {
  105.                 $val = uuid_numeric_value($this->getSystemID());
  106.                 if (!$val) {
  107.                         $metadata = array();
  108.                         throw new VolcanoException("system_unique_id is not a valid UUID. Please check db/local.conf", $metadata);
  109.                 }
  110.                 return $val;
  111.         }
  112.  
  113.         protected function saveConfiguration() {
  114.                 if (is_null($this->configuration_file)) return;
  115.                 file_put_contents($this->configuration_file, '');
  116.                 foreach ($this->configuration as $name => $val) {
  117.                         file_put_contents($this->configuration_file, $name.'='.$val."\n", FILE_APPEND);
  118.                 }
  119.         }
  120.  
  121.         public function setRedactedMessage($msg) {
  122.                 $this->redactedMessage = $msg;
  123.         }
  124.  
  125.         public function getRedactedMessage() {
  126.                 return $this->redactedMessage;
  127.         }
  128.  
  129.         public function disableRedactedMessage() {
  130.                 $this->redactedMessage = null;
  131.         }
  132.  
  133.         public function isRedactedMessageEnabled() {
  134.                 return !is_null($this->redactedMessage);
  135.         }
  136.  
  137.         public function addAuthToken($token) {
  138.                 if (!in_array($token, $this->authTokens)) {
  139.                         $this->authTokens[] = $token;
  140.                         $this->clearCaches();
  141.                 }
  142.         }
  143.  
  144.         public function clearAuthTokens() {
  145.                 $this->authTokens = array();
  146.                 $this->clearCaches();
  147.         }
  148.  
  149.         public function addDir($dir, $recursive=true) {
  150.                 if (!is_dir($dir)) {
  151.                         throw new VolcanoException("Directory not found: $dir");
  152.                 }
  153.                 $ary = glob($dir . '/*');
  154.                 if (count($ary) == 0) return;
  155.                 if ($ary === false) return;
  156.                 sort($ary);
  157.                 foreach ($ary as &$a) {
  158.                         if ($a[0] == '.') continue; // e.g. '.', '..', or '.htaccess'
  159.                         if (self::endsWith($a, '~')) continue; // recycled files
  160.                         if (($recursive) && (is_dir($a))) {
  161.                                 $this->addDir($a, $recursive);
  162.                                 continue;
  163.                         }
  164.                         if (!is_file($a)) continue;
  165.                         if (!self::endsWith($a, '.db')) continue;
  166.                         $this->addFile($a);
  167.                 }
  168.                 unset($a);
  169.  
  170. #echo '<!--';
  171. #print_r($this->oid_data);
  172. #print_r($this->macro_data);
  173. #echo '-->';
  174.  
  175.         }
  176.  
  177.         public function addFile($file) {
  178.                 $h = fopen($file, 'r');
  179.  
  180.                 $lineno = 1;
  181.                 $header = fgets($h);
  182.                 if (trim($header) != '['.$this->getFileformatIdentifier().']') {
  183.                         $metadata = array();
  184.                         $metadata['source'] = "$file:0";
  185.                         throw new VolcanoException("Header of file is invalid", $metadata);
  186.                 }
  187.  
  188.                 while (!feof($h)) { // TODO OK?
  189.                         $line = fgets($h);
  190.                         $lineno++;
  191.                         $this->addLine($line, $file.':'.$lineno);
  192.                 }
  193.                 fclose($h);
  194.         }
  195.  
  196. # todo: wie das problem bzgl allowed field-override lösen? (superior RA verbietet "identifier" change)
  197.  
  198.         protected function isSpecialInvisibleField($data) {
  199.                 assert(isset($data['attrib_name']));
  200.  
  201.                 $field_name = $data['attrib_name'];
  202.  
  203.                 if (empty($field_name)) return false;
  204.  
  205.                 if ($field_name == '*read-auth')  return true;
  206.         #       if ($field_name == '*write-auth') return true;
  207.                 if ($field_name == '*invisible')  return true;
  208.  
  209.                 if ($field_name[0] == '*') {
  210.                         throw new VolcanoException("Invalid system command '$field_name'", $data);
  211.                 }
  212.  
  213.                 return false;
  214.         }
  215.  
  216.         public function isAuthentificated($oid) {
  217.                 $readAuths = $this->getDatasets($oid, '*read-auth', false); # todo: [co] erzwingen?
  218.                 foreach ($readAuths as &$ra) {
  219.                         $nid = isset($ra['attrib_params'][0]) ? $ra['attrib_params'][0] : '';
  220.                         $found_valid_authprovider = false;
  221.                         foreach (self::$authProvs as &$ap) {
  222.                                 if ($ap->checkId($nid)) {
  223.                                         $found_valid_authprovider = true;
  224.                                         if ($ap->checkAuth($ra['value'], null)) return true; // first, check with empty/null authToken, e.g. for IP-Authentification
  225.                                         foreach ($this->authTokens as &$token) {
  226.                                                 if ($ap->checkAuth($ra['value'], $token)) return true;
  227.                                         }
  228.                                         unset($token);
  229.                                 }
  230.                         }
  231.                         if (!$found_valid_authprovider) {
  232.                                 # return false;
  233.                                 throw new VolcanoException("No authentification provider found", $ra);
  234.                         }
  235.                         unset($ap);
  236.                 }
  237.                 unset($ra);
  238.                 return false;
  239.         }
  240.  
  241.         protected static function showSource($source) {
  242.                 if (strpos($source, ':') === false) return $source;
  243.                 preg_match('@^(.+):(\\d+)$@', $source, $m);
  244.                 $file = $m[1];
  245.                 $line = $m[2];
  246.                 return "$file at line $line";
  247.         }
  248.  
  249.         protected function clearCaches() { # TODO: aufsplitten in caches, die die auth betreffen und die, die es nicht tun?
  250.                 $this->all_cache = null;
  251.                 $this->cache_recdatasets = array();
  252.                 $this->cache_listOIDs = array();
  253.         }
  254.  
  255.         protected function getIndexGenerationRoot() {
  256.                 $val = $this->getConfigValue('local_index_generation_root');
  257.                 if (!$val) {
  258.                         $val = GENERATION_ROOT_DEFAULT;
  259.                         $this->setConfigValue('local_index_generation_root', $val);
  260.                 }
  261.  
  262.                 return $this->extendOID($val, true);
  263.         }
  264.  
  265.         public function extendOID($identifier, $no_gen_root_replacement=false) {
  266.                 $identifier = str_replace('<SYSID>', $this->getSystemIDInteger(), $identifier);
  267.                 if (!$no_gen_root_replacement) {
  268.                         // Avoids an endless recursion with getIndexGenerationRoot
  269.                         $identifier = str_replace('<GENROOT>', $this->getIndexGenerationRoot(), $identifier);
  270.                 }
  271.                 $identifier = sanitizeOID($identifier, true);
  272.                 return $identifier;
  273.         }
  274.  
  275.         public function addLine($line, $source='(Direct Input):0') {
  276.                 $line = trim($line);
  277.                 $line = str_replace("\t", ' ', $line);
  278.                 $line = str_replace(array("\n", "\r"), '', $line);
  279.  
  280.                 $line_expl = explode(' ', $line, 2);
  281.  
  282.                 if (empty($line) || ($line[0] == '#')) {
  283.                         return;
  284.                 } else if (strpos($line_expl[0], ':') !== false) {
  285.                         $this->clearCaches();
  286.  
  287.                         $expl2 = explode(':', $line_expl[0], 2);
  288.                         $namespace  = trim(strtolower($expl2[0]));
  289.                         $identifier = trim($expl2[1]);
  290.  
  291.                         if (empty($namespace)) {
  292.                                 $metadata = array();
  293.                                 $metadata['source'] = $source;
  294.                                 throw new VolcanoException("The namespace may not be empty", $metadata);
  295.                         }
  296.  
  297.                         if (empty($identifier)) {
  298.                                 $metadata = array();
  299.                                 $metadata['source'] = $source;
  300.                                 throw new VolcanoException("The identifier may not be empty", $metadata);
  301.                         }
  302.  
  303.                         $is_oid      = $namespace == 'oid';
  304.                         $is_macro    = $namespace == '*macro';
  305.                         $is_external = $namespace == '*external';
  306.                         $is_other    = (!$is_oid) && (!$is_macro) && (!$is_external);
  307.  
  308.                         # TODO: isSpecialNamespace($namespace, $source) ?
  309.                         if (($namespace[0] == '*') && (!$is_macro) && (!$is_external)) {
  310.                                 $metadata = array();
  311.                                 $metadata['source'] = $source;
  312.                                 throw new VolcanoException("Invalid system namespace '$namepsace'", $metadata);
  313.                         }
  314.  
  315.                         if ($is_external) {
  316.                                 # TODO: wenn lokal und mit .php endend, dann eval()
  317.                                 $url = $identifier;
  318.                                 $this->addFile($url);
  319.                                 return;
  320.                         }
  321.  
  322.                         if ($is_macro) {
  323.                                 $identifier = strtoupper($identifier);
  324.                         }
  325.  
  326.                         if ($is_other) {
  327.                                 # TODO!!! diese handlers als plugins auslagern!
  328.                                 # QUE: sollte sowas nicht OID+ und nicht Volcano sein?
  329.  
  330.                                 $flags = '';
  331.                                 if (strpos($identifier, '[co]') !== false) {
  332.                                         $identifier = str_replace('[co]', '', $identifier);
  333.                                         $flags .= '[co]';
  334.                                 }
  335.                                 $original_identifier = $identifier;
  336.                                 # TODO: check for unknown attrib flags
  337.  
  338. /*
  339.                                 if (($namespace == 'guid') || ($namespace == 'uuid')) {
  340.                                         $guid = $identifier;
  341.                                 } else {
  342.                                         $guid = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusMisc, $namespace.':'.$identifier);
  343.                                 }
  344.                                 $identifier = '.'.uuid_to_oid($guid); # TODO: parameter, ob mit leading dot oder nicht
  345. */
  346.  
  347.                                 $is_guid_ns = ($namespace == 'guid') || ($namespace == 'uuid');
  348.  
  349.                                 if ($is_guid_ns && (!$this->getConfigValue('uuid_indexes_in_genroot'))) {
  350.                                         # uuid_indexes_in_genroot wird verwendet, sonst würde bei einer sammlung von MS COM clsids würde sonst die root zone platzen
  351.                                         $guid = $identifier;
  352.                                         $identifier = '.'.uuid_to_oid($guid); # TODO: parameter, ob mit leading dot oder nicht
  353.                                 } else {
  354.                                         # Es wird ein weltweit einzigartiger Root angelegt, anstelle 2.25 für jeden einzelnen index zu verwenden.
  355.                                         # Grund: Indexes sollen (alleine schon aus Performancegründen bei der Root-Anzeige im Webinterface)
  356.                                         # in einer Wurzel zusammengefasst werden. Diese Wurzel sollte aber unter der legalen Kontrolle des
  357.                                         # Eigentümers stehen. "2.25" kann nicht als Wurzel definiert werden, da der Benutzer kein Recht hat,
  358.                                         # "2.25" sein Eigen zu nennen.
  359.                                         # PROBLEM: aber dann kann man von den einzelnen items nicht das "uuid" feld ablesen, um an die
  360.                                         # per UUID_NAMEBASED_NS_OidPlusMisc erstellte UUID ranzukommen, da das "uuid" feld nur kindsknoten von 2.25
  361.                                         # konvertiert.
  362.                                         $gen_root = $this->getIndexGenerationRoot();
  363.  
  364.                                         if ($namespace == 'doi') {
  365.                                                 if (!self::beginswith($identifier, '10.')) {
  366.                                                         $metadata = array();
  367.                                                         $metadata['source'] = $source;
  368.                                                         throw new VolcanoException("Invalid DOI '$identifier'. Must start with '10.'", $metadata);
  369.                                                 }
  370.                                                 $x = substr($identifier, 3/* strlen('10.') */);
  371.                                                 $orgid = explode('/', $x, 2)[0];
  372.  
  373.                                                 # doi(10) <orgid> <doi:identifier>
  374.                                                 $identifier_ns = $gen_root.'.10.'.$orgid;
  375.  
  376.                                                 if (!$this->oidDescribed($identifier_ns, false)) {
  377.                                                         $this->addLine("oid:$identifier_ns description: Organisation $orgid", $source);
  378.                                                 }
  379.  
  380.                                                 $guid_item = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusMisc, 'doi:'.$identifier);
  381.                                         } else if ($is_guid_ns) {
  382.                                                 # uuid(25) <numeric_uuid>
  383.                                                 $identifier_ns = $gen_root.'.25';
  384.  
  385.                                                 # TODO: im web frontend trotzdem die originale index uuid zum klicken abieten (action=uuid_info, nicht nur action=show_index!)
  386.                                                 $guid_item = $identifier;
  387.                                         } else {
  388.                                                 $guid_ns = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusNSOnly, $namespace);
  389.  
  390.                                                 # ns(0) <ns> <ns:identifier>
  391.                                                 $identifier_ns = $gen_root.'.0.'.uuid_numeric_value($guid_ns);
  392.  
  393.                                                 if (!$this->oidDescribed($identifier_ns, false)) {
  394.                                                         $this->addLine("oid:$identifier_ns description: Automatically generated arc for namespace \"$namespace\"", $source);
  395.                                                         if (oid_id_is_valid($namespace)) {
  396.                                                                 $this->addLine("oid:$identifier_ns identifier:$namespace", $source);
  397.                                                         }
  398.                                                 }
  399.  
  400.                                                 $guid_item = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusMisc, $namespace.':'.$identifier);
  401.                                         }
  402.  
  403.                                         $identifier = $identifier_ns.'.'.uuid_numeric_value($guid_item);
  404.                                 }
  405.                                 if (!$this->oidDescribed($identifier, false)) { # TODO: WARUM? man kann den index doch auch so anlegen??? !!
  406.                                         $this->addLine('oid:'.$identifier." ${flags}index($namespace):$original_identifier", $source);
  407.                                 }
  408.                                 $is_other = false;
  409.                                 $is_oid   = true;
  410.                         }
  411.  
  412.                         # Muss als letztes stehen, da $is_other zu $is_oid konvertiert werden kann
  413.                         if ($is_oid) {
  414.                                 $bak_oid = $identifier;
  415.                                 $identifier = $this->extendOID($identifier);
  416.  
  417.                                 if ($identifier === false) {
  418.                                         $metadata = array();
  419.                                         $metadata['source'] = $source;
  420.                                         $metadata['oid']    = $bak_oid;
  421.                                         throw new VolcanoException("Illegal OID or dot notation with leading dot not recognized", $metadata);
  422.                                 }
  423.                         }
  424.  
  425.                         if (!isset($line_expl[1])) {
  426.                                 $metadata = array();
  427.                                 $metadata['source'] = $source;
  428.                                 throw new VolcanoException("Syntax error in DB: line contains no data", $metadata);
  429.                         }
  430.  
  431.                         $data = isset($line_expl[1]) ? $line_expl[1] : '';
  432.  
  433.                         $bry = explode(':', $data, 2);
  434.                         $attrib_name = (isset($bry[0])) ? $bry[0] : '';
  435.                         $value       = (isset($bry[1])) ? $bry[1] : '';
  436.  
  437.                         // <attrib_name>(<attrib_params>)
  438.                         if (preg_match("@^(.*)\((.+)\)(.*)$@isU", $attrib_name, $m)) {
  439.                                 $attrib_name   = $m[1].$m[3];
  440.                                 $attrib_params = explode(',', $m[2]);
  441.                         } else {
  442.                                 $attrib_params = array();
  443.                         }
  444.  
  445.                         // Process params
  446.                         // [co]<attrib_name> marks the line as confidential (read-auth necessary)
  447.                         $attrib_name = str_replace('[co]', '', $attrib_name, $cnt);
  448.                         $flag_confidential = $cnt > 0;
  449.  
  450.                         $attrib_name = str_replace('[xt]', '', $attrib_name, $cnt);
  451.                         $flag_extend = $cnt > 0;
  452.  
  453.                         $attrib_name = str_replace('[add]', '', $attrib_name, $cnt);
  454.                         $flag_add = $cnt > 0;
  455.  
  456.                         $attrib_name = str_replace('[del]', '', $attrib_name, $cnt);
  457.                         $flag_del = $cnt > 0;
  458.  
  459.                         $attrib_name = str_replace('[in]', '', $attrib_name, $cnt);
  460.                 ##      $flag_inherit = $cnt > 0;
  461.                         # Wir speichern hier keinen boolean, sondern den $attrib_name .
  462.                         # Dadurch wird sichergestellt, dass ein [xt]-Block im ganzen überschrieben wird, und nicht jede Zeile einzeln.
  463.                         # Jede Zeile enthält also im flag_inherit den Namen ihres [xt]-Blocks.
  464.                         $flag_inherit = ($cnt > 0) ? $attrib_name : false; // attrib_name muss ohne restliche flags sein, deswegen [in] zuletzt parsen
  465.  
  466.                         # No! We just leave inherited so we can use it later
  467.                         /*
  468.                         if (($flag_inherit) && ($is_macro)) {
  469.                                 $metadata = array();
  470.                                 $metadata['source'] = $source;
  471.                                 throw new VolcanoException("Macro attributes cannot be inherited", $metadata);
  472.                         }
  473.                         */
  474.  
  475.                         if (preg_match('@\[(.*)\]@isU', $attrib_name, $m)) {
  476.                                 $metadata = array();
  477.                                 $unkn_flag = $m[1];
  478.                                 $metadata['source'] = $source;
  479.                                 throw new VolcanoException("Flag [$unkn_flag] unknown", $metadata);
  480.                         }
  481.  
  482.                         $attrib_name = trim($attrib_name);
  483.  
  484.                         // Add data
  485.                         if ($flag_extend) {
  486.                                 $value = trim($value);
  487.  
  488.                                 // Allow multiple whitespaces as separator for macro params
  489.                                 $value = preg_replace('@(\s+)@', ' ', $value);
  490.  
  491.                                 # $macro_params = explode(' ', $value); // <macroname> <macroparam_1> <...>
  492.                                 $macro_params = str_getcsv($value, ' '); // requires PHP 5.3, see http://stackoverflow.com/questions/2202435/php-explode-the-string-but-treat-words-in-quotes-as-a-single-word for alternatives
  493.                                 $macroname = $macro_params[0];
  494.                                 if (!isset($this->macro_data[strtoupper($macroname)])) {
  495.                                         $metadata = array();
  496.                                         $metadata['source'] = $source;
  497.  
  498.                                         # TODO: zuerst alle anderen *.db files durchprobieren ob es auftaucht?
  499.                                         throw new VolcanoException("Macro '$macroname' not found", $metadata);
  500.                                 }
  501.  
  502.                                 $x = $this->macro_data[strtoupper($macroname)];
  503.  
  504.                                 foreach ($x as $y) { // no &$y
  505.                                 #       $y['source'] bleibt erhalten
  506.  
  507.                                         // Replace macro params in value, attrib_name and attrib_params
  508.                                         foreach ($macro_params as $mp_n => &$mp_x) {
  509.                                                 $y['value']         = str_replace('__'.$mp_n.'__', $mp_x, $y['value']);
  510.                                                 $y['attrib_name']   = str_replace('__'.$mp_n.'__', $mp_x, $y['attrib_name']);
  511.                                                 foreach ($y['attrib_params'] as &$ap_val) {
  512.                                                         $ap_val = str_replace('__'.$mp_n.'__', $mp_x, $ap_val);
  513.                                                 }
  514.                                         }
  515.                                         // Remove unused macro param place holders
  516.                                         // NO! Otherwise we cannot use parametrized macros which are using parametrized macros itself
  517.                                         /*
  518.                                         $y['value']         = preg_replace('@__(\\d+)__@sU', '', $y['value']);
  519.                                         $y['attrib_name']   = preg_replace('@__(\\d+)__@sU', '', $y['attrib_name']);
  520.                                         foreach ($y['attrib_params'] as &$ap_val) {
  521.                                                 $ap_val = preg_replace('@__(\\d+)__@sU', '', $ap_val);
  522.                                         }
  523.                                         */
  524.  
  525.                                         // $y['attrib_params']     = $attrib_params;
  526.                                         // Nein, attrib_params lieber direkt nach ?? einfügen:
  527.                                         if (count($attrib_params) > 0) {
  528.                                                 $attr_ext = '('.implode(',', $attrib_params).')';
  529.                                         } else {
  530.                                                 $attr_ext = '';
  531.                                         }
  532.                                         # NG: '??' auch ersetzen in attrib_param, value ?
  533.                                         $y['attrib_name']       = str_replace('??', $attrib_name.$attr_ext, $y['attrib_name']);
  534.                                         // QUE: wie verhalten sich die params im falle einer vererbung? kann man params missbrauchen um z.B. ra(1), ra(2) etc zu kennzeichnen?
  535.  
  536.                                         $y['flag_confidential'] = $flag_confidential || $y['flag_confidential'];
  537.                                         # todo: read-auth im macro-bereich?
  538.                                 #       $y['flag_extend']       = true; // TODO?
  539.                                         $y['flag_inherit']      = trim($flag_inherit); # dieser flag_inherit geht von der ursprungs-oid über alle stufen der macros und submacros hinweg
  540.                                         $y['flag_add']          = $flag_add;
  541.  
  542.                                         if ($is_macro) {
  543.                                                 $y['macro'] = $identifier;
  544.                                         } else {
  545.                                                 $y['oid']   = $identifier;
  546.                                         }
  547.  
  548.                                         // Check if the identifier is valid to the ASN.1 standards
  549.                                         $flag_extend = strpos($y['attrib_name'], '[xt]') !== false;
  550.                                         if ((!$flag_extend) && (!$is_macro) && (strtolower($y['attrib_name']) == 'identifier') && (!oid_id_is_valid($y['value']))) {
  551.                                                 throw new VolcanoException("Identifier '".$y['value']."' is not a valid ASN.1 identifier", $y);
  552.                                         }
  553.  
  554.                                         if ((!$flag_extend) && (!$is_macro) && (strtolower($y['attrib_name']) == 'iri') && (!iri_valid($y['value']))) {
  555.                                                 throw new VolcanoException("Identifier '".$y['value']."' is not a valid IRI identifier", $y);
  556.                                         }
  557.  
  558.                                         if ($is_macro) {
  559.                                                 $this->macro_data[$identifier][] = $y;
  560.                                         } else {
  561.                                                 $this->oid_data[$identifier][] = $y;
  562.                                         }
  563.                                 }
  564.                         } else {
  565.                                 // $value = trim($value); // TODO: oder doch lieber? z.b. descriptions einrücken?
  566.  
  567.                                 # NG: '??' auch ersetzen in attrib_param, value ?
  568.                                 if ((!$is_macro) && (strpos($attrib_name, '??') !== false)) {
  569.                                         $metadata = array();
  570.                                         $metadata['source'] = $source;
  571.                                         throw new VolcanoException("'??' is only allowed inside a macro", $metadata);
  572.                                 }
  573.  
  574.                                 $y = array(
  575.                                         'source'            => $source,
  576.                                         'attrib_name'       => $attrib_name,
  577.                                         'attrib_params'     => $attrib_params,
  578.                                         'value'             => $value,
  579.                                         'flag_confidential' => $flag_confidential,
  580.                                 #       'flag_extend'       => $flag_extend,
  581.                                         'flag_inherit'      => trim($flag_inherit),
  582.                                         'flag_add'          => $flag_add,
  583.                                         'flag_del'          => $flag_del
  584.                                 );
  585.  
  586.                                 if ($is_macro) {
  587.                                         $y['macro'] = $identifier;
  588.                                 } else {
  589.                                         $y['oid']   = $identifier;
  590.                                 }
  591.  
  592.                                 // Check if the identifier is valid to the ASN.1 standards
  593.                                 if ((!$flag_extend) && (!$is_macro) && (strtolower($y['attrib_name']) == 'identifier') && (!oid_id_is_valid($y['value']))) {
  594.                                         throw new VolcanoException("Identifier '".$y['value']."' is not a valid ASN.1 identifier", $y);
  595.                                 }
  596.  
  597.                                 if ($is_macro) {
  598.                                         $this->macro_data[$identifier][] = $y;
  599.                                 } else {
  600.                                         $this->oid_data[$identifier][] = $y;
  601.                                 }
  602.                         }
  603.                 } else {
  604.                         $metadata = array();
  605.                         $metadata['source'] = $source;
  606.                         throw new VolcanoException("Syntax error in database (not beginning with 'oid:' or '*macro:' or a valid identifier like 'guid:<uuid>')", $metadata);
  607.                 }
  608.         }
  609.  
  610.         private $all_cache = array();
  611.         # TODO: auch $parent_oid parameter
  612.         public function getAllOIDs($check_auth=true) {
  613.                 $vvv = $check_auth;
  614.                 if (isset($this->all_cache[$vvv])) return $this->all_cache[$vvv];
  615.  
  616.                 // TODO: $with_macros per default false. ausblenden. xxx
  617.  
  618.                 $ary = array();
  619.                 foreach ($this->oid_data as $b => &$data) {
  620.                         if ($check_auth) {
  621.                                 if ($this->oidDescribed($b, $check_auth)) $ary[] = $b;
  622.                         } else {
  623.                                 $ary[] = $b;
  624.                         }
  625.                 }
  626.  
  627.                 $this->all_cache[$vvv] = $ary;
  628.                 return $ary;
  629.         }
  630.  
  631.         // TODO: -> functions.inc.php
  632.         protected static function getShortestString(&$ary) {
  633.                 $minlen = PHP_INT_MAX;
  634.                 $out = false;
  635.                 foreach ($ary as &$a) {
  636.                         if ($a === null) continue;
  637.                         $len = strlen($a);
  638.                         if ($len < $minlen) {
  639.                                 $out = $a;
  640.                                 $minlen = $len;
  641.                         }
  642.                 }
  643.                 unset($a);
  644.                 return $out;
  645.         }
  646.  
  647.         // TODO: -> oid_utils.inc.php ?
  648.         protected static function strikeRoot(&$ary, $oid) {
  649.                 $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
  650.                 if ($oid === false) return false;
  651.  
  652.                 $dotstop = self::appendDot($oid);
  653.                 foreach ($ary as &$a) {
  654.                         if (($a == $oid) || (self::beginsWith($a, $dotstop))) {
  655.                                 $a = null;
  656.                         }
  657.                 }
  658.                 unset($a);
  659.         }
  660.  
  661.         public function findRoots($check_auth=true) {
  662.                 $out = array();
  663.  
  664.                 $ary = $this->getAllOIDs($check_auth);
  665.  
  666.                 while (($oid = $this->getShortestString($ary)) !== false) {
  667.                         $out[] = $oid;
  668.                         $this->strikeRoot($ary, $oid);
  669.                 }
  670.  
  671.                 oidSort($out);
  672.  
  673.                 return $out;
  674.         }
  675.  
  676.         public static function findSearchProvider($nid) {
  677.                 $found_prov = null;
  678.                 foreach (self::$searchProvs as &$searchprov) {
  679.                         if ($searchprov->checkId($nid)) {
  680.                                 $found_prov = $searchprov;
  681.                                 break;
  682.                         }
  683.                 }
  684.                 unset($searchprov);
  685.  
  686.                 return $found_prov;
  687.         }
  688.  
  689.         public function findOID(&$indexname, $check_auth=true) {
  690.                 $lowest_oid  = null;
  691.                 $lowest_dist = PHP_INT_MAX;
  692.                 $lowest_nid  = null;
  693.                 $oids = $this->getAllOIDs($check_auth);
  694.  
  695.                 $ary = explode(':', $indexname, 2);
  696.                 if (count($ary) == 2) {
  697.                         $suggested_nid = $ary[0]; // This does not neccessarily be a searchprovider NID, since it could also be a part of an IPv6 address
  698.                         $found_prov = self::findSearchProvider($suggested_nid);
  699.                         if (!is_null($found_prov)) {
  700.                                 $indexname = $ary[1];
  701.                         } else {
  702.                                 // No search provider was found. We don't strip the text before ':', because it
  703.                                 // could be actually a part of the index name, e.g. for an IPv6
  704.                                 # $indexname = $ary[1];
  705.                         }
  706.                 } else {
  707.                         $suggested_nid = '';
  708.                         $found_prov = null;
  709.                 }
  710.  
  711.                 $has_preferred_prov = !is_null($found_prov);
  712.  
  713.                 foreach ($oids as &$oid) {
  714.                         $search_data = $this->getDatasets($oid, 'index'); # todo: wird [co] beachtet?
  715.                         foreach ($search_data as &$data) {
  716.                                 $nid   = $data['attrib_params'][0];
  717.                                 $value = $data['value'];
  718.  
  719.  
  720.                                 if (!$has_preferred_prov) $found_prov = self::findSearchProvider($nid);
  721.  
  722.                                 if (!is_null($found_prov)) {
  723.                                         $cur_distance = $found_prov->calcDistance($value, $indexname);
  724.                                         if ($cur_distance === false) continue;
  725.                                         if ($cur_distance < 0) continue; // item is too specific for the request
  726.                                         // else if ($cur_distance == 0) return array($oid, 0, $nid);
  727.                                         else if ($cur_distance < $lowest_dist) {
  728.                                                 $lowest_dist = $cur_distance;
  729.                                                 $lowest_oid  = $oid;
  730.                                                 $lowest_nid  = $nid;
  731.                                                 if ($cur_distance == 0) break 2;
  732.                                         }
  733.                                 } else {
  734.                                         // We don't have a Searchprovider, but we can look for an index which matches exactly
  735.                                         if ($suggested_nid == $nid) {
  736.                                                 $indexname = isset($ary[1]) ? $ary[1] : '';
  737.                                         }
  738.                                         if ($value == $indexname) {
  739.                                                 $lowest_dist = 0;
  740.                                                 $lowest_oid = $oid;
  741.                                                 $lowest_nid = $nid;
  742.                                                 break 2;
  743.                                         }
  744.                                 }
  745.                         }
  746.                         unset($data);
  747.                 }
  748.                 unset($oid);
  749.  
  750.                 if (is_null($lowest_oid)) return null;
  751.                 return array($lowest_oid, $lowest_dist, $lowest_nid);
  752.         }
  753.  
  754.         private $cache_listOIDs = array();
  755.         public function listOIDs($parent_oid, $absolute=true, $depth=-1, $check_auth=true) {
  756.                 $parent_oid = sanitizeOID($parent_oid, substr($parent_oid,0,1) == '.');
  757.                 if ($parent_oid === false) return false;
  758.  
  759.                 $vvv = ($absolute ? 'T' : 'F').($check_auth ? 'T' : 'F').$depth.'/'.$parent_oid;
  760.                 if (isset($this->cache_listOIDs[$vvv])) return $this->cache_listOIDs[$vvv];
  761.  
  762.                 $dotstop = self::appendDot($parent_oid);
  763.  
  764.                 $oids = $this->getAllOIDs($check_auth);
  765.  
  766.                 $out = array();
  767.                 foreach ($oids as &$oid) {
  768.                         if (self::beginsWith($oid, $dotstop)) {
  769.                                 if ($depth >= 0) {
  770.                                         if (substr_count($oid, '.')-substr_count($dotstop, '.') > $depth-1) continue;
  771.                                 }
  772.                                 if ($absolute) {
  773.                                         $out[] = $oid;
  774.                                 } else {
  775.                                         $out[] = substr($oid, strlen($dotstop));
  776.                                 }
  777.                         }
  778.                 }
  779.                 unset($oid);
  780.  
  781.                 $this->cache_listOIDs[$vvv] = $out;
  782.                 return $out;
  783.         }
  784.  
  785.         public function listChildren($parent_oid, $levels=-1, $check_auth=true) {
  786.                 if ($levels == 0) return false;
  787.  
  788.                 $parent_oid = sanitizeOID($parent_oid, substr($parent_oid,0,1) == '.');
  789.                 if ($parent_oid === false) return false;
  790.  
  791.                 $deepChildrenSearch = $this->listOIDs($parent_oid, false, -1, $check_auth);
  792.                 if (count($deepChildrenSearch) == 0) return array();
  793.  
  794.                 $firstarcs = array();
  795.                 foreach ($deepChildrenSearch as &$child) {
  796.                         $ary = explode('.', $child, 2);
  797.                         $firstarc = $ary[0];
  798.                         if ($firstarc == '') continue; # Achtung: Darf nicht empty() sein, da empty(0)===true
  799.  
  800. #                       if (!in_array($firstarc, $firstarcs)) $firstarcs[] = $firstarc; # slow
  801.                         if (!isset($firstarcs[$firstarc])) $firstarcs[$firstarc] = true;
  802.                 }
  803.                 unset($child);
  804. #               sort($firstarcs);
  805.                 ksort($firstarcs);
  806.  
  807.                 $dotstop = self::appendDot($parent_oid);
  808.  
  809.                 $out = array();
  810. #               foreach ($firstarcs as &$firstarc) {
  811.                 foreach ($firstarcs as $firstarc => &$dummy) {
  812.  
  813.  
  814. /*      This doesn't work with orphan OIDs
  815.                 $firstarcs = $this->listOIDs($parent_oid, false, 1, $check_auth);
  816.                 if (count($firstarcs) == 0) return array();
  817.  
  818.                 sort($firstarcs);
  819.  
  820.                 $dotstop = self::appendDot($parent_oid);
  821.  
  822.                 $out = array();
  823.                 foreach ($firstarcs as &$firstarc) {
  824. */
  825.                         $cur_oid = $dotstop.$firstarc;
  826.  
  827.                         $out[$firstarc] = array(
  828.                                 'children'      => $this->listChildren($cur_oid, $levels-1, $check_auth),
  829.                                 'described'     => $this->oidDescribed($cur_oid, $check_auth),
  830.                                 'identifiers'   => $this->getIdentifiers($cur_oid, $check_auth),
  831.                                 'unicodelabels' => $this->getUnicodeLabels($cur_oid, $check_auth),
  832.                                 'attributes'    => $this->getOIDAttribs($cur_oid, $check_auth)
  833.                         );
  834.                 }
  835.  
  836.                 return $out;
  837.         }
  838.  
  839.         /* protected */ public function getIdentifiers($oid, $check_auth=true) {
  840.                 // TODO: value sanity check
  841.                 return $this->getValuesOf($oid, 'identifier', $check_auth);
  842.         }
  843.  
  844.         /* protected */ public function getUnicodeLabels($oid, $check_auth=true) {
  845.                 // TODO: value sanity check
  846.                 return $this->getValuesOf($oid, 'unicodelabel', $check_auth);
  847.         }
  848.  
  849.         /* protected */ public function getOIDAttribs($oid, $check_auth=true) {
  850.                 return $this->getValuesOf($oid, 'attribute', $check_auth, 1);
  851.         }
  852.  
  853.         // case 0 = preserve            TODO: als konstanten auslagern
  854.         // case 1 = upper case
  855.         // case 2 = lower case
  856.         /* protected */ public function getValuesOf($oid, $attrib_name, $unique=true, $case=0) {
  857.                 $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
  858.                 if ($oid === false) return false;
  859.  
  860.                 $identifiers = array();
  861.                 $identifiers_raw = $this->getDatasets($oid, $attrib_name);
  862.                 foreach ($identifiers_raw as &$idd) {
  863.                         $val = $idd['value'];
  864.                         if ($case == 1) $val = strtoupper($val);
  865.                         elseif ($case == 2) $val = strtolower($val);
  866.                         $identifiers[] = $val;
  867.                 }
  868.                 unset($idd);
  869.  
  870.                 if ($unique) $identifiers = array_unique($identifiers);
  871.  
  872.                 return $identifiers;
  873.         }
  874.  
  875.         /* protected */ public function getValues($oid, $attribute_name) {
  876.                 $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
  877.                 if ($oid === false) return false;
  878.  
  879.                 $values = array();
  880.                 $values_raw = $this->getDatasets($oid, $attribute_name);
  881.                 foreach ($values_raw as &$idd) {
  882.                         $values[] = $idd['value'];
  883.                 }
  884.                 unset($idd);
  885.                 return $values;
  886.         }
  887.  
  888.         public function oidDescribed($oid, $check_auth=true) {
  889.                 $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
  890.                 if ($oid === false) return false;
  891.  
  892.                 if ($check_auth) {
  893.                         $m = $this->getDatasets($oid, '', $check_auth);
  894.                         $c = 0;
  895.                         foreach ($m as &$data) {
  896.                                 if ($this->isSpecialInvisibleField($data)) continue;
  897.                                 $c++;
  898.                         }
  899.                         if ($c == 0) return false; // secret OID or not available OID
  900.                 }
  901.  
  902.                 return isset($this->oid_data[$oid]); # TODO: was ist wenn OID secret?
  903.         }
  904.  
  905.         public function getDatasets($oid, $attrib_name='', $check_auth=true) { // TODO filter nach params, value?
  906.                 $out = array();
  907.                 $ary = $this->rec_getDatasets($oid, $attrib_name, $check_auth);
  908.                 foreach ($ary as &$d) {
  909.                         if ((!empty($attrib_name)) && ($d['attrib_name'] != $attrib_name)) continue;
  910.                         $out[] = $d;
  911.                 }
  912.                 return $out;
  913.         }
  914.  
  915.         public function stripAttribs($oid, $attrib_name, $limit=-1) {
  916.                 $count = 0;
  917.                 foreach ($this->oid_data[$oid] as $n => $data) { // no &$data
  918.                         if (empty($attrib_name) || ($data['attrib_name'] == $attrib_name)) {
  919.                                 $count++;
  920.                                 if (($limit >= 0) && ($count > $limit)) break;
  921.                                 unset($this->oid_data[$oid][$n]);
  922.                         }
  923.                 }
  924.         }
  925.  
  926.         private $cache_recdatasets = array();
  927.  
  928.         # FUT QUE: return by reference?
  929.         protected function rec_getDatasets($oid, $attrib_name='', $check_auth=true) { // TODO filter nach params, value?
  930.                 $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
  931.                 if ($oid === false) {
  932.                         throw new VolcanoException("'$oid' is not a valid OID."); # TODO: exception source ("stacktrace") metadata -- idee: global $source variable?
  933.                 }
  934.  
  935.                 $vvv = $oid.'/'.$attrib_name.'/'.($check_auth ? 'T' : 'F');
  936.                 if (isset($this->cache_recdatasets[$vvv])) return $this->cache_recdatasets[$vvv];
  937.  
  938.                 if (!$this->oidDescribed($oid, false)) return array();
  939.  
  940.                 $out = array();
  941.                 foreach ($this->oid_data[$oid] as $data) { // no &$data
  942.                         if ($check_auth) {
  943.                                 # Anstelle *invisible lieber ein "attribute:" ? ist leider schwieriger zu bedienen , z.b. wenn man es wieder weghaben möchte
  944.                                 if (($data['attrib_name'] == '*invisible') && ($data['value'] == '1')) {
  945.                                         if ($this->isAuthentificated($oid)) { # TODO: parameter, für was man sich authentifizieren möchte (read,write, etc).
  946.                                                 $dd = $data; // TODO: OK?
  947.                                                 $dd['attrib_name']   = 'attribute';
  948.                                                 $dd['value']         = 'INVISIBLE';
  949.                                                 $dd['flag_inherit']  = $data['flag_inherit'];
  950.                                                 $dd['flag_add']      = $data['flag_add']; # TODO: ok?
  951.                                                 $dd['attrib_params'] = array();
  952.                                                 $dd['source']        = $data['source'];
  953.                                                 # TODO: weitere dinge in $dd notwendig?
  954.                                                 $out[] = $dd;
  955.                                         } else {
  956.                                                 $this->cache_recdatasets[$vvv] = array($data);
  957.                                                 return array($data);
  958.                                         }
  959.                                 }
  960.  
  961.                                 if (($data['flag_confidential']) && (!$this->isAuthentificated($oid))) {
  962.                                         if (!$this->isRedactedMessageEnabled()) {
  963.                                                 continue; // hide complete line
  964.                                         } else {
  965.                                                 $data['value'] = $this->redactedMessage;
  966.                                         }
  967.                                 }
  968.                         }
  969.  
  970.                         if ((!empty($attrib_name)) && ($data['attrib_name'] != $attrib_name)) continue;
  971.  
  972.                         // Add it to output
  973.                         if (!$data['flag_del']) $out[] = $data;
  974.                 }
  975.                 unset($data);
  976.  
  977.                 // Get the inherited fields
  978.                 if ($oid != '.') {
  979.                         $ori_oid = $oid;
  980.                         while ($oid != '.') {
  981.                                 $oid = oid_up($oid);
  982.                                 if ($this->oidDescribed($oid, false)) break;
  983.                         }
  984.                         $ds = $this->rec_getDatasets($oid, $attrib_name, $check_auth);
  985.                         foreach ($ds as &$d) {
  986.                                 if ($d['flag_inherit']) {
  987.                                         // Only inherit if it isn't overwritten
  988.                                         $is_overwritten = false;
  989.  
  990.                                         foreach ($this->oid_data[$ori_oid] as &$x) { // $d = diese oid ; $x = vater oid
  991. #/*
  992.                                                 // compare single fields to each other (may also include fields that were extended from a macro)
  993.                                                 if ($x['attrib_name'] == $d['attrib_name']) { # todo: case sensitive?
  994.                                                         $is_overwritten = true;
  995.                                                         break;
  996.                                                 }
  997. #*/
  998.  
  999.                                                 if ($x['attrib_name'] == $d['flag_inherit']) { # todo: case sensitive?
  1000.                                                         $is_overwritten = true;
  1001.                                                         break;
  1002.                                                 }
  1003.  
  1004.                                                 // ein ganzer [xt] block wird inherited, z.B. "ra". nicht die einzelnen felder einzeln behandeln.
  1005.                                                 if ($x['flag_inherit'] == $d['flag_inherit']) { # todo: case sensitive?
  1006.                                                         $is_overwritten = true;
  1007.                                                         break;
  1008.                                                 }
  1009.                                         }
  1010.  
  1011.                                         if ($x['flag_add']) {
  1012.                                                 $is_overwritten = false;
  1013.                                         }
  1014.  
  1015.                                         if ($is_overwritten) continue;
  1016.  
  1017.                                         if ($check_auth) {
  1018.                                                 if (($d['attrib_name'] == '*invisible') && ($d['value'] == '1')) {
  1019.                                                         if ($this->isAuthentificated($oid)) {
  1020.                                                                 $dd['attrib_name']   = 'attribute';
  1021.                                                                 $dd['value']         = 'INVISIBLE';
  1022.                                                                 $dd['flag_inherit']  = $d['flag_inherit'];
  1023.                                                                 $dd['flag_add']      = $d['flag_add']; # TODO: ok?
  1024.                                                                 $dd['attrib_params'] = array();
  1025.                                                         #       $out[] = $dd;
  1026.                                                         } else {
  1027.                                                                 $this->cache_recdatasets[$vvv] = array($d);
  1028.                                                                 return array($d);
  1029.                                                         }
  1030.                                                 }
  1031.                                         }
  1032.  
  1033.                                         // Add to output
  1034.                                         if (!$d['flag_del']) $out[] = $d;
  1035.                                 }
  1036.                         }
  1037.                 }
  1038.  
  1039.                 $this->cache_recdatasets[$vvv] = $out;
  1040.                 return $out;
  1041.         }
  1042.  
  1043.         // TODO: iso.org auch aufloesen und standardized
  1044.         public function resolveIdentifiers($term) {
  1045.                 $ary = explode('.', $term);
  1046.                 $cd = $this->listChildren('.');
  1047.  
  1048.                 foreach ($ary as $n => &$a) {
  1049.                         if ($n == 0) continue;
  1050.                         if (!is_numeric($a[0])) {
  1051.                                 if (is_null($cd)) return false; // TODO FUT: loessen durch referenzierende whois server (forward)?
  1052.                                 $found = false;
  1053.                                 foreach ($cd as $arc => &$data) {
  1054.                                         foreach ($data['identifiers'] as &$idc) {
  1055.                                                 if ($idc == $a) {
  1056.                                                         $a = $arc;
  1057.                                                         $found = true;
  1058.                                                         break 2;
  1059.                                                 }
  1060.                                         }
  1061.                                 }
  1062.                                 unset($data);
  1063.                                 if (!$found) return false;
  1064.                         }
  1065.                         $cd = (!isset($cd[$a]['children'])) ? null : $cd[$a]['children'];
  1066.                 }
  1067.                 unset($a);
  1068.  
  1069.                 return implode('.', $ary);
  1070.         }
  1071.  
  1072.         // --------- STATIC
  1073.  
  1074.         protected static $searchProvs = array();
  1075.         protected static $authProvs = array();
  1076.  
  1077.         public static function getFileformatIdentifier() {
  1078.                 return '1.3.6.1.4.1.37476.2.5.1.1.2';
  1079.         }
  1080.  
  1081.         public static function registerSearchProvider($prov) {
  1082.                 self::$searchProvs[] = $prov;
  1083.         }
  1084.  
  1085.         public static function registerAuthProvider($prov) {
  1086.                 self::$authProvs[] = $prov;
  1087.         }
  1088.  
  1089.         protected static function appendDot($oid) {
  1090.                 return ($oid == '.') ? '.' : $oid . '.';
  1091.         }
  1092.  
  1093.         // TODO: functions.inc.php
  1094.         protected static function beginsWith($haystack, $needle) {
  1095.                 # http://maettig.com/code/php/php-performance-benchmarks.php
  1096.                 return strpos($haystack, $needle) === 0;
  1097.         }
  1098.  
  1099.         // TODO: functions.inc.php
  1100.         protected static function endsWith($haystack, $needle) {
  1101.                 # $length = strlen($needle);
  1102.                 # if ($length == 0) return true;
  1103.                 # return (substr($haystack, -$length) === $needle);
  1104.                 return substr($haystack, -strlen($needle)) === $needle;
  1105.         }
  1106. }
  1107.