OSDN Git Service

BugTrack2/55: Too strict to die() with invalid touch() (Pointed out by ishii)
[pukiwiki/pukiwiki.git] / lib / file.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // $Id: file.php,v 1.15 2005/04/14 13:33:29 henoheno Exp $
4 //
5 // File related functions
6
7 // Get source(wiki text) data of the page
8 function get_source($page = NULL)
9 {
10         // Removing line-feeds: Because file() doesn't remove them.
11         return is_page($page) ? str_replace("\r", '', file(get_filename($page))) : array();
12 }
13
14 // Get last-modified filetime of the page
15 function get_filetime($page)
16 {
17         return is_page($page) ? filemtime(get_filename($page)) - LOCALZONE : 0;
18 }
19
20 // Get physical file name of the page
21 function get_filename($page)
22 {
23         return DATA_DIR . encode($page) . '.txt';
24 }
25
26 // Put a data(wiki text) into a physical file(diff, backup, text)
27 function page_write($page, $postdata, $notimestamp = FALSE)
28 {
29         global $trackback;
30
31         if (PKWK_READONLY) return; // Do nothing
32
33         $postdata = make_str_rules($postdata);
34
35         // Create and write diff
36         $oldpostdata = is_page($page) ? join('', get_source($page)) : '';
37         $diffdata    = do_diff($oldpostdata, $postdata);
38         file_write(DIFF_DIR, $page, $diffdata);
39
40         // Create backup
41         make_backup($page, $postdata == ''); // Is $postdata null?
42
43         // Create wiki text
44         file_write(DATA_DIR, $page, $postdata, $notimestamp);
45
46         if ($trackback) {
47                 // TrackBack Ping
48                 $_diff = explode("\n", $diffdata);
49                 $plus  = join("\n", preg_replace('/^\+/', '', preg_grep('/^\+/', $_diff)));
50                 $minus = join("\n", preg_replace('/^-/',  '', preg_grep('/^-/',  $_diff)));
51                 tb_send($page, $plus, $minus);
52         }
53
54         links_update($page);
55 }
56
57 // User-defined rules (replace the source)
58 function make_str_rules($str)
59 {
60         global $str_rules, $fixed_heading_anchor;
61
62         $arr = explode("\n", $str);
63
64         $retvars = $matches = array();
65         foreach ($arr as $str) {
66                 if ($str != '' && $str{0} != ' ' && $str{0} != "\t")
67                         foreach ($str_rules as $rule => $replace)
68                                 $str = preg_replace('/' . $rule . '/', $replace, $str);
69                 
70                 // Adding fixed anchor into headings
71                 if ($fixed_heading_anchor &&
72                         preg_match('/^(\*{1,3}(.(?!\[#[A-Za-z][\w-]+\]))+)$/', $str, $matches))
73                 {
74                         // Generate ID:
75                         // A random alphabetic letter + 7 letters of random strings from md()
76                         $anchor = chr(mt_rand(ord('a'), ord('z'))) .
77                                 substr(md5(uniqid(substr($matches[1], 0, 100), 1)), mt_rand(0, 24), 7);
78                         $str = rtrim($matches[1]) . ' [#' . $anchor . ']';
79                 }
80                 $retvars[] = $str;
81         }
82
83         return join("\n", $retvars);
84 }
85
86 // Output to a file
87 function file_write($dir, $page, $str, $notimestamp = FALSE)
88 {
89         global $update_exec, $_msg_invalidiwn;
90         global $notify, $notify_diff_only, $notify_to, $notify_subject, $notify_header;
91         global $smtp_server, $smtp_auth;
92         global $whatsdeleted, $maxshow_deleted;
93
94         if (PKWK_READONLY) return; // Do nothing
95
96         if (! is_pagename($page))
97                 die_message(str_replace('$1', htmlspecialchars($page),
98                             str_replace('$2', 'WikiName', $_msg_invalidiwn)));
99
100         $page      = strip_bracket($page);
101         $timestamp = FALSE;
102         $file      = $dir . encode($page) . '.txt';
103
104         if ($dir == DATA_DIR && $str == '' && file_exists($file)) {
105                 unlink($file);
106                 add_recent($page, $whatsdeleted, '', $maxshow_deleted); // RecentDeleted
107         }
108
109         if ($str != '') {
110                 $str = preg_replace('/' . "\r" . '/', '', $str);
111                 $str = rtrim($str) . "\n";
112
113                 if ($notimestamp && file_exists($file))
114                         $timestamp = filemtime($file) - LOCALZONE;
115
116                 $fp = fopen($file, 'w') or
117                         die_message('Cannot write page file or diff file or other ' .
118                         htmlspecialchars($page) .
119                         '<br />Maybe permission is not writable or filename is too long');
120
121                 set_file_buffer($fp, 0);
122                 flock($fp, LOCK_EX);
123                 rewind($fp);
124                 fputs($fp, $str);
125                 flock($fp, LOCK_UN);
126                 fclose($fp);
127
128                 // With safe_mode, touch() always check UID
129                 if ($timestamp) touch($file, $timestamp + LOCALZONE);
130         }
131
132         // Clear is_page() cache
133         is_page($page, TRUE);
134
135         if (! $timestamp && $dir == DATA_DIR)
136                 put_lastmodified();
137
138         // Execute $update_exec here
139         if ($update_exec && $dir == DATA_DIR)
140                 system($update_exec . ' > /dev/null &');
141
142         if ($notify && $dir == DIFF_DIR) {
143                 if ($notify_diff_only) $str = preg_replace('/^[^-+].*\n/m', '', $str);
144                 $str .= "\n" .
145                         str_repeat('-', 30) . "\n" .
146                         'URI: ' . get_script_uri() . '?' . rawurlencode($page) . "\n" .
147                         'REMOTE_ADDR: ' . $_SERVER['REMOTE_ADDR'] . "\n";
148
149                 $subject = str_replace('$page', $page, $notify_subject);
150                 ini_set('SMTP', $smtp_server);
151                 mb_language(LANG);
152
153                 if ($smtp_auth) pop_before_smtp();
154                 mb_send_mail($notify_to, $subject, $str, $notify_header);
155         }
156 }
157
158 // Update RecentDeleted
159 function add_recent($page, $recentpage, $subject = '', $limit = 0)
160 {
161         if (PKWK_READONLY || $limit == 0 || $page == '' || $recentpage == '') return;
162
163         // Load
164         $lines = $matches = array();
165         foreach (get_source($recentpage) as $line)
166                 if (preg_match('/^-(.+) - (\[\[.+\]\])$/', $line, $matches))
167                         $lines[$matches[2]] = $line;
168
169         $_page = '[[' . $page . ']]';
170
171         // Remove a report about the same page
172         if (isset($lines[$_page])) unset($lines[$_page]);
173
174         // Add
175         array_unshift($lines, '-' . format_date(UTIME) . ' - ' . $_page .
176                 htmlspecialchars($subject) . "\n");
177
178         // Get latest $limit reports
179         $lines = array_splice($lines, 0, $limit);
180
181         // Update
182         $fp = fopen(get_filename($recentpage), 'w') or
183                 die_message('Cannot write page file ' .
184                 htmlspecialchars($recentpage) .
185                 '<br />Maybe permission is not writable or filename is too long');
186         set_file_buffer($fp, 0);
187         flock($fp, LOCK_EX);
188         rewind($fp);
189         fputs($fp, '#freeze'    . "\n");
190         fputs($fp, '#norelated' . "\n"); // :)
191         fputs($fp, join('', $lines));
192         flock($fp, LOCK_UN);
193         fclose($fp);
194 }
195
196 // Update RecentChanges
197 function put_lastmodified()
198 {
199         global $maxshow, $whatsnew, $non_list, $autolink;
200
201         if (PKWK_READONLY) return; // Do nothing
202
203         $pages = get_existpages();
204         $recent_pages = array();
205         $non_list_pattern = '/' . $non_list . '/';
206         foreach($pages as $page)
207                 if ($page != $whatsnew && ! preg_match($non_list_pattern, $page))
208                         $recent_pages[$page] = get_filetime($page);
209
210         // Sort decending order of last-modification date
211         arsort($recent_pages, SORT_NUMERIC);
212
213         // Create recent.dat (for recent.inc.php)
214         $fp = fopen(CACHE_DIR . 'recent.dat', 'w') or
215                 die_message('Cannot write cache file ' .
216                 CACHE_DIR . 'recent.dat' .
217                 '<br />Maybe permission is not writable or filename is too long');
218
219         set_file_buffer($fp, 0);
220         flock($fp, LOCK_EX);
221         rewind($fp);
222         foreach ($recent_pages as $page=>$time)
223                 fputs($fp, $time . "\t" . $page . "\n");
224         flock($fp, LOCK_UN);
225         fclose($fp);
226
227         // Create RecentChanges
228         $fp = fopen(get_filename($whatsnew), 'w') or
229                 die_message('Cannot write page file ' .
230                 htmlspecialchars($whatsnew) .
231                 '<br />Maybe permission is not writable or filename is too long');
232
233         set_file_buffer($fp, 0);
234         flock($fp, LOCK_EX);
235         rewind($fp);
236         foreach (array_splice(array_keys($recent_pages), 0, $maxshow) as $page) {
237                 $time      = $recent_pages[$page];
238                 $s_lastmod = htmlspecialchars(format_date($time));
239                 $s_page    = htmlspecialchars($page);
240                 fputs($fp, '-' . $s_lastmod . ' - [[' . $s_page . ']]' . "\n");
241         }
242         fputs($fp, '#norelated' . "\n"); // :)
243         flock($fp, LOCK_UN);
244         fclose($fp);
245
246         // For AutoLink
247         if ($autolink) {
248                 list($pattern, $pattern_a, $forceignorelist) =
249                         get_autolink_pattern($pages);
250
251                 $fp = fopen(CACHE_DIR . 'autolink.dat', 'w') or
252                         die_message('Cannot write autolink file ' .
253                         CACHE_DIR . '/autolink.dat' .
254                         '<br />Maybe permission is not writable');
255                 set_file_buffer($fp, 0);
256                 flock($fp, LOCK_EX);
257                 rewind($fp);
258                 fputs($fp, $pattern   . "\n");
259                 fputs($fp, $pattern_a . "\n");
260                 fputs($fp, join("\t", $forceignorelist) . "\n");
261                 flock($fp, LOCK_UN);
262                 fclose($fp);
263         }
264 }
265
266 // Get elapsed date of the pate
267 function get_pg_passage($page, $sw = TRUE)
268 {
269         global $show_passage;
270         if (! $show_passage) return '';
271
272         $time = get_filetime($page);
273         $pg_passage = ($time != 0) ? get_passage($time) : '';
274
275         return $sw ? '<small>' . $pg_passage . '</small>' : ' ' . $pg_passage;
276 }
277
278 // Last-Modified header
279 function header_lastmod($page = NULL)
280 {
281         global $lastmod;
282
283         if ($lastmod && is_page($page)) {
284                 pkwk_headers_sent();
285                 header('Last-Modified: ' .
286                         date('D, d M Y H:i:s', get_filetime($page)) . ' GMT');
287         }
288 }
289
290 // Get a page list of this wiki
291 function get_existpages($dir = DATA_DIR, $ext = '.txt')
292 {
293         $aryret = array();
294
295         $pattern = '((?:[0-9A-F]{2})+)';
296         if ($ext != '') $ext = preg_quote($ext, '/');
297         $pattern = '/^' . $pattern . $ext . '$/';
298
299         $dp = @opendir($dir) or
300                 die_message($dir . ' is not found or not readable.');
301         $matches = array();
302         while ($file = readdir($dp))
303                 if (preg_match($pattern, $file, $matches))
304                         $aryret[$file] = decode($matches[1]);
305         closedir($dp);
306
307         return $aryret;
308 }
309
310 // Get PageReading(pronounce-annotated) data in an array()
311 function get_readings()
312 {
313         global $pagereading_enable, $pagereading_kanji2kana_converter;
314         global $pagereading_kanji2kana_encoding, $pagereading_chasen_path;
315         global $pagereading_kakasi_path, $pagereading_config_page;
316         global $pagereading_config_dict;
317
318         $pages = get_existpages();
319
320         $readings = array();
321         foreach ($pages as $page) 
322                 $readings[$page] = '';
323
324         $deletedPage = FALSE;
325         $matches = array();
326         foreach (get_source($pagereading_config_page) as $line) {
327                 $line = chop($line);
328                 if(preg_match('/^-\[\[([^]]+)\]\]\s+(.+)$/', $line, $matches)) {
329                         if(isset($readings[$matches[1]])) {
330                                 // This page is not clear how to be pronounced
331                                 $readings[$matches[1]] = $matches[2];
332                         } else {
333                                 // This page seems deleted
334                                 $deletedPage = TRUE;
335                         }
336                 }
337         }
338
339         // If enabled ChaSen/KAKASI execution
340         if($pagereading_enable) {
341
342                 // Check there's non-clear-pronouncing page
343                 $unknownPage = FALSE;
344                 foreach ($readings as $page => $reading) {
345                         if($reading == '') {
346                                 $unknownPage = TRUE;
347                                 break;
348                         }
349                 }
350
351                 // Execute ChaSen/KAKASI, and get annotation
352                 if($unknownPage) {
353                         switch(strtolower($pagereading_kanji2kana_converter)) {
354                         case 'chasen':
355                                 if(! file_exists($pagereading_chasen_path))
356                                         die_message('ChaSen not found: ' . $pagereading_chasen_path);
357
358                                 $tmpfname = tempnam(CACHE_DIR, 'PageReading');
359                                 $fp = fopen($tmpfname, 'w') or
360                                         die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n");
361                                 foreach ($readings as $page => $reading) {
362                                         if($reading != '') continue;
363                                         fputs($fp, mb_convert_encoding($page . "\n",
364                                                 $pagereading_kanji2kana_encoding, SOURCE_ENCODING));
365                                 }
366                                 fclose($fp);
367
368                                 $chasen = "$pagereading_chasen_path -F %y $tmpfname";
369                                 $fp     = popen($chasen, 'r');
370                                 if($fp === FALSE) {
371                                         unlink($tmpfname);
372                                         die_message('ChaSen execution failed: ' . $chasen);
373                                 }
374                                 foreach ($readings as $page => $reading) {
375                                         if($reading != '') continue;
376
377                                         $line = fgets($fp);
378                                         $line = mb_convert_encoding($line, SOURCE_ENCODING,
379                                                 $pagereading_kanji2kana_encoding);
380                                         $line = chop($line);
381                                         $readings[$page] = $line;
382                                 }
383                                 pclose($fp);
384
385                                 unlink($tmpfname) or
386                                         die_message('Temporary file can not be removed: ' . $tmpfname);
387                                 break;
388
389                         case 'kakasi':  /*FALLTHROUGH*/
390                         case 'kakashi':
391                                 if(! file_exists($pagereading_kakasi_path))
392                                         die_message('KAKASI not found: ' . $pagereading_kakasi_path);
393
394                                 $tmpfname = tempnam(CACHE_DIR, 'PageReading');
395                                 $fp       = fopen($tmpfname, 'w') or
396                                         die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n");
397                                 foreach ($readings as $page => $reading) {
398                                         if($reading != '') continue;
399                                         fputs($fp, mb_convert_encoding($page . "\n",
400                                                 $pagereading_kanji2kana_encoding, SOURCE_ENCODING));
401                                 }
402                                 fclose($fp);
403
404                                 $kakasi = "$pagereading_kakasi_path -kK -HK -JK < $tmpfname";
405                                 $fp     = popen($kakasi, 'r');
406                                 if($fp === FALSE) {
407                                         unlink($tmpfname);
408                                         die_message('KAKASI execution failed: ' . $kakasi);
409                                 }
410
411                                 foreach ($readings as $page => $reading) {
412                                         if($reading != '') continue;
413
414                                         $line = fgets($fp);
415                                         $line = mb_convert_encoding($line, SOURCE_ENCODING,
416                                                 $pagereading_kanji2kana_encoding);
417                                         $line = chop($line);
418                                         $readings[$page] = $line;
419                                 }
420                                 pclose($fp);
421
422                                 unlink($tmpfname) or
423                                         die_message('Temporary file can not be removed: ' . $tmpfname);
424                                 break;
425
426                         case 'none':
427                                 $patterns = $replacements = $matches = array();
428                                 foreach (get_source($pagereading_config_dict) as $line) {
429                                         $line = chop($line);
430                                         if(preg_match('|^ /([^/]+)/,\s*(.+)$|', $line, $matches)) {
431                                                 $patterns[]     = $matches[1];
432                                                 $replacements[] = $matches[2];
433                                         }
434                                 }
435                                 foreach ($readings as $page => $reading) {
436                                         if($reading != '') continue;
437
438                                         $readings[$page] = $page;
439                                         foreach ($patterns as $no => $pattern)
440                                                 $readings[$page] = mb_convert_kana(mb_ereg_replace($pattern,
441                                                         $replacements[$no], $readings[$page]), 'aKCV');
442                                 }
443                                 break;
444
445                         default:
446                                 die_message('Unknown kanji-kana converter: ' . $pagereading_kanji2kana_converter . '.');
447                                 break;
448                         }
449                 }
450
451                 if($unknownPage || $deletedPage) {
452
453                         asort($readings); // Sort by pronouncing(alphabetical/reading) order
454                         $body = '';
455                         foreach ($readings as $page => $reading)
456                                 $body .= '-[[' . $page . ']] ' . $reading . "\n";
457
458                         page_write($pagereading_config_page, $body);
459                 }
460         }
461
462         // Pages that are not prounouncing-clear, return pagenames of themselves
463         foreach ($pages as $page) {
464                 if($readings[$page] == '')
465                         $readings[$page] = $page;
466         }
467
468         return $readings;
469 }
470
471 // Get a list of encoded files (must specify a directory and a suffix)
472 function get_existfiles($dir, $ext)
473 {
474         $pattern = '/^(?:[0-9A-F]{2})+' . preg_quote($ext, '/') . '$/';
475         $aryret = array();
476         $dp = @opendir($dir) or die_message($dir . ' is not found or not readable.');
477         while ($file = readdir($dp))
478                 if (preg_match($pattern, $file))
479                         $aryret[] = $dir . $file;
480         closedir($dp);
481         return $aryret;
482 }
483
484 // Get a list of related pages of the page
485 function links_get_related($page)
486 {
487         global $vars, $related;
488         static $links = array();
489
490         if (isset($links[$page])) return $links[$page];
491
492         // If possible, merge related pages generated by make_link()
493         $links[$page] = ($page == $vars['page']) ? $related : array();
494
495         // Get repated pages from DB
496         $links[$page] += links_get_related_db($vars['page']);
497
498         return $links[$page];
499 }
500 ?>