|
@ -0,0 +1,40 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="text-center p-block puock-text">
|
||||
<h3 class="mt20">你访问的资源不存在!</h3>
|
||||
<h5 class="mt20"><span id="time-count-down"></span>秒后即将跳转至首页</h5>
|
||||
<div class="text-center mt20">
|
||||
<a class="a-link" href="<?php $this->options->siteUrl(); ?>"><i class="fa fa-home"></i> 返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var countdown = 5; // 设置倒计时时间(秒)
|
||||
var countdownElement = document.getElementById('time-count-down');
|
||||
|
||||
// 更新倒计时显示
|
||||
function updateCountdown() {
|
||||
countdownElement.textContent = countdown;
|
||||
countdown--;
|
||||
|
||||
if (countdown < 0) {
|
||||
// 倒计时结束,跳转到首页
|
||||
window.location.href = "<?php $this->options->siteUrl(); ?>";
|
||||
} else {
|
||||
// 继续倒计时
|
||||
setTimeout(updateCountdown, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始倒计时
|
||||
updateCountdown();
|
||||
});
|
||||
</script>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,24 @@
|
|||
## 说明
|
||||
|
||||
### 主题介绍
|
||||
|
||||
源主题为`wordpress`主题`puock`,现移植到`Typecho`平台。
|
||||
|
||||
### 主题特点
|
||||
- 主题简洁,适合个人博客使用
|
||||
- 支持`Typecho`的所有功能
|
||||
|
||||
### 主题预览
|
||||
|
||||

|
||||
|
||||
### 使用说明
|
||||
|
||||
1. 将主题文件夹放入`Typecho`的`usr/themes`目录下。
|
||||
2. 在后台管理界面启用该主题。
|
||||
3. 根据需要自定义主题设置。
|
||||
4. 主题部分功能需要安装插件`Puock`。项目地址: [Puock Plugin](https://github.com/jkjoy/typecho-plugin-puock)
|
||||
|
||||
### 主题配置
|
||||
|
||||
- 全部标签页面需要启用php的 `iconv` 扩展
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
||||
$this->need('header.php');
|
||||
?>
|
||||
<div class="row row-cols-1">
|
||||
<div class="col-lg-8 col-md-12 animated fadeInLeft ">
|
||||
<div class="animated fadeInLeft ">
|
||||
<div> <!--文章列表-->
|
||||
<div id="posts">
|
||||
<div class=" mr-0 ml-0">
|
||||
<?php while ($this->next()): ?>
|
||||
<?php
|
||||
$coverImage = getPostCover($this->content, $this->cid);
|
||||
?>
|
||||
<article class="block card-plain post-item p-block post-item-list">
|
||||
<div class="thumbnail">
|
||||
<a class="t-sm ww" href="<?php $this->permalink() ?>">
|
||||
<img title="<?php $this->title() ?>" alt="<?php $this->title() ?>" src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>' class='lazy' data-src='<?php echo $coverImage; ?>' />
|
||||
</a>
|
||||
</div>
|
||||
<div class="post-info">
|
||||
<div class="info-top">
|
||||
<h2 class="info-title">
|
||||
<?php if (isset($this->isSticky) && $this->isSticky): ?><?php echo $this->stickyHtml; ?><?php endif; ?>
|
||||
<?php foreach($this->categories as $category): ?>
|
||||
<a class="badge d-none d-md-inline-block bg-primary ahfff" href="<?php echo $category['permalink']; ?>">
|
||||
<i class="fa-regular fa-folder-open"></i> <?php echo $category['name']; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<a class="a-link" title="<?php $this->title() ?>" href="<?php $this->permalink() ?>"><?php $this->title() ?></a>
|
||||
</h2>
|
||||
<div class="info-meta c-sub text-2line d-none d-md-block"><?php $this->excerpt(200, '...'); ?></div>
|
||||
</div>
|
||||
<div class="info-footer w-100">
|
||||
<div>
|
||||
<span class="t-sm c-sub">
|
||||
<span class="mr-2">
|
||||
<i class="fa-regular fa-eye mr-1"></i>
|
||||
<span class="view">浏览:<?php get_post_view($this) ?></span>
|
||||
<span class="t-sm d-none d-sm-inline-block">次阅读</span>
|
||||
</span>
|
||||
<a class="c-sub-a" href="<?php $this->permalink() ?>#comments">
|
||||
<i class="fa-regular fa-comment mr-1"></i><?php $this->commentsNum('0', '1', '%d'); ?>
|
||||
<span class="t-sm d-none d-sm-inline-block">个评论</span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<?php foreach($this->categories as $category): ?>
|
||||
<a class="c-sub-a t-sm ml-md-2 line-h-20 d-inline-block d-md-none" href="<?php echo $category['permalink']; ?>">
|
||||
<i class="fa-regular fa-folder-open"></i> <?php echo $category['name']; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<span class="t-sm ml-md-2 c-sub line-h-20 d-none d-md-inline-block">
|
||||
<i class="fa-regular fa-clock"></i> <?php $this->date(); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="title-l-c bg-primary"></span>
|
||||
</article>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
<div class="mt20 p-flex-s-right" data-no-instant>
|
||||
<?php $this->pageNav('«', '»', 1, '...', array(
|
||||
'wrapTag' => 'ul',
|
||||
'wrapClass' => 'pagination comment-ajax-load',
|
||||
'itemTag' => 'li',
|
||||
'textTag' => 'span',
|
||||
'currentClass' => 'active',
|
||||
'prevClass' => 'prev',
|
||||
'nextClass' => 'next'
|
||||
)); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,165 @@
|
|||
Fonticons, Inc. (https://fontawesome.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Font Awesome Free License
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
The Font Awesome Free download is licensed under a Creative Commons
|
||||
Attribution 4.0 International License and applies to all icons packaged
|
||||
as SVG and JS file types.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Fonts: SIL OFL 1.1 License
|
||||
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
SIL OPEN FONT LICENSE
|
||||
Version 1.1 - 26 February 2007
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting — in part or in whole — any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2022 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Attribution
|
||||
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Brand Icons
|
||||
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
After Width: | Height: | Size: 662 KiB |
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="81px" height="81px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<rect x="19" y="19" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0s" calcMode="discrete"></animate>
|
||||
</rect><rect x="40" y="19" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.125s" calcMode="discrete"></animate>
|
||||
</rect><rect x="61" y="19" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.25s" calcMode="discrete"></animate>
|
||||
</rect><rect x="19" y="40" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.875s" calcMode="discrete"></animate>
|
||||
</rect><rect x="61" y="40" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.375s" calcMode="discrete"></animate>
|
||||
</rect><rect x="19" y="61" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.75s" calcMode="discrete"></animate>
|
||||
</rect><rect x="40" y="61" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.625s" calcMode="discrete"></animate>
|
||||
</rect><rect x="61" y="61" width="20" height="20" fill="#a9a9a9">
|
||||
<animate attributeName="fill" values="rgba(255, 255, 255, 0);#a9a9a9;#a9a9a9" keyTimes="0;0.125;1" dur="1s" repeatCount="indefinite" begin="0.5s" calcMode="discrete"></animate>
|
||||
</rect>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 996 B |
After Width: | Height: | Size: 702 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 871 B |
After Width: | Height: | Size: 980 B |
After Width: | Height: | Size: 837 B |
After Width: | Height: | Size: 727 B |
After Width: | Height: | Size: 816 B |
After Width: | Height: | Size: 695 B |
After Width: | Height: | Size: 760 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 904 B |
After Width: | Height: | Size: 881 B |
After Width: | Height: | Size: 921 B |
After Width: | Height: | Size: 880 B |
After Width: | Height: | Size: 640 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 866 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1007 B |
After Width: | Height: | Size: 646 B |
After Width: | Height: | Size: 683 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 509 B |
After Width: | Height: | Size: 806 B |
After Width: | Height: | Size: 683 B |
After Width: | Height: | Size: 595 B |
After Width: | Height: | Size: 946 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,488 @@
|
|||
"v4.1.5 Geetest Inc.";
|
||||
|
||||
(function (window) {
|
||||
"use strict";
|
||||
if (typeof window === 'undefined') {
|
||||
throw new Error('Geetest requires browser environment');
|
||||
}
|
||||
|
||||
var document = window.document;
|
||||
var Math = window.Math;
|
||||
var head = document.getElementsByTagName("head")[0];
|
||||
var TIMEOUT = 10000;
|
||||
|
||||
function _Object(obj) {
|
||||
this._obj = obj;
|
||||
}
|
||||
|
||||
_Object.prototype = {
|
||||
_each: function (process) {
|
||||
var _obj = this._obj;
|
||||
for (var k in _obj) {
|
||||
if (_obj.hasOwnProperty(k)) {
|
||||
process(k, _obj[k]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
_extend: function (obj){
|
||||
var self = this;
|
||||
new _Object(obj)._each(function (key, value){
|
||||
self._obj[key] = value;
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
var uuid = function () {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0;
|
||||
var v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
function Config(config) {
|
||||
var self = this;
|
||||
new _Object(config)._each(function (key, value) {
|
||||
self[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
Config.prototype = {
|
||||
apiServers: ['gcaptcha4.geetest.com','gcaptcha4.geevisit.com','gcaptcha4.gsensebot.com'],
|
||||
staticServers: ["static.geetest.com",'static.geevisit.com', "dn-staticdown.qbox.me"],
|
||||
protocol: 'http://',
|
||||
typePath: '/load',
|
||||
fallback_config: {
|
||||
bypass: {
|
||||
staticServers: ["static.geetest.com",'static.geevisit.com',"dn-staticdown.qbox.me"],
|
||||
type: 'bypass',
|
||||
bypass: '/v4/bypass.js'
|
||||
}
|
||||
},
|
||||
_get_fallback_config: function () {
|
||||
var self = this;
|
||||
if (isString(self.type)) {
|
||||
return self.fallback_config[self.type];
|
||||
} else {
|
||||
return self.fallback_config.bypass;
|
||||
}
|
||||
},
|
||||
_extend: function (obj) {
|
||||
var self = this;
|
||||
new _Object(obj)._each(function (key, value) {
|
||||
self[key] = value;
|
||||
})
|
||||
}
|
||||
};
|
||||
var isNumber = function (value) {
|
||||
return (typeof value === 'number');
|
||||
};
|
||||
var isString = function (value) {
|
||||
return (typeof value === 'string');
|
||||
};
|
||||
var isBoolean = function (value) {
|
||||
return (typeof value === 'boolean');
|
||||
};
|
||||
var isObject = function (value) {
|
||||
return (typeof value === 'object' && value !== null);
|
||||
};
|
||||
var isFunction = function (value) {
|
||||
return (typeof value === 'function');
|
||||
};
|
||||
var MOBILE = /Mobi/i.test(navigator.userAgent);
|
||||
|
||||
var callbacks = {};
|
||||
var status = {};
|
||||
|
||||
var random = function () {
|
||||
return parseInt(Math.random() * 10000) + (new Date()).valueOf();
|
||||
};
|
||||
|
||||
// bind 函数polify, 不带new功能的bind
|
||||
|
||||
var bind = function(target,context){
|
||||
if(typeof target !== 'function'){
|
||||
return;
|
||||
}
|
||||
var args = Array.prototype.slice.call(arguments,2);
|
||||
|
||||
if(Function.prototype.bind){
|
||||
return target.bind(context, args);
|
||||
}else {
|
||||
return function(){
|
||||
var _args = Array.prototype.slice.call(arguments);
|
||||
return target.apply(context,args.concat(_args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
var _isFunction = function(obj) {
|
||||
return typeof(obj) === 'function';
|
||||
};
|
||||
var _isObject = function(obj) {
|
||||
return obj === Object(obj);
|
||||
};
|
||||
var _isArray = function(obj) {
|
||||
return toString.call(obj) == '[object Array]';
|
||||
};
|
||||
var _isDate = function(obj) {
|
||||
return toString.call(obj) == '[object Date]';
|
||||
};
|
||||
var _isRegExp = function(obj) {
|
||||
return toString.call(obj) == '[object RegExp]';
|
||||
};
|
||||
var _isBoolean = function(obj) {
|
||||
return toString.call(obj) == '[object Boolean]';
|
||||
};
|
||||
|
||||
|
||||
function resolveKey(input){
|
||||
return input.replace(/(\S)(_([a-zA-Z]))/g, function(match, $1, $2, $3){
|
||||
return $1 + $3.toUpperCase() || "";
|
||||
})
|
||||
}
|
||||
|
||||
function camelizeKeys(input, convert){
|
||||
if(!_isObject(input) || _isDate(input) || _isRegExp(input) || _isBoolean(input) || _isFunction(input)){
|
||||
return convert ? resolveKey(input) : input;
|
||||
}
|
||||
|
||||
if(_isArray(input)){
|
||||
var temp = [];
|
||||
for(var i = 0; i < input.length; i++){
|
||||
temp.push(camelizeKeys(input[i]));
|
||||
}
|
||||
|
||||
}else {
|
||||
var temp = {};
|
||||
for(var prop in input){
|
||||
if(input.hasOwnProperty(prop)){
|
||||
temp[camelizeKeys(prop, true)] = camelizeKeys(input[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
var loadScript = function (url, cb, timeout) {
|
||||
var script = document.createElement("script");
|
||||
script.charset = "UTF-8";
|
||||
script.async = true;
|
||||
|
||||
// 对geetest的静态资源添加 crossOrigin
|
||||
if ( /static\.geetest\.com/g.test(url)) {
|
||||
script.crossOrigin = "anonymous";
|
||||
}
|
||||
|
||||
script.onerror = function () {
|
||||
cb(true);
|
||||
// 错误触发了,超时逻辑就不用了
|
||||
loaded = true;
|
||||
};
|
||||
var loaded = false;
|
||||
script.onload = script.onreadystatechange = function () {
|
||||
if (!loaded &&
|
||||
(!script.readyState ||
|
||||
"loaded" === script.readyState ||
|
||||
"complete" === script.readyState)) {
|
||||
|
||||
loaded = true;
|
||||
setTimeout(function () {
|
||||
cb(false);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
script.src = url;
|
||||
head.appendChild(script);
|
||||
|
||||
setTimeout(function () {
|
||||
if (!loaded) {
|
||||
script.onerror = script.onload = null;
|
||||
script.remove && script.remove();
|
||||
cb(true);
|
||||
}
|
||||
}, timeout || TIMEOUT);
|
||||
};
|
||||
|
||||
var normalizeDomain = function (domain) {
|
||||
// special domain: uems.sysu.edu.cn/jwxt/geetest/
|
||||
// return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn
|
||||
return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest
|
||||
};
|
||||
var normalizePath = function (path) {
|
||||
path = path.replace(/\/+/g, '/');
|
||||
if (path.indexOf('/') !== 0) {
|
||||
path = '/' + path;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
var normalizeQuery = function (query) {
|
||||
if (!query) {
|
||||
return '';
|
||||
}
|
||||
var q = '?';
|
||||
new _Object(query)._each(function (key, value) {
|
||||
if (isString(value) || isNumber(value) || isBoolean(value)) {
|
||||
q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&';
|
||||
}
|
||||
});
|
||||
if (q === '?') {
|
||||
q = '';
|
||||
}
|
||||
return q.replace(/&$/, '');
|
||||
};
|
||||
var makeURL = function (protocol, domain, path, query) {
|
||||
domain = normalizeDomain(domain);
|
||||
|
||||
var url = normalizePath(path) + normalizeQuery(query);
|
||||
if (domain) {
|
||||
url = protocol + domain + url;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
var load = function (config, protocol, domains, path, query, cb, handleCb) {
|
||||
var tryRequest = function (at) {
|
||||
|
||||
// 处理jsonp回调,这里为了保证每个不同jsonp都有唯一的回调函数
|
||||
if(handleCb){
|
||||
var cbName = "geetest_" + random();
|
||||
// 需要与预先定义好cbname参数,删除对象
|
||||
window[cbName] = bind(handleCb, null, cbName);
|
||||
query.callback = cbName;
|
||||
}
|
||||
var url = makeURL(protocol, domains[at], path, query);
|
||||
loadScript(url, function (err) {
|
||||
if (err) {
|
||||
// 超时或者出错的时候 移除回调
|
||||
if(cbName){
|
||||
try {
|
||||
window[cbName] = function(){
|
||||
window[cbName] = null;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (at >= domains.length - 1) {
|
||||
cb(true);
|
||||
// report gettype error
|
||||
} else {
|
||||
tryRequest(at + 1);
|
||||
}
|
||||
} else {
|
||||
cb(false);
|
||||
}
|
||||
}, config.timeout);
|
||||
};
|
||||
tryRequest(0);
|
||||
};
|
||||
|
||||
|
||||
var jsonp = function (domains, path, config, callback) {
|
||||
|
||||
var handleCb = function (cbName, data) {
|
||||
|
||||
// 保证只执行一次,全部超时的情况下不会再触发;
|
||||
|
||||
if (data.status == 'success') {
|
||||
callback(data.data);
|
||||
} else if (!data.status) {
|
||||
callback(data);
|
||||
} else {
|
||||
//接口有返回,但是返回了错误状态,进入报错逻辑
|
||||
callback(data);
|
||||
}
|
||||
window[cbName] = undefined;
|
||||
try {
|
||||
delete window[cbName];
|
||||
} catch (e) {
|
||||
}
|
||||
};
|
||||
load(config, config.protocol, domains, path, {
|
||||
captcha_id: config.captchaId,
|
||||
challenge: config.challenge || uuid(),
|
||||
client_type: MOBILE? 'h5':'web',
|
||||
risk_type: config.riskType,
|
||||
user_info: config.userInfo,
|
||||
call_type: config.callType,
|
||||
lang: config.language? config.language : navigator.appName === 'Netscape' ? navigator.language.toLowerCase() : navigator.userLanguage.toLowerCase()
|
||||
}, function (err) {
|
||||
// 网络问题接口没有返回,直接使用本地验证码,走宕机模式
|
||||
// 这里可以添加用户的逻辑
|
||||
if(err && typeof config.offlineCb === 'function'){
|
||||
// 执行自己的宕机
|
||||
config.offlineCb();
|
||||
return;
|
||||
}
|
||||
|
||||
if(err){
|
||||
callback(config._get_fallback_config());
|
||||
}
|
||||
}, handleCb);
|
||||
};
|
||||
|
||||
var reportError = function (config, url) {
|
||||
load(config, config.protocol, ['monitor.geetest.com'], '/monitor/send', {
|
||||
time: Date.now().getTime(),
|
||||
captcha_id: config.gt,
|
||||
challenge: config.challenge,
|
||||
exception_url: url,
|
||||
error_code: config.error_code
|
||||
}, function (err) {})
|
||||
}
|
||||
|
||||
var throwError = function (errorType, config, errObj) {
|
||||
var errors = {
|
||||
networkError: '网络错误',
|
||||
gtTypeError: 'gt字段不是字符串类型'
|
||||
};
|
||||
if (typeof config.onError === 'function') {
|
||||
config.onError({
|
||||
desc: errObj.desc,
|
||||
msg: errObj.msg,
|
||||
code: errObj.code
|
||||
});
|
||||
} else {
|
||||
throw new Error(errors[errorType]);
|
||||
}
|
||||
};
|
||||
|
||||
var detect = function () {
|
||||
return window.Geetest || document.getElementById("gt_lib");
|
||||
};
|
||||
|
||||
if (detect()) {
|
||||
status.slide = "loaded";
|
||||
}
|
||||
var GeetestIsLoad = function (fname) {
|
||||
var GeetestIsLoad = false;
|
||||
var tags = { js: 'script', css: 'link' };
|
||||
var tagname = tags[fname.split('.').pop()];
|
||||
if (tagname !== undefined) {
|
||||
var elts = document.getElementsByTagName(tagname);
|
||||
for (var i in elts) {
|
||||
if ((elts[i].href && elts[i].href.toString().indexOf(fname) > 0)
|
||||
|| (elts[i].src && elts[i].src.toString().indexOf(fname) > 0)) {
|
||||
GeetestIsLoad = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return GeetestIsLoad;
|
||||
};
|
||||
window.initGeetest4 = function (userConfig,callback) {
|
||||
|
||||
var config = new Config(userConfig);
|
||||
if (userConfig.https) {
|
||||
config.protocol = 'https://';
|
||||
} else if (!userConfig.protocol) {
|
||||
config.protocol = window.location.protocol + '//';
|
||||
}
|
||||
|
||||
|
||||
if (isObject(userConfig.getType)) {
|
||||
config._extend(userConfig.getType);
|
||||
}
|
||||
|
||||
jsonp(config.apiServers , config.typePath, config, function (newConfig) {
|
||||
|
||||
//错误捕获,第一个load请求可能直接报错
|
||||
var newConfig = camelizeKeys(newConfig);
|
||||
|
||||
if(newConfig.status === 'error'){
|
||||
return throwError('networkError', config, newConfig);
|
||||
}
|
||||
|
||||
var type = newConfig.type;
|
||||
|
||||
if(config.debug){
|
||||
new _Object(newConfig)._extend(config.debug)
|
||||
}
|
||||
var init = function () {
|
||||
config._extend(newConfig);
|
||||
callback(new window.Geetest4(config));
|
||||
};
|
||||
|
||||
callbacks[type] = callbacks[type] || [];
|
||||
|
||||
var s = status[type] || 'init';
|
||||
if (s === 'init') {
|
||||
|
||||
status[type] = 'loading';
|
||||
|
||||
callbacks[type].push(init);
|
||||
|
||||
if(newConfig.gctPath){
|
||||
load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){
|
||||
if(err){
|
||||
throwError('networkError', config, {
|
||||
code: '60205',
|
||||
msg: 'Network failure',
|
||||
desc: {
|
||||
detail: 'gct resource load timeout'
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers, newConfig.bypass || (newConfig.staticPath + newConfig.js), null, function (err) {
|
||||
if (err) {
|
||||
status[type] = 'fail';
|
||||
throwError('networkError', config, {
|
||||
code: '60204',
|
||||
msg: 'Network failure',
|
||||
desc: {
|
||||
detail: 'js resource load timeout'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
status[type] = 'loaded';
|
||||
var cbs = callbacks[type];
|
||||
for (var i = 0, len = cbs.length; i < len; i = i + 1) {
|
||||
var cb = cbs[i];
|
||||
if (isFunction(cb)) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
callbacks[type] = [];
|
||||
}
|
||||
});
|
||||
} else if (s === "loaded") {
|
||||
// 判断gct是否需要重新加载
|
||||
if(!GeetestIsLoad(newConfig.gctPath)){
|
||||
load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){
|
||||
if(err){
|
||||
throwError('networkError', config, {
|
||||
code: '60205',
|
||||
msg: 'Network failure',
|
||||
desc: {
|
||||
detail: 'gct resource load timeout'
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
return init();
|
||||
} else if (s === "fail") {
|
||||
throwError('networkError', config, {
|
||||
code: '60204',
|
||||
msg: 'Network failure',
|
||||
desc: {
|
||||
detail: 'js resource load timeout'
|
||||
}
|
||||
});
|
||||
} else if (s === "loading") {
|
||||
callbacks[type].push(init);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
})(window);
|
|
@ -0,0 +1,2 @@
|
|||
/*! layer mobile-v2.0.0 Web 通用弹出层组件 MIT License */
|
||||
;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);
|
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="60px" height="60px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="50" cy="50" r="0" fill="none" stroke="#2ebf3a" stroke-width="32">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;35" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="0s"></animate>
|
||||
<animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="0s"></animate>
|
||||
</circle><circle cx="50" cy="50" r="0" fill="none" stroke="#2ebf3a" stroke-width="32">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;35" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="-0.5s"></animate>
|
||||
<animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="-0.5s"></animate>
|
||||
</circle>
|
||||
<!-- generated by https://loading.io/ --></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,59 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<div class="row row-cols-1 animated fadeInUp " id="magazines">
|
||||
<?php $this->widget('Widget_Metas_Category_List')->to($categories); ?>
|
||||
<?php while($categories->next()): ?>
|
||||
<div class="col-md-6 pr-0 magazine">
|
||||
<div class="p-block">
|
||||
<div>
|
||||
<span class="t-lg puock-text pb-2 d-inline-block border-bottom border-primary">
|
||||
<a class="ta3 a-link" href="<?php $categories->permalink(); ?>">
|
||||
<i class="fa fa-layer-group"></i> <?php $categories->name(); ?>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<?php
|
||||
$this->widget('Widget_Archive@category_' . $categories->mid, 'pageSize=5&type=category', 'mid=' . $categories->mid)->to($posts);
|
||||
?>
|
||||
<?php if($posts->have()): ?>
|
||||
<?php $postCount = 0; ?>
|
||||
<?php while($posts->next()): ?>
|
||||
<?php $postCount++; ?>
|
||||
<?php $coverImage = getPostCover($posts->content, $posts->cid);?>
|
||||
<?php if($postCount === 1): ?>
|
||||
<!-- 第一篇文章使用特殊样式 -->
|
||||
<div class="mtb10 magazine-media-item">
|
||||
<a class="img ww" href="<?php $posts->permalink() ?>">
|
||||
<img alt="<?php $posts->title() ?>" src='<?php echo $coverImage; ?>' height="80" width="120"/>
|
||||
</a>
|
||||
<div class="t-line-1">
|
||||
<h2 class="t-lg t-line-1">
|
||||
<a class="a-link" title="<?php $posts->title() ?>" href="<?php $posts->permalink() ?>">
|
||||
<?php $posts->title() ?>
|
||||
</a>
|
||||
</h2>
|
||||
<div class="t-md c-sub text-2line"><?php $posts->excerpt(200, '...'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- 其他文章使用原有样式 -->
|
||||
<div class="media-link media-row-2">
|
||||
<div class="t-lg t-line-1 row">
|
||||
<div class="col-lg-9 col-12 text-nowrap text-truncate">
|
||||
<i class="fa fa-angle-right t-sm c-sub mr-1"></i>
|
||||
<a class="a-link t-w-400 t-md" title="<?php $posts->title(); ?>" href="<?php $posts->permalink() ?>"><?php $posts->title(); ?></a>
|
||||
</div>
|
||||
<div class="col-lg-3 text-end d-none d-lg-block">
|
||||
<span class="c-sub t-sm"><?php $posts->date(); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endwhile; ?>
|
||||
<?php else: ?>
|
||||
<p>该分类下没有文章。</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
|
@ -0,0 +1,227 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<div class="p-block" id="comments">
|
||||
<div>
|
||||
<span class="t-lg border-bottom border-primary puock-text pb-2">
|
||||
<i class="fa-regular fa-comments mr-1"></i>评论(<?php $this->commentsNum(_t('暂无评论'), _t('1 条评论'), _t('%d 条评论')); ?>)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 评论表单 -->
|
||||
<?php if($this->allow('comment')): ?>
|
||||
<div class="mt20 clearfix" id="comment-form-box">
|
||||
<div id="<?php $this->respondId(); ?>">
|
||||
<form method="post" action="<?php $this->commentUrl() ?>" id="comment-form" class="mt10" role="form">
|
||||
<div class="form-group">
|
||||
<textarea rows="4" name="text" id="comment" class="form-control form-control-sm t-sm" placeholder="世界这么大发表一下你的看法~" required><?php $this->remember('text'); ?></textarea>
|
||||
</div>
|
||||
|
||||
<?php if($this->user->hasLogin()): ?>
|
||||
<!-- 登录用户 -->
|
||||
<div class="row row-cols-1 comment-info">
|
||||
<input type="text" value="1" hidden name="comment-logged" id="comment-logged">
|
||||
<div class="col-12">
|
||||
<p class="t-sm c-sub">登录身份: <a href="<?php $this->options->profileUrl(); ?>"><?php $this->user->screenName(); ?></a> . <a href="<?php $this->options->logoutUrl(); ?>" title="Logout">登出 »</a></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- 访客表单 -->
|
||||
<div class="row row-cols-1 comment-info">
|
||||
<input type="text" value="0" hidden name="comment-logged" id="comment-logged">
|
||||
<div class="col-12 col-sm-3">
|
||||
<input type="text" name="author" id="comment_author" class="form-control form-control-sm t-sm" placeholder="昵称(必填)" value="<?php $this->remember('author'); ?>" required>
|
||||
</div>
|
||||
<div class="col-12 col-sm-3">
|
||||
<input type="email" name="mail" id="comment_email" class="form-control form-control-sm t-sm" placeholder="邮箱(必填)" value="<?php $this->remember('mail'); ?>"<?php if ($this->options->commentsRequireMail): ?> required<?php endif; ?>>
|
||||
</div>
|
||||
<div class="col-12 col-sm-3">
|
||||
<input type="url" name="url" id="comment_url" class="form-control form-control-sm t-sm" placeholder="网站" value="<?php $this->remember('url'); ?>"<?php if ($this->options->commentsRequireURL): ?> required<?php endif; ?>>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="p-flex-sbc mt10">
|
||||
<div>
|
||||
<?php if(!$this->user->hasLogin()): ?>
|
||||
<div class="d-inline-block">
|
||||
<a class="btn btn-primary btn-ssm" href="<?php $this->options->loginUrl(); ?>" title="登录" target="_blank">
|
||||
<i class="fa fa-right-to-bracket"></i> 登录
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<?php if($this->is('post')): ?>
|
||||
<button id="comment-cancel" type="button" class="btn btn-outline-dark d-none btn-ssm">取消</button>
|
||||
<?php endif; ?>
|
||||
<?php if($this->options->social): ?>
|
||||
<button id="comment-smiley" class="btn btn-outline-secondary btn-ssm pk-modal-toggle" type="button" title="表情" data-once-load="true"
|
||||
data-url="<?php echo get_correct_url('/emoji/'); ?>">
|
||||
<i class="fa-regular fa-face-smile t-md"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button type="submit" id="comment-submit" class="btn btn-primary btn-ssm">
|
||||
<i class="fa-regular fa-paper-plane"></i> 发布评论
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="comment-ajax-load" class="text-center mt20 d-none">
|
||||
<div class="pk-skeleton _comment">
|
||||
<div class="_h">
|
||||
<div class="_avatar"></div>
|
||||
<div class="_info">
|
||||
<div class="_name"></div>
|
||||
<div class="_date"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="_text">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pk-skeleton _comment">
|
||||
<div class="_h">
|
||||
<div class="_avatar"></div>
|
||||
<div class="_info">
|
||||
<div class="_name"></div>
|
||||
<div class="_date"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="_text">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pk-skeleton _comment">
|
||||
<div class="_h">
|
||||
<div class="_avatar"></div>
|
||||
<div class="_info">
|
||||
<div class="_name"></div>
|
||||
<div class="_date"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="_text">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="mt20 alert alert-warning">
|
||||
<i class="fa fa-exclamation-circle"></i> 评论已关闭
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 评论列表 -->
|
||||
<?php if ($this->have()): ?>
|
||||
<div id="post-comments">
|
||||
<?php $this->comments()->to($comments); ?>
|
||||
<?php $comments->listComments(); ?>
|
||||
|
||||
<!-- 评论分页 -->
|
||||
<div class="mt20 p-flex-s-right" data-no-instant>
|
||||
<?php $comments->pageNav('«', '»', 1, '...', array(
|
||||
'wrapTag' => 'ul',
|
||||
'wrapClass' => 'pagination comment-ajax-load',
|
||||
'itemTag' => 'li',
|
||||
'textTag' => 'span',
|
||||
'currentClass' => 'active',
|
||||
'prevClass' => 'prev',
|
||||
'nextClass' => 'next'
|
||||
)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php function threadedComments($comments, $options) { ?>
|
||||
<li id="<?php $comments->theId(); ?>" class="post-comment">
|
||||
<div class="info">
|
||||
<div>
|
||||
<?php $mail = $comments->mail;$mailHash = md5(strtolower(trim($mail)));
|
||||
$cnavatar = Helper::options()->cnavatar ? Helper::options()->cnavatar : 'https://cravatar.cn/avatar/';
|
||||
$avatarurl = rtrim($cnavatar, '/') . '/' . $mailHash . '?s=80&d=identicon';
|
||||
$loadingImg = Helper::options()->themeUrl . '/assets/img/load.svg';
|
||||
?>
|
||||
<img src="<?php echo $loadingImg; ?>"
|
||||
data-src="<?php echo $avatarurl; ?>"
|
||||
class="avatar avatar-64 photo md-avatar lazy"
|
||||
width="60" height="60">
|
||||
</div>
|
||||
<div class="ml-2 two-info">
|
||||
<div class="puock-text ta3b">
|
||||
<span class="t-md puock-links">
|
||||
<?php $comments->author(); ?>
|
||||
</span>
|
||||
<?php $commentApprove = commentApprove($comments, $comments->mail); ?>
|
||||
<?php if ($comments->authorId === $comments->ownerId): ?>
|
||||
<span class="t-sm text-danger">
|
||||
<i class="fa-regular fa-gem mr-1"></i>博主
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="t-sm c-sub">
|
||||
<i class="fa-regular fa-gem mr-1"></i><?php echo $commentApprove['userLevel']; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="t-sm c-sub">
|
||||
<span><?php $comments->date('Y-m-d H:i:s'); ?></span>
|
||||
<?php $comments->reply(
|
||||
sprintf('<a onclick="return TypechoComment.reply(\'%s\', %d);" class="hide-info animated bounceIn c-sub-a t-sm ml-1 comment-reply" href="%s#comment-%d"><span class="comment-reply-text"><i class="fa fa-share-from-square"></i>回复</span></a>',
|
||||
$comments->theId,
|
||||
$comments->coid,
|
||||
$comments->commentUrl($comments->coid),
|
||||
$comments->coid)
|
||||
); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="content-text t-md mt10 puock-text">
|
||||
<?php if ($comments->parent) {echo getPermalinkFromCoid($comments->parent);} $comments->content();?>
|
||||
<div class="comment-os c-sub">
|
||||
<?php
|
||||
$deviceInfo = getBrowsersInfo($comments->agent);
|
||||
$icons = getDeviceIcon($deviceInfo);
|
||||
?>
|
||||
<!-- 系统信息 -->
|
||||
<span class="mt10" title="<?php echo $deviceInfo['system'] . ' ' . $deviceInfo['systemVersion']; ?>">
|
||||
<?php echo $icons['system']; ?>
|
||||
<?php
|
||||
echo $deviceInfo['system']
|
||||
?
|
||||
: '未知系统';
|
||||
?>
|
||||
</span>
|
||||
|
||||
<!-- 浏览器信息 -->
|
||||
<span class="mt10" title="<?php echo $deviceInfo['browser'] . ' ' . $deviceInfo['version']; ?>">
|
||||
<?php echo $icons['browser']; ?>
|
||||
<?php
|
||||
echo $deviceInfo['browser']
|
||||
?
|
||||
: '未知浏览器';
|
||||
?>
|
||||
</span>
|
||||
|
||||
<?php if($comments->ip): ?>
|
||||
<span class="mt10" title="IP">
|
||||
<i class="fa-solid fa-location-dot"></i> <?php echo get_ip_region($comments->ip); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($comments->children): ?>
|
||||
<div class="comment-children">
|
||||
<?php $comments->threadedComments($options); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php } ?>
|
|
@ -0,0 +1,106 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php if ($this->is('index')): ?>
|
||||
<?php if ($this->options->cmsmodel): ?>
|
||||
<?php $this->need('cms.php'); ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($this->options->friendlink): ?>
|
||||
<div class="p-block index-links">
|
||||
<div>
|
||||
<span class="t-lg puock-text pb-2 d-inline-block border-bottom border-primary">
|
||||
<i class="fa fa-link"></i>友情链接
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt20 t-md index-links-box">
|
||||
<?php Links_Plugin::output('
|
||||
<a class="badge links-item" href="{url}" target="_blank" title="{title}" rel="nofollow">{name}</a>
|
||||
'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<!--全局下方-->
|
||||
<?php if($this->options->adlistfoot): ?>
|
||||
<div class="puock-text p-block t-md ad-global-bottom"><?php $this->options->adlistfoot(); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="post-menus" class="post-menus-box">
|
||||
<div id="post-menu-state" class="post-menu-toggle" title="打开或关闭文章目录">
|
||||
<i class="puock-text ta3 fa fa-bars"></i>
|
||||
</div>
|
||||
<div id="post-menu-content" class="animated slideInRight mini-scroll">
|
||||
<div id="post-menu-head"> </div>
|
||||
<div id="post-menu-content-items">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="rb-float-actions">
|
||||
<?php if ($this->is('post')): ?>
|
||||
<div data-to-area="#comments" class="p-block"><i class="fa-regular fa-comments puock-text"></i></div>
|
||||
<?php endif; ?>
|
||||
<div data-to="top" class="p-block"><i class="fa fa-arrow-up puock-text"></i></div>
|
||||
<div data-to="bottom" class="p-block"><i class="fa fa-arrow-down puock-text"></i></div>
|
||||
</div>
|
||||
<footer id="footer">
|
||||
<div class="container">
|
||||
<div class="row row-cols-md-1">
|
||||
<div class="col-md-6">
|
||||
<?php if($this->options->footerinfo): ?>
|
||||
<div><span class="t-md pb-2 d-inline-block border-bottom border-primary"><i class="fa-regular fa-bell"></i> 联系我们</span> </div>
|
||||
<p class="mt20 t-md">
|
||||
<?php $this->options->footerinfo(); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="col-md-6">
|
||||
<?php if($this->options->footercopyright): ?>
|
||||
<div><span class="t-md pb-2 d-inline-block border-bottom border-primary"><i class="fa-regular fa-copyright"></i> 版权说明</span> </div>
|
||||
<p class="mt20 t-md">
|
||||
<?php $this->options->footercopyright(); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt20 text-center t-md">
|
||||
<div class="info">
|
||||
<p><?php $this->options->tongji(); ?></p>
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow"><?php $this->options->ICP(); ?></a>
|
||||
© <?php echo date('Y'); ?> <a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>
|
||||
<div class="fs12 mt10 c-sub">
|
||||
<span> Theme by
|
||||
<a target="_blank" class="c-sub" title="Puock v2.8.14" href="https://github.com/jkjoy/typecho-theme-puock">Puock</a>
|
||||
</span>
|
||||
<span> Powered by
|
||||
<a target="_blank" class="c-sub" title="Typecho" href="https://typecho.org">Typecho</a> <p><a target="_blank" class="c-sub" title="老孙博客" href="https://imsun.org">老孙博客</a>制作</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div id="gt-validate-box"></div>
|
||||
<script data-instant>
|
||||
var puock_metas = {
|
||||
"home": "<?php $this->options->siteUrl(); ?>",
|
||||
"use_post_menu": true,
|
||||
"is_single": false,
|
||||
"is_pjax": false,
|
||||
"main_lazy_img": true,
|
||||
"link_blank_open": true,
|
||||
//"async_view_id": null,
|
||||
"mode_switch": true,
|
||||
"off_img_viewer": false,
|
||||
"off_code_highlighting": false
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript" data-no-instant src="<?php $this->options->themeUrl('assets/js/libs.min.js'); ?>?ver=2.8.14" id="puock-libs-js"></script>
|
||||
<script type="text/javascript" data-no-instant src="<?php $this->options->themeUrl('assets/layer/layer.js'); ?>?ver=2.8.14" id="puock-layer-js"></script>
|
||||
<script type="text/javascript" data-no-instant src="<?php $this->options->themeUrl('assets/js/spark-md5.min.js'); ?>?ver=2.8.14" id="puock-spark-md5-js"></script>
|
||||
<script type="text/javascript" data-no-instant src="<?php $this->options->themeUrl('assets/js/html2canvas.min.js'); ?>?ver=2.8.14" id="puock-html2canvas-js"></script>
|
||||
<script type="text/javascript" data-no-instant src="<?php $this->options->themeUrl('assets/js/gt4.js'); ?>?ver=2.8.14" id="puock-gt4-js"></script>
|
||||
<script type="text/javascript" data-no-instant src="<?php $this->options->themeUrl('assets/js/puock.js'); ?>" id="puock-js"></script>
|
||||
<?php $this->footer(); ?>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,896 @@
|
|||
<?php
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
||||
|
||||
function themeConfig($form)
|
||||
{
|
||||
$logoUrl = new Typecho_Widget_Helper_Form_Element_Text('logoUrl', NULL, NULL, _t('站点 LOGO 地址'), _t('建议尺寸 100px * 100px,不填写则使用站点标题'));
|
||||
$form->addInput($logoUrl);
|
||||
$icoUrl = new Typecho_Widget_Helper_Form_Element_Text('icoUrl', NULL, NULL, _t('站点 Favicon 地址'), _t('建议尺寸 16px * 16px,不填写则使用默认图标'));
|
||||
$form->addInput($icoUrl);
|
||||
$sticky = new Typecho_Widget_Helper_Form_Element_Text('sticky', NULL, NULL, _t('置顶文章cid'), _t('多篇文章以`|`符号隔开'), _t('会在首页展示置顶文章。'));
|
||||
$form->addInput($sticky);
|
||||
$ICP = new Typecho_Widget_Helper_Form_Element_Text('ICP', NULL, NULL, _t('ICP 备案号'), _t('用于网站备案的 ICP 号'));
|
||||
$form->addInput($ICP);
|
||||
$bgUrl = new Typecho_Widget_Helper_Form_Element_Text('bgUrl', NULL, NULL, _t('个人信息背景图片地址'), _t('用于个人信息展示的背景图片'));
|
||||
$form->addInput($bgUrl);
|
||||
$cnavatar = new Typecho_Widget_Helper_Form_Element_Text('cnavatar', NULL, NULL, _t('Gravatar镜像'), _t('默认使用https://cravatar.cn/avatar/'));
|
||||
$form->addInput($cnavatar);
|
||||
$listmodel = new Typecho_Widget_Helper_Form_Element_Radio('listmodel',
|
||||
array('0'=> _t('否'), '1'=> _t('是')),
|
||||
'0', _t('列表模式'), _t('选择"是"将在首页显示列表模式。选择否则显示卡片模式'));
|
||||
$form->addInput($listmodel);
|
||||
$pageprev = new Typecho_Widget_Helper_Form_Element_Radio('pageprev',
|
||||
array('0'=> _t('否'), '1'=> _t('是')),
|
||||
'0', _t('首页文章列表页码'), _t('选择"是"首页文章列表显示页码。选择否则不显示分页'));
|
||||
$form->addInput($pageprev);
|
||||
$cmsmodel = new Typecho_Widget_Helper_Form_Element_Radio('cmsmodel',
|
||||
array('0'=> _t('否'), '1'=> _t('是')),
|
||||
'0', _t('CMS模式'), _t('选择"是"开启CMS模式。选择否则使用博客模式'));
|
||||
$form->addInput($cmsmodel);
|
||||
$friendlink = new Typecho_Widget_Helper_Form_Element_Radio('friendlink',
|
||||
array('0'=> _t('否'), '1'=> _t('是')),
|
||||
'0', _t('友情链接'), _t('选择"是"在首页显示友情链接。开启前请安装"Links"插件。默认关闭'));
|
||||
$form->addInput($friendlink);
|
||||
$social = new Typecho_Widget_Helper_Form_Element_Radio('social',
|
||||
array('0'=> _t('否'), '1'=> _t('是')),
|
||||
'0', _t('社交分享显示'), _t('选择"是"在文章页面显示社交分享。需要搭配插件使用,默认关闭'));
|
||||
$form->addInput($social);
|
||||
$gonggao = new Typecho_Widget_Helper_Form_Element_Textarea('gonggao', NULL, NULL, _t('站点公告'), _t('支持HTML'));
|
||||
$form->addInput($gonggao);
|
||||
$adlisttop = new Typecho_Widget_Helper_Form_Element_Textarea('adlisttop', NULL, NULL, _t('文章列表上方广告位'), _t('支持HTML'));
|
||||
$form->addInput($adlisttop);
|
||||
$adlistfoot = new Typecho_Widget_Helper_Form_Element_Textarea('adlistfoot', NULL, NULL, _t('文章列表下方广告位'), _t('支持HTML'));
|
||||
$form->addInput($adlistfoot);
|
||||
$articletop = new Typecho_Widget_Helper_Form_Element_Textarea('articletop', NULL, NULL, _t('文章页顶部广告位'), _t('支持HTML'));
|
||||
$form->addInput($articletop);
|
||||
$articlemid = new Typecho_Widget_Helper_Form_Element_Textarea('articlemid', NULL, NULL, _t('文章页中部广告位'), _t('支持HTML'));
|
||||
$form->addInput($articlemid);
|
||||
$articlefoot = new Typecho_Widget_Helper_Form_Element_Textarea('articlefoot', NULL, NULL, _t('文章页底部广告位'), _t('支持HTML'));
|
||||
$form->addInput($articlefoot);
|
||||
$addhead = new Typecho_Widget_Helper_Form_Element_Textarea('addhead', NULL, NULL, _t('网站验证代码'), _t('支持HTML'));
|
||||
$form->addInput($addhead);
|
||||
$tongji = new Typecho_Widget_Helper_Form_Element_Textarea('tongji', NULL, NULL, _t('网站统计代码'), _t('支持HTML'));
|
||||
$form->addInput($tongji);
|
||||
$footerinfo = new Typecho_Widget_Helper_Form_Element_Textarea('footerinfo', NULL, NULL, _t('底部关于我们'), _t('支持HTML'));
|
||||
$form->addInput($footerinfo);
|
||||
$footercopyright = new Typecho_Widget_Helper_Form_Element_Textarea('footercopyright', NULL, NULL, _t('底部版权信息'), _t('支持HTML'));
|
||||
$form->addInput($footercopyright);
|
||||
$sidebarBlock = new \Typecho\Widget\Helper\Form\Element\Checkbox(
|
||||
'sidebarBlock',
|
||||
[
|
||||
'ShowSearch' => _t('显示搜索框'),
|
||||
'ShowAdmin' => _t('显示作者信息'),
|
||||
'ShowRecentPosts' => _t('显示最新文章'),
|
||||
'ShowHotPosts' => _t('显示热门文章'),
|
||||
'ShowRecentComments' => _t('显示最近回复'),
|
||||
'ShowTags' => _t('显示标签云')
|
||||
],
|
||||
['ShowSearch', 'ShowAdmin', 'ShowRecentPosts', 'ShowHotPosts', 'ShowRecentComments', 'ShowTags'],
|
||||
_t('侧边栏显示')
|
||||
);
|
||||
|
||||
$form->addInput($sidebarBlock->multiMode());
|
||||
}
|
||||
|
||||
function themeFields($layout) {
|
||||
$summary= new Typecho_Widget_Helper_Form_Element_Textarea('summary', NULL, NULL, _t('文章摘要'), _t('自定义摘要'));
|
||||
$layout->addItem($summary);
|
||||
$cover= new Typecho_Widget_Helper_Form_Element_Text('cover', NULL, NULL, _t('文章封面'), _t('自定义文章封面'));
|
||||
$layout->addItem($cover);
|
||||
}
|
||||
|
||||
/*
|
||||
* 文章浏览数统计
|
||||
*/
|
||||
function get_post_view($archive) {
|
||||
$cid = $archive->cid;
|
||||
$db = Typecho_Db::get();
|
||||
$prefix = $db->getPrefix();
|
||||
if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
|
||||
$db->query('ALTER TABLE `' . $prefix . 'contents` ADD `views` INT(10) DEFAULT 0;');
|
||||
echo 0;
|
||||
return;
|
||||
}
|
||||
$row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
|
||||
if ($archive->is('single')) {
|
||||
$views = Typecho_Cookie::get('extend_contents_views');
|
||||
if (empty($views)) {
|
||||
$views = array();
|
||||
} else {
|
||||
$views = explode(',', $views);
|
||||
}
|
||||
if (!in_array($cid, $views)) {
|
||||
$db->query($db->update('table.contents')->rows(array('views' => (int)$row['views'] + 1))->where('cid = ?', $cid));
|
||||
array_push($views, $cid);
|
||||
$views = implode(',', $views);
|
||||
Typecho_Cookie::set('extend_contents_views', $views); //记录查看cookie
|
||||
|
||||
}
|
||||
}
|
||||
echo $row['views'];
|
||||
}
|
||||
|
||||
/*
|
||||
* 点赞数统计
|
||||
*/
|
||||
// 点赞显示函数
|
||||
function get_post_like($archive) {
|
||||
$cid = $archive->cid;
|
||||
$db = Typecho_Db::get();
|
||||
$prefix = $db->getPrefix();
|
||||
if (!array_key_exists('likes', $db->fetchRow($db->select()->from('table.contents')))) {
|
||||
$db->query('ALTER TABLE `' . $prefix . 'contents` ADD `likes` INT(10) DEFAULT 0;');
|
||||
echo 0;
|
||||
return;
|
||||
}
|
||||
$row = $db->fetchRow($db->select('likes')->from('table.contents')->where('cid = ?', $cid));
|
||||
echo $row['likes'] ?? 0;
|
||||
}
|
||||
|
||||
// AJAX 处理函数
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['likeup']) && isset($_POST['cid'])) {
|
||||
$cid = intval($_POST['cid']);
|
||||
$db = Typecho_Db::get();
|
||||
$prefix = $db->getPrefix();
|
||||
|
||||
// 确保likes字段存在
|
||||
if (!array_key_exists('likes', $db->fetchRow($db->select()->from('table.contents')))) {
|
||||
$db->query('ALTER TABLE `' . $prefix . 'contents` ADD `likes` INT(10) DEFAULT 0;');
|
||||
}
|
||||
|
||||
$row = $db->fetchRow($db->select('likes')->from('table.contents')->where('cid = ?', $cid));
|
||||
if ($row) {
|
||||
$likes = Typecho_Cookie::get('extend_contents_likes');
|
||||
$likesArr = $likes ? explode(',', $likes) : [];
|
||||
if (!in_array($cid, $likesArr)) {
|
||||
// 更新点赞数
|
||||
$newLikes = intval($row['likes']) + 1;
|
||||
$db->query($db->update('table.contents')->rows(['likes' => $newLikes])->where('cid = ?', $cid));
|
||||
$likesArr[] = $cid;
|
||||
Typecho_Cookie::set('extend_contents_likes', implode(',', $likesArr));
|
||||
echo json_encode(['success' => true, 'likes' => $newLikes]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'likes' => $row['likes'], 'msg' => '已点赞']);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'likes' => 0, 'msg' => '文章ID错误']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* 随机封面
|
||||
*/
|
||||
function getPostCover($content, $cid, $fields = null) {
|
||||
// 首先检查是否有自定义封面字段
|
||||
if ($fields && !empty($fields->cover)) {
|
||||
return $fields->cover;
|
||||
}
|
||||
|
||||
// 尝试从内容中提取第一张图片
|
||||
preg_match_all('/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $content, $matches);
|
||||
|
||||
if (!empty($matches[1][0])) {
|
||||
// 如果找到图片,返回第一张图片URL
|
||||
return $matches[1][0];
|
||||
} else {
|
||||
// 如果没有图片,使用随机封面(基于文章ID的伪随机)
|
||||
$coverNumber = ($cid % 8) + 1; // 得到1-8的值
|
||||
return Helper::options()->themeUrl . '/assets/img/random/' . $coverNumber . '.jpg';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一篇文章
|
||||
*
|
||||
* @param Widget_Archive $archive 当前文章归档对象
|
||||
* @return object|null 上一篇文章对象,如果没有则返回null
|
||||
*/
|
||||
function get_previous_post($archive) {
|
||||
if (!$archive->is('single')) {
|
||||
return null;
|
||||
}
|
||||
$db = Typecho_Db::get();
|
||||
$prefix = $db->getPrefix();
|
||||
// 获取上一篇文章(按创建时间排序)
|
||||
$post = $db->fetchRow($db->select()
|
||||
->from('table.contents')
|
||||
->where('table.contents.status = ?', 'publish')
|
||||
->where('table.contents.created < ?', $archive->created)
|
||||
->where('table.contents.type = ?', 'post')
|
||||
->order('table.contents.created', Typecho_Db::SORT_DESC)
|
||||
->limit(1));
|
||||
|
||||
if (!$post) {
|
||||
return null;
|
||||
}
|
||||
// 构建标准化的文章对象
|
||||
$result = new stdClass();
|
||||
$result->cid = $post['cid'];
|
||||
$result->title = $post['title'];
|
||||
$result->slug = $post['slug'];
|
||||
$result->created = $post['created'];
|
||||
$result->content = isset($post['text']) ? $post['text'] : '';
|
||||
$result->text = isset($post['text']) ? $post['text'] : '';
|
||||
$result->permalink = get_permalink($post['cid']);
|
||||
// 获取文章自定义字段
|
||||
$fields = $db->fetchAll($db->select()->from('table.fields')
|
||||
->where('cid = ?', $post['cid']));
|
||||
// 添加自定义字段到文章对象
|
||||
if ($fields) {
|
||||
$result->fields = new stdClass();
|
||||
foreach ($fields as $field) {
|
||||
$result->fields->{$field['name']} = $field['str_value'] ? $field['str_value'] : $field['int_value'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一篇文章
|
||||
*
|
||||
* @param Widget_Archive $archive 当前文章归档对象
|
||||
* @return object|null 下一篇文章对象,如果没有则返回null
|
||||
*/
|
||||
function get_next_post($archive) {
|
||||
if (!$archive->is('single')) {
|
||||
return null;
|
||||
}
|
||||
$db = Typecho_Db::get();
|
||||
$prefix = $db->getPrefix();
|
||||
// 获取下一篇文章(按创建时间排序)
|
||||
$post = $db->fetchRow($db->select()
|
||||
->from('table.contents')
|
||||
->where('table.contents.status = ?', 'publish')
|
||||
->where('table.contents.created > ?', $archive->created)
|
||||
->where('table.contents.type = ?', 'post')
|
||||
->order('table.contents.created', Typecho_Db::SORT_ASC)
|
||||
->limit(1));
|
||||
|
||||
if (!$post) {
|
||||
return null;
|
||||
}
|
||||
// 构建标准化的文章对象
|
||||
$result = new stdClass();
|
||||
$result->cid = $post['cid'];
|
||||
$result->title = $post['title'];
|
||||
$result->slug = $post['slug'];
|
||||
$result->created = $post['created'];
|
||||
$result->content = isset($post['text']) ? $post['text'] : '';
|
||||
$result->text = isset($post['text']) ? $post['text'] : '';
|
||||
$result->permalink = get_permalink($post['cid']);
|
||||
// 获取文章自定义字段
|
||||
$fields = $db->fetchAll($db->select()->from('table.fields')
|
||||
->where('cid = ?', $post['cid']));
|
||||
// 添加自定义字段到文章对象
|
||||
if ($fields) {
|
||||
$result->fields = new stdClass();
|
||||
foreach ($fields as $field) {
|
||||
$result->fields->{$field['name']} = $field['str_value'] ? $field['str_value'] : $field['int_value'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章永久链接
|
||||
*
|
||||
* @param int $cid 文章ID
|
||||
* @return string 文章链接
|
||||
*/
|
||||
function get_permalink($cid) {
|
||||
try {
|
||||
// 获取文章对象
|
||||
$db = Typecho_Db::get();
|
||||
$post = $db->fetchRow($db->select()
|
||||
->from('table.contents')
|
||||
->where('cid = ?', $cid)
|
||||
->where('status = ?', 'publish'));
|
||||
if (!$post) {
|
||||
return '';
|
||||
}
|
||||
// 构造文章对象
|
||||
$post['type'] = 'post'; // 确保类型为文章
|
||||
$post = Typecho_Widget::widget('Widget_Abstract_Contents')->filter($post);
|
||||
// 使用文章对象的 permalink 方法生成链接
|
||||
return $post['permalink'];
|
||||
} catch (Exception $e) {
|
||||
// 出现异常时使用最简单的方式
|
||||
$options = Helper::options();
|
||||
return $options->siteUrl . '?cid=' . $cid;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取所有评论者信息的函数
|
||||
*/
|
||||
function getAllCommenters() {
|
||||
$db = Typecho_Db::get();
|
||||
$commenters = array();
|
||||
|
||||
// 查询所有评论者信息并按邮箱分组统计
|
||||
$query = $db->select('author, mail, COUNT(*) as comment_count, url')
|
||||
->from('table.comments')
|
||||
->where('status = ?', 'approved') // 只统计已通过审核的评论
|
||||
->group('mail')
|
||||
->order('comment_count', Typecho_Db::SORT_DESC);
|
||||
|
||||
$rows = $db->fetchAll($query);
|
||||
|
||||
// 获取 Gravatar 镜像设置
|
||||
$cnavatar = Helper::options()->cnavatar ? Helper::options()->cnavatar : 'https://cravatar.cn/avatar/';
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$email_hash = md5(strtolower(trim($row['mail'])));
|
||||
$avatar_url = rtrim($cnavatar, '/') . '/' . $email_hash . '?s=50&d=mp';
|
||||
|
||||
$commenters[] = array(
|
||||
'nickname' => $row['author'],
|
||||
'email' => $row['mail'],
|
||||
'url' => $row['url'],
|
||||
'avatar' => $avatar_url,
|
||||
'comment_count' => $row['comment_count']
|
||||
);
|
||||
}
|
||||
|
||||
return $commenters;
|
||||
}
|
||||
/**
|
||||
* 获取IP归属地
|
||||
*/
|
||||
// 加载 XdbSearcher 类
|
||||
require_once __DIR__ . '/ip2region/XdbSearcher.php';
|
||||
|
||||
// 单例方式加载 ip2region.xdb 到内存
|
||||
function getIp2regionSearcher() {
|
||||
static $searcher = null;
|
||||
if ($searcher === null) {
|
||||
$dbPath = __DIR__ . '/ip2region/ip2region.xdb';
|
||||
$cBuff = XdbSearcher::loadContentFromFile($dbPath);
|
||||
if ($cBuff === null) {
|
||||
error_log("无法加载 ip2region.xdb");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$searcher = XdbSearcher::newWithBuffer($cBuff);
|
||||
} catch (Exception $e) {
|
||||
error_log("创建 ip2region searcher 失败: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $searcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 IP 归属地
|
||||
*
|
||||
* @param string $region 归属地字符串
|
||||
* @return string 格式化后的归属地
|
||||
*/
|
||||
function format_ip_region($region) {
|
||||
// 分割字符串
|
||||
$parts = explode('|', $region);
|
||||
|
||||
// 去除为 0 或 空字符串的部分
|
||||
$parts = array_filter($parts, function($item) {
|
||||
return $item !== '0' && $item !== '';
|
||||
});
|
||||
|
||||
// 重新拼接
|
||||
return implode('', $parts);
|
||||
}
|
||||
|
||||
// 通过 IP 获取归属地
|
||||
function get_ip_region($ip) {
|
||||
// 检查是否是 IPv6 地址
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
return 'IPv6';
|
||||
}
|
||||
// 检查是否是内网IP
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
return '内网IP';
|
||||
}
|
||||
|
||||
$searcher = getIp2regionSearcher();
|
||||
if (!$searcher) return '未知';
|
||||
$region = $searcher->search($ip);
|
||||
if ($region === null) return '未知';
|
||||
return format_ip_region($region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器和设备信息
|
||||
*
|
||||
* @param string $userAgent 用户代理
|
||||
* @return string[]
|
||||
*/
|
||||
function getBrowsersInfo ($userAgent) {
|
||||
|
||||
$deviceInfo = [
|
||||
"system" => "",
|
||||
"systemVersion" => "",
|
||||
"browser" => "",
|
||||
"version" => "",
|
||||
"device" => "PC"
|
||||
];
|
||||
|
||||
|
||||
$match = [
|
||||
// 浏览器 - 国外浏览器
|
||||
"Safari" => strstr($userAgent, 'Safari') != false ,
|
||||
"Chrome" => strstr($userAgent, 'Chrome') != false || strstr($userAgent, 'CriOS') != false ,
|
||||
"IE" => strstr($userAgent, 'MSIE') != false || strstr($userAgent, 'Trident') != false ,
|
||||
"Edge" => strstr($userAgent, 'Edge') != false || strstr($userAgent, 'Edg/') != false || strstr($userAgent, 'EdgA') != false || strstr($userAgent, 'EdgiOS') != false,
|
||||
"Firefox" => strstr($userAgent, 'Firefox') != false || strstr($userAgent, 'FxiOS') != false ,
|
||||
"Firefox Focus" => strstr($userAgent, 'Focus') != false,
|
||||
"Chromium" => strstr($userAgent,'Chromium') != false,
|
||||
"Opera" => strstr($userAgent,'Opera') != false || strstr($userAgent,'OPR') != false,
|
||||
"Vivaldi" => strstr($userAgent,'Vivaldi') != false,
|
||||
"Yandex" => strstr($userAgent,'YaBrowser') != false,
|
||||
"Arora" => strstr($userAgent,'Arora') != false,
|
||||
"Lunascape" => strstr($userAgent,'Lunascape') != false,
|
||||
"QupZilla" => strstr($userAgent,'QupZilla') != false,
|
||||
"Coc Coc" => strstr($userAgent,'coc_coc_browser') != false,
|
||||
"Kindle" => strstr($userAgent,'Kindle') != false || strstr($userAgent,'Silk/') != false,
|
||||
"Iceweasel" => strstr($userAgent,'Iceweasel') != false,
|
||||
"Konqueror" => strstr($userAgent,'Konqueror') != false,
|
||||
"Iceape" => strstr($userAgent,'Iceape') != false,
|
||||
"SeaMonkey" => strstr($userAgent,'SeaMonkey') != false,
|
||||
"Epiphany" => strstr($userAgent,'Epiphany') != false,
|
||||
// 浏览器 - 国内浏览器
|
||||
"360" => strstr($userAgent,'QihooBrowser') != false || strstr($userAgent,'QHBrowser') != false,
|
||||
"360EE" => strstr($userAgent,'360EE') != false,
|
||||
"360SE" => strstr($userAgent,'360SE') != false,
|
||||
"UC" => strstr($userAgent,'UCBrowser') != false || strstr($userAgent,' UBrowser') != false || strstr($userAgent,'UCWEB') != false,
|
||||
"QQBrowser" => strstr($userAgent,'QQBrowser') != false,
|
||||
"QQ" => strstr($userAgent,'QQ/') != false,
|
||||
"Baidu" => strstr($userAgent,'Baidu') != false || strstr($userAgent,'BIDUBrowser') != false || strstr($userAgent,'baidubrowser') != false || strstr($userAgent,'baiduboxapp') != false || strstr($userAgent,'BaiduHD') != false,
|
||||
"Maxthon" => strstr($userAgent,'Maxthon') != false,
|
||||
"Sogou" => strstr($userAgent,'MetaSr') != false || strstr($userAgent,'Sogou') != false,
|
||||
"Liebao" => strstr($userAgent,'LBBROWSER') != false || strstr($userAgent,'LieBaoFast') != false,
|
||||
"2345Explorer" => strstr($userAgent,'2345Explorer') != false || strstr($userAgent,'Mb2345Browser') != false || strstr($userAgent,'2345chrome') != false,
|
||||
"115Browser" => strstr($userAgent,'115Browser') != false,
|
||||
"TheWorld" => strstr($userAgent,'TheWorld') != false,
|
||||
"Quark" => strstr($userAgent,'Quark') != false,
|
||||
"Qiyu" => strstr($userAgent,'Qiyu') != false,
|
||||
// 浏览器 - 手机厂商
|
||||
"XiaoMi" => strstr($userAgent,'MiuiBrowser') != false,
|
||||
"Huawei" => strstr($userAgent,'HuaweiBrowser') != false || strstr($userAgent,'HUAWEI/') != false || strstr($userAgent,'HONOR') != false || strstr($userAgent,'HBPC/') != false,
|
||||
"Vivo" => strstr($userAgent,'VivoBrowser') != false,
|
||||
"OPPO" => strstr($userAgent,'HeyTapBrowser') != false,
|
||||
// 浏览器 - 客户端
|
||||
"Wechat" => strstr($userAgent,'MicroMessenger') != false,
|
||||
"WechatWork" => strstr($userAgent,'wxwork/') != false,
|
||||
"Taobao" => strstr($userAgent,'AliApp(TB') != false,
|
||||
"Alipay" => strstr($userAgent,'AliApp(AP') != false,
|
||||
"Weibo" => strstr($userAgent,'Weibo') != false,
|
||||
"Douban" => strstr($userAgent,'com.douban.frodo') != false,
|
||||
"Suning" => strstr($userAgent,'SNEBUY-APP') != false,
|
||||
"iQiYi" => strstr($userAgent,'IqiyiApp') != false,
|
||||
"DingTalk" => strstr($userAgent,'DingTalk') != false,
|
||||
"Douyin" => strstr($userAgent,'aweme') != false,
|
||||
// 系统或平台
|
||||
"Windows" => strstr($userAgent,'Windows') != false,
|
||||
"Linux" => strstr($userAgent,'Linux') != false || strstr($userAgent,'X11') != false,
|
||||
"Mac OS" => strstr($userAgent,'Macintosh') != false,
|
||||
"Android" => strstr($userAgent,'Android') != false || strstr($userAgent,'Adr') != false,
|
||||
"HarmonyOS" => strstr($userAgent,'HarmonyOS') != false,
|
||||
"Ubuntu" => strstr($userAgent,'Ubuntu') != false,
|
||||
"FreeBSD" => strstr($userAgent,'FreeBSD') != false,
|
||||
"Debian" => strstr($userAgent,'Debian') != false,
|
||||
"Windows Phone" => strstr($userAgent,'IEMobile') != false || strstr($userAgent,'Windows Phone') != false,
|
||||
"BlackBerry" => strstr($userAgent,'BlackBerry') != false || strstr($userAgent,'RIM') != false,
|
||||
"MeeGo" => strstr($userAgent,'MeeGo') != false,
|
||||
"Symbian" => strstr($userAgent,'Symbian') != false,
|
||||
"iOS" => strstr($userAgent,'like Mac OS X') != false,
|
||||
"Chrome OS" => strstr($userAgent,'CrOS') != false,
|
||||
"WebOS" => strstr($userAgent,'hpwOS') != false,
|
||||
// 设备
|
||||
"Mobile" => strstr($userAgent,'Mobi') != false || strstr($userAgent,'iPh') != false || strstr($userAgent,'480') != false,
|
||||
"Tablet" => strstr($userAgent,'Tablet') != false || strstr($userAgent,'Pad') != false || strstr($userAgent,'Nexus 7') != false,
|
||||
];
|
||||
|
||||
// 部分修正 | 因typecho评论数据只存储了ua的信息,所以不能完全进行修正尤其是360相关浏览器
|
||||
if ($match['Baidu'] && $match['Opera']) $match['Baidu'] = false;
|
||||
if ($match['iOS']) $match['Safari'] = true;
|
||||
|
||||
// 基本信息
|
||||
$baseInfo = [
|
||||
"browser" => [
|
||||
'Safari', 'Chrome', 'Edge', 'IE', 'Firefox', 'Firefox Focus', 'Chromium',
|
||||
'Opera', 'Vivaldi', 'Yandex', 'Arora', 'Lunascape','QupZilla', 'Coc Coc',
|
||||
'Kindle', 'Iceweasel', 'Konqueror', 'Iceape','SeaMonkey', 'Epiphany', 'XiaoMi',
|
||||
'Vivo', 'OPPO', '360', '360SE','360EE', 'UC', 'QQBrowser', 'QQ', 'Huawei', 'Baidu',
|
||||
'Maxthon', 'Sogou', 'Liebao', '2345Explorer', '115Browser', 'TheWorld', 'Quark', 'Qiyu',
|
||||
'Wechat', 'WechatWork', 'Taobao', 'Alipay', 'Weibo', 'Douban', 'Suning', 'iQiYi', 'DingTalk', 'Douyin'
|
||||
],
|
||||
"system" => [
|
||||
'Windows', 'Linux', 'Mac OS', 'Android', 'HarmonyOS', 'Ubuntu',
|
||||
'FreeBSD', 'Debian', 'iOS', 'Windows Phone', 'BlackBerry', 'MeeGo',
|
||||
'Symbian', 'Chrome OS', 'WebOS'
|
||||
],
|
||||
"device" => ['Mobile', 'Tablet'],
|
||||
];
|
||||
|
||||
foreach ($baseInfo as $k => $v) {
|
||||
foreach ($v as $xv) {
|
||||
if ($match[$xv]) $deviceInfo[$k] = $xv;
|
||||
}
|
||||
}
|
||||
|
||||
// 操作系统版本信息
|
||||
$windowsVersion = [
|
||||
'10' => "10",
|
||||
'6.4' => '10',
|
||||
'6.3' => '8.1',
|
||||
'6.2' => '8',
|
||||
'6.1' => '7',
|
||||
'6.0' => 'Vista',
|
||||
'5.2' => 'XP',
|
||||
'5.1' => 'XP',
|
||||
'5.0' => '2000',
|
||||
];
|
||||
$wv = pregMatch("/^Mozilla\/\d.0 \(Windows NT ([\d.]+)[;)].*$/", $userAgent);
|
||||
$HarmonyOSVersion = [
|
||||
10 => "2",
|
||||
12 => "3"
|
||||
];
|
||||
$systemVersion = [
|
||||
"Windows" => $windowsVersion[$wv] ?? $wv,
|
||||
"Android" => pregMatch("/^.*Android ([\d.]+);.*$/", $userAgent),
|
||||
"HarmonyOS" => $HarmonyOSVersion[pregMatch("/^Mozilla.*Android ([\d.]+)[;)].*$/", $userAgent)] ?? '',
|
||||
"iOS" => preg_replace("/_/", '.', pregMatch("/^.*OS ([\d_]+) like.*$/", $userAgent)),
|
||||
"Debian" => pregMatch("/^.*Debian\/([\d.]+).*$/", $userAgent),
|
||||
"Windows Phone" => pregMatch("/^.*Windows Phone( OS)? ([\d.]+);.*$/", $userAgent),
|
||||
"Mac OS" => preg_replace("/_/", '.',pregMatch("/^.*Mac OS X ([\d_]+).*$/", $userAgent)),
|
||||
"WebOS" => pregMatch("/^.*hpwOS\/([\d.]+);.*$/", $userAgent)
|
||||
];
|
||||
|
||||
if ($systemVersion[$deviceInfo['system']]) {
|
||||
$deviceInfo['systemVersion'] = $systemVersion[$deviceInfo['system']];
|
||||
if ($deviceInfo['systemVersion'] == $userAgent) $deviceInfo['systemVersion'] = '';
|
||||
}
|
||||
|
||||
// if ($deviceInfo['system'] == 'Windows' && $_windowsVersion) $deviceInfo['systemVersion'] = $_windowsVersion;
|
||||
|
||||
|
||||
// 浏览器版本信息
|
||||
$browsers_360SE = [
|
||||
108 => '14.0',
|
||||
86 => '13.0',
|
||||
78 => '12.0',
|
||||
69 => '11.0',
|
||||
63 => '10.0',
|
||||
55 => '9.1',
|
||||
45 => '8.1',
|
||||
42 => '8.0',
|
||||
31 => '7.0',
|
||||
21 => '6.3',
|
||||
];
|
||||
$browsers_360EE = [
|
||||
95 => '21',
|
||||
86 => '13.0',
|
||||
78 => '12.0',
|
||||
69 => '11.0',
|
||||
63 => '9.5',
|
||||
55 => '9.0',
|
||||
50 => '8.7',
|
||||
30 => '7.5',
|
||||
];
|
||||
$browsers_liebao = [
|
||||
57 => '6.5',
|
||||
49 => '6.0',
|
||||
46 => '5.9',
|
||||
42 => '5.3',
|
||||
39 => '5.2',
|
||||
34 => '5.0',
|
||||
29 => '4.5',
|
||||
21 => '4.0'
|
||||
];
|
||||
$browsers_2345 = [
|
||||
69 => '10.0',
|
||||
55 => '9.9',
|
||||
69 => '10.0',
|
||||
55 => '9.9',
|
||||
69 => '10.0',
|
||||
55 => '9.9'
|
||||
];
|
||||
|
||||
$chromeVersion = pregMatch('/^.*Chrome\/([\d]+).*$/', $userAgent);
|
||||
|
||||
$browsersVersion = [
|
||||
"Safari" => pregMatch("/^.*Version\/([\d.]+).*$/", $userAgent),
|
||||
"Chrome" => pregMatch("/^.*Chrome\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*CriOS\/([\d.]+).*$/", $userAgent),
|
||||
"IE" => pregMatch("/^.*MSIE ([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*rv:([\d.]+).*$/", $userAgent),
|
||||
"Edge" => pregMatch("/^.*Edge\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*Edg\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*EdgA\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*EdgiOS\/([\d.]+).*$/", $userAgent),
|
||||
"Firefox" => pregMatch("/^.*Firefox\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*FxiOS\/([\d.]+).*$/", $userAgent),
|
||||
"Firefox Focus" => pregMatch("/^.*Focus\/([\d.]+).*$/", $userAgent),
|
||||
"Chromium" => pregMatch("/^.*Chromium\/([\d.]+).*$/", $userAgent),
|
||||
"Opera" => pregMatch("/^.*Opera\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*OPR\/([\d.]+).*$/", $userAgent),
|
||||
"Vivaldi" => pregMatch("/^.*Vivaldi\/([\d.]+).*$/", $userAgent),
|
||||
"Yandex" => pregMatch("/^.*YaBrowser\/([\d.]+).*$/", $userAgent),
|
||||
"Brave" => pregMatch("/^.*Chrome\/([\d.]+).*$/", $userAgent),
|
||||
"Arora" => pregMatch("/^.*Arora\/([\d.]+).*$/", $userAgent),
|
||||
"Lunascape" => pregMatch("/^.*Lunascape[\/\s]([\d.]+).*$/", $userAgent),
|
||||
"QupZilla" => pregMatch("/^.*QupZilla[\/\s]([\d.]+).*$/", $userAgent),
|
||||
"Coc Coc" => pregMatch("/^.*coc_coc_browser\/([\d.]+).*$/", $userAgent),
|
||||
"Kindle" => pregMatch("/^.*Version\/([\d.]+).*$/", $userAgent),
|
||||
"Iceweasel" => pregMatch("/^.*Iceweasel\/([\d.]+).*$/", $userAgent),
|
||||
"Konqueror" => pregMatch("/^.*Konqueror\/([\d.]+).*$/", $userAgent),
|
||||
"Iceape" => pregMatch("/^.*Iceape\/([\d.]+).*$/", $userAgent),
|
||||
"SeaMonkey" => pregMatch("/^.*SeaMonkey\/([\d.]+).*$/", $userAgent),
|
||||
"Epiphany" => pregMatch("/^.*Epiphany\/([\d.]+).*$/", $userAgent),
|
||||
"360" => pregMatch("/^.*QihooBrowser(HD)?\/([\d.]+).*$/", $userAgent),
|
||||
"Maxthon" => pregMatch("/^.*Maxthon\/([\d.]+).*$/", $userAgent),
|
||||
"QQBrowser" => pregMatch("/^.*QQBrowser\/([\d.]+).*$/", $userAgent),
|
||||
"QQ" => pregMatch("/^.*QQ\/([\d.]+).*$/", $userAgent),
|
||||
"Baidu" => pregMatch("/^.*BIDUBrowser[\s\/]([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*baiduboxapp\/([\d.]+).*$/", $userAgent),
|
||||
"UC" => pregMatch("/^.*UC?Browser\/([\d.]+).*$/", $userAgent),
|
||||
"Sogou" => pregMatch("/^.*SE ([\d.X]+).*$/", $userAgent) ?? pregMatch("/^.*SogouMobileBrowser\/([\d.]+).*$/", $userAgent),
|
||||
"115Browser" => pregMatch("/^.*115Browser\/([\d.]+).*$/", $userAgent),
|
||||
"TheWorld" => pregMatch("/^.*TheWorld ([\d.]+).*$/", $userAgent),
|
||||
"XiaoMi" => pregMatch("/^.*MiuiBrowser\/([\d.]+).*$/", $userAgent),
|
||||
"Vivo" => pregMatch("/^.*VivoBrowser\/([\d.]+).*$/", $userAgent),
|
||||
"OPPO" => pregMatch("/^.*HeyTapBrowser\/([\d.]+).*$/", $userAgent),
|
||||
"Quark" => pregMatch("/^.*Quark\/([\d.]+).*$/", $userAgent),
|
||||
"Qiyu" => pregMatch("/^.*Qiyu\/([\d.]+).*$/", $userAgent),
|
||||
"Wechat" => pregMatch("/^.*MicroMessenger\/([\d.]+).*$/", $userAgent),
|
||||
"WechatWork" => pregMatch("/^.*wxwork\/([\d.]+).*$/", $userAgent),
|
||||
"Taobao" => pregMatch("/^.*AliApp\(TB\/([\d.]+).*$/", $userAgent),
|
||||
"Alipay" => pregMatch("/^.*AliApp\(AP\/([\d.]+).*$/", $userAgent),
|
||||
"Weibo" => pregMatch("/^.*weibo__([\d.]+).*$/", $userAgent),
|
||||
"Douban" => pregMatch("/^.*com.douban.frodo\/([\d.]+).*$/", $userAgent),
|
||||
"Suning" => pregMatch("/^.*SNEBUY-APP([\d.]+).*$/", $userAgent),
|
||||
"iQiYi" => pregMatch("/^.*IqiyiVersion\/([\d.]+).*$/", $userAgent),
|
||||
"DingTalk" => pregMatch("/^.*DingTalk\/([\d.]+).*$/", $userAgent),
|
||||
"Douyin" => pregMatch("/^.*app_version\/([\d.]+).*$/", $userAgent),
|
||||
"Huawei" => pregMatch("/^.*Version\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*HuaweiBrowser\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*HBPC\/([\d.]+).*$/", $userAgent),
|
||||
"360SE" => $browsers_360SE[$chromeVersion] ?? '',
|
||||
"360EE" => $browsers_360EE[$chromeVersion] ?? '',
|
||||
"Liebao" => pregMatch("/^.*LieBaoFast\/([\d.]+).*$/", $userAgent) ?? $browsers_liebao[$chromeVersion],
|
||||
"2345Explorer" => $browsers_2345[$chromeVersion] ?? pregMatch("/^.*2345Explorer\/([\d.]+).*$/", $userAgent) ?? pregMatch("/^.*Mb2345Browser\/([\d.]+).*$/", $userAgent),
|
||||
];
|
||||
|
||||
|
||||
if ($browsersVersion[$deviceInfo['browser']]) {
|
||||
$deviceInfo["version"] = $browsersVersion[$deviceInfo['browser']];
|
||||
if ($deviceInfo["version"] == $userAgent) $deviceInfo['version'] = '';
|
||||
}
|
||||
|
||||
// 修正浏览器版本信息
|
||||
$chrome = pregMatch('/\S+Browser/', $userAgent);
|
||||
if ($deviceInfo['browser'] == 'Chrome' && $chrome) {
|
||||
$deviceInfo['browser'] = $chrome;
|
||||
$deviceInfo['version'] = pregMatch('/^.*Browser\/([\d.]+).*$/', $userAgent);
|
||||
}
|
||||
|
||||
return $deviceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回符合正则的值
|
||||
*
|
||||
* @param string $reg 正则
|
||||
* @param string $sourceData 源数据
|
||||
* @return mixed|void
|
||||
*/
|
||||
function pregMatch($reg, $sourceData) {
|
||||
if (preg_match($reg, $sourceData, $mat)) return $mat[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备和浏览器的图标
|
||||
* @param array $data 设备信息数组
|
||||
* @return array 包含系统和浏览器图标的数组
|
||||
*/
|
||||
function getDeviceIcon($data) {
|
||||
// 系统图标映射
|
||||
$systemIcons = [
|
||||
'Windows' => '<i class="fa-brands fa-windows"></i>',
|
||||
'Linux' => '<i class="fa-brands fa-linux"></i>',
|
||||
'Mac OS' => '<i class="fa-brands fa-apple"></i>',
|
||||
'Android' => '<i class="fa-brands fa-android"></i>',
|
||||
'iOS' => '<i class="fa-brands fa-apple"></i>',
|
||||
'HarmonyOS' => '<i class="fa fa-mobile"></i>', // HarmonyOS 使用手机图标
|
||||
'Chrome OS' => '<i class="fa-brands fa-chrome"></i>',
|
||||
'' => '<i class="fa fa-desktop"></i>' // 未知系统使用桌面图标
|
||||
];
|
||||
|
||||
// 浏览器图标映射
|
||||
$browserIcons = [
|
||||
'Chrome' => '<i class="fa-brands fa-chrome"></i>',
|
||||
'Firefox' => '<i class="fa-brands fa-firefox"></i>',
|
||||
'Safari' => '<i class="fa-brands fa-safari"></i>',
|
||||
'Edge' => '<i class="fa-brands fa-edge"></i>',
|
||||
'IE' => '<i class="fa-brands fa-internet-explorer"></i>',
|
||||
'Opera' => '<i class="fa-brands fa-opera"></i>',
|
||||
'QQ' => '<i class="fa-brands fa-qq"></i>',
|
||||
'Wechat' => '<i class="fa-brands fa-weixin"></i>',
|
||||
'Weibo' => '<i class="fa-brands fa-weibo"></i>',
|
||||
'' => '<i class="fa fa-globe"></i>' // 未知浏览器使用地球图标
|
||||
];
|
||||
|
||||
// 设备类型图标
|
||||
$deviceIcons = [
|
||||
'Mobile' => 'fa fa-mobile',
|
||||
'Tablet' => 'fa fa-tablet',
|
||||
'PC' => 'fa fa-desktop'
|
||||
];
|
||||
|
||||
// 获取对应图标,如果没有匹配则使用默认图标
|
||||
$systemIcon = $systemIcons[$data['system']] ?? $systemIcons[''];
|
||||
$browserIcon = $browserIcons[$data['browser']] ?? $browserIcons[''];
|
||||
$deviceIcon = $deviceIcons[$data['device']] ?? $deviceIcons['PC'];
|
||||
|
||||
return [
|
||||
'system' => $systemIcon,
|
||||
'browser' => $browserIcon,
|
||||
'device' => $deviceIcon
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论者认证等级 + 身份
|
||||
*
|
||||
* @author Chrison
|
||||
* @access public
|
||||
* @param str $email 评论者邮址
|
||||
* @return result
|
||||
*/
|
||||
function commentApprove($widget, $email = NULL)
|
||||
{
|
||||
$result = array(
|
||||
"state" => -1,//状态
|
||||
"isAuthor" => 0,//是否是博主
|
||||
"userLevel" => '',//用户身份或等级名称
|
||||
"userDesc" => '',//用户title描述
|
||||
"bgColor" => '',//用户身份或等级背景色
|
||||
"commentNum" => 0//评论数量
|
||||
);
|
||||
if (empty($email)) return $result;
|
||||
$result['state'] = 1;
|
||||
if ($widget->authorId == $widget->ownerId) {
|
||||
$result['isAuthor'] = 1;//」
|
||||
$result['userLevel'] = '博主';
|
||||
$result['userDesc'] = '本站站长';
|
||||
$result['bgColor'] = '#FFD67A';
|
||||
$result['commentNum'] = 999;
|
||||
} else {
|
||||
try {
|
||||
//数据库获取
|
||||
$db = Typecho_Db::get();
|
||||
//获取评论条数
|
||||
$commentNumSql = $db->fetchAll($db->select(array('COUNT(cid)'=>'commentNum'))
|
||||
->from('table.comments')
|
||||
->where('mail = ?', $email));
|
||||
$commentNum = $commentNumSql[0]['commentNum'];
|
||||
//获取友情链接
|
||||
$linkSql = $db->fetchAll($db->select()->from('table.links')
|
||||
->where('user = ?',$email));
|
||||
//等级判定
|
||||
if($commentNum==1){
|
||||
$result['userLevel'] = '初见 LV.1';
|
||||
$result['bgColor'] = '#999999';
|
||||
$userDesc = '人生一大步!';
|
||||
} else {
|
||||
if ($commentNum<10 && $commentNum>1) {
|
||||
$result['userLevel'] = '初识 LV.2';
|
||||
$result['bgColor'] = '#999999';
|
||||
}elseif ($commentNum<20 && $commentNum>=10) {
|
||||
$result['userLevel'] = '相识 LV.3';
|
||||
$result['bgColor'] = '#A0DAD0';
|
||||
}elseif ($commentNum<40 && $commentNum>=20) {
|
||||
$result['userLevel'] = '熟识 LV.4';
|
||||
$result['bgColor'] = '#A0DAD0';
|
||||
}elseif ($commentNum<80 && $commentNum>=40) {
|
||||
$result['userLevel'] = '好友 LV.5';
|
||||
$result['bgColor'] = '#A0DAD0';
|
||||
}elseif ($commentNum<160 && $commentNum>=80) {
|
||||
$result['userLevel'] = '知己 LV.6';
|
||||
$result['bgColor'] = '#A0DAD0';
|
||||
}elseif ($commentNum>=160) {
|
||||
$result['userLevel'] = '挚友 LV.7';
|
||||
$result['bgColor'] = '#A0DAD0';
|
||||
}
|
||||
$userDesc = '您在本站有'.$commentNum.'条留言!';
|
||||
}
|
||||
if($linkSql){
|
||||
$result['userLevel'] = '「博友」';
|
||||
$result['bgColor'] = '#21b9bb';
|
||||
$userDesc = '🔗'.$linkSql[0]['description'].' ✌️'.$userDesc;
|
||||
}
|
||||
|
||||
$result['userDesc'] = $userDesc;
|
||||
$result['commentNum'] = $commentNum;
|
||||
} catch (Exception $e) {
|
||||
error_log('Error in commentApprove function: ' . $e->getMessage());
|
||||
// 设置默认值
|
||||
$result['userLevel'] = '「访客」';
|
||||
$result['bgColor'] = '#999999';
|
||||
$result['userDesc'] = '欢迎留言';
|
||||
$result['commentNum'] = 0;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子评论加上@用户名
|
||||
*
|
||||
* @param int $coid 评论ID
|
||||
* @return string 评论的永久链接
|
||||
*/
|
||||
function getPermalinkFromCoid($coid) {
|
||||
$db = Typecho_Db::get();
|
||||
$row = $db->fetchRow($db->select('author')->from('table.comments')->where('coid = ? AND status = ?', $coid, 'approved'));
|
||||
if (empty($row)) return '';
|
||||
return '<a href="#comment-'.$coid.'" class="c-sub">@'.$row['author'].'</a>';
|
||||
}
|
||||
/**
|
||||
* 全部标签按字母书序排列
|
||||
*/
|
||||
// 获取中文字符首字母的函数
|
||||
function getFirstChar($str) {
|
||||
if (empty($str)) return '#';
|
||||
|
||||
// 如果是数字
|
||||
if (is_numeric($str[0])) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// 如果是英文字母
|
||||
$firstChar = ord($str[0]);
|
||||
if ($firstChar >= ord('A') && $firstChar <= ord('z')) {
|
||||
return strtoupper($str[0]);
|
||||
}
|
||||
|
||||
// 如果是中文
|
||||
$s = iconv('UTF-8', 'gb2312', $str);
|
||||
if (strlen($s) < 2) return '#';
|
||||
|
||||
$asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
|
||||
if ($asc >= -20319 && $asc <= -20284) return 'A';
|
||||
if ($asc >= -20283 && $asc <= -19776) return 'B';
|
||||
if ($asc >= -19775 && $asc <= -19219) return 'C';
|
||||
if ($asc >= -19218 && $asc <= -18711) return 'D';
|
||||
if ($asc >= -18710 && $asc <= -18527) return 'E';
|
||||
if ($asc >= -18526 && $asc <= -18240) return 'F';
|
||||
if ($asc >= -18239 && $asc <= -17923) return 'G';
|
||||
if ($asc >= -17922 && $asc <= -17418) return 'H';
|
||||
if ($asc >= -17417 && $asc <= -16475) return 'J';
|
||||
if ($asc >= -16474 && $asc <= -16213) return 'K';
|
||||
if ($asc >= -16212 && $asc <= -15641) return 'L';
|
||||
if ($asc >= -15640 && $asc <= -15166) return 'M';
|
||||
if ($asc >= -15165 && $asc <= -14923) return 'N';
|
||||
if ($asc >= -14922 && $asc <= -14915) return 'O';
|
||||
if ($asc >= -14914 && $asc <= -14631) return 'P';
|
||||
if ($asc >= -14630 && $asc <= -14150) return 'Q';
|
||||
if ($asc >= -14149 && $asc <= -14091) return 'R';
|
||||
if ($asc >= -14090 && $asc <= -13319) return 'S';
|
||||
if ($asc >= -13318 && $asc <= -12839) return 'T';
|
||||
if ($asc >= -12838 && $asc <= -12557) return 'W';
|
||||
if ($asc >= -12556 && $asc <= -11848) return 'X';
|
||||
if ($asc >= -11847 && $asc <= -11056) return 'Y';
|
||||
if ($asc >= -11055 && $asc <= -10247) return 'Z';
|
||||
return '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含index.php
|
||||
*/
|
||||
function get_correct_url($path) {
|
||||
// 获取当前请求的URI
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
|
||||
// 检查是否包含index.php
|
||||
$isIndexPhp = strpos($requestUri, '/index.php/') !== false;
|
||||
|
||||
// 获取站点URL
|
||||
$siteUrl = Helper::options()->siteUrl;
|
||||
|
||||
// 如果是/index.php/结构
|
||||
if ($isIndexPhp) {
|
||||
return $siteUrl . 'index.php' . $path;
|
||||
}
|
||||
|
||||
return $siteUrl . ltrim($path, '/');
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta http-equiv='content-language' content='zh_CN'>
|
||||
<title><?php $this->archiveTitle([
|
||||
'category' => _t('分类 %s 下的文章'),
|
||||
'search' => _t('包含关键字 %s 的文章'),
|
||||
'tag' => _t('标签 %s 下的文章'),
|
||||
'author' => _t('%s 发布的文章')
|
||||
], '', ' - '); ?><?php $this->options->title(); ?></title>
|
||||
<link rel="canonical" href="<?php $this->options->siteUrl(); ?>">
|
||||
<meta name='robots' content='max-image-preview:large' />
|
||||
<style id='puock-inline-css' type='text/css'>
|
||||
body {
|
||||
--pk-c-primary: #A7E6F4
|
||||
}
|
||||
:root {
|
||||
--puock-block-not-tran: 100%
|
||||
}
|
||||
* {
|
||||
font-family: "LXGW WenKai", sans-serif;
|
||||
}
|
||||
</style>
|
||||
<?php if ($this->options->icoUrl): ?>
|
||||
<link rel="icon" href="<?php $this->options->icoUrl() ?>" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" href="<?php $this->options->icoUrl() ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="<?php $this->options->themeUrl('assets/css/style.css'); ?>">
|
||||
<script src='<?php $this->options->themeUrl('assets/js/jquery.min.js'); ?>' type="text/javascript"></script>
|
||||
<!-- 通过自有函数输出HTML头部信息 -->
|
||||
<?php $this->header(); ?>
|
||||
</head>
|
||||
|
||||
<body class="puock-auto custom-background">
|
||||
<div>
|
||||
<div id="header-box" class="animated fadeInDown"></div>
|
||||
<header id="header" class="animated fadeInDown blur">
|
||||
<div class="navbar navbar-dark shadow-sm">
|
||||
<div class="container">
|
||||
<?php if($this->options->logoUrl): ?>
|
||||
<a href="<?php $this->options->siteUrl(); ?>" id="logo" class="navbar-brand logo-loop-light">
|
||||
<img id="logo-light" alt="logo" class="w-100 " src="<?php $this->options->logoUrl() ?>">
|
||||
<img id="logo-dark" alt="logo" class="w-100 d-none" src="<?php $this->options->logoUrl() ?>">
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="<?php $this->options->siteUrl(); ?>" id="logo" class="navbar-brand logo-loop-light">
|
||||
<span class="puock-text txt-logo"><?php $this->options->title(); ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<div class="d-none d-lg-block puock-links">
|
||||
<div id="menus" class="t-md ">
|
||||
<ul>
|
||||
<?php if ($this->is('index')): ?>
|
||||
<li class="menu-current current-menu-item"><?php else: ?><li><?php endif; ?>
|
||||
<a class="nav-link" href="<?php $this->options->siteUrl(); ?>">
|
||||
<?php _e('首页'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li class='menu-item menu-item-type-post_type menu-item-object-page '>
|
||||
<a class='ww' data-color='auto' href='#'>分类<i class="fa fa-chevron-down t-sm ml-1 menu-sub-icon"></i></a>
|
||||
<ul class="sub-menu ">
|
||||
<?php $categories = Typecho_Widget::widget('Widget_Metas_Category_List'); ?>
|
||||
<?php while($categories->next()): ?>
|
||||
<li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-child">
|
||||
<a href="<?php $categories->permalink(); ?>" class='ww' data-color='auto'>
|
||||
<?php $categories->name(); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
</ul>
|
||||
</li>
|
||||
<?php \Widget\Contents\Page\Rows::alloc()->to($pages); ?>
|
||||
<?php while ($pages->next()): ?>
|
||||
<li <?php if ($this->is('page', $pages->slug)): ?> class='current-menu-item current_page_item menu-current<?php endif; ?> menu-item menu-item-type-post_type menu-item-object-page '>
|
||||
<a class='ww'
|
||||
href="<?php $pages->permalink(); ?>"
|
||||
title="<?php $pages->title(); ?>">
|
||||
<?php $pages->title(); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
<li><a class="colorMode" data-bs-toggle="tooltip" title="模式切换" href="javascript:void(0)"><i class="fa fa-circle-half-stroke"></i></a></li>
|
||||
<li><a class="search-modal-btn" data-bs-toggle="tooltip" title="搜索" href="javascript:void(0)"><i class="fa fa-search"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-menus d-block d-lg-none p-1 puock-text">
|
||||
<i class="fa fa-bars t-md mr-2 mobile-menu-s"></i>
|
||||
<i class="fa fa-circle-half-stroke colorMode t-md mr-2"></i>
|
||||
<i class="search-modal-btn fa fa-search t-md position-relative" style="top:0.5px"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div id="search" class="d-none">
|
||||
<div class="w-100 d-flex justify-content-center">
|
||||
<div id="search-main" class="container p-block">
|
||||
<form class="global-search-form" action="<?php $this->options->siteUrl(); ?>">
|
||||
<div class="search-layout">
|
||||
<div class="search-input"> <input required type="text" name="s" class="form-control" placeholder="请输入搜索关键字"> </div>
|
||||
<div class="search-start"> <button type="submit" class="btn-dark btn"><i class="fa fa-search mr-1"></i>搜索</button> </div>
|
||||
<div class="search-close-btn"> <button type="button" class="btn-danger btn ml-1 search-modal-btn"><i class="fa fa-close"></i></button> </div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile-menu" class="d-none">
|
||||
<div class="menus">
|
||||
<div class="p-block">
|
||||
<div class="text-end"><i class="fa fa-close t-xl puock-link mobile-menu-close ta3"></i></div>
|
||||
<nav>
|
||||
<ul class='puock-links t-md'>
|
||||
<li class='menu-item menu-item-type-custom menu-item-object-custom current-menu-item current_page_item menu-item-home menu-current'>
|
||||
<span>
|
||||
<a href="<?php $this->options->siteUrl(); ?>">首页</a>
|
||||
</span>
|
||||
</li>
|
||||
<?php \Widget\Contents\Page\Rows::alloc()->to($pages); ?>
|
||||
<?php while ($pages->next()): ?>
|
||||
<li class='menu-item menu-item-type-post_type menu-item-object-page'>
|
||||
<span><a class='ww' href="<?php $pages->permalink(); ?>" title="<?php $pages->title(); ?>">
|
||||
<?php $pages->title(); ?>
|
||||
</a></span>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
<li class='menu-item menu-item-type-post_type menu-item-object-page'>
|
||||
<span><a href="#">分类</a>
|
||||
<a href="#menu-sub-689" data-bs-toggle="collapse"><i class="fa fa-chevron-down t-sm ml-1 menu-sub-icon"></i></a></span>
|
||||
<ul id="menu-sub-689" class="sub-menu collapse">
|
||||
<?php $categories = Typecho_Widget::widget('Widget_Metas_Category_List'); ?>
|
||||
<?php while($categories->next()): ?>
|
||||
<li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-child">
|
||||
<span>
|
||||
<a href="<?php $categories->permalink(); ?>" class='ww' data-color='auto'>
|
||||
<?php $categories->name(); ?>
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
</ul>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile-menu-backdrop" class="modal-backdrop d-none"></div>
|
||||
<div id="search-backdrop" class="modal-backdrop d-none"></div>
|
||||
<div id="content" class="mt15 container"> <!--全局上方-->
|
||||
<?php if($this->options->adlisttop): ?>
|
||||
<div class="puock-text p-block t-md ad-global-top"><?php $this->options->adlisttop(); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if($this->options->gonggao): ?>
|
||||
<div class="puock-text p-block t-md global-top-notice">
|
||||
<div data-swiper="init" data-swiper-class="global-top-notice-swiper" data-swiper-args='{"direction":"vertical","autoplay":{"delay":3000,"disableOnInteraction":false},"loop":true}'>
|
||||
<div class="swiper global-top-notice-swiper">
|
||||
<div class="swiper-wrapper">
|
||||
<div class="swiper-slide t-line-1">
|
||||
<a class="ta3" data-no-instant href="javascript:void(0)">
|
||||
<span class="notice-icon"><i class="fa-regular fa-bell"></i></span>
|
||||
<span><?php $this->options->gonggao(); ?></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
/**
|
||||
* Pouck theme for Typecho
|
||||
*
|
||||
* @package Typecho Pouck Theme
|
||||
* @author 老孙博客
|
||||
* @version 1.0
|
||||
* @link http://www.imsun.org
|
||||
*/
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
||||
$this->need('header.php');
|
||||
$this->need('sticky.php');
|
||||
?>
|
||||
<div class="row row-cols-1">
|
||||
<div class="col-lg-8 col-md-12 animated fadeInLeft ">
|
||||
<div class="animated fadeInLeft ">
|
||||
<div> <!--文章列表-->
|
||||
<div id="posts">
|
||||
<div class=" mr-0 ml-0">
|
||||
<?php while ($this->next()): ?>
|
||||
<?php
|
||||
$coverImage = getPostCover($this->content, $this->cid);
|
||||
?>
|
||||
<article class="block card-plain post-item p-block post-item-list">
|
||||
<div class="thumbnail">
|
||||
<a class="t-sm ww" href="<?php $this->permalink() ?>">
|
||||
<img title="<?php $this->title() ?>" alt="<?php $this->title() ?>" src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>' class='lazy' data-src='<?php echo $coverImage; ?>' />
|
||||
</a>
|
||||
</div>
|
||||
<div class="post-info">
|
||||
<div class="info-top">
|
||||
<h2 class="info-title">
|
||||
<?php if (isset($this->isSticky) && $this->isSticky): ?><?php echo $this->stickyHtml; ?><?php endif; ?>
|
||||
<?php foreach($this->categories as $category): ?>
|
||||
<a class="badge d-none d-md-inline-block bg-primary ahfff" href="<?php echo $category['permalink']; ?>">
|
||||
<i class="fa-regular fa-folder-open"></i> <?php echo $category['name']; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<a class="a-link" title="<?php $this->title() ?>" href="<?php $this->permalink() ?>"><?php $this->title() ?></a>
|
||||
</h2>
|
||||
<div class="info-meta c-sub text-2line d-none d-md-block"><?php $this->excerpt(200, '...'); ?></div>
|
||||
</div>
|
||||
<div class="info-footer w-100">
|
||||
<div>
|
||||
<span class="t-sm c-sub">
|
||||
<span class="mr-2">
|
||||
<i class="fa-regular fa-eye mr-1"></i>
|
||||
<span class="view">浏览:<?php get_post_view($this) ?></span>
|
||||
<span class="t-sm d-none d-sm-inline-block">次阅读</span>
|
||||
</span>
|
||||
<a class="c-sub-a" href="<?php $this->permalink() ?>#comments">
|
||||
<i class="fa-regular fa-comment mr-1"></i><?php $this->commentsNum('0', '1', '%d'); ?>
|
||||
<span class="t-sm d-none d-sm-inline-block">个评论</span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<?php foreach($this->categories as $category): ?>
|
||||
<a class="c-sub-a t-sm ml-md-2 line-h-20 d-inline-block d-md-none" href="<?php echo $category['permalink']; ?>">
|
||||
<i class="fa-regular fa-folder-open"></i> <?php echo $category['name']; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<span class="t-sm ml-md-2 c-sub line-h-20 d-none d-md-inline-block">
|
||||
<i class="fa-regular fa-clock"></i> <?php $this->date(); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="title-l-c bg-primary"></span>
|
||||
</article>
|
||||
<?php endwhile; ?>
|
||||
<?php
|
||||
$pageprev = $this->options->pageprev ?? '0';
|
||||
if ($pageprev == '1' && $this->have()):
|
||||
?>
|
||||
<div class="mt20 p-flex-s-right" data-no-instant>
|
||||
<?php $this->pageNav('«', '»', 1, '...', array(
|
||||
'wrapTag' => 'ul',
|
||||
'wrapClass' => 'pagination comment-ajax-load',
|
||||
'itemTag' => 'li',
|
||||
'textTag' => 'span',
|
||||
'currentClass' => 'active',
|
||||
'prevClass' => 'prev',
|
||||
'nextClass' => 'next'
|
||||
)); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,4 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
/.idea
|
||||
/vendor/
|
||||
/composer.lock
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* class Ip2Region
|
||||
* 为兼容老版本调度而创建
|
||||
* @author Anyon<zoujingli@qq.com>
|
||||
* @datetime 2022/07/18
|
||||
*/
|
||||
class Ip2Region
|
||||
{
|
||||
/**
|
||||
* 查询实例对象
|
||||
* @var XdbSearcher
|
||||
*/
|
||||
private $searcher;
|
||||
|
||||
/**
|
||||
* 初始化构造方法
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
class_exists('XdbSearcher') or include __DIR__ . '/XdbSearcher.php';
|
||||
$this->searcher = XdbSearcher::newWithFileOnly(__DIR__ . '/ip2region.xdb');
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容原 memorySearch 查询
|
||||
* @param string $ip
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function memorySearch($ip)
|
||||
{
|
||||
return ['city_id' => 0, 'region' => $this->searcher->search($ip)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容原 binarySearch 查询
|
||||
* @param string $ip
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function binarySearch($ip)
|
||||
{
|
||||
return $this->memorySearch($ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容原 btreeSearch 查询
|
||||
* @param string $ip
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function btreeSearch($ip)
|
||||
{
|
||||
return $this->memorySearch($ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接查询并返回名称
|
||||
* @param string $ip
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function simple($ip)
|
||||
{
|
||||
$geo = $this->memorySearch($ip);
|
||||
$arr = explode('|', str_replace(['0|'], '|', isset($geo['region']) ? $geo['region'] : ''));
|
||||
if (($last = array_pop($arr)) === '内网IP') $last = '';
|
||||
return join('', $arr) . (empty($last) ? '' : "【{$last}】");
|
||||
}
|
||||
|
||||
/**
|
||||
* destruct method
|
||||
* resource destroy
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->searcher->close();
|
||||
unset($this->searcher);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
==========================================================================
|
||||
The following license applies to the ip2region library
|
||||
--------------------------------------------------------------------------
|
||||
Copyright (c) 2015 Lionsoul<chenxin619315@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,229 @@
|
|||
[](https://packagist.org/packages/zoujingli/ip2region)
|
||||
[](https://packagist.org/packages/zoujingli/ip2region)
|
||||
[](https://packagist.org/packages/zoujingli/ip2region)
|
||||
[](https://packagist.org/packages/zoujingli/ip2region)
|
||||
[](https://packagist.org/packages/ip2region)
|
||||
[](https://packagist.org/packages/zoujingli/ip2region)
|
||||
|
||||
本库基于 [ip2region](https://github.com/lionsoul2014/ip2region) 简单整合,方便使用 `Composer` 管理。
|
||||
|
||||
# Ip2region 是什么
|
||||
|
||||
ip2region v2.0 - 是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率,提供了众多主流编程语言的 `xdb` 数据生成和查询客户端实现。
|
||||
|
||||
# Ip2region 特性
|
||||
|
||||
### 1、标准化的数据格式
|
||||
|
||||
每个 ip 数据段的 region 信息都固定了格式:`国家|区域|省份|城市|ISP`,只有中国的数据绝大部分精确到了城市,其他国家部分数据只能定位到国家,后前的选项全部是0。
|
||||
|
||||
### 2、数据去重和压缩
|
||||
|
||||
`xdb` 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ip2region.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。
|
||||
|
||||
### 3、极速查询响应
|
||||
|
||||
即使是完全基于 `xdb` 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
|
||||
|
||||
1. `vIndex` 索引缓存 :使用固定的 `512KiB` 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间。
|
||||
2. `xdb` 整个文件缓存:将整个 `xdb` 文件全部加载到内存,内存占用等同于 `xdb` 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。
|
||||
|
||||
### 4、IP 数据管理框架
|
||||
|
||||
v2.0 格式的 `xdb` 支持亿级别的 IP 数据段行数,region 信息也可以完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。
|
||||
|
||||
# `xdb` 数据查询
|
||||
|
||||
API 介绍,使用文档和测试程序请参考对应 `searcher` 查询客户端下的 ReadMe 介绍,全部查询 binding 实现情况如下:
|
||||
|
||||
| Ok? | 状态 | 编程语言 | 描述 | 贡献者 |
|
||||
|:-------------------|:----|:----------------------------------------------------------------------------------|:---------------------|:------------------------------------------|
|
||||
| :white_check_mark: | 已完成 | [golang](https://github.com/lionsoul2014/ip2region/blob/master/binding/golang) | golang xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [php](https://github.com/lionsoul2014/ip2region/blob/master/binding/php) | php xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [java](https://github.com/lionsoul2014/ip2region/blob/master/binding/java) | java xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [lua](https://github.com/lionsoul2014/ip2region/blob/master/binding/lua) | 纯 lua xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [c](https://github.com/lionsoul2014/ip2region/blob/master/binding/c) | ANSC c xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [lua_c](https://github.com/lionsoul2014/ip2region/blob/master/binding/lua_c) | lua c 扩展 xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| | 待开始 | [rust](https://github.com/lionsoul2014/ip2region/blob/master/binding/rust) | rust xdb 查询客户端实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [python](https://github.com/lionsoul2014/ip2region/blob/master/binding/python) | python xdb 查询客户端实现 | [厉害的花花](https://github.com/luckydog6132) |
|
||||
| :white_check_mark: | 已完成 | [nodejs](https://github.com/lionsoul2014/ip2region/blob/master/binding/nodejs) | nodejs xdb 查询客户端实现 | [Wu Jian Ping](https://github.com/wujjpp) |
|
||||
| :white_check_mark: | 已完成 | [csharp](https://github.com/lionsoul2014/ip2region/blob/master/binding/csharp) | csharp xdb 查询客户端实现 | [Alen Lee](https://github.com/malus2077) |
|
||||
| | 待开始 | [php_ext](https://github.com/lionsoul2014/ip2region/blob/master/binding/php7_ext) | php c 扩展 xdb 查询客户端实现 | 待确定 |
|
||||
| | 待开始 | [nginx](https://github.com/lionsoul2014/ip2region/blob/master/binding/nginx) | nginx 扩展 xdb 查询客户端实现 | 待确定 |
|
||||
|
||||
# `xdb` 数据生成
|
||||
|
||||
API 介绍,使用文档和测试程序请参考对应 `maker` 生成程序下的 ReadMe 介绍,全部生成 maker 实现情况如下:
|
||||
|
||||
| Ok? | 状态 | 编程语言 | 描述 | 贡献者 |
|
||||
|:-------------------|:----|:-----------------------------------------------------------------------------|:------------------|:-----------------------------------------|
|
||||
| :white_check_mark: | 已完成 | [golang](https://github.com/lionsoul2014/ip2region/blob/master/maker/golang) | golang xdb 生成程序实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [java](https://github.com/lionsoul2014/ip2region/blob/master/maker/java) | java xdb 生成程序实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| | 待开始 | [c](https://github.com/lionsoul2014/ip2region/blob/master/maker/c) | ANSC c xdb 生成程序实现 | [Lion](https://github.com/lionsoul2014) |
|
||||
| :white_check_mark: | 已完成 | [python](https://github.com/lionsoul2014/ip2region/blob/master/maker/python) | python xdb 生成程序实现 | [leolin49](https://github.com/leolin49) |
|
||||
| :white_check_mark: | 已完成 | [csharp](https://github.com/lionsoul2014/ip2region/blob/master/maker/csharp) | csharp xdb 生成程序实现 | [Alan Lee](https://github.com/malus2077) |
|
||||
|
||||
# 并发查询必读
|
||||
|
||||
全部查询客户端的 search 接口都 <b>不是</b> 并发安全的实现,不同进程/线程/协程需要通过创建不同的查询对象来安全使用,并发量很大的情况下,基于文件查询的方式可能会导致打开文件数过多的错误,请修改内核的最大允许打开文件数(fs.file-max=一个更高的值),或者将整个xdb加载到内存进行安全并发使用。
|
||||
|
||||
# 相关备注
|
||||
|
||||
### 1、使用声明
|
||||
|
||||
ip2region 重点在于<b>研究 IP 定位数据的存储设计和各种语言的查询实现</b>,并没有原始 IP 数据的支撑,本项目不保证及时的数据更新,没有也不会有商用版本,你可以使用自定义的数据导入 ip2region 进行管理。
|
||||
|
||||
### 2、技术交流
|
||||
|
||||
ip2region 微信交流群,请先加微信:lionsoul2014 (请备注 ip2region)
|
||||
|
||||
### 3、数据更新
|
||||
|
||||
基于检测算法的数据更新方式视频分享:[数据更新实现视频分享 - part1](https://www.bilibili.com/video/BV1934y1E7Q5/),[数据更新实现视频分享 - part2](https://www.bilibili.com/video/BV1pF411j7Aw/)
|
||||
|
||||
### 4、数据结构
|
||||
|
||||
1. xdb 数据结构分析:[“ip2region xdb 数据结构和查询过程详解“](https://mp.weixin.qq.com/s?__biz=MzU4MDc2MzQ5OA==&mid=2247483696&idx=1&sn=6e9e138e86cf18245656c54ff4be3129&chksm=fd50ab35ca2722239ae7c0bb08efa44f499110c810227cbad3a16f36ebc1c2afc58eb464a57c#rd)
|
||||
2. xdb 查询过程分析:[“ip2region xdb 数据结构和查询过程详解”](https://mp.weixin.qq.com/s?__biz=MzU4MDc2MzQ5OA==&mid=2247483696&idx=1&sn=6e9e138e86cf18245656c54ff4be3129&chksm=fd50ab35ca2722239ae7c0bb08efa44f499110c810227cbad3a16f36ebc1c2afc58eb464a57c#rd)
|
||||
3. xdb 生成过程分析:[“ip2region xdb 二进制数据生成过程详解”](https://mp.weixin.qq.com/s?__biz=MzU4MDc2MzQ5OA==&mid=2247483718&idx=1&sn=92e552f3bba44a97ca661da244f35574&chksm=fd50ab43ca2722559733ed4e1082f239f381aaa881f9dbeb479174936145522696d9d200531e#rd)
|
||||
|
||||
# 关于 ip2region v2.0 的 PHP 用法
|
||||
|
||||
### 完全基于文件的查询
|
||||
|
||||
```php
|
||||
$dbFile = "ip2region.xdb file path";
|
||||
try {
|
||||
$searcher = XdbSearcher::newWithFileOnly($dbFile);
|
||||
} catch (Exception $e) {
|
||||
printf("failed to create searcher with '%s': %s\n", $dbFile, $e);
|
||||
return;
|
||||
}
|
||||
|
||||
$ip = '1.2.3.4';
|
||||
$sTime = XdbSearcher::now();
|
||||
$region = $searcher->search($ip);
|
||||
if ($region === null) {
|
||||
// something is wrong
|
||||
printf("failed search(%s)\n", $ip);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("{region: %s, took: %.5f ms}\n", $region, XdbSearcher::now() - $sTime);
|
||||
|
||||
// 备注:并发使用,每个线程或者协程需要创建一个独立的 searcher 对象。
|
||||
```
|
||||
|
||||
### 缓存 `VectorIndex` 索引
|
||||
|
||||
如果你的 php 母环境支持,可以预先加载 vectorIndex 缓存,然后做成全局变量,每次创建 Searcher 的时候使用全局的 vectorIndex,可以减少一次固定的 IO 操作从而加速查询,减少 io 压力。
|
||||
|
||||
```php
|
||||
// 1、从 dbPath 加载 VectorIndex 缓存,把下述的 vIndex 变量缓存到内存里面。
|
||||
$vIndex = XdbSearcher::loadVectorIndexFromFile($dbPath);
|
||||
if ($vIndex === null) {
|
||||
printf("failed to load vector index from '%s'\n", $dbPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
|
||||
try {
|
||||
$searcher = XdbSearcher::newWithVectorIndex($dbFile, $vIndex);
|
||||
} catch (Exception $e) {
|
||||
printf("failed to create vectorIndex cached searcher with '%s': %s\n", $dbFile, $e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3、查询
|
||||
$sTime = XdbSearcher::now();
|
||||
$region = $searcher->search('1.2.3.4');
|
||||
if ($region === null) {
|
||||
printf("failed search(1.2.3.4)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("{region: %s, took: %.5f ms}\n", $region, XdbSearcher::now() - $sTime);
|
||||
|
||||
// 备注:并发使用,每个线程或者协程需要创建一个独立的 searcher 对象,但是都共享统一的只读 vectorIndex。
|
||||
```
|
||||
|
||||
### 缓存整个 `xdb` 数据
|
||||
|
||||
如果你的 PHP 母环境支持,可以预先加载整个 `xdb` 的数据到内存,这样可以实现完全基于内存的查询,类似之前的 memory search 查询。
|
||||
|
||||
```php
|
||||
// 1、从 dbPath 加载整个 xdb 到内存。
|
||||
$cBuff = XdbSearcher::loadContentFromFile($dbPath);
|
||||
if ($cBuff === null) {
|
||||
printf("failed to load content buffer from '%s'\n", $dbPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2、使用全局的 cBuff 创建带完全基于内存的查询对象。
|
||||
try {
|
||||
$searcher = XdbSearcher::newWithBuffer($cBuff);
|
||||
} catch (Exception $e) {
|
||||
printf("failed to create buffer cached searcher: %s\n", $dbFile, $e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3、查询
|
||||
$sTime = XdbSearcher::now();
|
||||
$region = $searcher->search('1.2.3.4');
|
||||
if ($region === null) {
|
||||
printf("failed search(1.2.3.4)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("{region: %s, took: %.5f ms}\n", $region, XdbSearcher::now() - $sTime);
|
||||
|
||||
// 备注:并发使用,用整个 xdb 缓存创建的 searcher 对象可以安全用于并发。
|
||||
```
|
||||
|
||||
# 查询测试
|
||||
|
||||
通过 `search_test.php` 脚本来进行查询测试:
|
||||
|
||||
```bash
|
||||
➜ php git:(v2.0_xdb) ✗ php ./search_test.php
|
||||
php ./search_test.php [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
例如:使用默认的 data/ip2region.xdb 进行查询测试:
|
||||
|
||||
```bash
|
||||
➜ php git:(v2.0_xdb) ✗ php ./search_test.php --db=../../data/ip2region.xdb --cache-policy=vectorIndex
|
||||
ip2region xdb searcher test program, cachePolicy: vectorIndex
|
||||
type 'quit' to exit
|
||||
ip2region>> 1.2.3.4
|
||||
{region: 美国|0|华盛顿|0|谷歌, ioCount: 7, took: 0.04492 ms}
|
||||
ip2region>>
|
||||
```
|
||||
|
||||
输入 ip 即可进行查询测试。也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的效率。
|
||||
|
||||
# bench 测试
|
||||
|
||||
通过 `bench_test.php` 脚本来进行自动 bench 测试,一方面确保 `xdb` 文件没有错误,另一方面通过大量的查询测试平均查询性能:
|
||||
|
||||
```bash
|
||||
➜ php git:(v2.0_xdb) ✗ php ./bench_test.php
|
||||
php ./bench_test.php [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--src string source ip text file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
例如:通过默认的 data/ip2region.xdb 和 data/ip.merge.txt 来进行 bench 测试:
|
||||
|
||||
```bash
|
||||
➜ php git:(v2.0_xdb) ✗ php ./bench_test.php --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt --cache-policy=vectorIndex
|
||||
Bench finished, {cachePolicy: vectorIndex, total: 3417955, took: 15s, cost: 0.005 ms/op}
|
||||
```
|
||||
|
||||
可以通过设置 `cache-policy` 参数来分别测试 file/vectorIndex/content 三种不同的缓存实现的的性能。
|
||||
@Note:请注意 bench 使用的 src 文件需要是生成对应的 xdb 文件的相同的源文件。
|
|
@ -0,0 +1,368 @@
|
|||
<?php
|
||||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/21
|
||||
|
||||
class XdbSearcher
|
||||
{
|
||||
const HeaderInfoLength = 256;
|
||||
const VectorIndexRows = 256;
|
||||
const VectorIndexCols = 256;
|
||||
const VectorIndexSize = 8;
|
||||
const SegmentIndexSize = 14;
|
||||
|
||||
// xdb file handle
|
||||
private $handle = null;
|
||||
|
||||
// header info
|
||||
private $header = null;
|
||||
private $ioCount = 0;
|
||||
|
||||
// vector index in binary string.
|
||||
// string decode will be faster than the map based Array.
|
||||
private $vectorIndex = null;
|
||||
|
||||
// xdb content buffer
|
||||
private $contentBuff = null;
|
||||
|
||||
// ---
|
||||
// static function to create searcher
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function newWithFileOnly($dbFile)
|
||||
{
|
||||
return new XdbSearcher($dbFile, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function newWithVectorIndex($dbFile, $vIndex)
|
||||
{
|
||||
return new XdbSearcher($dbFile, $vIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function newWithBuffer($cBuff)
|
||||
{
|
||||
return new XdbSearcher(null, null, $cBuff);
|
||||
}
|
||||
|
||||
// --- End of static creator
|
||||
|
||||
/**
|
||||
* initialize the xdb searcher
|
||||
* @throws Exception
|
||||
*/
|
||||
function __construct($dbFile = null, $vectorIndex = null, $cBuff = null)
|
||||
{
|
||||
// check the content buffer first
|
||||
if ($cBuff != null) {
|
||||
$this->vectorIndex = null;
|
||||
$this->contentBuff = $cBuff;
|
||||
} else {
|
||||
// 加载默认数据文件 by Anyon
|
||||
if (is_null($dbFile)) {
|
||||
$dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'ip2region.xdb';
|
||||
}
|
||||
// open the xdb binary file
|
||||
$this->handle = fopen($dbFile, "r");
|
||||
if ($this->handle === false) {
|
||||
throw new Exception("failed to open xdb file '%s'", $dbFile);
|
||||
}
|
||||
|
||||
$this->vectorIndex = $vectorIndex;
|
||||
}
|
||||
}
|
||||
|
||||
function close()
|
||||
{
|
||||
if ($this->handle != null) {
|
||||
fclose($this->handle);
|
||||
}
|
||||
}
|
||||
|
||||
function getIOCount()
|
||||
{
|
||||
return $this->ioCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* find the region info for the specified ip address
|
||||
* @throws Exception
|
||||
*/
|
||||
function search($ip)
|
||||
{
|
||||
// check and convert the sting ip to a 4-bytes long
|
||||
if (is_string($ip)) {
|
||||
$t = self::ip2long($ip);
|
||||
if ($t === null) {
|
||||
throw new Exception("invalid ip address `$ip`");
|
||||
}
|
||||
$ip = $t;
|
||||
}
|
||||
|
||||
// reset the global counter
|
||||
$this->ioCount = 0;
|
||||
|
||||
// locate the segment index block based on the vector index
|
||||
$il0 = ($ip >> 24) & 0xFF;
|
||||
$il1 = ($ip >> 16) & 0xFF;
|
||||
$idx = $il0 * self::VectorIndexCols * self::VectorIndexSize + $il1 * self::VectorIndexSize;
|
||||
if ($this->vectorIndex != null) {
|
||||
$sPtr = self::getLong($this->vectorIndex, $idx);
|
||||
$ePtr = self::getLong($this->vectorIndex, $idx + 4);
|
||||
} elseif ($this->contentBuff != null) {
|
||||
$sPtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx);
|
||||
$ePtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx + 4);
|
||||
} else {
|
||||
// read the vector index block
|
||||
$buff = $this->read(self::HeaderInfoLength + $idx, 8);
|
||||
if ($buff === null) {
|
||||
throw new Exception("failed to read vector index at {$idx}");
|
||||
}
|
||||
|
||||
$sPtr = self::getLong($buff, 0);
|
||||
$ePtr = self::getLong($buff, 4);
|
||||
}
|
||||
|
||||
// printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr);
|
||||
|
||||
// binary search the segment index to get the region info
|
||||
$dataLen = 0;
|
||||
$dataPtr = null;
|
||||
$l = 0;
|
||||
$h = ($ePtr - $sPtr) / self::SegmentIndexSize;
|
||||
while ($l <= $h) {
|
||||
$m = ($l + $h) >> 1;
|
||||
$p = $sPtr + $m * self::SegmentIndexSize;
|
||||
|
||||
// read the segment index
|
||||
$buff = $this->read($p, self::SegmentIndexSize);
|
||||
if ($buff == null) {
|
||||
throw new Exception("failed to read segment index at {$p}");
|
||||
}
|
||||
|
||||
$sip = self::getLong($buff, 0);
|
||||
if ($ip < $sip) {
|
||||
$h = $m - 1;
|
||||
} else {
|
||||
$eip = self::getLong($buff, 4);
|
||||
if ($ip > $eip) {
|
||||
$l = $m + 1;
|
||||
} else {
|
||||
$dataLen = self::getShort($buff, 8);
|
||||
$dataPtr = self::getLong($buff, 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// match nothing interception.
|
||||
// @TODO: could this even be a case ?
|
||||
// printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr);
|
||||
if ($dataPtr == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// load and return the region data
|
||||
$buff = $this->read($dataPtr, $dataLen);
|
||||
if ($buff == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buff;
|
||||
}
|
||||
|
||||
// read specified bytes from the specified index
|
||||
private function read($offset, $len)
|
||||
{
|
||||
// check the in-memory buffer first
|
||||
if ($this->contentBuff != null) {
|
||||
return substr($this->contentBuff, $offset, $len);
|
||||
}
|
||||
|
||||
// read from the file
|
||||
$r = fseek($this->handle, $offset);
|
||||
if ($r == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->ioCount++;
|
||||
$buff = fread($this->handle, $len);
|
||||
if ($buff === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strlen($buff) != $len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buff;
|
||||
}
|
||||
|
||||
// --- static util functions ----
|
||||
|
||||
// convert a string ip to long
|
||||
public static function ip2long($ip)
|
||||
{
|
||||
$ip = ip2long($ip);
|
||||
if ($ip === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// convert signed int to unsigned int if on 32 bit operating system
|
||||
if ($ip < 0 && PHP_INT_SIZE == 4) {
|
||||
$ip = sprintf("%u", $ip);
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
|
||||
// read a 4bytes long from a byte buffer
|
||||
public static function getLong($b, $idx)
|
||||
{
|
||||
$val = (ord($b[$idx])) | (ord($b[$idx + 1]) << 8)
|
||||
| (ord($b[$idx + 2]) << 16) | (ord($b[$idx + 3]) << 24);
|
||||
|
||||
// convert signed int to unsigned int if on 32 bit operating system
|
||||
if ($val < 0 && PHP_INT_SIZE == 4) {
|
||||
$val = sprintf("%u", $val);
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
// read a 2bytes short from a byte buffer
|
||||
public static function getShort($b, $idx)
|
||||
{
|
||||
return ((ord($b[$idx])) | (ord($b[$idx + 1]) << 8));
|
||||
}
|
||||
|
||||
// load header info from a specified file handle
|
||||
public static function loadHeader($handle)
|
||||
{
|
||||
if (fseek($handle, 0) == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$buff = fread($handle, self::HeaderInfoLength);
|
||||
if ($buff === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// read bytes length checking
|
||||
if (strlen($buff) != self::HeaderInfoLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// return the decoded header info
|
||||
return [
|
||||
'version' => self::getShort($buff, 0),
|
||||
'indexPolicy' => self::getShort($buff, 2),
|
||||
'createdAt' => self::getLong($buff, 4),
|
||||
'startIndexPtr' => self::getLong($buff, 8),
|
||||
'endIndexPtr' => self::getLong($buff, 12)
|
||||
];
|
||||
}
|
||||
|
||||
// load header info from the specified xdb file path
|
||||
public static function loadHeaderFromFile($dbFile)
|
||||
{
|
||||
$handle = fopen($dbFile, 'r');
|
||||
if ($handle === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$header = self::loadHeader($handle);
|
||||
fclose($handle);
|
||||
return $header;
|
||||
}
|
||||
|
||||
// load vector index from a file handle
|
||||
public static function loadVectorIndex($handle)
|
||||
{
|
||||
if (fseek($handle, self::HeaderInfoLength) == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rLen = self::VectorIndexRows * self::VectorIndexCols * self::SegmentIndexSize;
|
||||
$buff = fread($handle, $rLen);
|
||||
if ($buff === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strlen($buff) != $rLen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buff;
|
||||
}
|
||||
|
||||
// load vector index from a specified xdb file path
|
||||
public static function loadVectorIndexFromFile($dbFile)
|
||||
{
|
||||
$handle = fopen($dbFile, 'r');
|
||||
if ($handle === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$vIndex = self::loadVectorIndex($handle);
|
||||
fclose($handle);
|
||||
return $vIndex;
|
||||
}
|
||||
|
||||
// load the xdb content from a file handle
|
||||
public static function loadContent($handle)
|
||||
{
|
||||
if (fseek($handle, 0, SEEK_END) == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$size = ftell($handle);
|
||||
if ($size === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// seek to the head for reading
|
||||
if (fseek($handle, 0) == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$buff = fread($handle, $size);
|
||||
if ($buff === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// read length checking
|
||||
if (strlen($buff) != $size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $buff;
|
||||
}
|
||||
|
||||
// load the xdb content from a file path
|
||||
public static function loadContentFromFile($dbFile)
|
||||
{
|
||||
$str = file_get_contents($dbFile, false);
|
||||
if ($str === false) {
|
||||
return null;
|
||||
} else {
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
public static function now()
|
||||
{
|
||||
return (microtime(true) * 1000);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
// 建议使用 php _test.php 命令行运行测试文件
|
||||
|
||||
require 'Ip2Region.php';
|
||||
|
||||
$ip2region = new Ip2Region();
|
||||
|
||||
|
||||
// array (
|
||||
// 'city_id' => 1713,
|
||||
// 'region' => '中国|0|广东省|广州市|电信',
|
||||
// )
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
test();
|
||||
}
|
||||
|
||||
function getIp()
|
||||
{
|
||||
$ip_long = array(
|
||||
array('607649792', '608174079'), // 36.56.0.0-36.63.255.255
|
||||
array('1038614528', '1039007743'), // 61.232.0.0-61.237.255.255
|
||||
array('1783627776', '1784676351'), // 106.80.0.0-106.95.255.255
|
||||
array('2035023872', '2035154943'), // 121.76.0.0-121.77.255.255
|
||||
array('2078801920', '2079064063'), // 123.232.0.0-123.235.255.255
|
||||
array('-1950089216', '-1948778497'), // 139.196.0.0-139.215.255.255
|
||||
array('-1425539072', '-1425014785'), // 171.8.0.0-171.15.255.255
|
||||
array('-1236271104', '-1235419137'), // 182.80.0.0-182.92.255.255
|
||||
array('-770113536', '-768606209'), // 210.25.0.0-210.47.255.255
|
||||
array('-569376768', '-564133889'), // 222.16.0.0-222.95.255.255
|
||||
);
|
||||
$rkey = mt_rand(0, 9);
|
||||
return long2ip(mt_rand($ip_long[$rkey][0], $ip_long[$rkey][1]));
|
||||
}
|
||||
|
||||
function test()
|
||||
{
|
||||
$ip = getIp();
|
||||
global $ip2region;
|
||||
|
||||
echo PHP_EOL . "===============================";
|
||||
echo PHP_EOL . "测试 IP 地址: {$ip}";
|
||||
echo PHP_EOL . "--------【完整结果】------------" . PHP_EOL;
|
||||
$info = $ip2region->memorySearch($ip);
|
||||
var_export($info);
|
||||
|
||||
echo PHP_EOL . "---------【简易结果】----------" . PHP_EOL;
|
||||
var_export($ip2region->simple($ip));
|
||||
echo PHP_EOL . "===============================" . PHP_EOL . PHP_EOL;
|
||||
sleep(2);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"type": "library",
|
||||
"name": "zoujingli/ip2region",
|
||||
"homepage": "https://github.com/zoujingli/Ip2Region",
|
||||
"description": "Ip2Region for PHP",
|
||||
"license": "Apache-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Anyon",
|
||||
"email": "zoujingli@qq.com",
|
||||
"homepage": "https://thinkadmin.top"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4"
|
||||
},
|
||||
"keywords": [
|
||||
"Ip2Region"
|
||||
],
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"Ip2Region.php",
|
||||
"XdbSearcher.php"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* 文章归档
|
||||
*
|
||||
* @package custom
|
||||
*/
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="page-archives">
|
||||
<div id="page" class="w-100">
|
||||
<div id="posts" class="animated fadeInLeft ">
|
||||
<div class="p-block puock-text">
|
||||
<div class="timeline no-style">
|
||||
<?php
|
||||
$stat = Typecho_Widget::widget('Widget_Stat');
|
||||
Typecho_Widget::widget('Widget_Contents_Post_Recent', 'pageSize=' . $stat->publishedPostsNum)->to($archives);
|
||||
$year = 0;
|
||||
$mon = 0;
|
||||
$output = '';
|
||||
|
||||
while ($archives->next()) {
|
||||
$year_tmp = date('Y', $archives->created);
|
||||
$mon_tmp = date('m', $archives->created);
|
||||
$day_tmp = date('d', $archives->created);
|
||||
|
||||
// 检查是否需要新的时间线项目
|
||||
if ($year != $year_tmp || $mon != $mon_tmp) {
|
||||
// 如果不是第一个项目,先关闭之前的ul
|
||||
if ($year > 0 && $mon > 0) {
|
||||
$output .= '</ul></div></div>';
|
||||
}
|
||||
|
||||
$year = $year_tmp;
|
||||
$mon = $mon_tmp;
|
||||
|
||||
// 开始新的时间线项目
|
||||
$output .= '<div class="timeline-item">';
|
||||
$output .= '<div class="timeline-location"></div>';
|
||||
$output .= '<div class="timeline-content">';
|
||||
$output .= '<h4>' . $year . '-' . $mon . '</h4>';
|
||||
$output .= '<ul class="pd-links pl-0">';
|
||||
}
|
||||
|
||||
// 输出文章项
|
||||
$output .= '<li><a title="' . $archives->title . '" href="' . $archives->permalink . '">';
|
||||
$output .= $archives->title . ' ( ' . $day_tmp . '日 )</a></li>';
|
||||
}
|
||||
|
||||
// 循环结束后关闭最后的标签
|
||||
if ($year > 0 && $mon > 0) {
|
||||
$output .= '</ul></div></div>';
|
||||
}
|
||||
|
||||
echo $output;
|
||||
?>
|
||||
</div> </div> </div> </div> </div>
|
||||
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
/**
|
||||
* 评论墙
|
||||
*
|
||||
* @package custom
|
||||
*/
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
|
||||
</div>
|
||||
</nav>
|
||||
</ol>
|
||||
<?php $commenters = getAllCommenters(); ?>
|
||||
<div id="page-reads">
|
||||
<div id="page" class="row row-cols-1">
|
||||
<div id="posts" class="col-lg-8 col-md-12 animated fadeInLeft ">
|
||||
<div class="p-block puock-text">
|
||||
<h2 class="t-lg"><?php $this->title() ?></h2>
|
||||
<div class="mt20 row pd-links">
|
||||
<?php foreach ($commenters as $commenter): ?>
|
||||
<div class="col col-6 col-md-4 col-lg-3 pl-0">
|
||||
<div class="p-2 text-truncate text-nowrap">
|
||||
<?php if ($commenter['url']): ?>
|
||||
<a href="<?php echo htmlspecialchars($commenter['url']); ?>"
|
||||
target="_blank" rel="nofollow">
|
||||
<img data-bs-toggle="tooltip"
|
||||
src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>'
|
||||
class='lazy md-avatar'
|
||||
data-src='<?php echo htmlspecialchars($commenter['avatar']); ?>'
|
||||
title="<?php echo htmlspecialchars($commenter['nickname']); ?>" alt="<?php echo htmlspecialchars($commenter['nickname']); ?>"> <span class="t-sm"><span
|
||||
class="c-sub">+(<?php echo $commenter['comment_count']; ?>) </span><?php echo htmlspecialchars($commenter['nickname']); ?></span> </a>
|
||||
<?php else: ?>
|
||||
<img data-bs-toggle="tooltip"
|
||||
src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>'
|
||||
class='lazy md-avatar'
|
||||
data-src='<?php echo htmlspecialchars($commenter['avatar']); ?>'
|
||||
title="<?php echo htmlspecialchars($commenter['nickname']); ?>" alt="<?php echo htmlspecialchars($commenter['nickname']); ?>">
|
||||
<span class="t-sm">
|
||||
<span class="c-sub">+(<?php echo $commenter['comment_count']; ?>) </span><?php echo htmlspecialchars($commenter['nickname']); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* 宽屏页面
|
||||
*
|
||||
* @package custom
|
||||
*/
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="page-links">
|
||||
<div id="page-24" class="row row-cols-1">
|
||||
<div id="posts" class="col-12 animated fadeInLeft ">
|
||||
<div class="puock-text no-style">
|
||||
<p><?php $this->content(); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
/**
|
||||
* 友情链接
|
||||
*
|
||||
* @package custom
|
||||
*/
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="page-links">
|
||||
<div id="page" class="row row-cols-1">
|
||||
<div id="posts" class="col-12 animated fadeInLeft ">
|
||||
<div class="puock-text no-style">
|
||||
<div class="p-block links-main" id="page-links-217">
|
||||
<h6><?php $this->title() ?></h6>
|
||||
<div class="links-main-box row t-sm">
|
||||
<?php
|
||||
Links_Plugin::output('
|
||||
<a class="link-item a-link col-lg-3 col-md-4 col-sm-6 col-6"
|
||||
href="{url}"
|
||||
target="_blank" rel="me" title="{name}" data-bs-toggle="tooltip">
|
||||
<div class="clearfix puock-bg">
|
||||
<img alt="{name}"
|
||||
src="{image}"
|
||||
class="lazy md-avatar"
|
||||
data-src="{image}"
|
||||
alt="{name}">
|
||||
<div class="info">
|
||||
<p class="ml-1 text-nowrap text-truncate">{name}</p>
|
||||
<p class="c-sub ml-1 text-nowrap text-truncate">{title}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
');
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* 全部标签
|
||||
*
|
||||
* @package custom
|
||||
*/
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="page-tags">
|
||||
<div id="page" class="row row-cols-1">
|
||||
<div id="posts" class="col-lg-8 col-md-12 animated fadeInLeft">
|
||||
<div class="puock-text p-block no-style pb-2">
|
||||
<?php
|
||||
// 获取所有标签
|
||||
$tags = Typecho_Widget::widget('Widget_Metas_Tag_Cloud');
|
||||
|
||||
// 准备字母数组
|
||||
$letters = range('A', 'Z');
|
||||
$letters[] = '0';
|
||||
|
||||
// 获取所有存在的首字母
|
||||
$existingLetters = array();
|
||||
$tagsArray = array();
|
||||
|
||||
while ($tags->next()) {
|
||||
$firstChar = getFirstChar($tags->name);
|
||||
if (!in_array($firstChar, $existingLetters)) {
|
||||
$existingLetters[] = $firstChar;
|
||||
}
|
||||
$tagsArray[$firstChar][] = array(
|
||||
'name' => $tags->name,
|
||||
'permalink' => $tags->permalink,
|
||||
'count' => $tags->count
|
||||
);
|
||||
}
|
||||
|
||||
// 对每个字母下的标签按名称排序
|
||||
foreach ($tagsArray as &$letterTags) {
|
||||
usort($letterTags, function($a, $b) {
|
||||
return strcmp($a['name'], $b['name']);
|
||||
});
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- 字母导航 -->
|
||||
<ul class='pl-0' id='tags-main-index'>
|
||||
<?php foreach ($letters as $letter): ?>
|
||||
<?php if (in_array($letter, $existingLetters)): ?>
|
||||
<li class='a'><a href='#<?php echo $letter; ?>'><?php echo $letter; ?></a></li>
|
||||
<?php else: ?>
|
||||
<li class='t'><?php echo $letter; ?></li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php for ($i = 1; $i <= 9; $i++): ?>
|
||||
<li class='t'><?php echo $i; ?></li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
|
||||
<!-- 标签列表 -->
|
||||
<ul id='tags-main-box' class='pl-0'>
|
||||
<?php
|
||||
// 按字母顺序排序
|
||||
ksort($tagsArray);
|
||||
|
||||
foreach ($tagsArray as $letter => $tags): ?>
|
||||
<li class='n' id='<?php echo $letter; ?>'>
|
||||
<h4 class='tag-name'><span class='t-lg c-sub'>#</span><?php echo $letter; ?></h4>
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<a href='<?php echo $tag['permalink']; ?>'><?php echo $tag['name']; ?>(<?php echo $tag['count']; ?>)</a>
|
||||
<?php endforeach; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
?>
|
||||
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,64 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="page-empty">
|
||||
<div id="page" class="row row-cols-1">
|
||||
<div id="post-main" class="col-lg-8 col-md-12 animated fadeInLeft ">
|
||||
<div class="p-block"><div>
|
||||
<h1 id="post-title" class="mb-0 puock-text t-xxl"><?php $this->title() ?></h1>
|
||||
</div>
|
||||
<div class="options p-flex-sbc mt20"><div>
|
||||
<div class="option puock-bg ta3 t-sm mr-1">
|
||||
<i class="fa-regular fa-eye mr-1"></i>
|
||||
<span id="post-views">
|
||||
<span class="view">浏览:<?php get_post_view($this) ?></span>
|
||||
</span>
|
||||
<span>次阅读</span>
|
||||
</div>
|
||||
<a href="#comments">
|
||||
<div class="option puock-bg ta3 t-sm mr-1">
|
||||
<i class="fa-regular fa-comment mr-1"></i><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?>
|
||||
</div>
|
||||
</a>
|
||||
<?php if($this->user->hasLogin() && $this->user->pass('editor', true)): ?>
|
||||
<a target="_blank" href="<?php $this->options->adminUrl('write-page.php?cid=' . $this->cid); ?>">
|
||||
<div class="option puock-bg ta3 t-sm mr-1">
|
||||
<i class="fa-regular fa-pen-to-square mr-1"></i>编辑
|
||||
</div>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div class="option puock-bg ta3 t-sm mr-1 d-none d-lg-inline-block post-main-size">
|
||||
<i class="fa fa-up-right-and-down-left-from-center"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt20 puock-text entry-content show-link-icon">
|
||||
<p class="fs12 c-sub">
|
||||
<?php
|
||||
$modified = $this->modified;
|
||||
$now = time();
|
||||
$days = ($now - $modified) / 86400;
|
||||
|
||||
if($days > 180){
|
||||
echo '<i class="fa fa-circle-exclamation me-1"></i> 本文最后更新于 ' . date('Y-m-d H:i', $modified) . ',文中所关联的信息可能已发生改变,请知悉!';
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
<p><?php $this->content(); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($this->allow('comment')): ?>
|
||||
<?php $this->need('comments.php'); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
<?php $this->need('footer.php'); ?>
|
|
@ -0,0 +1,192 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<?php $this->need('header.php'); ?>
|
||||
<div id="breadcrumb" class="animated fadeInUp">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a class="a-link" href="<?php $this->options->siteUrl(); ?>">首页</a></li>
|
||||
<li class="breadcrumb-item"><?php $this->category(','); ?></li>
|
||||
<li class="breadcrumb-item active " aria-current="page">正文</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<!--内页上方-->
|
||||
<div id="post" class="container mt20">
|
||||
<?php if ($this->options->articletop): ?>
|
||||
<div class="puock-text p-block t-md ad-page-top"><?php $this->options->articletop(); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="row row-cols-1 post-row">
|
||||
<div id="post-main" class="col-lg-8 col-md-12 animated fadeInLeft ">
|
||||
<div class="p-block"><div>
|
||||
<h1 id="post-title" class="mb-0 puock-text t-xxl"><?php $this->title() ?></h1>
|
||||
</div>
|
||||
<div class="options p-flex-sbc mt20"><div>
|
||||
<div class="option puock-bg ta3 t-sm mr-1">
|
||||
<i class="fa-regular fa-eye mr-1"></i>
|
||||
<span id="post-views">
|
||||
<span class="view">浏览:<?php get_post_view($this) ?></span>
|
||||
</span>
|
||||
<span>次阅读</span>
|
||||
</div>
|
||||
<a href="#comments">
|
||||
<div class="option puock-bg ta3 t-sm mr-1">
|
||||
<i class="fa-regular fa-comment mr-1"></i><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?>
|
||||
</div>
|
||||
</a>
|
||||
<?php if($this->user->hasLogin() && $this->user->pass('editor', true)): ?>
|
||||
<a target="_blank" href="<?php $this->options->adminUrl('write-post.php?cid=' . $this->cid); ?>">
|
||||
<div class="option puock-bg ta3 t-sm mr-1">
|
||||
<i class="fa-regular fa-pen-to-square mr-1"></i>编辑
|
||||
</div>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="option puock-bg ta3 t-sm mr-1 d-none d-lg-inline-block post-main-size">
|
||||
<i class="fa fa-up-right-and-down-left-from-center"></i>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
// 获取当前文章内容(去除 HTML 标签)
|
||||
$content = strip_tags($this->content);
|
||||
// 计算字数(适用于中英文混合)
|
||||
$wordCount = mb_strlen($content, 'UTF-8');
|
||||
?>
|
||||
<div class="mt20 entry-content-box">
|
||||
<div class="entry-content show-link-icon content-main puock-text ">
|
||||
<p class="fs12 c-sub no-indent"> <i class="fa-regular fa-clock"></i> 共计<?php echo $wordCount; ?>个字符,预计需要花费 <?php echo ceil($wordCount / 100); ?>分钟才能阅读完成。 </p>
|
||||
<p class="fs12 c-sub">
|
||||
<?php
|
||||
$modified = $this->modified;
|
||||
$now = time();
|
||||
$days = ($now - $modified) / 86400;
|
||||
|
||||
if($days > 180){
|
||||
echo '<i class="fa fa-circle-exclamation me-1"></i> 本文最后更新于 ' . date('Y-m-d H:i', $modified) . ',文中所关联的信息可能已发生改变,请知悉!';
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
|
||||
<p><?php $this->content(); ?></p>
|
||||
</div>
|
||||
<div class="t-separator c-sub t-sm mt30">正文完</div>
|
||||
<div class="footer-info puock-text mt20">
|
||||
<div class="mt20 tags">
|
||||
<?php if ($this->tags): ?>
|
||||
<?php foreach ($this->tags as $tag): ?>
|
||||
<a href="<?php echo $tag['permalink']; ?>" class="pk-badge pk-badge-sm mr5 mb10"><i class="fa-solid fa-tag"></i> <?php echo $tag['name']; ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="p-flex-sbc mt20 t-sm">
|
||||
<div>
|
||||
<span>发表至:</span>
|
||||
<?php foreach($this->categories as $category): ?>
|
||||
<a class=" mr5" href="<?php echo $category['permalink']; ?>">
|
||||
<i class="fa-regular fa-folder-open"></i> <?php echo $category['name']; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div>
|
||||
<span class="c-sub"><i class="fa-regular fa-clock"></i> <?php $this->date(); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt15 post-action-panel">
|
||||
<div class="post-action-content">
|
||||
<div class="d-flex justify-content-center w-100 c-sub">
|
||||
<div class="circle-button puock-bg text-center " id="post-like" data-id="<?php echo $this->cid; ?>">
|
||||
<i class="fa-regular fa-thumbs-up t-md"></i> <span class="t-sm"><?php get_post_like($this) ?></span>
|
||||
</div>
|
||||
<?php if ($this->options->social): ?>
|
||||
<div class="circle-button puock-bg text-center pk-modal-toggle" title="海报"
|
||||
data-no-title data-no-padding data-once-load="true"
|
||||
data-url="<?php echo get_correct_url('/poster/' . $this->cid); ?>">
|
||||
<i class="fa-regular fa-images"></i>
|
||||
</div>
|
||||
<div class="circle-button puock-bg text-center pk-modal-toggle" title="赞赏"
|
||||
data-once-load="true"
|
||||
data-url="<?php echo get_correct_url('/reward/'); ?>">
|
||||
<i class="fa fa-sack-dollar"></i>
|
||||
</div>
|
||||
<div class="circle-button puock-bg text-center pk-modal-toggle" title="分享"
|
||||
data-once-load="true"
|
||||
data-url="<?php echo get_correct_url('/share/' . $this->cid); ?>">
|
||||
<i class="fa fa-share-from-square t-md"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="ls">
|
||||
<div class="circle-button puock-bg text-center post-menu-toggle post-menus-box">
|
||||
<i class="fa fa-bars t-md"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!--内页中-->
|
||||
</div>
|
||||
<?php if ($this->options->articlemid): ?>
|
||||
<div class="puock-text p-block t-md ad-page-content-bottom"><?php $this->options->articlemid(); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php $this->related(4)->to($relatedPosts); if ($relatedPosts->have()):?>
|
||||
<div class="p-block pb-0">
|
||||
<div class="row puock-text post-relevant">
|
||||
<?php while ($relatedPosts->next()): ?>
|
||||
<a href="<?php $relatedPosts->permalink(); ?>"
|
||||
class="col-6 col-md-3 post-relevant-item ww">
|
||||
<div
|
||||
style="background:url('<?php echo getPostCover($relatedPosts->content, $relatedPosts->cid); ?>')">
|
||||
<div class="title"> <?php $relatedPosts->title(); ?></div>
|
||||
</div>
|
||||
</a>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="p-block p-lf-15">
|
||||
<div class="row text-center pd-links single-next-or-pre t-md ">
|
||||
<?php $prevPost = get_previous_post($this); ?>
|
||||
<div class="col-6 p-border-r-1 p-0">
|
||||
<?php if ($prevPost) { ?>
|
||||
<a href="<?php echo $prevPost->permalink; ?>"
|
||||
rel="prev">
|
||||
<div class='abhl puock-text'>
|
||||
<p class='t-line-1'><?php echo $prevPost->title; ?></p>
|
||||
<span>上一篇</span>
|
||||
</div>
|
||||
</a>
|
||||
<?php } else { ?>
|
||||
<a href="javascript:void(0);" rel="prev">
|
||||
<div class='abhl puock-text'>
|
||||
<p class='t-line-1'>没有上一篇</p>
|
||||
<span>上一篇</span>
|
||||
</div>
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php $nextPost = get_next_post($this); ?>
|
||||
<div class="col-6 p-0">
|
||||
<?php if ($nextPost) { ?>
|
||||
<a href="<?php echo $nextPost->permalink; ?>" rel="next">
|
||||
<div class="abhl">
|
||||
<p class="t-line-1"><?php echo $nextPost->title; ?></p>
|
||||
<span>下一篇</span>
|
||||
</div>
|
||||
</a>
|
||||
<?php } else { ?>
|
||||
<a href="javascript:void(0);" rel="next">
|
||||
<div class='abhl puock-text'>
|
||||
<p class='t-line-1'>已是最新的文章</p>
|
||||
<span>下一篇</span>
|
||||
</div>
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!--评论上方-->
|
||||
<?php $this->need('comments.php'); ?>
|
||||
</div>
|
||||
<?php if ($this->options->articlefoot): ?>
|
||||
<div class="puock-text p-block t-md ad-comment-top"><?php $this->options->articlefoot(); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
<?php $this->need('footer.php'); ?>
|
After Width: | Height: | Size: 250 KiB |
|
@ -0,0 +1,231 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
|
||||
<div id="sidebar" class="animated fadeInRight col-lg-4 d-none d-lg-block">
|
||||
<div class="sidebar-main">
|
||||
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowSearch', $this->options->sidebarBlock)): ?>
|
||||
<div class="p-block">
|
||||
<div>
|
||||
<span class="t-lg border-bottom border-primary puock-text pb-2">
|
||||
<i class="fa fa-search mr-1"></i>文章搜索
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt20">
|
||||
<form class="global-search-form" action="<?php $this->options->siteUrl(); ?>" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="s" class="form-control t-md" placeholder="输入关键字回车搜索">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 个人信息 -->
|
||||
<?php
|
||||
// 获取数据库连接
|
||||
$db = Typecho_Db::get();
|
||||
$prefix = $db->getPrefix();
|
||||
// 1. 获取uid=1的用户信息
|
||||
$user = $db->fetchRow($db->select()->from('table.users')->where('uid = ?', 1));
|
||||
$email = $user['mail'];
|
||||
$nickname = $user['screenName'];
|
||||
// 获取用户设置的 Gravatar 镜像
|
||||
$cnavatar = Helper::options()->cnavatar ? Helper::options()->cnavatar : 'https://cravatar.cn/avatar/';
|
||||
$hash = md5($email);
|
||||
$avatar = rtrim($cnavatar, '/') . '/' . $hash . '?s=80&d=identicon';
|
||||
// 2. 获取用户总数
|
||||
$userCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.users'))->num;
|
||||
// 3. 获取文章总数(只统计 type='post' 且 status='publish')
|
||||
$postCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.contents')->where('type = ?', 'post')->where('status = ?', 'publish'))->num;
|
||||
// 4. 获取评论总数
|
||||
$commentCount = $db->fetchObject($db->select(array('COUNT(*)' => 'num'))->from('table.comments'))->num;
|
||||
// 5. 获取文章浏览总量(累加所有文章的 views 字段)
|
||||
$totalViews = $db->fetchObject(
|
||||
$db->select(array('SUM(views)' => 'viewsum'))->from('table.contents')->where('type = ?', 'post')
|
||||
)->viewsum;
|
||||
if ($totalViews === null) $totalViews = 0;
|
||||
?>
|
||||
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowAdmin', $this->options->sidebarBlock)): ?>
|
||||
<div class="widget-puock-author widget">
|
||||
<div class="header" style="background-image: url('<?php echo $this->options->bgUrl() ?: $this->options->themeUrl('assets/img/cover.png'); ?>')">
|
||||
<img src='<?php $this->options->themeUrl('assets/img/load.svg'); ?>' class='lazy avatar' data-src='<?php echo $avatar; ?>' >
|
||||
</div>
|
||||
<div class="content t-md puock-text">
|
||||
<div class="text-center p-2">
|
||||
<div class="t-lg"><?php echo $nickname; ?></div>
|
||||
<div class="mt10 t-sm"><?php $this->options->description(); ?></div>
|
||||
</div>
|
||||
<div class="row mt10">
|
||||
<div class="col-3 text-center">
|
||||
<div class="c-sub t-sm">用户数</div>
|
||||
<div><?php echo $userCount; ?></div>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<div class="c-sub t-sm">文章数</div>
|
||||
<div><?php echo $postCount; ?></div>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<div class="c-sub t-sm">评论数</div>
|
||||
<div><?php echo $commentCount; ?></div>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<div class="c-sub t-sm">阅读量</div>
|
||||
<div><?php echo $totalViews; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 最新文章 -->
|
||||
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowRecentPosts', $this->options->sidebarBlock)): ?>
|
||||
<div class="pk-widget p-block ">
|
||||
<div>
|
||||
<span class="t-lg border-bottom border-primary puock-text pb-2">
|
||||
<i class="fa fa-chart-simple mr-1"></i>最新文章
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt20">
|
||||
<?php \Widget\Contents\Post\Recent::alloc()
|
||||
->parse('
|
||||
<div class="media-link mt20">
|
||||
<h2 class="t-lg t-line-1" title="{title}">
|
||||
<i class="fa fa-angle-right t-sm c-sub mr-1"></i>
|
||||
<a class="a-link t-w-400 t-md" title="{title}" href="{permalink}">
|
||||
{title}
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<!-- 热门文章 -->
|
||||
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowHotPosts', $this->options->sidebarBlock)): ?>
|
||||
<?php
|
||||
$db = Typecho_Db::get();
|
||||
$select = $db->select(
|
||||
'table.contents.cid',
|
||||
'table.contents.title',
|
||||
'table.contents.slug',
|
||||
'table.contents.created',
|
||||
'table.contents.authorId',
|
||||
'table.contents.type',
|
||||
'table.contents.status',
|
||||
'table.contents.commentsNum'
|
||||
)
|
||||
->from('table.contents')
|
||||
->where('table.contents.type = ?', 'post')
|
||||
->where('table.contents.status = ?', 'publish')
|
||||
->where('table.contents.password IS NULL')
|
||||
->order('table.contents.commentsNum', Typecho_Db::SORT_DESC)
|
||||
->limit(5);
|
||||
|
||||
try {
|
||||
$hotPosts = $db->fetchAll($select);
|
||||
} catch (Exception $e) {
|
||||
$hotPosts = [];
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (!empty($hotPosts)): ?>
|
||||
<div class="pk-widget p-block">
|
||||
<div>
|
||||
<span class="t-lg border-bottom border-primary puock-text pb-2">
|
||||
<i class="fa fa-chart-simple mr-1"></i>热门文章
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt20">
|
||||
<?php foreach ($hotPosts as $post): ?>
|
||||
<?php
|
||||
// 完整处理文章数据
|
||||
$widget = Typecho_Widget::widget('Widget_Abstract_Contents');
|
||||
try {
|
||||
$post = $widget->filter($post);
|
||||
if (empty($post['title']) || empty($post['slug'])) {
|
||||
continue; // 跳过无效数据
|
||||
}
|
||||
$post['title'] = htmlspecialchars($post['title']);
|
||||
$post['slug'] = htmlspecialchars($post['slug']);
|
||||
if (empty($post['permalink'])) {
|
||||
$post['permalink'] = Typecho_Common::url($post['slug'], $this->options->index);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="media-link mt20">
|
||||
<h2 class="t-lg t-line-1" title="<?php echo htmlspecialchars($post['title']); ?>">
|
||||
<i class="fa fa-angle-right t-sm c-sub mr-1"></i>
|
||||
<a class="a-link t-w-400 t-md"
|
||||
title="<?php echo htmlspecialchars($post['title']); ?>"
|
||||
href="<?php echo htmlspecialchars($post['permalink']); ?>">
|
||||
<?php echo htmlspecialchars($post['title']); ?>
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 最近评论 -->
|
||||
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowRecentComments', $this->options->sidebarBlock)): ?>
|
||||
<?php
|
||||
// 设置参数来排除管理员评论
|
||||
$comments = \Widget\Comments\Recent::alloc(array(
|
||||
'ignoreAuthor' => true // 这里添加参数来排除作者/管理员评论
|
||||
));
|
||||
?>
|
||||
<div class="pk-widget p-block ">
|
||||
<div>
|
||||
<span class="t-lg border-bottom border-primary puock-text pb-2">
|
||||
<i class="fa fa-comment mr-1"></i>最新评论
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt20">
|
||||
<div class="min-comments t-md">
|
||||
<?php \Widget\Comments\Recent::alloc()->to($comments); ?>
|
||||
<?php while ($comments->next()): ?>
|
||||
<div class="comment t-md t-line-1">
|
||||
<?php echo $comments->gravatar('40', ''); ?>
|
||||
<a class="puock-link" href="<?php $comments->permalink(); ?>">
|
||||
<span class="ta3 link-hover"><?php $comments->author(false); ?></span>
|
||||
</a>
|
||||
<span class="c-sub t-w-400"><?php $comments->excerpt(35, '...'); ?></span>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<!-- 热门标签 -->
|
||||
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowTags', $this->options->sidebarBlock)): ?>
|
||||
<?php
|
||||
$tags = \Widget\Metas\Tag\Cloud::alloc('sort=count&desc=1&limit=20');
|
||||
if ($tags->have()):
|
||||
// 定义可用的颜色类数组
|
||||
$colors = ['bg-primary', 'bg-secondary', 'bg-success', 'bg-danger', 'bg-warning', 'bg-info'];
|
||||
?>
|
||||
<div class="pk-widget p-block ">
|
||||
<div>
|
||||
<span class="t-lg border-bottom border-primary puock-text pb-2">
|
||||
<i class="fa fa-tag mr-1"></i>标签云
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt20">
|
||||
<div class="widget-puock-tag-cloud">
|
||||
<?php while ($tags->next()): ?>
|
||||
<!-- 使用随机数选择颜色类 -->
|
||||
<a href="<?php $tags->permalink(); ?>" title="<?php $tags->name(); ?>"
|
||||
class="badge d-none d-md-inline-block <?php echo $colors[array_rand($colors)]; ?> ahfff">
|
||||
<?php $tags->name(); ?>
|
||||
</a>
|
||||
<?php endwhile; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,124 @@
|
|||
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
||||
$sticky = $this->options->sticky;
|
||||
$db = Typecho_Db::get();
|
||||
$pageSize = $this->options->pageSize;
|
||||
if ($sticky && !empty(trim($sticky))) {
|
||||
$sticky_cids = array_filter(explode('|', $sticky));
|
||||
if (!empty($sticky_cids)) {
|
||||
$sticky_html = " <span class=\"badge bg-danger\"><i class=\"fa fa-bolt-lightning\"></i>置顶</span>";
|
||||
// 保存原始对象状态
|
||||
$originalRows = $this->row;
|
||||
$originalStack = $this->stack;
|
||||
$originalLength = $this->length;
|
||||
$totalOriginal = $this->getTotal();
|
||||
// 重置当前对象状态
|
||||
$this->row = [];
|
||||
$this->stack = [];
|
||||
$this->length = 0;
|
||||
// 关键修改:不再减少总文章数
|
||||
// 保持原始总数,这样分页逻辑不会受影响
|
||||
// $this->setTotal(max($totalOriginal - $stickyCount, 0));
|
||||
if (isset($this->currentPage) && $this->currentPage == 1) {
|
||||
// 查询置顶文章
|
||||
$selectSticky = $this->select()->where('type = ?', 'post');
|
||||
foreach ($sticky_cids as $i => $cid) {
|
||||
if ($i == 0)
|
||||
$selectSticky->where('cid = ?', $cid);
|
||||
else
|
||||
$selectSticky->orWhere('cid = ?', $cid);
|
||||
}
|
||||
$stickyPosts = $db->fetchAll($selectSticky);
|
||||
|
||||
// 添加置顶文章到结果集
|
||||
foreach ($stickyPosts as &$stickyPost) {
|
||||
$stickyPost['isSticky'] = true;
|
||||
$stickyPost['stickyHtml'] = $sticky_html;
|
||||
$this->push($stickyPost);
|
||||
}
|
||||
|
||||
// 计算当前页应显示的普通文章数量
|
||||
$standardPageSize = $pageSize - count($stickyPosts);
|
||||
|
||||
// 确保第一页不会显示太多文章
|
||||
if ($standardPageSize <= 0) {
|
||||
$standardPageSize = 0; // 如果置顶文章已经填满或超过一页,则不显示普通文章
|
||||
}
|
||||
} else {
|
||||
// 非第一页显示正常数量的文章
|
||||
$standardPageSize = $pageSize;
|
||||
}
|
||||
// 查询普通文章
|
||||
if ($this->currentPage == 1) {
|
||||
// 第一页需要排除置顶文章并限制数量
|
||||
$selectNormal = $this->select()
|
||||
->where('type = ?', 'post')
|
||||
->where('status = ?', 'publish')
|
||||
->where('created < ?', time());
|
||||
|
||||
// 排除所有置顶文章
|
||||
foreach ($sticky_cids as $cid) {
|
||||
$selectNormal->where('table.contents.cid != ?', $cid);
|
||||
}
|
||||
|
||||
$selectNormal->order('created', Typecho_Db::SORT_DESC)
|
||||
->limit($standardPageSize)
|
||||
->offset(0);
|
||||
} else {
|
||||
// 非第一页的查询
|
||||
// 计算正确的偏移量:(当前页码-1) * 每页数量 - 置顶文章数
|
||||
// 这样可以确保不会漏掉文章
|
||||
$offset = ($this->currentPage - 1) * $pageSize - count($sticky_cids);
|
||||
$offset = max($offset, 0); // 确保偏移量不为负
|
||||
|
||||
$selectNormal = $this->select()
|
||||
->where('type = ?', 'post')
|
||||
->where('status = ?', 'publish')
|
||||
->where('created < ?', time());
|
||||
|
||||
// 排除所有置顶文章
|
||||
foreach ($sticky_cids as $cid) {
|
||||
$selectNormal->where('table.contents.cid != ?', $cid);
|
||||
}
|
||||
|
||||
$selectNormal->order('created', Typecho_Db::SORT_DESC)
|
||||
->limit($pageSize)
|
||||
->offset($offset);
|
||||
}
|
||||
} else {
|
||||
// 没有有效的置顶文章ID,正常查询
|
||||
$selectNormal = $this->select()
|
||||
->where('type = ?', 'post')
|
||||
->where('status = ?', 'publish')
|
||||
->where('created < ?', time())
|
||||
->order('created', Typecho_Db::SORT_DESC)
|
||||
->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
|
||||
}
|
||||
} else {
|
||||
// 没有设置置顶文章,正常查询
|
||||
$selectNormal = $this->select()
|
||||
->where('type = ?', 'post')
|
||||
->where('status = ?', 'publish')
|
||||
->where('created < ?', time())
|
||||
->order('created', Typecho_Db::SORT_DESC)
|
||||
->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
|
||||
}
|
||||
// 添加私有文章查询条件
|
||||
if ($this->user->hasLogin()) {
|
||||
$uid = $this->user->uid;
|
||||
if ($uid) {
|
||||
$selectNormal->orWhere('authorId = ? AND status = ?', $uid, 'private');
|
||||
}
|
||||
}
|
||||
// 获取普通文章
|
||||
$normalPosts = $db->fetchAll($selectNormal);
|
||||
// 如果没有置顶文章或在前面的代码中没有重置对象状态,则在这里重置
|
||||
if (empty($sticky) || empty(trim($sticky)) || empty($sticky_cids)) {
|
||||
$this->row = [];
|
||||
$this->stack = [];
|
||||
$this->length = 0;
|
||||
}
|
||||
// 将普通文章添加到结果集
|
||||
foreach ($normalPosts as $normalPost) {
|
||||
$this->push($normalPost);
|
||||
}
|
||||
?>
|