mirror of
https://github.com/tznb1/TwoNav.git
synced 2025-08-10 08:51:49 +00:00
v2.0.18-20230510
This commit is contained in:
11
README.md
11
README.md
@@ -1,10 +1,8 @@
|
||||
TwoNav 是一款开源免费的书签(导航)管理程序,使用PHP + SQLite 3开发,界面简洁,安装简单,使用方便。TwoNav可帮助你你将浏览器书签集中式管理,解决跨设备、跨平台、跨浏览器之间同步和访问困难问题,做到一处部署,随处访问。
|
||||
TwoNav 是一款开源免费的书签(导航)管理程序,界面简洁,安装简单,使用方便。TwoNav可帮助你将浏览器书签集中式管理,解决跨设备、跨平台、跨浏览器之间同步和访问困难问题,做到一处部署,随处访问。
|
||||
|
||||
- **演示站**: [http://two.lm21.top](http://two.lm21.top)
|
||||
- **仅供体验,定期清理数据** 账号密码`admin`
|
||||
|
||||
### 内测版转正
|
||||
* 删除安装目录下`system`文件夹后在解压覆盖
|
||||
|
||||
### 相关文档
|
||||
* [安装教程](https://gitee.com/tznb/TwoNav/wikis/pages?sort_id=7968668&doc_id=3767990) | [使用说明](https://gitee.com/tznb/TwoNav/wikis) | [下载TwoNav](https://gitee.com/tznb/TwoNav/releases)
|
||||
@@ -18,7 +16,12 @@ TwoNav 是一款开源免费的书签(导航)管理程序,使用PHP + SQLi
|
||||
- QQ: 271152681
|
||||
- QQ群: 695720839
|
||||
|
||||
### 运行环境
|
||||
* PHP: 7.3 - 8.2
|
||||
* 数据库: SQLite3 或 MySQL > 5.6.0
|
||||
|
||||
### 功能特色
|
||||
* 支持
|
||||
* 支持后台管理
|
||||
* 支持私有链接
|
||||
* 支持加密链接
|
||||
@@ -34,4 +37,4 @@ TwoNav 是一款开源免费的书签(导航)管理程序,使用PHP + SQLi
|
||||
* 支持Chromium内核的[浏览器扩展]
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
@@ -159,8 +159,14 @@ function write_category(){
|
||||
msg(-1,'加密组不存在');
|
||||
}
|
||||
|
||||
//长度检测
|
||||
if(strlen(htmlspecialchars($_POST['name'],ENT_QUOTES)) > 128 ){
|
||||
msg(-1,'分类名称长度超限');
|
||||
}
|
||||
if(strlen(htmlspecialchars($_POST['description'],ENT_QUOTES)) > 128 ){
|
||||
msg(-1,'分类描述长度超限');
|
||||
}
|
||||
//取最大CID
|
||||
//$cid = intval(max_db('user_categorys','cid',['uid'=>UID])) +1;
|
||||
$cid = get_maxid('category_id');
|
||||
//插入数据库
|
||||
insert_db('user_categorys',[
|
||||
@@ -186,17 +192,14 @@ function write_category(){
|
||||
if($_POST['cid'] == $_POST['fid']){
|
||||
msg(-1,'父分类不能是自己');
|
||||
}
|
||||
|
||||
//查CID是否存在
|
||||
if(!get_db('user_categorys','cid',['uid'=>UID ,"cid" => intval($_POST['cid'])])){
|
||||
msg(-1,'分类不存在');
|
||||
}
|
||||
|
||||
//分类名查重(排除自身)
|
||||
if(get_db('user_categorys','cid',['uid'=>UID,'cid[!]'=>intval($_POST['cid']),"name" => $_POST['name']])){
|
||||
msg(-1,'分类名称已存在');
|
||||
}
|
||||
|
||||
//父分类不能是二级分类
|
||||
if(intval($_POST['fid']) !=0 && get_db('user_categorys','fid',['uid'=>UID ,"cid" => intval($_POST['fid']) ]) !=0 ){
|
||||
msg(-1,'父分类不能是二级分类');
|
||||
@@ -205,16 +208,22 @@ function write_category(){
|
||||
if( $_POST['fid']!=0 && count_db('user_categorys',['uid'=>UID,'fid'=>$_POST['cid']])>0){
|
||||
msg(-1,'该分类下已存在子分类!');
|
||||
}
|
||||
|
||||
//查父分类是否存在
|
||||
if( $_POST['fid'] !=0 && !get_db('user_categorys','cid',['uid'=>UID ,"cid" => intval($_POST['fid'])])){
|
||||
msg(-1,'父分类不存在');
|
||||
}
|
||||
|
||||
//加密组pid是否存在
|
||||
if(intval($_POST['pwd_id']) !=0 && empty(get_db('user_pwd_group','pid',['uid'=>UID ,"pid" => intval($_POST['pwd_id'])]))){
|
||||
msg(-1,'加密组不存在');
|
||||
}
|
||||
//长度检测
|
||||
if(strlen(htmlspecialchars($_POST['name'],ENT_QUOTES)) > 128 ){
|
||||
msg(-1,'分类名称长度超限');
|
||||
}
|
||||
if(strlen(htmlspecialchars($_POST['description'],ENT_QUOTES)) > 128 ){
|
||||
msg(-1,'分类描述长度超限');
|
||||
}
|
||||
|
||||
//更新数据
|
||||
$data = [
|
||||
'fid'=>$_POST['fid'],
|
||||
@@ -363,22 +372,16 @@ function write_link(){
|
||||
$description = empty($_POST['description']) ? '' : $_POST['description'];
|
||||
$property = empty($_POST['property']) ? 0 : 1;
|
||||
//检测链接是否合法
|
||||
check_link($fid,$title,$url);
|
||||
check_link($fid,$title,$url,$_POST['url_standby']);
|
||||
//检查链接是否已存在
|
||||
if(get_db('user_links','lid',['uid'=>UID ,"url" => $url])){
|
||||
msg(-1,'链接已存在!');
|
||||
}
|
||||
//备用链接检测
|
||||
if(!empty($_POST['url_standby'])){
|
||||
foreach ($_POST['url_standby'] as $key => $url_standby){
|
||||
//尝试匹配Markdown语法的URL,如果没有则认为直接输入
|
||||
if(preg_match('/\[(.*?)\]\((.*?)\)/', $url_standby, $match)){
|
||||
check_link($fid,$title,$match[2]);
|
||||
}else{
|
||||
check_link($fid,$title,$url_standby);
|
||||
}
|
||||
}
|
||||
//描述长度检测
|
||||
if(strlen($description) > 128 || strlen(htmlspecialchars($description,ENT_QUOTES)) > 128 ){
|
||||
msg(-1,'描述长度超限');
|
||||
}
|
||||
|
||||
//取最大链接ID
|
||||
$lid = get_maxid('link_id');
|
||||
//图标处理
|
||||
@@ -526,22 +529,15 @@ function write_link(){
|
||||
$description = empty($_POST['description']) ? '' : $_POST['description'];
|
||||
$property = empty($_POST['property']) ? 0 : 1;
|
||||
//检测链接是否合法
|
||||
check_link($fid,$title,$url);
|
||||
//检查链接是否已存在
|
||||
if(get_db('user_links','lid',['uid'=>UID ,'lid[!]'=>$lid, "url" => $url])){msg(-1,'链接已存在!');}
|
||||
//检查链接ID是否存在
|
||||
if(!get_db('user_links','lid',['uid'=>UID ,'lid'=>$lid])){msg(-1,'链接ID不存在!');}
|
||||
//备用链接检测
|
||||
if(!empty($_POST['url_standby'])){
|
||||
foreach ($_POST['url_standby'] as $key => $url_standby){
|
||||
//尝试匹配Markdown语法的URL,如果没有则认为直接输入
|
||||
if(preg_match('/\[(.*?)\]\((.*?)\)/', $url_standby, $match)){
|
||||
check_link($fid,$title,$match[2]);
|
||||
}else{
|
||||
check_link($fid,$title,$url_standby);
|
||||
}
|
||||
}
|
||||
check_link($fid,$title,$url,$_POST['url_standby']);
|
||||
//描述长度检测
|
||||
if(strlen($description) > 128 || strlen(htmlspecialchars($description,ENT_QUOTES)) > 128 ){
|
||||
msg(-1,'描述长度超限');
|
||||
}
|
||||
//检查链接是否已存在
|
||||
if(has_db('user_links',['uid'=>UID ,'lid[!]'=>$lid, "url" => $url])){msg(-1,'链接已存在!');}
|
||||
//检查链接ID是否存在
|
||||
if(!has_db('user_links',['uid'=>UID ,'lid'=>$lid])){msg(-1,'链接ID不存在!');}
|
||||
|
||||
$data = [
|
||||
'fid' => $fid,
|
||||
@@ -948,6 +944,7 @@ function write_site_setting(){
|
||||
'keywords'=>['empty'=>true],
|
||||
'description'=>['empty'=>true],
|
||||
'link_model'=>['v'=>['direct','Privacy','Privacy_js','Privacy_meta','301','302','Transition'],'msg'=>'链接模式参数错误'],
|
||||
'main_link_priority'=>['int'=>true,'min'=>0,'max'=>1,'msg'=>'主链优先参数错误'],
|
||||
'link_icon'=>['int'=>true,'min'=>0,'max'=>10,'msg'=>'链接图标参数错误'],
|
||||
'site_icon'=>['empty'=>true],
|
||||
'top_link'=>['int'=>true,'min'=>0,'max'=>20,'msg'=>'热门链接参数错误'],
|
||||
@@ -1454,8 +1451,8 @@ function read_data(){
|
||||
$php_version = floatval(PHP_VERSION);
|
||||
$log .= "PHP版本:{$php_version}\n";
|
||||
$log .= "Web版本:{$_SERVER['SERVER_SOFTWARE']}\n";
|
||||
if( ( $php_version < 7.3 ) || ( $php_version > 8.1 ) ) {
|
||||
$log .= "PHP版本:不满足要求,需要7.3 <= PHP <= 8.1 )\n";
|
||||
if( ( $php_version < 7.3 ) || ( $php_version > 8.2 ) ) {
|
||||
$log .= "PHP版本:不满足要求,支持范围7.3 - 8.2 )\n";
|
||||
}
|
||||
//获取加载的模块
|
||||
$ext = get_loaded_extensions();
|
||||
|
||||
@@ -131,15 +131,26 @@ if($global_config['link_extend'] == 1 && check_purview('link_extend',1) && in_ar
|
||||
//如果存在备用链接,则强制载入过渡页
|
||||
if(!empty($link['url_standby'])) {
|
||||
$link['url_standby'] = unserialize($link['url_standby']);
|
||||
require $transit_path;
|
||||
exit;
|
||||
//主链优先模式
|
||||
if($site['main_link_priority'] == 1){
|
||||
$code = get_http_code($link['url'],3);
|
||||
if(in_array(intval($code),[200,301,302]) ){
|
||||
$site['link_model'] = $site['link_model'] == 'direct' ? '302' : $site['link_model'];
|
||||
}else{
|
||||
require $transit_path;
|
||||
exit;
|
||||
}
|
||||
}else{
|
||||
require $transit_path;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($site['link_model'] == '302'){ //302重定向
|
||||
if ($site['link_model'] == '302'){ //302重定向(临时)
|
||||
header("HTTP/1.1 302 Moved Permanently");
|
||||
header("Location: ".$link['url']);
|
||||
exit;
|
||||
}elseif($site['link_model'] == '301'){ //301重定向
|
||||
}elseif($site['link_model'] == '301'){ //301重定向(永久)
|
||||
header("HTTP/1.1 301 Moved Permanently");
|
||||
header("Location: ".$link['url']);
|
||||
exit;
|
||||
|
||||
@@ -32,7 +32,7 @@ function check_env() {
|
||||
$php_version = floatval(PHP_VERSION); //获取PHP版本
|
||||
|
||||
if( ( $php_version < 7.3 ) || ( $php_version > 8.2 ) ) {
|
||||
exit("当前PHP版本{$php_version}不满足要求,需要7.3 <= PHP <= 8.2");
|
||||
exit("当前PHP版本{$php_version}不满足要求,支持范围7.3 - 8.2");
|
||||
}
|
||||
|
||||
//检查是否支持pdo_sqlite
|
||||
|
||||
@@ -233,18 +233,42 @@ function echo_pwds(){
|
||||
}
|
||||
}
|
||||
//检查链接
|
||||
function check_link($fid,$title,$url,$url_standby=''){
|
||||
global $db;
|
||||
function check_link($fid,$title,$url,$url_standby_s=''){
|
||||
$pattern = "/^(http:\/\/|https:\/\/|ftp:\/\/|ftps:\/\/|sftp:\/\/|magnet:?|ed2k:\/\/|thunder:\/\/|tcp:\/\/|udp:\/\/|rtsp:\/\/).+/";
|
||||
if (empty($fid)) msg(-1,'分类id(fid)不能为空');
|
||||
if (empty($title)) msg(-1,'名称不能为空');
|
||||
if (strlen($title) > 64 ) msg(-1,'名称长度超限');
|
||||
if (strlen(htmlspecialchars($title,ENT_QUOTES)) > 128 ) msg(-1,'名称长度超限-2');
|
||||
if (!has_db('user_categorys',['uid'=>UID ,"cid" => $fid])) msg(-1,'分类不存在');
|
||||
//主链接检测
|
||||
if (empty($url)) msg(-1,'URL不能为空');
|
||||
if (!preg_match($pattern,$url)) msg(-1,'URL无效');
|
||||
if (strlen($url) > 1024 ) msg(-1,'URL长度超限');
|
||||
if (check_xss($url)) msg(-1,'URL存在非法字符');
|
||||
|
||||
//备用链接检测
|
||||
if(!empty($url_standby_s)){
|
||||
foreach ($url_standby_s as $key => $url_standby){
|
||||
//尝试匹配Markdown语法的URL,如果没有则认为直接输入
|
||||
if(preg_match('/\[(.*?)\]\((.*?)\)/', $url_standby, $match)){
|
||||
if (empty($match[1])) msg(-1,'备用链接名称不能为空,若不需要名称请直接输入URL');
|
||||
if (strlen($match[1]) > 64 ) msg(-1,'备用链接名称长度超限');
|
||||
if (strlen(htmlspecialchars($match[1],ENT_QUOTES)) > 128 ) msg(-1,'备用链接名称长度超限-2');
|
||||
$url = $match[2];
|
||||
}else{
|
||||
$url = $url_standby;
|
||||
}
|
||||
|
||||
if(!preg_match($pattern,$url)){
|
||||
msg(-1,'备选URL无效');
|
||||
}elseif(strlen($url) > 1024){
|
||||
msg(-1,'备选URL长度超限');
|
||||
}elseif(check_xss($url)){
|
||||
msg(-1,'备用URL存在非法字符');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($fid)) {msg(-1007,'分类id(fid)不能为空!');}
|
||||
if(!get_db('user_categorys','cid',['uid'=>UID ,"cid" => $fid])){msg(-1,'分类不存在');}
|
||||
if (empty($title)){msg(-1008,'标题不能为空!');}
|
||||
if (empty($url)){msg(-1009,'URL不能为空!');}
|
||||
if (check_xss($url)){msg(-1010,'URL存在非法字符!');}
|
||||
if (check_xss($url_standby)){msg(-1010,'备用URL存在非法字符!');}
|
||||
if (!preg_match($pattern,$url)){msg(-1010,'URL无效!');}
|
||||
if ( ( !empty($url_standby) ) && ( !preg_match($pattern,$url_standby) ) ) {msg(-1010,'备选URL无效!');}
|
||||
return true;
|
||||
}
|
||||
//获取版本号
|
||||
@@ -473,13 +497,13 @@ function Get_IP() {
|
||||
}
|
||||
|
||||
//获取URL状态码
|
||||
function get_http_code($url) {
|
||||
function get_http_code($url,$TIMEOUT = 10) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_HEADER, 1);
|
||||
curl_setopt($curl, CURLOPT_NOBODY, true);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, $TIMEOUT);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36');
|
||||
$data = curl_exec($curl);
|
||||
$return = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
@@ -51,7 +51,7 @@ if(empty($c) || in_array($c,['index','click'])){
|
||||
}elseif ($icon ==1){
|
||||
return('./favicon/index2.php?url='.$link['real_url']);
|
||||
}elseif($icon ==2){
|
||||
return('//favicon.rss.ink/v1/'.base64($link['real_url']));
|
||||
return('//favicon.png.pub/v1/'.base64($link['real_url']));
|
||||
}elseif($icon ==4){
|
||||
return('//api.15777.cn/get.php?url='.$link['real_url']);
|
||||
}elseif($icon ==5){
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.17-20230428
|
||||
v2.0.18-20230510
|
||||
@@ -19,27 +19,12 @@ layui.use(['form','table','dropdown','miniTab'], function () {
|
||||
layer.alert("获取分类数据失败,请刷新重试",{icon:5,title:'错误',anim: 2,closeBtn: 0,btn: ['刷新页面']},function () {location.reload();});
|
||||
return;
|
||||
}else{
|
||||
$("#fid").empty();
|
||||
$("#fid").append('<option value="0" selected="">全部</option>');
|
||||
$("#fid").append('<optgroup label="用户分类">');
|
||||
for( key in data.data ){
|
||||
if(data.data[key].fid == 0){
|
||||
$("#fid").append("<option value=\""+key+"\">"+data.data[key].name+"</option>");
|
||||
$("#batch_category_fid").append("<option value=\""+key+"\">"+data.data[key].name+"</option>");
|
||||
}else{
|
||||
$("#fid").append("<option value=\""+key+"\"> "+data.data[key].name+"</option>");
|
||||
$("#batch_category_fid").append("<option value=\""+key+"\"> "+data.data[key].name+"</option>");
|
||||
}
|
||||
}
|
||||
$("#fid").append('</optgroup>');
|
||||
layui.form.render("select");
|
||||
categorys = data.data;//赋值分类数据
|
||||
renderTable2();//开始渲染表格
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var cols=[[ //表头
|
||||
{type:'checkbox'} //开启复选框
|
||||
,{field: 'lid', title: 'ID', width:80, sort: true,hide:false}
|
||||
|
||||
@@ -52,13 +52,24 @@
|
||||
<div class="layui-form-mid layui-word-aux">直连模式无法统计点击数且无法使用备用链接,但它响应最快!</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" title="存在备用链接并且链接模式为隐私保护或302重定向时生效">主链优先</label>
|
||||
<div class="layui-input-inline" >
|
||||
<select name="main_link_priority" >
|
||||
<option value="0" selected>关闭</option>
|
||||
<option value="1">开启</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">特定条件下生效,主链接可用则直接跳转反之进入过渡页,用法参照帮助文档</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">链接图标</label>
|
||||
<div class="layui-input-inline" >
|
||||
<select name="link_icon">
|
||||
<option value="0" selected>离线图标</option>
|
||||
<!--<option value="1" >本地服务(支持缓存)</option>-->
|
||||
<option value="2" >favicon.rss.ink (小图标)</option>
|
||||
<option value="2" >favicon.png.pub (小图标)</option>
|
||||
<option value="4" >api.15777.cn</option>
|
||||
<option value="5" >favicon.cccyun.cc</option>
|
||||
<option value="6" >api.iowen.cn</option>
|
||||
@@ -98,11 +109,11 @@
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">输出上限</label>
|
||||
<label class="layui-form-label">链接数量</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="number" name="max_link" class="layui-input" value="0" placeholder="输入范围: 0-100" lay-verify="required">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">限制每个分类下显示多少链接,0表示不限制</div>
|
||||
<div class="layui-form-mid layui-word-aux">限制首页每个分类下显示多少链接,0表示不限制</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<label class="layui-form-label " style="width:60px;padding-left: 5px;padding-right: 5px;">分类筛选:</label>
|
||||
<div class="layui-input-inline">
|
||||
<select id="fid" lay-filter="fid" name="categorys" lay-search>
|
||||
<?php echo_category(true); ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,6 +85,7 @@
|
||||
<label class="layui-form-label">父级分类</label>
|
||||
<div class="layui-input-block">
|
||||
<select id="batch_category_fid">
|
||||
<?php echo_category(true); ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
<body>
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main" style=" margin-left: 20px;">
|
||||
<li class="layui-timeline-item">
|
||||
<i class="layui-icon layui-timeline-axis"></i>
|
||||
<div class="layui-timeline-content layui-text">
|
||||
<h4 class="layui-timeline-title">v2.0.18-20230510</h4>
|
||||
<ul>
|
||||
<li>[新增] 限制链接和分类的名称描述长度为128个字符 ( 注:一个汉字≈3个字符,数字/字母=1个字符 )</li>
|
||||
<li>[新增] 站点设置 > 主链优先 ( 对存在备用链接的书签有效,主链接可用则直接跳转反之进入过渡页,具体用法参照文档 )</li>
|
||||
<li>[修复] PHP8.2安装时提示不支持 ( 实际支持 )</li>
|
||||
<li>[优化] 链接列表选择分类时按分类层级显示</li>
|
||||
<li>[变更] 链接图标域名由favicon.rss.ink更换为favicon.png.pub ( 由xiaoz.me提供 )</li>
|
||||
<li>[变更] 站点设置 > 输出上限改为链接数量</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="layui-timeline-item">
|
||||
<i class="layui-icon layui-timeline-axis"></i>
|
||||
<div class="layui-timeline-content layui-text">
|
||||
|
||||
Reference in New Issue
Block a user