OSDN Git Service

115d6927b87b6e26f875fab3089c0a2435d63759
[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 (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         $value = "";
570         for ($i = 0; $i < 2; $i++) {
571             // for Linux
572             if (file_exists('/proc/net/dev')) {
573                 $rx = $tx = 0;
574                 $fp = fopen('/proc/net/dev', 'r');
575                 if ($fp != null) {
576                     $header = true;
577                     while (feof($fp) === false) {
578                         $s = fgets($fp, 4096);
579                         if ($header) {
580                             $header = false;
581                             continue;
582                         }
583                         $v = preg_split('/[:\s]+/', $s);
584                         if (is_array($v) && count($v) > 10) {
585                             $rx += $v[2];
586                             $tx += $v[10];
587                         }
588                     }
589                 }
590                 $platform_value = $rx . $tx . mt_rand() . getmypid();
591             } else {
592                 $platform_value = mt_rand() . getmypid();
593             }
594             $now = strftime('%Y%m%d %T');
595             $time = gettimeofday();
596             $v = $now . $time['usec'] . $platform_value . mt_rand(0, time());
597             $value .= md5($v);
598         }
599
600         if ($length < 64) {
601             $value = substr($value, 0, $length);
602         }
603         return $value;
604     }
605     // }}}
606
607     // {{{ get2dArray
608     /**
609      *  1次元配列をm x nに再構成する
610      *
611      *  @access public
612      *  @param  array   $array  処理対象の1次元配列
613      *  @param  int     $m      軸の要素数
614      *  @param  int     $order  $mをX軸と見做すかY軸と見做すか(0:X軸 1:Y軸)
615      *  @return array   m x nに再構成された配列
616      */
617     function get2dArray($array, $m, $order)
618     {
619         $r = array();
620         
621         $n = intval(count($array) / $m);
622         if ((count($array) % $m) > 0) {
623             $n++;
624         }
625         for ($i = 0; $i < $n; $i++) {
626             $elts = array();
627             for ($j = 0; $j < $m; $j++) {
628                 if ($order == 0) {
629                     // 横並び(横:$m列 縦:無制限)
630                     $key = $i*$m+$j;
631                 } else {
632                     // 縦並び(横:無制限 縦:$m行)
633                     $key = $i+$n*$j;
634                 }
635                 if (array_key_exists($key, $array) == false) {
636                     $array[$key] = null;
637                 }
638                 $elts[] = $array[$key];
639             }
640             $r[] = $elts;
641         }
642
643         return $r;
644     }
645     // }}}
646
647     // {{{ isAbsolute
648     /**
649      *  パス名が絶対パスかどうかを返す
650      *
651      *  port from File in PEAR (for BC)
652      *
653      *  @access public
654      *  @param  string  $path
655      *  @return bool    true:絶対パス false:相対パス
656      */
657     function isAbsolute($path)
658     {
659         if (preg_match("/\.\./", $path)) {
660             return false;
661         }
662
663         if (DIRECTORY_SEPARATOR == '/'
664             && (substr($path, 0, 1) == '/' || substr($path, 0, 1) == '~')) {
665             return true;
666         } else if (DIRECTORY_SEPARATOR == '\\' && preg_match('/^[a-z]:\\\/i', $path)) {
667             return true;
668         }
669
670         return false;
671     }
672     // }}}
673
674     // {{{ isRootDir
675     /**
676      *  パス名がルートディレクトリかどうかを返す
677      *
678      *  @access public
679      *  @param  string  $path
680      *  @static
681      */
682     function isRootDir($path)
683     {
684         if ($path === DIRECTORY_SEPARATOR) {
685             // avoid stat().
686             return true;
687         }
688         if (is_dir($path) === false) {
689             return false;
690         }
691         return $path === basename($path) . DIRECTORY_SEPARATOR;
692     }
693     // }}}
694
695     // {{{ mkdir
696     /**
697      *  mkdir -p
698      *
699      *  @access public
700      *  @param  string  $dir    作成するディレクトリ
701      *  @param  int     $mode   パーミッション
702      *  @return bool    true:成功 false:失敗
703      *  @static
704      */
705     function mkdir($dir, $mode)
706     {
707         if (file_exists($dir)) {
708             return is_dir($dir);
709         }
710
711         $parent = dirname($dir);
712         if ($dir === $parent) {
713             return true;
714         }
715
716         if (is_dir($parent) === false) {
717             if (Ethna_Util::mkdir($parent, $mode) === false) {
718                 return false;
719             }
720         }
721
722         return mkdir($dir, $mode) && Ethna_Util::chmod($dir, $mode);
723     }
724     // }}}
725
726     // {{{ chmod
727     /**
728      *  ファイルのパーミッションを変更する
729      */
730     function chmod($file, $mode)
731     {
732         $st = stat($file);
733         if (($st[2] & 0777) == $mode) {
734             return true;
735         }
736         return chmod($file, $mode);
737     }
738     // }}}
739
740     // {{{ purgeDir
741     /**
742      *  ディレクトリを再帰的に削除する
743      *  (途中で失敗しても中断せず、削除できるものはすべて消す)
744      *
745      *  @access public
746      *  @param  string  $file   削除するファイルまたはディレクトリ
747      *  @return bool    true:成功 false:失敗
748      *  @static
749      */
750     function purgeDir($dir)
751     {
752         if (file_exists($dir) === false) {
753             return false;
754         }
755         if (is_dir($dir) === false) {
756             return unlink($dir);
757         }
758
759         $dh = opendir($dir);
760         if ($dh === false) {
761             return false;
762         }
763         $ret = true;
764         while (($entry = readdir($dh)) !== false) {
765             if ($entry === '.' || $entry === '..') {
766                 continue;
767             }
768             $ret = $ret && Ethna_Util::purgeDir("{$dir}/{$entry}");
769         }
770         closedir($dh);
771         if ($ret) {
772             return rmdir($dir);
773         } else {
774             return false;
775         }
776     }
777     // }}}
778
779     // {{{ purgeTmp
780     /**
781      *  テンポラリディレクトリのファイルを削除する
782      *
783      *  @access public
784      *  @param  string  $prefix     ファイルのプレフィクス
785      *  @param  int     $timeout    削除対象閾値(秒−60*60*1なら1時間)
786      */
787     function purgeTmp($prefix, $timeout)
788     {
789         $c =& Ethna_Controller::getInstance();
790
791         $dh = opendir($c->getDirectory('tmp'));
792         if ($dh) {
793             while (($file = readdir($dh)) !== false) {
794                 if ($file == '.' || $file == '..') {
795                     continue;
796                 } else if (is_dir($c->getDirectory('tmp') . '/' . $file)) {
797                     continue;
798                 } else if (strncmp($file, $prefix, strlen($prefix)) == 0) {
799                     $f = $c->getDirectory('tmp') . "/" . $file;
800                     $st = stat($f);
801                     if ($st[9] + $timeout < time()) {
802                         unlink($f);
803                     }
804                 }
805             }
806             closedir($dh);
807         }
808     }
809     // }}}
810
811     // {{{ lockFile
812     /**
813      *  ファイルをロックする
814      *
815      *  @access public
816      *  @param  string  $file       ロックするファイル名
817      *  @param  int     $mode       ロックモード('r', 'rw')
818      *  @param  int     $timeout    ロック待ちタイムアウト(秒−0なら無限)
819      *  @return int     ロックハンドル(falseならエラー)
820      */
821     function lockFile($file, $mode, $timeout = 0)
822     {
823         if (file_exists($file) === false) {
824             touch($file);
825         }
826         $lh = fopen($file, 'r');
827         if ($lh == null) {
828             return Ethna::raiseError("File Read Error [%s]", E_APP_READ, $file);
829         }
830
831         $lock_mode = $mode == 'r' ? LOCK_SH : LOCK_EX;
832
833         for ($i = 0; $i < $timeout || $timeout == 0; $i++) {
834             $r = flock($lh, $lock_mode | LOCK_NB);
835             if ($r == true) {
836                 break;
837             }
838             sleep(1);
839         }
840         if ($timeout > 0 && $i == $timeout) {
841             // timed out
842             return Ethna::raiseError("File lock get error [%s]", E_APP_LOCK, $file);
843         }
844
845         return $lh;
846     }
847     // }}}
848
849     // {{{ unlockFile
850     /**
851      *  ファイルのロックを解除する
852      *
853      *  @access public
854      *  @param  int     $lh     ロックハンドル
855      */
856     function unlockFile($lh)
857     {
858         fclose($lh);
859     }
860     // }}}
861
862     // {{{ formatBacktrace
863     /**
864      *  バックトレースをフォーマットして返す
865      *
866      *  @access public
867      *  @param  array   $bt     debug_backtrace()関数で取得したバックトレース
868      *  @return string  文字列にフォーマットされたバックトレース
869      */
870     function formatBacktrace($bt) 
871     {
872         $r = "";
873         $i = 0;
874         foreach ($bt as $elt) {
875             $r .= sprintf("[%02d] %s:%d:%s.%s\n", $i,
876                           isset($elt['file']) ? $elt['file'] : 'unknown file',
877                           isset($elt['line']) ? $elt['line'] : 'unknown line',
878                           isset($elt['class']) ? $elt['class'] : 'global',
879                           $elt['function']);
880             $i++;
881
882             if (isset($elt['args']) == false || is_array($elt['args']) == false) {
883                 continue;
884             }
885
886             // 引数のダンプ
887             foreach ($elt['args'] as $arg) {
888                 $r .= Ethna_Util::_formatBacktrace($arg);
889             }
890         }
891
892         return $r;
893     }
894
895     /**
896      *  バックトレース引数をフォーマットして返す
897      *
898      *  @access private
899      *  @param  string  $arg    バックトレースの引数
900      *  @param  int     $level  バックトレースのネストレベル
901      *  @param  int     $wrap   改行フラグ
902      *  @return string  文字列にフォーマットされたバックトレース
903      */
904     function _formatBacktrace($arg, $level = 0, $wrap = true)
905     {
906         $pad = str_repeat("  ", $level);
907         if (is_array($arg)) {
908             $r = sprintf("     %s[array] => (\n", $pad);
909             if ($level+1 > 4) {
910                 $r .= sprintf("     %s  *too deep*\n", $pad);
911             } else {
912                 foreach ($arg as $key => $elt) {
913                     $r .= Ethna_Util::_formatBacktrace($key, $level, false);
914                     $r .= " => \n";
915                     $r .= Ethna_Util::_formatBacktrace($elt, $level+1);
916                 }
917             }
918             $r .= sprintf("     %s)\n", $pad);
919         } else if (is_object($arg)) {
920             $r = sprintf("     %s[object]%s%s", $pad, get_class($arg), $wrap ? "\n" : "");
921         } else {
922             $r = sprintf("     %s[%s]%s%s", $pad, gettype($arg), $arg, $wrap ? "\n" : "");
923         }
924
925         return $r;
926     }
927     // }}}
928 }
929 // }}}
930 ?>