OSDN Git Service

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