1<?php 2/** 3 * Offline Plugin: Create a static version that is offline browseable 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Carsten Graw <dokuwiki@graw.eu> 7 */ 8 9 10/* ----------------------- Set some global variables ----------------------- */ 11// Global configuration for offline plugin will be stored in this array: 12$offlineConf = array(); 13 14// The following variable is used to determine whether the script is run in the 15// dry run mode or not. 16// Because it is needed in 'init_log_file()' it is set here and not in the 17// function 'set_my_global_variables()': 18$offlineConf['offlineMode'] = strtolower(htmlentities(strip_tags(stripslashes($_REQUEST['offlineMode'])))); 19 20$offlineConf['pathDelimiterStr'] = strstr(PHP_OS, 'WIN') ? "\\" : "/"; 21 22 23 24/* ----------------------------- Include files ----------------------------- */ 25require_once('inc/log.inc.php'); 26 27// Get DokuWiki configuration: 28// Note: Original DokuWiki defines paths with a trailing slash: 29if (!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . $offlineConf['pathDelimiterStr'] . '..' . $offlineConf['pathDelimiterStr'] . '..' . $offlineConf['pathDelimiterStr'] . '..' . $offlineConf['pathDelimiterStr']); 30require_once(DOKU_INC.'inc/init.php'); 31 32// Get offline plugin configuration: 33require_once('conf/default.php'); 34 35 36 37/* -------------------- Main program starts below here -------------------- */ 38echo '<pre>'; 39 40// Initialize log file: 41init_log_file(); 42 43// Store required information in global array '$offlineConf': 44set_my_global_variables(); 45 46// Recursively delete old temporary directory in which remains of a previous 47// offline version might still be stored: 48delete_old_tmp_session_directory($offlineConf['absolutePathToTmpSessionDirStr']); 49 50// Create temporary directory in which the offline version will be stored 51create_directory($offlineConf['absolutePathToTmpSessionDirStr'], 0777); 52 53// Copy or download css files: 54process_css_files(); 55 56// Copy media files: 57process_media_files(); 58 59// Process directory in which pages are stored: 60process_wiki_pages(); 61 62// Build indices for easier navigation and write them to file: 63process_indices(); 64 65// Building an archiv, e.g. a zip-file: 66create_archive(); 67 68// Delete temporary directory in which the offline version was stored: 69// (In case the archiver was not called with the option 'move files' the 70// remaining files should be deleted to leave the temporary directory clean.) 71delete_old_tmp_session_directory($offlineConf['absolutePathToTmpSessionDirStr']); 72 73 74log_msg(__LINE__, date('Y-m-d') . ' Offline version created'); 75echo '</pre>'; 76die; 77 78 79 80 81/* ----------------- Function definitions start below here ----------------- */ 82 83 84/** 85 * Initialize the log file. 86 * 87 * Global variables for logging are being set. 88 * An existing log file will be deleted. 89 * 90 */ 91function init_log_file() { 92 global $offlineConf; 93 94 // Settings for logging: 95 $offlineConf['logWriteToFile'] = true; 96 $offlineConf['logPrintToScreen'] = true; 97 $offlineConf['logDirectory'] = dirname(__FILE__); 98 $offlineConf['logDefaultFileName'] = 'offline.log'; 99 100 // Delete old log file 101 delete_file($offlineConf['logDirectory'] . '/' . $offlineConf['logDefaultFileName'], false); 102 103 log_msg(__LINE__, date('Y-m-d') . ' Creating offline version'); 104} 105 106 107/** 108 * Read user defined settings. 109 * If there aren't any user defined settings, the default settings will be 110 * used instead. 111 * Values will be returned in the global hash $offlineConf. 112 * 113 * 114 * Example: 115 * 1) The user defined setting "wgetPathToBinary' can be accessed like this: 116 * $conf['plugin']['offline']['wgetPathToBinary'] 117 * 118 * 2) The default setting for wgetPathToBinary'is derived from the file 119 * "default.php" 120 * and is stored in 121 * $conf['wgetPathToBinary'] 122 * 123 */ 124function set_my_global_variables() { 125 global $conf;// Variables from DokuWiki 126 global $offlineConf;// Variables for offline plugin 127 128 // Merge default values for offline plugin with user defined settings: 129 $optionsArr = array('template', 'dokuwikiProtocol', 'dokuwikiHost', 'dokuwikiRelativePath', 'wgetPathToBinary', 'wgetHttpUser', 'wgetHttpPasswd', 'archiverPathToBinary', 'archiverOptions', 'insertLinkBackToHome', 'writeLogFile'); 130 foreach($optionsArr as $optionStr) { 131 log_msg(__LINE__, $optionStr . '=' . $conf['plugin']['offline'][$optionStr] . ' (Default: ' . $conf[$optionStr] . ')'); 132 isset($conf['plugin']['offline'][$optionStr]) ? $offlineConf[$optionStr] = $conf['plugin']['offline'][$optionStr] : $offlineConf[$optionStr] = $conf[$optionStr]; 133 log_msg(__LINE__, $optionStr . ' = ' . $offlineConf[$optionStr]); 134 } 135 136 $offlineConf['absolutePathToDokuwikiDirStr'] = dirname(__FILE__) . $offlineConf['pathDelimiterStr'] . '..' . $offlineConf['pathDelimiterStr'] . '..' . $offlineConf['pathDelimiterStr'] . '..'; 137 $offlineConf['absolutePathToOfflinePluginDirStr'] = dirname(__FILE__); 138 $offlineConf['absolutePathToDataDirStr'] = $offlineConf['absolutePathToDokuwikiDirStr'] . $offlineConf['pathDelimiterStr'] . 'data'; 139 $offlineConf['absolutePathToMediaDirStr'] = $offlineConf['absolutePathToDokuwikiDirStr'] . $offlineConf['pathDelimiterStr'] . 'data' . $offlineConf['pathDelimiterStr'] . 'media'; 140 $offlineConf['absolutePathToPagesDirStr'] = $offlineConf['absolutePathToDokuwikiDirStr'] . $offlineConf['pathDelimiterStr'] . 'data' . $offlineConf['pathDelimiterStr'] . 'pages'; 141 $offlineConf['absolutePathToTmpDirStr'] = $offlineConf['absolutePathToDokuwikiDirStr'] . $offlineConf['pathDelimiterStr'] . 'data' . $offlineConf['pathDelimiterStr'] . 'tmp'; 142// $offlineConf['absolutePathToTmpSessionDirStr'] = $offlineConf['absolutePathToDokuwikiDirStr'] . '/data/tmp' . '/offline' . date('Y-m-d_His'); 143 $offlineConf['absolutePathToTmpSessionDirStr'] = $offlineConf['absolutePathToDokuwikiDirStr'] . $offlineConf['pathDelimiterStr'] . 'data' . $offlineConf['pathDelimiterStr'] . 'tmp' . $offlineConf['pathDelimiterStr'] . 'offline'; 144 $offlineConf['pageExtensionStr'] = 'txt'; 145// $offlineConf['dokuwikiBaseUrlStr'] = 'http://localhost/dokuwiki'; 146 if ($offlineConf['dokuwikiRelativePath'] != '') { 147 $offlineConf['dokuwikiBaseUrlStr'] = $offlineConf['dokuwikiProtocol'] . '://' . $offlineConf['dokuwikiHost'] . '/' . $offlineConf['dokuwikiRelativePath']; 148 } else { 149 $offlineConf['dokuwikiBaseUrlStr'] = $offlineConf['dokuwikiProtocol'] . '://' . $offlineConf['dokuwikiHost']; 150 } 151 $offlineConf['dokuwikiShowPageUrlStr'] = $offlineConf['dokuwikiBaseUrlStr'] . '/doku.php?id='; 152 $offlineConf['dokuwikiExportCommandSuffixStr'] = '&do=export_offline'; 153 $offlineConf['wgetCommandStr'] = assemble_wget_command(); 154 $offlineConf['pageDirectoriesArr'] = array(); 155 $offlineConf['wgetCommandsArr'] = array(); 156 $offlineConf['mediaDirectoriesArr'] = array(); 157 $offlineConf['mediaFilesSourceAndTargetArr'] = array(); 158 159 // List all parameters and there values for debbuging: 160 foreach ($offlineConf as $key => $value) { 161 $parametersStr .= '-- ' . $key . ' = ' . $value . "\n"; 162 } 163 log_msg(__LINE__, $parametersStr); 164} 165 166 167/** 168 * Processes the directory in which pages are stored. 169 * 170 * First, the page directory tree is traversed and all files and subdirectories 171 * are stored in the global array $offlineConf['pageDirectoriesArr']. 172 * In the second step this array is walked through and the according directory 173 * structure is created in the temporary pages directory. 174 * (Subdirectories are created by DokuWiki if wiki pages are arranged in name 175 * spaces.) 176 * Finally, each wiki page is downloaded and saved in the temp directory. 177 * 178 */ 179function process_wiki_pages() { 180 global $offlineConf; 181 182 log_msg(__LINE__, 'Processing wiki pages ...'); 183 retrieve_page_tree($offlineConf['absolutePathToPagesDirStr']); 184 log_msg(__LINE__, join("\n", $offlineConf['wgetCommandsArr'])); 185 186 foreach ($offlineConf['pageDirectoriesArr'] as $currentDirStr => $count) { 187 create_directory($offlineConf['absolutePathToTmpSessionDirStr'] . $offlineConf['pathDelimiterStr'] . $currentDirStr, 0777); 188 } 189 190 foreach ($offlineConf['wgetCommandsArr'] as $currentWgetCommandStr) { 191 log_msg(__LINE__, $currentWgetCommandStr); 192 if ($offlineConf['offlineMode'] != 'dryrun') system($currentWgetCommandStr); 193 } 194 unset($offlineConf['wgetCommandsArr']); 195} 196 197 198/** 199 * Expects an absolute path to start from and traverses the whole directory. 200 * All subdirectories and files are returned in a nested array. 201 * 202 * Credits: 203 * This routine is based on the function 'retrieveTree' which is explained in 204 * the article 'Recursion In PHP: Tapping Unharnessed Power' by Robert Peake 205 * published on November 18, 2004, on the website 206 * http://devzone.zend.com/node/view/id/1235. Discovered: 12/20/2008. 207 * Thank you, Robert! 208 * 209 */ 210function retrieve_page_tree($pathStr) { 211 global $offlineConf; 212 213 if ($dir = @opendir($pathStr)) { 214 while (($element = readdir($dir)) !== false) { 215 if (is_dir($pathStr . $offlineConf['pathDelimiterStr'] . $element) && $element != "." && $element != "..") { 216 $array[$element] = retrieve_page_tree($pathStr . $offlineConf['pathDelimiterStr'] . $element); 217 } elseif ($element!= "." && $element!= ".." && (ereg('\.txt$', $element))) { 218 $array[] = $pathStr . $offlineConf['pathDelimiterStr'] . $element; 219 $tmpDokuwikiUrlStr = '"' . $offlineConf['dokuwikiShowPageUrlStr'] . convert_absolute_file_system_path_to_relative_dokuwiki_path($pathStr . $offlineConf['pathDelimiterStr'] . $element) . $offlineConf['dokuwikiExportCommandSuffixStr'] . '"'; 220 $tmpRelativeFilenameWithPathStr = str_replace($offlineConf['absolutePathToDataDirStr'] . $offlineConf['pathDelimiterStr'], '', $pathStr) . $offlineConf['pathDelimiterStr'] . str_replace('.txt', '.html', $element); 221 $tmpOutputFilenameWithPathStr = '"' . $offlineConf['absolutePathToTmpSessionDirStr'] . $offlineConf['pathDelimiterStr'] . $tmpRelativeFilenameWithPathStr . '"'; 222 $offlineConf['wgetCommandsArr'][] = $offlineConf['wgetCommandStr'] . ' ' . $tmpDokuwikiUrlStr . ' -O ' . $tmpOutputFilenameWithPathStr; 223 $offlineConf['pageDirectoriesArr'][str_replace($offlineConf['absolutePathToDataDirStr'] . $offlineConf['pathDelimiterStr'], '', $pathStr)]++; 224 $offlineConf['indexHierarchicalArr'][] = $tmpRelativeFilenameWithPathStr; 225 226 $offlineConf['indexAlphabeticalArr'][$element] = $tmpRelativeFilenameWithPathStr; 227 } 228 } 229 closedir($dir); 230 } 231 232 return (isset($array) ? $array : false); 233} 234 235 236/** 237 * Converts an absolute path from the file system into a relative dokuwiki 238 * path. 239 * 240 * 1) Remove prefix for absolute path, e.g.: 241 * '/data/htdocs/dokuwiki/data/pages/admin/start.txt' 242 * --> 243 * 'admin/start.txt' 244 * 2) Remove file extension: 245 * 'admin/start.txt' 246 * --> 247 * 'admin/start' 248 * 3) Convert delimiter string to colon: 249 * 'admin/start' 250 * --> 251 * 'admin:start' 252 * 253 */ 254function convert_absolute_file_system_path_to_relative_dokuwiki_path($elementStr) { 255 global $offlineConf; 256 257 $elementStr = str_replace($offlineConf['absolutePathToPagesDirStr'] . $offlineConf['pathDelimiterStr'], '', $elementStr); 258 $elementStr = ereg_replace('^(.*)\.' . $offlineConf['pageExtensionStr'] . '$', '\\1', $elementStr); 259 $elementStr = str_replace($offlineConf['pathDelimiterStr'], ':', $elementStr); 260 261 return $elementStr; 262} 263 264 265/** 266 * Assembles the wget command, which will be used to download the wiki pages. 267 * 268 */ 269function assemble_wget_command() { 270 global $offlineConf; 271 272 // Verify existence of wget binary file: 273 check_existence($offlineConf['wgetPathToBinary'], 'file'); 274 275 if (($offlineConf['wgetHttpUser'] != '') || ($offlineConf['wgetHttpPasswd'] != '')) { 276 $commandStr = ' --http-user="' . $offlineConf['wgetHttpUser'] . '" --http-passwd="' . $offlineConf['wgetHttpPasswd'] . '"'; 277 } 278 279 $commandStr = escapeshellcmd($offlineConf['wgetPathToBinary'] . ' --proxy=off ' . $commandStr); 280 281 return $commandStr; 282} 283 284 285/** 286 * Copies or downloads the cascading style sheet files which a are needed to 287 * display the wiki pages properly. 288 * 289 */ 290function process_css_files() { 291 global $offlineConf; 292 293 log_msg(__LINE__, 'Processing css files ...'); 294 if ($offlineConf['template'] == 'default') { 295 // Use DokuWiki's current default template: 296 download_css_files($offlineConf['absolutePathToTmpSessionDirStr'], $offlineConf['wgetCommandStr']); 297 } else { 298 // Use one of the templates provided with this plugin: 299 copy_css_files($offlineConf['absolutePathToTmpSessionDirStr'], $offlineConf['template']); 300 } 301} 302 303 304/** 305 * Download necessary css files. 306 * 307 */ 308function download_css_files($absolutePathToTmpDirStr, $wgetCommandStr) { 309 global $offlineConf; 310 311 log_msg(__LINE__, 'Downloading css files ...'); 312 313 create_directory($absolutePathToTmpDirStr . $offlineConf['pathDelimiterStr'] . 'css', 0777); 314 $cssFilesArr = array(); 315 $cssFilesArr['all'] = '/lib/exe/css.php?s=all&t=default'; 316 $cssFilesArr['screen'] = '/lib/exe/css.php?t=default'; 317 $cssFilesArr['print'] = '/lib/exe/css.php?s=print&t=default'; 318 foreach ($cssFilesArr as $currentTypeStr => $currentCssSubPathStr) { 319 $currentWgetCommandStr = $offlineConf['wgetCommandStr'] . ' "' . $offlineConf['dokuwikiBaseUrlStr'] . $currentCssSubPathStr . '" -O "' . $absolutePathToTmpDirStr . $offlineConf['pathDelimiterStr'] . 'css' . $offlineConf['pathDelimiterStr'] . $currentTypeStr . '.css" ' . "\n"; 320 log_msg(__LINE__, $currentWgetCommandStr); 321 if ($offlineConf['offlineMode'] != 'dryrun') system($currentWgetCommandStr); 322 } 323} 324 325 326/** 327 * Copying necessary css files. 328 * 329 */ 330function copy_css_files($absolutePathToTmpDirStr, $cssTemplateStr = 'default') { 331 global $offlineConf; 332 333 log_msg(__LINE__, 'Copying css files ...'); 334 335 create_directory($absolutePathToTmpDirStr . $offlineConf['pathDelimiterStr'] . 'css', 0777); 336 foreach (array('all.css', 'print.css', 'screen.css') as $currentCssFileStr) { 337 $tmpSourceFilenameWithPathStr = $offlineConf['absolutePathToOfflinePluginDirStr'] . $offlineConf['pathDelimiterStr'] . 'ui' . $offlineConf['pathDelimiterStr'] . $cssTemplateStr . $offlineConf['pathDelimiterStr'] . $currentCssFileStr; 338 $tmpTargetFilenameWithPathStr = $absolutePathToTmpDirStr . $offlineConf['pathDelimiterStr'] . 'css' . $offlineConf['pathDelimiterStr'] . $currentCssFileStr; 339 log_msg(__LINE__, 'copying "' . $tmpSourceFilenameWithPathStr . '" to "' . $tmpTargetFilenameWithPathStr . '"'); 340 copy_file($tmpSourceFilenameWithPathStr, $tmpTargetFilenameWithPathStr); 341 } 342} 343 344 345/** 346 * Creates a directory in the file system. 347 * 348 * The directory is created only if it does not exist already. 349 * If the directory cannot be created, the program will be aborted. 350 * 351 * Subdirectories will be created when necessary. 352 * 353 */ 354function create_directory($dirStr, $mode = 0777) { 355 global $offlineConf; 356 357 $msgStr = 'Creating directory "' . $dirStr . '" ... ' . "\n"; 358 log_msg(__LINE__, $msgStr); 359 360 if (!is_dir($dirStr)) { 361 if (strstr(PHP_OS, 'WIN')) { 362 // Windows file system: 363 $singleDirectoriesArr = explode($offlineConf['pathDelimiterStr'] , $dirStr); 364 array_shift($singleDirectoriesArr);// Throw away drive letter from array 365 $currentDirStr = substr($dirStr, 0, 1) . ':';// Drive letter 366 } else { 367 // Unix like file system: 368 $dirStr = ereg_replace('^\/', '', $dirStr); 369 $singleDirectoriesArr = explode($offlineConf['pathDelimiterStr'] , $dirStr); 370 $currentDirStr = ''; 371 } 372 373 for ($d = 0; $d < sizeof($singleDirectoriesArr); $d++) { 374 $currentDirStr .= $offlineConf['pathDelimiterStr'] . $singleDirectoriesArr[$d]; 375 if (!is_dir($currentDirStr)) { 376 $msgStr = ' Creating directory "' . $currentDirStr . '" ... ' . "\n"; 377 if ($offlineConf['offlineMode'] != 'dryrun') { 378 if (mkdir($currentDirStr, $mode)) { 379 $msgStr .= 'ok.'; 380 log_msg(__LINE__, compact('msgStr')); 381 } else { 382 $msgStr .= 'ERROR: Could not create directory!' . "\n"; 383 $msgStr .= 'Program aborted.' . "\n\n"; 384 log_msg(__LINE__, compact('msgStr')); 385 die; 386 } 387 } 388 } 389 } 390 } else { 391 $msgStr .= ' already exists.'; 392 log_msg(__LINE__, $msgStr); 393 } 394} 395 396 397/** 398 * Copies a file. 399 * 400 */ 401function copy_file($sourceFilenameWithPathStr, $targetFilenameWithPathStr) { 402 global $offlineConf; 403 404 log_msg(__LINE__, 'Copying files ...' . "\n" . ' Source: ' . $sourceFilenameWithPathStr . "\n" . ' Target: ' . $targetFilenameWithPathStr); 405 if ($offlineConf['offlineMode'] != 'dryrun') { 406 check_existence($sourceFilenameWithPathStr, 'file'); 407 check_existence(dirname($targetFilenameWithPathStr), 'directory'); 408 if (!copy($sourceFilenameWithPathStr, $targetFilenameWithPathStr)) { 409 log_msg(__LINE__, 'ERROR: Could not copy!'); 410 } 411 } 412} 413 414 415/** 416 * Delete a file from the file system. 417 * 418 */ 419function delete_file($filenameWithPathStr, $log = true) { 420 global $offlineConf; 421 422 if ($log) log_msg(__LINE__, 'Deleting "' . $filenameWithPathStr . '" ...'); 423 if (file_exists($filenameWithPathStr)) { 424 if ($offlineConf['offlineMode'] != 'dryrun') { 425 if (!unlink($filenameWithPathStr)) { 426 log_msg(__LINE__, 'Could not delete "' . $filenameWithPathStr . '".'); 427 } 428 } 429 } 430} 431 432 433/** 434 * Removes a directory from the file system. 435 * 436 */ 437function remove_directory($pathStr, $log = true) { 438 global $offlineConf; 439 440 if ($log) log_msg(__LINE__, 'Removing directory "' . $pathStr . '" ...'); 441 if (is_dir($pathStr)) { 442 if ($offlineConf['offlineMode'] != 'dryrun') { 443 if (!rmdir($pathStr)) { 444 log_msg(__LINE__, 'Could not remove "' . $pathStr . '".'); 445 } 446 } 447 } 448} 449 450 451/** 452 * Checks, whether a file or a directory in the file system exists or not. 453 * If the file/directory exists, the program simply continues. 454 * If there is no such file/directory, an error message will be written to the 455 * log file (only if logging is enabled, i.e. 'writeLogFile' is set to 1). 456 * 457 */ 458function check_existence($locationStr, $typeStr) { 459 log_msg(__LINE__, 'Checking whether ' . $typeStr . ' "' . $locationStr . '" exists ...'); 460 if ($typeStr == 'file') { 461 if (file_exists($locationStr)) { 462 log_msg(__LINE__, 'ok'); 463 } else { 464 $msgStr .= 'ERROR: No such file.' . "\n"; 465 $msgStr .= 'Program aborted.' . "\n\n"; 466 log_msg(__LINE__, compact('msgStr')); 467 die; 468 } 469 } else if ($typeStr == 'directory') { 470 if (is_dir($locationStr)) { 471 log_msg(__LINE__, 'ok'); 472 } else { 473 $msgStr .= 'ERROR: No such directory.' . "\n"; 474 $msgStr .= 'Program aborted.' . "\n\n"; 475 log_msg(__LINE__, compact('msgStr')); 476 die; 477 } 478 } 479} 480 481 482/** 483 * Expects an absolute path to start from and traverses the whole directory. 484 * All subdirectories and files are returned in a nested array. 485 * 486 * Credits: 487 * This routine is based on the function 'retrieveTree' which is explained in 488 * the article 'Recursion In PHP: Tapping Unharnessed Power' by Robert Peake 489 * published on November 18, 2004, on the website 490 * http://devzone.zend.com/node/view/id/1235. Discovered: 12/20/2008. 491 * Thank you, Robert! 492 * 493 */ 494function delete_tmp_session_tree($pathStr) { 495 global $offlineConf; 496 497 log_msg(__LINE__, 'Deleting old temporary session dir ...'); 498 if ($dir = @opendir($pathStr)) { 499 while (($element = readdir($dir)) !== false) { 500 if (is_dir($pathStr . $offlineConf['pathDelimiterStr'] . $element) && $element != "." && $element != "..") { 501 $offlineConf['directoriesToDeleteArr'][] = $pathStr . $offlineConf['pathDelimiterStr'] . $element; 502 $array[$element] = delete_tmp_session_tree($pathStr . $offlineConf['pathDelimiterStr'] . $element); 503 remove_directory($pathStr . $offlineConf['pathDelimiterStr'] . $element); 504 $countDirectoriesDeleted++; 505 } elseif (($element != ".") && ($element != "..")) { 506 delete_file($pathStr . $offlineConf['pathDelimiterStr'] . $element); 507 $array[] = $pathStr . $offlineConf['pathDelimiterStr'] . $element; 508 $countFilesDeleted++; 509 } 510 } 511 closedir($dir); 512 } else { 513 log_msg(__LINE__, 'Nothing to do.'); 514 } 515 516 return (isset($array) ? $array : false); 517} 518 519 520function delete_old_tmp_session_directory($absolutePathToTmpSessionDirStr) { 521 global $offlineConf; 522 523 if (is_dir($absolutePathToTmpSessionDirStr)) { 524 delete_tmp_session_tree($absolutePathToTmpSessionDirStr); 525 } else { 526 // There is no temporary session directory left from a previous run 527 // of the offline plugin: 528 529 return; 530 } 531} 532 533 534/** 535 * Writes a string to a file specified by its absolute path in the file system 536 * and its name and extension. 537 * 538 * An existing file will be overwritten! 539 * 540 */ 541function write_file($filenameWithPathStr, $fileContentsStr) { 542 global $offlineConf; 543 544 log_msg(__LINE__, 'Writing file "' . $filenameWithPathStr . '" ...'); 545 if (isset($fileContentsStr) && $fileContentsStr != '') { 546 if ($offlineConf['offlineMode'] != 'dryrun') { 547 $filehandle = fopen($filenameWithPathStr, 'w') or log_msg('File could not be opened for writing.'); 548 fwrite($filehandle, $fileContentsStr); 549 fclose($filehandle) or die('File could not be closed'); 550 } 551 } else { 552 log_msg(__LINE__, 'There is nothing to write to nowhere.'); 553 } 554} 555 556 557/** 558 * Creates an archive of the offline browseable pages in the temporary 559 * directory. 560 * 561 */ 562function create_archive() { 563 global $offlineConf; 564 565 log_msg(__LINE__, 'Creating archive ...'); 566 567 if (($offlineConf['archiverPathToBinary'] != '') && ($offlineConf['archiverOptions'] != '')) { 568 log_msg(__LINE__, 'Changing working directory to "' . $offlineConf['absolutePathToTmpDirStr'] . '"'); 569 if (!chdir($offlineConf['absolutePathToTmpDirStr'])) { 570 log_msg(__LINE__, 'Could not change directory to "' . $offlineConf['absolutePathToTmpDirStr'] . '".'); 571 } 572 573 $commandStr = escapeshellcmd($offlineConf['archiverPathToBinary'] . ' ' . $offlineConf['archiverOptions']); 574 $commandStr .= ' "' . dirname(__FILE__) . $offlineConf['pathDelimiterStr'] . 'offline.zip" "offline" >> '; 575 $commandStr .= $offlineConf['logDirectory'] . $offlineConf['pathDelimiterStr'] . $offlineConf['logDefaultFileName'] . ' 2>&1'; 576 log_msg(__LINE__, $commandStr); 577 if ($offlineConf['offlineMode'] != 'dryrun') system($commandStr); 578 } 579} 580 581 582/** 583 * Processes the directory in which pages are stored. 584 * 585 * First, the page directory tree is traversed and all files and subdirectories 586 * are stored in the global array $offlineConf['pageDirectoriesArr']. 587 * In the second step this array is walked through and the according directory 588 * structure is created in the temporary pages directory. 589 * (Subdirectories are created by DokuWiki if wiki pages are arranged in name 590 * spaces.) 591 * Finally, each wiki page is downloaded and saved in the temp directory. 592 * 593 */ 594function process_media_files() { 595 global $offlineConf; 596 597 log_msg(__LINE__, 'Processing media files ...'); 598 retrieve_media_tree($offlineConf['absolutePathToMediaDirStr']); 599 log_msg(__LINE__, '--' . join("\n--", array_keys($offlineConf['mediaDirectoriesArr']))); 600 601 foreach ($offlineConf['mediaDirectoriesArr'] as $currentDirStr => $count) { 602 create_directory($offlineConf['absolutePathToTmpSessionDirStr'] . $offlineConf['pathDelimiterStr'] . $currentDirStr, 0777); 603 } 604 605 $currentSourceAndTargetArr = array(); 606 foreach ($offlineConf['mediaFilesSourceAndTargetArr'] as $currentSourceAndTargetArr) { 607 if ($offlineConf['offlineMode'] != 'dryrun') copy_file($currentSourceAndTargetArr[0], $currentSourceAndTargetArr[1]); 608 } 609 unset($offlineConf['mediaFilesSourceAndTargetArr']); 610} 611 612 613/** 614 * Expects an absolute path to start from and traverses the whole directory. 615 * All subdirectories and files are returned in a nested array. 616 * 617 * Credits: 618 * This routine is based on the function 'retrieveTree' which is explained in 619 * the article 'Recursion In PHP: Tapping Unharnessed Power' by Robert Peake 620 * published on November 18, 2004, on the website 621 * http://devzone.zend.com/node/view/id/1235. Discovered: 12/20/2008. 622 * Thank you, Robert! 623 * 624 */ 625function retrieve_media_tree($pathStr) { 626 global $offlineConf; 627 628 if ($dir = @opendir($pathStr)) { 629 while (($element = readdir($dir)) !== false) { 630 if (is_dir($pathStr . $offlineConf['pathDelimiterStr'] . $element) && $element != "." && $element != "..") { 631 $array[$element] = retrieve_media_tree($pathStr . $offlineConf['pathDelimiterStr'] . $element); 632 } elseif (($element != ".") && ($element != "..")) { 633 $array[] = $pathStr . $offlineConf['pathDelimiterStr'] . $element; 634 $currentMediaSubDirStr = str_replace($offlineConf['absolutePathToDataDirStr'] . $offlineConf['pathDelimiterStr'], '', $pathStr); 635 $tmpSourceFilenameWithPathStr = $pathStr . $offlineConf['pathDelimiterStr'] . $element; 636 $tmpTargetFilenameWithPathStr = $offlineConf['absolutePathToTmpSessionDirStr'] . $offlineConf['pathDelimiterStr'] . $currentMediaSubDirStr . $offlineConf['pathDelimiterStr'] . $element; 637 $offlineConf['mediaFilesSourceAndTargetArr'][] = array($tmpSourceFilenameWithPathStr, $tmpTargetFilenameWithPathStr); 638 $offlineConf['mediaDirectoriesArr'][$currentMediaSubDirStr]++; 639 } 640 } 641 closedir($dir); 642 } 643 644 return (isset($array) ? $array : false); 645} 646 647 648/** 649 * Returns the head of a standard DokuWiki offline HTML page. 650 * 651 */ 652function assemble_html_head($relativePathStr) { 653 $headStr = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 654 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 655<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" 656 lang="de" dir="ltr"> 657 658<head> 659<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 660<title>DokuWiki offline version</title> 661<!-- metadata --> 662<meta name="generator" content="Offline" /> 663<meta name="version" content="Offline 0.1" /> 664<!-- style sheet links --> 665<link rel="stylesheet" media="all" type="text/css" href="' . $relativePathStr . 'css/all.css" /> 666<link rel="stylesheet" media="screen" type="text/css" href="' . $relativePathStr . 'css/screen.css" /> 667<link rel="stylesheet" media="print" type="text/css" href="' . $relativePathStr . 'css/print.css" /> 668 669</head> 670<body> 671<div class="dokuwiki export">'; 672 673 return $headStr; 674} 675 676 677/** 678 * Builds indices for easier navigation and writes them to file. 679 * 680 */ 681function process_indices() { 682 global $offlineConf; 683 684 log_msg(__LINE__, 'Creating index files ...'); 685 686 // Create index page to start browsing the offline version: 687 create_index_page(); 688 689 $absolutePathToIndexDirStr = $offlineConf['absolutePathToTmpSessionDirStr'] . $offlineConf['pathDelimiterStr'] . 'index'; 690 create_directory($absolutePathToIndexDirStr, 0755); 691 692 // Create alphabetical index: 693 $indexAlphabeticalStr = build_alphabetical_index(); 694 write_file($absolutePathToIndexDirStr . $offlineConf['pathDelimiterStr'] . 'alphabetical.html', $indexAlphabeticalStr); 695} 696 697 698/** 699 * Creates a file called 'index.html' within the temporary directory. 700 * The index file contains a link to the start page off the offline wiki. 701 * 702 */ 703function create_index_page($nameOfWikiStartPageStr = 'start') { 704 global $conf; 705 global $offlineConf; 706 707 log_msg(__LINE__, 'Creating index file ...'); 708 $pageStr = assemble_html_head(''); 709 $pageStr .= ' 710 <h1>' . $conf['title'] . ' - Offline Version</h1> 711 <div class="level2"> 712 <p><a href="pages/' . $nameOfWikiStartPageStr . '.html" class="wikilink1" title="Start">Start</a></p> 713 <p><a href="index/alphabetical.html" class="wikilink1" title="Start">Index (alphabetisch)</a></p> 714 </div> 715</div> 716</body> 717</html> 718'; 719 720 write_file($offlineConf['absolutePathToTmpSessionDirStr'] . $offlineConf['pathDelimiterStr'] . 'index.html', $pageStr); 721} 722 723 724/** 725 * Build alphabetical index. 726 * 727 */ 728function build_alphabetical_index() { 729 global $offlineConf; 730 731 $navigationLettersArr = array(); 732 $htmlHeadStr = assemble_html_head('../'); 733 $indexStr = '<div>'; 734 735 ksort($offlineConf['indexAlphabeticalArr']); 736 $previousFirstChar = ''; 737 foreach($offlineConf['indexAlphabeticalArr'] as $pagenameStr => $relativePathToPageStr) { 738 $currentFirstChar = substr($pagenameStr, 0, 1); 739 if ($currentFirstChar != $previousFirstChar) { 740 $indexStr .= '</div><a name="' . strtolower($currentFirstChar) . '"></a><h2>' . strtoupper($currentFirstChar) . '</h2><div class="level2">'; 741 $navigationLettersArr[strtolower($currentFirstChar)] = 1; 742 } 743 744 $indexStr .= '<a href="../' . $relativePathToPageStr . '" class="wikilink1">' . str_replace('.txt', '', $pagenameStr) . '</a><br />'; 745 $previousFirstChar = $currentFirstChar; 746 } 747 $indexStr .= '</div>'; 748 749 for ($c = 97; $c < 123; $c++) { 750 $currentChar = chr($c); 751 if ($navigationLettersArr[$currentChar] == 1) { 752 $navigationBarStr .= '<a href="#' . $currentChar . '">' . strtoupper($currentChar) . '</a> '; 753 } else { 754 $navigationBarStr .= strtoupper($currentChar) . ' '; 755 } 756 } 757 $navigationBarStr = '<p>' . $navigationBarStr . '</p>'; 758 759 $linkToMainIndexStr = '<p><a href="../index.html" class="wikilink1" title="Back to main index">Back to main index</a></p>'; 760 unset($offlineConf['indexAlphabeticalArr']); 761 762 return $htmlHeadStr . '<h1>Alphabetical Index</h1><div class="level2">' . $navigationBarStr . $linkToMainIndexStr . '</div>' . $indexStr . '<br /><br /><hr /><div class="level2">' . $linkToMainIndexStr . '</div>'; 763} 764 765 766?>