. ** ** it.class - static functions */ class it { /** * Create config class with static members initialized (e.g. $home). * NOTE: PHP5 ONLY * @param $p Static members to be generated in newly created class * service: Class name of created class (default: from caller path) * home: Home directory of application (default: from caller path) * site: Domain name of application (default: from caller path) * db: Database of application (default: from caller path) */ function createconfig($p = array()) { $stack = debug_backtrace(); $filename = $stack[0]['file']; preg_match('!/www/((\w+)[^/]+)!', $filename, $parts); $p += array( 'home' => $parts[0], 'site' => $parts[1], 'db' => strtr($parts[1], ".-", "__"), 'service' => $parts[2], ); if ($file = @fopen($p['service'] . "_tools.class", "r", true)) { $extends = "extends {$p['service']}_tools "; fclose($file); } $code = array("class {$p['service']} $extends{"); foreach ($p as $name => $value) $code[] = "static \$$name = " . var_export($value, true) . ";"; $code[] = "}"; eval(join("\n", $code)); } /** * Clone an object and return copy, works for all PHP versions */ function &cloneobj(&$object) { $result = (is_object($object) && version_compare(zend_version(), 2, '>=')) ? clone($object) : $object; return $result; # PHP internals need a tmp var to return by ref } /** * Append a line to a logfile in log/. Date will be added to filename and line * @param $name Name of logfile * @param $line1 Line to append (varargs) */ function log($name /* ... */) { $args = func_get_args(); $line = date("Y-m-d H:i:s") . "\t" . implode("\t", array_slice($args, 1)) . "\n"; $fn = $GLOBALS['ULTRAHOME'] . "/log/$name-" . date('Ymd'); $existed = file_exists($fn); if (isset($GLOBALS['ULTRAHOME']) && ($fh = fopen($fn, "a"))) { fputs($fh, $line); fclose($fh); if (!$existed) { @chgrp($fn, "www"); @unlink($GLOBALS['ULTRAHOME'] . "/log/$name"); @symlink($fn, $GLOBALS['ULTRAHOME'] . "/log/$name"); } } } /** * Store timings for appending to log/timer_log-* in auto_append.php */ function timerlog($label = '') { if ($GLOBALS['debug_timerlog']) { $s = $GLOBALS['ULTRATIME']; $e = gettimeofday(); $msec= ($e['sec'] - $s['sec']) * 1000 + ($e['usec'] - $s['usec']) / 1000; $GLOBALS['ULTRATIMERLOG'] .= sprintf(" %s:%d", $label, $msec); } } /** * Send verbose error report to browser or (if display_errors is off) by email to .diffnotice gods, file owner or SERVER_ADMIN * All params optional. Single string parameter means 'title'. * @parma $p['title'] error title, one line * @param $p['body'] error body, multiline * @param $p['to'] comma separated recipient list * @param $p['id'] identifier of error. if given, only subsequent errors of same id will trigger message * @param $p['graceperiod'] number of seconds within which additional errors are ignored if id is set * @param $p['timewindow'] number of seconds after graceperiod within which the second error must occur if id is set * @param $p['backtraceskip'] number of stack levels to drop * @param $p['blockmail'] number of seconds to block mails after having sent a mail [3600] */ function error($p = array(), $body = null, $to = null) # $body and $to deprecated { if (!is_array($p)) $p = array('title' => $p, 'body' => $body, 'to' => $to); if ($_SERVER['REMOTE_ADDR']) $url = ($_SERVER['HTTPS'] ? "https://" : "http://") . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; else $url = $_SERVER['SCRIPT_NAME']; $gods = strtr(trim(@file_get_contents($GLOBALS['ULTRAHOME'] . "/.diffnotice")), array("\n"=>', ')); if (!$p['to']) unset($p['to']); # allow defaults to kick in $p += array( 'title' => "it::error", 'to' => $gods ? $gods : (get_current_user() ? get_current_user() : $_SERVER['SERVER_ADMIN']), 'graceperiod' => 60, 'timewindow' => 25*3600, 'backtraceskip' => 0, 'blockmail' => 3600, ); $toscreen = (ini_get('display_errors') || EDC('astwin') || EDC('asdevel')) && !EDC('aslive'); if (!$toscreen) # this error can only be sent by mail: find out if we want to suppress it { @mkdir("/tmp/alertdata"); @chmod("/tmp/alertdata", 0777); if (!$p['id']) $sendmail = true; else { $errstampfn = "/tmp/alertdata/errstamp_" . urlencode($p['id']); $errstampage = time() - @filemtime($errstampfn); $sendmail = $errstampage >= $p['graceperiod'] && $errstampage <= $p['graceperiod'] + $p['timewindow']; if ($errstampage > $p['graceperiod']) { @unlink($errstampfn); @touch($errstampfn); } } if ($sendmail) { $lastsentfn = "/tmp/alertdata/lastsent_" . getmyuid() . "." . md5($p['to']); if (($sendmail = time() - @filemtime($lastsentfn) > $p['blockmail'])) { @unlink($lastsentfn); @touch($lastsentfn); } } } if ($toscreen || $sendmail) { $trace = it_debug::backtrace($p['backtraceskip']); # moved in here for performance in mass error case $body = ($body ? trim($body)."\n\n" : "") . ($url && !$toscreen? "Url: $url\n\n" : "") . ($trace ? ($sendmail?"" :" ")."Trace: $trace\n\n" : ""); if ($sendmail) # we're mailing: send maximum info { $p['title'] = "Alert: " . $p['title'] . " (on " . getenv('HOSTNAME') . ")"; unset ($p['locals']['_GLOBALS'], $p['locals']['_GET'], $p['locals']['_POST'], $p['locals']['_COOKIE']); $locals = print_r($p['locals'], true); $body .= $p['locals'] && strlen($locals) < 100000 ? "Locals: $locals\n\n" : ""; $body .= $_GET ? "\$_GET: " . print_r($_GET, true) . "\n\n" : ""; $body .= $_POST ? "\$_POST: " . print_r($_POST, true) . "\n\n" : ""; $body .= $_COOKIE ? "\$_COOKIE: " . print_r($_COOKIE, true) . "\n\n" : ""; $body .= $_SERVER ? "\$_SERVER: " . print_r($_SERVER, true) . "\n\n" : ""; $body .= $trace ? "Stack: " . print_r(debug_backtrace(), true) . "\n\n" : ""; mail($p['to'], $p['title'], $body); } else if ($_SERVER['REMOTE_ADDR']) # in shell mode we rely on error_log below echo "
{$p['title']}\n$body"; } error_log("it::error: " . $p['title'] . " in " . ($trace ? $trace : $p['line']) . " Url: $url " . (EDC('verbose') ? D($p['locals']) : "")); } /** * Same as error(), plus exit */ function fatal($title='', $body='', $to='') { $p = is_array($title) ? $title : array('title' => $title, 'body' => $body, 'to' => $to); $p['backtraceskip']++; it::error($p); exit(1); } /** * Print message to stderr and exit with error code */ function bail($message = "Bailed.\n") { fputs(STDERR, $message); exit(1); } /** * Convert a string to ASCII-only chars, map/strip ISO-8859-1 accents * @param $text Text to convert * @return mapped/stripped version of $text contains only chars [0..127] */ function toascii($text) { return strtr(strtr($text, 'ÇéâàåçêëèïîìÅÉôòûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ', 'CeaaaceeeiiiAEoouuyooaiounNAAAaAEEEIIIOOoOUUUyY'), array('ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'æ' => 'ae', 'Æ' => 'Ae', 'ß' => 'ss')); } /** * Convert regex for preg (adds and escapes delimiter, adds modifiers) * @param $pattern Regex to convert * @param $p['casesensitive'] Regex is case sensitive (omit modifier i) * @param $p['multiline'] add modifier m: ^ and $ match \n * @param $p['singleline'] add modifier s: . matches \n * @param $p['utf8'] add modifier u * @param $p['extended'] add modifier x * @return converted regex to use with preg */ function convertregex($pattern, $p = array()) { $pattern = preg_replace('|/|', '\/', $pattern); $modifiers = ''; if (!$p['casesensitive']) $modifiers .= 'i'; foreach (array( 'multiline' => 'm', 'singleline' => 's', 'utf8' => 'u', 'extended' => 'x', ) as $key => $mod) { if ($p[$key]) $modifiers .= $mod; } return "/$pattern/$modifiers"; } /** * Try to match string against regex. Case insensitive by default. * @param $pattern Regex to match against * @param $string String to match * @param $p['offset_capture'] Set flag preg_offset_capture (returns offsets with the matches). * @param $p['all'] Return every match as array instead of first match. * @param $p['locale'] Use given locale (default: de_CH), mainly affects handling of iso-latin chars * @param $p contains pattern modifiers, @see convertregex() * @return Matched string or false */ function match($pattern, $string, $p = array()) { $flags = 0; $p += array('locale' => 'de_CH'); if($p['offset_capture']) $flags |= PREG_OFFSET_CAPTURE; $oldlocale = setlocale( LC_CTYPE, 0 ); setlocale(LC_CTYPE, $p['locale']); if ($p['all']) $r = preg_match_all(it::convertregex($pattern, $p), $string, $m, $flags | PREG_PATTERN_ORDER, $p['offset']); else $r = preg_match(it::convertregex($pattern, $p), $string, $m, $flags, $p['offset']); setlocale(LC_CTYPE, $oldlocale); if (!$r) # no match $result = $p['all'] ? array() : null; else if (count($m) == 1) # no capture $result = $m[0]; else if (count($m) == 2) # one capture $result = $m[1]; else if ($p['all'] && !$p['pattern_order']) # captures, reorder pattern_order to set_order but without first element $result = call_user_func_array('array_map', array_merge(array(null), array_slice($m, 1))); else # captures, don't return first element (matched string) $result = array_slice($m, 1); return $result; } /** * Replace parts of a string matched by a pattern with according replacement string. See convertregex for named parameters. * @param $replacementes Array with patterns as keys and replacement strings as values. * @param $string String to change. * @return New string. */ function replace($replacements, $string, $p = array()) { $patterns = array(); foreach (array_keys($replacements) as $pattern) $patterns[] = it::convertregex($pattern, $p); $oldlocale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'de_CH'); $result = preg_replace($patterns, array_values($replacements), $string, isset($p['limit']) ? $p['limit'] : -1); setlocale(LC_CTYPE, $oldlocale); return $result; } /** * Extract key => value pairs from assoc array by key * @param $array array to filter * @param $keys array of keys to keep */ function filter_keys($array, $keys) { $result = array(); $keep = array_flip($keys); foreach ($array as $key => $val) if (isset($keep[$key])) $result[$key] = $val; return $result; } /** * Construct shell command, log it, execute it and return output as string. * Keywords {keyword} are replace a la ET(), {-opts} takes an array and * inserts options a la it_html attributes (value, true, false or null) * @param $cmd Format string with {keywords} replace a la ET() * @param $values (zero, one or more arrays can be passed) * @return output of command. shell errors not detectable, see error_log in /www/server/logs */ function exec(/* $cmd, $values1 = array(), ... */) { $args = func_get_args(); $cmd = array_shift($args); $values = array(); # Merge values into one array foreach ($args as $arg) $values += (array)$arg; while (list($tag, $option, $key) = it::match('({(-?)(\w+)})', $cmd)) { $parts = array(); if ($option) { foreach ((array)$values["-$key"] as $key => $value) { if ($value === true || $value === false || $value === null) $parts[] = $value ? $key : ""; else $parts[] = "$key " . it::_exec_quotevalue($value); } } else { foreach ((array)$values[$key] as $value) $parts[] = it::_exec_quotevalue($value); } $cmd = str_replace($tag, join(" ", $parts), $cmd); } $s = gettimeofday(); $result = EDC('noexec') ? "" : (string)shell_exec($cmd); $e = gettimeofday(); $msec= intval(($e['sec'] - $s['sec']) * 1000 + ($e['usec'] - $s['usec']) / 1000); it::log('exec', "$msec\t$cmd"); return $result; } function _exec_quotevalue($value) { $result = strval($value); if (it::match('^-', $result)) it::fatal("leading - in value"); return escapeshellarg($result); } /** * Convert an image to a given size and type (ensures input is an image) * @param $p['in'] Input filename (mandatory) * @param $p['out'] Output filename (mandatory) * @param $p['size'] Width x height of resulting image, e.g. "160x60" * @param $p['type'] Output file type, e.g. "jpg" * @param $p['types'] Comma separated list of accepted input types, default "gif,jpg,png,bmp,tif,jp2" * @return Success of convert as true/false */ function imageconvert($p) { $result = false; $imagetype = @exif_imagetype($p['in']); if (!function_exists("image_type_to_extension") || !($type = it::replace(array("jpeg" => "jpg", "tiff" => "tif"), image_type_to_extension($imagetype, false)))) { # Fallback for PHP4 $knowntypes = array(IMAGETYPE_GIF => "gif", IMAGETYPE_JPEG => "jpg", IMAGETYPE_PNG => "png", IMAGETYPE_BMP => "bmp"); $type = $knowntypes[$imagetype]; } $p += array('type' => $type, 'types' => "gif,jpg,png,bmp,tif,jp2"); if (it::match(",$type,", ",{$p['types']},")) # Valid type? $result = it::exec("convert 2>&1 -flatten -quality 75 {-opts} {in} {type}:{out}", array('-opts' => array('-thumbnail' => $p['size'])), $p) === ""; return $result; } /** * Parse command line options with Usage given as template and return assoc array. Example: (like grep --help) * Usage: myprogram.php [OPTIONS] PATTERN * -s, --short Use short ouput * -f, --file=FILE Use FILE for input * -x EXTENSION Ignore EXTENSION * Mandatory arguments from the Usage: line are returned under their (lowercased!) name. * All non-option arguments are returned in 'args' * Option text must be indented; if long and short option present, value is stored in long option * Options without arguments store true or false under their key * Options -h and --help will be handled internally by printing usage and exiting * When printing, the usage will be de-indented so the first line starts in the first column * Two or more blanks must be in front of option explanation * @param $helplines Usage parsed to determine options * @return Associative array of options */ function getopt($helplines) { $GLOBALS['it_stdin'] = array( 'fd' => null, 'args' => array(), 'filename' => "", 'line' => 0, ); $result = array('args' => array()); if ($indentation = it::match('^\s+', $helplines)) $helplines = it::replace(array($indentation => "\n"), $helplines); foreach(explode("\n", trim($helplines)) as $helpline) { $shortoptname = $shortoptarg = $longoptname = $longoptarg = ""; foreach (explode(',', $helpline) as $optdesc) { $optdesc = trim($optdesc); if ($matches = (array)it::match('^--(\w[\w-]*)(=[A-Z])?', $optdesc)) list($longoptname, $longoptarg) = $matches; elseif ($matches = (array)it::match('^-(\w)( [A-Z])?', $optdesc)) list($shortoptname, $shortoptarg) = $matches; } if ($longoptname || $shortoptname) { if ($longoptname && $shortoptname) $alias[$shortoptname] = $longoptname; $witharg[$longoptname ? $longoptname : $shortoptname] = $longoptarg || $shortoptarg; } } $mandatoryargs = array(); if ($tmp = trim(it::replace(array("\n.*" => "", "^\S+\s+\S+\s*" => "", "\[.*?\]\s*" => ""), trim($helplines)))) $mandatoryargs = preg_split('/\s+/', $tmp); foreach (array_slice($_SERVER['argv'], 1) as $arg) { if ($eat) { if (it::match('^--?\w', $arg)) # Already next option => Missing argument? $err = true; else $result[array_shift($eat)] = $arg; } elseif ($matches = (array)it::match('^--(\w[\w-]*)(=\S*)?', $arg)) { list($optname, $val) = $matches; if (!isset($witharg[$optname]) || isset($val) && !$witharg[$optname]) $err = true; else if($witharg[$optname] && !$val) $eat[] = $optname; else $result[$optname] = $val ? substr($val, 1) : true; } else if ($letters = it::match('^-(\w+)', $arg)) { foreach (explode("\n", trim(chunk_split($letters, 1, "\n"))) as $letter) { $optname = $alias[$letter] ? $alias[$letter] : $letter; if ($witharg[$optname]) $eat[] = $optname; else if (!isset($witharg[$optname])) $err = true; else $result[$optname] = true; } } elseif($mandatoryargs) $result[strtolower(array_shift($mandatoryargs))] = $arg; else $result['args'][] = $arg; } if ($err || $eat || $result['h'] || $result['help'] || $mandatoryargs) { fputs(($result['h'] || $result['help'] ? STDOUT : STDERR), trim($helplines) . "\n"); exit(1); } $GLOBALS['it_stdin']['args'] = $result['args'] ? $result['args'] : array("-"); it::_stdin_next(); return $result; } function _stdin_next() { if ($result = $GLOBALS['it_stdin']['args']) { $GLOBALS['it_stdin']['filename'] = array_shift($GLOBALS['it_stdin']['args']); $GLOBALS['it_stdin']['fd'] = ($GLOBALS['it_stdin']['filename'] == "-") ? STDIN : @fopen($GLOBALS['it_stdin']['filename'], "r"); $GLOBALS['it_stdin']['line'] = 0; } return $result; } /** * Get one line from stdin (or files given on command line) a la Perl <>. * Note: You need to call getopt() before using this function. * @return Line (including newline) or false on eof */ function gets() { do { $result = fgets($GLOBALS['it_stdin']['fd']); } while (($result === false) && it::_stdin_next()); $GLOBALS['it_stdin']['line']++; return $result; } /** * Output formatted and localized date * @param format optional format (default is 2007-01-02 03:04:05). * Other formats are "date", "datetime", "time". * Formats can be qualified with language, e.g. "date:en" * @param stamp optional unix timestamp (default is now). * If it contains nondigits, it is fed to strtotime */ function date($format = "", $stamp = null) { list($name, $language) = explode(":", $format); if ($format && !$language) $language = T_lang(); $formats = array( "" => "Y-m-d H:i:s", "date" => "d.m.Y", "datetime" => "d.m.Y H:i", "time" => "H:i", "date:en" => "m/d/Y", "datetime:en" => "m/d/Y h:ia", "time:en" => "h:ia", ); if (!($formatstring = $formats["$name:$language"]) && !($formatstring = $formats[$name])) $formatstring = $format; $stamp = !isset($stamp) ? time() : (it::match('^\d+$', $stamp) ? $stamp : strtotime($stamp)); return date($formatstring, $stamp); } } ?>