OSDN Git Service

Merge branch 'release/2.6.0beta2'
[ethna/ethna.git] / class / Ethna_Util.php
1 <?php
2 // vim: foldmethod=marker
3 /**
4  *  Ethna_Util.php
5  *
6  *  @author     Masaki Fujimoto <fujimoto@php.net>
7  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
8  *  @package    Ethna
9  *  @version    $Id$
10  */
11
12 // {{{ to_array
13 /**
14  *  グローバルユーティリティ関数: スカラー値を要素数1の配列として返す
15  *
16  *  @param  mixed   $v  配列として扱う値
17  *  @return array   配列に変換された値
18  */
19 function to_array($v)
20 {
21     if (is_array($v)) {
22         return $v;
23     } else {
24         return array($v);
25     }
26 }
27 // }}}
28
29 // {{{ is_error
30 /**
31  *  グローバルユーティリティ関数: 指定されたフォーム項目にエラーがあるかどうかを返す
32  *
33  *  @param  string  $name   フォーム項目名
34  *  @return bool    true:エラー有り false:エラー無し
35  */
36 function is_error($name = null)
37 {
38     $c =& Ethna_Controller::getInstance();
39     $action_error =& $c->getActionError();
40     if ($name !== null) {
41         return $action_error->isError($name);
42     } else {
43         return $action_error->count() > 0;
44     }
45 }
46 // }}}
47
48 // {{{ file_exists_ex
49 /**
50  *  グローバルユーティリティ関数: include_pathを検索しつつfile_exists()する
51  *
52  *  @param  string  $path               ファイル名
53  *  @param  bool    $use_include_path   include_pathをチェックするかどうか
54  *  @return bool    true:有り false:無し
55  */
56 function file_exists_ex($path, $use_include_path = true)
57 {
58     if ($use_include_path == false) {
59         return file_exists($path);
60     }
61
62     // check if absolute
63     if (is_absolute_path($path)) {
64         return file_exists($path);
65     }
66
67     $include_path_list = explode(PATH_SEPARATOR, get_include_path());
68     if (is_array($include_path_list) == false) {
69         return file_exists($path);
70     }
71
72     foreach ($include_path_list as $include_path) {
73         if (file_exists($include_path . DIRECTORY_SEPARATOR . $path)) {
74             return true;
75         }
76     }
77     return false;
78 }
79 // }}}
80
81 // {{{ is_absolute_path
82 /**
83  *  グローバルユーティリティ関数: 絶対パスかどうかを返す
84  *
85  *  @param  string  $path               ファイル名
86  *  @return bool    true:絶対 false:相対
87  */
88 function is_absolute_path($path)
89 {
90     if (ETHNA_OS_WINDOWS) {
91         if (preg_match('/^[a-z]:/i', $path) && $path{2} == DIRECTORY_SEPARATOR) {
92             return true;
93         }
94     } else {
95         if ($path{0} == DIRECTORY_SEPARATOR) {
96             return true;
97         }
98     }
99     return false;
100 }
101 // }}}
102
103 // {{{ Ethna_Util
104 /**
105  *  ユーティリティクラス
106  *
107  *  @author     Masaki Fujimoto <fujimoto@php.net>
108  *  @access     public
109  *  @package    Ethna
110  */
111 class Ethna_Util
112 {
113     // {{{ isDuplicatePost
114     /**
115      *  POSTのユニークチェックを行う
116      *
117      *  @access public
118      *  @return bool    true:2回目以降のPOST false:1回目のPOST
119      */
120     function isDuplicatePost()
121     {
122         $c =& Ethna_Controller::getInstance();
123
124         // use raw post data
125         if (isset($_POST['uniqid'])) {
126             $uniqid = $_POST['uniqid'];
127         } else if (isset($_GET['uniqid'])) {
128             $uniqid = $_GET['uniqid'];
129         } else {
130             return false;
131         }
132
133         // purge old files
134         Ethna_Util::purgeTmp("uniqid_", 60*60*1);
135
136         $filename = sprintf("%s/uniqid_%s_%s",
137                             $c->getDirectory('tmp'),
138                             $_SERVER['REMOTE_ADDR'],
139                             $uniqid);
140         if (file_exists($filename) == false) {
141             touch($filename);
142             return false;
143         }
144
145         $st = stat($filename);
146         if ($st[9] + 60*60*1 < time()) {
147             // too old
148             return false;
149         }
150
151         return true;
152     }
153     // }}}
154
155     // {{{ clearDuplicatePost
156     /**
157      *  POSTのユニークチェックフラグをクリアする
158      *
159      *  @acccess public
160      *  @return mixed   0:正常終了 Ethna_Error:エラー
161      */
162     function clearDuplicatePost()
163     {
164         $c =& Ethna_Controller::getInstance();
165
166         // use raw post data
167         if (isset($_POST['uniqid'])) {
168             $uniqid = $_POST['uniqid'];
169         } else {
170             return 0;
171         }
172
173         $filename = sprintf("%s/uniqid_%s_%s",
174                             $c->getDirectory('tmp'),
175                             $_SERVER['REMOTE_ADDR'],
176                             $uniqid);
177         if (file_exists($filename)) {
178             if (unlink($filename) == false) {
179                 return Ethna::raiseWarning("File Write Error [%s]", E_APP_WRITE, $filename);
180             }
181         }
182
183         return 0;
184     }
185     // }}}
186
187     // {{{ isCsrfSafeValid
188     /**
189      *  CSRFをチェックする
190      *
191      *  @access public
192      *  @return bool    true:正常なPOST false:不正なPOST
193      */
194     function isCsrfSafe()
195     {
196         $c =& Ethna_Controller::getInstance();
197         $name = $c->config->get('csrf');
198         
199         if (is_null($name)) {
200             $name = 'Session';
201         }
202         
203         $plugin =& $c->getPlugin('Csrf', $name);
204         $csrf =& $plugin->getPlugin('Csrf', $name);
205         return $csrf->isValid();
206     }
207     // }}}
208
209     // {{{ setCsrfID
210     /**
211      *  CSRFをチェックする
212      *
213      *  @access public
214      *  @return bool    true:成功
215      */
216     function setCsrfID()
217     {
218         $c =& Ethna_Controller::getInstance();
219         $name = $c->config->get('csrf');
220         
221         if (is_null($name)) {
222             $name = 'Session';
223         }
224         
225         $plugin =& $c->getPlugin('Csrf', $name);
226         $csrf =& $plugin->getPlugin('Csrf', $name);
227         return $csrf->set();
228     }
229     // }}}
230
231     // {{{ checkMailAddress
232     /**
233      *  メールアドレスが正しいかどうかをチェックする
234      *
235      *  @access public
236      *  @param  string  $mailaddress    チェックするメールアドレス
237      *  @return bool    true: 正しいメールアドレス false: 不正な形式
238      */
239     function checkMailAddress($mailaddress)
240     {
241         if (preg_match('/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i',
242                        $mailaddress)) {
243             return true;
244         }
245         return false;
246     }
247     // }}}
248
249     // {{{ explodeCSV
250     /**
251      *  CSV形式の文字列を配列に分割する
252      *
253      *  @access public
254      *  @param  string  $csv        CSV形式の文字列(1行分)
255      *  @param  string  $delimiter  フィールドの区切り文字
256      *  @return mixed   (array):分割結果 Ethna_Error:エラー(行継続)
257      */
258     function explodeCSV($csv, $delimiter = ",")
259     {
260         $space_list = '';
261         foreach (array(" ", "\t", "\r", "\n") as $c) {
262             if ($c != $delimiter) {
263                 $space_list .= $c;
264             }
265         }
266
267         $line_end = "";
268         if (preg_match("/([$space_list]+)\$/sS", $csv, $match)) {
269             $line_end = $match[1];
270         }
271         $csv = substr($csv, 0, strlen($csv)-strlen($line_end));
272         $csv .= ' ';
273
274         $field = '';
275         $retval = array();
276
277         $index = 0;
278         $csv_len = strlen($csv);
279         do {
280             // 1. skip leading spaces
281             if (preg_match("/^([$space_list]+)/sS", substr($csv, $index), $match)) {
282                 $index += strlen($match[1]);
283             }
284             if ($index >= $csv_len) {
285                 break;
286             }
287
288             // 2. read field
289             if ($csv{$index} == '"') {
290                 // 2A. handle quote delimited field
291                 $index++;
292                 while ($index < $csv_len) {
293                     if ($csv{$index} == '"') {
294                         // handle double quote
295                         if ($csv{$index+1} == '"') {
296                             $field .= $csv{$index};
297                             $index += 2;
298                         } else {
299                             // must be end of string
300                             while ($csv{$index} != $delimiter && $index < $csv_len) {
301                                 $index++;
302                             }
303                             if ($csv{$index} == $delimiter) {
304                                 $index++;
305                             }
306                             break;
307                         }
308                     } else {
309                         // normal character
310                         if (preg_match("/^([^\"]*)/S", substr($csv, $index), $match)) {
311                             $field .= $match[1];
312                             $index += strlen($match[1]);
313                         }
314
315                         if ($index == $csv_len) {
316                             $field = substr($field, 0, strlen($field)-1);
317                             $field .= $line_end;
318
319                             // request one more line
320                             return Ethna::raiseNotice('CSV Split Error (line continue)', E_UTIL_CSV_CONTINUE);
321                         }
322                     }
323                 }
324             } else {
325                 // 2B. handle non-quoted field
326                 if (preg_match("/^([^$delimiter]*)/S", substr($csv, $index), $match)) {
327                     $field .= $match[1];
328                     $index += strlen($match[1]);
329                 }
330
331                 // remove trailing spaces
332                 $field = preg_replace("/[$space_list]+\$/S", '', $field);
333                 if ($csv{$index} == $delimiter) {
334                     $index++;
335                 }
336             }
337             $retval[] = $field;
338             $field = '';
339         } while ($index < $csv_len);
340
341         return $retval;
342     }
343     // }}}
344
345     // {{{ escapeCSV
346     /**
347      *  CSVエスケープ処理を行う
348      *
349      *  @access public
350      *  @param  string  $csv        エスケープ対象の文字列(CSVの各要素)
351      *  @param  bool    $escape_nl  改行文字(\r/\n)のエスケープフラグ
352      *  @return string  CSVエスケープされた文字列
353      */
354     function escapeCSV($csv, $escape_nl = false)
355     {
356         if (preg_match('/[,"\r\n]/', $csv)) {
357             if ($escape_nl) {
358                 $csv = preg_replace('/\r/', "\\r", $csv);
359                 $csv = preg_replace('/\n/', "\\n", $csv);
360             }
361             $csv = preg_replace('/"/', "\"\"", $csv);
362             $csv = "\"$csv\"";
363         }
364
365         return $csv;
366     }
367     // }}}
368
369     // {{{ escapeHtml
370     /**
371      *  配列の要素を全てHTMLエスケープして返す
372      *
373      *  @access public
374      *  @param  array   $target     HTMLエスケープ対象となる配列
375      *  @return array   エスケープされた配列
376      */
377     function escapeHtml($target)
378     {
379         $r = array();
380         Ethna_Util::_escapeHtml($target, $r);
381         return $r;
382     }
383
384     /**
385      *  配列の要素を全てHTMLエスケープして返す
386      *
387      *  @access public
388      *  @param  mixed   $vars   HTMLエスケープ対象となる配列
389      *  @param  mixed   $retval HTMLエスケープ対象となる子要素
390      */
391     function _escapeHtml(&$vars, &$retval)
392     {
393         foreach (array_keys($vars) as $name) {
394             if (is_array($vars[$name])) {
395                 $retval[$name] = array();
396                 Ethna_Util::_escapeHtml($vars[$name], $retval[$name]);
397             } else if (!is_object($vars[$name])) {
398                 $retval[$name] = htmlspecialchars($vars[$name], ENT_QUOTES);
399             }
400         }
401     }
402     // }}}
403
404     // {{{ encode_MIME
405     /**
406      *  文字列をMIMEエンコードする
407      *
408      *  @access public
409      *  @param  string  $string     MIMEエンコードする文字列
410      *  @return エンコード済みの文字列
411      */
412     function encode_MIME($string)
413     {
414         $pos = 0;
415         $split = 36;
416         $_string = "";
417         while ($pos < mb_strlen($string))
418         {
419             $tmp = mb_strimwidth($string, $pos, $split, "");
420             $pos += mb_strlen($tmp);
421             $_string .= (($_string)? ' ' : '') . mb_encode_mimeheader($tmp, 'ISO-2022-JP');
422         }
423         return $_string;
424     }
425     // }}}
426
427     // {{{ getDirectLinkList
428     /**
429      *  Google風リンクリストを返す
430      *
431      *  @access public
432      *  @param  int     $total      検索総件数
433      *  @param  int     $offset     表示オフセット
434      *  @param  int     $count      表示件数
435      *  @return array   リンク情報を格納した配列
436      */
437     function getDirectLinkList($total, $offset, $count)
438     {
439         $direct_link_list = array();
440
441         if ($total == 0) {
442             return array();
443         }
444
445         // backwards
446         $current = $offset - $count;
447         while ($current > 0) {
448             array_unshift($direct_link_list, $current);
449             $current -= $count;
450         }
451         if ($offset != 0 && $current <= 0) {
452             array_unshift($direct_link_list, 0);
453         }
454
455         // current
456         $backward_count = count($direct_link_list);
457         array_push($direct_link_list, $offset);
458
459         // forwards
460         $current = $offset + $count;
461         for ($i = 0; $i < 10; $i++) {
462             if ($current >= $total) {
463                 break;
464             }
465             array_push($direct_link_list, $current);
466             $current += $count;
467         }
468         $forward_count = count($direct_link_list) - $backward_count - 1;
469
470         $backward_count -= 4;
471         if ($forward_count < 5) {
472             $backward_count -= 5 - $forward_count;
473         }
474         if ($backward_count < 0) {
475             $backward_count = 0;
476         }
477
478         // add index
479         $n = 1;
480         $r = array();
481         foreach ($direct_link_list as $direct_link) {
482             $v = array('offset' => $direct_link, 'index' => $n);
483             $r[] = $v;
484             $n++;
485         }
486
487         return array_splice($r, $backward_count, 10);
488     }
489     // }}}
490
491     // {{{ getEra
492     /**
493      *  元号制での年を返す
494      *
495      *  @access public
496      *  @param  int     $t      unix time
497      *  @return string  元号(不明な場合はnull)
498      */
499     function getEra($t)
500     {
501         $tm = localtime($t, true);
502         $year = $tm['tm_year'] + 1900;
503
504         if ($year >= 1989) {
505             $heisei_str = _et('Heisei');
506             return array($heisei_str, $year - 1988);
507         } else if ($year >= 1926) {
508             $showa_str = _et('Showa');
509             return array($showa_str, $year - 1925);
510         }
511
512         return null;
513     }
514     // }}}
515
516     // {{{ getImageExtName
517     /**
518      *  getimagesize()の返すイメージタイプに対応する拡張子を返す
519      *
520      *  @access public
521      *  @param  int     $type   getimagesize()関数の返すイメージタイプ
522      *  @return string  $typeに対応する拡張子
523      */
524     function getImageExtName($type)
525     {
526         $ext_list = array(
527             1   => 'gif',
528             2   => 'jpg',
529             3   => 'png',
530             4   => 'swf',
531             5   => 'psd',
532             6   => 'bmp',
533             7   => 'tiff',
534             8   => 'tiff',
535             9   => 'jpc',
536             10  => 'jp2',
537             11  => 'jpx',
538             12  => 'jb2',
539             13  => 'swc',
540             14  => 'iff',
541             15  => 'wbmp',
542             16  => 'xbm',
543         );
544
545         return @$ext_list[$type];
546     }
547     // }}}
548
549     // {{{ getRandom
550     /**
551      *  ランダムなハッシュ値を生成する
552      *
553      *  決して高速ではないので乱用は避けること
554      *
555      *  @access public
556      *  @param  int     $length ハッシュ値の長さ(〜64)
557      *  @return string  ハッシュ値
558      */
559     function getRandom($length = 64)
560     {
561         static $srand = false;
562
563         if ($srand == false) {
564             list($usec, $sec) = explode(' ', microtime());
565             mt_srand((float) $sec + ((float) $usec * 100000) + getmypid());
566             $srand = true;
567         }
568
569         // open_basedir がオンで、かつ /proc が許可されているか?
570         // open_basedir が空なら許可されていると看做す
571         $devfile = '/proc/net/dev';
572         $open_basedir_conf = ini_get('open_basedir');
573         $devfile_enabled = (empty($open_basedir_conf) 
574                         || (preg_match('#:/proc#', $open_basedir_conf) > 0
575                         ||  preg_match('#^/proc#', $open_basedir_conf) > 0));
576
577         $value = "";
578         for ($i = 0; $i < 2; $i++) {
579             // for Linux
580             if ($devfile_enabled && file_exists($devfile)) {
581                 $rx = $tx = 0;
582                 $fp = fopen($devfile, 'r');
583                 if ($fp != null) {
584                     $header = true;
585                     while (feof($fp) === false) {
586                         $s = fgets($fp, 4096);
587                         if ($header) {
588                             $header = false;
589                             continue;
590                         }
591                         $v = preg_split('/[:\s]+/', $s);
592                         if (is_array($v) && count($v) > 10) {
593                             $rx += $v[2];
594                             $tx += $v[10];
595                         }
596                     }
597                 }
598                 $platform_value = $rx . $tx . mt_rand() . getmypid();
599             } else {
600                 $platform_value = mt_rand() . getmypid();
601             }
602             $now = strftime('%Y%m%d %T');
603             $time = gettimeofday();
604             $v = $now . $time['usec'] . $platform_value . mt_rand(0, time());
605             $value .= md5($v);
606         }
607
608         if ($length < 64) {
609             $value = substr($value, 0, $length);
610         }
611         return $value;
612     }
613     // }}}
614
615     // {{{ get2dArray
616     /**
617      *  1次元配列をm x nに再構成する
618      *
619      *  @access public
620      *  @param  array   $array  処理対象の1次元配列
621      *  @param  int     $m      軸の要素数
622      *  @param  int     $order  $mをX軸と見做すかY軸と見做すか(0:X軸 1:Y軸)
623      *  @return array   m x nに再構成された配列
624      */
625     function get2dArray($array, $m, $order)
626     {
627         $r = array();
628         
629         $n = intval(count($array) / $m);
630         if ((count($array) % $m) > 0) {
631             $n++;
632         }
633         for ($i = 0; $i < $n; $i++) {
634             $elts = array();
635             for ($j = 0; $j < $m; $j++) {
636                 if ($order == 0) {
637                     // 横並び(横:$m列 縦:無制限)
638                     $key = $i*$m+$j;
639                 } else {
640                     // 縦並び(横:無制限 縦:$m行)
641                     $key = $i+$n*$j;
642                 }
643                 if (array_key_exists($key, $array) == false) {
644                     $array[$key] = null;
645                 }
646                 $elts[] = $array[$key];
647             }
648             $r[] = $elts;
649         }
650
651         return $r;
652     }
653     // }}}
654
655     // {{{ isAbsolute
656     /**
657      *  パス名が絶対パスかどうかを返す
658      *
659      *  port from File in PEAR (for BC)
660      *
661      *  @access public
662      *  @param  string  $path
663      *  @return bool    true:絶対パス false:相対パス
664      */
665     function isAbsolute($path)
666     {
667         if (preg_match("/\.\./", $path)) {
668             return false;
669         }
670
671         if (DIRECTORY_SEPARATOR == '/'
672             && (substr($path, 0, 1) == '/' || substr($path, 0, 1) == '~')) {
673             return true;
674         } else if (DIRECTORY_SEPARATOR == '\\' && preg_match('/^[a-z]:\\\/i', $path)) {
675             return true;
676         }
677
678         return false;
679     }
680     // }}}
681
682     // {{{ isRootDir
683     /**
684      *  パス名がルートディレクトリかどうかを返す
685      *
686      *  @access public
687      *  @param  string  $path
688      *  @static
689      */
690     function isRootDir($path)
691     {
692         if ($path === DIRECTORY_SEPARATOR) {
693             // avoid stat().
694             return true;
695         }
696         if (is_dir($path) === false) {
697             return false;
698         }
699         return $path === basename($path) . DIRECTORY_SEPARATOR;
700     }
701     // }}}
702
703     // {{{ mkdir
704     /**
705      *  mkdir -p
706      *
707      *  @access public
708      *  @param  string  $dir    作成するディレクトリ
709      *  @param  int     $mode   パーミッション
710      *  @return bool    true:成功 false:失敗
711      *  @static
712      */
713     function mkdir($dir, $mode)
714     {
715         if (file_exists($dir)) {
716             return is_dir($dir);
717         }
718
719         $parent = dirname($dir);
720         if ($dir === $parent) {
721             return true;
722         }
723
724         if (is_dir($parent) === false) {
725             if (Ethna_Util::mkdir($parent, $mode) === false) {
726                 return false;
727             }
728         }
729
730         return mkdir($dir, $mode) && Ethna_Util::chmod($dir, $mode);
731     }
732     // }}}
733
734     // {{{ chmod
735     /**
736      *  ファイルのパーミッションを変更する
737      */
738     function chmod($file, $mode)
739     {
740         $st = stat($file);
741         if (($st[2] & 0777) == $mode) {
742             return true;
743         }
744         return chmod($file, $mode);
745     }
746     // }}}
747
748     // {{{ purgeDir
749     /**
750      *  ディレクトリを再帰的に削除する
751      *  (途中で失敗しても中断せず、削除できるものはすべて消す)
752      *
753      *  @access public
754      *  @param  string  $file   削除するファイルまたはディレクトリ
755      *  @return bool    true:成功 false:失敗
756      *  @static
757      */
758     function purgeDir($dir)
759     {
760         if (file_exists($dir) === false) {
761             return false;
762         }
763         if (is_dir($dir) === false) {
764             return unlink($dir);
765         }
766
767         $dh = opendir($dir);
768         if ($dh === false) {
769             return false;
770         }
771         $ret = true;
772         while (($entry = readdir($dh)) !== false) {
773             if ($entry === '.' || $entry === '..') {
774                 continue;
775             }
776             $ret = $ret && Ethna_Util::purgeDir("{$dir}/{$entry}");
777         }
778         closedir($dh);
779         if ($ret) {
780             return rmdir($dir);
781         } else {
782             return false;
783         }
784     }
785     // }}}
786
787     // {{{ purgeTmp
788     /**
789      *  テンポラリディレクトリのファイルを削除する
790      *
791      *  @access public
792      *  @param  string  $prefix     ファイルのプレフィクス
793      *  @param  int     $timeout    削除対象閾値(秒−60*60*1なら1時間)
794      */
795     function purgeTmp($prefix, $timeout)
796     {
797         $c =& Ethna_Controller::getInstance();
798
799         $dh = opendir($c->getDirectory('tmp'));
800         if ($dh) {
801             while (($file = readdir($dh)) !== false) {
802                 if ($file == '.' || $file == '..') {
803                     continue;
804                 } else if (is_dir($c->getDirectory('tmp') . '/' . $file)) {
805                     continue;
806                 } else if (strncmp($file, $prefix, strlen($prefix)) == 0) {
807                     $f = $c->getDirectory('tmp') . "/" . $file;
808                     $st = stat($f);
809                     if ($st[9] + $timeout < time()) {
810                         unlink($f);
811                     }
812                 }
813             }
814             closedir($dh);
815         }
816     }
817     // }}}
818
819     // {{{ lockFile
820     /**
821      *  ファイルをロックする
822      *
823      *  @access public
824      *  @param  string  $file       ロックするファイル名
825      *  @param  int     $mode       ロックモード('r', 'rw')
826      *  @param  int     $timeout    ロック待ちタイムアウト(秒−0なら無限)
827      *  @return int     ロックハンドル(falseならエラー)
828      */
829     function lockFile($file, $mode, $timeout = 0)
830     {
831         if (file_exists($file) === false) {
832             touch($file);
833         }
834         $lh = fopen($file, $mode);
835         if ($lh == null) {
836             return Ethna::raiseError("File Read Error [%s]", E_APP_READ, $file);
837         }
838
839         $lock_mode = $mode == 'r' ? LOCK_SH : LOCK_EX;
840
841         for ($i = 0; $i < $timeout || $timeout == 0; $i++) {
842             $r = flock($lh, $lock_mode | LOCK_NB);
843             if ($r == true) {
844                 break;
845             }
846             sleep(1);
847         }
848         if ($timeout > 0 && $i == $timeout) {
849             // timed out
850             return Ethna::raiseError("File lock get error [%s]", E_APP_LOCK, $file);
851         }
852
853         return $lh;
854     }
855     // }}}
856
857     // {{{ unlockFile
858     /**
859      *  ファイルのロックを解除する
860      *
861      *  @access public
862      *  @param  int     $lh     ロックハンドル
863      */
864     function unlockFile($lh)
865     {
866         fclose($lh);
867     }
868     // }}}
869
870     // {{{ formatBacktrace
871     /**
872      *  バックトレースをフォーマットして返す
873      *
874      *  @access public
875      *  @param  array   $bt     debug_backtrace()関数で取得したバックトレース
876      *  @return string  文字列にフォーマットされたバックトレース
877      */
878     function formatBacktrace($bt) 
879     {
880         $r = "";
881         $i = 0;
882         foreach ($bt as $elt) {
883             $r .= sprintf("[%02d] %s:%d:%s.%s\n", $i,
884                           isset($elt['file']) ? $elt['file'] : 'unknown file',
885                           isset($elt['line']) ? $elt['line'] : 'unknown line',
886                           isset($elt['class']) ? $elt['class'] : 'global',
887                           $elt['function']);
888             $i++;
889
890             if (isset($elt['args']) == false || is_array($elt['args']) == false) {
891                 continue;
892             }
893
894             // 引数のダンプ
895             foreach ($elt['args'] as $arg) {
896                 $r .= Ethna_Util::_formatBacktrace($arg);
897             }
898         }
899
900         return $r;
901     }
902
903     /**
904      *  バックトレース引数をフォーマットして返す
905      *
906      *  @access private
907      *  @param  string  $arg    バックトレースの引数
908      *  @param  int     $level  バックトレースのネストレベル
909      *  @param  int     $wrap   改行フラグ
910      *  @return string  文字列にフォーマットされたバックトレース
911      */
912     function _formatBacktrace($arg, $level = 0, $wrap = true)
913     {
914         $pad = str_repeat("  ", $level);
915         if (is_array($arg)) {
916             $r = sprintf("     %s[array] => (\n", $pad);
917             if ($level+1 > 4) {
918                 $r .= sprintf("     %s  *too deep*\n", $pad);
919             } else {
920                 foreach ($arg as $key => $elt) {
921                     $r .= Ethna_Util::_formatBacktrace($key, $level, false);
922                     $r .= " => \n";
923                     $r .= Ethna_Util::_formatBacktrace($elt, $level+1);
924                 }
925             }
926             $r .= sprintf("     %s)\n", $pad);
927         } else if (is_object($arg)) {
928             $r = sprintf("     %s[object]%s%s", $pad, get_class($arg), $wrap ? "\n" : "");
929         } else {
930             $r = sprintf("     %s[%s]%s%s", $pad, gettype($arg), $arg, $wrap ? "\n" : "");
931         }
932
933         return $r;
934     }
935     // }}}
936 }
937 // }}}