2 // vim: foldmethod=marker
6 * @author Masaki Fujimoto <fujimoto@php.net>
7 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
14 * グローバルユーティリティ関数: スカラー値を要素数1の配列として返す
16 * @param mixed $v 配列として扱う値
17 * @return array 配列に変換された値
31 * グローバルユーティリティ関数: 指定されたフォーム項目にエラーがあるかどうかを返す
33 * @param string $name フォーム項目名
34 * @return bool true:エラー有り false:エラー無し
36 function is_error($name = null)
38 $c =& Ethna_Controller::getInstance();
39 $action_error =& $c->getActionError();
41 return $action_error->isError($name);
43 return $action_error->count() > 0;
50 * グローバルユーティリティ関数: include_pathを検索しつつfile_exists()する
52 * @param string $path ファイル名
53 * @param bool $use_include_path include_pathをチェックするかどうか
54 * @return bool true:有り false:無し
56 function file_exists_ex($path, $use_include_path = true)
58 if ($use_include_path == false) {
59 return file_exists($path);
63 if (is_absolute_path($path)) {
64 return file_exists($path);
67 $include_path_list = explode(PATH_SEPARATOR, get_include_path());
68 if (is_array($include_path_list) == false) {
69 return file_exists($path);
72 foreach ($include_path_list as $include_path) {
73 if (file_exists($include_path . DIRECTORY_SEPARATOR . $path)) {
81 // {{{ is_absolute_path
83 * グローバルユーティリティ関数: 絶対パスかどうかを返す
85 * @param string $path ファイル名
86 * @return bool true:絶対 false:相対
88 function is_absolute_path($path)
90 if (ETHNA_OS_WINDOWS) {
91 if (preg_match('/^[a-z]:/i', $path) && $path{2} == DIRECTORY_SEPARATOR) {
95 if ($path{0} == DIRECTORY_SEPARATOR) {
107 * @author Masaki Fujimoto <fujimoto@php.net>
113 // {{{ isDuplicatePost
118 * @return bool true:2回目以降のPOST false:1回目のPOST
120 function isDuplicatePost()
122 $c =& Ethna_Controller::getInstance();
125 if (isset($_POST['uniqid'])) {
126 $uniqid = $_POST['uniqid'];
127 } else if (isset($_GET['uniqid'])) {
128 $uniqid = $_GET['uniqid'];
134 Ethna_Util::purgeTmp("uniqid_", 60*60*1);
136 $filename = sprintf("%s/uniqid_%s_%s",
137 $c->getDirectory('tmp'),
138 $_SERVER['REMOTE_ADDR'],
140 if (file_exists($filename) == false) {
145 $st = stat($filename);
146 if ($st[9] + 60*60*1 < time()) {
155 // {{{ clearDuplicatePost
157 * POSTのユニークチェックフラグをクリアする
160 * @return mixed 0:正常終了 Ethna_Error:エラー
162 function clearDuplicatePost()
164 $c =& Ethna_Controller::getInstance();
167 if (isset($_POST['uniqid'])) {
168 $uniqid = $_POST['uniqid'];
173 $filename = sprintf("%s/uniqid_%s_%s",
174 $c->getDirectory('tmp'),
175 $_SERVER['REMOTE_ADDR'],
177 if (file_exists($filename)) {
178 if (unlink($filename) == false) {
179 return Ethna::raiseWarning("File Write Error [%s]", E_APP_WRITE, $filename);
187 // {{{ isCsrfSafeValid
192 * @return bool true:正常なPOST false:不正なPOST
194 function isCsrfSafe()
196 $c =& Ethna_Controller::getInstance();
197 $name = $c->config->get('csrf');
199 if (is_null($name)) {
203 $plugin =& $c->getPlugin('Csrf', $name);
204 $csrf =& $plugin->getPlugin('Csrf', $name);
205 return $csrf->isValid();
214 * @return bool true:成功
218 $c =& Ethna_Controller::getInstance();
219 $name = $c->config->get('csrf');
221 if (is_null($name)) {
225 $plugin =& $c->getPlugin('Csrf', $name);
226 $csrf =& $plugin->getPlugin('Csrf', $name);
231 // {{{ checkMailAddress
233 * メールアドレスが正しいかどうかをチェックする
236 * @param string $mailaddress チェックするメールアドレス
237 * @return bool true: 正しいメールアドレス false: 不正な形式
239 function checkMailAddress($mailaddress)
241 if (preg_match('/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i',
254 * @param string $csv CSV形式の文字列(1行分)
255 * @param string $delimiter フィールドの区切り文字
256 * @return mixed (array):分割結果 Ethna_Error:エラー(行継続)
258 function explodeCSV($csv, $delimiter = ",")
261 foreach (array(" ", "\t", "\r", "\n") as $c) {
262 if ($c != $delimiter) {
268 if (preg_match("/([$space_list]+)\$/sS", $csv, $match)) {
269 $line_end = $match[1];
271 $csv = substr($csv, 0, strlen($csv)-strlen($line_end));
278 $csv_len = strlen($csv);
280 // 1. skip leading spaces
281 if (preg_match("/^([$space_list]+)/sS", substr($csv, $index), $match)) {
282 $index += strlen($match[1]);
284 if ($index >= $csv_len) {
289 if ($csv{$index} == '"') {
290 // 2A. handle quote delimited field
292 while ($index < $csv_len) {
293 if ($csv{$index} == '"') {
294 // handle double quote
295 if ($csv{$index+1} == '"') {
296 $field .= $csv{$index};
299 // must be end of string
300 while ($csv{$index} != $delimiter && $index < $csv_len) {
303 if ($csv{$index} == $delimiter) {
310 if (preg_match("/^([^\"]*)/S", substr($csv, $index), $match)) {
312 $index += strlen($match[1]);
315 if ($index == $csv_len) {
316 $field = substr($field, 0, strlen($field)-1);
319 // request one more line
320 return Ethna::raiseNotice('CSV Split Error (line continue)', E_UTIL_CSV_CONTINUE);
325 // 2B. handle non-quoted field
326 if (preg_match("/^([^$delimiter]*)/S", substr($csv, $index), $match)) {
328 $index += strlen($match[1]);
331 // remove trailing spaces
332 $field = preg_replace("/[$space_list]+\$/S", '', $field);
333 if ($csv{$index} == $delimiter) {
339 } while ($index < $csv_len);
350 * @param string $csv エスケープ対象の文字列(CSVの各要素)
351 * @param bool $escape_nl 改行文字(\r/\n)のエスケープフラグ
352 * @return string CSVエスケープされた文字列
354 function escapeCSV($csv, $escape_nl = false)
356 if (preg_match('/[,"\r\n]/', $csv)) {
358 $csv = preg_replace('/\r/', "\\r", $csv);
359 $csv = preg_replace('/\n/', "\\n", $csv);
361 $csv = preg_replace('/"/', "\"\"", $csv);
371 * 配列の要素を全てHTMLエスケープして返す
374 * @param array $target HTMLエスケープ対象となる配列
375 * @return array エスケープされた配列
377 function escapeHtml($target)
380 Ethna_Util::_escapeHtml($target, $r);
385 * 配列の要素を全てHTMLエスケープして返す
388 * @param mixed $vars HTMLエスケープ対象となる配列
389 * @param mixed $retval HTMLエスケープ対象となる子要素
391 function _escapeHtml(&$vars, &$retval)
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);
409 * @param string $string MIMEエンコードする文字列
410 * @return エンコード済みの文字列
412 function encode_MIME($string)
417 while ($pos < mb_strlen($string))
419 $tmp = mb_strimwidth($string, $pos, $split, "");
420 $pos += mb_strlen($tmp);
421 $_string .= (($_string)? ' ' : '') . mb_encode_mimeheader($tmp, 'ISO-2022-JP');
427 // {{{ getDirectLinkList
432 * @param int $total 検索総件数
433 * @param int $offset 表示オフセット
434 * @param int $count 表示件数
435 * @return array リンク情報を格納した配列
437 function getDirectLinkList($total, $offset, $count)
439 $direct_link_list = array();
446 $current = $offset - $count;
447 while ($current > 0) {
448 array_unshift($direct_link_list, $current);
451 if ($offset != 0 && $current <= 0) {
452 array_unshift($direct_link_list, 0);
456 $backward_count = count($direct_link_list);
457 array_push($direct_link_list, $offset);
460 $current = $offset + $count;
461 for ($i = 0; $i < 10; $i++) {
462 if ($current >= $total) {
465 array_push($direct_link_list, $current);
468 $forward_count = count($direct_link_list) - $backward_count - 1;
470 $backward_count -= 4;
471 if ($forward_count < 5) {
472 $backward_count -= 5 - $forward_count;
474 if ($backward_count < 0) {
481 foreach ($direct_link_list as $direct_link) {
482 $v = array('offset' => $direct_link, 'index' => $n);
487 return array_splice($r, $backward_count, 10);
496 * @param int $t unix time
497 * @return string 元号(不明な場合はnull)
501 $tm = localtime($t, true);
502 $year = $tm['tm_year'] + 1900;
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);
516 // {{{ getImageExtName
518 * getimagesize()の返すイメージタイプに対応する拡張子を返す
521 * @param int $type getimagesize()関数の返すイメージタイプ
522 * @return string $typeに対応する拡張子
524 function getImageExtName($type)
545 return @$ext_list[$type];
553 * 決して高速ではないので乱用は避けること
556 * @param int $length ハッシュ値の長さ(〜64)
557 * @return string ハッシュ値
559 function getRandom($length = 64)
561 static $srand = false;
563 if ($srand == false) {
564 list($usec, $sec) = explode(' ', microtime());
565 mt_srand((float) $sec + ((float) $usec * 100000) + getmypid());
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));
578 for ($i = 0; $i < 2; $i++) {
580 if ($devfile_enabled && file_exists($devfile)) {
582 $fp = fopen($devfile, 'r');
585 while (feof($fp) === false) {
586 $s = fgets($fp, 4096);
591 $v = preg_split('/[:\s]+/', $s);
592 if (is_array($v) && count($v) > 10) {
598 $platform_value = $rx . $tx . mt_rand() . getmypid();
600 $platform_value = mt_rand() . getmypid();
602 $now = strftime('%Y%m%d %T');
603 $time = gettimeofday();
604 $v = $now . $time['usec'] . $platform_value . mt_rand(0, time());
609 $value = substr($value, 0, $length);
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に再構成された配列
625 function get2dArray($array, $m, $order)
629 $n = intval(count($array) / $m);
630 if ((count($array) % $m) > 0) {
633 for ($i = 0; $i < $n; $i++) {
635 for ($j = 0; $j < $m; $j++) {
643 if (array_key_exists($key, $array) == false) {
646 $elts[] = $array[$key];
659 * port from File in PEAR (for BC)
662 * @param string $path
663 * @return bool true:絶対パス false:相対パス
665 function isAbsolute($path)
667 if (preg_match("/\.\./", $path)) {
671 if (DIRECTORY_SEPARATOR == '/'
672 && (substr($path, 0, 1) == '/' || substr($path, 0, 1) == '~')) {
674 } else if (DIRECTORY_SEPARATOR == '\\' && preg_match('/^[a-z]:\\\/i', $path)) {
684 * パス名がルートディレクトリかどうかを返す
687 * @param string $path
690 function isRootDir($path)
692 if ($path === DIRECTORY_SEPARATOR) {
696 if (is_dir($path) === false) {
699 return $path === basename($path) . DIRECTORY_SEPARATOR;
708 * @param string $dir 作成するディレクトリ
709 * @param int $mode パーミッション
710 * @return bool true:成功 false:失敗
713 function mkdir($dir, $mode)
715 if (file_exists($dir)) {
719 $parent = dirname($dir);
720 if ($dir === $parent) {
724 if (is_dir($parent) === false) {
725 if (Ethna_Util::mkdir($parent, $mode) === false) {
730 return mkdir($dir, $mode) && Ethna_Util::chmod($dir, $mode);
738 function chmod($file, $mode)
741 if (($st[2] & 0777) == $mode) {
744 return chmod($file, $mode);
751 * (途中で失敗しても中断せず、削除できるものはすべて消す)
754 * @param string $file 削除するファイルまたはディレクトリ
755 * @return bool true:成功 false:失敗
758 function purgeDir($dir)
760 if (file_exists($dir) === false) {
763 if (is_dir($dir) === false) {
772 while (($entry = readdir($dh)) !== false) {
773 if ($entry === '.' || $entry === '..') {
776 $ret = $ret && Ethna_Util::purgeDir("{$dir}/{$entry}");
789 * テンポラリディレクトリのファイルを削除する
792 * @param string $prefix ファイルのプレフィクス
793 * @param int $timeout 削除対象閾値(秒−60*60*1なら1時間)
795 function purgeTmp($prefix, $timeout)
797 $c =& Ethna_Controller::getInstance();
799 $dh = opendir($c->getDirectory('tmp'));
801 while (($file = readdir($dh)) !== false) {
802 if ($file == '.' || $file == '..') {
804 } else if (is_dir($c->getDirectory('tmp') . '/' . $file)) {
806 } else if (strncmp($file, $prefix, strlen($prefix)) == 0) {
807 $f = $c->getDirectory('tmp') . "/" . $file;
809 if ($st[9] + $timeout < time()) {
824 * @param string $file ロックするファイル名
825 * @param int $mode ロックモード('r', 'rw')
826 * @param int $timeout ロック待ちタイムアウト(秒−0なら無限)
827 * @return int ロックハンドル(falseならエラー)
829 function lockFile($file, $mode, $timeout = 0)
831 if (file_exists($file) === false) {
834 $lh = fopen($file, $mode);
836 return Ethna::raiseError("File Read Error [%s]", E_APP_READ, $file);
839 $lock_mode = $mode == 'r' ? LOCK_SH : LOCK_EX;
841 for ($i = 0; $i < $timeout || $timeout == 0; $i++) {
842 $r = flock($lh, $lock_mode | LOCK_NB);
848 if ($timeout > 0 && $i == $timeout) {
850 return Ethna::raiseError("File lock get error [%s]", E_APP_LOCK, $file);
862 * @param int $lh ロックハンドル
864 function unlockFile($lh)
870 // {{{ formatBacktrace
875 * @param array $bt debug_backtrace()関数で取得したバックトレース
876 * @return string 文字列にフォーマットされたバックトレース
878 function formatBacktrace($bt)
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',
890 if (isset($elt['args']) == false || is_array($elt['args']) == false) {
895 foreach ($elt['args'] as $arg) {
896 $r .= Ethna_Util::_formatBacktrace($arg);
904 * バックトレース引数をフォーマットして返す
907 * @param string $arg バックトレースの引数
908 * @param int $level バックトレースのネストレベル
909 * @param int $wrap 改行フラグ
910 * @return string 文字列にフォーマットされたバックトレース
912 function _formatBacktrace($arg, $level = 0, $wrap = true)
914 $pad = str_repeat(" ", $level);
915 if (is_array($arg)) {
916 $r = sprintf(" %s[array] => (\n", $pad);
918 $r .= sprintf(" %s *too deep*\n", $pad);
920 foreach ($arg as $key => $elt) {
921 $r .= Ethna_Util::_formatBacktrace($key, $level, false);
923 $r .= Ethna_Util::_formatBacktrace($elt, $level+1);
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" : "");
930 $r = sprintf(" %s[%s]%s%s", $pad, gettype($arg), $arg, $wrap ? "\n" : "");