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&amp;t=default';
316	$cssFilesArr['screen'] = '/lib/exe/css.php?t=default';
317	$cssFilesArr['print']  = '/lib/exe/css.php?s=print&amp;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>&nbsp;';
753		} else {
754			$navigationBarStr .= strtoupper($currentChar) . '&nbsp;';
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?>