1<?php 2 3// lightmenu utilities class 4 5class lightmenu 6{ 7 protected static $syntax = [ 8 '|^-head$|' => 'head', 9 '|^-min=(\d+)$|' => 'min' 10 ]; 11 12 protected static $options; 13 14 15 protected static function _options(string $match) : array 16 { 17 $options = []; 18 19 $list = preg_split('|\s+|',$match); 20 foreach ($list as $option) 21 { 22 foreach (self::$syntax as $regex => $name) 23 { 24 if (preg_match($regex,$option,$matches)) 25 { 26 $options[$name] = (count($matches) === 2)?$matches[1]:true; 27 break; 28 } 29 } 30 } 31 32 return $options; 33 } 34 35 // function give lightmenu meta data file path from page id and environment 36 // $id : dokuwiki page id 37 // return : lightmenu meta data file path 38 protected static function _meta_path(string $id) : string 39 { 40 global $conf; 41 42 $_id = ':'.$id; 43 $pos = strrpos($_id,':'); 44 $path = strtr(substr($_id,0,$pos),':','/'); 45 $name = substr($_id,$pos + 1); 46 file_put_contents('/home/michel/Documents/projects/dokuwiki-plugin-lightmenu/lightmenu.log', 47 sprintf("%s _meta_path :\n id:%s\n _id:%s\n path:%s\n name:%s\n",date('Y/m/d H:i:s') 48 ,$id,$_id,$path,$name),FILE_APPEND); 49 if (($name === $conf['start'] && ($path !== '')) || (basename($path) === $name)) 50 return $path; 51// return sprintf('%s.lightmenu.json',$path); 52 if (is_dir(sprintf('%s%s/%s',$conf['datadir'],$path,$name))) 53 return sprintf('%s/%s',$path,$name); 54// return sprintf('%s/%s.lightmenu.json',$path,$name); 55 return sprintf('%s/%s.txt',$path,$name); 56// return sprintf('%s%s/%s.txt.lightmenu.json',$conf['metadir'],$path,$name); 57 } 58 59 // function give lightmenu meta data file path from page id and environment 60 // $id : dokuwiki page id 61 // return : lightmenu meta data file path 62 protected static function _subpath(string $id) : string 63 { 64 global $conf; 65 66 $_id = ':'.$id; 67 $pos = strrpos($_id,':'); 68 $path = strtr(substr($_id,0,$pos),':','/'); 69 $name = substr($_id,$pos + 1); 70 file_put_contents('/home/michel/Documents/projects/dokuwiki-plugin-lightmenu/lightmenu.log', 71 sprintf("%s _subpath :\n id:%s\n _id:%s\n path:%s\n name:%s\n",date('Y/m/d H:i:s') 72 ,$id,$_id,$path,$name),FILE_APPEND); 73 if (($name === $conf['start'] && ($path !== '')) || (basename($path) === $name)) 74 return $path; 75 if (is_dir(sprintf('%s%s/%s',$conf['datadir'],$path,$name))) 76 return sprintf('%s/%s',$path,$name); 77 return sprintf('%s/%s.txt',$path,$name); 78 } 79 80 81 // return data about wiki path identified by file subpath and name 82 // subpath : the sub path to the file from data page root directory 83 // name : the file name of the page or directory (ex : start.txt) 84 // returned values : array 85 // (boolean) true if the $name is a page, false if a directory 86 // (string) the dokuwiki id of the page 87 // (array) the lightmenu meta data 88 protected static function _get_page_data(string $subpath, string $name) : array 89 { 90 global $conf; 91 92 $path = sprintf('%s%s/%s.lightmenu.json',$conf['metadir'],$subpath,$name); 93 file_put_contents('/home/michel/Documents/projects/dokuwiki-plugin-lightmenu/lightmenu.log', 94 sprintf("%s _get_page_data :\n path:%s\n",date('Y/m/d H:i:s'),$path),FILE_APPEND); 95 96 $data = []; 97 if (is_file($path) && is_readable($path)) 98 $data = json_decode(file_get_contents($path),true,2,JSON_THROW_ON_ERROR); 99 return [ 100 $is_page = substr($name,-4) === '.txt', 101 ($is_page)?substr($name,0,-4):$name, 102 $data 103 ]; 104 } 105 106 protected static function _set_page_data(string $subpath, array &$data) 107 { 108 global $conf; 109 110 $path = sprintf('%s%s.lightmenu.json',$conf['metadir'],$subpath); 111 file_put_contents('/home/michel/Documents/projects/dokuwiki-plugin-lightmenu/lightmenu.log', 112 sprintf("%s _set_page_data :\n path:%s\n",date('Y/m/d H:i:s'),$path),FILE_APPEND); 113 if (is_file($path) && (! is_writable($path))) 114 throw new Exception(sprintf('Lightmenu : meta data file "%s" not writable.',$path)); 115 if (is_file($path) && (count($data) === 0)) 116 unlink($path); 117 elseif (file_put_contents($path,json_encode($data,JSON_THROW_ON_ERROR)) === false) 118 throw new Exception('Lightmenu unable to write meta data.'); 119 } 120 121 protected static function _touch_sidebar() 122 { 123 global $conf; 124 125 if (is_writeable($path = $conf['datadir'].'/'.$conf['sidebar'].'.txt')) 126 touch($path); 127 } 128 129 public static function update(string $id, string &$contents) 130 { 131 $data = []; 132 if (preg_match('|<lm:([^>]+)>|',$contents,$matches)) 133 $data = json_decode(trim($matches[1]),true,2,JSON_THROW_ON_ERROR); 134 if (preg_match('/(?:^|[^=])======([^=\n]+)======(?:$|[^=])/',$contents,$matches)) 135 $data['head'] = trim($matches[1]); 136 self::_set_page_data(self::_subpath($id),$data); 137 self::_touch_sidebar(); 138 } 139 140 protected static function _browse(string $subpath = '') : array 141 { 142 global $conf; 143 144 $tree = [[],[]]; 145 $path = $conf['datadir'].$subpath; 146 $list = scandir($path); 147 148 foreach ($list as $name) 149 { 150 if (($name === '.') || ($name === '..')) 151 continue; 152 $filepath = $path.'/'.$name; 153 [$is_page,$id,$data] = self::_get_page_data($subpath,$name); 154 if (is_dir($filepath)) 155 $tree[0][] = [$id,$data,self::_browse($subpath.'/'.$name)]; 156 elseif ($is_page) 157 { 158 if (($id === $conf['start']) || is_dir($path.'/'.$id) || ($id === basename($path))) 159 continue; 160 $tree[1][] = [$id,$data]; 161 } 162 } 163 $sort = function ($a,$b) { return strcmp($a[0],$b[0]); }; 164 usort($tree[0],$sort); 165 usort($tree[1],$sort); 166 return $tree; 167 } 168 169 /* browse the wiki hierarchy to get data needed for Lightmenu tree. 170 $options : string of options after "<lightmenu" tag. 171 return : array with start page data, wiki hierachy tree data and options 172 */ 173 public static function get_data(string $options) : array 174 { 175 global $conf; 176 177 [$is_page,$id,$data] = self::_get_page_data('',$conf['start'].'.txt'); 178 return [[$id,$data],self::_browse(),lightmenu::_options($options)]; 179 } 180 181 protected static function _get_label(string $id,array &$data) : string 182 { 183 global $conf; 184 185 if (isset($data['label.'.$conf['lang']])) 186 return $data['label.'.$conf['lang']]; 187 if (isset($data['label'])) 188 return $data['label']; 189 if (self::$options['head'] && isset($data['head'])) 190 return $data['head']; 191 return $id; 192 } 193 194 protected static function _format_attributes(array &$metas) : string 195 { 196 $html = ''; 197 198 foreach ($metas as $name => $value) 199 { 200 if ((strncmp($name,'label',5) === 0) || ($name === 'head') || ($name === 'title') || ($name === 'href')) 201 continue; 202 $html .= sprintf(' %s="%s"',$name,$value); 203 } 204 205 return $html; 206 } 207 208 protected static function _get_page(string $prefix, string $id, array &$metas) : string 209 { 210 $label = self::_get_label($id,$metas); 211 $html = '<div class="child">'.PHP_EOL; 212 $html .= sprintf('<span class="label" id="lm-%s%s"><a%s title="%s" href="%s">%s</a></span>'.PHP_EOL, 213 $prefix,$id,self::_format_attributes($metas),isset($metas['title'])?$metas['title']:$label,wl($prefix.$id),trim($label)); 214 $html .= '</div>'.PHP_EOL; 215 return $html; 216 } 217 218 protected static function _get_html(array &$data, string $prefix = '', int $level = 0) : string 219 { 220 global $conf; 221 222 $html = ''; 223 224 foreach ($data[0] as [$id,$metas,$children]) 225 { 226 $label = self::_get_label($id,$metas); 227 $html .= '<div class="child">'.PHP_EOL; 228 $html .= sprintf('<input type="checkbox" id="checkbox-%s%s" />',$prefix,$id); 229 $html .= sprintf('<label class="label" id="lm-%s%s" for="checkbox-%s%s"><a%s title="%s" href="%s">%s</a></label>'.PHP_EOL,$prefix,$id,$prefix,$id, 230 self::_format_attributes($metas),isset($metas['title'])?$metas['title']:$label,wl($prefix.$id),trim($label)); 231 if (count($children) > 0) 232 $html .= '<div class="tree">'.PHP_EOL.self::_get_html($children,$prefix.$id.':').'</div>'.PHP_EOL; 233 $html .= '</div>'.PHP_EOL; 234 } 235 236 foreach ($data[1] as [$id,$metas]) 237 { 238 if (($prefix === '') && ($id === $conf['sidebar'])) 239 continue; 240 $html .= self::_get_page($prefix,$id,$metas); 241 } 242 243 return $html; 244 } 245 246 public static function render(array &$data) : string 247 { 248 self::$options = $data[2]; 249 $html = '<div class="lm">'; 250 $html .= self::_get_page('',$data[0][0],$data[0][1]); 251 $html .= self::_get_html($data[1]); 252 $html .= '</div>'; 253 254 return $html; 255 } 256 257 protected static function _rescan(string $subpath = '') 258 { 259 global $conf; 260 261 $path = $conf['datadir'].$subpath; 262 $list = scandir($path); 263 foreach ($list as $name) 264 { 265 if (($name === '.') || ($name === '..')) 266 continue; 267 if (($subpath === '') && ($name === $conf['sidebar'].'.txt')) 268 continue; 269 $filepath = $path.'/'.$name; 270 [$is_page,$id,$data] = self::_get_page_data($subpath,$name); 271 if (is_dir($filepath)) 272 self::_rescan($subpath.'/'.$name); 273 elseif ($is_page) 274 { 275 $contents = file_get_contents($filepath); 276 if (preg_match('/(?:^|[^=])======([^=\n]+)======(?:$|[^=])/',$contents,$matches)) 277 { 278 $data['head'] = trim($matches[1]); 279 if ((($id === $conf['start']) && ($subpath !== '')) || ($id === basename($path))) 280 self::_set_page_data($subpath,$data); 281 elseif (is_dir($path.'/'.$id)) 282 self::_set_page_data($subpath.'/'.$id,$data); 283 else 284 self::_set_page_data($subpath.'/'.$name,$data); 285 } 286 } 287 } 288 } 289 290 public static function rescan() 291 { 292 global $conf; 293 294 self::_rescan(); 295 self::_touch_sidebar(); 296 } 297}