OSDN Git Service

0cc0aacb7b71f590a4e31e86ca9b25079c9f89ba
[pukiwiki/pukiwiki.git] / plugin / attach.inc.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // attach.inc.php
4 // Copyright
5 //   2003-2022 PukiWiki Development Team
6 //   2002-2003 PANDA <panda@arino.jp> http://home.arino.jp/
7 //   2002      Y.MASUI <masui@hisec.co.jp> http://masui.net/pukiwiki/
8 //   2001-2002 Originally written by yu-ji
9 // License: GPL v2 or (at your option) any later version
10 //
11 // File attach plugin
12
13 // NOTE (PHP > 4.2.3):
14 //    This feature is disabled at newer version of PHP.
15 //    Set this at php.ini if you want.
16 // Max file size for upload on PHP (PHP default: 2MB)
17 ini_set('upload_max_filesize', '2M');
18
19 // Max file size for upload on script of PukiWikiX_FILESIZE
20 define('PLUGIN_ATTACH_MAX_FILESIZE', (1024 * 1024)); // default: 1MB
21
22 // 管理者だけが添付ファイルをアップロードできるようにする
23 define('PLUGIN_ATTACH_UPLOAD_ADMIN_ONLY', TRUE); // FALSE or TRUE
24
25 // 管理者だけが添付ファイルを削除できるようにする
26 define('PLUGIN_ATTACH_DELETE_ADMIN_ONLY', TRUE); // FALSE or TRUE
27
28 // 管理者が添付ファイルを削除するときは、バックアップを作らない
29 // PLUGIN_ATTACH_DELETE_ADMIN_ONLY=TRUEのとき有効
30 define('PLUGIN_ATTACH_DELETE_ADMIN_NOBACKUP', FALSE); // FALSE or TRUE
31
32 // アップロード/削除時にパスワードを要求する(ADMIN_ONLYが優先)
33 define('PLUGIN_ATTACH_PASSWORD_REQUIRE', FALSE); // FALSE or TRUE
34
35 // 添付ファイル名を変更できるようにする
36 define('PLUGIN_ATTACH_RENAME_ENABLE', TRUE); // FALSE or TRUE
37
38 // ファイルのアクセス権
39 define('PLUGIN_ATTACH_FILE_MODE', 0644);
40 //define('PLUGIN_ATTACH_FILE_MODE', 0604); // for XREA.COM
41
42 // File icon image
43 define('PLUGIN_ATTACH_FILE_ICON', '<img src="' . IMAGE_DIR .  'file.png"' .
44         ' width="20" height="20" alt="file"' .
45         ' style="border-width:0" />');
46
47 // mime-typeを記述したページ
48 define('PLUGIN_ATTACH_CONFIG_PAGE_MIME', 'plugin/attach/mime-type');
49
50 // Limit attach file name length
51 define('PLUGIN_ATTACH_MAX_LEAF_BASE_NAME_LENGTH', 240);
52
53 //-------- convert
54 function plugin_attach_convert()
55 {
56         global $vars;
57
58         $page = isset($vars['page']) ? $vars['page'] : '';
59
60         $nolist = $noform = FALSE;
61         if (func_num_args() > 0) {
62                 foreach (func_get_args() as $arg) {
63                         $arg = strtolower($arg);
64                         $nolist |= ($arg == 'nolist');
65                         $noform |= ($arg == 'noform');
66                 }
67         }
68
69         $ret = '';
70         if (! $nolist) {
71                 $obj  = new AttachPages($page);
72                 $ret .= $obj->toString($page, TRUE);
73         }
74         if (! $noform) {
75                 $ret .= attach_form($page);
76         }
77
78         return $ret;
79 }
80
81 //-------- action
82 function plugin_attach_action()
83 {
84         global $vars, $_attach_messages;
85
86         // Backward compatible
87         if (isset($vars['openfile'])) {
88                 $vars['file'] = $vars['openfile'];
89                 $vars['pcmd'] = 'open';
90         }
91         if (isset($vars['delfile'])) {
92                 $vars['file'] = $vars['delfile'];
93                 $vars['pcmd'] = 'delete';
94         }
95
96         $pcmd  = isset($vars['pcmd'])  ? $vars['pcmd']  : '';
97         $refer = isset($vars['refer']) ? $vars['refer'] : '';
98         $pass  = isset($vars['pass'])  ? $vars['pass']  : NULL;
99         $page  = isset($vars['page'])  ? $vars['page']  : '';
100
101         if ($refer === '' && $page !== '') {
102                 $refer = $page;
103         }
104         if ($refer != '' && is_pagename($refer)) {
105                 if(in_array($pcmd, array('info', 'open', 'list'))) {
106                         check_readable($refer);
107                 } else {
108                         check_editable($refer);
109                 }
110         }
111
112         // Dispatch
113         if (isset($_FILES['attach_file'])) {
114                 // Upload
115                 return attach_upload($_FILES['attach_file'], $refer, $pass);
116         } else {
117                 switch ($pcmd) {
118                 case 'delete':  /*FALLTHROUGH*/
119                 case 'freeze':
120                 case 'unfreeze':
121                         if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
122                 }
123                 switch ($pcmd) {
124                 case 'info'     : return attach_info();
125                 case 'delete'   : return attach_delete();
126                 case 'open'     : return attach_open();
127                 case 'list'     : return attach_list();
128                 case 'freeze'   : return attach_freeze(TRUE);
129                 case 'unfreeze' : return attach_freeze(FALSE);
130                 case 'rename'   : return attach_rename();
131                 case 'upload'   : return attach_showform();
132                 }
133                 if ($page == '' || ! is_page($page)) {
134                         return attach_list();
135                 } else {
136                         return attach_showform();
137                 }
138         }
139 }
140
141 //-------- call from skin
142 function attach_filelist()
143 {
144         global $vars, $_attach_messages;
145
146         $page = isset($vars['page']) ? $vars['page'] : '';
147
148         $obj = new AttachPages($page, 0);
149
150         if (! isset($obj->pages[$page])) {
151                 return '';
152         } else {
153                 return $_attach_messages['msg_file'] . ': ' .
154                 $obj->toString($page, TRUE) . "\n";
155         }
156 }
157
158 //-------- 実体
159 // ファイルアップロード
160 // $pass = NULL : パスワードが指定されていない
161 // $pass = TRUE : アップロード許可
162 function attach_upload($file, $page, $pass = NULL)
163 {
164         global $_attach_messages, $notify, $notify_subject;
165
166         if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
167
168         // Check query-string
169         $query = 'plugin=attach&amp;pcmd=info&amp;refer=' . rawurlencode($page) .
170                 '&amp;file=' . rawurlencode($file['name']);
171
172         if (PKWK_QUERY_STRING_MAX && strlen($query) > PKWK_QUERY_STRING_MAX) {
173                 pkwk_common_headers();
174                 echo('Query string (page name and/or file name) too long');
175                 exit;
176         } else if (! is_page($page)) {
177                 die_message('No such page');
178         } else if ($file['tmp_name'] == '' || ! is_uploaded_file($file['tmp_name'])) {
179                 return array('result'=>FALSE);
180         } else if ($file['size'] > PLUGIN_ATTACH_MAX_FILESIZE) {
181                 return array(
182                         'result'=>FALSE,
183                         'msg'=>$_attach_messages['err_exceed']);
184         } else if (! is_pagename($page) || ($pass !== TRUE && ! is_editable($page))) {
185                 return array(
186                         'result'=>FALSE,'
187                         msg'=>$_attach_messages['err_noparm']);
188         } else if (PLUGIN_ATTACH_UPLOAD_ADMIN_ONLY && $pass !== TRUE &&
189                   ($pass === NULL || ! pkwk_login($pass))) {
190                 return array(
191                         'result'=>FALSE,
192                         'msg'=>$_attach_messages['err_adminpass']);
193         }
194
195         $obj = new AttachFile($page, $file['name']);
196         if ($obj->exist) {
197                 return array('result'=>FALSE,
198                         'msg'=>$_attach_messages['err_exists']);
199         }
200         $leafbasename_len = strlen($obj->leafbasename);
201         if (PLUGIN_ATTACH_MAX_LEAF_BASE_NAME_LENGTH < $leafbasename_len) {
202                 die_message('Too long file name (' . $leafbasename_len . ' bytes)');
203                 exit;
204         }
205         if (move_uploaded_file($file['tmp_name'], $obj->filename))
206                 chmod($obj->filename, PLUGIN_ATTACH_FILE_MODE);
207
208         if (is_page($page))
209                 pkwk_touch_file(get_filename($page));
210
211         $obj->getstatus();
212         $obj->status['pass'] = ($pass !== TRUE && $pass !== NULL) ? md5($pass) : '';
213         $obj->putstatus();
214
215         if ($notify) {
216                 $footer['ACTION']   = 'File attached';
217                 $footer['FILENAME'] = $file['name'];
218                 $footer['FILESIZE'] = $file['size'];
219                 $footer['PAGE']     = $page;
220
221                 $footer['URI']      = get_base_uri(PKWK_URI_ABSOLUTE) .
222                         // MD5 may heavy
223                         '?plugin=attach' .
224                                 '&refer=' . rawurlencode($page) .
225                                 '&file='  . rawurlencode($file['name']) .
226                                 '&pcmd=info';
227
228                 $footer['USER_AGENT']  = TRUE;
229                 $footer['REMOTE_ADDR'] = TRUE;
230
231                 pkwk_mail_notify($notify_subject, "\n", $footer) or
232                         die('pkwk_mail_notify(): Failed');
233         }
234
235         return array(
236                 'result'=>TRUE,
237                 'msg'=>$_attach_messages['msg_uploaded']);
238 }
239
240 // 詳細フォームを表示
241 function attach_info($err = '')
242 {
243         global $vars, $_attach_messages;
244
245         foreach (array('refer', 'file', 'age') as $var)
246                 ${$var} = isset($vars[$var]) ? $vars[$var] : '';
247
248         $obj = new AttachFile($refer, $file, $age);
249         return $obj->getstatus() ?
250                 $obj->info($err) :
251                 array('msg'=>$_attach_messages['err_notfound']);
252 }
253
254 // 削除
255 function attach_delete()
256 {
257         global $vars, $_attach_messages;
258
259         foreach (array('refer', 'file', 'age', 'pass') as $var)
260                 ${$var} = isset($vars[$var]) ? $vars[$var] : '';
261
262         if (is_freeze($refer) || ! is_editable($refer))
263                 return array('msg'=>$_attach_messages['err_noparm']);
264
265         $obj = new AttachFile($refer, $file, $age);
266         if (! $obj->getstatus())
267                 return array('msg'=>$_attach_messages['err_notfound']);
268                 
269         return $obj->delete($pass);
270 }
271
272 // 凍結
273 function attach_freeze($freeze)
274 {
275         global $vars, $_attach_messages;
276
277         foreach (array('refer', 'file', 'age', 'pass') as $var) {
278                 ${$var} = isset($vars[$var]) ? $vars[$var] : '';
279         }
280
281         if (is_freeze($refer) || ! is_editable($refer)) {
282                 return array('msg'=>$_attach_messages['err_noparm']);
283         } else {
284                 $obj = new AttachFile($refer, $file, $age);
285                 return $obj->getstatus() ?
286                         $obj->freeze($freeze, $pass) :
287                         array('msg'=>$_attach_messages['err_notfound']);
288         }
289 }
290
291 // リネーム
292 function attach_rename()
293 {
294         global $vars, $_attach_messages;
295
296         foreach (array('refer', 'file', 'age', 'pass', 'newname') as $var) {
297                 ${$var} = isset($vars[$var]) ? $vars[$var] : '';
298         }
299
300         if (is_freeze($refer) || ! is_editable($refer)) {
301                 return array('msg'=>$_attach_messages['err_noparm']);
302         }
303         $obj = new AttachFile($refer, $file, $age);
304         if (! $obj->getstatus())
305                 return array('msg'=>$_attach_messages['err_notfound']);
306
307         return $obj->rename($pass, $newname);
308
309 }
310
311 // ダウンロード
312 function attach_open()
313 {
314         global $vars, $_attach_messages;
315
316         foreach (array('refer', 'file', 'age') as $var) {
317                 ${$var} = isset($vars[$var]) ? $vars[$var] : '';
318         }
319
320         $obj = new AttachFile($refer, $file, $age);
321         return $obj->getstatus() ?
322                 $obj->open() :
323                 array('msg'=>$_attach_messages['err_notfound']);
324 }
325
326 // 一覧取得
327 function attach_list()
328 {
329         global $vars, $_attach_messages;
330
331         $refer = isset($vars['refer']) ? $vars['refer'] : '';
332
333         $obj = new AttachPages($refer);
334
335         $msg = $_attach_messages[($refer == '') ? 'msg_listall' : 'msg_listpage'];
336         $body = ($refer == '' || isset($obj->pages[$refer])) ?
337                 $obj->toString($refer, FALSE) :
338                 $_attach_messages['err_noexist'];
339
340         return array('msg'=>$msg, 'body'=>$body);
341 }
342
343 // アップロードフォームを表示 (action時)
344 function attach_showform()
345 {
346         global $vars, $_attach_messages;
347
348         $page = isset($vars['page']) ? $vars['page'] : '';
349         $vars['refer'] = $page;
350         $body = attach_form($page);
351         return array('msg'=>$_attach_messages['msg_upload'], 'body'=>$body);
352 }
353
354 //-------- サービス
355 // mime-typeの決定
356 function attach_mime_content_type($filename, $displayname)
357 {
358         $type = 'application/octet-stream'; // default
359
360         if (! file_exists($filename)) return $type;
361         $pathinfo = pathinfo($displayname);
362         $ext0 = $pathinfo['extension'];
363         if (preg_match('/^(gif|jpg|jpeg|png|swf)$/i', $ext0)) {
364                 $size = @getimagesize($filename);
365                 if (is_array($size)) {
366                         switch ($size[2]) {
367                                 case 1: return 'image/gif';
368                                 case 2: return 'image/jpeg';
369                                 case 3: return 'image/png';
370                                 case 4: return 'application/x-shockwave-flash';
371                         }
372                 }
373         }
374         // mime-type一覧表を取得
375         $config = new Config(PLUGIN_ATTACH_CONFIG_PAGE_MIME);
376         $table = $config->read() ? $config->get('mime-type') : array();
377         unset($config); // メモリ節約
378         foreach ($table as $row) {
379                 $_type = trim($row[0]);
380                 $exts = preg_split('/\s+|,/', trim($row[1]), -1, PREG_SPLIT_NO_EMPTY);
381                 foreach ($exts as $ext) {
382                         if (preg_match("/\.$ext$/i", $displayname)) return $_type;
383                 }
384         }
385         return $type;
386 }
387
388 // アップロードフォームの出力
389 function attach_form($page)
390 {
391         global $vars, $_attach_messages;
392
393         $script = get_base_uri();
394         $r_page = rawurlencode($page);
395         $s_page = htmlsc($page);
396         $navi = <<<EOD
397   <span class="small">
398    [<a href="$script?plugin=attach&amp;pcmd=list&amp;refer=$r_page">{$_attach_messages['msg_list']}</a>]
399    [<a href="$script?plugin=attach&amp;pcmd=list">{$_attach_messages['msg_listall']}</a>]
400   </span><br />
401 EOD;
402
403         if (! ini_get('file_uploads')) return '#attach(): file_uploads disabled<br />' . $navi;
404         if (! is_page($page))          return '#attach(): No such page<br />'          . $navi;
405
406         $maxsize = PLUGIN_ATTACH_MAX_FILESIZE;
407         $msg_maxsize = sprintf($_attach_messages['msg_maxsize'], number_format($maxsize/1024) . 'KB');
408
409         $pass = '';
410         if (PLUGIN_ATTACH_PASSWORD_REQUIRE || PLUGIN_ATTACH_UPLOAD_ADMIN_ONLY) {
411                 $title = $_attach_messages[PLUGIN_ATTACH_UPLOAD_ADMIN_ONLY ? 'msg_adminpass' : 'msg_password'];
412                 $pass = '<br />' . $title . ': <input type="password" name="pass" size="8" />';
413         }
414         return <<<EOD
415 <form enctype="multipart/form-data" action="$script" method="post">
416  <div>
417   <input type="hidden" name="plugin" value="attach" />
418   <input type="hidden" name="pcmd"   value="post" />
419   <input type="hidden" name="refer"  value="$s_page" />
420   <input type="hidden" name="max_file_size" value="$maxsize" />
421   $navi
422   <span class="small">
423    $msg_maxsize
424   </span><br />
425   <label for="_p_attach_file">{$_attach_messages['msg_file']}:</label> <input type="file" name="attach_file" id="_p_attach_file" />
426   $pass
427   <input type="submit" value="{$_attach_messages['btn_upload']}" />
428  </div>
429 </form>
430 EOD;
431 }
432
433 //-------- クラス
434 // ファイル
435 class AttachFile
436 {
437         var $page, $file, $age, $basename, $filename, $logname;
438         var $time = 0;
439         var $size = 0;
440         var $time_str = '';
441         var $size_str = '';
442         var $status = array('count'=>array(0), 'age'=>'', 'pass'=>'', 'freeze'=>FALSE);
443
444         function AttachFile($page, $file, $age = 0)
445         {
446                 $this->__construct($page, $file, $age);
447         }
448         function __construct($page, $file, $age = 0)
449         {
450                 $this->page = $page;
451                 $this->file = preg_replace('#^.*/#','',$file);
452                 $this->age  = is_numeric($age) ? $age : 0;
453
454                 $this->leafbasename = encode($page) . '_' . encode($this->file);
455                 $this->basename = UPLOAD_DIR . $this->leafbasename;
456                 $this->filename = $this->basename . ($age ? '.' . $age : '');
457                 $this->logname  = $this->basename . '.log';
458                 $this->exist    = file_exists($this->filename);
459                 $this->time     = $this->exist ? filemtime($this->filename) - LOCALZONE : 0;
460         }
461
462         function gethash()
463         {
464                 return $this->exist ? md5_file($this->filename) : '';
465         }
466
467         // ファイル情報取得
468         function getstatus()
469         {
470                 // ログファイル取得
471                 if (file_exists($this->logname)) {
472                         $data = file($this->logname);
473                         foreach ($this->status as $key=>$value) {
474                                 $this->status[$key] = chop(array_shift($data));
475                         }
476                         $this->status['count'] = explode(',', $this->status['count']);
477                 }
478                 if (! $this->exist) return FALSE;
479                 $this->time_str = get_date('Y/m/d H:i:s', $this->time);
480                 $this->size     = filesize($this->filename);
481                 $this->size_str = sprintf('%01.1f', round($this->size/1024, 1)) . 'KB';
482                 $this->type     = attach_mime_content_type($this->filename, $this->file);
483                 return TRUE;
484         }
485
486         // ステータス保存
487         function putstatus()
488         {
489                 $this->status['count'] = join(',', $this->status['count']);
490                 $fp = fopen($this->logname, 'wb') or
491                         die_message('cannot write ' . $this->logname);
492                 set_file_buffer($fp, 0);
493                 flock($fp, LOCK_EX);
494                 rewind($fp);
495                 foreach ($this->status as $key=>$value) {
496                         fwrite($fp, $value . "\n");
497                 }
498                 flock($fp, LOCK_UN);
499                 fclose($fp);
500         }
501
502         function toString($showicon, $showinfo)
503         {
504                 global $_attach_messages;
505
506                 $script = get_base_uri();
507                 $this->getstatus();
508                 $param  = '&amp;file=' . rawurlencode($this->file) . '&amp;refer=' . rawurlencode($this->page) .
509                         ($this->age ? '&amp;age=' . $this->age : '');
510                 $title = $this->time_str . ' ' . $this->size_str;
511                 $label = ($showicon ? PLUGIN_ATTACH_FILE_ICON : '') . htmlsc($this->file);
512                 if ($this->age) {
513                         $label .= ' (backup No.' . $this->age . ')';
514                 }
515                 $info = $count = '';
516                 if ($showinfo) {
517                         $_title = str_replace('$1', rawurlencode($this->file), $_attach_messages['msg_info']);
518                         $info = "\n<span class=\"small\">[<a href=\"$script?plugin=attach&amp;pcmd=info$param\" title=\"$_title\">{$_attach_messages['btn_info']}</a>]</span>\n";
519                         $count = ($showicon && ! empty($this->status['count'][$this->age])) ?
520                                 sprintf($_attach_messages['msg_count'], $this->status['count'][$this->age]) : '';
521                 }
522                 return "<a href=\"$script?plugin=attach&amp;pcmd=open$param\" title=\"$title\">$label</a>$count$info";
523         }
524
525         // 情報表示
526         function info($err)
527         {
528                 global $_attach_messages;
529
530                 $script = get_base_uri();
531                 $r_page = rawurlencode($this->page);
532                 $s_page = htmlsc($this->page);
533                 $s_file = htmlsc($this->file);
534                 $s_err = ($err == '') ? '' : '<p style="font-weight:bold">' . $_attach_messages[$err] . '</p>';
535
536                 $msg_rename  = '';
537                 if ($this->age) {
538                         $msg_freezed = '';
539                         $msg_delete  = '<input type="radio" name="pcmd" id="_p_attach_delete" value="delete" />' .
540                                 '<label for="_p_attach_delete">' .  $_attach_messages['msg_delete'] .
541                                 $_attach_messages['msg_require'] . '</label><br />';
542                         $msg_freeze  = '';
543                 } else {
544                         if ($this->status['freeze']) {
545                                 $msg_freezed = "<dd>{$_attach_messages['msg_isfreeze']}</dd>";
546                                 $msg_delete  = '';
547                                 $msg_freeze  = '<input type="radio" name="pcmd" id="_p_attach_unfreeze" value="unfreeze" />' .
548                                         '<label for="_p_attach_unfreeze">' .  $_attach_messages['msg_unfreeze'] .
549                                         $_attach_messages['msg_require'] . '</label><br />';
550                         } else {
551                                 $msg_freezed = '';
552                                 $msg_delete = '<input type="radio" name="pcmd" id="_p_attach_delete" value="delete" />' .
553                                         '<label for="_p_attach_delete">' . $_attach_messages['msg_delete'];
554                                 if (PLUGIN_ATTACH_DELETE_ADMIN_ONLY || $this->age)
555                                         $msg_delete .= $_attach_messages['msg_require'];
556                                 $msg_delete .= '</label><br />';
557                                 $msg_freeze  = '<input type="radio" name="pcmd" id="_p_attach_freeze" value="freeze" />' .
558                                         '<label for="_p_attach_freeze">' .  $_attach_messages['msg_freeze'] .
559                                         $_attach_messages['msg_require'] . '</label><br />';
560
561                                 if (PLUGIN_ATTACH_RENAME_ENABLE) {
562                                         $msg_rename  = '<input type="radio" name="pcmd" id="_p_attach_rename" value="rename" />' .
563                                                 '<label for="_p_attach_rename">' .  $_attach_messages['msg_rename'] .
564                                                 $_attach_messages['msg_require'] . '</label><br />&nbsp;&nbsp;&nbsp;&nbsp;' .
565                                                 '<label for="_p_attach_newname">' . $_attach_messages['msg_newname'] .
566                                                 ':</label> ' .
567                                                 '<input type="text" name="newname" id="_p_attach_newname" size="40" value="' .
568                                                 $this->file . '" /><br />';
569                                 }
570                         }
571                 }
572                 $info = $this->toString(TRUE, FALSE);
573                 $hash = $this->gethash();
574
575                 $retval = array('msg'=>sprintf($_attach_messages['msg_info'], htmlsc($this->file)));
576                 $retval['body'] = <<< EOD
577 <p class="small">
578  [<a href="$script?plugin=attach&amp;pcmd=list&amp;refer=$r_page">{$_attach_messages['msg_list']}</a>]
579  [<a href="$script?plugin=attach&amp;pcmd=list">{$_attach_messages['msg_listall']}</a>]
580 </p>
581 <dl>
582  <dt>$info</dt>
583  <dd>{$_attach_messages['msg_page']}:$s_page</dd>
584  <dd>{$_attach_messages['msg_filename']}:{$this->filename}</dd>
585  <dd>{$_attach_messages['msg_md5hash']}:$hash</dd>
586  <dd>{$_attach_messages['msg_filesize']}:{$this->size_str} ({$this->size} bytes)</dd>
587  <dd>Content-type:{$this->type}</dd>
588  <dd>{$_attach_messages['msg_date']}:{$this->time_str}</dd>
589  <dd>{$_attach_messages['msg_dlcount']}:{$this->status['count'][$this->age]}</dd>
590  $msg_freezed
591 </dl>
592 <hr />
593 $s_err
594 <form action="$script" method="post">
595  <div>
596   <input type="hidden" name="plugin" value="attach" />
597   <input type="hidden" name="refer" value="$s_page" />
598   <input type="hidden" name="file" value="$s_file" />
599   <input type="hidden" name="age" value="{$this->age}" />
600   $msg_delete
601   $msg_freeze
602   $msg_rename
603   <br />
604   <label for="_p_attach_password">{$_attach_messages['msg_password']}:</label>
605   <input type="password" name="pass" id="_p_attach_password" size="8" />
606   <input type="submit" value="{$_attach_messages['btn_submit']}" />
607  </div>
608 </form>
609 EOD;
610                 return $retval;
611         }
612
613         function delete($pass)
614         {
615                 global $_attach_messages, $notify, $notify_subject;
616
617                 if ($this->status['freeze']) return attach_info('msg_isfreeze');
618
619                 if (! pkwk_login($pass)) {
620                         if (PLUGIN_ATTACH_DELETE_ADMIN_ONLY || $this->age) {
621                                 return attach_info('err_adminpass');
622                         } else if (PLUGIN_ATTACH_PASSWORD_REQUIRE &&
623                                 md5($pass) !== $this->status['pass']) {
624                                 return attach_info('err_password');
625                         }
626                 }
627
628                 // バックアップ
629                 if ($this->age ||
630                         (PLUGIN_ATTACH_DELETE_ADMIN_ONLY && PLUGIN_ATTACH_DELETE_ADMIN_NOBACKUP)) {
631                         @unlink($this->filename);
632                 } else {
633                         do {
634                                 $age = ++$this->status['age'];
635                         } while (file_exists($this->basename . '.' . $age));
636
637                         if (! rename($this->basename,$this->basename . '.' . $age)) {
638                                 // 削除失敗 why?
639                                 return array('msg'=>$_attach_messages['err_delete']);
640                         }
641
642                         $this->status['count'][$age] = $this->status['count'][0];
643                         $this->status['count'][0] = 0;
644                         $this->putstatus();
645                 }
646
647                 if (is_page($this->page))
648                         touch(get_filename($this->page));
649
650                 if ($notify) {
651                         $footer['ACTION']   = 'File deleted';
652                         $footer['FILENAME'] = $this->file;
653                         $footer['PAGE']     = $this->page;
654                         $footer['URI']      = get_page_uri($this->page, PKWK_URI_ABSOLUTE);
655                         $footer['USER_AGENT']  = TRUE;
656                         $footer['REMOTE_ADDR'] = TRUE;
657                         pkwk_mail_notify($notify_subject, "\n", $footer) or
658                                 die('pkwk_mail_notify(): Failed');
659                 }
660
661                 return array('msg'=>$_attach_messages['msg_deleted']);
662         }
663
664         function rename($pass, $newname)
665         {
666                 global $_attach_messages, $notify, $notify_subject;
667
668                 if ($this->status['freeze']) return attach_info('msg_isfreeze');
669
670                 if (! pkwk_login($pass)) {
671                         if (PLUGIN_ATTACH_DELETE_ADMIN_ONLY || $this->age) {
672                                 return attach_info('err_adminpass');
673                         } else if (PLUGIN_ATTACH_PASSWORD_REQUIRE &&
674                                 md5($pass) !== $this->status['pass']) {
675                                 return attach_info('err_password');
676                         }
677                 }
678                 $newbase = UPLOAD_DIR . encode($this->page) . '_' . encode($newname);
679                 if (file_exists($newbase)) {
680                         return array('msg'=>$_attach_messages['err_exists']);
681                 }
682                 if (! PLUGIN_ATTACH_RENAME_ENABLE) {
683                         return array('msg'=>$_attach_messages['err_rename']);
684                 }
685                 if (! rename($this->basename, $newbase)) {
686                         return array('msg'=>$_attach_messages['err_rename']);
687                 }
688                 // Rename primary file succeeded.
689                 // Then, rename backup(archive) files and log file)
690                 $rename_targets = array();
691                 $dir = opendir(UPLOAD_DIR);
692                 if ($dir) {
693                         $matches_leaf = array();
694                         if (preg_match('/(((?:[0-9A-F]{2})+)_((?:[0-9A-F]{2})+))$/', $this->basename, $matches_leaf)) {
695                                 $attachfile_leafname = $matches_leaf[1];
696                                 $attachfile_leafname_pattern = preg_quote($attachfile_leafname, '/');
697                                 $pattern = "/^({$attachfile_leafname_pattern})(\.((\d+)|(log)))$/";
698                                 $matches = array();
699                                 while ($file = readdir($dir)) {
700                                         if (! preg_match($pattern, $file, $matches))
701                                                 continue;
702                                         $basename2 = $matches[0];
703                                         $newbase2 = $newbase . $matches[2];
704                                         $rename_targets[$basename2] = $newbase2;
705                                 }
706                         }
707                         closedir($dir);
708                 }
709                 foreach ($rename_targets as $basename2=>$newbase2) {
710                         $basename2path = UPLOAD_DIR . $basename2;
711                         rename($basename2path, $newbase2);
712                 }
713                 return array('msg'=>$_attach_messages['msg_renamed']);
714         }
715
716         function freeze($freeze, $pass)
717         {
718                 global $_attach_messages;
719
720                 if (! pkwk_login($pass)) return attach_info('err_adminpass');
721
722                 $this->getstatus();
723                 $this->status['freeze'] = $freeze;
724                 $this->putstatus();
725
726                 return array('msg'=>$_attach_messages[$freeze ? 'msg_freezed' : 'msg_unfreezed']);
727         }
728
729         function open()
730         {
731                 $this->getstatus();
732                 $this->status['count'][$this->age]++;
733                 $this->putstatus();
734                 $filename = $this->file;
735
736                 // Care for Japanese-character-included file name
737                 $legacy_filename = mb_convert_encoding($filename, 'UTF-8', SOURCE_ENCODING);
738                 if (LANG == 'ja') {
739                         switch(UA_NAME . '/' . UA_PROFILE){
740                         case 'MSIE/default':
741                                 $legacy_filename = mb_convert_encoding($filename, 'SJIS', SOURCE_ENCODING);
742                                 break;
743                         }
744                 }
745                 $utf8filename = mb_convert_encoding($filename, 'UTF-8', SOURCE_ENCODING);
746
747                 ini_set('default_charset', '');
748                 mb_http_output('pass');
749
750                 pkwk_common_headers();
751                 header('Content-Disposition: inline; filename="' . $legacy_filename
752                         . '"; filename*=utf-8\'\'' . rawurlencode($utf8filename));
753                 header('Content-Length: ' . $this->size);
754                 header('Content-Type: '   . $this->type);
755                 // Disable output bufferring
756                 while (ob_get_level()) {
757                         ob_end_flush();
758                 }
759                 flush();
760                 @readfile($this->filename);
761                 exit;
762         }
763 }
764
765 // ファイルコンテナ
766 class AttachFiles
767 {
768         var $page;
769         var $files = array();
770
771         function AttachFiles($page)
772         {
773                 $this->__construct($page);
774         }
775         function __construct($page)
776         {
777                 $this->page = $page;
778         }
779
780         function add($file, $age)
781         {
782                 $this->files[$file][$age] = new AttachFile($this->page, $file, $age);
783         }
784
785         // date comparison function for uasort()
786         // $a, $b: AttachFile object
787         function datecomp($a, $b) {
788                 return ($a->time == $b->time) ? 0 : (($a->time > $b->time) ? -1 : 1);
789         }
790
791         // ファイル一覧を取得
792         function toString($flat)
793         {
794                 global $_title_cannotread;
795
796                 if (! check_readable($this->page, FALSE, FALSE)) {
797                         return str_replace('$1', make_pagelink($this->page), $_title_cannotread);
798                 } else if ($flat) {
799                         return $this->to_flat();
800                 }
801
802                 $ret = '';
803                 $files = array_keys($this->files);
804                 sort($files, SORT_STRING);
805
806                 foreach ($files as $file) {
807                         $_files = array();
808                         foreach (array_keys($this->files[$file]) as $age) {
809                                 $_files[$age] = $this->files[$file][$age]->toString(FALSE, TRUE);
810                         }
811                         if (! isset($_files[0])) {
812                                 $_files[0] = htmlsc($file);
813                         }
814                         ksort($_files, SORT_NUMERIC);
815                         $_file = $_files[0];
816                         unset($_files[0]);
817                         $ret .= " <li>$_file\n";
818                         if (count($_files)) {
819                                 $ret .= "<ul>\n<li>" . join("</li>\n<li>", $_files) . "</li>\n</ul>\n";
820                         }
821                         $ret .= " </li>\n";
822                 }
823                 return make_pagelink($this->page) . "\n<ul>\n$ret</ul>\n";
824         }
825
826         // ファイル一覧を取得(inline)
827         function to_flat()
828         {
829                 $ret = '';
830                 $files = array();
831                 foreach (array_keys($this->files) as $file) {
832                         if (isset($this->files[$file][0])) {
833                                 $files[$file] = & $this->files[$file][0];
834                         }
835                 }
836                 uasort($files, array($this, 'datecomp'));
837                 foreach (array_keys($files) as $file) {
838                         $ret .= $files[$file]->toString(TRUE, TRUE) . ' ';
839                 }
840                 return $ret;
841         }
842 }
843
844 // ページコンテナ
845 class AttachPages
846 {
847         var $pages = array();
848
849         function AttachPages($page = '', $age = NULL)
850         {
851                 $this->__construct($page, $age);
852         }
853         function __construct($page = '', $age = NULL)
854         {
855
856                 $dir = opendir(UPLOAD_DIR) or
857                         die('directory ' . UPLOAD_DIR . ' is not exist or not readable.');
858
859                 $page_pattern = ($page == '') ? '(?:[0-9A-F]{2})+' : preg_quote(encode($page), '/');
860                 $age_pattern = ($age === NULL) ?
861                         '(?:\.([0-9]+))?' : ($age ?  "\.($age)" : '');
862                 $pattern = "/^({$page_pattern})_((?:[0-9A-F]{2})+){$age_pattern}$/";
863
864                 $matches = array();
865                 while (($file = readdir($dir)) !== FALSE) {
866                         if (! preg_match($pattern, $file, $matches)) continue;
867
868                         $_page = decode($matches[1]);
869                         $_file = decode($matches[2]);
870                         $_age  = isset($matches[3]) ? $matches[3] : 0;
871                         if (! isset($this->pages[$_page])) {
872                                 $this->pages[$_page] = new AttachFiles($_page);
873                         }
874                         $this->pages[$_page]->add($_file, $_age);
875                 }
876                 closedir($dir);
877         }
878
879         function toString($page = '', $flat = FALSE)
880         {
881                 if ($page != '') {
882                         if (! isset($this->pages[$page])) {
883                                 return '';
884                         } else {
885                                 return $this->pages[$page]->toString($flat);
886                         }
887                 }
888                 $ret = '';
889
890                 $pages = array_keys($this->pages);
891                 sort($pages, SORT_STRING);
892
893                 foreach ($pages as $page) {
894                         if (check_non_list($page)) continue;
895                         $ret .= '<li>' . $this->pages[$page]->toString($flat) . '</li>' . "\n";
896                 }
897                 return "\n" . '<ul>' . "\n" . $ret . '</ul>' . "\n";
898         }
899 }