OSDN Git Service

BugTrack2/62:
[pukiwiki/pukiwiki.git] / lib / file.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // $Id: file.php,v 1.95 2011/01/25 15:01:01 henoheno Exp $
4 // Copyright (C)
5 //   2002-2006 PukiWiki Developers Team
6 //   2001-2002 Originally written by yu-ji
7 // License: GPL v2 or (at your option) any later version
8 //
9 // File related functions
10
11 // RecentChanges
12 define('PKWK_MAXSHOW_ALLOWANCE', 10);
13 define('PKWK_MAXSHOW_CACHE', 'recent.dat');
14
15 // AutoLink
16 define('PKWK_AUTOLINK_REGEX_CACHE', 'autolink.dat');
17
18 // Get source(wiki text) data of the page
19 function get_source($page = NULL, $lock = TRUE, $join = FALSE)
20 {
21         $result = $join ? '' : array();
22
23         if (is_page($page)) {
24                 $path  = get_filename($page);
25
26                 if ($lock) {
27                         $fp = @fopen($path, 'r');
28                         if ($fp == FALSE) return $result;
29                         flock($fp, LOCK_SH);
30                 }
31
32                 if ($join) {
33                         // Returns a value
34                         $result = str_replace("\r", '', fread($fp, filesize($path)));
35                 } else {
36                         // Returns an array
37                         // Removing line-feeds: Because file() doesn't remove them.
38                         $result = str_replace("\r", '', file($path));
39                 }
40
41                 if ($lock) {
42                         flock($fp, LOCK_UN);
43                         @fclose($fp);
44                 }
45         }
46
47         return $result;
48 }
49
50 // Get last-modified filetime of the page
51 function get_filetime($page)
52 {
53         return is_page($page) ? filemtime(get_filename($page)) - LOCALZONE : 0;
54 }
55
56 // Get physical file name of the page
57 function get_filename($page)
58 {
59         return DATA_DIR . encode($page) . '.txt';
60 }
61
62 // Put a data(wiki text) into a physical file(diff, backup, text)
63 function page_write($page, $postdata, $notimestamp = FALSE)
64 {
65         if (PKWK_READONLY) return; // Do nothing
66
67         $postdata = make_str_rules($postdata);
68
69         // Create and write diff
70         $oldpostdata = is_page($page) ? join('', get_source($page)) : '';
71         $diffdata    = do_diff($oldpostdata, $postdata);
72         file_write(DIFF_DIR, $page, $diffdata);
73
74         // Create backup
75         make_backup($page, $postdata == ''); // Is $postdata null?
76
77         // Create wiki text
78         file_write(DATA_DIR, $page, $postdata, $notimestamp);
79
80         links_update($page);
81 }
82
83 // Modify original text with user-defined / system-defined rules
84 function make_str_rules($source)
85 {
86         global $str_rules, $fixed_heading_anchor;
87
88         $lines = explode("\n", $source);
89         $count = count($lines);
90
91         $modify    = TRUE;
92         $multiline = 0;
93         $matches   = array();
94         for ($i = 0; $i < $count; $i++) {
95                 $line = & $lines[$i]; // Modify directly
96
97                 // Ignore null string and preformatted texts
98                 if ($line == '' || $line{0} == ' ' || $line{0} == "\t") continue;
99
100                 // Modify this line?
101                 if ($modify) {
102                         if (! PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK &&
103                             $multiline == 0 &&
104                             preg_match('/#[^{]*(\{\{+)\s*$/', $line, $matches)) {
105                                 // Multiline convert plugin start
106                                 $modify    = FALSE;
107                                 $multiline = strlen($matches[1]); // Set specific number
108                         }
109                 } else {
110                         if (! PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK &&
111                             $multiline != 0 &&
112                             preg_match('/^\}{' . $multiline . '}\s*$/', $line)) {
113                                 // Multiline convert plugin end
114                                 $modify    = TRUE;
115                                 $multiline = 0;
116                         }
117                 }
118                 if ($modify === FALSE) continue;
119
120                 // Replace with $str_rules
121                 foreach ($str_rules as $pattern => $replacement)
122                         $line = preg_replace('/' . $pattern . '/', $replacement, $line);
123                 
124                 // Adding fixed anchor into headings
125                 if ($fixed_heading_anchor &&
126                     preg_match('/^(\*{1,3}.*?)(?:\[#([A-Za-z][\w-]*)\]\s*)?$/', $line, $matches) &&
127                     (! isset($matches[2]) || $matches[2] == '')) {
128                         // Generate unique id
129                         $anchor = generate_fixed_heading_anchor_id($matches[1]);
130                         $line = rtrim($matches[1]) . ' [#' . $anchor . ']';
131                 }
132         }
133
134         // Multiline part has no stopper
135         if (! PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK &&
136             $modify === FALSE && $multiline != 0)
137                 $lines[] = str_repeat('}', $multiline);
138
139         return implode("\n", $lines);
140 }
141
142 // Generate ID
143 function generate_fixed_heading_anchor_id($seed)
144 {
145         // A random alphabetic letter + 7 letters of random strings from md()
146         return chr(mt_rand(ord('a'), ord('z'))) .
147                 substr(md5(uniqid(substr($seed, 0, 100), TRUE)),
148                 mt_rand(0, 24), 7);
149 }
150
151 // Read top N lines as an array
152 // (Use PHP file() function if you want to get ALL lines)
153 function file_head($file, $count = 1, $lock = TRUE, $buffer = 8192)
154 {
155         $array = array();
156
157         $fp = @fopen($file, 'r');
158         if ($fp === FALSE) return FALSE;
159         set_file_buffer($fp, 0);
160         if ($lock) flock($fp, LOCK_SH);
161         rewind($fp);
162         $index = 0;
163         while (! feof($fp)) {
164                 $line = fgets($fp, $buffer);
165                 if ($line != FALSE) $array[] = $line;
166                 if (++$index >= $count) break;
167         }
168         if ($lock) flock($fp, LOCK_UN);
169         if (! fclose($fp)) return FALSE;
170
171         return $array;
172 }
173
174 // Output to a file
175 function file_write($dir, $page, $str, $notimestamp = FALSE)
176 {
177         global $_msg_invalidiwn, $notify, $notify_diff_only, $notify_subject;
178         global $whatsdeleted, $maxshow_deleted;
179
180         if (PKWK_READONLY) return; // Do nothing
181         if ($dir != DATA_DIR && $dir != DIFF_DIR) die('file_write(): Invalid directory');
182
183         $page = strip_bracket($page);
184         $file = $dir . encode($page) . '.txt';
185         $file_exists = file_exists($file);
186
187         // ----
188         // Delete?
189
190         if ($dir == DATA_DIR && $str === '') {
191                 // Page deletion
192                 if (! $file_exists) return; // Ignore null posting for DATA_DIR
193
194                 // Update RecentDeleted (Add the $page)
195                 add_recent($page, $whatsdeleted, '', $maxshow_deleted);
196
197                 // Remove the page
198                 unlink($file);
199
200                 // Update RecentDeleted, and remove the page from RecentChanges
201                 lastmodified_add($whatsdeleted, $page);
202
203                 // Clear is_page() cache
204                 is_page($page, TRUE);
205
206                 return;
207
208         } else if ($dir == DIFF_DIR && $str === " \n") {
209                 return; // Ignore null posting for DIFF_DIR
210         }
211
212         // ----
213         // File replacement (Edit)
214
215         if (! is_pagename($page))
216                 die_message(str_replace('$1', htmlsc($page),
217                             str_replace('$2', 'WikiName', $_msg_invalidiwn)));
218
219         $str = rtrim(preg_replace('/' . "\r" . '/', '', $str)) . "\n";
220         $timestamp = ($file_exists && $notimestamp) ? filemtime($file) : FALSE;
221
222         $fp = fopen($file, 'a') or die('fopen() failed: ' .
223                 htmlsc(basename($dir) . '/' . encode($page) . '.txt') . 
224                 '<br />' . "\n" .
225                 'Maybe permission is not writable or filename is too long');
226         set_file_buffer($fp, 0);
227         flock($fp, LOCK_EX);
228         ftruncate($fp, 0);
229         rewind($fp);
230         fputs($fp, $str);
231         flock($fp, LOCK_UN);
232         fclose($fp);
233
234         if ($timestamp) pkwk_touch_file($file, $timestamp);
235
236         // Optional actions
237         if ($dir == DATA_DIR) {
238                 // Update RecentChanges (Add or renew the $page)
239                 if ($timestamp === FALSE) lastmodified_add($page);
240
241                 // Command execution per update
242                 if (defined('PKWK_UPDATE_EXEC') && PKWK_UPDATE_EXEC)
243                         system(PKWK_UPDATE_EXEC . ' > /dev/null &');
244
245         } else if ($dir == DIFF_DIR && $notify) {
246                 if ($notify_diff_only) $str = preg_replace('/^[^-+].*\n/m', '', $str);
247                 $footer['ACTION'] = 'Page update';
248                 $footer['PAGE']   = & $page;
249                 $footer['URI']    = get_script_uri() . '?' . rawurlencode($page);
250                 $footer['USER_AGENT']  = TRUE;
251                 $footer['REMOTE_ADDR'] = TRUE;
252                 pkwk_mail_notify($notify_subject, $str, $footer) or
253                         die('pkwk_mail_notify(): Failed');
254         }
255
256         is_page($page, TRUE); // Clear is_page() cache
257 }
258
259 // Update RecentDeleted
260 function add_recent($page, $recentpage, $subject = '', $limit = 0)
261 {
262         if (PKWK_READONLY || $limit == 0 || $page == '' || $recentpage == '' ||
263             check_non_list($page)) return;
264
265         // Load
266         $lines = $matches = array();
267         foreach (get_source($recentpage) as $line)
268                 if (preg_match('/^-(.+) - (\[\[.+\]\])$/', $line, $matches))
269                         $lines[$matches[2]] = $line;
270
271         $_page = '[[' . $page . ']]';
272
273         // Remove a report about the same page
274         if (isset($lines[$_page])) unset($lines[$_page]);
275
276         // Add
277         array_unshift($lines, '-' . format_date(UTIME) . ' - ' . $_page .
278                 htmlsc($subject) . "\n");
279
280         // Get latest $limit reports
281         $lines = array_splice($lines, 0, $limit);
282
283         // Update
284         $fp = fopen(get_filename($recentpage), 'w') or
285                 die_message('Cannot write page file ' .
286                 htmlsc($recentpage) .
287                 '<br />Maybe permission is not writable or filename is too long');
288         set_file_buffer($fp, 0);
289         flock($fp, LOCK_EX);
290         rewind($fp);
291         fputs($fp, '#freeze'    . "\n");
292         fputs($fp, '#norelated' . "\n"); // :)
293         fputs($fp, join('', $lines));
294         flock($fp, LOCK_UN);
295         fclose($fp);
296 }
297
298 // Update PKWK_MAXSHOW_CACHE itself (Add or renew about the $page) (Light)
299 // Use without $autolink
300 function lastmodified_add($update = '', $remove = '')
301 {
302         global $maxshow, $whatsnew, $autolink;
303
304         // AutoLink implimentation needs everything, for now
305         if ($autolink) {
306                 put_lastmodified(); // Try to (re)create ALL
307                 return;
308         }
309
310         if (($update == '' || check_non_list($update)) && $remove == '')
311                 return; // No need
312
313         $file = CACHE_DIR . PKWK_MAXSHOW_CACHE;
314         if (! file_exists($file)) {
315                 put_lastmodified(); // Try to (re)create ALL
316                 return;
317         }
318
319         // Open
320         pkwk_touch_file($file);
321         $fp = fopen($file, 'r+') or
322                 die_message('Cannot open ' . 'CACHE_DIR/' . PKWK_MAXSHOW_CACHE);
323         set_file_buffer($fp, 0);
324         flock($fp, LOCK_EX);
325
326         // Read (keep the order of the lines)
327         $recent_pages = $matches = array();
328         foreach(file_head($file, $maxshow + PKWK_MAXSHOW_ALLOWANCE, FALSE) as $line)
329                 if (preg_match('/^([0-9]+)\t(.+)/', $line, $matches))
330                         $recent_pages[$matches[2]] = $matches[1];
331
332         // Remove if it exists inside
333         if (isset($recent_pages[$update])) unset($recent_pages[$update]);
334         if (isset($recent_pages[$remove])) unset($recent_pages[$remove]);
335
336         // Add to the top: like array_unshift()
337         if ($update != '')
338                 $recent_pages = array($update => get_filetime($update)) + $recent_pages;
339
340         // Check
341         $abort = count($recent_pages) < $maxshow;
342
343         if (! $abort) {
344                 // Write
345                 ftruncate($fp, 0);
346                 rewind($fp);
347                 foreach ($recent_pages as $_page=>$time)
348                         fputs($fp, $time . "\t" . $_page . "\n");
349         }
350
351         flock($fp, LOCK_UN);
352         fclose($fp);
353
354         if ($abort) {
355                 put_lastmodified(); // Try to (re)create ALL
356                 return;
357         }
358
359
360
361         // ----
362         // Update the page 'RecentChanges'
363
364         $recent_pages = array_splice($recent_pages, 0, $maxshow);
365         $file = get_filename($whatsnew);
366
367         // Open
368         pkwk_touch_file($file);
369         $fp = fopen($file, 'r+') or
370                 die_message('Cannot open ' . htmlsc($whatsnew));
371         set_file_buffer($fp, 0);
372         flock($fp, LOCK_EX);
373
374         // Recreate
375         ftruncate($fp, 0);
376         rewind($fp);
377         foreach ($recent_pages as $_page=>$time)
378                 fputs($fp, '-' . htmlsc(format_date($time)) .
379                         ' - ' . '[[' . htmlsc($_page) . ']]' . "\n");
380         fputs($fp, '#norelated' . "\n"); // :)
381
382         flock($fp, LOCK_UN);
383         fclose($fp);
384 }
385
386 // Re-create PKWK_MAXSHOW_CACHE (Heavy)
387 function put_lastmodified()
388 {
389         global $maxshow, $whatsnew, $autolink;
390
391         if (PKWK_READONLY) return; // Do nothing
392
393         // Get WHOLE page list
394         $pages = get_existpages();
395
396         // Check ALL filetime
397         $recent_pages = array();
398         foreach($pages as $page)
399                 if ($page != $whatsnew && ! check_non_list($page))
400                         $recent_pages[$page] = get_filetime($page);
401
402         // Sort decending order of last-modification date
403         arsort($recent_pages, SORT_NUMERIC);
404
405         // Cut unused lines
406         // BugTrack2/179: array_splice() will break integer keys in hashtable
407         $count   = $maxshow + PKWK_MAXSHOW_ALLOWANCE;
408         $_recent = array();
409         foreach($recent_pages as $key=>$value) {
410                 unset($recent_pages[$key]);
411                 $_recent[$key] = $value;
412                 if (--$count < 1) break;
413         }
414         $recent_pages = & $_recent;
415
416         // Re-create PKWK_MAXSHOW_CACHE
417         $file = CACHE_DIR . PKWK_MAXSHOW_CACHE;
418         pkwk_touch_file($file);
419         $fp = fopen($file, 'r+') or
420                 die_message('Cannot open' . 'CACHE_DIR/' . PKWK_MAXSHOW_CACHE);
421         set_file_buffer($fp, 0);
422         flock($fp, LOCK_EX);
423         ftruncate($fp, 0);
424         rewind($fp);
425         foreach ($recent_pages as $page=>$time)
426                 fputs($fp, $time . "\t" . $page . "\n");
427         flock($fp, LOCK_UN);
428         fclose($fp);
429
430         // Create RecentChanges
431         $file = get_filename($whatsnew);
432         pkwk_touch_file($file);
433         $fp = fopen($file, 'r+') or
434                 die_message('Cannot open ' . htmlsc($whatsnew));
435         set_file_buffer($fp, 0);
436         flock($fp, LOCK_EX);
437         ftruncate($fp, 0);
438         rewind($fp);
439         foreach (array_keys($recent_pages) as $page) {
440                 $time      = $recent_pages[$page];
441                 $s_lastmod = htmlsc(format_date($time));
442                 $s_page    = htmlsc($page);
443                 fputs($fp, '-' . $s_lastmod . ' - [[' . $s_page . ']]' . "\n");
444         }
445         fputs($fp, '#norelated' . "\n"); // :)
446         flock($fp, LOCK_UN);
447         fclose($fp);
448
449         // For AutoLink
450         if ($autolink) {
451                 list($pattern, $pattern_a, $forceignorelist) =
452                         get_autolink_pattern($pages);
453
454                 $file = CACHE_DIR . PKWK_AUTOLINK_REGEX_CACHE;
455                 pkwk_touch_file($file);
456                 $fp = fopen($file, 'r+') or
457                         die_message('Cannot open ' . 'CACHE_DIR/' . PKWK_AUTOLINK_REGEX_CACHE);
458                 set_file_buffer($fp, 0);
459                 flock($fp, LOCK_EX);
460                 ftruncate($fp, 0);
461                 rewind($fp);
462                 fputs($fp, $pattern   . "\n");
463                 fputs($fp, $pattern_a . "\n");
464                 fputs($fp, join("\t", $forceignorelist) . "\n");
465                 flock($fp, LOCK_UN);
466                 fclose($fp);
467         }
468 }
469
470 // Get elapsed date of the page
471 function get_pg_passage($page, $sw = TRUE)
472 {
473         global $show_passage;
474         if (! $show_passage) return '';
475
476         $time = get_filetime($page);
477         $pg_passage = ($time != 0) ? get_passage($time) : '';
478
479         return $sw ? '<small>' . $pg_passage . '</small>' : ' ' . $pg_passage;
480 }
481
482 // Last-Modified header
483 function header_lastmod($page = NULL)
484 {
485         global $lastmod;
486
487         if ($lastmod && is_page($page)) {
488                 pkwk_headers_sent();
489                 header('Last-Modified: ' .
490                         date('D, d M Y H:i:s', get_filetime($page)) . ' GMT');
491         }
492 }
493
494 // Get a page list of this wiki
495 function get_existpages($dir = DATA_DIR, $ext = '.txt')
496 {
497         $aryret = array();
498
499         $pattern = '((?:[0-9A-F]{2})+)';
500         if ($ext != '') $ext = preg_quote($ext, '/');
501         $pattern = '/^' . $pattern . $ext . '$/';
502
503         $dp = @opendir($dir) or
504                 die_message($dir . ' is not found or not readable.');
505         $matches = array();
506         while ($file = readdir($dp))
507                 if (preg_match($pattern, $file, $matches))
508                         $aryret[$file] = decode($matches[1]);
509         closedir($dp);
510
511         return $aryret;
512 }
513
514 // Get PageReading(pronounce-annotated) data in an array()
515 function get_readings()
516 {
517         global $pagereading_enable, $pagereading_kanji2kana_converter;
518         global $pagereading_kanji2kana_encoding, $pagereading_chasen_path;
519         global $pagereading_kakasi_path, $pagereading_config_page;
520         global $pagereading_config_dict;
521
522         $pages = get_existpages();
523
524         $readings = array();
525         foreach ($pages as $page) 
526                 $readings[$page] = '';
527
528         $deletedPage = FALSE;
529         $matches = array();
530         foreach (get_source($pagereading_config_page) as $line) {
531                 $line = chop($line);
532                 if(preg_match('/^-\[\[([^]]+)\]\]\s+(.+)$/', $line, $matches)) {
533                         if(isset($readings[$matches[1]])) {
534                                 // This page is not clear how to be pronounced
535                                 $readings[$matches[1]] = $matches[2];
536                         } else {
537                                 // This page seems deleted
538                                 $deletedPage = TRUE;
539                         }
540                 }
541         }
542
543         // If enabled ChaSen/KAKASI execution
544         if($pagereading_enable) {
545
546                 // Check there's non-clear-pronouncing page
547                 $unknownPage = FALSE;
548                 foreach ($readings as $page => $reading) {
549                         if($reading == '') {
550                                 $unknownPage = TRUE;
551                                 break;
552                         }
553                 }
554
555                 // Execute ChaSen/KAKASI, and get annotation
556                 if($unknownPage) {
557                         switch(strtolower($pagereading_kanji2kana_converter)) {
558                         case 'chasen':
559                                 if(! file_exists($pagereading_chasen_path))
560                                         die_message('ChaSen not found: ' . $pagereading_chasen_path);
561
562                                 $tmpfname = tempnam(realpath(CACHE_DIR), 'PageReading');
563                                 $fp = fopen($tmpfname, 'w') or
564                                         die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n");
565                                 foreach ($readings as $page => $reading) {
566                                         if($reading != '') continue;
567                                         fputs($fp, mb_convert_encoding($page . "\n",
568                                                 $pagereading_kanji2kana_encoding, SOURCE_ENCODING));
569                                 }
570                                 fclose($fp);
571
572                                 $chasen = "$pagereading_chasen_path -F %y $tmpfname";
573                                 $fp     = popen($chasen, 'r');
574                                 if($fp === FALSE) {
575                                         unlink($tmpfname);
576                                         die_message('ChaSen execution failed: ' . $chasen);
577                                 }
578                                 foreach ($readings as $page => $reading) {
579                                         if($reading != '') continue;
580
581                                         $line = fgets($fp);
582                                         $line = mb_convert_encoding($line, SOURCE_ENCODING,
583                                                 $pagereading_kanji2kana_encoding);
584                                         $line = chop($line);
585                                         $readings[$page] = $line;
586                                 }
587                                 pclose($fp);
588
589                                 unlink($tmpfname) or
590                                         die_message('Temporary file can not be removed: ' . $tmpfname);
591                                 break;
592
593                         case 'kakasi':  /*FALLTHROUGH*/
594                         case 'kakashi':
595                                 if(! file_exists($pagereading_kakasi_path))
596                                         die_message('KAKASI not found: ' . $pagereading_kakasi_path);
597
598                                 $tmpfname = tempnam(realpath(CACHE_DIR), 'PageReading');
599                                 $fp       = fopen($tmpfname, 'w') or
600                                         die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n");
601                                 foreach ($readings as $page => $reading) {
602                                         if($reading != '') continue;
603                                         fputs($fp, mb_convert_encoding($page . "\n",
604                                                 $pagereading_kanji2kana_encoding, SOURCE_ENCODING));
605                                 }
606                                 fclose($fp);
607
608                                 $kakasi = "$pagereading_kakasi_path -kK -HK -JK < $tmpfname";
609                                 $fp     = popen($kakasi, 'r');
610                                 if($fp === FALSE) {
611                                         unlink($tmpfname);
612                                         die_message('KAKASI execution failed: ' . $kakasi);
613                                 }
614
615                                 foreach ($readings as $page => $reading) {
616                                         if($reading != '') continue;
617
618                                         $line = fgets($fp);
619                                         $line = mb_convert_encoding($line, SOURCE_ENCODING,
620                                                 $pagereading_kanji2kana_encoding);
621                                         $line = chop($line);
622                                         $readings[$page] = $line;
623                                 }
624                                 pclose($fp);
625
626                                 unlink($tmpfname) or
627                                         die_message('Temporary file can not be removed: ' . $tmpfname);
628                                 break;
629
630                         case 'none':
631                                 $patterns = $replacements = $matches = array();
632                                 foreach (get_source($pagereading_config_dict) as $line) {
633                                         $line = chop($line);
634                                         if(preg_match('|^ /([^/]+)/,\s*(.+)$|', $line, $matches)) {
635                                                 $patterns[]     = $matches[1];
636                                                 $replacements[] = $matches[2];
637                                         }
638                                 }
639                                 foreach ($readings as $page => $reading) {
640                                         if($reading != '') continue;
641
642                                         $readings[$page] = $page;
643                                         foreach ($patterns as $no => $pattern)
644                                                 $readings[$page] = mb_convert_kana(mb_ereg_replace($pattern,
645                                                         $replacements[$no], $readings[$page]), 'aKCV');
646                                 }
647                                 break;
648
649                         default:
650                                 die_message('Unknown kanji-kana converter: ' . $pagereading_kanji2kana_converter . '.');
651                                 break;
652                         }
653                 }
654
655                 if($unknownPage || $deletedPage) {
656
657                         asort($readings); // Sort by pronouncing(alphabetical/reading) order
658                         $body = '';
659                         foreach ($readings as $page => $reading)
660                                 $body .= '-[[' . $page . ']] ' . $reading . "\n";
661
662                         page_write($pagereading_config_page, $body);
663                 }
664         }
665
666         // Pages that are not prounouncing-clear, return pagenames of themselves
667         foreach ($pages as $page) {
668                 if($readings[$page] == '')
669                         $readings[$page] = $page;
670         }
671
672         return $readings;
673 }
674
675 // Get a list of encoded files (must specify a directory and a suffix)
676 function get_existfiles($dir, $ext)
677 {
678         $pattern = '/^(?:[0-9A-F]{2})+' . preg_quote($ext, '/') . '$/';
679         $aryret = array();
680         $dp = @opendir($dir) or die_message($dir . ' is not found or not readable.');
681         while ($file = readdir($dp))
682                 if (preg_match($pattern, $file))
683                         $aryret[] = $dir . $file;
684         closedir($dp);
685         return $aryret;
686 }
687
688 // Get a list of related pages of the page
689 function links_get_related($page)
690 {
691         global $vars, $related;
692         static $links = array();
693
694         if (isset($links[$page])) return $links[$page];
695
696         // If possible, merge related pages generated by make_link()
697         $links[$page] = ($page == $vars['page']) ? $related : array();
698
699         // Get repated pages from DB
700         $links[$page] += links_get_related_db($vars['page']);
701
702         return $links[$page];
703 }
704
705 // _If needed_, re-create the file to change/correct ownership into PHP's
706 // NOTE: Not works for Windows
707 function pkwk_chown($filename, $preserve_time = TRUE)
708 {
709         static $php_uid; // PHP's UID
710
711         if (! isset($php_uid)) {
712                 if (extension_loaded('posix')) {
713                         $php_uid = posix_getuid(); // Unix
714                 } else {
715                         $php_uid = 0; // Windows
716                 }
717         }
718
719         // Lock for pkwk_chown()
720         $lockfile = CACHE_DIR . 'pkwk_chown.lock';
721         $flock = fopen($lockfile, 'a') or
722                 die('pkwk_chown(): fopen() failed for: CACHEDIR/' .
723                         basename(htmlsc($lockfile)));
724         flock($flock, LOCK_EX) or die('pkwk_chown(): flock() failed for lock');
725
726         // Check owner
727         $stat = stat($filename) or
728                 die('pkwk_chown(): stat() failed for: '  . basename(htmlsc($filename)));
729         if ($stat[4] === $php_uid) {
730                 // NOTE: Windows always here
731                 $result = TRUE; // Seems the same UID. Nothing to do
732         } else {
733                 $tmp = $filename . '.' . getmypid() . '.tmp';
734
735                 // Lock source $filename to avoid file corruption
736                 // NOTE: Not 'r+'. Don't check write permission here
737                 $ffile = fopen($filename, 'r') or
738                         die('pkwk_chown(): fopen() failed for: ' .
739                                 basename(htmlsc($filename)));
740
741                 // Try to chown by re-creating files
742                 // NOTE:
743                 //   * touch() before copy() is for 'rw-r--r--' instead of 'rwxr-xr-x' (with umask 022).
744                 //   * (PHP 4 < PHP 4.2.0) touch() with the third argument is not implemented and retuns NULL and Warn.
745                 //   * @unlink() before rename() is for Windows but here's for Unix only
746                 flock($ffile, LOCK_EX) or die('pkwk_chown(): flock() failed');
747                 $result = touch($tmp) && copy($filename, $tmp) &&
748                         ($preserve_time ? (touch($tmp, $stat[9], $stat[8]) || touch($tmp, $stat[9])) : TRUE) &&
749                         rename($tmp, $filename);
750                 flock($ffile, LOCK_UN) or die('pkwk_chown(): flock() failed');
751
752                 fclose($ffile) or die('pkwk_chown(): fclose() failed');
753
754                 if ($result === FALSE) @unlink($tmp);
755         }
756
757         // Unlock for pkwk_chown()
758         flock($flock, LOCK_UN) or die('pkwk_chown(): flock() failed for lock');
759         fclose($flock) or die('pkwk_chown(): fclose() failed for lock');
760
761         return $result;
762 }
763
764 // touch() with trying pkwk_chown()
765 function pkwk_touch_file($filename, $time = FALSE, $atime = FALSE)
766 {
767         // Is the owner incorrected and unable to correct?
768         if (! file_exists($filename) || pkwk_chown($filename)) {
769                 if ($time === FALSE) {
770                         $result = touch($filename);
771                 } else if ($atime === FALSE) {
772                         $result = touch($filename, $time);
773                 } else {
774                         $result = touch($filename, $time, $atime);
775                 }
776                 return $result;
777         } else {
778                 die('pkwk_touch_file(): Invalid UID and (not writable for the directory or not a flie): ' .
779                         htmlsc(basename($filename)));
780         }
781 }
782 ?>