OSDN Git Service

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