diff --git a/README.md b/README.md
index 77b4908..d776b56 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,23 @@
# onenav
-一个简约导航
+使用PHP + SQLite 3开发的简约导航/书签管理器,xiaoz新作,欢迎体验。
+
+![](https://i.bmp.ovh/imgs/2020/12/7a1eee25c16d2d81.png)
+
+![](https://i.bmp.ovh/imgs/2020/12/abba0af566f3c16a.png)
+
+
+
+## 功能特色
+
+* 支持后台管理
+* 支持私有链接
+* 支持链接信息自动识别
+* 支持API
+
+## 安装
+
+1. 需安装PHP环境,并确保支持SQLite3
+2. 下载源码解压到站点根目录
+3. 将`config.simple.php`修改为`config.php`并填写自己的站点信息
+4. 将`db/onenav.simple.db3`重命名为`onenav.db3`
+5. 访问后台:`http://IP/index.php?c=login`
\ No newline at end of file
diff --git a/class/.htaccess b/class/.htaccess
new file mode 100644
index 0000000..baa56e5
--- /dev/null
+++ b/class/.htaccess
@@ -0,0 +1,2 @@
+order allow,deny
+deny from all
\ No newline at end of file
diff --git a/class/Api.php b/class/Api.php
new file mode 100644
index 0000000..4771933
--- /dev/null
+++ b/class/Api.php
@@ -0,0 +1,422 @@
+
+ * blog:xiaoz.me
+ */
+class Api {
+ protected $db;
+ public function __construct($db){
+ $this->db = $db;
+ //返回json类型
+ header('Content-Type:application/json; charset=utf-8');
+ }
+ /**
+ * name:创建分类目录
+ */
+ public function add_category($token,$name,$property = 0,$weight = 0,$description = ''){
+ $this->auth($token);
+ $data = [
+ 'name' => $name,
+ 'add_time' => time(),
+ 'weight' => $weight,
+ 'property' => $property,
+ 'description' => $description
+ ];
+ //插入分类目录
+ $this->db->insert("on_categorys",$data);
+ //返回ID
+ $id = $this->db->id();
+ //如果id为空(NULL),说明插入失败了,姑且认为是name重复导致
+ if( empty($id) ){
+ $this->err_msg(-1000,'Categorie already exist!');
+ }
+ else{
+ //成功并返回json格式
+ $data = [
+ 'code' => 0,
+ 'id' => intval($id)
+ ];
+ exit(json_encode($data));
+ }
+
+ }
+ /**
+ * 修改分类目录
+ *
+ */
+ public function edit_category($token,$id,$name,$property = 0,$weight = 0,$description = ''){
+ $this->auth($token);
+ //如果id为空
+ if( empty($id) ){
+ $this->err_msg(-1003,'The category ID cannot be empty!');
+ }
+ //如果分类名为空
+ elseif( empty($name) ){
+ $this->err_msg(-1004,'The category name cannot be empty!');
+ }
+ //更新数据库
+ else{
+ $data = [
+ 'name' => $name,
+ 'up_time' => time(),
+ 'weight' => $weight,
+ 'property' => $property,
+ 'description' => $description
+ ];
+ $re = $this->db->update('on_categorys',$data,[ 'id' => $id]);
+ //var_dump( $this->db->log() );
+ //获取影响行数
+ $row = $re->rowCount();
+ if($row) {
+ $data = [
+ 'code' => 0,
+ 'msg' => 'successful'
+ ];
+ exit(json_encode($data));
+ }
+ else{
+ $this->err_msg(-1005,'The category name already exists!');
+ }
+ }
+ }
+ /**
+ * 删除分类目录
+ */
+ public function del_category($token,$id) {
+ //验证授权
+ $this->auth($token);
+ //如果id为空
+ if( empty($id) ){
+ $this->err_msg(-1003,'The category ID cannot be empty!');
+ }
+ //如果分类目录下存在数据
+ $count = $this->db->count("on_links", [
+ "fid" => $id
+ ]);
+ //如果分类目录下存在数据,则不允许删除
+ if($count > 0) {
+ $this->err_msg(-1006,'The category is not empty and cannot be deleted!');
+ }
+ else{
+ $data = $this->db->delete('on_categorys',[ 'id' => $id] );
+ //返回影响行数
+ $row = $data->rowCount();
+ if($row) {
+ $data = [
+ 'code' => 0,
+ 'msg' => 'successful'
+ ];
+ exit(json_encode($data));
+ }
+ else{
+ $this->err_msg(-1007,'The category delete failed!');
+ }
+ }
+ }
+
+ /**
+ * name:返回错误(json)
+ *
+ */
+ protected function err_msg($code,$err_msg){
+ $data = [
+ 'code' => $code,
+ 'err_msg' => $err_msg
+ ];
+ //返回json类型
+ header('Content-Type:application/json; charset=utf-8');
+ exit(json_encode($data));
+ }
+ /**
+ * name:验证方法
+ */
+ protected function auth($token){
+ //计算正确的token:用户名 + TOKEN
+ $token_yes = md5(USER.TOKEN);
+ //如果token为空,则验证cookie
+ if(empty($token)) {
+ if( !$this->is_login() ) {
+ $this->err_msg(-1002,'Authorization failure!');
+ }
+ }
+ else if($token != $token_yes){
+ $this->err_msg(-1002,'Authorization failure!');
+ }
+ else{
+ return true;
+ }
+ }
+ /**
+ * name:添加链接
+ */
+ public function add_link($token,$fid,$title,$url,$description = '',$weight = 0,$property = 0){
+ $this->auth($token);
+ $fid = intval($fid);
+ //检测链接是否合法
+ $this->check_link($fid,$title,$url);
+ //合并数据
+ $data = [
+ 'fid' => $fid,
+ 'title' => $title,
+ 'url' => $url,
+ 'description' => $description,
+ 'add_time' => time(),
+ 'weight' => $weight,
+ 'property' => $property
+ ];
+ //插入数据库
+ $re = $this->db->insert('on_links',$data);
+ //返回影响行数
+ $row = $re->rowCount();
+ //如果为真
+ if( $row ){
+ $id = $this->db->id();
+ $data = [
+ 'code' => 0,
+ 'id' => $id
+ ];
+ exit(json_encode($data));
+ }
+ //如果插入失败
+ else{
+ $this->err_msg(-1011,'The URL already exists!');
+ }
+ }
+ /**
+ * name:修改链接
+ */
+ public function edit_link($token,$id,$fid,$title,$url,$description = '',$weight = 0,$property = 0){
+ $this->auth($token);
+ $fid = intval($fid);
+ //检测链接是否合法
+ $this->check_link($fid,$title,$url);
+ //查询ID是否存在
+ $count = $this->db->count('on_links',[ 'id' => $id]);
+ //如果id不存在
+ if( (empty($id)) || ($count == false) ) {
+ $this->err_msg(-1010,'link id not exists!');
+ }
+ //合并数据
+ $data = [
+ 'fid' => $fid,
+ 'title' => $title,
+ 'url' => $url,
+ 'description' => $description,
+ 'up_time' => time(),
+ 'weight' => $weight,
+ 'property' => $property
+ ];
+ //插入数据库
+ $re = $this->db->update('on_links',$data,[ 'id' => $id]);
+ //返回影响行数
+ $row = $re->rowCount();
+ //如果为真
+ if( $row ){
+ $id = $this->db->id();
+ $data = [
+ 'code' => 0,
+ 'msg' => 'successful'
+ ];
+ exit(json_encode($data));
+ }
+ //如果插入失败
+ else{
+ $this->err_msg(-1011,'The URL already exists!');
+ }
+ }
+ /**
+ * 删除链接
+ */
+ public function del_link($token,$id){
+ //验证token是否合法
+ $this->auth($token);
+ //查询ID是否存在
+ $count = $this->db->count('on_links',[ 'id' => $id]);
+ //如果id不存在
+ if( (empty($id)) || ($count == false) ) {
+ $this->err_msg(-1010,'link id not exists!');
+ }
+ else{
+ $re = $this->db->delete('on_links',[ 'id' => $id] );
+ if($re) {
+ $data = [
+ 'code' => 0,
+ 'msg' => 'successful'
+ ];
+ exit(json_encode($data));
+ }
+ else{
+ $this->err_msg(-1010,'link id not exists!');
+ }
+ }
+ }
+ /**
+ * 验证链接合法性
+ */
+ protected function check_link($fid,$title,$url){
+ //如果父及(分类)ID不存在
+ if( empty($fid )) {
+ $this->err_msg(-1007,'The category id(fid) not exist!');
+ }
+ //如果父及ID不存在数据库中
+ //验证分类目录是否存在
+ $count = $this->db->count("on_categorys", [
+ "id" => $fid
+ ]);
+ if ( empty($count) ){
+ $this->err_msg(-1007,'The category not exist!');
+ }
+ //如果链接标题为空
+ if( empty($title) ){
+ $this->err_msg(-1008,'The title cannot be empty!');
+ }
+ //链接不能为空
+ if( empty($url) ){
+ $this->err_msg(-1009,'URL cannot be empty!');
+ }
+ //链接不合法
+ if( !filter_var($url, FILTER_VALIDATE_URL) ) {
+ $this->err_msg(-1010,'URL is not valid!');
+ }
+ return true;
+ }
+ /**
+ * 查询分类目录
+ */
+ public function category_list($page,$limit){
+ $offset = ($page - 1) * $limit;
+ //如果成功登录,则查询所有
+ if( $this->is_login() ){
+ $sql = "SELECT * FROM on_categorys ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
+ }
+ else{
+ $sql = "SELECT * FROM on_categorys WHERE property = 0 ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
+ }
+ //统计总数
+ $count = $this->db->count('on_categorys','*');
+ //原生查询
+ $datas = $this->db->query($sql)->fetchAll();
+ $datas = [
+ 'code' => 0,
+ 'msg' => '',
+ 'count' => $count,
+ 'data' => $datas
+ ];
+ exit(json_encode($datas));
+ }
+ /**
+ * 查询链接
+ */
+ public function link_list($page,$limit,$token = ''){
+ $offset = ($page - 1) * $limit;
+ //如果成功登录,但token为空
+ if( ($this->is_login()) && (empty($token)) ){
+ //统计总数
+ $count = $this->db->count('on_links','*');
+ $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
+ }
+
+ //如果token验证通过
+ elseif( (!empty($token)) && ($this->auth($token)) ) {
+ //统计总数
+ $count = $this->db->count('on_links','*');
+ $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
+ }
+ else{
+ //统计总数
+ $count = $this->db->count('on_links','*',[ 'property' => 0 ]);
+ $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE property = 0 ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
+ }
+
+ //原生查询
+ $datas = $this->db->query($sql)->fetchAll();
+ $datas = [
+ 'code' => 0,
+ 'msg' => '',
+ 'count' => $count,
+ 'data' => $datas
+ ];
+ exit(json_encode($datas));
+ }
+ /**
+ * 验证是否登录
+ */
+ protected function is_login(){
+ $key = md5(USER.PASSWORD.$this->getIP().'onenav');
+ //获取session
+ $session = $_COOKIE['key'];
+ //如果已经成功登录
+ if($session == $key) {
+ return true;
+ }
+ else{
+ return false;
+ }
+ }
+ /**
+ * 获取链接信息
+ */
+ public function get_link_info($token,$url){
+ $this->auth($token);
+ //检查链接是否合法
+ //链接不合法
+ if( !filter_var($url, FILTER_VALIDATE_URL) ) {
+ $this->err_msg(-1010,'URL is not valid!');
+ }
+ //获取网站标题
+ $c = curl_init();
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($c, CURLOPT_SSL_VERIFYHOST, false);
+ //设置超时时间
+ curl_setopt($c , CURLOPT_TIMEOUT, 10);
+ $data = curl_exec($c);
+ curl_close($c);
+ $pos = strpos($data,'utf-8');
+ if($pos===false){$data = iconv("gbk","utf-8",$data);}
+ preg_match("/
(.*)<\/title>/i",$data, $title);
+
+ $link['title'] = $title[1];
+
+ //获取网站描述
+ $tags = get_meta_tags($url);
+ $link['description'] = $tags['description'];
+
+ $data = [
+ 'code' => 0,
+ 'data' => $link
+ ];
+ exit(json_encode($data));
+ }
+ /**
+ * 获取IP
+ */
+ //获取访客IP
+ protected function getIP() {
+ if (getenv('HTTP_CLIENT_IP')) {
+ $ip = getenv('HTTP_CLIENT_IP');
+ }
+ elseif (getenv('HTTP_X_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_X_FORWARDED_FOR');
+ }
+ elseif (getenv('HTTP_X_FORWARDED')) {
+ $ip = getenv('HTTP_X_FORWARDED');
+ }
+ elseif (getenv('HTTP_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_FORWARDED_FOR');
+ }
+ elseif (getenv('HTTP_FORWARDED')) {
+ $ip = getenv('HTTP_FORWARDED');
+ }
+ else {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ return $ip;
+ }
+
+ //
+}
+
diff --git a/class/Medoo.php b/class/Medoo.php
new file mode 100644
index 0000000..0164591
--- /dev/null
+++ b/class/Medoo.php
@@ -0,0 +1,1852 @@
+type = strtolower($options[ 'database_type' ]);
+
+ if ($this->type === 'mariadb')
+ {
+ $this->type = 'mysql';
+ }
+ }
+
+ if (isset($options[ 'prefix' ]))
+ {
+ $this->prefix = $options[ 'prefix' ];
+ }
+
+ if (isset($options[ 'logging' ]) && is_bool($options[ 'logging' ]))
+ {
+ $this->logging = $options[ 'logging' ];
+ }
+
+ $option = isset($options[ 'option' ]) ? $options[ 'option' ] : [];
+ $commands = (isset($options[ 'command' ]) && is_array($options[ 'command' ])) ? $options[ 'command' ] : [];
+
+ switch ($this->type)
+ {
+ case 'mysql':
+ // Make MySQL using standard quoted identifier
+ $commands[] = 'SET SQL_MODE=ANSI_QUOTES';
+
+ break;
+
+ case 'mssql':
+ // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting
+ $commands[] = 'SET QUOTED_IDENTIFIER ON';
+
+ // Make ANSI_NULLS is ON for NULL value
+ $commands[] = 'SET ANSI_NULLS ON';
+
+ break;
+ }
+
+ if (isset($options[ 'pdo' ]))
+ {
+ if (!$options[ 'pdo' ] instanceof PDO)
+ {
+ throw new InvalidArgumentException('Invalid PDO object supplied');
+ }
+
+ $this->pdo = $options[ 'pdo' ];
+
+ foreach ($commands as $value)
+ {
+ $this->pdo->exec($value);
+ }
+
+ return;
+ }
+
+ if (isset($options[ 'dsn' ]))
+ {
+ if (is_array($options[ 'dsn' ]) && isset($options[ 'dsn' ][ 'driver' ]))
+ {
+ $attr = $options[ 'dsn' ];
+ }
+ else
+ {
+ throw new InvalidArgumentException('Invalid DSN option supplied');
+ }
+ }
+ else
+ {
+ if (
+ isset($options[ 'port' ]) &&
+ is_int($options[ 'port' ] * 1)
+ )
+ {
+ $port = $options[ 'port' ];
+ }
+
+ $is_port = isset($port);
+
+ switch ($this->type)
+ {
+ case 'mysql':
+ $attr = [
+ 'driver' => 'mysql',
+ 'dbname' => $options[ 'database_name' ]
+ ];
+
+ if (isset($options[ 'socket' ]))
+ {
+ $attr[ 'unix_socket' ] = $options[ 'socket' ];
+ }
+ else
+ {
+ $attr[ 'host' ] = $options[ 'server' ];
+
+ if ($is_port)
+ {
+ $attr[ 'port' ] = $port;
+ }
+ }
+
+ break;
+
+ case 'pgsql':
+ $attr = [
+ 'driver' => 'pgsql',
+ 'host' => $options[ 'server' ],
+ 'dbname' => $options[ 'database_name' ]
+ ];
+
+ if ($is_port)
+ {
+ $attr[ 'port' ] = $port;
+ }
+
+ break;
+
+ case 'sybase':
+ $attr = [
+ 'driver' => 'dblib',
+ 'host' => $options[ 'server' ],
+ 'dbname' => $options[ 'database_name' ]
+ ];
+
+ if ($is_port)
+ {
+ $attr[ 'port' ] = $port;
+ }
+
+ break;
+
+ case 'oracle':
+ $attr = [
+ 'driver' => 'oci',
+ 'dbname' => $options[ 'server' ] ?
+ '//' . $options[ 'server' ] . ($is_port ? ':' . $port : ':1521') . '/' . $options[ 'database_name' ] :
+ $options[ 'database_name' ]
+ ];
+
+ if (isset($options[ 'charset' ]))
+ {
+ $attr[ 'charset' ] = $options[ 'charset' ];
+ }
+
+ break;
+
+ case 'mssql':
+ if (isset($options[ 'driver' ]) && $options[ 'driver' ] === 'dblib')
+ {
+ $attr = [
+ 'driver' => 'dblib',
+ 'host' => $options[ 'server' ] . ($is_port ? ':' . $port : ''),
+ 'dbname' => $options[ 'database_name' ]
+ ];
+
+ if (isset($options[ 'appname' ]))
+ {
+ $attr[ 'appname' ] = $options[ 'appname' ];
+ }
+
+ if (isset($options[ 'charset' ]))
+ {
+ $attr[ 'charset' ] = $options[ 'charset' ];
+ }
+ }
+ else
+ {
+ $attr = [
+ 'driver' => 'sqlsrv',
+ 'Server' => $options[ 'server' ] . ($is_port ? ',' . $port : ''),
+ 'Database' => $options[ 'database_name' ]
+ ];
+
+ if (isset($options[ 'appname' ]))
+ {
+ $attr[ 'APP' ] = $options[ 'appname' ];
+ }
+
+ $config = [
+ 'ApplicationIntent',
+ 'AttachDBFileName',
+ 'Authentication',
+ 'ColumnEncryption',
+ 'ConnectionPooling',
+ 'Encrypt',
+ 'Failover_Partner',
+ 'KeyStoreAuthentication',
+ 'KeyStorePrincipalId',
+ 'KeyStoreSecret',
+ 'LoginTimeout',
+ 'MultipleActiveResultSets',
+ 'MultiSubnetFailover',
+ 'Scrollable',
+ 'TraceFile',
+ 'TraceOn',
+ 'TransactionIsolation',
+ 'TransparentNetworkIPResolution',
+ 'TrustServerCertificate',
+ 'WSID',
+ ];
+
+ foreach ($config as $value)
+ {
+ $keyname = strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $value));
+
+ if (isset($options[ $keyname ]))
+ {
+ $attr[ $value ] = $options[ $keyname ];
+ }
+ }
+ }
+
+ break;
+
+ case 'sqlite':
+ $attr = [
+ 'driver' => 'sqlite',
+ $options[ 'database_file' ]
+ ];
+
+ break;
+ }
+ }
+
+ if (!isset($attr))
+ {
+ throw new InvalidArgumentException('Incorrect connection options');
+ }
+
+ $driver = $attr[ 'driver' ];
+
+ if (!in_array($driver, PDO::getAvailableDrivers()))
+ {
+ throw new InvalidArgumentException("Unsupported PDO driver: {$driver}");
+ }
+
+ unset($attr[ 'driver' ]);
+
+ $stack = [];
+
+ foreach ($attr as $key => $value)
+ {
+ $stack[] = is_int($key) ? $value : $key . '=' . $value;
+ }
+
+ $dsn = $driver . ':' . implode(';', $stack);
+
+ if (
+ in_array($this->type, ['mysql', 'pgsql', 'sybase', 'mssql']) &&
+ isset($options[ 'charset' ])
+ )
+ {
+ $commands[] = "SET NAMES '{$options[ 'charset' ]}'" . (
+ $this->type === 'mysql' && isset($options[ 'collation' ]) ?
+ " COLLATE '{$options[ 'collation' ]}'" : ''
+ );
+ }
+
+ $this->dsn = $dsn;
+
+ try {
+ $this->pdo = new PDO(
+ $dsn,
+ isset($options[ 'username' ]) ? $options[ 'username' ] : null,
+ isset($options[ 'password' ]) ? $options[ 'password' ] : null,
+ $option
+ );
+
+ foreach ($commands as $value)
+ {
+ $this->pdo->exec($value);
+ }
+ }
+ catch (PDOException $e) {
+ throw new PDOException($e->getMessage());
+ }
+ }
+
+ public function query($query, $map = [])
+ {
+ $raw = $this->raw($query, $map);
+
+ $query = $this->buildRaw($raw, $map);
+
+ return $this->exec($query, $map);
+ }
+
+ public function exec($query, $map = [])
+ {
+ $this->statement = null;
+
+ if ($this->debug_mode)
+ {
+ echo $this->generate($query, $map);
+
+ $this->debug_mode = false;
+
+ return false;
+ }
+
+ if ($this->logging)
+ {
+ $this->logs[] = [$query, $map];
+ }
+ else
+ {
+ $this->logs = [[$query, $map]];
+ }
+
+ $statement = $this->pdo->prepare($query);
+
+ if (!$statement)
+ {
+ $this->errorInfo = $this->pdo->errorInfo();
+ $this->statement = null;
+
+ return false;
+ }
+
+ $this->statement = $statement;
+
+ foreach ($map as $key => $value)
+ {
+ $statement->bindValue($key, $value[ 0 ], $value[ 1 ]);
+ }
+
+ $execute = $statement->execute();
+
+ $this->errorInfo = $statement->errorInfo();
+
+ if (!$execute)
+ {
+ $this->statement = null;
+ }
+
+ return $statement;
+ }
+
+ protected function generate($query, $map)
+ {
+ $identifier = [
+ 'mysql' => '`$1`',
+ 'mssql' => '[$1]'
+ ];
+
+ $query = preg_replace(
+ '/"([a-zA-Z0-9_]+)"/i',
+ isset($identifier[ $this->type ]) ? $identifier[ $this->type ] : '"$1"',
+ $query
+ );
+
+ foreach ($map as $key => $value)
+ {
+ if ($value[ 1 ] === PDO::PARAM_STR)
+ {
+ $replace = $this->quote($value[ 0 ]);
+ }
+ elseif ($value[ 1 ] === PDO::PARAM_NULL)
+ {
+ $replace = 'NULL';
+ }
+ elseif ($value[ 1 ] === PDO::PARAM_LOB)
+ {
+ $replace = '{LOB_DATA}';
+ }
+ else
+ {
+ $replace = $value[ 0 ];
+ }
+
+ $query = str_replace($key, $replace, $query);
+ }
+
+ return $query;
+ }
+
+ public static function raw($string, $map = [])
+ {
+ $raw = new Raw();
+
+ $raw->map = $map;
+ $raw->value = $string;
+
+ return $raw;
+ }
+
+ protected function isRaw($object)
+ {
+ return $object instanceof Raw;
+ }
+
+ protected function buildRaw($raw, &$map)
+ {
+ if (!$this->isRaw($raw))
+ {
+ return false;
+ }
+
+ $query = preg_replace_callback(
+ '/(([`\']).*?)?((FROM|TABLE|INTO|UPDATE|JOIN)\s*)?\<(([a-zA-Z0-9_]+)(\.[a-zA-Z0-9_]+)?)\>(.*?\2)?/i',
+ function ($matches)
+ {
+ if (!empty($matches[ 2 ]) && isset($matches[ 8 ]))
+ {
+ return $matches[ 0 ];
+ }
+
+ if (!empty($matches[ 4 ]))
+ {
+ return $matches[ 1 ] . $matches[ 4 ] . ' ' . $this->tableQuote($matches[ 5 ]);
+ }
+
+ return $matches[ 1 ] . $this->columnQuote($matches[ 5 ]);
+ },
+ $raw->value);
+
+ $raw_map = $raw->map;
+
+ if (!empty($raw_map))
+ {
+ foreach ($raw_map as $key => $value)
+ {
+ $map[ $key ] = $this->typeMap($value, gettype($value));
+ }
+ }
+
+ return $query;
+ }
+
+ public function quote($string)
+ {
+ return $this->pdo->quote($string);
+ }
+
+ protected function tableQuote($table)
+ {
+ if (!preg_match('/^[a-zA-Z0-9_]+$/i', $table))
+ {
+ throw new InvalidArgumentException("Incorrect table name \"$table\"");
+ }
+
+ return '"' . $this->prefix . $table . '"';
+ }
+
+ protected function mapKey()
+ {
+ return ':MeDoO_' . $this->guid++ . '_mEdOo';
+ }
+
+ protected function typeMap($value, $type)
+ {
+ $map = [
+ 'NULL' => PDO::PARAM_NULL,
+ 'integer' => PDO::PARAM_INT,
+ 'double' => PDO::PARAM_STR,
+ 'boolean' => PDO::PARAM_BOOL,
+ 'string' => PDO::PARAM_STR,
+ 'object' => PDO::PARAM_STR,
+ 'resource' => PDO::PARAM_LOB
+ ];
+
+ if ($type === 'boolean')
+ {
+ $value = ($value ? '1' : '0');
+ }
+ elseif ($type === 'NULL')
+ {
+ $value = null;
+ }
+
+ return [$value, $map[ $type ]];
+ }
+
+ protected function columnQuote($string)
+ {
+ if (!preg_match('/^[a-zA-Z0-9_]+(\.?[a-zA-Z0-9_]+)?$/i', $string))
+ {
+ throw new InvalidArgumentException("Incorrect column name \"$string\"");
+ }
+
+ if (strpos($string, '.') !== false)
+ {
+ return '"' . $this->prefix . str_replace('.', '"."', $string) . '"';
+ }
+
+ return '"' . $string . '"';
+ }
+
+ protected function columnPush(&$columns, &$map, $root, $is_join = false)
+ {
+ if ($columns === '*')
+ {
+ return $columns;
+ }
+
+ $stack = [];
+
+ if (is_string($columns))
+ {
+ $columns = [$columns];
+ }
+
+ foreach ($columns as $key => $value)
+ {
+ if (!is_int($key) && is_array($value) && $root && count(array_keys($columns)) === 1)
+ {
+ $stack[] = $this->columnQuote($key);
+
+ $stack[] = $this->columnPush($value, $map, false, $is_join);
+ }
+ elseif (is_array($value))
+ {
+ $stack[] = $this->columnPush($value, $map, false, $is_join);
+ }
+ elseif (!is_int($key) && $raw = $this->buildRaw($value, $map))
+ {
+ preg_match('/(?[a-zA-Z0-9_\.]+)(\s*\[(?(String|Bool|Int|Number))\])?/i', $key, $match);
+
+ $stack[] = $raw . ' AS ' . $this->columnQuote($match[ 'column' ]);
+ }
+ elseif (is_int($key) && is_string($value))
+ {
+ if ($is_join && strpos($value, '*') !== false)
+ {
+ throw new InvalidArgumentException('Cannot use table.* to select all columns while joining table');
+ }
+
+ preg_match('/(?[a-zA-Z0-9_\.]+)(?:\s*\((?[a-zA-Z0-9_]+)\))?(?:\s*\[(?(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match);
+
+ if (!empty($match[ 'alias' ]))
+ {
+ $stack[] = $this->columnQuote($match[ 'column' ]) . ' AS ' . $this->columnQuote($match[ 'alias' ]);
+
+ $columns[ $key ] = $match[ 'alias' ];
+
+ if (!empty($match[ 'type' ]))
+ {
+ $columns[ $key ] .= ' [' . $match[ 'type' ] . ']';
+ }
+ }
+ else
+ {
+ $stack[] = $this->columnQuote($match[ 'column' ]);
+ }
+ }
+ }
+
+ return implode(',', $stack);
+ }
+
+ protected function arrayQuote($array)
+ {
+ $stack = [];
+
+ foreach ($array as $value)
+ {
+ $stack[] = is_int($value) ? $value : $this->pdo->quote($value);
+ }
+
+ return implode(',', $stack);
+ }
+
+ protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor)
+ {
+ $stack = [];
+
+ foreach ($data as $value)
+ {
+ $stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')';
+ }
+
+ return implode($outer_conjunctor . ' ', $stack);
+ }
+
+ protected function dataImplode($data, &$map, $conjunctor)
+ {
+ $stack = [];
+
+ foreach ($data as $key => $value)
+ {
+ $type = gettype($value);
+
+ if (
+ $type === 'array' &&
+ preg_match("/^(AND|OR)(\s+#.*)?$/", $key, $relation_match)
+ )
+ {
+ $relationship = $relation_match[ 1 ];
+
+ $stack[] = $value !== array_keys(array_keys($value)) ?
+ '(' . $this->dataImplode($value, $map, ' ' . $relationship) . ')' :
+ '(' . $this->innerConjunct($value, $map, ' ' . $relationship, $conjunctor) . ')';
+
+ continue;
+ }
+
+ $map_key = $this->mapKey();
+
+ if (
+ is_int($key) &&
+ preg_match('/([a-zA-Z0-9_\.]+)\[(?\>\=?|\<\=?|\!?\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match)
+ )
+ {
+ $stack[] = $this->columnQuote($match[ 1 ]) . ' ' . $match[ 'operator' ] . ' ' . $this->columnQuote($match[ 3 ]);
+ }
+ else
+ {
+ preg_match('/([a-zA-Z0-9_\.]+)(\[(?\>\=?|\<\=?|\!|\<\>|\>\<|\!?~|REGEXP)\])?/i', $key, $match);
+ $column = $this->columnQuote($match[ 1 ]);
+
+ if (isset($match[ 'operator' ]))
+ {
+ $operator = $match[ 'operator' ];
+
+ if (in_array($operator, ['>', '>=', '<', '<=']))
+ {
+ $condition = $column . ' ' . $operator . ' ';
+
+ if (is_numeric($value))
+ {
+ $condition .= $map_key;
+ $map[ $map_key ] = [$value, is_float($value) ? PDO::PARAM_STR : PDO::PARAM_INT];
+ }
+ elseif ($raw = $this->buildRaw($value, $map))
+ {
+ $condition .= $raw;
+ }
+ else
+ {
+ $condition .= $map_key;
+ $map[ $map_key ] = [$value, PDO::PARAM_STR];
+ }
+
+ $stack[] = $condition;
+ }
+ elseif ($operator === '!')
+ {
+ switch ($type)
+ {
+ case 'NULL':
+ $stack[] = $column . ' IS NOT NULL';
+ break;
+
+ case 'array':
+ $placeholders = [];
+
+ foreach ($value as $index => $item)
+ {
+ $stack_key = $map_key . $index . '_i';
+
+ $placeholders[] = $stack_key;
+ $map[ $stack_key ] = $this->typeMap($item, gettype($item));
+ }
+
+ $stack[] = $column . ' NOT IN (' . implode(', ', $placeholders) . ')';
+ break;
+
+ case 'object':
+ if ($raw = $this->buildRaw($value, $map))
+ {
+ $stack[] = $column . ' != ' . $raw;
+ }
+ break;
+
+ case 'integer':
+ case 'double':
+ case 'boolean':
+ case 'string':
+ $stack[] = $column . ' != ' . $map_key;
+ $map[ $map_key ] = $this->typeMap($value, $type);
+ break;
+ }
+ }
+ elseif ($operator === '~' || $operator === '!~')
+ {
+ if ($type !== 'array')
+ {
+ $value = [ $value ];
+ }
+
+ $connector = ' OR ';
+ $data = array_values($value);
+
+ if (is_array($data[ 0 ]))
+ {
+ if (isset($value[ 'AND' ]) || isset($value[ 'OR' ]))
+ {
+ $connector = ' ' . array_keys($value)[ 0 ] . ' ';
+ $value = $data[ 0 ];
+ }
+ }
+
+ $like_clauses = [];
+
+ foreach ($value as $index => $item)
+ {
+ $item = strval($item);
+
+ if (!preg_match('/(\[.+\]|[\*\?\!\%#^-_]|%.+|.+%)/', $item))
+ {
+ $item = '%' . $item . '%';
+ }
+
+ $like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index;
+ $map[ $map_key . 'L' . $index ] = [$item, PDO::PARAM_STR];
+ }
+
+ $stack[] = '(' . implode($connector, $like_clauses) . ')';
+ }
+ elseif ($operator === '<>' || $operator === '><')
+ {
+ if ($type === 'array')
+ {
+ if ($operator === '><')
+ {
+ $column .= ' NOT';
+ }
+
+ $stack[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)';
+
+ $data_type = (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) ? PDO::PARAM_INT : PDO::PARAM_STR;
+
+ $map[ $map_key . 'a' ] = [$value[ 0 ], $data_type];
+ $map[ $map_key . 'b' ] = [$value[ 1 ], $data_type];
+ }
+ }
+ elseif ($operator === 'REGEXP')
+ {
+ $stack[] = $column . ' REGEXP ' . $map_key;
+ $map[ $map_key ] = [$value, PDO::PARAM_STR];
+ }
+ }
+ else
+ {
+ switch ($type)
+ {
+ case 'NULL':
+ $stack[] = $column . ' IS NULL';
+ break;
+
+ case 'array':
+ $placeholders = [];
+
+ foreach ($value as $index => $item)
+ {
+ $stack_key = $map_key . $index . '_i';
+
+ $placeholders[] = $stack_key;
+ $map[ $stack_key ] = $this->typeMap($item, gettype($item));
+ }
+
+ $stack[] = $column . ' IN (' . implode(', ', $placeholders) . ')';
+ break;
+
+ case 'object':
+ if ($raw = $this->buildRaw($value, $map))
+ {
+ $stack[] = $column . ' = ' . $raw;
+ }
+ break;
+
+ case 'integer':
+ case 'double':
+ case 'boolean':
+ case 'string':
+ $stack[] = $column . ' = ' . $map_key;
+ $map[ $map_key ] = $this->typeMap($value, $type);
+ break;
+ }
+ }
+ }
+ }
+
+ return implode($conjunctor . ' ', $stack);
+ }
+
+ protected function whereClause($where, &$map)
+ {
+ $where_clause = '';
+
+ if (is_array($where))
+ {
+ $where_keys = array_keys($where);
+
+ $conditions = array_diff_key($where, array_flip(
+ ['GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH']
+ ));
+
+ if (!empty($conditions))
+ {
+ $where_clause = ' WHERE ' . $this->dataImplode($conditions, $map, ' AND');
+ }
+
+ if (isset($where[ 'MATCH' ]) && $this->type === 'mysql')
+ {
+ $MATCH = $where[ 'MATCH' ];
+
+ if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ]))
+ {
+ $mode = '';
+
+ $mode_array = [
+ 'natural' => 'IN NATURAL LANGUAGE MODE',
+ 'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION',
+ 'boolean' => 'IN BOOLEAN MODE',
+ 'query' => 'WITH QUERY EXPANSION'
+ ];
+
+ if (isset($MATCH[ 'mode' ], $mode_array[ $MATCH[ 'mode' ] ]))
+ {
+ $mode = ' ' . $mode_array[ $MATCH[ 'mode' ] ];
+ }
+
+ $columns = implode(', ', array_map([$this, 'columnQuote'], $MATCH[ 'columns' ]));
+ $map_key = $this->mapKey();
+ $map[ $map_key ] = [$MATCH[ 'keyword' ], PDO::PARAM_STR];
+
+ $where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')';
+ }
+ }
+
+ if (isset($where[ 'GROUP' ]))
+ {
+ $GROUP = $where[ 'GROUP' ];
+
+ if (is_array($GROUP))
+ {
+ $stack = [];
+
+ foreach ($GROUP as $column => $value)
+ {
+ $stack[] = $this->columnQuote($value);
+ }
+
+ $where_clause .= ' GROUP BY ' . implode(',', $stack);
+ }
+ elseif ($raw = $this->buildRaw($GROUP, $map))
+ {
+ $where_clause .= ' GROUP BY ' . $raw;
+ }
+ else
+ {
+ $where_clause .= ' GROUP BY ' . $this->columnQuote($GROUP);
+ }
+
+ if (isset($where[ 'HAVING' ]))
+ {
+ if ($raw = $this->buildRaw($where[ 'HAVING' ], $map))
+ {
+ $where_clause .= ' HAVING ' . $raw;
+ }
+ else
+ {
+ $where_clause .= ' HAVING ' . $this->dataImplode($where[ 'HAVING' ], $map, ' AND');
+ }
+ }
+ }
+
+ if (isset($where[ 'ORDER' ]))
+ {
+ $ORDER = $where[ 'ORDER' ];
+
+ if (is_array($ORDER))
+ {
+ $stack = [];
+
+ foreach ($ORDER as $column => $value)
+ {
+ if (is_array($value))
+ {
+ $stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')';
+ }
+ elseif ($value === 'ASC' || $value === 'DESC')
+ {
+ $stack[] = $this->columnQuote($column) . ' ' . $value;
+ }
+ elseif (is_int($column))
+ {
+ $stack[] = $this->columnQuote($value);
+ }
+ }
+
+ $where_clause .= ' ORDER BY ' . implode(',', $stack);
+ }
+ elseif ($raw = $this->buildRaw($ORDER, $map))
+ {
+ $where_clause .= ' ORDER BY ' . $raw;
+ }
+ else
+ {
+ $where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER);
+ }
+
+ if (
+ isset($where[ 'LIMIT' ]) &&
+ in_array($this->type, ['oracle', 'mssql'])
+ )
+ {
+ $LIMIT = $where[ 'LIMIT' ];
+
+ if (is_numeric($LIMIT))
+ {
+ $LIMIT = [0, $LIMIT];
+ }
+
+ if (
+ is_array($LIMIT) &&
+ is_numeric($LIMIT[ 0 ]) &&
+ is_numeric($LIMIT[ 1 ])
+ )
+ {
+ $where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' ROWS FETCH NEXT ' . $LIMIT[ 1 ] . ' ROWS ONLY';
+ }
+ }
+ }
+
+ if (isset($where[ 'LIMIT' ]) && !in_array($this->type, ['oracle', 'mssql']))
+ {
+ $LIMIT = $where[ 'LIMIT' ];
+
+ if (is_numeric($LIMIT))
+ {
+ $where_clause .= ' LIMIT ' . $LIMIT;
+ }
+ elseif (
+ is_array($LIMIT) &&
+ is_numeric($LIMIT[ 0 ]) &&
+ is_numeric($LIMIT[ 1 ])
+ )
+ {
+ $where_clause .= ' LIMIT ' . $LIMIT[ 1 ] . ' OFFSET ' . $LIMIT[ 0 ];
+ }
+ }
+ }
+ elseif ($raw = $this->buildRaw($where, $map))
+ {
+ $where_clause .= ' ' . $raw;
+ }
+
+ return $where_clause;
+ }
+
+ protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null)
+ {
+ preg_match('/(?[a-zA-Z0-9_]+)\s*\((?[a-zA-Z0-9_]+)\)/i', $table, $table_match);
+
+ if (isset($table_match[ 'table' ], $table_match[ 'alias' ]))
+ {
+ $table = $this->tableQuote($table_match[ 'table' ]);
+
+ $table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]);
+ }
+ else
+ {
+ $table = $this->tableQuote($table);
+
+ $table_query = $table;
+ }
+
+ $is_join = false;
+ $join_key = is_array($join) ? array_keys($join) : null;
+
+ if (
+ isset($join_key[ 0 ]) &&
+ strpos($join_key[ 0 ], '[') === 0
+ )
+ {
+ $is_join = true;
+ $table_query .= ' ' . $this->buildJoin($table, $join);
+ }
+ else
+ {
+ if (is_null($columns))
+ {
+ if (
+ !is_null($where) ||
+ (is_array($join) && isset($column_fn))
+ )
+ {
+ $where = $join;
+ $columns = null;
+ }
+ else
+ {
+ $where = null;
+ $columns = $join;
+ }
+ }
+ else
+ {
+ $where = $columns;
+ $columns = $join;
+ }
+ }
+
+ if (isset($column_fn))
+ {
+ if ($column_fn === 1)
+ {
+ $column = '1';
+
+ if (is_null($where))
+ {
+ $where = $columns;
+ }
+ }
+ elseif ($raw = $this->buildRaw($column_fn, $map))
+ {
+ $column = $raw;
+ }
+ else
+ {
+ if (empty($columns) || $this->isRaw($columns))
+ {
+ $columns = '*';
+ $where = $join;
+ }
+
+ $column = $column_fn . '(' . $this->columnPush($columns, $map, true) . ')';
+ }
+ }
+ else
+ {
+ $column = $this->columnPush($columns, $map, true, $is_join);
+ }
+
+ return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map);
+ }
+
+ protected function buildJoin($table, $join)
+ {
+ $table_join = [];
+
+ $join_array = [
+ '>' => 'LEFT',
+ '<' => 'RIGHT',
+ '<>' => 'FULL',
+ '><' => 'INNER'
+ ];
+
+ foreach($join as $sub_table => $relation)
+ {
+ preg_match('/(\[(?\<\>?|\>\)\])?(?[a-zA-Z0-9_]+)\s?(\((?[a-zA-Z0-9_]+)\))?/', $sub_table, $match);
+
+ if ($match[ 'join' ] !== '' && $match[ 'table' ] !== '')
+ {
+ if (is_string($relation))
+ {
+ $relation = 'USING ("' . $relation . '")';
+ }
+
+ if (is_array($relation))
+ {
+ // For ['column1', 'column2']
+ if (isset($relation[ 0 ]))
+ {
+ $relation = 'USING ("' . implode('", "', $relation) . '")';
+ }
+ else
+ {
+ $joins = [];
+
+ foreach ($relation as $key => $value)
+ {
+ $joins[] = (
+ strpos($key, '.') > 0 ?
+ // For ['tableB.column' => 'column']
+ $this->columnQuote($key) :
+
+ // For ['column1' => 'column2']
+ $table . '."' . $key . '"'
+ ) .
+ ' = ' .
+ $this->tableQuote(isset($match[ 'alias' ]) ? $match[ 'alias' ] : $match[ 'table' ]) . '."' . $value . '"';
+ }
+
+ $relation = 'ON ' . implode(' AND ', $joins);
+ }
+ }
+
+ $table_name = $this->tableQuote($match[ 'table' ]) . ' ';
+
+ if (isset($match[ 'alias' ]))
+ {
+ $table_name .= 'AS ' . $this->tableQuote($match[ 'alias' ]) . ' ';
+ }
+
+ $table_join[] = $join_array[ $match[ 'join' ] ] . ' JOIN ' . $table_name . $relation;
+ }
+ }
+
+ return implode(' ', $table_join);
+ }
+
+ protected function columnMap($columns, &$stack, $root)
+ {
+ if ($columns === '*')
+ {
+ return $stack;
+ }
+
+ foreach ($columns as $key => $value)
+ {
+ if (is_int($key))
+ {
+ preg_match('/([a-zA-Z0-9_]+\.)?(?[a-zA-Z0-9_]+)(?:\s*\((?[a-zA-Z0-9_]+)\))?(?:\s*\[(?(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match);
+
+ $column_key = !empty($key_match[ 'alias' ]) ?
+ $key_match[ 'alias' ] :
+ $key_match[ 'column' ];
+
+ if (isset($key_match[ 'type' ]))
+ {
+ $stack[ $value ] = [$column_key, $key_match[ 'type' ]];
+ }
+ else
+ {
+ $stack[ $value ] = [$column_key, 'String'];
+ }
+ }
+ elseif ($this->isRaw($value))
+ {
+ preg_match('/([a-zA-Z0-9_]+\.)?(?[a-zA-Z0-9_]+)(\s*\[(?(String|Bool|Int|Number))\])?/i', $key, $key_match);
+
+ $column_key = $key_match[ 'column' ];
+
+ if (isset($key_match[ 'type' ]))
+ {
+ $stack[ $key ] = [$column_key, $key_match[ 'type' ]];
+ }
+ else
+ {
+ $stack[ $key ] = [$column_key, 'String'];
+ }
+ }
+ elseif (!is_int($key) && is_array($value))
+ {
+ if ($root && count(array_keys($columns)) === 1)
+ {
+ $stack[ $key ] = [$key, 'String'];
+ }
+
+ $this->columnMap($value, $stack, false);
+ }
+ }
+
+ return $stack;
+ }
+
+ protected function dataMap($data, $columns, $column_map, &$stack, $root, &$result)
+ {
+ if ($root)
+ {
+ $columns_key = array_keys($columns);
+
+ if (count($columns_key) === 1 && is_array($columns[$columns_key[0]]))
+ {
+ $index_key = array_keys($columns)[0];
+ $data_key = preg_replace("/^[a-zA-Z0-9_]+\./i", "", $index_key);
+
+ $current_stack = [];
+
+ foreach ($data as $item)
+ {
+ $this->dataMap($data, $columns[ $index_key ], $column_map, $current_stack, false, $result);
+
+ $index = $data[ $data_key ];
+
+ $result[ $index ] = $current_stack;
+ }
+ }
+ else
+ {
+ $current_stack = [];
+
+ $this->dataMap($data, $columns, $column_map, $current_stack, false, $result);
+
+ $result[] = $current_stack;
+ }
+
+ return;
+ }
+
+ foreach ($columns as $key => $value)
+ {
+ $isRaw = $this->isRaw($value);
+
+ if (is_int($key) || $isRaw)
+ {
+ $map = $column_map[ $isRaw ? $key : $value ];
+
+ $column_key = $map[ 0 ];
+
+ $item = $data[ $column_key ];
+
+ if (isset($map[ 1 ]))
+ {
+ if ($isRaw && in_array($map[ 1 ], ['Object', 'JSON']))
+ {
+ continue;
+ }
+
+ if (is_null($item))
+ {
+ $stack[ $column_key ] = null;
+ continue;
+ }
+
+ switch ($map[ 1 ])
+ {
+ case 'Number':
+ $stack[ $column_key ] = (double) $item;
+ break;
+
+ case 'Int':
+ $stack[ $column_key ] = (int) $item;
+ break;
+
+ case 'Bool':
+ $stack[ $column_key ] = (bool) $item;
+ break;
+
+ case 'Object':
+ $stack[ $column_key ] = unserialize($item);
+ break;
+
+ case 'JSON':
+ $stack[ $column_key ] = json_decode($item, true);
+ break;
+
+ case 'String':
+ $stack[ $column_key ] = $item;
+ break;
+ }
+ }
+ else
+ {
+ $stack[ $column_key ] = $item;
+ }
+ }
+ else
+ {
+ $current_stack = [];
+
+ $this->dataMap($data, $value, $column_map, $current_stack, false, $result);
+
+ $stack[ $key ] = $current_stack;
+ }
+ }
+ }
+
+ public function create($table, $columns, $options = null)
+ {
+ $stack = [];
+
+ $tableName = $this->prefix . $table;
+
+ foreach ($columns as $name => $definition)
+ {
+ if (is_int($name))
+ {
+ $stack[] = preg_replace('/\<([a-zA-Z0-9_]+)\>/i', '"$1"', $definition);
+ }
+ elseif (is_array($definition))
+ {
+ $stack[] = $name . ' ' . implode(' ', $definition);
+ }
+ elseif (is_string($definition))
+ {
+ $stack[] = $name . ' ' . $this->query($definition);
+ }
+ }
+
+ $table_option = '';
+
+ if (is_array($options))
+ {
+ $option_stack = [];
+
+ foreach ($options as $key => $value)
+ {
+ if (is_string($value) || is_int($value))
+ {
+ $option_stack[] = "$key = $value";
+ }
+ }
+
+ $table_option = ' ' . implode(', ', $option_stack);
+ }
+ elseif (is_string($options))
+ {
+ $table_option = ' ' . $options;
+ }
+
+ return $this->exec("CREATE TABLE IF NOT EXISTS $tableName (" . implode(', ', $stack) . ")$table_option");
+ }
+
+ public function drop($table)
+ {
+ $tableName = $this->prefix . $table;
+
+ return $this->exec("DROP TABLE IF EXISTS $tableName");
+ }
+
+ public function select($table, $join, $columns = null, $where = null)
+ {
+ $map = [];
+ $result = [];
+ $column_map = [];
+
+ $index = 0;
+
+ $column = $where === null ? $join : $columns;
+
+ $is_single = (is_string($column) && $column !== '*');
+
+ $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map);
+
+ $this->columnMap($columns, $column_map, true);
+
+ if (!$this->statement)
+ {
+ return false;
+ }
+
+ if ($columns === '*')
+ {
+ return $query->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ while ($data = $query->fetch(PDO::FETCH_ASSOC))
+ {
+ $current_stack = [];
+
+ $this->dataMap($data, $columns, $column_map, $current_stack, true, $result);
+ }
+
+ if ($is_single)
+ {
+ $single_result = [];
+ $result_key = $column_map[ $column ][ 0 ];
+
+ foreach ($result as $item)
+ {
+ $single_result[] = $item[ $result_key ];
+ }
+
+ return $single_result;
+ }
+
+ return $result;
+ }
+
+ public function insert($table, $datas)
+ {
+ $stack = [];
+ $columns = [];
+ $fields = [];
+ $map = [];
+
+ if (!isset($datas[ 0 ]))
+ {
+ $datas = [$datas];
+ }
+
+ foreach ($datas as $data)
+ {
+ foreach ($data as $key => $value)
+ {
+ $columns[] = $key;
+ }
+ }
+
+ $columns = array_unique($columns);
+
+ foreach ($datas as $data)
+ {
+ $values = [];
+
+ foreach ($columns as $key)
+ {
+ if ($raw = $this->buildRaw($data[ $key ], $map))
+ {
+ $values[] = $raw;
+ continue;
+ }
+
+ $map_key = $this->mapKey();
+
+ $values[] = $map_key;
+
+ if (!isset($data[ $key ]))
+ {
+ $map[ $map_key ] = [null, PDO::PARAM_NULL];
+ }
+ else
+ {
+ $value = $data[ $key ];
+
+ $type = gettype($value);
+
+ switch ($type)
+ {
+ case 'array':
+ $map[ $map_key ] = [
+ strpos($key, '[JSON]') === strlen($key) - 6 ?
+ json_encode($value) :
+ serialize($value),
+ PDO::PARAM_STR
+ ];
+ break;
+
+ case 'object':
+ $value = serialize($value);
+
+ case 'NULL':
+ case 'resource':
+ case 'boolean':
+ case 'integer':
+ case 'double':
+ case 'string':
+ $map[ $map_key ] = $this->typeMap($value, $type);
+ break;
+ }
+ }
+ }
+
+ $stack[] = '(' . implode(', ', $values) . ')';
+ }
+
+ foreach ($columns as $key)
+ {
+ $fields[] = $this->columnQuote(preg_replace("/(\s*\[JSON\]$)/i", '', $key));
+ }
+
+ return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map);
+ }
+
+ public function update($table, $data, $where = null)
+ {
+ $fields = [];
+ $map = [];
+
+ foreach ($data as $key => $value)
+ {
+ $column = $this->columnQuote(preg_replace("/(\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key));
+
+ if ($raw = $this->buildRaw($value, $map))
+ {
+ $fields[] = $column . ' = ' . $raw;
+ continue;
+ }
+
+ $map_key = $this->mapKey();
+
+ preg_match('/(?[a-zA-Z0-9_]+)(\[(?\+|\-|\*|\/)\])?/i', $key, $match);
+
+ if (isset($match[ 'operator' ]))
+ {
+ if (is_numeric($value))
+ {
+ $fields[] = $column . ' = ' . $column . ' ' . $match[ 'operator' ] . ' ' . $value;
+ }
+ }
+ else
+ {
+ $fields[] = $column . ' = ' . $map_key;
+
+ $type = gettype($value);
+
+ switch ($type)
+ {
+ case 'array':
+ $map[ $map_key ] = [
+ strpos($key, '[JSON]') === strlen($key) - 6 ?
+ json_encode($value) :
+ serialize($value),
+ PDO::PARAM_STR
+ ];
+ break;
+
+ case 'object':
+ $value = serialize($value);
+
+ case 'NULL':
+ case 'resource':
+ case 'boolean':
+ case 'integer':
+ case 'double':
+ case 'string':
+ $map[ $map_key ] = $this->typeMap($value, $type);
+ break;
+ }
+ }
+ }
+
+ return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map);
+ }
+
+ public function delete($table, $where)
+ {
+ $map = [];
+
+ return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map);
+ }
+
+ public function replace($table, $columns, $where = null)
+ {
+ if (!is_array($columns) || empty($columns))
+ {
+ return false;
+ }
+
+ $map = [];
+ $stack = [];
+
+ foreach ($columns as $column => $replacements)
+ {
+ if (is_array($replacements))
+ {
+ foreach ($replacements as $old => $new)
+ {
+ $map_key = $this->mapKey();
+
+ $stack[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)';
+
+ $map[ $map_key . 'a' ] = [$old, PDO::PARAM_STR];
+ $map[ $map_key . 'b' ] = [$new, PDO::PARAM_STR];
+ }
+ }
+ }
+
+ if (!empty($stack))
+ {
+ return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $stack) . $this->whereClause($where, $map), $map);
+ }
+
+ return false;
+ }
+
+ public function get($table, $join = null, $columns = null, $where = null)
+ {
+ $map = [];
+ $result = [];
+ $column_map = [];
+ $current_stack = [];
+
+ if ($where === null)
+ {
+ $column = $join;
+ unset($columns[ 'LIMIT' ]);
+ }
+ else
+ {
+ $column = $columns;
+ unset($where[ 'LIMIT' ]);
+ }
+
+ $is_single = (is_string($column) && $column !== '*');
+
+ $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map);
+
+ if (!$this->statement)
+ {
+ return false;
+ }
+
+ $data = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ if (isset($data[ 0 ]))
+ {
+ if ($column === '*')
+ {
+ return $data[ 0 ];
+ }
+
+ $this->columnMap($columns, $column_map, true);
+
+ $this->dataMap($data[ 0 ], $columns, $column_map, $current_stack, true, $result);
+
+ if ($is_single)
+ {
+ return $result[ 0 ][ $column_map[ $column ][ 0 ] ];
+ }
+
+ return $result[ 0 ];
+ }
+ }
+
+ public function has($table, $join, $where = null)
+ {
+ $map = [];
+ $column = null;
+
+ if ($this->type === 'mssql')
+ {
+ $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, Medoo::raw('TOP 1 1')), $map);
+ }
+ else
+ {
+ $query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map);
+ }
+
+ if (!$this->statement)
+ {
+ return false;
+ }
+
+ $result = $query->fetchColumn();
+
+ return $result === '1' || $result === 1 || $result === true;
+ }
+
+ public function rand($table, $join = null, $columns = null, $where = null)
+ {
+ $type = $this->type;
+
+ $order = 'RANDOM()';
+
+ if ($type === 'mysql')
+ {
+ $order = 'RAND()';
+ }
+ elseif ($type === 'mssql')
+ {
+ $order = 'NEWID()';
+ }
+
+ $order_raw = $this->raw($order);
+
+ if ($where === null)
+ {
+ if ($columns === null)
+ {
+ $columns = [
+ 'ORDER' => $order_raw
+ ];
+ }
+ else
+ {
+ $column = $join;
+ unset($columns[ 'ORDER' ]);
+
+ $columns[ 'ORDER' ] = $order_raw;
+ }
+ }
+ else
+ {
+ unset($where[ 'ORDER' ]);
+
+ $where[ 'ORDER' ] = $order_raw;
+ }
+
+ return $this->select($table, $join, $columns, $where);
+ }
+
+ private function aggregate($type, $table, $join = null, $column = null, $where = null)
+ {
+ $map = [];
+
+ $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, strtoupper($type)), $map);
+
+ if (!$this->statement)
+ {
+ return false;
+ }
+
+ $number = $query->fetchColumn();
+
+ return is_numeric($number) ? $number + 0 : $number;
+ }
+
+ public function count($table, $join = null, $column = null, $where = null)
+ {
+ return $this->aggregate('count', $table, $join, $column, $where);
+ }
+
+ public function avg($table, $join, $column = null, $where = null)
+ {
+ return $this->aggregate('avg', $table, $join, $column, $where);
+ }
+
+ public function max($table, $join, $column = null, $where = null)
+ {
+ return $this->aggregate('max', $table, $join, $column, $where);
+ }
+
+ public function min($table, $join, $column = null, $where = null)
+ {
+ return $this->aggregate('min', $table, $join, $column, $where);
+ }
+
+ public function sum($table, $join, $column = null, $where = null)
+ {
+ return $this->aggregate('sum', $table, $join, $column, $where);
+ }
+
+ public function action($actions)
+ {
+ if (is_callable($actions))
+ {
+ $this->pdo->beginTransaction();
+
+ try {
+ $result = $actions($this);
+
+ if ($result === false)
+ {
+ $this->pdo->rollBack();
+ }
+ else
+ {
+ $this->pdo->commit();
+ }
+ }
+ catch (Exception $e) {
+ $this->pdo->rollBack();
+
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ return false;
+ }
+
+ public function id()
+ {
+ if ($this->statement == null)
+ {
+ return null;
+ }
+
+ $type = $this->type;
+
+ if ($type === 'oracle')
+ {
+ return 0;
+ }
+ elseif ($type === 'pgsql')
+ {
+ return $this->pdo->query('SELECT LASTVAL()')->fetchColumn();
+ }
+
+ $lastId = $this->pdo->lastInsertId();
+
+ if ($lastId != "0" && $lastId != "")
+ {
+ return $lastId;
+ }
+
+ return null;
+ }
+
+ public function debug()
+ {
+ $this->debug_mode = true;
+
+ return $this;
+ }
+
+ public function error()
+ {
+ return $this->errorInfo;
+ }
+
+ public function last()
+ {
+ $log = end($this->logs);
+
+ return $this->generate($log[ 0 ], $log[ 1 ]);
+ }
+
+ public function log()
+ {
+ return array_map(function ($log)
+ {
+ return $this->generate($log[ 0 ], $log[ 1 ]);
+ },
+ $this->logs
+ );
+ }
+
+ public function info()
+ {
+ $output = [
+ 'server' => 'SERVER_INFO',
+ 'driver' => 'DRIVER_NAME',
+ 'client' => 'CLIENT_VERSION',
+ 'version' => 'SERVER_VERSION',
+ 'connection' => 'CONNECTION_STATUS'
+ ];
+
+ foreach ($output as $key => $value)
+ {
+ $output[ $key ] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value));
+ }
+
+ $output[ 'dsn' ] = $this->dsn;
+
+ return $output;
+ }
+}
\ No newline at end of file
diff --git a/class/index.html b/class/index.html
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/class/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/config.simple.php b/config.simple.php
new file mode 100644
index 0000000..81180e4
--- /dev/null
+++ b/config.simple.php
@@ -0,0 +1,32 @@
+ 'sqlite',
+ 'database_file' => 'db/onenav.db3'
+]);
+
+//用户名
+define('USER','xiaoz');
+//密码
+define('PASSWORD','xiaoz.me');
+//邮箱,用于后台Gravatar头像显示
+define('EMAIL','337003006@qq.com');
+//token参数,API需要使用
+define('TOKEN','xiaoz.me');
+//主题风格
+define('TEMPLATE','default');
+
+$site_setting = [];
+//用户名
+$site_setting['user'] = USER;
+$site_setting['password'] = PASSWORD;
+
+//站点标题
+$site_setting['title'] = 'OneNav';
+//站点关键词
+$site_setting['keywords'] = 'OneNav,简洁导航,云链接,个人书签';
+//站点描述
+$site_setting['description'] = '';
+
diff --git a/controller/.htaccess b/controller/.htaccess
new file mode 100644
index 0000000..baa56e5
--- /dev/null
+++ b/controller/.htaccess
@@ -0,0 +1,2 @@
+order allow,deny
+deny from all
\ No newline at end of file
diff --git a/controller/admin.php b/controller/admin.php
new file mode 100644
index 0000000..df1bde4
--- /dev/null
+++ b/controller/admin.php
@@ -0,0 +1,113 @@
+get('on_categorys','*',[ 'id' => $id ]);
+ //checked按钮
+ if( $category['property'] == 1 ) {
+ $category['checked'] = 'checked';
+ }
+ else{
+ $category['checked'] = '';
+ }
+}
+
+//如果页面是修改link
+if ($page == 'edit_link') {
+ //查询所有分类信息,用于分类框选择
+ $categorys = $db->select('on_categorys','*',[ 'ORDER' => ['weigth' => 'DESC'] ]);
+ //获取id
+ $id = intval($_GET['id']);
+ //查询单条链接信息
+ $link = $db->get('on_links','*',[ 'id' => $id ]);
+ //查询单个分类信息
+ $cat_name = $db->get('on_categorys',['name'],[ 'id' => $link['fid'] ]);
+ $cat_name = $cat_name['name'];
+
+ //checked按钮
+ if( $link['property'] == 1 ) {
+ $link['checked'] = 'checked';
+ }
+ else{
+ $link['checked'] = '';
+ }
+}
+
+//如果页面是添加链接页面
+if ($page == 'add_link') {
+ //查询所有分类信息
+ $categorys = $db->select('on_categorys','*',[ 'ORDER' => ['weigth' => 'DESC'] ]);
+ //checked按钮
+ if( $category['property'] == 1 ) {
+ $category['checked'] = 'checked';
+ }
+ else{
+ $category['checked'] = '';
+ }
+}
+
+//如果是退出
+//如果页面是添加链接页面
+if ($page == 'logout') {
+ //清除cookie
+ setcookie("key", $key, -(time()+7 * 24 * 60 * 60),"/");
+ //跳转到首页
+ header('location:/');
+ exit;
+}
+
+$page = $page.'.php';
+
+//获取访客IP
+function getIP() {
+ if (getenv('HTTP_CLIENT_IP')) {
+ $ip = getenv('HTTP_CLIENT_IP');
+ }
+ elseif (getenv('HTTP_X_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_X_FORWARDED_FOR');
+ }
+ elseif (getenv('HTTP_X_FORWARDED')) {
+ $ip = getenv('HTTP_X_FORWARDED');
+ }
+ elseif (getenv('HTTP_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_FORWARDED_FOR');
+ }
+ elseif (getenv('HTTP_FORWARDED')) {
+ $ip = getenv('HTTP_FORWARDED');
+ }
+ else {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ return $ip;
+ }
+
+/**
+ * 检查授权
+ */
+
+function check_auth($user,$password){
+ $ip = getIP();
+ $key = md5($user.$password.$ip.'onenav');
+ //获取cookie
+ $cookie = $_COOKIE['key'];
+ //如果cookie的值和计算的key不一致,则没有权限
+ if( $cookie != $key ){
+ exit("");
+ }
+}
+
+
+// 载入前台首页模板
+require('templates/admin/'.$page);
\ No newline at end of file
diff --git a/controller/api.php b/controller/api.php
new file mode 100644
index 0000000..96582eb
--- /dev/null
+++ b/controller/api.php
@@ -0,0 +1,177 @@
+
+ * blog:xiaoz.me
+ */
+
+//允许跨域访问
+header("Access-Control-Allow-Origin: *");
+require('./class/Api.php');
+
+$api = new Api($db);
+
+//获取请求方法
+$method = $_GET['method'];
+//对方法进行判断
+switch ($method) {
+ case 'add_category':
+ add_category($api);
+ break;
+ case 'edit_category':
+ edit_category($api);
+ break;
+ case 'del_category':
+ del_category($api);
+ break;
+ case 'add_link':
+ add_link($api);
+ break;
+ case 'edit_link':
+ edit_link($api);
+ break;
+ case 'del_link':
+ del_link($api);
+ break;
+ case 'category_list':
+ category_list($api);
+ break;
+ case 'link_list':
+ link_list($api);
+ break;
+ case 'get_link_info':
+ get_link_info($api);
+ break;
+ default:
+ # code...
+ break;
+}
+
+/**
+ * 添加分类目录入口
+ */
+function add_category($api){
+ //获取token
+ $token = $_POST['token'];
+ //获取分类名称
+ $name = $_POST['name'];
+ //获取私有属性
+ $property = empty($_POST['property']) ? 0 : 1;
+ //获取权重
+ $weight = empty($_POST['weight']) ? 0 : intval($_POST['weight']);
+ //获取描述
+ $description = empty($_POST['description']) ? '' : $_POST['description'];
+ //描述过滤
+ $description = htmlspecialchars($description);
+ $api->add_category($token,$name,$property,$weight,$description);
+}
+/**
+ * 修改分类目录入口
+ */
+function edit_category($api){
+ //获取ID
+ $id = intval($_POST['id']);
+
+ //获取token
+ $token = $_POST['token'];
+ //获取分类名称
+ $name = $_POST['name'];
+ //获取私有属性
+ $property = empty($_POST['property']) ? 0 : 1;
+ //获取权重
+ $weight = empty($_POST['weight']) ? 0 : intval($_POST['weight']);
+ //获取描述
+ $description = empty($_POST['description']) ? '' : $_POST['description'];
+ //描述过滤
+ $description = htmlspecialchars($description);
+ $api->edit_category($token,$id,$name,$property,$weight,$description);
+}
+/**
+ * 删除分类目录
+ */
+function del_category($api){
+ //获取ID
+ $id = intval($_POST['id']);
+ //获取token
+ $token = $_POST['token'];
+ $api->del_category($token,$id);
+}
+/**
+ * 插入链接
+ */
+function add_link($api){
+ //add_link($token,$fid,$title,$url,$description = '',$weight = 0,$property = 0)
+ //获取token
+ $token = $_POST['token'];
+
+ //获取fid
+ $fid = intval(@$_POST['fid']);
+ $title = $_POST['title'];
+ $url = $_POST['url'];
+ $description = empty($_POST['description']) ? '' : $_POST['description'];
+ $weight = empty($_POST['weight']) ? 0 : intval($_POST['weight']);
+ $property = empty($_POST['property']) ? 0 : 1;
+
+ $api->add_link($token,$fid,$title,$url,$description,$weight,$property);
+
+}
+/**
+ * 修改链接
+ */
+function edit_link($api){
+ //add_link($token,$fid,$title,$url,$description = '',$weight = 0,$property = 0)
+ //获取token
+ $token = $_POST['token'];
+ $id = intval(@$_POST['id']);
+
+ //获取fid
+ $fid = intval(@$_POST['fid']);
+ $title = $_POST['title'];
+ $url = $_POST['url'];
+ $description = empty($_POST['description']) ? '' : $_POST['description'];
+ $weight = empty($_POST['weight']) ? 0 : intval($_POST['weight']);
+ $property = empty($_POST['property']) ? 0 : 1;
+
+ $api->edit_link($token,$id,$fid,$title,$url,$description,$weight,$property);
+
+}
+
+/**
+ * 删除链接
+ */
+function del_link($api){
+ $token = $_POST['token'];
+ $id = intval(@$_POST['id']);
+ $api->del_link($token,$id);
+}
+/**
+ * 查询分类目录列表
+ */
+function category_list($api){
+ $page = empty(intval($_GET['page'])) ? 1 : intval($_GET['page']);
+ $limit = empty(intval($_GET['limit'])) ? 10 : intval($_GET['limit']);
+ $api->category_list($page,$limit);
+}
+
+/**
+ * 查询链接列表
+ */
+function link_list($api){
+ $page = empty(intval($_GET['page'])) ? 1 : intval($_GET['page']);
+ $limit = empty(intval($_GET['limit'])) ? 10 : intval($_GET['limit']);
+ //获取token
+ $token = $_POST['token'];
+ $api->link_list($page,$limit,$token);
+}
+
+/**
+ * 获取链接信息
+ */
+function get_link_info($api) {
+ //获取token
+ $token = $_POST['token'];
+ //获取URL
+ $url = @$_POST['url'];
+ $api->get_link_info($token,$url);
+}
\ No newline at end of file
diff --git a/controller/click.php b/controller/click.php
new file mode 100644
index 0000000..ccf656b
--- /dev/null
+++ b/controller/click.php
@@ -0,0 +1,65 @@
+get('on_links',['id','fid','url','property','click'],[
+ 'id' => $id
+]);
+
+//如果查询失败
+if( !$link ){
+ exit('无效ID!');
+}
+
+//查询该ID的父及ID信息
+$category = $db->get('on_categorys',['id','property'],[
+ 'id' => $link['fid']
+]);
+
+//link.id为公有,且category.id为公有
+if( ( $link['property'] == 0 ) && ($category['property'] == 0) ){
+ //增加link.id的点击次数
+ $click = $link['click'] + 1;
+ //更新数据库
+ $update = $db->update('on_links',[
+ 'click' => $click
+ ],[
+ 'id' => $id
+ ]);
+ //如果更新成功
+ if($update) {
+ //进行header跳转
+ header('location:'.$link['url']);
+ exit;
+ }
+}
+//如果已经成功登录,直接跳转
+elseif( is_login() ) {
+ //增加link.id的点击次数
+ $click = $link['click'] + 1;
+ //更新数据库
+ $update = $db->update('on_links',[
+ 'click' => $click
+ ],[
+ 'id' => $id
+ ]);
+ //如果更新成功
+ if($update) {
+ //进行header跳转
+ header('location:'.$link['url']);
+ exit;
+ }
+}
+//其它情况则没有权限
+else{
+ exit('无权限!');
+}
\ No newline at end of file
diff --git a/controller/index.html b/controller/index.html
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/controller/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/controller/index.php b/controller/index.php
new file mode 100644
index 0000000..5f4c4ce
--- /dev/null
+++ b/controller/index.php
@@ -0,0 +1,81 @@
+select('on_categorys','*',[
+ "ORDER" => ["weight" => "DESC"]
+ ]);
+ //根据category id查询链接
+ function get_links($fid) {
+ global $db;
+ $fid = intval($fid);
+ $links = $db->select('on_links','*',[
+ 'fid' => $fid,
+ 'ORDER' => ["weight" => "DESC"]
+ ]);
+ return $links;
+ }
+}
+//如果没有登录,只获取公有链接
+else{
+ //查询分类目录
+ $categorys = $db->select('on_categorys','*',[
+ "property" => 0,
+ "ORDER" => ["weight" => "DESC"]
+ ]);
+ //根据category id查询链接
+ function get_links($fid) {
+ global $db;
+ $fid = intval($fid);
+ $links = $db->select('on_links','*',[
+ 'fid' => $fid,
+ 'property' => 0,
+ 'ORDER' => ["weight" => "DESC"]
+ ]);
+ return $links;
+ }
+}
+
+
+//获取访客IP
+function getIP() {
+ if (getenv('HTTP_CLIENT_IP')) {
+ $ip = getenv('HTTP_CLIENT_IP');
+ }
+ elseif (getenv('HTTP_X_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_X_FORWARDED_FOR');
+ }
+ elseif (getenv('HTTP_X_FORWARDED')) {
+ $ip = getenv('HTTP_X_FORWARDED');
+ }
+ elseif (getenv('HTTP_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_FORWARDED_FOR');
+ }
+ elseif (getenv('HTTP_FORWARDED')) {
+ $ip = getenv('HTTP_FORWARDED');
+ }
+ else {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ return $ip;
+ }
+//判断用户是否已经登录
+function is_login(){
+ $key = md5(USER.PASSWORD.getIP().'onenav');
+ //获取session
+ $session = $_COOKIE['key'];
+ //如果已经成功登录
+ if($session == $key) {
+ return true;
+ }
+ else{
+ return false;
+ }
+}
+// 载入前台首页模板
+require('templates/'.TEMPLATE.'/index.php');
+?>
\ No newline at end of file
diff --git a/controller/login.php b/controller/login.php
new file mode 100644
index 0000000..6890712
--- /dev/null
+++ b/controller/login.php
@@ -0,0 +1,83 @@
+ 0,
+ 'msg' => 'successful'
+ ];
+ }
+ else{
+ $data = [
+ 'code' => -1012,
+ 'err_msg' => '用户名或密码错误!'
+ ];
+
+
+ }
+ exit(json_encode($data));
+}
+//如果cookie的值和计算的key不一致,则没有权限
+
+
+// if ( ($_SERVER['PHP_AUTH_PW'] !== $password) || ($_SERVER['PHP_AUTH_USER'] !== $username) ){
+// header('WWW-Authenticate: Basic realm="Please verify."');
+// header('HTTP/1.0 401 Unauthorized');
+// exit("认证失败!
");
+// }
+// else{
+
+// $key = md5($username.$password.$ip.'onenav');
+// //设置cookie
+// setcookie("key", $key, time()+7 * 24 * 60 * 60,"/");
+// header('location:index.php?c=admin');
+// }
+
+//获取访客IP
+function getIP() {
+if (getenv('HTTP_CLIENT_IP')) {
+$ip = getenv('HTTP_CLIENT_IP');
+}
+elseif (getenv('HTTP_X_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_X_FORWARDED_FOR');
+}
+ elseif (getenv('HTTP_X_FORWARDED')) {
+ $ip = getenv('HTTP_X_FORWARDED');
+}
+elseif (getenv('HTTP_FORWARDED_FOR')) {
+$ip = getenv('HTTP_FORWARDED_FOR');
+}
+elseif (getenv('HTTP_FORWARDED')) {
+$ip = getenv('HTTP_FORWARDED');
+}
+else {
+ $ip = $_SERVER['REMOTE_ADDR'];
+}
+ return $ip;
+}
+
+
+// 载入后台登录模板
+require('templates/admin/login.php');
\ No newline at end of file
diff --git a/db/.htaccess b/db/.htaccess
new file mode 100644
index 0000000..baa56e5
--- /dev/null
+++ b/db/.htaccess
@@ -0,0 +1,2 @@
+order allow,deny
+deny from all
\ No newline at end of file
diff --git a/db/index.html b/db/index.html
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/db/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/db/main.sql b/db/main.sql
new file mode 100644
index 0000000..b15a265
--- /dev/null
+++ b/db/main.sql
@@ -0,0 +1,76 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server : Onenav
+ Source Server Type : SQLite
+ Source Server Version : 3030001
+ Source Schema : main
+
+ Target Server Type : SQLite
+ Target Server Version : 3030001
+ File Encoding : 65001
+
+ Date: 11/12/2020 22:48:17
+*/
+
+PRAGMA foreign_keys = false;
+
+-- ----------------------------
+-- Table structure for on_categorys
+-- ----------------------------
+DROP TABLE IF EXISTS "on_categorys";
+CREATE TABLE "on_categorys" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "name" TEXT(32) NOT NULL,
+ "add_time" TEXT(10) NOT NULL,
+ "up_time" TEXT(10) DEFAULT '',
+ "weight" integer(3) NOT NULL DEFAULT 0,
+ "property" integer(1) NOT NULL DEFAULT 0,
+ "description" TEXT(128) DEFAULT ''
+);
+
+-- ----------------------------
+-- Records of on_categorys
+-- ----------------------------
+
+-- ----------------------------
+-- Table structure for on_links
+-- ----------------------------
+DROP TABLE IF EXISTS "on_links";
+CREATE TABLE "on_links" (
+ "id" INTEGER NOT NULL,
+ "fid" INTEGER(5) NOT NULL,
+ "title" TEXT(64) NOT NULL,
+ "url" TEXT(256) NOT NULL,
+ "description" TEXT(256),
+ "add_time" TEXT(10) NOT NULL,
+ "up_time" TEXT(10),
+ "weight" integer(3) NOT NULL DEFAULT 0,
+ "property" integer(1) NOT NULL DEFAULT 0,
+ "click" integer NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Records of on_links
+-- ----------------------------
+
+-- ----------------------------
+-- Table structure for sqlite_sequence
+-- ----------------------------
+DROP TABLE IF EXISTS "sqlite_sequence";
+CREATE TABLE "sqlite_sequence" (
+ "name",
+ "seq"
+);
+
+-- ----------------------------
+-- Records of sqlite_sequence
+-- ----------------------------
+INSERT INTO "sqlite_sequence" VALUES ('on_categorys', 0);
+
+-- ----------------------------
+-- Auto increment value for on_categorys
+-- ----------------------------
+
+PRAGMA foreign_keys = true;
diff --git a/db/onenav.simple.db3 b/db/onenav.simple.db3
new file mode 100644
index 0000000..248afcc
Binary files /dev/null and b/db/onenav.simple.db3 differ
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..3ab7e13
Binary files /dev/null and b/favicon.ico differ
diff --git a/functions/.htaccess b/functions/.htaccess
new file mode 100644
index 0000000..baa56e5
--- /dev/null
+++ b/functions/.htaccess
@@ -0,0 +1,2 @@
+order allow,deny
+deny from all
\ No newline at end of file
diff --git a/functions/helper.php b/functions/helper.php
new file mode 100644
index 0000000..6f10d92
--- /dev/null
+++ b/functions/helper.php
@@ -0,0 +1,37 @@
+配置文件不存在,请将config.simple.php复制一份并命名为config.php');
+}
+
+//载入配置文件
+require("./config.php");
+
+//根据不同的请求载入不同的方法
+//如果没有请求控制器
+if((!isset($c)) || ($c == '')){
+ //载入主页
+ include_once("./controller/index.php");
+
+}
+
+else{
+ include_once("./controller/".$c.'.php');
+}
\ No newline at end of file
diff --git a/templates/admin/add_category.php b/templates/admin/add_category.php
new file mode 100644
index 0000000..a9b2624
--- /dev/null
+++ b/templates/admin/add_category.php
@@ -0,0 +1,49 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin/add_link.php b/templates/admin/add_link.php
new file mode 100644
index 0000000..683b0e9
--- /dev/null
+++ b/templates/admin/add_link.php
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin/category_list.php b/templates/admin/category_list.php
new file mode 100644
index 0000000..bf4480c
--- /dev/null
+++ b/templates/admin/category_list.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin/edit_category.php b/templates/admin/edit_category.php
new file mode 100644
index 0000000..c18f254
--- /dev/null
+++ b/templates/admin/edit_category.php
@@ -0,0 +1,55 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin/edit_link.php b/templates/admin/edit_link.php
new file mode 100644
index 0000000..324ef94
--- /dev/null
+++ b/templates/admin/edit_link.php
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin/footer.php b/templates/admin/footer.php
new file mode 100644
index 0000000..0a77d69
--- /dev/null
+++ b/templates/admin/footer.php
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+