OSDN Git Service

NOTE: With safe_mode, touch() always check UID
[pukiwiki/pukiwiki.git] / lib / file.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // $Id: file.php,v 1.14 2005/04/05 14:40:43 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                 if ($timestamp) 
128                         touch($file, $timestamp + LOCALZONE) or
129                                 die('Touch failed for: ' . htmlspecialchars(basename($file)));
130                                 // With safe_mode, touch() always check UID
131         }
132
133         // Clear is_page() cache
134         is_page($page, TRUE);
135
136         if (! $timestamp && $dir == DATA_DIR)
137                 put_lastmodified();
138
139         // Execute $update_exec here
140         if ($update_exec && $dir == DATA_DIR)
141                 system($update_exec . ' > /dev/null &');
142
143         if ($notify && $dir == DIFF_DIR) {
144                 if ($notify_diff_only) $str = preg_replace('/^[^-+].*\n/m', '', $str);
145                 $str .= "\n" .
146                         str_repeat('-', 30) . "\n" .
147                         'URI: ' . get_script_uri() . '?' . rawurlencode($page) . "\n" .
148                         'REMOTE_ADDR: ' . $_SERVER['REMOTE_ADDR'] . "\n";
149
150                 $subject = str_replace('$page', $page, $notify_subject);
151                 ini_set('SMTP', $smtp_server);
152                 mb_language(LANG);
153
154                 if ($smtp_auth) pop_before_smtp();
155                 mb_send_mail($notify_to, $subject, $str, $notify_header);
156         }
157 }
158
159 // Update RecentDeleted
160 function add_recent($page, $recentpage, $subject = '', $limit = 0)
161 {
162         if (PKWK_READONLY || $limit == 0 || $page == '' || $recentpage == '') return;
163
164         // Load
165         $lines = $matches = array();
166         foreach (get_source($recentpage) as $line)
167                 if (preg_match('/^-(.+) - (\[\[.+\]\])$/', $line, $matches))
168                         $lines[$matches[2]] = $line;
169
170         $_page = '[[' . $page . ']]';
171
172         // Remove a report about the same page
173         if (isset($lines[$_page])) unset($lines[$_page]);
174
175         // Add
176         array_unshift($lines, '-' . format_date(UTIME) . ' - ' . $_page .
177                 htmlspecialchars($subject) . "\n");
178
179         // Get latest $limit reports
180         $lines = array_splice($lines, 0, $limit);
181
182         // Update
183         $fp = fopen(get_filename($recentpage), 'w') or
184                 die_message('Cannot write page file ' .
185                 htmlspecialchars($recentpage) .
186                 '<br />Maybe permission is not writable or filename is too long');
187         set_file_buffer($fp, 0);
188         flock($fp, LOCK_EX);
189         rewind($fp);
190         fputs($fp, '#freeze'    . "\n");
191         fputs($fp, '#norelated' . "\n"); // :)
192         fputs($fp, join('', $lines));
193         flock($fp, LOCK_UN);
194         fclose($fp);
195 }
196
197 // Update RecentChanges
198 function put_lastmodified()
199 {
200         global $maxshow, $whatsnew, $non_list, $autolink;
201
202         if (PKWK_READONLY) return; // Do nothing
203
204         $pages = get_existpages();
205         $recent_pages = array();
206         $non_list_pattern = '/' . $non_list . '/';
207         foreach($pages as $page)
208                 if ($page != $whatsnew && ! preg_match($non_list_pattern, $page))
209                         $recent_pages[$page] = get_filetime($page);
210
211         // Sort decending order of last-modification date
212         arsort($recent_pages, SORT_NUMERIC);
213
214         // Create recent.dat (for recent.inc.php)
215         $fp = fopen(CACHE_DIR . 'recent.dat', 'w') or
216                 die_message('Cannot write cache file ' .
217                 CACHE_DIR . 'recent.dat' .
218                 '<br />Maybe permission is not writable or filename is too long');
219
220         set_file_buffer($fp, 0);
221         flock($fp, LOCK_EX);
222         rewind($fp);
223         foreach ($recent_pages as $page=>$time)
224                 fputs($fp, $time . "\t" . $page . "\n");
225         flock($fp, LOCK_UN);
226         fclose($fp);
227
228         // Create RecentChanges
229         $fp = fopen(get_filename($whatsnew), 'w') or
230                 die_message('Cannot write page file ' .
231                 htmlspecialchars($whatsnew) .
232                 '<br />Maybe permission is not writable or filename is too long');
233
234         set_file_buffer($fp, 0);
235         flock($fp, LOCK_EX);
236         rewind($fp);
237         foreach (array_splice(array_keys($recent_pages), 0, $maxshow) as $page) {
238                 $time      = $recent_pages[$page];
239                 $s_lastmod = htmlspecialchars(format_date($time));
240                 $s_page    = htmlspecialchars($page);
241                 fputs($fp, '-' . $s_lastmod . ' - [[' . $s_page . ']]' . "\n");
242         }
243         fputs($fp, '#norelated' . "\n"); // :)
244         flock($fp, LOCK_UN);
245         fclose($fp);
246
247         // For AutoLink
248         if ($autolink) {
249                 list($pattern, $pattern_a, $forceignorelist) =
250                         get_autolink_pattern($pages);
251
252                 $fp = fopen(CACHE_DIR . 'autolink.dat', 'w') or
253                         die_message('Cannot write autolink file ' .
254                         CACHE_DIR . '/autolink.dat' .
255                         '<br />Maybe permission is not writable');
256                 set_file_buffer($fp, 0);
257                 flock($fp, LOCK_EX);
258                 rewind($fp);
259                 fputs($fp, $pattern   . "\n");
260                 fputs($fp, $pattern_a . "\n");
261                 fputs($fp, join("\t", $forceignorelist) . "\n");
262                 flock($fp, LOCK_UN);
263                 fclose($fp);
264         }
265 }
266
267 // Get elapsed date of the pate
268 function get_pg_passage($page, $sw = TRUE)
269 {
270         global $show_passage;
271         if (! $show_passage) return '';
272
273         $time = get_filetime($page);
274         $pg_passage = ($time != 0) ? get_passage($time) : '';
275
276         return $sw ? '<small>' . $pg_passage . '</small>' : ' ' . $pg_passage;
277 }
278
279 // Last-Modified header
280 function header_lastmod($page = NULL)
281 {
282         global $lastmod;
283
284         if ($lastmod && is_page($page)) {
285                 pkwk_headers_sent();
286                 header('Last-Modified: ' .
287                         date('D, d M Y H:i:s', get_filetime($page)) . ' GMT');
288         }
289 }
290
291 // Get a page list of this wiki
292 function get_existpages($dir = DATA_DIR, $ext = '.txt')
293 {
294         $aryret = array();
295
296         $pattern = '((?:[0-9A-F]{2})+)';
297         if ($ext != '') $ext = preg_quote($ext, '/');
298         $pattern = '/^' . $pattern . $ext . '$/';
299
300         $dp = @opendir($dir) or
301                 die_message($dir . ' is not found or not readable.');
302         $matches = array();
303         while ($file = readdir($dp))
304                 if (preg_match($pattern, $file, $matches))
305                         $aryret[$file] = decode($matches[1]);
306         closedir($dp);
307
308         return $aryret;
309 }
310
311 // Get PageReading(pronounce-annotated) data in an array()
312 function get_readings()
313 {
314         global $pagereading_enable, $pagereading_kanji2kana_converter;
315         global $pagereading_kanji2kana_encoding, $pagereading_chasen_path;
316         global $pagereading_kakasi_path, $pagereading_config_page;
317         global $pagereading_config_dict;
318
319         $pages = get_existpages();
320
321         $readings = array();
322         foreach ($pages as $page) 
323                 $readings[$page] = '';
324
325         $deletedPage = FALSE;
326         $matches = array();
327         foreach (get_source($pagereading_config_page) as $line) {
328                 $line = chop($line);
329                 if(preg_match('/^-\[\[([^]]+)\]\]\s+(.+)$/', $line, $matches)) {
330                         if(isset($readings[$matches[1]])) {
331                                 // This page is not clear how to be pronounced
332                                 $readings[$matches[1]] = $matches[2];
333                         } else {
334                                 // This page seems deleted
335                                 $deletedPage = TRUE;
336                         }
337                 }
338         }
339
340         // If enabled ChaSen/KAKASI execution
341         if($pagereading_enable) {
342
343                 // Check there's non-clear-pronouncing page
344                 $unknownPage = FALSE;
345                 foreach ($readings as $page => $reading) {
346                         if($reading == '') {
347                                 $unknownPage = TRUE;
348                                 break;
349                         }
350                 }
351
352                 // Execute ChaSen/KAKASI, and get annotation
353                 if($unknownPage) {
354                         switch(strtolower($pagereading_kanji2kana_converter)) {
355                         case 'chasen':
356                                 if(! file_exists($pagereading_chasen_path))
357                                         die_message('ChaSen not found: ' . $pagereading_chasen_path);
358
359                                 $tmpfname = tempnam(CACHE_DIR, 'PageReading');
360                                 $fp = fopen($tmpfname, 'w') or
361                                         die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n");
362                                 foreach ($readings as $page => $reading) {
363                                         if($reading != '') continue;
364                                         fputs($fp, mb_convert_encoding($page . "\n",
365                                                 $pagereading_kanji2kana_encoding, SOURCE_ENCODING));
366                                 }
367                                 fclose($fp);
368
369                                 $chasen = "$pagereading_chasen_path -F %y $tmpfname";
370                                 $fp     = popen($chasen, 'r');
371                                 if($fp === FALSE) {
372                                         unlink($tmpfname);
373                                         die_message('ChaSen execution failed: ' . $chasen);
374                                 }
375                                 foreach ($readings as $page => $reading) {
376                                         if($reading != '') continue;
377
378                                         $line = fgets($fp);
379                                         $line = mb_convert_encoding($line, SOURCE_ENCODING,
380                                                 $pagereading_kanji2kana_encoding);
381                                         $line = chop($line);
382                                         $readings[$page] = $line;
383                                 }
384                                 pclose($fp);
385
386                                 unlink($tmpfname) or
387                                         die_message('Temporary file can not be removed: ' . $tmpfname);
388                                 break;
389
390                         case 'kakasi':  /*FALLTHROUGH*/
391                         case 'kakashi':
392                                 if(! file_exists($pagereading_kakasi_path))
393                                         die_message('KAKASI not found: ' . $pagereading_kakasi_path);
394
395                                 $tmpfname = tempnam(CACHE_DIR, 'PageReading');
396                                 $fp       = fopen($tmpfname, 'w') or
397                                         die_message('Cannot write temporary file "' . $tmpfname . '".' . "\n");
398                                 foreach ($readings as $page => $reading) {
399                                         if($reading != '') continue;
400                                         fputs($fp, mb_convert_encoding($page . "\n",
401                                                 $pagereading_kanji2kana_encoding, SOURCE_ENCODING));
402                                 }
403                                 fclose($fp);
404
405                                 $kakasi = "$pagereading_kakasi_path -kK -HK -JK < $tmpfname";
406                                 $fp     = popen($kakasi, 'r');
407                                 if($fp === FALSE) {
408                                         unlink($tmpfname);
409                                         die_message('KAKASI execution failed: ' . $kakasi);
410                                 }
411
412                                 foreach ($readings as $page => $reading) {
413                                         if($reading != '') continue;
414
415                                         $line = fgets($fp);
416                                         $line = mb_convert_encoding($line, SOURCE_ENCODING,
417                                                 $pagereading_kanji2kana_encoding);
418                                         $line = chop($line);
419                                         $readings[$page] = $line;
420                                 }
421                                 pclose($fp);
422
423                                 unlink($tmpfname) or
424                                         die_message('Temporary file can not be removed: ' . $tmpfname);
425                                 break;
426
427                         case 'none':
428                                 $patterns = $replacements = $matches = array();
429                                 foreach (get_source($pagereading_config_dict) as $line) {
430                                         $line = chop($line);
431                                         if(preg_match('|^ /([^/]+)/,\s*(.+)$|', $line, $matches)) {
432                                                 $patterns[]     = $matches[1];
433                                                 $replacements[] = $matches[2];
434                                         }
435                                 }
436                                 foreach ($readings as $page => $reading) {
437                                         if($reading != '') continue;
438
439                                         $readings[$page] = $page;
440                                         foreach ($patterns as $no => $pattern)
441                                                 $readings[$page] = mb_convert_kana(mb_ereg_replace($pattern,
442                                                         $replacements[$no], $readings[$page]), 'aKCV');
443                                 }
444                                 break;
445
446                         default:
447                                 die_message('Unknown kanji-kana converter: ' . $pagereading_kanji2kana_converter . '.');
448                                 break;
449                         }
450                 }
451
452                 if($unknownPage || $deletedPage) {
453
454                         asort($readings); // Sort by pronouncing(alphabetical/reading) order
455                         $body = '';
456                         foreach ($readings as $page => $reading)
457                                 $body .= '-[[' . $page . ']] ' . $reading . "\n";
458
459                         page_write($pagereading_config_page, $body);
460                 }
461         }
462
463         // Pages that are not prounouncing-clear, return pagenames of themselves
464         foreach ($pages as $page) {
465                 if($readings[$page] == '')
466                         $readings[$page] = $page;
467         }
468
469         return $readings;
470 }
471
472 // Get a list of encoded files (must specify a directory and a suffix)
473 function get_existfiles($dir, $ext)
474 {
475         $pattern = '/^(?:[0-9A-F]{2})+' . preg_quote($ext, '/') . '$/';
476         $aryret = array();
477         $dp = @opendir($dir) or die_message($dir . ' is not found or not readable.');
478         while ($file = readdir($dp))
479                 if (preg_match($pattern, $file))
480                         $aryret[] = $dir . $file;
481         closedir($dp);
482         return $aryret;
483 }
484
485 // Get a list of related pages of the page
486 function links_get_related($page)
487 {
488         global $vars, $related;
489         static $links = array();
490
491         if (isset($links[$page])) return $links[$page];
492
493         // If possible, merge related pages generated by make_link()
494         $links[$page] = ($page == $vars['page']) ? $related : array();
495
496         // Get repated pages from DB
497         $links[$page] += links_get_related_db($vars['page']);
498
499         return $links[$page];
500 }
501 ?>