X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=lib%2Ffile.php;h=b90a2f8678de5b4eaf8b5801f7a14fbc90dcb7dd;hb=ef13260aa62b4b4d6d7da5f9c85a568eee44aa24;hp=2f81dc551f772e6cad421418982f98d6ac6b2bdd;hpb=8600618e5717f90165a9ddfd9fe2756b7cb8060e;p=pukiwiki%2Fpukiwiki.git diff --git a/lib/file.php b/lib/file.php index 2f81dc5..b90a2f8 100644 --- a/lib/file.php +++ b/lib/file.php @@ -1,14 +1,69 @@ $replace) - $str = preg_replace('/' . $rule . '/', $replace, $str); + // Replace with $str_rules + foreach ($str_rules as $pattern => $replacement) + $line = preg_replace('/' . $pattern . '/', $replacement, $line); // Adding fixed anchor into headings if ($fixed_heading_anchor && - preg_match('/^(\*{1,3}(.(?!\[#[A-Za-z][\w-]+\]))+)$/', $str, $matches)) - { - // Generate ID: - // A random alphabetic letter + 7 letters of random strings from md() - $anchor = chr(mt_rand(ord('a'), ord('z'))) . - substr(md5(uniqid(substr($matches[1], 0, 100), 1)), mt_rand(0, 24), 7); - $str = rtrim($matches[1]) . ' [#' . $anchor . ']'; + preg_match('/^(\*{1,3}.*?)(?:\[#([A-Za-z][\w-]*)\]\s*)?$/', $line, $matches) && + (! isset($matches[2]) || $matches[2] == '')) { + // Generate unique id + $anchor = generate_fixed_heading_anchor_id($matches[1]); + $line = rtrim($matches[1]) . ' [#' . $anchor . ']'; } - $retvars[] = $str; } - return join("\n", $retvars); + // Multiline part has no stopper + if (! PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK && + $modify === FALSE && $multiline != 0) + $lines[] = str_repeat('}', $multiline); + + return implode("\n", $lines); +} + +function add_author_info($wikitext) +{ + global $auth_user, $auth_user_fullname; + $author = preg_replace('/"/', '', $auth_user); + $fullname = $auth_user_fullname; + if (!$fullname && $author) { + // Fullname is empty, use $author as its fullname + $fullname = preg_replace('/^[^:]*:/', '', $author); + } + $displayname = preg_replace('/"/', '', $fullname); + $user_prefix = get_auth_user_prefix(); + $author_text = sprintf('#author("%s","%s","%s")', + get_date_atom(UTIME + LOCALZONE), + ($author ? $user_prefix . $author : ''), + $displayname) . "\n"; + return $author_text . $wikitext; +} + +function remove_author_info($wikitext) +{ + return preg_replace('/^\s*#author\([^\n]*(\n|$)/m', '', $wikitext); +} + +function get_date_atom($timestamp) +{ + // Compatible with DATE_ATOM format + // return date(DATE_ATOM, $timestamp); + $zmin = abs(LOCALZONE / 60); + return date('Y-m-d\TH:i:s', $timestamp) . sprintf('%s%02d:%02d', + (LOCALZONE < 0 ? '-' : '+') , $zmin / 60, $zmin % 60); +} + +// Generate ID +function generate_fixed_heading_anchor_id($seed) +{ + // A random alphabetic letter + 7 letters of random strings from md() + return chr(mt_rand(ord('a'), ord('z'))) . + substr(md5(uniqid(substr($seed, 0, 100), TRUE)), + mt_rand(0, 24), 7); +} + +// Read top N lines as an array +// (Use PHP file() function if you want to get ALL lines) +function file_head($file, $count = 1, $lock = TRUE, $buffer = 8192) +{ + $array = array(); + + $fp = @fopen($file, 'r'); + if ($fp === FALSE) return FALSE; + set_file_buffer($fp, 0); + if ($lock) flock($fp, LOCK_SH); + rewind($fp); + $index = 0; + while (! feof($fp)) { + $line = fgets($fp, $buffer); + if ($line != FALSE) $array[] = $line; + if (++$index >= $count) break; + } + if ($lock) flock($fp, LOCK_UN); + if (! fclose($fp)) return FALSE; + + return $array; } // Output to a file -function file_write($dir, $page, $str, $notimestamp = FALSE) +function file_write($dir, $page, $str, $notimestamp = FALSE, $is_delete = FALSE) { - global $update_exec, $_msg_invalidiwn; - global $notify, $notify_diff_only, $notify_to, $notify_subject, $notify_header; - global $smtp_server, $smtp_auth; + global $_msg_invalidiwn, $notify, $notify_diff_only, $notify_subject; global $whatsdeleted, $maxshow_deleted; if (PKWK_READONLY) return; // Do nothing + if ($dir != DATA_DIR && $dir != DIFF_DIR) die('file_write(): Invalid directory'); - if (! is_pagename($page)) - die_message(str_replace('$1', htmlspecialchars($page), - str_replace('$2', 'WikiName', $_msg_invalidiwn))); + $page = strip_bracket($page); + $file = $dir . encode($page) . '.txt'; + $file_exists = file_exists($file); - $page = strip_bracket($page); - $timestamp = FALSE; - $file = $dir . encode($page) . '.txt'; + // ---- + // Delete? - if ($dir == DATA_DIR && $str == '' && file_exists($file)) { - unlink($file); - add_recent($page, $whatsdeleted, '', $maxshow_deleted); // RecentDeleted - } + if ($dir == DATA_DIR && $is_delete) { + // Page deletion + if (! $file_exists) return; // Ignore null posting for DATA_DIR - if ($str != '') { - $str = preg_replace('/' . "\r" . '/', '', $str); - $str = rtrim($str) . "\n"; + // Update RecentDeleted (Add the $page) + add_recent($page, $whatsdeleted, '', $maxshow_deleted); - if ($notimestamp && file_exists($file)) - $timestamp = filemtime($file) - LOCALZONE; + // Remove the page + unlink($file); - $fp = fopen($file, 'w') or - die_message('Cannot write page file or diff file or other ' . - htmlspecialchars($page) . - '
Maybe permission is not writable or filename is too long'); + // Update RecentDeleted, and remove the page from RecentChanges + lastmodified_add($whatsdeleted, $page); - set_file_buffer($fp, 0); - flock($fp, LOCK_EX); - rewind($fp); - fputs($fp, $str); - flock($fp, LOCK_UN); - fclose($fp); + // Clear is_page() cache + is_page($page, TRUE); + + return; - // With safe_mode, touch() always check UID - if ($timestamp) touch($file, $timestamp + LOCALZONE); + } else if ($dir == DIFF_DIR && $str === " \n") { + return; // Ignore null posting for DIFF_DIR } - // Clear is_page() cache - is_page($page, TRUE); + // ---- + // File replacement (Edit) - if (! $timestamp && $dir == DATA_DIR) - put_lastmodified(); + if (! is_pagename($page)) + die_message(str_replace('$1', htmlsc($page), + str_replace('$2', 'WikiName', $_msg_invalidiwn))); - // Execute $update_exec here - if ($update_exec && $dir == DATA_DIR) - system($update_exec . ' > /dev/null &'); + $str = rtrim(preg_replace('/' . "\r" . '/', '', $str)) . "\n"; + $timestamp = ($file_exists && $notimestamp) ? filemtime($file) : FALSE; - if ($notify && $dir == DIFF_DIR) { - if ($notify_diff_only) $str = preg_replace('/^[^-+].*\n/m', '', $str); - $str .= "\n" . - str_repeat('-', 30) . "\n" . - 'URI: ' . get_script_uri() . '?' . rawurlencode($page) . "\n" . - 'REMOTE_ADDR: ' . $_SERVER['REMOTE_ADDR'] . "\n"; + $fp = fopen($file, 'a') or die('fopen() failed: ' . + htmlsc(basename($dir) . '/' . encode($page) . '.txt') . + '
' . "\n" . + 'Maybe permission is not writable or filename is too long'); + set_file_buffer($fp, 0); + flock($fp, LOCK_EX); + ftruncate($fp, 0); + rewind($fp); + fputs($fp, $str); + flock($fp, LOCK_UN); + fclose($fp); + + if ($timestamp) pkwk_touch_file($file, $timestamp); + + // Optional actions + if ($dir == DATA_DIR) { + // Update RecentChanges (Add or renew the $page) + if ($timestamp === FALSE) lastmodified_add($page); - $subject = str_replace('$page', $page, $notify_subject); - ini_set('SMTP', $smtp_server); - mb_language(LANG); + // Command execution per update + if (defined('PKWK_UPDATE_EXEC') && PKWK_UPDATE_EXEC) + system(PKWK_UPDATE_EXEC . ' > /dev/null &'); - if ($smtp_auth) pop_before_smtp(); - mb_send_mail($notify_to, $subject, $str, $notify_header); + } else if ($dir == DIFF_DIR && $notify) { + if ($notify_diff_only) $str = preg_replace('/^[^-+].*\n/m', '', $str); + $footer['ACTION'] = 'Page update'; + $footer['PAGE'] = & $page; + $footer['URI'] = get_script_uri() . '?' . pagename_urlencode($page); + $footer['USER_AGENT'] = TRUE; + $footer['REMOTE_ADDR'] = TRUE; + pkwk_mail_notify($notify_subject, $str, $footer) or + die('pkwk_mail_notify(): Failed'); } + + is_page($page, TRUE); // Clear is_page() cache } // Update RecentDeleted function add_recent($page, $recentpage, $subject = '', $limit = 0) { - if (PKWK_READONLY || $limit == 0 || $page == '' || $recentpage == '') return; + if (PKWK_READONLY || $limit == 0 || $page == '' || $recentpage == '' || + check_non_list($page)) return; // Load $lines = $matches = array(); @@ -173,7 +335,7 @@ function add_recent($page, $recentpage, $subject = '', $limit = 0) // Add array_unshift($lines, '-' . format_date(UTIME) . ' - ' . $_page . - htmlspecialchars($subject) . "\n"); + htmlsc($subject) . "\n"); // Get latest $limit reports $lines = array_splice($lines, 0, $limit); @@ -181,7 +343,7 @@ function add_recent($page, $recentpage, $subject = '', $limit = 0) // Update $fp = fopen(get_filename($recentpage), 'w') or die_message('Cannot write page file ' . - htmlspecialchars($recentpage) . + htmlsc($recentpage) . '
Maybe permission is not writable or filename is too long'); set_file_buffer($fp, 0); flock($fp, LOCK_EX); @@ -193,31 +355,132 @@ function add_recent($page, $recentpage, $subject = '', $limit = 0) fclose($fp); } -// Update RecentChanges +// Update PKWK_MAXSHOW_CACHE itself (Add or renew about the $page) (Light) +// Use without $autolink +function lastmodified_add($update = '', $remove = '') +{ + global $maxshow, $whatsnew, $autolink; + + // AutoLink implimentation needs everything, for now + if ($autolink) { + put_lastmodified(); // Try to (re)create ALL + return; + } + + if (($update == '' || check_non_list($update)) && $remove == '') + return; // No need + + $file = CACHE_DIR . PKWK_MAXSHOW_CACHE; + if (! file_exists($file)) { + put_lastmodified(); // Try to (re)create ALL + return; + } + + // Open + pkwk_touch_file($file); + $fp = fopen($file, 'r+') or + die_message('Cannot open ' . 'CACHE_DIR/' . PKWK_MAXSHOW_CACHE); + set_file_buffer($fp, 0); + flock($fp, LOCK_EX); + + // Read (keep the order of the lines) + $recent_pages = $matches = array(); + foreach(file_head($file, $maxshow + PKWK_MAXSHOW_ALLOWANCE, FALSE) as $line) + if (preg_match('/^([0-9]+)\t(.+)/', $line, $matches)) + $recent_pages[$matches[2]] = $matches[1]; + + // Remove if it exists inside + if (isset($recent_pages[$update])) unset($recent_pages[$update]); + if (isset($recent_pages[$remove])) unset($recent_pages[$remove]); + + // Add to the top: like array_unshift() + if ($update != '') + $recent_pages = array($update => get_filetime($update)) + $recent_pages; + + // Check + $abort = count($recent_pages) < $maxshow; + + if (! $abort) { + // Write + ftruncate($fp, 0); + rewind($fp); + foreach ($recent_pages as $_page=>$time) + fputs($fp, $time . "\t" . $_page . "\n"); + } + + flock($fp, LOCK_UN); + fclose($fp); + + if ($abort) { + put_lastmodified(); // Try to (re)create ALL + return; + } + + + + // ---- + // Update the page 'RecentChanges' + + $recent_pages = array_splice($recent_pages, 0, $maxshow); + $file = get_filename($whatsnew); + + // Open + pkwk_touch_file($file); + $fp = fopen($file, 'r+') or + die_message('Cannot open ' . htmlsc($whatsnew)); + set_file_buffer($fp, 0); + flock($fp, LOCK_EX); + + // Recreate + ftruncate($fp, 0); + rewind($fp); + foreach ($recent_pages as $_page=>$time) + fputs($fp, '-' . htmlsc(format_date($time)) . + ' - ' . '[[' . htmlsc($_page) . ']]' . "\n"); + fputs($fp, '#norelated' . "\n"); // :) + + flock($fp, LOCK_UN); + fclose($fp); +} + +// Re-create PKWK_MAXSHOW_CACHE (Heavy) function put_lastmodified() { - global $maxshow, $whatsnew, $non_list, $autolink; + global $maxshow, $whatsnew, $autolink; if (PKWK_READONLY) return; // Do nothing + // Get WHOLE page list $pages = get_existpages(); + + // Check ALL filetime $recent_pages = array(); - $non_list_pattern = '/' . $non_list . '/'; foreach($pages as $page) - if ($page != $whatsnew && ! preg_match($non_list_pattern, $page)) + if ($page !== $whatsnew && ! check_non_list($page)) $recent_pages[$page] = get_filetime($page); // Sort decending order of last-modification date arsort($recent_pages, SORT_NUMERIC); - // Create recent.dat (for recent.inc.php) - $fp = fopen(CACHE_DIR . 'recent.dat', 'w') or - die_message('Cannot write cache file ' . - CACHE_DIR . 'recent.dat' . - '
Maybe permission is not writable or filename is too long'); + // Cut unused lines + // BugTrack2/179: array_splice() will break integer keys in hashtable + $count = $maxshow + PKWK_MAXSHOW_ALLOWANCE; + $_recent = array(); + foreach($recent_pages as $key=>$value) { + unset($recent_pages[$key]); + $_recent[$key] = $value; + if (--$count < 1) break; + } + $recent_pages = & $_recent; + // Re-create PKWK_MAXSHOW_CACHE + $file = CACHE_DIR . PKWK_MAXSHOW_CACHE; + pkwk_touch_file($file); + $fp = fopen($file, 'r+') or + die_message('Cannot open' . 'CACHE_DIR/' . PKWK_MAXSHOW_CACHE); set_file_buffer($fp, 0); flock($fp, LOCK_EX); + ftruncate($fp, 0); rewind($fp); foreach ($recent_pages as $page=>$time) fputs($fp, $time . "\t" . $page . "\n"); @@ -225,18 +488,18 @@ function put_lastmodified() fclose($fp); // Create RecentChanges - $fp = fopen(get_filename($whatsnew), 'w') or - die_message('Cannot write page file ' . - htmlspecialchars($whatsnew) . - '
Maybe permission is not writable or filename is too long'); - + $file = get_filename($whatsnew); + pkwk_touch_file($file); + $fp = fopen($file, 'r+') or + die_message('Cannot open ' . htmlsc($whatsnew)); set_file_buffer($fp, 0); flock($fp, LOCK_EX); + ftruncate($fp, 0); rewind($fp); - foreach (array_splice(array_keys($recent_pages), 0, $maxshow) as $page) { + foreach (array_keys($recent_pages) as $page) { $time = $recent_pages[$page]; - $s_lastmod = htmlspecialchars(format_date($time)); - $s_page = htmlspecialchars($page); + $s_lastmod = htmlsc(format_date($time)); + $s_page = htmlsc($page); fputs($fp, '-' . $s_lastmod . ' - [[' . $s_page . ']]' . "\n"); } fputs($fp, '#norelated' . "\n"); // :) @@ -248,12 +511,13 @@ function put_lastmodified() list($pattern, $pattern_a, $forceignorelist) = get_autolink_pattern($pages); - $fp = fopen(CACHE_DIR . 'autolink.dat', 'w') or - die_message('Cannot write autolink file ' . - CACHE_DIR . '/autolink.dat' . - '
Maybe permission is not writable'); + $file = CACHE_DIR . PKWK_AUTOLINK_REGEX_CACHE; + pkwk_touch_file($file); + $fp = fopen($file, 'r+') or + die_message('Cannot open ' . 'CACHE_DIR/' . PKWK_AUTOLINK_REGEX_CACHE); set_file_buffer($fp, 0); flock($fp, LOCK_EX); + ftruncate($fp, 0); rewind($fp); fputs($fp, $pattern . "\n"); fputs($fp, $pattern_a . "\n"); @@ -263,7 +527,7 @@ function put_lastmodified() } } -// Get elapsed date of the pate +// Get elapsed date of the page function get_pg_passage($page, $sw = TRUE) { global $show_passage; @@ -287,21 +551,36 @@ function header_lastmod($page = NULL) } } +// Get a list of encoded files (must specify a directory and a suffix) +function get_existfiles($dir = DATA_DIR, $ext = '.txt') +{ + $aryret = array(); + $pattern = '/^(?:[0-9A-F]{2})+' . preg_quote($ext, '/') . '$/'; + + $dp = @opendir($dir) or die_message($dir . ' is not found or not readable.'); + while (($file = readdir($dp)) !== FALSE) { + if (preg_match($pattern, $file)) { + $aryret[] = $dir . $file; + } + } + closedir($dp); + + return $aryret; +} + // Get a page list of this wiki function get_existpages($dir = DATA_DIR, $ext = '.txt') { $aryret = array(); + $pattern = '/^((?:[0-9A-F]{2})+)' . preg_quote($ext, '/') . '$/'; - $pattern = '((?:[0-9A-F]{2})+)'; - if ($ext != '') $ext = preg_quote($ext, '/'); - $pattern = '/^' . $pattern . $ext . '$/'; - - $dp = @opendir($dir) or - die_message($dir . ' is not found or not readable.'); + $dp = @opendir($dir) or die_message($dir . ' is not found or not readable.'); $matches = array(); - while ($file = readdir($dp)) - if (preg_match($pattern, $file, $matches)) + while (($file = readdir($dp)) !== FALSE) { + if (preg_match($pattern, $file, $matches)) { $aryret[$file] = decode($matches[1]); + } + } closedir($dp); return $aryret; @@ -355,7 +634,7 @@ function get_readings() if(! file_exists($pagereading_chasen_path)) die_message('ChaSen not found: ' . $pagereading_chasen_path); - $tmpfname = tempnam(CACHE_DIR, 'PageReading'); + $tmpfname = tempnam(realpath(CACHE_DIR), 'PageReading'); $fp = fopen($tmpfname, 'w') or die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n"); foreach ($readings as $page => $reading) { @@ -391,7 +670,7 @@ function get_readings() if(! file_exists($pagereading_kakasi_path)) die_message('KAKASI not found: ' . $pagereading_kakasi_path); - $tmpfname = tempnam(CACHE_DIR, 'PageReading'); + $tmpfname = tempnam(realpath(CACHE_DIR), 'PageReading'); $fp = fopen($tmpfname, 'w') or die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n"); foreach ($readings as $page => $reading) { @@ -450,7 +729,7 @@ function get_readings() if($unknownPage || $deletedPage) { - asort($readings); // Sort by pronouncing(alphabetical/reading) order + asort($readings, SORT_STRING); // Sort by pronouncing(alphabetical/reading) order $body = ''; foreach ($readings as $page => $reading) $body .= '-[[' . $page . ']] ' . $reading . "\n"; @@ -468,19 +747,6 @@ function get_readings() return $readings; } -// Get a list of encoded files (must specify a directory and a suffix) -function get_existfiles($dir, $ext) -{ - $pattern = '/^(?:[0-9A-F]{2})+' . preg_quote($ext, '/') . '$/'; - $aryret = array(); - $dp = @opendir($dir) or die_message($dir . ' is not found or not readable.'); - while ($file = readdir($dp)) - if (preg_match($pattern, $file)) - $aryret[] = $dir . $file; - closedir($dp); - return $aryret; -} - // Get a list of related pages of the page function links_get_related($page) { @@ -490,7 +756,7 @@ function links_get_related($page) if (isset($links[$page])) return $links[$page]; // If possible, merge related pages generated by make_link() - $links[$page] = ($page == $vars['page']) ? $related : array(); + $links[$page] = ($page === $vars['page']) ? $related : array(); // Get repated pages from DB $links[$page] += links_get_related_db($vars['page']); @@ -516,12 +782,12 @@ function pkwk_chown($filename, $preserve_time = TRUE) $lockfile = CACHE_DIR . 'pkwk_chown.lock'; $flock = fopen($lockfile, 'a') or die('pkwk_chown(): fopen() failed for: CACHEDIR/' . - basename(htmlspecialchars($lockfile))); + basename(htmlsc($lockfile))); flock($flock, LOCK_EX) or die('pkwk_chown(): flock() failed for lock'); // Check owner $stat = stat($filename) or - die('pkwk_chown(): stat() failed for: ' . basename(htmlspecialchars($filename))); + die('pkwk_chown(): stat() failed for: ' . basename(htmlsc($filename))); if ($stat[4] === $php_uid) { // NOTE: Windows always here $result = TRUE; // Seems the same UID. Nothing to do @@ -532,17 +798,22 @@ function pkwk_chown($filename, $preserve_time = TRUE) // NOTE: Not 'r+'. Don't check write permission here $ffile = fopen($filename, 'r') or die('pkwk_chown(): fopen() failed for: ' . - basename(htmlspecialchars($filename))); + basename(htmlsc($filename))); // Try to chown by re-creating files - // NOTE: @unlink() before rename() is for Windows but here's for Unix only + // NOTE: + // * touch() before copy() is for 'rw-r--r--' instead of 'rwxr-xr-x' (with umask 022). + // * (PHP 4 < PHP 4.2.0) touch() with the third argument is not implemented and retuns NULL and Warn. + // * @unlink() before rename() is for Windows but here's for Unix only flock($ffile, LOCK_EX) or die('pkwk_chown(): flock() failed'); - $result = copy($filename, $tmp) && - ($preserve_time ? touch($tmp, $stat[9], $stat[8]) : TRUE) && + $result = touch($tmp) && copy($filename, $tmp) && + ($preserve_time ? (touch($tmp, $stat[9], $stat[8]) || touch($tmp, $stat[9])) : TRUE) && rename($tmp, $filename); flock($ffile, LOCK_UN) or die('pkwk_chown(): flock() failed'); fclose($ffile) or die('pkwk_chown(): fclose() failed'); + + if ($result === FALSE) @unlink($tmp); } // Unlock for pkwk_chown() @@ -556,7 +827,7 @@ function pkwk_chown($filename, $preserve_time = TRUE) function pkwk_touch_file($filename, $time = FALSE, $atime = FALSE) { // Is the owner incorrected and unable to correct? - if (pkwk_chown($filename)) { + if (! file_exists($filename) || pkwk_chown($filename)) { if ($time === FALSE) { $result = touch($filename); } else if ($atime === FALSE) { @@ -567,7 +838,22 @@ function pkwk_touch_file($filename, $time = FALSE, $atime = FALSE) return $result; } else { die('pkwk_touch_file(): Invalid UID and (not writable for the directory or not a flie): ' . - htmlspecialchars(basename($filename))); + htmlsc(basename($filename))); } } -?> + +/** + * Lock-enabled file_get_contents + * + * Require: PHP5+ + */ +function pkwk_file_get_contents($filename) { + if (! file_exists($filename)) { + return false; + } + $fp = fopen($filename, 'rb'); + flock($fp, LOCK_SH); + $file = file_get_contents($filename); + flock($fp, LOCK_UN); + return $file; +}