From bbc1bf5c219b9f9f18e283c3eefb55ba73be0664 Mon Sep 17 00:00:00 2001 From: akulikov Date: Tue, 2 Dec 2008 09:03:33 +0000 Subject: [PATCH] now right git-svn-id: svn+ssh://code.netmonsters.ru/svn/majestic/trunk@4 4cb57b5f-5bbd-dd11-951b-001d605cbbc5 --- Action.class.php | 101 +++++++++++++++++++++++++ Cache.class.php | 58 +++++++++++++++ DBConnector.class.php | 40 ++++++++++ Decorator.class.php | 55 ++++++++++++++ Env.class.php | 64 ++++++++++++++++ Load.class.php | 35 +++++++++ MJException.class.php | 57 +++++++++++++++ Model.class.php | 186 +++++++++++++++++++++++++++++++++++++++++++++++ PageController.class.php | 27 +++++++ Router.class.php | 106 +++++++++++++++++++++++++++ Sublimer.class.php | 93 ++++++++++++++++++++++++ User.class.php | 90 +++++++++++++++++++++++ 12 files changed, 912 insertions(+) create mode 100644 Action.class.php create mode 100644 Cache.class.php create mode 100644 DBConnector.class.php create mode 100644 Decorator.class.php create mode 100644 Env.class.php create mode 100644 Load.class.php create mode 100644 MJException.class.php create mode 100644 Model.class.php create mode 100644 PageController.class.php create mode 100644 Router.class.php create mode 100644 Sublimer.class.php create mode 100644 User.class.php diff --git a/Action.class.php b/Action.class.php new file mode 100644 index 0000000..69e2465 --- /dev/null +++ b/Action.class.php @@ -0,0 +1,101 @@ +class = get_class($this); + $this->template = substr($this->class, 0, -strlen(ACTION_POSTFIX)); + + if (CACHE_ENABLE && ($cache_name = $this->getCacheKey()) !== false) { + $cache = new Cache($this->class.'_'.$cache_name, $this->getCacheTime()); + if ($cache->isCached()) { + $this->restore($cache->load()); + } else { + $this->init(); + $cache->save($this->store()); + } + } else { + $this->init(); + } + } + + /** + * Выдает результат действия. + * + * @return string + */ + public function display() + { + $this->templater = Load::templater(ACTION_TPL_PATH.'/'.$this->template_dir); + $this->prepare(); + return $this->templater->fetch($this->template.'.tpl'); + } + + /** + * Инициализация данных действия. + * Тут должна быть ВСЯ работа с установкой данных, + * дабы потом они нормально закешировались. + * Этот метод НЕ исполняется при срабатывании кеша. + * + */ + abstract protected function init(); + + /** + * Подготовка данных для шаблона. + * Этот метод выполняется ВСЕГДА + */ + protected function prepare() {} + + /** + * Возвращает имя файла для кеширования + * Переопределяется в доченрих классах. + * + * @return false/string - имя файла кеша либо false если кеш запрещен + */ + protected function getCacheKey() + { + return false; + } + + /** + * Возвращает время жизни кеша в секундах + * При отрицательном значении кеш вечен. + * Переопределяется в доченрих классах. + * + * @return false/integer - время жизни кеша либо false если кеш запрещен + */ + protected function getCacheTime() + { + return 0; + } + + /** + * Выдает строку для кеширования. + */ + protected function store() + { + return serialize(get_object_vars($this)); + } + + /** + * Разбирает строку кеша, созданную методом store + */ + protected function restore($data) + { + foreach(unserialize($data) as $key => $val) { + $this->$key = $val; + } + } +} +?> \ No newline at end of file diff --git a/Cache.class.php b/Cache.class.php new file mode 100644 index 0000000..399926c --- /dev/null +++ b/Cache.class.php @@ -0,0 +1,58 @@ +cache_time = (int) $cache_time; + $this->cache_file = CACHE_PATH.'/'.$cache_name; + } + + /** + * Сохраняет кэш в файл + * + * @return boolean - сохранил или нет + */ + function save($data) + { + if ($this->cache_time != 0) { + return (bool) file_put_contents($this->cache_file, $data); + } + return false; + } + + /** + * Достает кэш из файла + * + * @return string - содержимое кеша + */ + function load() + { + return file_get_contents($this->cache_file); + } + + /** + * Проверяет, закешированы ли данные с таким именем + * + * @return boolean - закеширован или нет + */ + function isCached() + { + if (! file_exists($this->cache_file)) { + return false; + } + if ($this->cache_time > 0 && $this->cache_time + filemtime($this->cache_file) < TIME_NOW) { + return false; + } + return true; + } +} + +?> \ No newline at end of file diff --git a/DBConnector.class.php b/DBConnector.class.php new file mode 100644 index 0000000..bbfb4a4 --- /dev/null +++ b/DBConnector.class.php @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/Decorator.class.php b/Decorator.class.php new file mode 100644 index 0000000..0fa8675 --- /dev/null +++ b/Decorator.class.php @@ -0,0 +1,55 @@ +layout) { + throw new MJException('$layout not set in '.get_class($this)); + } + $this->templater = Load::templater(); + } + + /** + * Основной метод вывода + * + */ + function display(Action $action) + { + $this->action = $action; + $this->templater->assign($this->action_name, $this->action->display(), $this->layout); + + $this->exec(); + + $this->templater->setPath(WRAPPERS_TPL_PATH); + return $this->templater->fetch($this->layout); + } + + /** + * Добавить данные на вывод + * + * @param string $var_name - имя переменной, в которую будет осуществлен вывод + * @param Action $action - действие + * @param Decorator $decorator - декоратор, в котором должно выполнится действие. + */ + function addOutput($var_name, Action $action, Decorator $decorator = null) + { + $this->templater->assign($var_name, $decorator ? $decorator->display($action) : $action->display(), $this->layout); + } + + /** + * Основной метод для дочерних декораторов + * + */ + abstract function exec(); +} +?> \ No newline at end of file diff --git a/Env.class.php b/Env.class.php new file mode 100644 index 0000000..c2a7636 --- /dev/null +++ b/Env.class.php @@ -0,0 +1,64 @@ + \ No newline at end of file diff --git a/Load.class.php b/Load.class.php new file mode 100644 index 0000000..07e584f --- /dev/null +++ b/Load.class.php @@ -0,0 +1,35 @@ +setPath($path); + } + return self::$templater; + } + return self::$templater = new Sublimer($path); + } + + static function router() + { + return self::$router ? self::$router : self::$router = new Router; + } +} +?> \ No newline at end of file diff --git a/MJException.class.php b/MJException.class.php new file mode 100644 index 0000000..6db0916 --- /dev/null +++ b/MJException.class.php @@ -0,0 +1,57 @@ +MJ Error: "; + $return .= str_replace("\n", "
\n", $this->getMessage())."
\n"; + + $trace = $this->getTrace(); + + $file = reset($trace); + + //смещение в трейсе, указаное при вызове эксепшена (2й параметр). Нужно для более инофрмативного вывода + if ($shift = abs($this->getCode())) { + while($shift--) { + $file = next($trace); + } + } + + if ($fp = fopen($file['file'], 'r')) { + $error_line = $file['line']; + $start = $error_line - $this->line_range; + $end = $error_line + $this->line_range; + $i = 1; + $return .= "
";
+            while ($line = fgets($fp, 4096) and $i<=$end) {
+                $line = htmlspecialchars($line);
+                if ($i >= $start && $i <= $end) {
+                    if ($i == $error_line) $return .= '
'.$i.' '.$line.'
'; + else $return .= $i.' '.$line; + } + $i++; + } + $return .= "
"; + fclose($fp); + } + + $return .= ''; + $return .= "\n'; + foreach($trace as $row) { + if (isset($row['file'])) { //throwing exception from __call method will not return file and line + $return .= "\n'; + } + } + return $return . '
Backtrace
".$this->getFile().''.$this->getLine().'
".$row['file'].''.$row['line'].'
'; + } +} +?> diff --git a/Model.class.php b/Model.class.php new file mode 100644 index 0000000..b5da5bf --- /dev/null +++ b/Model.class.php @@ -0,0 +1,186 @@ +handler = DBConnector::getConnect(Env::getParam('db_settings')); + } + + /** + * Выполняет запрос и возвращает сырой результат + * + * @param string $sql + * @return resource + */ + function exec($sql) + { + $time = microtime(true); + $res = mysqli_query($this->handler, $sql); + if (mysqli_errno($this->handler)) { + throw new MJException("Query Error:\n".$sql."\nError:\n".mysqli_error($this->handler), 1); + } + + if (DEBUG_ENABLE) { + DBConnector::$queries[] = $sql.'; ('.round((microtime(true)-$time)*1000, 1).'ms)'; + } + + return $res; + } + + /** + * Выполняет запрос и возвращает объект результата + * + * @param string $sql + * @return object + */ + function query($sql) + { + $res = $this->exec($sql); + + switch (strtolower(substr($sql, 0, 6))) { + case 'select': + return new ModelSelectResult($res); + case 'insert': + return new ModelInsertResult($this->handler); + default: + return new ModelChangeResult($this->handler); + } + } + + /** + * Экранирует строку + * + * @param string $data - строка для экранирования + * @return string + */ + function escape($data) + { + return mysqli_real_escape_string($this->handler, $data); + } + +////////////////////////// + function update($id, $data) + { + $sql = ''; + foreach ($data as $key => $val) { + $sql .= $key."='".$this->escape($val)."', "; + } + return $this->query('UPDATE '.$this->table.' SET '.rtrim($sql, ', ').' WHERE '.$this->primary_key.'='.(int) $id); + } + + function insert($data, $postfix = '') + { + $sql = ''; + foreach ($data as $key => $val) { + $sql .= $key.'="'.$this->escape($val).'", '; + } + return $this->query('INSERT '.$this->table.' SET '.rtrim($sql, ', ').' '.$postfix); + } + + function delete($id) + { + return $this->query('DELETE FROM '.$this->table.' WHERE '.$this->primary_key.'='.(int) $id); + } + + function get($id) + { + return $this->query('SELECT * FROM '.$this->table.' WHERE '.$this->primary_key.'='.(int) $id); + } + + function getList($limit = false, $sort = 'ASC') + { + return $this->query('SELECT * FROM '.$this->table.' ORDER BY '.$this->primary_key.' '.($sort == 'ASC' ? 'ASC' : 'DESC').($limit === false ? ' LIMIT '.(int) $limit : '')); + } +} + +class ModelResult +{ + function __call($name, $args) + { + throw new MJException('Call undeclared method "'.$name.'" in "'.get_class($this).'" class', -1); + } +} + +class ModelSelectResult extends ModelResult +{ + public $result; + + function __construct($res) + { + $this->result = $res; + } + + function fetch() + { + return mysqli_fetch_object($this->result); + } + + function fetchField($field, $default = false) + { + $row = $this->fetch(); + return isset($row->$field) ? $row->$field : $default; + } + + function fetchAll() + { + $array = array(); + while ($row = mysqli_fetch_object($this->result)) { + $array[] = $row; + } + return $array; + } + + function count() + { + return mysqli_num_rows($this->result); + } + + function free() + { + mysqli_free_result($this->result); + } + + function __destruct() { + $this->free(); + } +} + +class ModelChangeResult extends ModelResult +{ + public $affected; + + function __construct($resource) + { + $this->affected = mysql_affected_rows($resource); + } + + function count() + { + return $this->affected; + } +} + +class ModelInsertResult extends ModelResult +{ + public $id; + + function __construct($resource) + { + $this->id = mysqli_insert_id($resource); + } + + function getId() + { + return $this->id; + } +} +?> \ No newline at end of file diff --git a/PageController.class.php b/PageController.class.php new file mode 100644 index 0000000..caa88e6 --- /dev/null +++ b/PageController.class.php @@ -0,0 +1,27 @@ +route = Load::router()->proccess(MJ_PATH); + $decorator = new $this->route->decorator; + return $decorator->display(new $this->route->action); + } catch (MJException $e) { + return $e->terminate(); + } catch (Exception $e) { + $decorator_name = DEFAULT_DECORATOR; + $action_name = DEFAULT_ACTION; + + $decorator = new $decorator_name; + return $decorator->display(new $action_name($e->getMessage())); + } + } +} +?> \ No newline at end of file diff --git a/Router.class.php b/Router.class.php new file mode 100644 index 0000000..c8d6373 --- /dev/null +++ b/Router.class.php @@ -0,0 +1,106 @@ +routes[$name] = new Route($path, $action, $params); + } + + /** + * Установить декоратор для роута (действия), отличный от стандартного + * + * @param string $name - имя роута (действия) + * @param string $decorator - имя декоратора + */ + function setDecorator($name, $decorator) + { + if (isset($this->routes[$name])) { + $this->routes[$name]->decorator = $decorator.DECORATOR_POSTFIX; + } + } + + /** + * Найти роутер соответствующий заданному пути + * + * @param stirng $path - путь + * @return Route - роутер + */ + function proccess($path) + { + $path = explode('/', $path); + + foreach ($this->routes as $name => $route) { + if ($route->match($path)) { + $route->action .= ACTION_POSTFIX; + Env::setParams($route->params); + return $route; + } + } + throw new Exception(E_404); + } +} + +/** + * Роутер + * + */ +final class Route +{ + const URL_VARIABLE = '&'; + + protected $path; + + public $decorator = DEFAULT_DECORATOR; + public $action; + public $params; + + function __construct($path, $action, $params=array()) + { + $this->path = $path; + $this->action = $action; + $this->params = $params; + } + + /** + * Проверяет соответствие роутера и пути + * + * @param string $path - путь для сравнения + * @return boolean - соответствует или нет + */ + function match($path_arr) + { + $parts = explode('/', $this->path); + $cnt = count($parts); + if (end($parts) == self::URL_VARIABLE) { + $cnt--; + } elseif ($cnt != count($path_arr)) { + return false; + } + + for ($i=0; $i<$cnt; $i++) { + if (substr($parts[$i], 0, 1) == self::URL_VARIABLE) { + $this->params[substr($parts[$i], 1)] = $path_arr[$i]; + } elseif ($parts[$i] != $path_arr[$i]) { + return false; + } + } + + return true; + } +} +?> \ No newline at end of file diff --git a/Sublimer.class.php b/Sublimer.class.php new file mode 100644 index 0000000..f79fb01 --- /dev/null +++ b/Sublimer.class.php @@ -0,0 +1,93 @@ +setPath($path); + $this->vars[self::GLOBAL_KEY] = array(); + } + + /** + * Присвоить переменную только + * + * @param string $name - имя переменной + * @param mixed $value - значение переменной + * @param string $template - шаблон, если не указан, то переменная глобальная + */ + public function assign($name, $value, $template = self::GLOBAL_KEY) + { + $this->vars[$template][$name] = $value; + } + + /** + * Очистить стек переменных + * @param boolean $with_local - включая локальные переменные + */ + public function clear($template = self::GLOBAL_KEY) + { + $this->vars[$template] = array(); + } + + /** + * Обработать шаблон + * + * @param string $template - относительный путь до шаблона + * @return string - обработанное содержимое шаблона + */ + public function fetch($template) + { + $this->template = $template; //дабы экстракт не перезатер нам переменную. Это важно! $this то он не перезатрет :) + extract($this->vars[self::GLOBAL_KEY]); + if (isset($this->vars[$this->template])) { + extract($this->vars[$this->template], EXTR_OVERWRITE); + } + ob_start(); + if (!(include $this->path.'/'.$this->template) == 'OK') { + throw new MJException('Template '.$this->path.'/'.$this->template.' not found'); + } + return ob_get_clean(); + } + + /** + * Установать путь до шаблонов + * + * @param string $path - путь до шаблонов + */ + public function setPath($path) + { + $this->path = $path; + } + +//Функции для вызова из шаблонов. + + protected function addHead($str) + { + $this->head_array[] = $str; + } + + protected function getHead() + { + return $this->head_array; + } +} + +?> \ No newline at end of file diff --git a/User.class.php b/User.class.php new file mode 100644 index 0000000..3267274 --- /dev/null +++ b/User.class.php @@ -0,0 +1,90 @@ +password != md5($password)) { + return false; + } + + self::setSession(); + return true; + } + + static function logout() + { + Env::setCookie(session_name(), '', 0); + Env::setCookie('login', '', 0); + Env::setCookie('login_hash', '', 0); + if (session_id()) { + session_destroy(); + } + } + + static function process() + { + if (Env::getCookie(session_name())) { //есть сессия + session_start(); + } elseif (Env::getCookie('login') && Env::getCookie('login_hash')) { + self::remember(); + } + } + + static function setSession() + { + Env::setCookie('login', self::$user->login, TIME_NOW + LOGIN_COOKIE_TTL); + Env::setCookie('login_hash', self::getHash(), TIME_NOW + LOGIN_COOKIE_TTL); + + session_start(); + + $_SESSION['user'] = self::$user; + } + + static function remember() + { + if (! self::$user = self::getByLogin(Env::getCookie('login'))) { + self::logout(); + } + + if (Env::getCookie('login_hash') == self::getHash()) { + self::setSession(); + } else { + self::logout(); + } + } + + static function getHash() + { + return md5(self::$user->id.'hckrz'.self::$user->login.'mst'.self::$user->password.'dai'); + } + + static function getInfo() + { + return Env::Session('user', self::$user); + } + + static function isGuest() + { + return ! (bool) Env::Session('user'); + } + + static function getByLogin($login) + { + return Load::model('UserData')->getByLogin($login); + } +} +?> \ No newline at end of file