diff --git a/lib/.htaccess b/lib/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/lib/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/lib/config.php b/lib/config.php new file mode 100644 index 0000000..83efb2d --- /dev/null +++ b/lib/config.php @@ -0,0 +1,23 @@ +row = $row; + } + public function offsetExists($k) + { + return array_key_exists($k, $this->row) || self::$calckeys[$k]; + } + public function offsetUnset($k) + { + unset($this->$k); + } + public function offsetSet($k, $v) + { + if (array_key_exists($k, $this->row)) + $this->row[$k] = $v; + else + $this->$k = $v; + } + public function offsetGet($k) + { + if (array_key_exists($k, $this->row)) + return $this->row[$k]; + if (!is_null($this->$k)) + return $this->$k; + $this->populate($k); + return $this->$k; + } + public function populate($k) + { + } +} + +function get_include_contents($filename) +{ + if (is_file($filename)) + { + ob_start(); + include $filename; + $contents = ob_get_contents(); + ob_end_clean(); + return $contents; + } + return false; +} + +function str_starts_with ($str, $sub) +{ + if (strcmp (substr ($str, 0, strlen ($sub)), $sub) == 0) + return true; + return false; +} + +function str_ends_with ($str, $sub) +{ + if (strcmp (substr ($str, strlen ($str) - strlen ($sub)), $sub) == 0) + return true; + return false; +} + +function normalize_domain ($st) +{ + $dom = ''; + if (!empty($st)) + { + if (($p = strpos ($st, '://')) !== false) + $dom = substr ($st, $p+3); + $dom = strtolower ($dom); + if (preg_match ('#^www[0-9]*\.#is', $dom, $m)) + $dom = substr ($dom, strlen ($m[0])); + if (($p = strpos ($st, '/')) !== false) + $dom = substr ($st, 0, $p); + } + return $dom; +} + +function normalize_link ($l, $pre) +{ + if (!empty ($l) && !empty ($pre)) + { + if (str_starts_with($l, './')) + $l = substr($l, 2); + if (!strpos($l, '://')) + { + if ($l{0} != '/') + { + if ($pre{strlen($pre)-1} != '/') + $pre .= '/'; + $l = $pre . $l; + } + else if (($sch = strpos($pre, '://')) !== false) + $l = substr($pre, 0, strpos($pre, '/', $sch+3)) . $l; + } + return $l; + } + return $l; +} + +function strlimit($str, $maxlen) +{ + if (!$maxlen || $maxlen < 1 || strlen($str) <= $maxlen) + return $str; + $str = substr($str, 0, $maxlen); + $p = strrpos($str, ' '); + if (!$p || ($pt = strrpos($str, "\t")) > $ps) + $p = $pt; + if ($p) + $str = substr($str, 0, $p); + return $str . '...'; +} + +function html_pbr($str) +{ + return str_replace ("\n", "
", htmlspecialchars($str)); +} + +function html_strip_pbr ($str) +{ + global $allowed_html; + if (!($a = $allowed_html)) + $a = '

        
    • '; + return nl2br (strip_tags ($str, $a)); +} + +function html_strip ($str) +{ + global $allowed_html; + if (!($a = $allowed_html)) + $a = '
    • '; + return strip_tags ($str, $a); +} + +function unhtmlentity ($s) +{ + $s = html_entity_decode ($s); + preg_match_all ('/&#([0-9]*);/', $s, $ce, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); + $c = count($ce); + for ($i = $c-1; $i >= 0; $i--) + $s = substr ($s, 0, $ce[$i][0][1]) . chr($ce[$i][1][0]) . substr ($s, $ce[$i][0][1]+strlen($ce[$i][0][0])); + return $s; +} + +function process_array ($arr, $kfunc, $vfunc) +{ + $na = array (); + foreach ($arr as $k => $v) + { + if (is_callable($kfunc)) + $k = $kfunc ($k); + if (is_callable($vfunc)) + $v = $vfunc ($v); + $na [$k] = $v; + } + return $na; +} + +/** + * Функция разворачивает массив во вложенный хеш + * Например [[0,1],[0,2],[1,1]] -> [0:[1:[[0,1]],2:[[0,2]]],1:[1:[[1,1]]]] + */ +function array_hashtree($a, $ka = NULL, $value = NULL) +{ + $r = array(); + if (!is_array($ka)) + $ka = array($ka); + foreach ($a as $ca) + { + $c = &$r; + if (!is_null($ka)) + { + foreach ($ka as $k) + { + $k = $ca[$k]; + if (!array_key_exists($k, $c)) + $c[$k] = array(); + $c = &$c[$k]; + } + } + else + { + foreach ($ca as $k) + { + if (!array_key_exists($k, $c)) + $c[$k] = array(); + $c = &$c[$k]; + } + } + if (is_null($value)) + $c[] = $ca; + else + $c = $value; + } + return $r; +} + +/** + * Составление HTML кода картинки или flash-файла (возможно со ссылкой и alt) + * Если $link начинается с popup:// - тогда запишется javascript-код для popup-а ссылки + * Если $link начинается с popupwh:// - тогда дальше идёт WIDTH:HEIGHT: + */ +function banner ($path, $alt = false, $link = false, $width = false, $height = false, $target = false) +{ + if (!$path) + return ''; + $code = ''; + $wh = ''; + if ($width !== false && $width > 0) + $wh .= ' width="' . $width . '"'; + if ($height !== false && $height > 0) + $wh .= ' height="' . $height . '"'; + if (str_ends_with ($path, ".swf")) // flash + { + $code .= " + + + + + + +"; + } + else // картинка + { + if ($link !== false && str_starts_with ($link, 'popup://')) + { + $link = substr ($link, 8); + $link = "javascript: void window.open('$link', 'newWin', 'width=640,height=480,menubar=no,location=no,resizable=yes,scrollbars=yes').focus();"; + } + else if ($link !== false && str_starts_with ($link, 'popupwh://')) + { + $link = substr ($link, 10); + list ($popupw, $popuph, $link) = explode (':', $link, 3); + $link = "javascript: void window.open('$link', 'newWin', 'width=$popupw,height=$popuph,menubar=no,location=no,resizable=yes,scrollbars=yes').focus();"; + } + if ($link !== false) + $code .= ''; + if ($alt !== false) + $alttag = ' alt="'.htmlspecialchars($alt).'" title="'.htmlspecialchars($alt).'"'; + else $alttag = ''; + $code .= ""; + if ($link !== false) + $code .= ''; + } + return $code; +} + +/* Уложить ширину и высоту изображения пропорционально в рамки maxw/maxh */ +function fit_width_height($cw, $ch, $maxw, $maxh) +{ + if ($maxw <= 0 && $maxh <= 0 || $cw <= 0 || $ch <= 0) + return array($cw, $ch); + if ($maxw > 0 && ($maxh <= 0 || $maxh/$ch >= $maxw/$cw)) + { + $nw = $maxw; + $nh = $ch * $maxw / $cw; + } + else if ($maxh > 0 && ($maxw <= 0 || $maxh/$ch < $maxw/$cw)) + { + $nw = $cw * $maxh / $ch; + $nh = $maxh; + } + return array($nw, $nh); +} + +function loadimagebyext($from) +{ + if (strrpos($from, '.') !== false) + { + $ext = strtolower(substr($from, strrpos($from, '.')+1)); + if (function_exists("imagejpeg") && ($ext == 'jpg' || $ext == 'jpeg' || $ext == 'jfif' || $ext == 'jpe')) + $img = @imagecreatefromjpeg($from); + else if (function_exists("imagepng") && ($ext == 'png')) + $img = @imagecreatefrompng($from); + else if (function_exists("imagegif") && ($ext == 'gif')) + $img = @imagecreatefromgif($from); + } + return $img; +} + +function saveimagebyext($img, $to) +{ + global $default_jpeg_quality; + $qual = $default_jpeg_quality; + if (!$qual) + $qual = 85; + if (strrpos($to, '.') !== false) + { + $ext = strtolower(substr($to, strrpos($to, '.')+1)); + if ($ext == 'jpg' || $ext == 'jpeg' || $ext == 'jfif' || $ext == 'jpe') + @imagejpeg($img, $to, $qual); + else if ($ext == 'png') + @imagepng($img, $to); + else if ($ext == 'gif') + @imagegif($img, $to); + else + return false; + return $to; + } + return false; +} + +/** + * Масштабирование изображения, находящегося в файле $filename + * Поддерживаются JPG, GIF, и PNG, в зависимости от возможностей PHP + * В $maxw и $maxh возвратит результаты. + */ +function scaleimage ($from, $to, &$maxw, &$maxh, $ret_after_size = false) +{ + if (is_string($from)) + $img = loadimagebyext($from); + else + $img = $from; + if (!$img) + return false; + $cw = imagesx($img); + $ch = imagesy($img); + $maxw = intval($maxw); + $maxh = intval($maxh); + if (($maxw <= 0 || $cw <= $maxw) && ($maxh <= 0 || $ch <= $maxh)) + { + $maxw = $cw; + $maxh = $ch; + copy($from, $to); + @imagedestroy($img); + return true; + } + list($nw, $nh) = fit_width_height($cw, $ch, $maxw, $maxh); + $thumb = imagecreatetruecolor($nw, $nh); + $f = function_exists('imagecopyresampled') ? 'imagecopyresampled' : 'imagecopyresized'; + call_user_func($f, $thumb, $img, 0, 0, 0, 0, $nw, $nh, $cw, $ch); + imagedestroy($img); + $r = saveimagebyext($thumb, $to); + imagedestroy($thumb); + if ($ret_after_size) + { + $maxw = $nw; + $maxh = $nh; + } + else + { + $maxw = $cw; + $maxh = $ch; + } + return $r; +} + +/* обрезание до нужного соотношения сторон $aspect = X/Y + и потом - масштабирование до ширины $forcewidth, если она задана */ +function centercropimage($from, $to, $aspect, $forcewidth = 0) +{ + $img = loadimagebyext($from); + if (!$img) + return false; + $cw = imagesx($img); + $ch = imagesy($img); + $ca = $cw/$ch; + if ($ca > $aspect) + { + $nw = $aspect*$ch; + $nh = $ch; + } + else + { + $nw = $cw; + $nh = $cw/$aspect; + } + $posx = intval(($cw-$nw)/2); + $posy = intval(($ch-$nh)/2); + $cw = $nw; + $ch = $nh; + if ($forcewidth > 0) + { + $nh = $nh*$forcewidth/$nw; + $nw = $forcewidth; + } + $thumb = imagecreatetruecolor($nw, $nh); + $f = function_exists('imagecopyresampled') ? 'imagecopyresampled' : 'imagecopyresized'; + call_user_func($f, $thumb, $img, 0, 0, $posx, $posy, $nw, $nh, $cw, $ch); + imagedestroy($img); + $r = saveimagebyext($thumb, $to); + imagedestroy($thumb); + return $r; +} + +function get_image_size($from, &$w, &$h) +{ + if (strrpos ($from, '.') !== false) + { + $ext = strtolower (substr ($from, strrpos ($from, '.')+1)); + if (function_exists ("imagejpeg") && ($ext == 'jpg' || $ext == 'jpeg' || $ext == 'jfif' || $ext == 'jpe')) + $img = @imagecreatefromjpeg ($from); + else if (function_exists ("imagepng") && ($ext == 'png')) + $img = @imagecreatefrompng ($from); + else if (function_exists ("imagegif") && ($ext == 'gif')) + $img = @imagecreatefromgif ($from); + if ($img) + { + $w = imagesx ($img); + $h = imagesy ($img); + imagedestroy ($img); + return true; + } + } + return false; +} + +function load_image ($filevarname, $newfilename, &$imgvar, &$thumbvar, &$imgw, &$imgh, $maxdim_config = '') +{ + global $local_path, $image_path, $file_creation_mode; + $ret = false; + + $path = $image_path . $newfilename; + if (($pos = strrpos ($path, '/')) !== false) + $path = substr ($path, 0, $pos+1); + else $path = '/'; + + if (isset ($_FILES [$filevarname]['tmp_name']) && + $_FILES [$filevarname]['name'] != '') + { + $ext = strtolower (substr ($_FILES [$filevarname]['name'], strrpos ($_FILES [$filevarname]['name'], '.')+1)); + if ($ext == 'jpg' || $ext == 'jpeg' || + $ext == 'jpe' || $ext == 'jfif' || + $ext == 'png' || $ext == 'gif') + { + $fn_i = $image_path . $newfilename . '.' . $ext; + $fn_t = $image_path . $newfilename . '_t.' . $ext; + @unlink ($local_path . $fn_i); // !!! - предыдущее файло сотрётся + @unlink ($local_path . $fn_t); // !!! + if ((@move_uploaded_file ($_FILES [$filevarname]['tmp_name'], $local_path . $fn_i))) + { + $imgw = 0+config_var ($maxdim_config.'img_maxw'); + $imgh = 0+config_var ($maxdim_config.'img_maxh'); + if ($imgw <= 0) + $imgw = false; + if ($imgh <= 0) + $imgh = false; + if (is_numeric($file_creation_mode)) + chmod($local_path . $fn_i, $file_creation_mode); + if (scaleimage ($local_path . $fn_i, $local_path . $fn_t, $imgw, $imgh)) + { + $imgvar = $fn_i; + $thumbvar = $fn_t; + $ret = true; + if (is_numeric($file_creation_mode)) + chmod($local_path . $fn_t, $file_creation_mode); + } + } + else + @unlink ($_FILES [$filevarname]['tmp_name']); + } + else + @unlink ($_FILES [$filevarname]['tmp_name']); + } + return $ret; +} + +function load_file ($filevarname, $newfilename) +{ + global $local_path, $file_creation_mode; + $ret = false; + + $path = $newfilename; + if (($pos = strrpos ($path, '/')) !== false) + $path = substr ($path, 0, $pos+1); + else $path = '/'; + + if (isset ($_FILES [$filevarname]['tmp_name'])) + { + $ext = strtolower (substr ($_FILES [$filevarname]['name'], strrpos ($_FILES [$filevarname]['name'], '.')+1)); + $fn = $newfilename . '.' . $ext; + @unlink ($local_path . $fn); + if ((@move_uploaded_file ($_FILES [$filevarname]['tmp_name'], $local_path . $fn))) + { + $ret = $fn; + if (is_numeric($file_creation_mode)) + chmod($local_path . $fn, $file_creation_mode); + } + else + @unlink ($_FILES [$filevarname]['tmp_name']); + } + return $ret; +} + +function file_size ($filename) +{ + global $local_path; + $r = false; + if ($filename{0} == '/' && + file_exists($local_path . $filename)) + $r = filesize ($local_path . $filename); + else if (str_starts_with ($filename, 'http://') || + str_starts_with ($filename, 'ftp://')) + { + $a = @get_headers ($filename, 1); + if (isset ($a ['Content-Length'])) + $r = $a ['Content-Length']; + } + return $r; +} + +function file_size_string ($bytes) +{ + $r = $bytes; + if (is_numeric ($r)) + { + if ($r >= 0 && $r < 1024) + $r = $r . ' байт'; + else if ($r >= 1024 && $r < 1024*1024) + $r = sprintf ('%.2f Кб', $r/1024); + else if ($r >= 1024*1024 && $r < 1024*1024*1024) + $r = sprintf ('%.2f Мб', $r/1024/1024); + else if ($r >= 1024*1024*1024) + $r = sprintf ('%.2f Гб', $r/1024/1024/1024); + else if ($r < 0) + $r = sprintf ('%.2f Гб', 2-($r/1024/1024/1024)); + } + return $r; +} + +/** + * Получить данные от URL $url. Если $userpwd задана - тогда будет использована + * HTTP аутентификация с логином и паролем, указанными в $userpwd как login:password. + * Если данные являются gzip-сжатыми - они будут автоматически распакованы. + */ +function http_get_contents ($url, $userpwd = false) +{ + global $local_path, $cache_path; + + $ch = curl_init(); + curl_setopt ($ch, CURLOPT_URL, $url); + curl_setopt ($ch, CURLOPT_HEADER, 0); + curl_setopt ($ch, CURLOPT_USERAGENT, 'CommercialRealty/1.0'); + + mt_srand ((float)microtime() * 1000000); + $eh = $local_path . $cache_path . '/php-' . md5(mt_rand(0,mt_getrandmax())) . '.gz'; + $fd = fopen ($eh, 'wb'); + curl_setopt ($ch, CURLOPT_FILE, $fd); + + if (!empty ($userpwd)) + curl_setopt ($ch, CURLOPT_USERPWD, $userpwd); + + @curl_exec ($ch); + curl_close ($ch); + fclose ($fd); + + ob_start (); + readgzfile ($eh); + $data = ob_get_clean (); + unlink ($eh); + + return $data; +} + +/* Преобразовать имена некоторых строк в ID некоторых строк */ +function get_tags($tags, $add_unknown = true, $table = 'tags', $id_field = 'id', $name_field = 'name') +{ + $tagids = array_hashtree(mysql_select($table, "$id_field, LOWER($name_field) $name_field", array($name_field => $tags)), $name_field); + $unk = array(); + foreach ($tags as $t) + if (!$tagids[mb_strtolower($t, 'utf-8')]) + $unk[] = array($name_field => $t); + if ($unk && $add_unknown) + { + mysql_update($table, $unk); + $tagids = array_hashtree(mysql_select($table, "$id_field, LOWER($name_field) $name_field", array($name_field => $tags)), $name_field); + } + foreach ($tags as &$t) + if ($tt = $tagids[mb_strtolower($t, 'utf-8')]) + $t = $tt[0]; + return $tags; +} + +/** + * Функция возвращает ID значения $v поля $stringfield (по умолчанию поля name), при дополнительных + * требованиях $adds = array (поле => значение, ...) и, если такого значения нету, добавляет его в БД. + * + * Функция работает так: + * - если $v пусто (empty($v)==true) - функция возвращает 0 (ибо нефиг); + * - ищет, есть ли заданное значение там, где удовлетворены все требования, если есть - всё ок, возвращает его ID; + * - по одному с конца ослабляет требования, и если находит соответствующую строку - редактирует её таким образом, + * чтобы она соответствовала всем заданным требованиям, и возвращает её ID; + * - если такая строка не найдена - добавляет её и возвращает её ID. + * - если произошла какая-либо ошибка, она возвращает 0. + * - если в найденной записи поле stop != 0 => функция возвращает 0. + * полезно, если мы хотим "обучать" функцию не совершать ошибки повторно. + */ +function stringtable_get_id ($v, $table, $adds, $stringfield = 'name') +{ + $rid = 0; + if (!empty ($v)) + { + $v = mysql_ecranize ($v); + $addset = array (); + if (is_array ($adds)) + foreach ($adds as $pk => $pf) + $addset [] = "`$pk`=" . mysql_ecranize($pf); + $search = array_merge (array ("`$stringfield`=$v"), $addset); + $change = array (); + while (count ($search) > 0) + { + if (($rid = 0+amquery1x1 ("SELECT `id` FROM `$table` WHERE " . implode (' AND ', $search) . ' LIMIT 1')) > 0) + { + if (!empty ($change)) + amysql_query ("UPDATE `$table` SET " . implode (', ', $change) . " WHERE `id`=$rid"); + break; + } + $change [] = array_pop ($search); + } + if (!$rid && amysql_query ("INSERT INTO `$table` SET " . implode (', ', $change))) + { + $rid = mysql_insert_id (); + if (!$rid) + $rid = 0+amquery1x1 ("SELECT id FROM `$table` WHERE " . implode (' AND ', $change) . ' LIMIT 1'); + } + if ($rid && acmget ($table, $rid, 'stop') > 0) + $rid = 0; + } + return $rid; +} + +/** + * Начало сессии :) + */ +function session_begin($lifetime = -1) +{ + global $Domain, $LoginSession; + session_name($LoginSession); + list($dom, $path) = explode('/', $Domain, 2); + $path = "/$path"; + session_set_cookie_params($lifetime, $path, '.' . $dom); + @session_start(); +} + +/** + * Определение нужного количества итераций блока $block на основе: + * sql-запроса $sql ($row[0] это ID и $row[1] это NAME) и + * массива $arr (массив сначала). + * Функция предназначена для присваивания блоков + * и присвоит "выделено" той опции, которая задана ID-ом $sel_id + */ +function option_block ($sql, $arr, $sel_id = NULL) +{ + $vars = array(); + if (is_array ($arr)) + foreach ($arr as $id => $name) + $vars[] = array ( + 'ID' => $id, + 'NAME' => $name, + 'SEL' => !is_null($sel_id) && $id == $sel_id ? 'selected' : '', + ); + if (is_string($sql) && ($rows = mysql_get_rows($sql))) + { + foreach ($rows as $row) + { + $vars[] = array ( + 'ID' => $row[0], + 'NAME' => $row[1], + 'SEL' => !is_null($sel_id) && $row[0] == $sel_id ? 'selected' : '', + ); + } + } + return $vars; +} + +/** + * Данные постраничной навигации внутри $allcount элементов по + * $perpage на странице, при том условии что текущий первый отображённый - это $start. + * Ссылка на страницу будет getURL($component, $args+array(page => НОМЕР_СТРАНИЦЫ)). + * Если задано $eps, то отобразится не более 2*$eps соседних страниц. + */ +function page_nav3 ($total, $perpage, $start, $component, $args = array(), $eps = NULL, $metanext = 'METAS', $pagearg = 'page') +{ + if ($perpage <= 0 && $total > 0) + $perpage = $total; + if ($perpage <= 0) + $perpage = 1; + $curpage = floor($start / $perpage); + $pagecount = ceil($total / $perpage); + if ($curpage >= $pagecount) + $curpage = $pagecount-1; + if ($curpage < 0) + $curpage = 0; + + $vars = array ( + 'total' => $total, + 'perpage' => $perpage, + 'list_start' => $start+1, + 'page_count' => $pagecount, + 'list_end' => min($total, $start+$perpage), + 'page' => array(), + 'current_page' => $curpage+1, + ); + if ($pagecount < 2) + return $vars; + + if ($curpage > 0) + { + $args[$pagearg] = $curpage-1; + $vars['page'][] = array ( + 'name' => '<', + 'href' => getURL($component, $args), + 'nc' => true, + ); + } + if (is_null($eps)) + $eps = config_var('page_nav_eps', 10); + if ($eps && $eps > 0) + { + $ppend = min ($pagecount, $curpage + $eps + 1); + for ($i = max (0, $curpage - $eps); $i < $ppend; $i++) + { + $args[$pagearg] = $i; + $vars['page'][] = array ( + 'name' => $i+1, + 'href' => getURL($component, $args), + 'nc' => $i != $curpage, + ); + } + } + else + { + for ($i = 0; $i < $pagecount; $i++) + { + $args[$pagearg] = $i; + $vars['page'][] = array ( + 'name' => $i+1, + 'href' => getURL($component, $args), + 'nc' => $i != $curpage, + ); + } + } + if ($curpage < $pagecount-1) + { + $args[$pagearg] = $curpage+1; + if ($metanext) + { + global $template; + $template->tpldata[$metanext] .= ''; + } + $vars['page'][] = array ( + 'name' => '>', + 'href' => getURL($component, $args), + 'nc' => true, + ); + } + return $vars; +} + +function php2js($a) +{ + if (is_null($a)) return 'null'; + if ($a === false) return 'false'; + if ($a === true) return 'true'; + if (is_scalar($a)) { + $a = addslashes($a); + $a = str_replace("\n", '\n', $a); + $a = str_replace("\r", '\r', $a); + $a = preg_replace('{($v) + $result[] = php2js($k) . ': ' . php2js($v); + return '{ ' . join(', ', $result) . ' }'; + } +} + +if (!function_exists("quoted_printable_encode")) +{ + define('QP_LINE_LENGTH', 75); + define('QP_LINE_SEPARATOR', "\r\n"); + + function quoted_printable_encode($string, $encodedWord = false) + { + if(!preg_match('//u', $string)) { + throw new Exception('Input string is not valid UTF-8'); + } + + static $wordStart = '=?UTF-8?Q?'; + static $wordEnd = '?='; + static $endl = QP_LINE_SEPARATOR; + + $lineLength = $encodedWord + ? QP_LINE_LENGTH - strlen($wordStart) - strlen($wordEnd) + : QP_LINE_LENGTH; + + $string = $encodedWord + ? preg_replace('~[\r\n]+~', ' ', $string) // we need encoded word to be single line + : preg_replace('~\r\n?~', "\n", $string); // normalize line endings + $string = preg_replace('~[\x00-\x08\x0B-\x1F]+~', '', $string); // remove control characters + + $output = $encodedWord ? $wordStart : ''; + $charsLeft = $lineLength; + + $chr = isset($string{0}) ? $string{0} : null; + $ord = ord($chr); + + for ($i = 0; isset($chr); $i++) { + $nextChr = isset($string{$i + 1}) ? $string{$i + 1} : null; + $nextOrd = ord($nextChr); + + if ( + $ord > 127 or // high byte value + $ord === 95 or // underscore "_" + $ord === 63 && $encodedWord or // "?" in encoded word + $ord === 61 or // equal sign "=" + // space or tab in encoded word or at line end + $ord === 32 || $ord === 9 and $encodedWord || !isset($nextOrd) || $nextOrd === 10 + ) { + $chr = sprintf('=%02X', $ord); + } + + if ($ord === 10) { // line feed + $output .= $endl; + $charsLeft = $lineLength; + } elseif ( + strlen($chr) < $charsLeft or + strlen($chr) === $charsLeft and $nextOrd === 10 || $encodedWord + ) { // add character + $output .= $chr; + $charsLeft-=strlen($chr); + } elseif (isset($nextOrd)) { // another line needed + $output .= $encodedWord + ? $wordEnd . $endl . "\t" . $wordStart . $chr + : '=' . $endl . $chr; + $charsLeft = $lineLength - strlen($chr); + } + + $chr = $nextChr; + $ord = $nextOrd; + } + + return $output . ($encodedWord ? $wordEnd : ''); + } +} + +function is_intval($a) +{ + return (is_string($a) || is_int($a)) && !strcmp(intval($a), $a); +} + +function litsplit($re, $s) +{ + $r = array(); + $s = trim($s); + $lit = "\'(?:[^\'\\\\]+|\\.|\'\')+\'|\"(?:[^\"\\\\]+|\\.|\"\")+\""; + $offset = 0; + $re = "/((?:$lit|.+?)*?)($re)/is"; + while (preg_match($re, $s, $m, PREG_OFFSET_CAPTURE, $offset)) + { + $r[] = $m[1][0]; + $offset = $m[0][1] + strlen($m[0][0]); + } + $s = substr($s, $offset); + if (strlen($s)) + $r[] = $s; + return $r; +} + +function probe_video($fn) +{ + global $VideoProbe; + if (!$VideoProbe || !$VideoProbe['cmd'] || !$VideoProbe['size'] || !$VideoProbe['duration']) + die(__FUNCTION__.": \$VideoProbe=array('cmd'=>'shell command','size'=>'size regexp','duration'=>'duration regexp') isn't configured yet"); + $cmd = $VideoProbe['cmd']; + $cmd = str_replace('$input', $fn, $cmd); + $out = shell_exec($cmd); + $db = array(); + if (preg_match($VideoProbe['size'], $out, $m)) + { + $db['width'] = $m[1]; + $db['height'] = $m[2]; + } + if (preg_match($VideoProbe['duration'], $out, $m)) + { + $dur = $m[1]; + $colon = explode(':', $dur); + if (count($colon) > 1) + { + $dur = 0; + for ($mul = 1, $i = 0; $i < count($colon); $i++, $mul *= 60) + $dur += $colon[count($colon)-1-$i] * $mul; + } + $db['duration'] = $dur; + } + return $db; +} + +function extract_video_frame($from, $time, $w, $h, $to) +{ + global $VideoConverter; + if (!$VideoConverter) + die(__FUNCTION.": \$VideoConverter = 'shell command' isn't configured yet"); + $cmd = $VideoConverter; + $repl = array( + '$input' => $from, + '$position' => $time, + '$output' => $to, + ); + $cmd = str_replace(array_keys($repl), array_values($repl), $cmd); + exec($cmd, $output, $exitcode); + if ($exitcode != 0) + return false; + scaleimage($to, $to, $w, $h); + return true; +} diff --git a/lib/mysql.php b/lib/mysql.php new file mode 100644 index 0000000..a6105de --- /dev/null +++ b/lib/mysql.php @@ -0,0 +1,493 @@ + 1 && $pop != $mysql_db_stack[$n-2]) + return mysql_select_db($mysql_db_stack[$n-2], $mysql_primary_link); + return false; +} + +function mysql_started() +{ + global $mysql_primary_link; + return is_resource($mysql_primary_link); +} + +function amysql_query($q) +{ + global $amy_query_count, $mysql_primary_link, $mysqlQueryLogFile, $mysqlThrowError; + if (!mysql_started()) + mysql_start(false); + if (!isset($amy_query_count)) + $amy_query_count = 0; + $amy_query_count++; + if ($mysqlQueryLogFile && ($fp = fopen($mysqlQueryLogFile, "ab"))) + $start = microtime(true); + $result = mysql_query($q, $mysql_primary_link); + if (!$result && $mysqlThrowError) + { + debug_print_backtrace(); + die(mysql_errno().': '.mysql_error()); + } + if ($fp) + { + $t = microtime(true)-$start; + fwrite($fp, date("[Y-m-d H:i:s] ").intval($t/1000).".".($t%1000)." sec $q".(mysql_errno() ? ': ERROR! '.mysql_errno().': '.mysql_error() : '')."\n"); + fclose($fp); + } + return $result; +} + +function amysql_queries($qs, $ignore_errors = true) +{ + $r = true; + $qs = explode(';', $qs); + foreach ($qs as $q) + { + $q = trim($q); + if ($q != '' && !amysql_query($q)) + { + $r = false; + if (!$ignore_errors) + break; + } + } + return $r; +} + +function amquery1x1($s) +{ + if (($r = amysql_query($s)) && ($r = mysql_fetch_row($r))) + return $r[0]; + return false; +} + +function mysql_ecranize($s) +{ + global $mysql_primary_link; + if (!$mysql_primary_link) + mysql_start(); + if (is_null($s)) + return "NULL"; + return "'" . mysql_real_escape_string($s) . "'"; +} + +function mysql_time($dat, $tim, $format) +{ + return @date($format, mktime (substr ($tim, 0, 2), substr ($tim, 3, 2), substr ($tim, 6, 2), substr($dat,5,2), substr($dat,8,2), substr($dat,0,4))); +} + +// Дополнить кэш таблицы $table с помощью запроса $q +function cmcache($table, $q) +{ + global $mysql_php_cache; + $cnt = 0; + $r = amysql_query($q); + if ($r) + { + while ($row = mysql_fetch_assoc ($r)) + { + $mysql_php_cache [$table][$row['id']] = $row; + $cnt++; + } + } + return $cnt; +} + +// Функция кэширующего чтения поля field из записи номер id таблицы table: "table/id/field" +// Кэш можно заполнять предыдущей функцией, хотя эта и сама может кэшировать записи по одной +function acmget($table, $id, $field) +{ + global $mysql_php_cache, $cfgTables; + if ($cfgTables[$table]) + $table = $cfgTables[$table]; + if (!isset ($mysql_php_cache[$table][$id][$field]) && + cmcache ($table, "SELECT * FROM `$table` WHERE `id`=$id LIMIT 1") < 1) + return false; + return $mysql_php_cache[$table][$id][$field]; +} + +function cmchainmatch($table, $parent_field, $start, $field, $value) +{ + while (($new = acmget($table, $start, $field)) != $value && $start > 0) + $start = $new; + if (acmget($table, $start, $field) == $value) + return true; + return false; +} + +function mysql_where_builder($where) +{ + $wh = array(); + foreach ($where as $k => $v) + { + if (is_numeric($k)) + $wh[] = $v; + else if (is_array($v)) + { + if (!$v) + { + debug_print_backtrace(); + die("Error: empty array for '$k IN (...)', don't know what to do"); + } + else + { + if (is_array($v[0])) + foreach ($v as &$l) + $l = "(" . implode(",", array_map('mysql_ecranize', $l)) . ")"; + else + $v = array_map('mysql_ecranize', $v); + $wh[] = "$k IN (" . implode(",", $v) . ")"; + } + } + elseif (!is_null($v)) + $wh[] = "$k=".mysql_ecranize($v); + else + $wh[] = "$k IS NULL"; + } + if (!$wh) + $where = '1'; + else + $where = '(' . join(') AND (', $wh) . ')'; + return $where; +} + +function mysql_select_builder($tables, $fields, $where, $options = NULL) +{ + if (!$options) + $options = array(); + else + { + foreach ($options as $k => $v) + if (is_intval($k)) + $options[$v] = true; + } + if (is_array($fields)) + $fields = join(',', $fields); + if (is_array($where)) + $where = mysql_where_builder($where); + $tables = mysql_tables_builder($tables); + $sql = 'SELECT '; + if ($options['CALC_FOUND_ROWS'] || $options['SQL_CALC_FOUND_ROWS']) + $sql .= 'SQL_CALC_FOUND_ROWS '; + $sql .= "$fields FROM $tables WHERE $where"; + if ($g = $options['GROUP BY']) + { + if (is_array($g)) + { + $g1 = array(); + foreach ($g as $k => $v) + { + if (is_numeric($k)) + $g1[] = $v; + else + $g1[] = "$k $v"; + } + $g = join(',', $g1); + } + $sql .= " GROUP BY $g"; + } + if ($g = $options['ORDER BY']) + { + if (is_array($g)) + { + $g1 = array(); + foreach ($g as $k => $v) + { + if (is_numeric($k)) + $g1[] = $v; + else + $g1[] = "$k $v"; + } + $g = join(',', $g1); + } + $sql .= " ORDER BY $g"; + } + if ($g = $options['LIMIT']) + { + if (is_array($g)) + $g = join(',', $g); + if ($g) + $sql .= " LIMIT $g"; + } + if ($options['FOR UPDATE']) + $sql .= ' FOR UPDATE'; + elseif ($options['LOCK IN SHARE MODE']) + $sql .= ' LOCK IN SHARE MODE'; + return $sql; +} + +function mysql_tables_builder($tables) +{ + global $cfgTables; + if (is_array($tables)) + { + $t = array(); + foreach ($tables as $k => $v) + { + if (!is_numeric($k)) + $v = ($cfgTables[$v] ? '`'.$cfgTables[$v].'`' : $v) . ' ' . $k; + else + $v = ($cfgTables[$v] ? '`'.$cfgTables[$v].'` '.$v : $v); + $t[] = $v; + } + $tables = join(',', $t); + } + else + $tables = preg_replace_callback('/((?:,|JOIN)\s*`)([^`]+)/s', 'mysql_tables_builder_pregcb1', $tables); + return $tables; +} + +function mysql_tables_builder_pregcb1($m) +{ + global $cfgTables; + $t = $cfgTables[$m[2]]; + if (!$t) + $t = $m[2]; + return $m[1].$t; +} + +function mysql_select($tables, $fields = '*', $where = 1, $options = NULL, $format = 0) +{ + if (!strcmp(intval($fields), "$fields")) + { + $sql = $tables; + $format = $fields; + } + else + $sql = mysql_select_builder($tables, $fields, $where, $options); + if ($format & MS_RESULT) + return amysql_query($sql); + if ($format & MS_LIST) + $r = mysql_get_rows($sql); + else + $r = mysql_get_assocs($sql); + if (!$r) + $r = array(); + if ($format & MS_ROW) + { + if ($format & MS_COL) + { + if (!count($r)) + return NULL; + $r = $r[0]; + $k = array_keys($r); + $r = $r[$k[0]]; + } + else + $r = $r[0]; + } + elseif ($format & MS_COL) + { + foreach ($r as $i => $v) + { + if (!$k) + { + $k = array_keys($v); + $k = $k[0]; + } + $r[$i] = $v[$k]; + } + } + return $r; +} + +function mysql_found_rows() +{ + return mysql_select('SELECT FOUND_ROWS()', MS_VALUE); +} + +function mysql_delete($tables, $where, $options = NULL) +{ + global $mysql_primary_link; + $tables = mysql_tables_builder($tables); + if (is_array($where)) + $where = mysql_where_builder($where); + $sql = "DELETE FROM $tables WHERE $where"; + if (is_array($options)) + { + if ($g = $options['LIMIT']) + { + if (is_array($g)) + $g = join(',', $g); + if ($g) + $sql .= " LIMIT $g"; + } + } + amysql_query($sql); + return mysql_affected_rows($mysql_primary_link); +} + +function mysql_insert_builder($table, $rows, $onduplicatekey = false) +{ + global $cfgTables; + if ($cfgTables[$table]) + $table = $cfgTables[$table]; + $key = array_keys($rows[0]); + foreach ($rows as &$r) + { + $rs = array(); + foreach ($key as &$k) + $rs[] = mysql_ecranize($r[$k]); + $r = "(".implode(",", $rs).")"; + } + foreach ($key as &$k) + if (strpos($k, '`') === false) + $k = "`$k`"; + $sql = "INSERT INTO $table (".implode(",",$key).") VALUES ".implode(",",$rows); + if ($onduplicatekey) + { + foreach ($key as &$k) + $k = "$k=VALUES($k)"; + $sql .= " ON DUPLICATE KEY UPDATE ".implode(",",$key); + } + return $sql; +} + +function mysql_insert_row($table, $row) +{ + global $mysql_primary_link; + $sql = mysql_insert_builder($table, array($row)); + if (amysql_query($sql)) + return mysql_insert_id($mysql_primary_link); + return NULL; +} + +function mysql_update($table, $rows, $where = NULL, $options = NULL) +{ + global $mysql_primary_link; + if (!$rows) + return false; + if (is_null($where)) + { + if (!is_array($rows)) + return false; + if (!is_array($rows[0])) + $rows = array($rows); + $sql = mysql_insert_builder($table, $rows, true); + } + else + { + $sql = array(); + if (!is_array($rows)) + $rows = array($rows); + foreach ($rows as $k => $v) + { + if (!is_intval($k)) + $sql[] = $k.'='.mysql_ecranize($v); + else + $sql[] = $v; + } + if (is_array($where)) + $where = mysql_where_builder($where); + $sql = 'UPDATE ' . mysql_tables_builder($table) . ' SET ' . implode(', ', $sql) . ' WHERE ' . $where; + if (is_array($options)) + { + if ($g = $options['LIMIT']) + { + if (is_array($g)) + $g = join(',', $g); + if ($g) + $sql .= " LIMIT $g"; + } + } + } + if (amysql_query($sql)) + return mysql_affected_rows($mysql_primary_link); + return false; +} + +function mysql_get_rows($sql) +{ + $r = array(); + if (is_resource($res = $sql) || ($res = amysql_query ($sql))) + while ($row = mysql_fetch_row ($res)) + $r[] = $row; + return $r; +} + +function mysql_get_assocs($sql) +{ + $r = array(); + if (is_resource($res = $sql) || ($res = amysql_query ($sql))) + while ($row = mysql_fetch_assoc ($res)) + $r[] = $row; + return $r; +} + +function load_config() +{ + global $engine_configuration, $mysqlNoSiteConfig; + if ($mysqlNoSiteConfig) + return; + $engine_configuration = array(); + if ($rows = mysql_select('config', array('name', 'value'), 1, array(), MS_LIST)) + foreach ($rows as $row) + $engine_configuration[$row[0]] = $row[1]; +} + +function config_var($name, $default = NULL) +{ + global $engine_configuration; + if (!$engine_configuration) + load_config(); + $v = @$engine_configuration[$name]; + if (!is_null($default) && !strlen($v) || is_intval($default) && $v <= 0) + $v = $default; + elseif (is_intval($default)) + $v = intval($v); + return $v; +} + +function mysql_finish() +{ + global $mysql_primary_link; + if (mysql_started()) + mysql_close($mysql_primary_link); + $mysql_primary_link = NULL; +} diff --git a/lib/template.php b/lib/template.php new file mode 100644 index 0000000..e0edf42 --- /dev/null +++ b/lib/template.php @@ -0,0 +1,1099 @@ +

            
    • '; + + var $errors = array(); // содержит последние ошибки + var $root = '.'; // каталог с шаблонами + var $reload = 1; // если 0, шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет + var $wrapper = false; // фильтр, вызываемый перед выдачей результата parse + var $tpldata = array(); // сюда будут сохранены: данные + var $cache_dir = false; // необязательный кэш, ускоряющий работу только в случае частых инициализаций интерпретатора + var $use_utf8 = true; // шаблоны в UTF-8 и с флагом UTF-8 + var $begin_code = ''; // конец кода + var $eat_code_line = true; // съедать "лишний" перевод строки, если в строке только инструкция? + var $begin_subst = '{'; // начало подстановки (необязательно) + var $end_subst = '}'; // конец подстановки (необязательно) + var $strict_end = false; // жёстко требовать имя блока в его завершающей инструкции () + var $raise_error = false; // говорить die() при ошибках в шаблонах + var $print_error = false; // печатать фатальные ошибки + var $compiletime_functions = array(); // дополнительные функции времени компиляции + + function __construct($args) + { + foreach ($args as $k => $v) + $this->$k = $v; + $this->cache_dir = preg_replace('!/*$!s', '/', $this->cache_dir); + if (!is_writable($this->cache_dir)) + $this->error('Template: cache_dir='.$this->cache_dir.' is not writable', true); + $this->root = preg_replace('!/*$!s', '/', $this->root); + } + + // Сохранить ошибку + function error($e, $fatal = false) + { + $this->errors[] = $e; + if ($this->raise_error && $fatal) + die(__CLASS__."::error: $e"); + elseif ($this->print_error) + print __CLASS__."::error: $e\n"; + } + + // Функция уничтожает данные шаблона + function clear() + { + $this->tpldata = array(); + return true; + } + + // Подлить в огонь переменных. Возвращает новый массив. + function assign_vars($new = NULL, $value = NULL) { return $this->vars($new, $value); } + function vars($new = NULL, $value = NULL) + { + if (is_array($new)) + $this->tpldata = array_merge($this->tpldata, $new); + else if ($new && $value !== NULL) + $this->tpldata[$new] = $value; + return $this->tpldata; + } + + // Кэш (xcache, eaccelerator) + static function cache_check_type() + { + if (is_null(self::$cache_type)) + { + if (function_exists('xcache_get')) + self::$cache_type = 'x'; + else if (function_exists('eaccelerator_get')) + self::$cache_type = 'e'; + else + self::$cache_type = ''; + } + } + static function cache_get($key) + { + self::cache_check_type(); + if (!array_key_exists($key, self::$cache)) + { + if (self::$cache_type == 'x') + self::$cache[$key] = xcache_get($key); + else if (self::$cache_type == 'e') + self::$cache[$key] = eaccelerator_get($key); + } + return self::$cache[$key]; + } + static function cache_del($key) + { + self::cache_check_type(); + unset(self::$cache[$key]); + if (self::$cache_type == 'x') + xcache_unset($key); + else if (self::$cache_type == 'e') + eaccelerator_rm($key); + } + static function cache_set($key, $value) + { + self::cache_check_type(); + self::$cache[$key] = $value; + if (self::$cache_type == 'x') + xcache_set($key, $value); + else if (self::$cache_type == 'e') + eaccelerator_put($key, $value); + } + + // Функция загружает, компилирует и возвращает результат для хэндла + // $page = $obj->parse( 'file/name.tpl' ); + // $page = $obj->parse( 'template {CODE}', true ); + function parse($fn, $inline = false) + { + $this->errors = array(); + if ($inline) + { + $text = $fn; + $fn = ''; + if (!$text) + return ''; + } + else + { + if (!strlen($fn)) + { + $this->error("Template: empty filename '$fn'", true); + return NULL; + } + if (substr($fn, 0, 1) != '/') + $fn = $this->root.$fn; + if (!($text = $this->loadfile($fn))) + { + $this->error("Template: couldn't load template file '$fn'", true); + return NULL; + } + } + if (!($file = $this->compile($text, $fn))) + return NULL; + $stack = array(); + include $file; + $w = $this->wrapper; + if (is_callable($w)) + $w(&$t); + return $t; + } + + // Функция загружает файл с кэшированием + // $textref = $obj->loadfile($file) + function loadfile($fn) + { + $load = false; + if (!($text = self::cache_get("U$fn")) || $this->reload) + { + $mtime = stat($fn); + $mtime = $mtime[9]; + if (!$text) + $load = true; + else + { + $ctime = self::cache_get("T$fn"); + if ($ctime < $mtime) + $load = true; + } + } + // если файл изменился - перезасасываем + if ($load) + { + if ($fp = fopen($fn, "rb")) + { + fseek($fp, 0, SEEK_END); + $t = ftell($fp); + fseek($fp, 0, SEEK_SET); + $text = fread($fp, $t); + fclose($fp); + } + else + return NULL; + self::cache_set("T$fn", $mtime); + self::cache_set("U$fn", $text); + } + return $text; + } + + // Функция компилирует код. + // $file = $this->compile($code, $fn); + // require $file; + // print $t; + function compile($code, $fn) + { + $md5 = md5($code); + $file = $this->cache_dir . 'tpl' . $md5 . '.php'; + if (file_exists($file)) + return $file; + + // начала/концы спецстрок + $bc = $this->begin_code; + if (!$bc) + $bc = ''; + + // маркер начала, маркер конца, обработчик, съедать ли начало и конец строки + $blk = array(array($bc, $ec, 'compile_code_fragment', $this->{eat_code_line})); + if ($this->begin_subst && $this->end_subst) + $blk[] = array($this->{begin_subst}, $this->{end_subst}, 'compile_substitution'); + foreach ($blk as &$v) + { + $v[4] = strlen($v[0]); + $v[5] = strlen($v[1]); + } + + $st = new TemplateState(); + + // ищем фрагменты кода - на регэкспах-то было не очень правильно, да и медленно! + $r = ''; + $pp = 0; + $l = strlen($code); + while ($code && $pp < $l) + { + $p = array(); + $b = NULL; + // ищем ближайшее + foreach ($blk as $i => $bi) + if (($p[$i] = strpos($code, $bi[0], $pp)) !== false && + (is_null($b) || $p[$i] < $p[$b])) + $b = $i; + if (!is_null($b)) + { + /* это означает, что в случае отсутствия корректной инструкции + в найденной позиции надо пропустить ТОЛЬКО её начало и попробовать + найти что-нибудь снова! */ + $pp = $p[$b]+$blk[$b][4]; + $e = strpos($code, $blk[$b][1], $pp); + if ($e >= 0) + { + $frag = substr($code, $p[$b]+$blk[$b][4], $e-$p[$b]-$blk[$b][4]); + $f = $blk[$b][2]; + $t = $frag; + if (!preg_match('/^\s*\n/s', $frag)) + $frag = $this->$f($st, $frag); + else + $frag = NULL; + if (!is_null($frag)) + { + // есть инструкция + $pp -= $blk[$b][4]; + if ($pp > 0) + { + $text = substr($code, 0, $pp); + $code = substr($code, $pp); + $text = addcslashes($text, '\\\''); + // съедаем перевод строки, если надо + if ($blk[$b][5]) + $text = preg_replace('/\r?\n\r?[ \t]*$/s', '', $text); + if (strlen($text)) + $r .= "\$t.='$text';\n"; + $pp = 0; + } + $r .= $frag; + $code = substr($code, $e+$blk[$b][5]-$p[$b]); + } + } + } + else + { + // финиш + $code = addcslashes($code, '\\\''); + $r .= "\$t.='$code';\n"; + $code = ''; + } + } + + // дописываем начало и конец кода + if (!$fn) + { + $c = debug_backtrace(); + $c = $c[2]; + $fn = 'inline code in '.$c['class'].$c['type'].$c['function'].'() at '.$c['file'].':'.$c['line']; + } + $code = "compile_code_fragment_if($st, 'elsif', $m[1]); + return $t ? NULL : "} else {"; + } + + // IF expression + // ELSIF expression + function compile_code_fragment_if($st, $kw, $t) + { + $e = $this->compile_expression($t); + if ($e === NULL) + { + $this->error("Invalid expression in $kw: '$t'"); + return NULL; + } + $cf_if = array('elseif' => "} else", 'elsif' => "} else", 'if' => ""); + $kw = $cf_if[$kw]; + if (!$kw) + $st->in[] = array('if'); + return $kw . "if ($e) {\n"; + } + function compile_code_fragment_elsif($st, $kw, $t) + { + return $this->compile_code_fragment_if($st, $kw, $t); + } + function compile_code_fragment_elseif($st, $kw, $t) + { + return $this->compile_code_fragment_if($st, $kw, $t); + } + + // END [block] + function compile_code_fragment_end($st, $kw, $t) + { + if (!count($st->in)) + { + $this->error("END $t without BEGIN, IF or SET"); + return NULL; + } + $in = array_pop($st->in); + $w = $in[0]; + if ($this->strict_end && + ($t && ($w != 'begin' || !$in[1] || $in[1] != $t) || + !$t && $w == 'begin' && $in[1])) + { + $st->in[] = $in; + $this->error(strtoupper($kw)." $t after ".strtoupper($w)." ".$in[1]); + return NULL; + } + if ($w == 'set') + { + $st->in_set--; + return $this->varref($in[1]) . " = \$t;\n\$t = array_pop(\$stack);\n"; + } + elseif ($w == 'function') + { + $st->in_set--; + return "EOF);\n"; + } + elseif ($w == 'begin' || $w == 'for') + { + if ($w == 'begin') + array_pop($st->blocks); + $v = $this->varref($in[2]); + $v_i = $this->varref($in[2].'#'); + return "} +array_pop(\$stack); +$v_i = array_pop(\$stack); +$v = array_pop(\$stack); +"; + } + return "}\n"; + } + + // SET varref ... END + // FUNCTION varref ... END + // SET varref = expression + function compile_code_fragment_set($st, $kw, $t) + { + if (!preg_match('/^((?:\w+\.)*\w+)(\s*=\s*(.*))?/is', $t, $m)) + return NULL; + if ($m[3]) + { + if ($kw != 'set') + { + $this->error("Only SET is allowed for inline expressions, but '".strtoupper($kw)."' given (expression = $m[3])"); + return NULL; + } + $e = $this->compile_expression($m[3]); + if ($e === NULL) + { + $this->error("Invalid expression in $kw: ($m[3])"); + return NULL; + } + return $this->varref($m[1]) . ' = ' . $e . ";\n"; + } + $st->in[] = array($kw, $m[1]); + $st->in_set++; + /* FIXME не работает в PHP < 5.3 + да и вообще-то не очень хорош механизм функций, + ибо не кэшируется */ + if ($kw == 'function') + return $this->varref($m[1]) . " = create_function(<<<'EOF'\n"; + return "\$stack[] = \$t;\n\$t = '';\n"; + } + function compile_code_fragment_function($st, $kw, $t) + { + return $this->compile_code_fragment_set($st, $kw, $t); + } + + // INCLUDE template.tpl + function compile_code_fragment_include($st, $kw, $t) + { + $t = addcslashes($t, '\\\''); + return "\$t.=\$this->parse('$t');\n"; + } + + static function array1($a) + { + if (is_null($a)) + return array(); + if (is_array($a) && !self::is_assoc($a)) + return $a; + return array($a); + } + + // FOR[EACH] varref = array + // или + // FOR[EACH] varref (тогда записывается в себя) + function compile_code_fragment_for($st, $kw, $t, $in = false) + { + if (preg_match('/^((?:\w+\.)*\w+)(\s*=\s*(.*))?/s', $t, $m)) + { + if (!$in) + $st->in[] = array('for', $t, $m[1]); + $v = $this->varref($m[1]); + $v_i = $this->varref($m[1].'#'); + if (substr($v_i,-1) == substr($v,-1)) + { + $iset = "$v_i = \$stack[count(\$stack)-1]++;\n"; + } + else + { + // небольшой хак для $1 =~ \.\d+$ + $iset = ''; + } + $t = $m[3] ? $this->compile_expression($m[3]) : $v; + return +"\$stack[] = $v; +\$stack[] = $v_i; +\$stack[] = 0; +foreach (self::array1($t) as \$item) { +$v = \$item; +$iset"; + } + return NULL; + } + + function compile_code_fragment_foreach($st, $kw, $t) + { + return $this->compile_code_fragment_for($st, $kw, $t); + } + + // BEGIN block [AT e] [BY e] [TO e] + // тоже legacy, но пока оставлю... + function compile_code_fragment_begin($st, $kw, $t) + { + if (preg_match('/^([a-z_][a-z0-9_]*)(?:\s+AT\s+(.+))?(?:\s+BY\s+(.+))?(?:\s+TO\s+(.+))?/is', $t, $m)) + { + $st->blocks[] = $m[1]; + $t = implode('.', $st->blocks); + $st->in[] = array('begin', $m[1], $t); + $e = $t; + if ($m[2]) + { + $e = "array_slice($e, $m[2]"; + if ($m[4]) + $e .= ", $m[4]"; + $e .= ")"; + } + if ($m[3]) + { + $e = "self::exec_subarray_divmod($e, $m[3])"; + } + if ($e != $t) + { + $e = "$t = $e"; + } + return $this->compile_code_fragment_for($st, 'for', $e, 1); + } + return NULL; + } + + // компиляция фрагмента кода . это может быть: + // 1) [ELSE] IF выражение + // 2) BEGIN/FOR/FOREACH имя блока + // 3) END [имя блока] + // 4) SET переменная + // 5) SET переменная = выражение + // 6) INCLUDE имя_файла_шаблона + // 7) выражение + function compile_code_fragment($st, $e) + { + $e = ltrim($e, " \t\r"); + $e = rtrim($e); + if (substr($e, 0, 1) == '#') + { + // комментарий! + return ''; + } + if (preg_match('/^(?:(ELS)(?:E\s*)?)?IF!\s+(.*)$/s', $e, $m)) + { + $e = $m[1].'IF NOT '.$m[2]; + // обратная совместимость... нафига она нужна?... + // но пока пусть останется... + $this->error("Legacy IF! used, consider changing it to IF NOT"); + } + list($kw, $t) = preg_split('/\s+/', $e, 2); + $kw = strtolower($kw); + if (!preg_match('/\W/s', $kw) && + method_exists($this, $sub = "compile_code_fragment_$kw") && + !is_null($r = $this->$sub($st, $kw, $t))) + return $r; + else if (!is_null($t = $this->compile_expression($e))) + return "\$t.=$t;\n"; + return NULL; + } + + // компиляция подстановки переменной {...} это просто выражение + function compile_substitution($st, $e) + { + $e = $this->compile_expression($e); + if ($e !== NULL) + return "\$t.=$e;\n"; + return NULL; + } + + // компиляция выражения. это может быть: + // 1) "строковой литерал" + // 2) 123.123 или 0123 или 0x123 + // 3) переменная + // 4) функция(выражение,выражение,...,выражение) + // 5) функция выражение + // 6) для legacy mode: переменная/имя_функции + function compile_expression($e, $after = NULL) + { + if ($after && (!is_array($after) || !count($after))) + $after = NULL; + $e = ltrim($e, " \t\r"); + if ($after) + $after[0] = ''; + else + $e = rtrim($e); + // строковой или числовой литерал + if (preg_match('/^((\")(?:[^\"\\\\]+|\\\\.)*\"|\'(?:[^\'\\\\]+|\\\\.)*\'|-?0\d+|-?[0-9]\d*(\.\d+)?|-?0x\d+)\s*(.*)$/is', $e, $m)) + { + if ($m[4]) + { + if (!$after) + return NULL; + $after[0] = $m[4]; + } + $e = $m[1]; + if ($m[2]) + $e = str_replace('$', '\\$', $e); + return $e; + } + // функция нескольких аргументов или вызов замыкания + else if (preg_match('/^([a-z_][a-z0-9_]*((?:\.[a-z0-9_]+)*))\s*\((.*)$/is', $e, $m)) + { + $f = strtolower($m[1]); + $ct_callable = array($this, "function_$f"); + if ($this->compiletime_functions[$f]) + $ct_callable = $this->compiletime_functions[$f]; + if ($m[2] || !is_callable($ct_callable)) + $varref = $this->varref($m[1]); + $a = $m[3]; + $args = array(); + while (!is_null($e = $this->compile_expression($a, array(&$a)))) + { + $args[] = $e; + if (preg_match('/^\s*\)/s', $a)) + break; + else if ($a == ($b = preg_replace('/^\s*,/s', '', $a))) + { + $this->error("Unexpected token: '$a' in $f($m[2] parameter list"); + return NULL; + } + $a = $b; + } + if ($a == ($b = preg_replace('/^\s*\)\s*/', '', $a))) + { + $this->error("Unexpected token: '$a' in the end of $f($m[2] parameter list"); + return NULL; + } + $a = $b; + if ($a) + { + if (!$after) + return NULL; + $after[0] = $a; + } + if ($varref) + return "\$this->exec_call('$f', $varref, array(".implode(",", $args)."))"; + return call_user_func_array($ct_callable, $args); + } + // функция одного аргумента + else if (preg_match('/^([a-z_][a-z0-9_]*)\s+(?=\S)(.*)$/is', $e, $m)) + { + $f = strtolower($m[1]); + if (!method_exists($this, "function_$f")) + { + $this->error("Unknown function: '$f' in '$e'"); + return NULL; + } + $a = $m[2]; + $arg = $this->compile_expression($a, array(&$a)); + if ($arg === NULL) + { + $this->error("Invalid expression: ($e)"); + return NULL; + } + $a = ltrim($a); + if ($a) + { + if (!$after) + return NULL; + $after[0] = $a; + } + $f = "function_$f"; + return $this->$f($arg); + } + // переменная плюс legacy-mode переменная/функция + else if (preg_match('/^((?:[a-z0-9_]+\.)*(?:[a-z0-9_]+\#?))(?:\/([a-z]+))?\s*(.*)$/is', $e, $m)) + { + if ($m[3]) + { + if (!$after) + return NULL; + $after[0] = $m[3]; + } + $e = $this->varref($m[1]); + if ($m[2]) + { + $f = strtolower($m[2]); + if (!method_exists($this, "function_$f")) + { + $this->error("Unknown function: '$f' called in legacy mode ($m[0])"); + return NULL; + } + $f = "function_$f"; + $e = $this->$f($e); + } + return $e; + } + return NULL; + } + + // генерация ссылки на переменную + function varref($e) + { + if (!$e) + return ""; + $e = explode('.', $e); + $t = '$this->tpldata'; + foreach ($e as $el) + { + if (preg_match('/^\d+$/', $el)) + { + $t .= "[$el]"; + } + else + { + $el = addcslashes($el, '\\\''); + $t .= "['$el']"; + } + } + return $t; + } + + // операция над аргументами + static function fmop($op, $args) + { + return "((" . join(") $op (", $args) . "))"; + } + + static function is_assoc($a) + { + foreach (array_keys($a) as $k) + if (!is_int($k)) + return true; + return false; + } + + // вспомогательная функция - вызов функции с раскрытием аргументов + static function call_array_func() + { + $args = func_get_args(); + $cb = array_shift($args); + $aa = array(); + foreach ($args as $a) + { + if (is_array($a) && !self::is_assoc($a)) + foreach ($a as $v) + $aa[] = $v; + else + $aa[] = $a; + } + return call_user_func_array($cb, $args); + } + + static function array_count($a) + { + if (is_array($a)) + return count($a); + return 0; + } + + // вызов функции с аргументами и раскрытием массивов + static function fearr($f, $args) + { + $e = "self::call_array_func($f"; + foreach ($args as $a) + $e .= ", $a"; + $e .= ")"; + return $e; + } + + // перлоподобный OR-оператор, который возвращает первое истинное значение + static function perlish_or() + { + $a = func_get_args(); + foreach ($a as $v) + if ($v) + return $v; + return false; + } + + // вызов своей функции + function exec_call($f, $sub, $args) + { + if (is_callable($sub)) + return call_user_func_array($sub, $args); + $this->error("Unknown function: '$f'"); + return NULL; + } + + /* функции */ + + /* "или", "и", +, -, *, /, конкатенация */ + function function_or() { $a = func_get_args(); return "self::perlish_or(".join(",", $a).")"; } + function function_and() { $a = func_get_args(); return $this->fmop('&&', $a); } + function function_add() { $a = func_get_args(); return $this->fmop('+', $a); } + function function_sub() { $a = func_get_args(); return $this->fmop('-', $a); } + function function_mul() { $a = func_get_args(); return $this->fmop('*', $a); } + function function_div() { $a = func_get_args(); return $this->fmop('/', $a); } + function function_mod($a,$b) { return "(($a) % ($b))"; } + function function_concat() { $a = func_get_args(); return $this->fmop('.', $a); } + + /* логарифм, количество элементов, "не", "чётное?", "нечётное?", приведение к целому */ + function function_log($e) { return "log($e)"; } + function function_count($e) { return "self::array_count($e)"; } + function function_not($e) { return "!($e)"; } + function function_even($e) { return "!(($e) & 1)"; } + function function_odd($e) { return "(($e) & 1)"; } + function function_int($e) { return "intval($e)"; } + function function_i($e) { return "intval($e)"; } + + /* сравнения: == != > < >= <= */ + function function_eq($a,$b) { return "(($a) == ($b))"; } + function function_ne($a,$b) { return "(($a) != ($b))"; } + function function_gt($a,$b) { return "(($a) > ($b))"; } + function function_lt($a,$b) { return "(($a) < ($b))"; } + function function_ge($a,$b) { return "(($a) >= ($b))"; } + function function_le($a,$b) { return "(($a) <= ($b))"; } + function function_yesno($a,$b,$c) { return "(($a) ? ($b) : ($c))"; } + + /* нижний регистр */ + function function_lc($e) { return "mb_strtolower($e)"; } + function function_lower($e) { return "mb_strtolower($e)"; } + function function_lowercase($e) { return "mb_strtolower($e)"; } + + /* верхний регистр */ + function function_uc($e) { return "mb_strtoupper($e)"; } + function function_upper($e) { return "mb_strtoupper($e)"; } + function function_uppercase($e) { return "mb_strtoupper($e)"; } + function function_strlimit($s,$l){ return "self::strlimit($s,$l)"; } + + /* экранирование символов, специльных для регулярок */ + function function_requote($e) { return "preg_quote($e)"; } + function function_re_quote($e) { return "preg_quote($e)"; } + function function_preg_quote($e) { return "preg_quote($e)"; } + + /* замены - по регулярке и по подстроке */ + function function_replace($re, $sub, $v) + { + return "preg_replace('#'.str_replace('#','\\\\#',$re).'#s', $sub, $v)"; + } + function function_str_replace($s, $sub, $v) + { + return "str_replace($s, $sub, $v)"; + } + + /* длина строки */ + function function_strlen($s) { return "mb_strlen($s)"; } + + /* подстрока */ + function function_substr($s, $start, $length = NULL) + { + return "mb_substr($s, $start" . ($length !== NULL ? ", $length" : "") . ")"; + } + function function_substring($s, $start, $length = NULL) + { + return "mb_substr($s, $start" . ($length !== NULL ? ", $length" : "") . ")"; + } + + /* разбиение строки по регулярному выражению */ + function function_split($re, $v, $limit = -1) + { + return "preg_split('#'.str_replace('#','\\\\#',$re).'#s', $v, $limit)"; + } + + /* экранирование кавычек */ + function function_quote($e) { return "str_replace(\"\\n\",\"\\\\n\",addslashes($e))"; } + function function_addslashes($e) { return "str_replace(\"\\n\",\"\\\\n\",addslashes($e))"; } + function function_q($e) { return "str_replace(\"\\n\",\"\\\\n\",addslashes($e))"; } + + /* преобразование символов <>&'" в HTML-сущности < > & ' " */ + function function_htmlspecialchars($e) { return "htmlspecialchars($e,ENT_QUOTES)"; } + function function_html($e) { return "htmlspecialchars($e,ENT_QUOTES)"; } + function function_s($e) { return "htmlspecialchars($e,ENT_QUOTES)"; } + + function function_nl2br($s) { return "nl2br($s)"; } + + /* экранирование в стиле URI */ + function function_uriquote($e) { return "urlencode($e)"; } + function function_uri_escape($e) { return "urlencode($e)"; } + function function_urlencode($e) { return "urlencode($e)"; } + + /* удаление всех, заданных или "небезопасных" HTML-тегов */ + function function_strip($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; } + function function_strip_tags($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; } + function function_t($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; } + function function_strip_unsafe($e) { return "strip_tags($e, self::\$safe_tags)"; } + function function_h($e) { return "strip_tags($e, self::\$safe_tags)"; } + + /* объединение всех скаляров и всех элементов аргументов-массивов */ + function function_join() { $a = func_get_args(); return self::fearr("'join'", $a); } + function function_implode() { $a = func_get_args(); return self::fearr("'join'", $a); } + + /* подставляет на места $1, $2 и т.п. в строке аргументы */ + function function_subst() { $a = func_get_args(); return self::fearr("'VMX_Template::exec_subst'", $a); } + + /* sprintf */ + function function_sprintf() { $a = func_get_args(); return self::fearr("'sprintf'", $a); } + + /* JSON-кодирование */ + function function_json($v) { return "json_encode($v)"; } + + /* создание хеша */ + function function_hash() + { + $s = "array("; + $i = 0; + $d = ''; + foreach (func_get_args() as $v) + { + $s .= $d; + $s .= $v; + $i++; + if ($i & 1) + $d = '=>'; + else + $d = ','; + } + $s .= ")"; + return $s; + } + + /* ключи хеша или массива */ + function function_keys($a) { return "array_keys($a)"; } + function function_hash_keys($a) { return "array_keys($a)"; } + function function_array_keys($a) { return "array_keys($a)"; } + + // создание массива + function function_array() + { + $a = func_get_args(); + return "array(" . join(",", $a) . ")"; + } + + // подмассив по номерам элементов + function function_subarray() { $a = func_get_args(); return "array_slice(" . join(",", $a) . ")"; } + function function_array_slice() { $a = func_get_args(); return "array_slice(" . join(",", $a) . ")"; } + + // подмассив по кратности номеров элементов + function function_subarray_divmod() { $a = func_get_args(); return "self::exec_subarray_divmod(" . join(",", $a) . ")"; } + + // объединение массивов + function function_array_merge() { $a = func_get_args(); return "array_merge(" . join(",", $a) . ")"; } + + // получить элемент хеша/массива по неконстантному ключу (например get(iteration.array, rand(5))) + // по-моему, это лучше, чем Template Toolkit'овский ад - hash.key.${another.hash.key}.зюка.хрюка и т.п. + function function_get($a, $k) { return $a."[$k]"; } + function function_hget($a, $k) { return $a."[$k]"; } + function function_aget($a, $k) { return $a."[$k]"; } + + // shift, unshift, pop, push + function function_shift($a) { return "array_shift($a)"; } + function function_pop($a) { return "array_pop($a)"; } + function function_unshift($a, $v) { return "array_unshift($a, $v)"; } + function function_push($a, $v) { return "array_push($a, $v)"; } + + // игнорирование результата (а-ля js) + function function_void($a) { return "self::void($a)"; } + function void($a) { return ''; } + + /* map() */ + function function_map($f) + { + if (!method_exists($this, "function_$f")) + { + $this->error("Unknown function specified for map(): $f"); + return NULL; + } + $f = "function_$f"; + $f = $this->$f('$arg'); + $args = func_get_args(); + array_shift($args); + array_unshift($args, "create_function('$arg',$f)"); + return self::fearr("array_map", $args); + } + + /* дамп переменной */ + function function_var_dump($var) + { + return "self::exec_dump($var)"; + } + function function_dump($var) + { + return "self::exec_dump($var)"; + } + + /* включение другого файла */ + function function_include($file) + { + return "\$this->parse($file)"; + } + function function_parse($file) + { + return "\$this->parse($file)"; + } + + // дамп переменной + static function exec_dump($var) + { + ob_start(); + var_dump($var); + $var = ob_get_contents(); + ob_end_clean(); + return $var; + } + + // подмассив по кратности номеров элементов + // exec_subarray_divmod([], 2) + // exec_subarray_divmod([], 2, 1) + static function exec_subarray_divmod($array, $div, $mod) + { + if (!$div || !is_array($array)) + return $array; + if (!$mod) + $mod = 0; + $i = 0; + $r = array(); + foreach ($array as $k => $v) + if (($i % $div) == $mod) + $r[$k] = $v; + return $r; + } + + // strftime + function function_strftime($fmt, $date, $time = '') + { + $e = $time ? "($date).' '.($time)" : $date; + return "strftime($fmt, self::timestamp($e))"; + } + + // выполняет подстановку function_subst + static function exec_subst($str) + { + $args = func_get_args(); + $str = preg_replace_callback('/(? $p) + $p = $pt; + if ($p) + $str = mb_substr($str, 0, $p); + return $str . '...'; + } + + // ограниченная распознавалка дат + static function timestamp($ts = 0, $format = 0) + { + if (!self::$Mon) + { + self::$Mon = split(' ', 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'); + self::$mon = array_reverse(split(' ', 'jan feb mar apr may jun jul aug sep oct nov dec')); + self::$Wday = split(' ', 'Sun Mon Tue Wed Thu Fri Sat'); + } + if (!strcmp(intval($ts), $ts)) + { + // TS_UNIX or Epoch + if (!$ts) + $ts = time; + } + elseif (preg_match('/^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$/s', $ts, $m)) + { + // TS_DB, TS_DB_DATE, TS_MW, TS_EXIF, TS_ISO_8601 + $ts = mktime(0+$m[4], 0+$m[5], 0+$m[6], $m[2], $m[3], $m[1]); + } + elseif (preg_match('/^\s*(\d\d?)-(...)-(\d\d(?:\d\d)?)\s*(\d\d)\.(\d\d)\.(\d\d)/s', $ts, $m)) + { + // TS_ORACLE + $ts = mktime($m[4], $m[5], $m[6], $mon[strtolower($m[2])]+1, intval($m[1]), $m[3] < 100 ? $m[3]+1900 : $m[3]); + } + elseif (preg_match('/^\s*..., (\d\d?) (...) (\d{4,}) (\d\d):(\d\d):(\d\d)\s*([\+\- ]\d\d)\s*$/s', $ts, $m)) + { + // TS_RFC822 + $ts = mktime($m[4], $m[5], $m[6], $mon[strtolower($m[2])]+1, intval($m[1]), $m[3]); + } + else + { + // Bogus value, return NULL + return NULL; + } + + if (!$format) + { + // TS_UNIX + return $ts; + } + elseif ($format == TS_MW) + { + return strftime("%Y%m%d%H%M%S", $ts); + } + elseif ($format == TS_DB) + { + return strftime("%Y-%m-%d %H:%M:%S", $ts); + } + elseif ($format == TS_DB_DATE) + { + return strftime("%Y-%m-%d", $ts); + } + elseif ($format == TS_ISO_8601) + { + return strftime("%Y-%m-%dT%H:%M:%SZ", $ts); + } + elseif ($format == TS_EXIF) + { + return strftime("%Y:%m:%d %H:%M:%S", $ts); + } + elseif ($format == TS_RFC822) + { + $l = localtime($ts); + return strftime($Wday[$l[6]].", %d ".$Mon[$l[4]]." %Y %H:%M:%S %z", $ts); + } + elseif ($format == TS_ORACLE) + { + $l = localtime($ts); + return strftime("%d-".$Mon[$l[4]]."-%Y %H.%M.%S %p", $ts); + } + return $ts; + } +} diff --git a/lib/templating.php b/lib/templating.php new file mode 100644 index 0000000..0cf5c1f --- /dev/null +++ b/lib/templating.php @@ -0,0 +1,27 @@ + $local_path . $template_path . $tpl, + 'cache_dir' => $local_path . $cache_path, + 'print_error' => 1, +)); +$template->vars(array( + 'DOMAIN' => "http://$Domain", +)); diff --git a/olap.php b/olap.php new file mode 100644 index 0000000..0902147 --- /dev/null +++ b/olap.php @@ -0,0 +1,288 @@ + array('name' => 'Год', 'time' => true), + 'month' => array('name' => 'Месяц', 'time' => true), + 'day' => array('name' => 'Дата', 'time' => true), + 'weekday' => array('name' => 'День недели', 'time' => true), + 'week' => array('name' => '№ недели', 'time' => true), + ); + static $aggregates = array( + 'distinct' => array('name' => 'Значения', 'cell_only' => true), + 'count' => array('name' => 'Количество'), + 'sum' => array('name' => 'Сумма'), + 'avg' => array('name' => 'Среднее'), + 'min' => array('name' => 'Минимум'), + 'max' => array('name' => 'Максимум'), + ); + static $specf = array('v_field', 'v_func', 'h_field', 'h_func', 'cell_field', 'cell_aggr', 'cell_func', 'v_sort_dir', 'v_sort_field', 'v_sort_aggr', 'v_sort_func', 'h_sort_dir', 'h_sort_field', 'h_sort_aggr', 'h_sort_func'); + + static function execute($request) + { + global $template; + set_time_limit(300); + $time_start = microtime(true); + $template->compiletime_functions['fformat'] = "OLAP::tpl_field_format"; + self::$current_srcid = $request['datasource']; + self::$current_src = self::$sources[self::$current_srcid]; + $template->vars('sources', array_values(self::$sources)); + if (!self::$current_src) + { + $template->vars('select', true); + print $template->parse('admin_olap.tpl'); + exit; + } + foreach (self::$current_src['fielddescs'] as $k => &$v) + { + $v['id'] = $k; + if ($v['options']) + foreach ($v['options'] as &$o) + $v['options_hash'][$o['id']] = $o['name']; + } + foreach (self::$functions as $k => &$v) + $v['id'] = $k; + foreach (self::$aggregates as $k => &$v) + $v['id'] = $k; + unset($v); + + $wh = array(); + $where = array(); + foreach ($request as $k => $t) + { + if (substr($k, 0, 5) == 'type-') + { + $i = substr($k, 5); + $f = $request["field-$i"]; + $v = $request["value-$i"]; + $fd = self::$current_src['fielddescs'][$f]; + if (!strlen($v)) + continue; + $wh["type-$i"] = $t; + $wh["field-$i"] = $f; + $wh["value-$i"] = $v; + if ($fd['is_time']) + $v = Template::timestamp($v, self::$current_src['fielddefs'][$f]['format']); + $dn = $fd['dbname']; + if (!$dn) + $dn = $f; + if ($t == 'eq') + $where[$dn] = $v; + elseif ($t == 'ge') + $where[] = "$dn >= ".mysql_ecranize($v); + elseif ($t == 'le') + $where[] = "$dn <= ".mysql_ecranize($v); + } + } + + $template->vars(array( + 'wh' => $wh, + 'srcid' => self::$current_srcid, + 'src' => self::$current_src, + 'fielddescs' => array_values(self::$current_src['fielddescs']), + 'functions' => array_values(self::$functions), + 'aggregates' => array_values(self::$aggregates), + )); + + if (!$request['build']) + { + $vars = array(); + foreach (self::$specf as $f) + $vars[$f] = $$f; + $template->vars($vars); + print $template->parse('admin_olap.tpl'); + exit; + } + + $result = mysql_select(self::$current_src['tables'], self::$current_src['fields'], array_merge(self::$current_src['where'], $where), self::$current_src['options'], MS_RESULT); + + foreach(self::$specf as $x) + $$x = $request[$x]; + + $group = array(); + $hkeys = array(); + $v_v = ''; + $v_h = ''; + $fetch = self::$current_src['fetch_row'] ? 'mysql_fetch_row' : 'mysql_fetch_assoc'; + + $code = 'while($r = '.$fetch.'($result)) {'; + if ($v_field !== '') + { + $code .= '$v_v = '; + if ($v_func !== '') + $code .= 'self::transform_'.$v_func.'(&$r, $v_field);'; + else + $code .= '$r[$v_field];'; + } + if ($h_field !== '') + { + $code .= '$v_h = '; + if ($h_func !== '') + $code .= 'self::transform_'.$h_func.'(&$r, $h_field);'; + else + $code .= '$r[$h_field];'; + } + $code .= '$v_c = '; + if ($cell_func !== '') + $code .= 'self::transform_'.$cell_func.'(&$r, $cell_field);'; + else + $code .= '$r[$cell_field];'; + $code .= '$hkeys[$v_h] = 1;'; + $code .= 'self::aggr_update_'.$cell_aggr.'(&$group[$v_v][$v_h], &$v_c);'; + if ($v_field !== '' && $v_sort_aggr !== '' && $v_sort_field !== '') + { + $code .= '$v_vs = '; + if ($v_sort_func !== '') + $code .= 'self::transform_'.$v_sort_func.'(&$r, $v_sort_field);'; + else + $code .= '$r[$v_sort_field];'; + $code .= 'self::aggr_update_'.$v_sort_aggr.'(&$vsort[$v_v], &$v_vs);'; + } + if ($h_field !== '' && $h_sort_aggr !== '' && $h_sort_field !== '') + { + $code .= '$v_hs = '; + if ($h_sort_func !== '') + $code .= 'self::transform_'.$h_sort_func.'(&$r, $h_sort_field);'; + else + $code .= '$r[$h_sort_field];'; + $code .= 'self::aggr_update_'.$h_sort_aggr.'(&$vsort[$v_h], &$v_hs);'; + } + $code .= '}'; + + eval($code); + + if (is_callable("OLAP::aggr_finish_$cell_aggr")) + foreach ($group as $i => &$r) + foreach ($r as $j => &$c) + call_user_func_array("OLAP::aggr_finish_$cell_aggr", array(&$c)); + + if ($v_field !== '' && $v_sort_aggr !== '' && $v_sort_field !== '' && is_callable("OLAP::aggr_finish_$v_sort_aggr")) + foreach ($vsort as $v => &$c) + call_user_func_array("OLAP::aggr_finish_$v_sort_aggr", array(&$c)); + + if ($h_field !== '' && $h_sort_aggr !== '' && $h_sort_field !== '' && is_callable("OLAP::aggr_finish_$h_sort_aggr")) + foreach ($hsort as $v => &$c) + call_user_func_array("OLAP::aggr_finish_$h_sort_aggr", array(&$c)); + + $hkeys = array_keys($hkeys); + self::do_sort($hkeys, $hsort, $h_sort_dir); + + $vkeys = array_keys($group); + self::do_sort($vkeys, $vsort, $v_sort_dir); + + $vars = array( + 'build' => 1, + 'hkeys' => $hkeys, + 'vkeys' => $vkeys, + 'hsort' => $hsort, + 'vsort' => $vsort, + 'data' => $group, + 'rpt_link' => "?".http_build_query($_GET+$_POST), + 'csv_link' => "?csv=1&".http_build_query($_GET+$_POST), + 'memory' => file_size_string(memory_get_usage()), + 'time_elapsed' => sprintf("%.3f", microtime(true)-$time_start), + ); + + foreach (self::$specf as $f) + $vars[$f] = $$f; + + $template->vars($vars); + if ($request['csv']) + { + self::$is_html_format = false; + header("Content-Type: text/csv; charset=windows-1251"); + header("Content-Disposition: inline; filename=OLAP-".date('YmdHis').".csv"); + $out = $template->parse('admin_olap_csv.tpl'); + print iconv("utf-8", "windows-1251", $out); + } + else + print $template->parse('admin_olap.tpl'); + } + + static function do_sort(&$values, &$sortvalues, &$sort_dir) + { + $fn = 'sort'; + $sort_dir = strtolower($sort_dir); + if ($sort_dir == 'desc') + $fn = 'rsort'; + if ($sortvalues) + { + $ufn = $sort_dir == 'desc' ? "OLAP::custom_reverse_cmp" : "OLAP::custom_sort_cmp"; + self::$sort = &$sortvalues; + usort($values, $ufn); + } + else + $fn($values); + } + + static $sort; + static function custom_sort_cmp($a, $b) + { + return self::$sort[$a] < self::$sort[$b] ? -1 : (self::$sort[$a] > self::$sort[$b] ? 1 : 0); + } + static function custom_reverse_cmp($a, $b) + { + return self::$sort[$a] > self::$sort[$b] ? -1 : (self::$sort[$a] < self::$sort[$b] ? 1 : 0); + } + + static $is_html_format = true; + static function field_format($field, $func, $aggr, $value) + { + $o = &self::$current_src['fielddescs'][$field]['options_hash']; + if ($aggr == 'distinct') + { + $r = ''; + foreach($value as $k => $n) + $r .= self::field_format($field, $func, false, $k).': '.$n.(self::$is_html_format ? "
      " : "")."\n"; + return $r; + } + elseif ($o && $o[$value]) + return $o[$value]; + $fn = self::$is_html_format ? 'htmlspecialchars' : 'addslashes'; + return $fn($value); + } + + static function tpl_field_format($field, $func, $aggr, $value) + { + return "OLAP::field_format($field, $func, $aggr, $value)"; + } + + static function o_tsformat($f, &$row, $field) + { + $tf = "_t$field"; + if (!$row[$tf]) + $row[$tf] = Template::timestamp($row[$field]); + return date($f, $row[$tf]); + } + + /* Агрегатные функции и преобразования */ + + static function transform_year(&$row, $field) { return self::o_tsformat('Y', $row, $field); } + static function transform_month(&$row, $field) { return self::o_tsformat('Y-m', $row, $field); } + static function transform_day(&$row, $field) { return self::o_tsformat('Y-m-d', $row, $field); } + static function transform_weekday(&$row, $field) { return self::o_tsformat('N', $row, $field); } + static function transform_week(&$row, $field) { return self::o_tsformat('W', $row, $field); } + + static function aggr_update_count(&$n, &$v) { $n++; } + static function aggr_update_sum(&$sum, &$v) { $sum += $v; } + static function aggr_update_avg(&$avg, &$v) { $avg['n']++; $avg['s'] += $v; } + static function aggr_finish_avg(&$avg) { $avg = $avg['s']/$avg['n']; } + static function aggr_update_min(&$min, &$v) { if ($min > $v) $min = $v; } + static function aggr_update_max(&$max, &$v) { if ($max < $v) $max = $v; } + static function aggr_update_distinct(&$d, &$v) { $d[$v]++; } +} diff --git a/olap_config.php b/olap_config.php new file mode 100644 index 0000000..0655aff --- /dev/null +++ b/olap_config.php @@ -0,0 +1,28 @@ + array( + 'id' => 'v2tags', + 'name' => 'VitaphotoV2, статистика по тегам', + 'tables' => array('t' => 'stats_tag_all'), + 'fields' => 't.ts, t.tag', + 'where' => array(), + 'fetch_row' => true, + 'fielddescs' => array( + #'ts' + 0 => array( + 'name' => 'Время', + 'le_ge' => true, + 'is_time' => true, + 'format' => TS_UNIX, + 'dbname' => 'ts', + ), + #'tag' + 1 => array( + 'name' => 'Тег', + 'dbname' => 'tag', + 'options' => array_merge(array('id' => 0, 'name' => '*'), mysql_select('tags', 'id, name', array('type' => 0))), + ), + ), + ), +); diff --git a/templates/default/admin_footer.tpl b/templates/default/admin_footer.tpl new file mode 100644 index 0000000..d7eefe0 --- /dev/null +++ b/templates/default/admin_footer.tpl @@ -0,0 +1,10 @@ + + +

      Powered by VitaliF, 2010

      + + diff --git a/templates/default/admin_header.tpl b/templates/default/admin_header.tpl new file mode 100644 index 0000000..4b293ef --- /dev/null +++ b/templates/default/admin_header.tpl @@ -0,0 +1,36 @@ + + +{title/s} + +{METAS} + + + + + + + + + +
      +

      {detailtitle}{title}

      diff --git a/templates/default/admin_olap.tpl b/templates/default/admin_olap.tpl new file mode 100644 index 0000000..bc98415 --- /dev/null +++ b/templates/default/admin_olap.tpl @@ -0,0 +1,166 @@ +Статистика + + +

      Добро пожаловать в простую OLAPообразную статистику. Выберите источник данных:

      +
      + + + + +

      Выбранный источник данных: {s src.name}. Выбрать другой источник данных.

      +

      Настройте желаемый отчёт:

      +
      + + +
    + + +
    + По горизонтали:
    + +
    + Сортировка:
    + + + + +
    + По вертикали:
    + +
    + Сортировка:
    + + + + +
    + В таблице:
    + + + +
    +Условия выборки данных: + + + + + + +
    {s f.name} + = + + + + + + + + или ≥ + и ≤ + + (YYYY-MM-DD HH:MM:SS) + (0-9) +
    + + +

    Отчёт

    +

    Ссылка на данный отчёт | В формате CSV

    + +

    Нет данных для показа.

    + + + + + + + + + +
    {fformat(h_field,h_func,'',k)}
    {fformat(v_field,v_func,'',v)}{fformat(cell_field,cell_func,cell_aggr,get(get(data,v),k))}{get(vsort,v)}
    {get(hsort,k)}
    +

    Отчёт занял {time_elapsed} сек. Использовано {memory} памяти для работы.

    + + + + diff --git a/templates/default/admin_olap_csv.tpl b/templates/default/admin_olap_csv.tpl new file mode 100644 index 0000000..cee275f --- /dev/null +++ b/templates/default/admin_olap_csv.tpl @@ -0,0 +1,4 @@ +;"{fformat(h_field,h_func,'',k)}" + +"{fformat(v_field,v_func,'',v)}";"{fformat(cell_field,cell_func,cell_aggr,get(get(data,v),k))}" +