with attributes and content
+ html($params, $content) -- return a but adds correct doctype
+ head($params, $content) -- return a but needs special params
+ select($tags, $options, $selected) -- build a html select from an array
+ tag($tag, $content) -- create arbitrary <$tag>
+ Q($str) -- html encode a value, roughly like htmlentities
+ U($arr) -- create a valid url from strings and key=>value
+ it_html::sanitize($html) -- remove dangerous tags from html code
+
+
+
+it_dbi - Database access
+------------------------
+The dbi object is a simple mysql interface. For each table in your database,
+a class is created automatically. Queries are encoded as arrays which ensures
+correct quoting, see select(). Errors are by default reported within dbi.
+
+Example:
+ it_dbi::createclasses();
+ $record = new T_Customers('ID' => 'mueller');
+ $record->update('email' => "mueller@spam.com");
+ $response = "Email added for $record->name";
+
+Functions:
+ it_dbi::createclasses($confg) -- create database objects for each table name
+ $t = new Tablename($query) -- return a dbi object, executes optional select
+ $t->select($query) -- read first result of (encoded) query into t.
+ $t->iterate() -- advance to next result
+ $t->update($fields) -- update selected record from key=>value pairs
+ $t->insert($fields) -- insert a new record from key=>value pairs
+ $t->replace($fields) -- replace a new record from key=>value pairs
+ $t->delete($query) -- delete current record or those found by query
+ $t->query($sqlquery) -- execute a raw SQL query on db connection
+
+
+
+it_text - Translation support
+-----------------------------
+it_text finds the best language to use from browser and override settings. It
+then reads texts.php in the format documented in the constructor. You can then
+use T() to translate a label. Unknown labels are logged in text_log.
+
+Example:
+ echo T('hello') . ' ' . Q($customer->name);
+ echo ET('chainletter', 'name' => Q($customer->name));
+
+Functions:
+ new it_text($config) -- read texts. usually called implicitly
+ T($label) -- return translated $label
+ ET($label, $values) -- return translated $label w/ values replaced
+ T_lang() -- returns current language
+ T_set_language($language) -- sets a new language
+ T_exists($label) -- returns whether a label is defined
+
+
+
+it_debug - Debug support
+------------------------
+it_debug is used for debugging. The function ED($foo, $bar) outputs echoes the
+values of $foo and $bar AND prepends it with the names of the variables. EDX()
+does the same and exists. EDC('verbose', $foo) only echoes if
+$GLOBALS['debug_verbose'] is set. it_debug::backtrace() outputs a compact
+stackdump.
+
+Example:
+ ED($foo, $bar); # outputs name and value of $foo and $bar
+ EDC('verbose', $foo);
+
+Functions:
+ ED($args...) -- echoes names and values of all args
+ EDX($args...) -- echoes names and values of all args and exits
+ EDC('foo', $var...) -- echoes only if $GLOBALS['debug_foo'] is set
+ D($args...) -- returns formatted names and values of params
+ it_debug::backtrace($skip) -- prints short backtrace, skipping $skip levels
+
+
+
+it.class - Tool functions
+-------------------------
+it.class provides various statically callable functions. The main groups are:
+a) Much simpler perl regex matching (no delimiters, matches returned directly
+as scalar or array, case insensitive, locale support) and a multi-pattern
+replacement function.
+b) Better error functions: stack and variable dumps added to error messages,
+error messages mailed if display_errors is off. Extra parameter allow the
+filtering of sporadic errors.
+c) Better shell support, specifically a command line parser and an exec
+function that handles quoting.
+
+Example:
+ $from = it::match('From: (.*)', $mail);
+ $page = it::replace('<.*?>' => '', ' +' => ' ', $page);
+ it::error('title'=>"cannot connect", 'id'=>"db"); # suppress sporadic errors
+ it::fatal("internal error");
+ $diff = it::exec("diff -wu {old} {new}", 'old' => $old, 'new' => $new);
+ it::imageconvert('in' => "src.jpg", 'out' => "dst.jpg", 'size' => "80x80");
+
+Functions:
+ it::match($pattern, $subject, $opts) -- find pattern in subject using opts; return matches
+ it::replace($replace, $subject, $opts) -- replace patterns in subject; return result
+ it::error($info) -- print or mail error message
+ it::fatal($info) -- print or mail error message, then exit
+ it::bail($message) -- print message to stderr, exit with errcode
+ it::exec($command, $values) -- execute command, return output
+ it::getopt($usage) -- parse (or print) usage, return options,
+ it::gets() -- fetch next line from stdin or named arg
+ it::imageconvert($params) -- Convert image using ImageMagick convert
+
+
+
+it_url - URL handling
+---------------------
+Helper functions dealing with URLs.
+
+Example:
+ $filename = it_url::get_cache(
+ 'url' => "http://static.php.net/www.php.net/images/php.gif",
+ 'timeout' => 5,
+ 'cachedir' => $_SERVER['DOCUMENT_ROOT'] . "/cache",
+ );
+
+Functions:
+ it_url::get($params) -- performs GET/POST requests to web servers
+ it_url::get_cache($params) -- performs GET/POST and caches the result
+
+
+it_user - Session handling
+--------------------------
+This allows maintaining session cookies and authentication status for users.
+Not documented yet, check the source.
+
+it_xml - XML parser
+-------------------
+This will parse an XML string and returns a tree of PHP objects; similar to
+simplexml in PHP 5 but works in PHP 4 and supports a streaming mode for huge
+XML files. Not documented yet, check the source.
diff --git a/auto_prepend.php b/auto_prepend.php
new file mode 100644
index 0000000..b27d6e3
--- /dev/null
+++ b/auto_prepend.php
@@ -0,0 +1,152 @@
+debug($text, $level);
+}
+
+/**
+ * Convert a htmlentities-encoded string back to normal
+ */
+function it_htmlentities_decode($string)
+{
+ return strtr($string, array_flip(get_html_translation_table(HTML_ENTITIES)));
+}
+
+/**
+ * Clone an object and return copy, works for all PHP versions
+ */
+function &it_clone(&$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
+}
+
+/**
+ * Return string containing names and values of all arguments
+ */
+function D()
+{
+ $args = func_get_args();
+ return it_debug::dump($args);
+}
+
+/**
+ * Echo string containing names and values of all arguments
+ */
+function ED()
+{
+ $args = func_get_args();
+ echo it_debug::dump($args);
+ return $args[0];
+}
+
+/**
+ * Same as ED(), but if first argument is foo then $GLOBALS['debug_foo'] must be set for output
+ * @return boolean indicating whether $GLOBALS['debug_foo'] was set
+ */
+function EDC()
+{
+ $args = func_get_args();
+ $var = array_shift($args);
+ $GLOBALS['ULTRADEBUGVARS'][$var] = 1;
+
+ if (($result = $GLOBALS["debug_$var"]) && $args)
+ echo it_debug::dump($args);
+
+ if (!$result || $result === true) # Compatibility with old map relying on 0|1
+ $result = intval($result);
+
+ return $result;
+}
+
+/**
+ * Echo string containing names and values of all arguments, then exit
+ */
+function EDX()
+{
+ $args = func_get_args();
+ exit(it_debug::dump($args));
+}
+
+/**
+ * Return a text in the selected language
+ * @param $label Label of text to return
+ * @param $language Optional language to return text in
+ * @return Localized text string
+ */
+function T($label, $language = null, $dummy = false)
+{
+ it_text::init();
+ return $GLOBALS['it_text']->text($label, $language, $dummy);
+}
+
+/**
+ * Return a text in the selected language
+ * Replaces variables of the form {var} with value from argument $values
+ * @param $label Label of text to return
+ * @param $values Associative array containing values to fill in
+ * @param $language Optional language to return text in.
+ * @return Localized text string with variables replaced by their values
+ */
+function ET($label, $values = null, $language = null)
+{
+ it_text::init();
+ return $GLOBALS['it_text']->etext($label, $values, $language);
+}
+
+/**
+ * Change language
+ * @param $language New language to set
+ * @param $setcookie Optional flag if a cookie is to be set (default: true)
+ */
+function T_set_language($language, $setcookie = true)
+{
+ it_text::init();
+ return $GLOBALS['it_text']->set_language($language, $setcookie);
+}
+
+/**
+ * Get active language
+ * @return currently active language
+ */
+function T_lang()
+{
+ it_text::init();
+ return $GLOBALS['it_text']->get_language();
+}
+
+/**
+ * Check if a text entry for a specific label exists
+ * @param $label Label to check
+ * @return true if text exists in actual (or supplied) language, false otherwise.
+ */
+function T_exists($label, $language = null)
+{
+ it_text::init();
+ return $GLOBALS['it_text']->text_exists($label, $language);
+}
+
+/**
+ * Return "db4" or "db2" depending on availability
+ */
+function db_version()
+{
+ return in_array("db4", dba_handlers()) ? "db4" : "db2";
+}
+
+?>
diff --git a/it.class b/it.class
new file mode 100644
index 0000000..1b6c317
--- /dev/null
+++ b/it.class
@@ -0,0 +1,625 @@
+.
+**
+** 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');
+ 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);
+
+ if ($trace && ($fulltrace = array_slice(debug_backtrace(), $p['backtraceskip'])))
+ while (strlen(print_r($fulltrace, true)) > 100000)
+ array_pop($fulltrace);
+
+ $body .= "Host: " . getenv('HOSTNAME') . "\n\n";
+ $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 .= $fulltrace ? "Stack: " . print_r($fulltrace, 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['file']}:{$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 (!f