<?php require_once($INC . "/prof.inc.php"); require_once($INC . "/log.inc.php"); set_error_handler("rg_error_handler"); register_shutdown_function("rg_shutdown_error"); $rg_util_error = ""; function rg_util_set_error($str) { global $rg_util_error; $rg_util_error = $str; } function rg_util_error() { global $rg_util_error; return $rg_util_error; } function rg_1024($v) { if ($v <= 9999) return number_format($v); $v /= 1024; if ($v <= 9999) return number_format($v) . "KiB"; $v /= 1024; if ($v <= 9999) return number_format($v) . "MiB"; $v /= 1024; if ($v <= 9999) return number_format($v) . "GiB"; $v /= 1024; return number_format($v) . "TiB"; } /* * Unique ID generator */ function rg_id($len) { $id = ""; rg_prof_start("urandom"); $f = @fopen("/dev/urandom", "r"); if ($f !== NULL) { $buf = @fread($f, 128); if ($buf !== NULL) $id = sha1($buf); fclose($f); } if (empty($id)) $id = sha1(mt_rand() . serialize($_REQUEST)); rg_prof_end("urandom"); return substr($id, 0, $len); } /* * Locks a file */ $_lock = array(); function rg_lock($file) { global $_lock; global $rg_lock_dir; if (!isset($rg_lock_dir)) $rg_lock_dir = "/var/lib/rocketgit/locks"; // Double locking? if (isset($_lock[$file])) { rg_internal_error("Double locking [$file]: " . rg_array2string($_lock)); return FALSE; } $f = @fopen($rg_lock_dir . "/" . $file, "w"); if ($f === FALSE) { rg_internal_error("Cannot open lock $file ($php_errormsg)."); return FALSE; } if (!flock($f, LOCK_EX | LOCK_NB)) { fclose($f); return FALSE; } fwrite($f, getmypid() . "\n"); $_lock[$file] = $f; return TRUE; } function rg_lock_or_exit($file) { global $_lock; if (rg_lock($file) === FALSE) exit(0); } function rg_unlock($file) { global $_lock; if (isset($_lock[$file])) fclose($_lock[$file]); } function rg_load() { return intval(file_get_contents("/proc/loadavg")); } /* * Builds URLs */ function rg_re_url($area) { if (isset($_REQUEST['rwe'])) return $area; return "/?vv=" . $area; } /* * This is used for forms */ function rg_re_post($op) { if (isset($_REQUEST['rwe'])) return $op; return "/?vv=$op"; } function rg_re_userpage($ui) { if (!isset($ui['organization'])) { rg_internal_error("rg_re_userpage called with wrong ui (no org)!"); exit(1); } $prefix = ""; if ($ui['organization'] == 0) $prefix = "/user"; $s = $prefix . "/" . $ui['username']; if (isset($_REQUEST['rwe'])) return $s; return $_SERVER['PHP_SELF'] . "?vv=$s"; } function rg_re_repopage($ui, $repo_name) { if (!isset($ui['organization'])) { rg_internal_error("rg_re_repopage called with wrong ui (no org)!"); exit(1); } $s = rg_re_userpage($ui) . "/" . $repo_name; if (isset($_REQUEST['rwe'])) return $s; return $_SERVER['PHP_SELF'] . "?vv=$s"; } function rg_re_bugpage($ui, $repo_name, $bug_id) { if (!isset($ui['organization'])) { rg_internal_error("rg_re_repopage called with wrong ui (no org)!"); exit(1); } $s = rg_re_repopage($ui, $repo_name) . "/bug/" . $bug_id; if (isset($_REQUEST['rwe'])) return $s; return $_SERVER['PHP_SELF'] . "?vv=$s"; } function rg_base_url() { $port = ""; if (isset($_SERVER['HTTPS'])) { $proto = "https"; if ($_SERVER['SERVER_PORT'] != 443) $port = ":" . $_SERVER['SERVER_PORT']; } else { $proto = "http"; if ($_SERVER['SERVER_PORT'] != 80) $port = ":" . $_SERVER['SERVER_PORT']; } return $proto . "://" . $_SERVER['SERVER_NAME'] . $port; } function rg_re_repo_ssh($organization, $user, $repo) { global $rg_ssh_host; global $rg_ssh_port; if ($rg_ssh_port == 22) $port = ""; else $port = ":" . $rg_ssh_port; if ($organization == 1) $prefix = "/org"; else $prefix = ""; return "ssh://rocketgit@" . $rg_ssh_host . $port . $prefix . "/" . $user . "/" . $repo; } function rg_re_repo_git($organization, $user, $repo) { global $rg_git_port; if ($rg_git_port == 9418) $port = ""; else $port = ":" . $rg_git_port; if ($organization == 1) $prefix = "/org"; else $prefix = ""; return "git://" . $_SERVER['SERVER_NAME'] . $port . $prefix . "/" . $user . "/" . $repo; } function rg_var_str($name) { $ret = ""; if (isset($_SERVER[$name])) $ret = $_SERVER[$name]; else if (isset($_COOKIE[$name])) $ret = $_COOKIE[$name]; else if (isset($_POST[$name])) $ret = $_POST[$name]; else if (isset($_GET[$name])) $ret = $_GET[$name]; if (is_string($ret)) return htmlspecialchars($ret, ENT_QUOTES); if (is_array($ret)) { $ret2 = array(); foreach ($ret as $k => $v) $ret2[$k] = htmlspecialchars($v, ENT_QUOTES); return $ret; } return ""; } function rg_var_int($name) { return sprintf("%d", rg_var_str($name)); } function rg_var_uint($name) { return sprintf("%u", rg_var_str($name)); } function rg_var_re($name, $re) { $a = rg_var_str($name); return preg_replace($re, "", $a); } /* * Enforce chars in a name. It is used for user and repo. */ function rg_chars_allow($name, $allowed_regexp) { if (preg_match($allowed_regexp, $name) === 0) return FALSE; return TRUE; } /* * Deletes a folder and the files inside it */ function rg_rmdir($dir) { rg_log("rmdir: $dir"); if (!is_dir($dir)) { rg_util_set_error("WARN: asked to remove a non-existing dir ($dir)"); return TRUE; } $scan = glob($dir . "/*"); if ($scan === FALSE) { rg_util_set_error("invalid pattern [$dir/*]"); return FALSE; } if (count($scan) > 0) { $all_good = TRUE; foreach ($scan as $junk => $path) { if (is_dir($path)) { $r = rg_rmdir($path); if ($r !== TRUE) return FALSE; continue; } if (!unlink($path)) { rg_util_set_error("cannot remove [$path] ($php_errormsg)"); return FALSE; } } } if (!rmdir($dir)) { rg_util_set_error("cannot remove main dir ($php_errormsg)"); return FALSE; } return TRUE; } /* * Adds an submenu * It is normal op to be empty */ function rg_menu_add(&$menu, $sub, $op) { //rg_log_ml("DEBUG: rg_menu_add (op=$op): " . print_r($sub, TRUE)); if (isset($sub[$op])) $sub[$op]['active'] = 1; if (empty($menu)) { $menu = $sub; return; } // search for last active menu foreach ($menu as $_op => $_info) { if (!isset($_info['active'])) continue; if (!isset($_info['sub'])) { // we found the correct place $menu[$_op]['sub'] = $sub; break; } // we are on parent of the correct menu rg_menu_add($menu[$_op], $sub, $op); break; } } /* * Generates a menu */ function rg_menu($a, $url, $ui) { rg_log_ml("DEBUG: rg_menu: url=$url a=" . print_r($a, TRUE)); $ret = array(); $submenu = array(); $menu = "<div class=\"menu\">\n"; $menu .= "\t<ul>\n"; foreach ($a as $_id => $_info) { // we ignore fake menus like 'home' if (!isset($_info['text'])) continue; if (isset($_info['needs_admin']) && ($ui['is_admin'] == 0)) continue; if (isset($_info['uid0']) && ($ui['uid'] > 0)) continue; if (!isset($_info['uid0']) && ($ui['uid'] == 0)) continue; $_text = $_info['text']; $prefix = empty($url) ? "" : $url . "/"; $_url = $prefix . rg_re_url($_info['op']); //$menu .= "<!-- op=" . $_info['op'] . " url=$_url." . " -->\n"; //rg_log("\tDEBUG: compare with [" . $_info['op'] . "]"); $add = ""; if (isset($_info['active'])) $add = " class=\"selected\"";; $menu .= "\t\t<li><a href=\"" . $_url . "\"" . $add . ">" . $_text . "</a></li>\n"; if (!isset($_info['sub']) || (count($_info['sub']) == 0)) continue; // submenu $submenu = rg_menu($_info['sub'], $_url, $ui); } $menu .= "\t</ul>\n"; $menu .= "\t</div>\n"; $ret[] = $menu; if (!empty($submenu)) { foreach ($submenu as $_index => $_menu) $ret[] = $_menu; } return $ret; } /* * Provides a link to an image, taking in consideration the theme * Used by rg_prepare_image. */ function rg_image_callback($matches) { global $rg_scripts; global $rg_theme; $n = $matches[1]; $url = "/themes/" . $rg_theme . "/" . $n; $xfile = $rg_scripts . "/root" . $url; if (!is_file($xfile)) $url = "/themes/default/" . $n; return $url; } /* * Prepare an image taking in consideration the them. It will use rg_img as * callback. */ function rg_prepare_image($line) { return preg_replace_callback('/@@IMG:(.*)@@/uU', "rg_image_callback", $line); } function rg_prepare_replace(&$data, &$what, &$values) { if (!empty($data)) { if (!is_array($data)) rg_internal_error("invalid type passed"); foreach ($data as $k => $v) { if (is_array($v)) { rg_log_ml("value of key [$k] is array!" . " data: " . print_r($data, TRUE)); exit(1); } if (strncmp($k, "HTML:", 5) == 0) { $k = substr($k, 5); } else { if (is_array($v)) rg_log_ml("DEBUG: Invalid type for [$k]: " . print_r($v, TRUE)); $v = htmlspecialchars($v); } $what[$k] = "/@@" . $k . "@@/uU"; $values[$k] = $v; } } $what['DUMP'] = "/@@DUMP@@/uU"; $values['DUMP'] = htmlspecialchars(print_r($data, TRUE)); // we replace @@unknown@@ with empty //$what['FINAL'] = "/@@.*@@/U"; //$values['FINAL'] = ""; //rg_log_ml("DEBUG: what: " . print_r($what, TRUE)); //rg_log_ml("DEBUG: values: " . print_r($values, TRUE)); } /* * Lookup a var into data array, if needed. * It is used for conditionals. */ function rg_replace_lookup(&$data, $var) { rg_prepare_replace($data, $what, $values); return preg_replace($what, $values, $var); } /* * Helper for rg_replace_conditionals. * It works at line level. * @master_block (TRUE / FALSE) is the condition for parent block. */ function rg_replace_conditionals_block($block, &$data, &$stack) { //rg_log("DEBUG: rg_replace_conditionals_block: block=[$block]" // . " stack=" . rg_array2string($stack)); if (!is_string($block)) { rg_internal_error("Block is not a string!"); return FALSE; } // Nesting error if (empty($stack)) { rg_internal_error("Nesting error!"); return FALSE; } $cond = array_pop($stack); $stack[] = $cond; //rg_log("cond is " . ($cond ? "TRUE" : "FALSE")); // First, try to match a start of 'if' $match1 = "@@if\s*\((.*?)\)\s*?{{"; $match2 = "{{"; $match3 = "}}"; $search = $match1 . '|' . $match2 . '|' . $match3; $r = preg_match('/^(.*?)(' . $search . ')(.*)/su', $block, $matches); if ($r === FALSE) return FALSE; if ($r === 1) { //rg_log("DEBUG: matches: " . rg_array2string($matches)); $ret = ""; if ($cond === TRUE) $ret = $matches[1]; $rest = $matches[4]; if (strcmp($matches[2], "}}") == 0) { // We pop from stack only at }} and not at {{ //rg_log("}}: Pop the stack!"); array_pop($stack); } else if (strcmp($matches[2], "{{") == 0) { //rg_log("{{"); } else { //rg_log("DEBUG: cond=" . $matches[3]); if (empty($matches[3])) { $new_cond = FALSE; } else { $r = preg_match('/^\s*(.*?)\s*(==|!=)\s*(.*?)\s*$/su', $matches[3], $matches2); if ($r === FALSE) { rg_internal_error("Invalid condition!"); return FALSE; } //rg_log("DEBUG: matches2: " . rg_array2string($matches2)); $left = rg_replace_lookup($data, trim($matches2[1])); $op = trim($matches2[2]); $right = rg_replace_lookup($data, trim($matches2[3])); //rg_log("DEBUG: if left=[$left] op=[$op] right=[$right]"); if (empty($op)) { $new_cond = empty($left) ? FALSE : TRUE; } else if (strcmp($op, "==") == 0) { $new_cond = strcmp($left, $right) == 0 ? TRUE : FALSE; } else if (strcmp($op, "!=") == 0) { $new_cond = strcmp($left, $right) == 0 ? FALSE : TRUE; } else { rg_internal_error("Invalid operation!"); return FALSE; } } $not_new_cond = $new_cond ? FALSE : TRUE; // We have to respect the outer block condition // We have to push in reverse order. Remeber, first in, last out $stack[] = $not_new_cond && $cond; $stack[] = $new_cond && $cond; } $tmp = rg_replace_conditionals_block($rest, $data, $stack); if ($tmp === FALSE) return FALSE; //rg_log("DEBUG: returning [" . $ret . $tmp . "]"); return $ret . $tmp; } if ($cond === FALSE) $block = ""; //rg_log("DEBUG: returning [$block]"); return $block; } /* * Replace conditionals * @@if(X == Y){{A}}{{B}} - if X == Y it will return A, else B. * Do note that there is no ending @@. * Because of complexity, I choosed to have a restriction: the 'if' is on * a single line or '@@if(...){{', '}}{{' and '}}' are on separate lines. * TODO: Also, we must have both branches (both true and false), for now. * We support nested ifs. */ function rg_replace_conditionals($block, &$data) { rg_prof_start("replace_conditionals"); $ret = array(); do { $stack = array(); $stack[] = TRUE; $ret = rg_replace_conditionals_block($block, $data, $stack); if ($ret === FALSE) break; if (empty($stack) || ($stack[0] !== TRUE)) { rg_internal_error("Template nesting error!"); $ret = FALSE; break; } } while (0); rg_prof_end("replace_conditionals"); return $ret; } /* * Loads a file if exists, else return "" */ function rg_file_get_contents($f) { if (!file_exists($f)) return ""; $c = file_get_contents($f); if ($c === FALSE) { rg_internal_error("Could not load file [$f] ($php_errormsg)."); return ""; } return $c; } /* * Builds a html output based on a template with header, footer and line * @data - array of data for every line: index 0 is line 1, index 1 is line 2... */ function rg_template_table($dir, &$data, $more) { global $rg_theme; global $rg_scripts; $xdir = $rg_scripts . "/root/themes/" . $rg_theme . "/" . $dir; if (!is_dir($xdir)) { rg_log("$xdir not found."); $xdir = $rg_scripts . "/root/themes/default/" . $dir; rg_log("Using [$xdir]"); } $m_what = array(); $m_values = array(); rg_prepare_replace($more, $m_what, $m_values); if (!is_array($data) || empty($data)) { $no_data = rg_file_get_contents($xdir . "/nodata.html"); $r = rg_replace_conditionals($no_data, $more); return preg_replace($m_what, $m_values, $r); } $head = rg_file_get_contents($xdir . "/header.html"); $line = rg_file_get_contents($xdir . "/line.html"); $foot = rg_file_get_contents($xdir . "/footer.html"); $between = rg_file_get_contents($xdir . "/between.html"); $head = rg_replace_conditionals($head, $more); $foot = rg_replace_conditionals($foot, $more); $between = rg_replace_conditionals($between, $more); $head = preg_replace($m_what, $m_values, $head); $foot = preg_replace($m_what, $m_values, $foot); $between = preg_replace($m_what, $m_values, $between); $body = ""; $first = 1; foreach ($data as $index => $info) { $what = $m_what; $values = $m_values; rg_prepare_replace($info, $what, $values); $line = rg_prepare_image($line); if ($first == 1) { $first = 0; } else { $body .= $between; } $r = rg_replace_conditionals($line, $more); $body .= preg_replace($what, $values, $r); } return $head . $body . $foot; } function rg_template($file, &$data) { global $rg_scripts; global $rg_theme; rg_log("Loading template from $file..."); $xfile = $rg_scripts . "/root/themes/" . $rg_theme . "/" . $file; if (!is_file($xfile)) $xfile = $rg_scripts . "/root/themes/default/" . $file; $body = rg_file_get_contents($xfile); if (empty($body)) { rg_log("File [$xfile] is empty. Return ''."); return ""; } $what = array(); $values = array(); rg_prepare_replace($data, $what, $values); $body = rg_prepare_image($body); $r = rg_replace_conditionals($body, $data); // TODO: check error code! $ret = preg_replace($what, $values, $r); // TODO: check error code! return $ret; } /* * Outputs a numbered list */ function rg_template_list($c) { $a = explode("\n", $c); if (count($a) == 0) return ""; $ret = ""; $i = 1; $add = ""; foreach ($a as $line) { $ret .= $add . $i . " " . htmlspecialchars($line); $add = "<br />"; $i++; } return $ret; } /* * Show errors using a template */ function rg_template_errmsg($a) { if (empty($a)) return ""; $b = array(); foreach ($a as $junk => $err) $b[] = array("error" => $err); return rg_template_table("errmsg", $b, array()); } /* * Show a warning using a template */ function rg_warning($msg) { if (empty($msg)) return ""; $x = array("msg" => $msg); return rg_template("warning.html", $x); } /* * Show an OK message using a template */ function rg_ok($msg) { if (empty($msg)) return ""; $x = array("msg" => $msg); return rg_template("ok.html", $x); } /* * Execute $cmd and returns the output as a string, binary safe */ function rg_exec($cmd) { rg_prof_start("exec"); rg_log("Executing [$cmd]..."); $ret = array(); $ret['ok'] = 0; $ret['errmsg'] = ""; $ret['code'] = 65000; // fake code do { $desc = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); $a = proc_open($cmd, $desc, $pipes); if ($a === FALSE) { $ret['errmsg'] = "cannot call proc_open"; break; } $stderr = ""; $ret['data'] = ""; $rx = array($pipes[1], $pipes[2]); while (!empty($rx)) { $revents = $rx; $wevents = NULL; $ex = NULL; $r = stream_select($revents, $wevents, $ex, 10, 0); if ($r === FALSE) { $ret['errmsg'] = "cannot select"; break; } //rg_log("DEBUG: stream_select returned $r" // . ", revents: " . rg_array2string($revents)); foreach ($revents as $fd) { if (!empty($ret['errmsg'])) break; //rg_log("Event on fd $fd!"); if (feof($fd)) { //rg_log("eof on fd $fd!"); foreach ($rx as $_key => $_fd) if ($_fd == $fd) unset($rx[$_key]); continue; } $_d = fread($fd, 32 * 4096); if ($_d === FALSE) { $ret['errmsg'] = "cannot read"; break; } //rg_log("DEBUG: got data from fd $fd [$_d]"); if ($fd === $pipes[2]) $stderr .= $_d; else if ($fd === $pipes[1]) $ret['data'] .= $_d; } if (!empty($ret['errmsg'])) break; } $stderr = trim($stderr); $ret['data'] = trim($ret['data']); for ($i = 0; $i < 3; $i++) fclose($pipes[$i]); $err = proc_close($a); if ($err != 0) { $ret['code'] = $err; $ret['errmsg'] = "code " . $err . ": " . $stderr; } if (empty($ret['errmsg'])) { $ret['code'] = 0; $ret['ok'] = 1; } } while (0); rg_prof_end("exec"); return $ret; } /* * Force browser to redirect to another page */ function rg_redirect($url) { header("Location: $url"); exit(0); } /* * Force browser to redirect to another page, using a HTML header */ function rg_redirect_html($seconds, $url) { global $more; $more['rg_redirect_html'] = 1; $more['rg_redirect_html_seconds'] = $seconds; $more['rg_redirect_html_url'] = $url; } /* * Transforms strange chars to hexa */ function rg_callback_hexa($matches) { $n = pack("a*", $matches[0]); $tmp = unpack("H*", $n); return "[" . $tmp[1] . "]"; } /* * Transforms an array in a string */ function rg_array2string($a) { if (!is_array($a)) rg_internal_error("not an array!"); if (empty($a)) return ""; $what = array("/[^\pL\pN\pP\pS ]/uU"); $ret = ""; $add = ""; foreach ($a as $k => $v) { if (is_array($v)) $s = rg_array2string($v); else $s = preg_replace_callback($what, "rg_callback_hexa", $v); $ret .= $add . "$k=[$s]"; $add = " "; } return $ret; } /* * Load files from a folder */ function rg_dir_load($dir) { $ret = array(); if (!file_exists($dir)) { rg_log("$dir does not exists!"); return $ret; } $d = @scandir($dir); if ($d === FALSE) { rg_log("Cannot scan dir $dir ($php_errormsg)."); return $ret; } foreach ($d as $file) { if ((strcmp($file, ".") == 0) || (strcmp($file, "..") == 0)) continue; $ret[] = $file; } return $ret; } /* * Recursive dir load (used for references) */ function rg_dir_load_deep($dir) { $ret = array(); if (is_file($dir)) return array($dir); $d = rg_dir_load($dir); foreach ($d as $obj) { if (is_dir($dir . "/" . $obj)) { $c = rg_dir_load_deep($dir . "/" . $obj); foreach ($c as $obj2) $ret[] = $obj . "/" . $obj2; } else { $ret[] = $obj; } } return $ret; } /* * Copy a fs tree to another place */ function rg_copy_tree($src, $dst, $mask) { if (!is_dir($dst)) { $r = @mkdir($dst, $mask); if ($r !== TRUE) { rg_log("ERROR: Cannot mkdir [$dst] ($php_errormsg)."); return FALSE; } } $d = rg_dir_load($src); foreach ($d as $obj) { if (is_dir($src . "/" . $obj)) { if (!is_dir($dst . "/" . $obj)) { $r = @mkdir($dst . "/" . $obj, $mask); if ($r !== TRUE) { rg_log("ERROR: Cannot mkdir [$dst/$obj]" . " ($php_errormsg)."); return FALSE; } } $r = rg_copy_tree($src . "/" . $obj, $dst . "/" . $obj, $mask); if ($r !== TRUE) return FALSE; } else { $r = @copy($src . "/" . $obj, $dst . "/" . $obj); if ($r !== TRUE) { rg_log("ERROR: Cannot copy file ($php_errormsg)."); return FALSE; } } } return TRUE; } /* * Called by PHP in case of error */ function rg_error_handler($no, $str, $file, $line) { global $rg_admin_email; if ($no == 0) return; $str = str_replace("\n", "\\n", $str); $msg = "PHP ERROR: $file:$line: $str (errno=$no)"; rg_log($msg); mail("rg_error@embedromix.ro", "PHP ERROR", $msg . "\n" . rg_log_buffer(), "", "-f $rg_admin_email"); if ($no == E_ERROR) die(); return FALSE; } /* * shutdown function to call fatal errors */ function rg_shutdown_error() { $a = error_get_last(); if ($a === NULL) return; rg_error_handler($a['type'], $a['message'], $a['file'], $a['line']); } /* * YYYY-MM-DD -> timestamp */ function rg_date2ts($s) { rg_log("rg_date2ts s=[$s]"); if (strlen($s) != 10) return FALSE; $f = explode("-", $s); if (count($f) != 3) return FALSE; return gmmktime(0, 0, 0, $f[1], $f[2], $f[0]); } /* * YYYY-MM-DD -> timestamp (last second of the day) */ function rg_date2ts_last_second($s) { rg_log("rg_date2ts_last_second s=[$s]"); if (strlen($s) != 10) return FALSE; $f = explode("-", $s); if (count($f) != 3) return FALSE; return gmmktime(0, 0, 0, $f[1], $f[2] + 1, $f[0]) - 1; } /* * Function to send e-mails * TODO: Replace mail() wil rg_mail everywhere. */ function rg_mail($template, $more) { global $rg_admin_name, $rg_admin_email; rg_prof_start("mail"); rg_log("mail: $template, more=" . rg_array2string($more)); $more['HTML:rg_admin_email'] = $rg_admin_email; $more['HTML:utf8_rg_admin_name'] = "=?UTF-8?B?" . base64_encode($rg_admin_name) . "?="; $subject = rg_template($template . ".subj.txt", $more); $subject = "=?UTF-8?B?" . base64_encode(trim($subject)) . "?="; $header = rg_template("mail/common.head.txt", $more); $header .= rg_template($template . ".head.txt", $more); $body = rg_template($template . ".body.txt", $more); $ret = mail($more['ui.email'], $subject, $body, $header, "-f $rg_admin_email"); if ($ret === FALSE) rg_log("Sending mail failed!"); rg_prof_end("mail"); return $ret; } /* * Merges an array (a) into another (src), using a namespace */ function rg_array_merge($src, $namespace, $a) { $ret = $src; if (empty($a)) return $ret; foreach ($a as $k => $v) $ret[$namespace . "." . $k] = $v; return $ret; } /* * Special implode, with prefix/postfix */ function rg_implode($prefix, $a, $postfix) { if (!is_array($a)) return $a; if (empty($a)) return ""; $ret = array(); foreach ($a as $index => $data) $ret[] = $prefix . $data; return implode($postfix, $ret); } ?>