Subversion Repositories oidplus

Rev

Rev 1084 | Rev 1116 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusPagePublicResources extends OIDplusPagePluginPublic {
  27.  
  28.         private function getMainTitle() {
  29.                 return _L('Documents and Resources');
  30.         }
  31.  
  32.         public function init($html=true) {
  33.                 OIDplus::config()->prepareConfigKey('resource_plugin_autoopen_level', 'Resource plugin: How many levels should be open in the treeview when OIDplus is loaded?', '1', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  34.                         if (!is_numeric($value) || ($value < 0)) {
  35.                                 throw new OIDplusException(_L('Please enter a valid value.'));
  36.                         }
  37.                 });
  38.                 OIDplus::config()->delete('resource_plugin_title');
  39.                 OIDplus::config()->delete('resource_plugin_path');
  40.                 OIDplus::config()->prepareConfigKey('resource_plugin_hide_empty_path','Resource plugin: Hide empty paths? (0=no, 1=yes)', '1', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  41.                         if (!is_numeric($value) || (($value != 0) && ($value != 1))) {
  42.                                 throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).'));
  43.                         }
  44.                 });
  45.         }
  46.  
  47.         private static function getDocumentContent($file) {
  48.                 $file = self::realname($file);
  49.                 $file2 = preg_replace('/\.([^.]+)$/', '$'.OIDplus::getCurrentLang().'.\1', $file);
  50.                 if (file_exists($file2)) $file = $file2;
  51.  
  52.                 $cont = file_get_contents($file);
  53.  
  54.                 list($html, $js, $css) = extractHtmlContents($cont);
  55.                 $cont = '';
  56.                 if (!empty($js))  $cont .= "<script>\n$js\n</script>";
  57.                 if (!empty($css)) $cont .= "<style>\n$css\n</style>";
  58.                 $cont .= stripHtmlComments($html);
  59.  
  60.                 return $cont;
  61.         }
  62.  
  63.         private static function getDocumentTitle($file) {
  64.                 $file = self::realname($file);
  65.                 $file2 = preg_replace('/\.([^.]+)$/', '$'.OIDplus::getCurrentLang().'.\1', $file);
  66.                 if (file_exists($file2)) $file = $file2;
  67.  
  68.                 $cont = file_get_contents($file);
  69.  
  70.                 // make sure the program works even if the user provided HTML is not UTF-8
  71.                 $cont = convert_to_utf8_no_bom($cont);
  72.  
  73.                 $m = array();
  74.                 if (preg_match('@<title>(.+)</title>@ismU', $cont, $m)) return $m[1];
  75.                 if (preg_match('@<h1>(.+)</h1>@ismU', $cont, $m)) return $m[1];
  76.                 if (preg_match('@<h2>(.+)</h2>@ismU', $cont, $m)) return $m[1];
  77.                 if (preg_match('@<h3>(.+)</h3>@ismU', $cont, $m)) return $m[1];
  78.                 if (preg_match('@<h4>(.+)</h4>@ismU', $cont, $m)) return $m[1];
  79.                 if (preg_match('@<h5>(.+)</h5>@ismU', $cont, $m)) return $m[1];
  80.                 if (preg_match('@<h6>(.+)</h6>@ismU', $cont, $m)) return $m[1];
  81.                 return pathinfo($file, PATHINFO_FILENAME); // filename without extension
  82.         }
  83.  
  84.         protected static function mayAccessResource($source) {
  85.                 if (OIDplus::authUtils()->isAdminLoggedIn()) return true;
  86.  
  87.                 $candidates = array(
  88.                         OIDplus::localpath().'userdata/resources/security.ini',
  89.                         OIDplus::localpath().'res/security.ini'
  90.                 );
  91.                 foreach ($candidates as $ini_file) {
  92.                         if (file_exists($ini_file)) {
  93.                                 $data = @parse_ini_file($ini_file, true);
  94.                                 if (isset($data['Security']) && isset($data['Security'][$source])) {
  95.                                         $level = $data['Security'][$source];
  96.                                         if ($level == 'PUBLIC') {
  97.                                                 return true;
  98.                                         } else if ($level == 'RA') {
  99.                                                 return
  100.                                                         ((OIDplus::authUtils()->raNumLoggedIn() > 0) ||
  101.                                                         (OIDplus::authUtils()->isAdminLoggedIn()));
  102.                                         } else if ($level == 'ADMIN') {
  103.                                                 return OIDplus::authUtils()->isAdminLoggedIn();
  104.                                         } else {
  105.                                                 throw new OIDplusException(_L('Unexpected security level in %1 (expect PUBLIC, RA or ADMIN)', $ini_file));
  106.                                         }
  107.                                 }
  108.                         }
  109.                 }
  110.                 return true;
  111.         }
  112.  
  113.         private static function myglob($reldir, $onlydir=false) {
  114.                 $out = array();
  115.  
  116.                 $root = OIDplus::localpath().'userdata/resources/';
  117.                 $res = $onlydir ? @glob($root.ltrim($reldir,'/'), GLOB_ONLYDIR) : @glob($root.ltrim($reldir,'/'));
  118.                 if ($res) foreach ($res as &$x) {
  119.                         $x = substr($x, strlen($root));
  120.                         if (strpos($x,'$') !== false) continue;
  121.                         $out[] = $x;
  122.                 }
  123.  
  124.                 $root = OIDplus::localpath().'res/';
  125.                 $res = $onlydir ? @glob($root.ltrim($reldir,'/'), GLOB_ONLYDIR) : @glob($root.ltrim($reldir,'/'));
  126.                 if ($res) foreach ($res as $x) {
  127.                         $x = substr($x, strlen($root));
  128.                         if (strpos($x,'$') !== false) continue;
  129.                         $out[] = $x;
  130.                 }
  131.  
  132.                 $out = array_unique($out);
  133.  
  134.                 return array_filter($out, function($v, $k) {
  135.                         return self::mayAccessResource($v);
  136.                 }, ARRAY_FILTER_USE_BOTH);
  137.         }
  138.  
  139.         private static function realname($rel) {
  140.                 $candidate1 = OIDplus::localpath().'userdata/resources/'.$rel;
  141.                 $candidate2 = OIDplus::localpath().'res/'.$rel;
  142.                 if (file_exists($candidate1) || is_dir($candidate1)) return $candidate1;
  143.                 if (file_exists($candidate2) || is_dir($candidate2)) return $candidate2;
  144.                 return null;
  145.         }
  146.  
  147.         protected static function checkRedirect($source, &$target): bool {
  148.                 $candidates = array(
  149.                         OIDplus::localpath().'userdata/resources/redirect.ini',
  150.                         OIDplus::localpath().'res/redirect.ini'
  151.                 );
  152.                 foreach ($candidates as $ini_file) {
  153.                         if (file_exists($ini_file)) {
  154.                                 $data = @parse_ini_file($ini_file, true);
  155.                                 if (isset($data['Redirects']) && isset($data['Redirects'][$source])) {
  156.                                         $target = $data['Redirects'][$source];
  157.                                         return true;
  158.                                 }
  159.                         }
  160.                 }
  161.                 return false;
  162.         }
  163.  
  164.         public function gui($id, &$out, &$handled) {
  165.                 if (explode('$',$id,2)[0] === 'oidplus:resources') {
  166.                         $handled = true;
  167.  
  168.                         $tmp = explode('$',$id);
  169.                         $file = isset($tmp[1]) ? $tmp[1] : '';
  170.                         unset($tmp);
  171.  
  172.                         // Security checks
  173.  
  174.                         if (
  175.                                 ($file != '') && (
  176.                                 (strpos($file, chr(0)) !== false) || // Directory traversal (LFI,RFI) helper
  177.                                 (strpos($file, '../') !== false) || ($file[0] == '/') || ($file[0] == '~') || // <-- Local File Injection (LFI)
  178.                                 ($file[0] == '.') || (strpos($file, '/.') !== false) ||                       // <-- Calling hidden files e.g. ".htpasswd"
  179.                                 (strpos($file, '://') !== false)                                              // <-- Remote File Injection (RFI)
  180.                            )) {
  181.                                 if (strpos($file, chr(0)) !== false) {
  182.                                         $file = str_replace(chr(0), '[NUL]', $file);
  183.                                 }
  184.                                 // This will not be logged anymore, because people could spam the log files otherwise
  185.                                 //OIDplus::logger()->log("[WARN]A!", "LFI/RFI attack blocked (requested file '$file')");
  186.                                 $out['title'] = _L('Access denied');
  187.                                 $out['icon'] = 'img/error.png';
  188.                                 $out['text'] = '<p>'._L('This request is invalid').'</p>';
  189.                                 return;
  190.                         }
  191.  
  192.                         $out['text'] = '';
  193.  
  194.                         // Check for permission
  195.  
  196.                         if ($file != '') {
  197.                                 if (!self::mayAccessResource($file)) {
  198.                                         $out['title'] = _L('Access denied');
  199.                                         $out['icon'] = 'img/error.png';
  200.                                         $out['text'] = '<p>'._L('Authentication error. Please log in.').'</p>';
  201.                                         return;
  202.                                 }
  203.                         }
  204.  
  205.                         // Redirections
  206.  
  207.                         if ($file != '') {
  208.                                 $target = null;
  209.                                 if (self::checkRedirect($file, $target)) {
  210.                                         $out['title'] = _L('Please wait...');
  211.                                         $out['text'] = '<p>'._L('You are being redirected...').'</p><script>window.location.href = '.js_escape($target).';</script>';
  212.                                         return;
  213.                                 }
  214.                         }
  215.  
  216.                         // First, "Go back to" line
  217.  
  218.                         if ($file != '') {
  219.                                 $dir = dirname($file);
  220.  
  221.                                 if ($dir == '.') {
  222.                                         if (file_exists(__DIR__.'/img/main_icon16.png')) {
  223.                                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png';
  224.                                         } else {
  225.                                                 $tree_icon = null; // default icon (folder)
  226.                                         }
  227.  
  228.                                         $ic = empty($tree_icon) ? '' : '<img src="'.$tree_icon.'" alt="">';
  229.  
  230.                                         $lng_gobackto = _L('Go back to').':';
  231.                                         $out['text'] .= '<p><a '.OIDplus::gui()->link('oidplus:resources').'><img src="img/arrow_back.png" width="16" alt="'._L('Go back').'"> '.$lng_gobackto.' '.$ic.' '.htmlentities($this->getMainTitle()).'</a></p>';
  232.                                 } else {
  233.                                         $realdir = self::realname($dir);
  234.  
  235.                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=folder_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($dir);
  236.                                         /*
  237.                                         $icon_candidate = pathinfo($realdir)['dirname'].'/'.pathinfo($realdir)['filename'].'_tree.png';
  238.                                         if (file_exists($icon_candidate)) {
  239.                                                 $tree_icon = $icon_candidate;
  240.                                         } else if (file_exists(__DIR__.'/img/folder_icon16.png')) {
  241.                                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/folder_icon16.png';
  242.                                         } else {
  243.                                                 $tree_icon = null; // no icon
  244.                                         }
  245.                                         */
  246.  
  247.                                         $ic = /*empty($tree_icon) ? '' : */'<img src="'.$tree_icon.'" alt="">';
  248.  
  249.                                         $out['text'] .= '<p><a '.OIDplus::gui()->link('oidplus:resources$'.rtrim($dir,'/').'/').'><img src="img/arrow_back.png" width="16" alt="'._L('Go back').'"> '._L('Go back to').': '.$ic.' '.htmlentities(self::getFolderTitle($realdir)).'</a></p><br>';
  250.                                 }
  251.                         }
  252.  
  253.                         // Then the content
  254.  
  255.                         $realfile = self::realname($file);
  256.                         // $realfile2 = preg_replace('/\.([^.]+)$/', '$'.OIDplus::getCurrentLang().'.\1', $realfile);
  257.                         // if (file_exists($realfile2)) $realfile = $realfile2;
  258.  
  259.                         if (file_exists($realfile) && (!is_dir($realfile))) {
  260.                                 if ((substr($file,-4,4) == '.url') || (substr($file,-5,5) == '.link')) {
  261.                                         $out['title'] = $this->getHyperlinkTitle($realfile);
  262.  
  263.                                         $out['icon'] = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=leaf_url_icon&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  264.                                         /*
  265.                                         $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_big.png';
  266.                                         if (file_exists($icon_candidate)) {
  267.                                                 $out['icon'] = $icon_candidate;
  268.                                         } else if (file_exists(__DIR__.'/img/leaf_url_icon.png')) {
  269.                                                 $out['icon'] = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/leaf_url_icon.png';
  270.                                         } else {
  271.                                                 $out['icon'] = '';
  272.                                         }
  273.                                         */
  274.  
  275.                                         // Should not happen though, due to conditionalselect
  276.                                         $out['text'] .= '<a href="'.htmlentities(self::getHyperlinkURL($realfile)).'" target="_blank">'._L('Open in new window').'</a>';
  277.                                 } else if ((substr($file,-4,4) == '.htm') || (substr($file,-5,5) == '.html')) {
  278.                                         $out['title'] = $this->getDocumentTitle($file);
  279.  
  280.                                         $out['icon'] = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=leaf_doc_icon&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  281.                                         /*
  282.                                         $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_big.png';
  283.                                         if (file_exists($icon_candidate)) {
  284.                                                 $out['icon'] = $icon_candidate;
  285.                                         } else if (file_exists(__DIR__.'/img/leaf_doc_icon.png')) {
  286.                                                 $out['icon'] = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/leaf_doc_icon.png';
  287.                                         } else {
  288.                                                 $out['icon'] = '';
  289.                                         }
  290.                                         */
  291.  
  292.                                         $out['text'] .= self::getDocumentContent($file);
  293.                                 } else {
  294.                                         $out['title'] = _L('Unknown file type');
  295.                                         $out['icon'] = 'img/error.png';
  296.                                         $out['text'] = '<p>'._L('The system does not know how to handle this file type.').'</p>';
  297.                                         return;
  298.                                 }
  299.                         } else if (is_dir($realfile)) {
  300.                                 $out['title'] = ($file == '') ? $this->getMainTitle() : self::getFolderTitle($realfile);
  301.  
  302.                                 if ($file == '') {
  303.                                         $out['icon'] = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  304.                                 } else {
  305.                                         $out['icon'] = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=folder_icon&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  306.                                         /*
  307.                                         $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_big.png';
  308.                                         if (file_exists($icon_candidate)) {
  309.                                                 $out['icon'] = $icon_candidate;
  310.                                         } else if (file_exists(__DIR__.'/img/folder_icon.png')) {
  311.                                                 $out['icon'] = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/folder_icon.png';
  312.                                         } else {
  313.                                                 $out['icon'] = null; // no icon
  314.                                         }
  315.                                         */
  316.                                 }
  317.  
  318.                                 if (file_exists(__DIR__.'/img/main_icon16.png')) {
  319.                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png';
  320.                                 } else {
  321.                                         $tree_icon = null; // default icon (folder)
  322.                                 }
  323.  
  324.                                 $count = 0;
  325.  
  326.                                 $dirs = self::myglob(rtrim($file,'/').'/'.'*', true);
  327.                                 natcasesort($dirs);
  328.                                 foreach ($dirs as $dir) {
  329.                                         $realdir = self::realname($dir);
  330.                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=folder_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($dir);
  331.                                         /*
  332.                                         $icon_candidate = pathinfo($realdir)['dirname'].'/'.pathinfo($realdir)['filename'].'_tree.png';
  333.                                         if (file_exists($icon_candidate)) {
  334.                                                 $tree_icon = $icon_candidate;
  335.                                         } else if (file_exists(__DIR__.'/img/folder_icon16.png')) {
  336.                                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/folder_icon16.png';
  337.                                         } else {
  338.                                                 $tree_icon = null; // no icon
  339.                                         }
  340.                                         */
  341.  
  342.                                         $ic = /*empty($tree_icon) ? '' : */'<img src="'.$tree_icon.'" alt="">';
  343.  
  344.                                         $out['text'] .= '<p><a '.OIDplus::gui()->link('oidplus:resources$'.rtrim($dir,'/').'/').'>'.$ic.' '.htmlentities(self::getFolderTitle($realdir)).'</a></p>';
  345.                                         $count++;
  346.                                 }
  347.  
  348.                                 $files = array_merge(
  349.                                         self::myglob(rtrim($file,'/').'/'.'*.htm'), // TODO: also PHP?
  350.                                         self::myglob(rtrim($file,'/').'/'.'*.html'),
  351.                                         self::myglob(rtrim($file,'/').'/'.'*.url'),
  352.                                         self::myglob(rtrim($file,'/').'/'.'*.link')
  353.                                 );
  354.                                 natcasesort($files);
  355.                                 foreach ($files as $file) {
  356.                                         $realfile = self::realname($file);
  357.                                         if ((substr($file,-4,4) == '.url') || (substr($file,-5,5) == '.link')) {
  358.                                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=leaf_url_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  359.                                                 /*
  360.                                                 $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_tree.png';
  361.                                                 if (file_exists($icon_candidate)) {
  362.                                                         $tree_icon = $icon_candidate;
  363.                                                 } else if (file_exists(__DIR__.'/img/leaf_url_icon16.png')) {
  364.                                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/leaf_url_icon16.png';
  365.                                                 } else {
  366.                                                         $tree_icon = null; // default icon (folder)
  367.                                                 }
  368.                                                 */
  369.  
  370.                                                 $ic = /*empty($tree_icon) ? '' : */'<img src="'.$tree_icon.'" alt="">';
  371.  
  372.                                                 $out['text'] .= '<p><a href="'.htmlentities(self::getHyperlinkURL($realfile)).'" target="_blank">'.$ic.' '.htmlentities($this->getHyperlinkTitle($realfile)).'</a></p>';
  373.                                                 $count++;
  374.                                         } else {
  375.                                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=leaf_doc_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  376.                                                 /*
  377.                                                 $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_tree.png';
  378.                                                 if (file_exists($icon_candidate)) {
  379.                                                         $tree_icon = $icon_candidate;
  380.                                                 } else if (file_exists(__DIR__.'/img/leaf_doc_icon16.png')) {
  381.                                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/leaf_doc_icon16.png';
  382.                                                 } else {
  383.                                                         $tree_icon = null; // default icon (folder)
  384.                                                 }
  385.                                                 */
  386.  
  387.                                                 $ic = /*empty($tree_icon) ? '' : */'<img src="'.$tree_icon.'" alt="">';
  388.  
  389.                                                 $out['text'] .= '<p><a '.OIDplus::gui()->link('oidplus:resources$'.$file).'>'.$ic.' '.htmlentities($this->getDocumentTitle($file)).'</a></p>';
  390.                                                 $count++;
  391.                                         }
  392.                                 }
  393.  
  394.                                 if ($count == 0) {
  395.                                         $out['text'] .= '<p>'._L('This folder does not contain any elements').'</p>';
  396.                                 }
  397.                         } else {
  398.                                 $out['title'] = _L('Not found');
  399.                                 $out['icon'] = 'img/error.png';
  400.                                 $out['text'] = '<p>'._L('This resource doesn\'t exist anymore.').'</p>';
  401.                         }
  402.                 }
  403.         }
  404.  
  405.         private function tree_rec(&$children, $rootdir=null, $depth=0) {
  406.                 if (is_null($rootdir)) $rootdir = '';
  407.                 if ($depth > 100) return false; // something is wrong!
  408.  
  409.                 $dirs = self::myglob($rootdir.'*'.'/', true);
  410.                 natcasesort($dirs);
  411.                 foreach ($dirs as $dir) {
  412.                         $tmp = array();
  413.  
  414.                         $this->tree_rec($tmp, $dir, $depth+1);
  415.  
  416.                         $realdir = self::realname($dir);
  417.  
  418.                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=folder_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($dir);
  419.                         /*
  420.                         $icon_candidate = pathinfo($realdir)['dirname'].'/'.pathinfo($realdir)['filename'].'_tree.png';
  421.                         if (file_exists($icon_candidate)) {
  422.                                 $tree_icon = $icon_candidate;
  423.                         } else if (file_exists(__DIR__.'/img/folder_icon16.png')) {
  424.                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/folder_icon16.png';
  425.                         } else {
  426.                                 $tree_icon = null; // default icon (folder)
  427.                         }
  428.                         */
  429.  
  430.                         $children[] = array(
  431.                                 'id' => 'oidplus:resources$'.$dir,
  432.                                 'icon' => $tree_icon,
  433.                                 'text' => self::getFolderTitle($realdir),
  434.                                 'children' => $tmp,
  435.                                 'state' => array("opened" => $depth <= OIDplus::config()->getValue('resource_plugin_autoopen_level', 1)-1)
  436.                         );
  437.                 }
  438.  
  439.                 $files = array_merge(
  440.                         self::myglob($rootdir.'*.htm'), // TODO: Also PHP?
  441.                         self::myglob($rootdir.'*.html'),
  442.                         self::myglob($rootdir.'*.url'),
  443.                         self::myglob($rootdir.'*.link')
  444.                 );
  445.                 natcasesort($files);
  446.                 foreach ($files as $file) {
  447.                         $realfile = self::realname($file);
  448.                         if ((substr($file,-4,4) == '.url') || (substr($file,-5,5) == '.link')) {
  449.                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=leaf_url_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  450.                                 /*
  451.                                 $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_tree.png';
  452.                                 if (file_exists($icon_candidate)) {
  453.                                         $tree_icon = $icon_candidate;
  454.                                 } else if (file_exists(__DIR__.'/img/leaf_url_icon16.png')) {
  455.                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/leaf_url_icon16.png';
  456.                                 } else {
  457.                                         $tree_icon = null; // default icon (folder)
  458.                                 }
  459.                                 */
  460.  
  461.                                 $children[] = array(
  462.                                         'id' => 'oidplus:resources$'.$file,
  463.                                         'conditionalselect' => 'window.open('.js_escape(self::getHyperlinkURL($realfile)).'); false;',
  464.                                         'icon' => $tree_icon,
  465.                                         'text' => $this->getHyperlinkTitle($realfile),
  466.                                         'state' => array("opened" => $depth <= OIDplus::config()->getValue('resource_plugin_autoopen_level', 1)-1),
  467.                                         'a_attr' => array(
  468.                                                 'href' => self::getHyperlinkURL($realfile),
  469.                                                 'target' => '_blank'
  470.                                         )
  471.                                 );
  472.                         } else {
  473.                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'show_icon.php?mode=leaf_doc_icon16&lang='.OIDplus::getCurrentLang().'&file='.urlencode($file);
  474.                                 /*
  475.                                 $icon_candidate = pathinfo($realfile)['dirname'].'/'.pathinfo($realfile)['filename'].'_tree.png';
  476.                                 if (file_exists($icon_candidate)) {
  477.                                         $tree_icon = $icon_candidate;
  478.                                 } else if (file_exists(__DIR__.'/img/leaf_doc_icon16.png')) {
  479.                                         $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/leaf_doc_icon16.png';
  480.                                 } else {
  481.                                         $tree_icon = null; // default icon (folder)
  482.                                 }
  483.                                 */
  484.                                 $children[] = array(
  485.                                         'id' => 'oidplus:resources$'.$file,
  486.                                         'icon' => $tree_icon,
  487.                                         'text' => $this->getDocumentTitle($file),
  488.                                         'state' => array("opened" => $depth <= OIDplus::config()->getValue('resource_plugin_autoopen_level', 1)-1)
  489.                                 );
  490.                         }
  491.                 }
  492.         }
  493.  
  494.         private function publicSitemap_rec($json, &$out) {
  495.                 foreach ($json as $x) {
  496.                         if (isset($x['id']) && $x['id']) {
  497.                                 $out[] = $x['id'];
  498.                         }
  499.                         if (isset($x['children'])) {
  500.                                 $this->publicSitemap_rec($x['children'], $out);
  501.                         }
  502.                 }
  503.         }
  504.  
  505.         public function publicSitemap(&$out) {
  506.                 $json = array();
  507.                 $this->tree($json, null/*RA EMail*/, false/*HTML tree algorithm*/, true/*display all*/);
  508.                 $this->publicSitemap_rec($json, $out);
  509.         }
  510.  
  511.         public function tree(&$json, $ra_email=null, $nonjs=false, $req_goto='') {
  512.                 $children = array();
  513.  
  514.                 $this->tree_rec($children, '/');
  515.  
  516.                 if (!OIDplus::config()->getValue('resource_plugin_hide_empty_path', true) || (count($children) > 0)) {
  517.                         if (file_exists(__DIR__.'/img/main_icon16.png')) {
  518.                                 $tree_icon = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png';
  519.                         } else {
  520.                                 $tree_icon = null; // default icon (folder)
  521.                         }
  522.  
  523.                         $json[] = array(
  524.                                 'id' => 'oidplus:resources',
  525.                                 'icon' => $tree_icon,
  526.                                 'state' => array("opened" => true),
  527.                                 'text' => $this->getMainTitle(),
  528.                                 'children' => $children
  529.                         );
  530.                 }
  531.  
  532.                 return true;
  533.         }
  534.  
  535.         public function tree_search($request) {
  536.                 return false;
  537.         }
  538.  
  539.         private static function getHyperlinkTitle($file) {
  540.                 $file2 = preg_replace('/\.([^.]+)$/', '$'.OIDplus::getCurrentLang().'.\1', $file);
  541.                 if (file_exists($file2)) $file = $file2;
  542.  
  543.                 if (substr($file,-4,4) == '.url') {
  544.                         return preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($file));
  545.                 } else if (substr($file,-5,5) == '.link') {
  546.                         /*
  547.                         [Link]
  548.                         Title=Report a bug
  549.                         URL=https://github.com/danielmarschall/oidplus/issues
  550.                         */
  551.  
  552.                         $data = @parse_ini_file($file, true);
  553.                         if (!$data) {
  554.                                 throw new OIDplusException(_L('File %1 has an invalid INI format!',$file));
  555.                         }
  556.                         if (!isset($data['Link'])) {
  557.                                 throw new OIDplusException(_L('Could not find "%1" section at %2','Link',$file));
  558.                         }
  559.                         if (!isset($data['Link']['Title'])) {
  560.                                 throw new OIDplusException(_L('"%1" is missing in %2','Title',$file));
  561.                         }
  562.                         return $data['Link']['Title'];
  563.                 } else {
  564.                         throw new OIDplusException(_L('Unexpected file extension for file %1',$file));
  565.                 }
  566.         }
  567.  
  568.         private static function getHyperlinkURL($file) {
  569.                 $file2 = preg_replace('/\.([^.]+)$/', '$'.OIDplus::getCurrentLang().'.\1', $file);
  570.                 if (file_exists($file2)) $file = $file2;
  571.  
  572.                 if (substr($file,-4,4) == '.url') {
  573.                         /*
  574.                         [InternetShortcut]
  575.                         URL=http://www.example.com/
  576.                         */
  577.  
  578.                         $data = @parse_ini_file($file, true);
  579.                         if (!$data) {
  580.                                 throw new OIDplusException(_L('File %1 has an invalid INI format!',$file));
  581.                         }
  582.                         if (!isset($data['InternetShortcut'])) {
  583.                                 throw new OIDplusException(_L('Could not find "%1" section at %2','InternetShortcut',$file));
  584.                         }
  585.                         if (!isset($data['InternetShortcut']['URL'])) {
  586.                                 throw new OIDplusException(_L('"%1" is missing in %2','URL',$file));
  587.                         }
  588.                         return $data['InternetShortcut']['URL'];
  589.                 } else if (substr($file,-5,5) == '.link') {
  590.                         /*
  591.                         [Link]
  592.                         Title=Report a bug
  593.                         URL=https://github.com/danielmarschall/oidplus/issues
  594.                         */
  595.  
  596.                         $data = @parse_ini_file($file, true);
  597.                         if (!$data) {
  598.                                 throw new OIDplusException(_L('File %1 has an invalid INI format!',$file));
  599.                         }
  600.                         if (!isset($data['Link'])) {
  601.                                 throw new OIDplusException(_L('Could not find "%1" section at %2','Link',$file));
  602.                         }
  603.                         if (!isset($data['Link']['URL'])) {
  604.                                 throw new OIDplusException(_L('"%1" is missing in %2','URL',$file));
  605.                         }
  606.                         return $data['Link']['URL'];
  607.                 } else {
  608.                         throw new OIDplusException(_L('Unexpected file extension for file %1',$file));
  609.                 }
  610.  
  611.         }
  612.  
  613.         private static function getFolderTitle($dir) {
  614.                 $data = @parse_ini_file("$dir/folder\$".OIDplus::getCurrentLang().".ini", true);
  615.                 if ($data && isset($data['Folder']) && isset($data['Folder']['Title'])) {
  616.                         return $data['Folder']['Title'];
  617.                 }
  618.  
  619.                 $data = @parse_ini_file("$dir/folder.ini", true);
  620.                 if ($data && isset($data['Folder']) && isset($data['Folder']['Title'])) {
  621.                         return $data['Folder']['Title'];
  622.                 }
  623.  
  624.                 return basename($dir);
  625.         }
  626. }
  627.