1<?php 2/** 3 * Plugin Bookmarkfile: Displays a bookmark file as linklist 4 * Syntax: <BOOKMARKFILE file="..." [separators="hide"] [folder="..."]> 5 * e.g. <BOOKMARKFILE file="bookmarks.json"> 6 * 7 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 8 * 9 * @author Ekkart Kleinod 10 * @author LarsDW223 11 */ 12 13/** 14 * Plugin-Class for Bookmarkfile-Plugin. 15 * 16 * All DokuWiki plugins to extend the parser/rendering mechanism 17 * need to inherit from DokuWiki_Syntax_Plugin 18 */ 19class syntax_plugin_bookmarkfile extends DokuWiki_Syntax_Plugin 20{ 21 /** First line of opera/firefox bookmark files in HTML format. */ 22 private $netscape_bmf = '<!DOCTYPE NETSCAPE-Bookmark-file-1>'; 23 24 /** 25 * What kind of syntax are we? 26 */ 27 public function getType() 28 { 29 return 'substition'; 30 } 31 32 /** 33 * How to handle paragraphs? 34 */ 35 public function getPType() 36 { 37 return 'block'; 38 } 39 40 /** 41 * Where to sort in? 42 */ 43 public function getSort() 44 { 45 return 100; 46 } 47 48 /** 49 * Connect pattern to lexer. 50 */ 51 public function connectTo($mode) 52 { 53 $this->Lexer->addSpecialPattern('\<BOOKMARKFILE .*?\>',$mode,'plugin_bookmarkfile'); 54 } 55 56 /** 57 * Handle the match. 58 */ 59 public function handle($match, $state, $pos, Doku_Handler $handler) 60 { 61 global $conf; 62 63 preg_match('/ file="(.*?)"/', $match, $matches); 64 $filename = $conf['mediadir'].'/'.str_replace(':', '/', $matches[1]); 65 66 preg_match('/ separators="(.*?)"/', $match, $matches); 67 $separators = $matches[1]; 68 69 preg_match('/ folder="(.*?)"/', $match, $matches); 70 $folder = $matches[1]; 71 72 // Tries to open the file 73 $result = array(); 74 $result['separators'] = $separators; 75 $bookmarkfile = fopen($filename, "r-"); 76 if ($bookmarkfile) { 77 // Detect bookmark browser 78 $first_line = trim(fgets($bookmarkfile)); 79 80 if (strcasecmp($first_line, $this->netscape_bmf) == 0) { 81 $bookmarks = $this->parseNetscapeFile($bookmarkfile); 82 fclose($bookmarkfile); 83 $result['bookmarks'] = $bookmarks; 84 } else { 85 if (strpos($first_line, 'x-moz') !== false) { 86 // Close the file 87 fclose($bookmarkfile); 88 89 $bookmarks = $this->parseFirefoxFile($filename); 90 if ($bookmarks !== null) { 91 $result['bookmarks'] = $bookmarks; 92 } else { 93 $result['message'] = $this->getLang('json_failed'); 94 } 95 } else { 96 $result['message'] = $this->getLang('err_format'); 97 } 98 } 99 } else { 100 $result['message'] = $this->getLang('err_nofile'); 101 } 102 103 if (!empty($folder) && !empty($result['bookmarks'])) { 104 $result['bookmarks'] = $this->findBookmarksFolder($result['bookmarks'], $folder); 105 } 106 107 return $result; 108 } 109 110 /** 111 * Create output. 112 */ 113 public function render($mode, Doku_Renderer $renderer, $data) 114 { 115 // At the moment, only xhtml is supported 116 if ($mode == 'xhtml') { 117 if (is_array($data['bookmarks'])) { 118 $this->renderBookmarks($renderer, $data['bookmarks'], $data['separators']); 119 } else { 120 $renderer->cdata($data['message']); 121 } 122 123 return true; 124 } 125 126 return false; 127 } 128 129 /** 130 * Process one level of a Firefox bookmark array 131 * (decoded from JSON encoded bookmark file). 132 */ 133 private function parseFirefoxJSON(array $json) 134 { 135 // Skip root node 136 if ($json['root'] == 'placesRoot') { 137 $pos = $json['children']; 138 } else { 139 $pos = $json; 140 } 141 142 $bookmarks = array(); 143 foreach ($pos as $json_item) { 144 if ($json_item['typeCode'] == 2) { 145 // A folder 146 switch ($json_item['guid']) { 147 case 'toolbar_____': 148 $title = $this->getLang('firefox_toolbar_folder'); 149 break; 150 case 'menu________': 151 $title = $this->getLang('firefox_menu_folder'); 152 break; 153 case 'mobile______': 154 $title = $this->getLang('firefox_mobile_folder'); 155 break; 156 case 'unfiled_____': 157 $title = $this->getLang('firefox_other_folder'); 158 break; 159 default: 160 $title = $json_item['title']; 161 break; 162 } 163 $item = array(); 164 $item ['type'] = 'folder'; 165 $item ['title'] = $title; 166 if (is_array($json_item['children'])) { 167 $item ['children'] = $this->parseFirefoxJSON($json_item['children']); 168 } else { 169 $item ['children'] = array(); 170 } 171 } else if ($json_item['typeCode'] == 1) { 172 // An entry/link 173 $item = array(); 174 $item ['type'] = 'link'; 175 $item ['title'] = $json_item['title']; 176 $item ['uri'] = $json_item['uri']; 177 } else if ($json_item['typeCode'] == 3) { 178 // A separator 179 $item = array(); 180 $item ['type'] = 'separator'; 181 } 182 $bookmarks [] = $item; 183 } 184 185 return $bookmarks; 186 } 187 188 /** 189 * Parse a Firefox bookmark file (JSON encoded). 190 */ 191 private function parseFirefoxFile($filename) 192 { 193 $json = file_get_contents($filename); 194 195 $json_bookmarks = json_decode($json, true); 196 if ($json_bookmarks === null) { 197 return null; 198 } 199 $bookmarks = $this->parseFirefoxJSON($json_bookmarks); 200 return $bookmarks; 201 } 202 203 /** 204 * Process an HTML (Netscape) bookmark file. 205 */ 206 private function parseNetscapeFile($bookmarkfile) 207 { 208 // read file line by line 209 $bookmarks = array(); 210 while (!feof($bookmarkfile)) { 211 $sLine = trim(fgets($bookmarkfile)); 212 213 // Ordner 214 if (preg_match('/\<H1\>(.*?)\<\/H1\>/', $sLine, $matches) == 1) { 215 // Root folder 216 $item = array(); 217 $item ['type'] = 'folder'; 218 $item ['title'] = $matches[1]; 219 } else if (preg_match('/\<DT\>\<H3.*?\>(.*?)\<\/H3\>/', $sLine, $matches) == 1) { 220 $item = array(); 221 $item ['type'] = 'folder'; 222 $item ['title'] = $matches[1]; 223 } else if (preg_match('/\<DT\>\<A.*?HREF="(.*?)".*?\>(.*?)\<\/A\>/', $sLine, $matches) == 1) { 224 $item = array(); 225 $item ['type'] = 'link'; 226 $item ['title'] = $matches[2]; 227 $item ['uri'] = $matches[1]; 228 $bookmarks [] = $item; 229 } else if (preg_match('/\<DL>/', $sLine, $matches) == 1) { 230 $item['children'] = $this->parseNetscapeFile($bookmarkfile); 231 $bookmarks [] = $item; 232 } else if (preg_match('/\<\/DL>/', $sLine, $matches) == 1) { 233 return $bookmarks; 234 } 235 } 236 237 return $bookmarks; 238 } 239 240 /** 241 * Find a folder in the bookmarks array and return it's children. 242 * (or null if the folder doesn't exist). 243 */ 244 private function findBookmarksFolder($bookmarks, $folder) 245 { 246 foreach ($bookmarks as $item) { 247 if ($item['type'] == 'folder') { 248 if ($item['title'] == $folder) { 249 return $item['children']; 250 } 251 $found = $this->findBookmarksFolder($item['children'], $folder); 252 if ($found !== null) { 253 return $found; 254 } 255 } 256 } 257 return null; 258 } 259 260 /** 261 * Render the bookmarks as a list. 262 */ 263 private function renderBookmarks(Doku_Renderer $renderer, array $bookmarks, $separators, $level=1) 264 { 265 $renderer->listu_open('bookmarkfile'); 266 foreach ($bookmarks as $item) { 267 switch ($item['type']) { 268 case 'link': 269 // An entry/link 270 $renderer->listitem_open($level); 271 $renderer->listcontent_open(); 272 $renderer->externallink($item['uri'], $item['title']); 273 $renderer->listcontent_close(); 274 $renderer->listitem_close(); 275 break; 276 case 'folder': 277 // A folder 278 $renderer->listitem_open($level); 279 $renderer->listcontent_open(); 280 $renderer->cdata($item['title']); 281 $renderer->listcontent_close(); 282 $this->renderBookmarks($renderer, $item['children'], $separators, $level+1); 283 $renderer->listitem_close(); 284 break; 285 case 'separator': 286 // A separator 287 if ($separators !== 'hide') { 288 $renderer->doc .= '<li class="level'.$level.' separator">'; 289 $renderer->listcontent_open(); 290 $renderer->hr(); 291 $renderer->listcontent_close(); 292 $renderer->doc .= '</li>'.DOKU_LF; 293 } 294 break; 295 } 296 } 297 $renderer->listu_close(); 298 } 299} 300