初始版本
This commit is contained in:
浪子 2025-06-25 15:16:12 +08:00
commit a7c4800ae2
93 changed files with 5659 additions and 0 deletions

40
404.php Normal file
View File

@ -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>&nbsp;返回首页</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'); ?>

24
README.MD Normal file
View File

@ -0,0 +1,24 @@
## 说明
### 主题介绍
源主题为`wordpress`主题`puock`,现移植到`Typecho`平台。
### 主题特点
- 主题简洁,适合个人博客使用
- 支持`Typecho`的所有功能
### 主题预览
![主题预览](/screenshot.png)
### 使用说明
1. 将主题文件夹放入`Typecho`的`usr/themes`目录下。
2. 在后台管理界面启用该主题。
3. 根据需要自定义主题设置。
4. 主题部分功能需要安装插件`Puock`。项目地址: [Puock Plugin](https://github.com/jkjoy/typecho-plugin-puock)
### 主题配置
- 全部标签页面需要启用php的 `iconv` 扩展

81
archive.php Normal file
View File

@ -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('&laquo;', '&raquo;', 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'); ?>

2
assets/css/style.css Normal file

File diff suppressed because one or more lines are too long

View File

@ -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.**

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/img/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

20
assets/img/load.svg Normal file
View File

@ -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

BIN
assets/img/random/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
assets/img/random/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/img/random/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
assets/img/random/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/img/random/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
assets/img/random/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
assets/img/random/7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/img/random/8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/img/smiley/beer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

BIN
assets/img/smiley/bz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/img/smiley/cool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

BIN
assets/img/smiley/cry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

BIN
assets/img/smiley/doubt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

BIN
assets/img/smiley/eek.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

BIN
assets/img/smiley/evil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

BIN
assets/img/smiley/grin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

BIN
assets/img/smiley/idea.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/img/smiley/lol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

BIN
assets/img/smiley/love.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

BIN
assets/img/smiley/mad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/img/smiley/oops.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/img/smiley/razz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

BIN
assets/img/smiley/roll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

BIN
assets/img/smiley/sad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

BIN
assets/img/smiley/scare.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/img/smiley/shock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

BIN
assets/img/smiley/smile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

BIN
assets/img/smiley/warn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

BIN
assets/img/smiley/wink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/img/smiley/xmas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

488
assets/js/gt4.js Normal file
View File

@ -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);

20
assets/js/html2canvas.min.js vendored Normal file

File diff suppressed because one or more lines are too long

6
assets/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
assets/js/layer.js Normal file

File diff suppressed because one or more lines are too long

174
assets/js/libs.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1184
assets/js/puock.js Normal file

File diff suppressed because it is too large Load Diff

1
assets/js/spark-md5.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
assets/layer/layer.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -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);

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

View File

@ -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
card.php Normal file
View File

59
cms.php Normal file
View File

@ -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>&nbsp;<?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>

227
comments.php Normal file
View File

@ -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>&nbsp;登录
</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>&nbsp;发布评论
</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('&laquo;', '&raquo;', 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']; ?>&nbsp;
<?php
echo $deviceInfo['system']
?
: '未知系统';
?>
</span>
<!-- 浏览器信息 -->
<span class="mt10" title="<?php echo $deviceInfo['browser'] . ' ' . $deviceInfo['version']; ?>">
<?php echo $icons['browser']; ?>&nbsp;
<?php
echo $deviceInfo['browser']
?
: '未知浏览器';
?>
</span>
<?php if($comments->ip): ?>
<span class="mt10" title="IP">
<i class="fa-solid fa-location-dot"></i>&nbsp;<?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 } ?>

106
footer.php Normal file
View File

@ -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>
&copy; <?php echo date('Y'); ?> <a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>
<div class="fs12 mt10 c-sub">
<span> &nbsp;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> &nbsp;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>

896
functions.php Normal file
View File

@ -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'].'&#10;✌️'.$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, '/');
}

172
header.php Normal file
View File

@ -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; ?>

95
index.php Normal file
View File

@ -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('&laquo;', '&raquo;', 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'); ?>

4
ip2region/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Created by .ignore support plugin (hsz.mobi)
/.idea
/vendor/
/composer.lock

83
ip2region/Ip2Region.php Normal file
View File

@ -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);
}
}

225
ip2region/LICENSE.md Normal file
View File

@ -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.

229
ip2region/README.md Normal file
View File

@ -0,0 +1,229 @@
[![Latest Stable Version](https://poser.pugx.org/zoujingli/ip2region/v/stable)](https://packagist.org/packages/zoujingli/ip2region)
[![Total Downloads](https://poser.pugx.org/zoujingli/ip2region/downloads)](https://packagist.org/packages/zoujingli/ip2region)
[![Monthly Downloads](https://poser.pugx.org/zoujingli/ip2region/d/monthly)](https://packagist.org/packages/zoujingli/ip2region)
[![Daily Downloads](https://poser.pugx.org/zoujingli/ip2region/d/daily)](https://packagist.org/packages/zoujingli/ip2region)
[![PHP Version Require](http://poser.pugx.org/zoujingli/ip2region/require/php)](https://packagist.org/packages/ip2region)
[![License](https://poser.pugx.org/zoujingli/ip2region/license)](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) |
| &nbsp;&nbsp;&nbsp; | 待开始 | [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) |
| &nbsp;&nbsp;&nbsp; | 待开始 | [php_ext](https://github.com/lionsoul2014/ip2region/blob/master/binding/php7_ext) | php c 扩展 xdb 查询客户端实现 | 待确定 |
| &nbsp;&nbsp;&nbsp; | 待开始 | [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) |
| &nbsp;&nbsp;&nbsp; | 待开始 | [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 文件的相同的源文件。

368
ip2region/XdbSearcher.php Normal file
View File

@ -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);
}
}

52
ip2region/_test.php Normal file
View File

@ -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);
}

26
ip2region/composer.json Normal file
View File

@ -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"
]
}
}

BIN
ip2region/ip2region.xdb Normal file

Binary file not shown.

66
page-archives.php Normal file
View File

@ -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 . '&nbsp;&nbsp;' . $day_tmp . '日&nbsp;</a></li>';
}
// 循环结束后关闭最后的标签
if ($year > 0 && $mon > 0) {
$output .= '</ul></div></div>';
}
echo $output;
?>
</div> </div> </div> </div> </div>
<?php $this->need('footer.php'); ?>

52
page-commentuserlist.php Normal file
View File

@ -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']; ?>)&nbsp;</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']; ?>)&nbsp;</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'); ?>

26
page-full.php Normal file
View File

@ -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'); ?>

50
page-links.php Normal file
View File

@ -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'); ?>

89
page-tags.php Normal file
View File

@ -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'); ?>

64
page.php Normal file
View File

@ -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'); ?>

192
post.php Normal file
View File

@ -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>&nbsp;<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'); ?>

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

231
sidebar.php Normal file
View File

@ -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>

124
sticky.php Normal file
View File

@ -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);
}
?>