8 Commits

Author SHA1 Message Date
浪子 c313e29d99 1.1.6 2025-07-07 19:39:19 +08:00
浪子 33bc5d53f5 1.1.5 2025-07-07 17:30:50 +08:00
浪子 58b2e80883 1.1.4 2025-07-07 13:47:59 +08:00
浪子 c7efd5abd6 1.1.3
- 兼容typecho 1.3.0版本
    - 修复pjax黑暗模式下闪烁问题
    - 修复说说页面的pjax加载问题
    - 修复上一篇与下一篇文章的链接
    - 增加归属地显示设置
    - 增加系统显示设置
    - 增加浏览器信息显示设置
    - 修复pjax模式下的404跳转
2025-07-05 12:09:23 +08:00
浪子 28a72cb279 1.1.2
不再使用API获取IP归属地
2025-07-03 19:33:06 +08:00
浪子 5abd899791 1.1.1
修复评论
fix 卡片分页
2025-07-03 19:23:08 +08:00
浪子 e604c6b4d3 1.1.1
Implemented a function to handle and validate user input in the main application module. This improves input reliability and prepares the codebase for future enhancements.
2025-06-29 20:13:19 +08:00
浪子 074af5187a Refactor IP region lookup to use external API
Replaced the local ip2region database and related PHP classes with an external API (ip.asbid.cn) for IP region lookup in functions.php. Removed the entire ip2region directory and its dependencies, simplifying maintenance and reducing local storage requirements. Updated caching logic to store API results for one month. Also updated the theme version in index.php to 1.1.
2025-06-29 11:29:04 +08:00
50 changed files with 44914 additions and 500 deletions
+24 -5
View File
@@ -18,23 +18,42 @@
document.addEventListener('DOMContentLoaded', function() {
var countdown = 5; // 设置倒计时时间(秒)
var countdownElement = document.getElementById('time-count-down');
// 检查是否在 PJAX 环境中
var isPjax = typeof window.Pjax !== 'undefined' ||
(typeof jQuery !== 'undefined' && jQuery.pjax);
// 更新倒计时显示
function updateCountdown() {
countdownElement.textContent = countdown;
countdown--;
countdown--;
if (countdown < 0) {
// 倒计时结束,跳转到首页
window.location.href = "<?php $this->options->siteUrl(); ?>";
var homeUrl = "<?php $this->options->siteUrl(); ?>";
if (isPjax) {
// 使用 PJAX 方式跳转
if (typeof window.Pjax !== 'undefined') {
// 使用原生 PJAX
var pjax = new Pjax();
pjax.loadUrl(homeUrl);
} else if (typeof jQuery !== 'undefined' && jQuery.pjax) {
// 使用 jQuery PJAX
$.pjax({url: homeUrl, container: '[data-pjax-container]'});
}
} else {
// 普通跳转
window.location.href = homeUrl;
}
} else {
// 继续倒计时
setTimeout(updateCountdown, 1000);
}
}
// 开始倒计时
updateCountdown();
// 如果是 PJAX 加载,需要手动执行一些初始化
if (isPjax && typeof window.puockInit !== 'undefined') {
window.puockInit();
}
});
</script>
<?php $this->need('footer.php'); ?>
+18 -2
View File
@@ -20,6 +20,22 @@
4. 主题部分功能需要安装插件`Puock`。项目地址: [Puock Plugin](https://github.com/jkjoy/typecho-plugin-puock)
5. 友情链接功能需要使用插件`Links`。项目地址: [Links Plugin](https://file.imsun.org/upload/2025-06/Links-1.2.7.zip),首页友情链接需要在`友链分类`中添加分类`home`
### 主题配置
### 更新
- 全部标签页面需要启用php的 `iconv` 扩展
- 2025.07.05
- 1.1.3
- 兼容typecho 1.3.0版本
- 修复pjax黑暗模式下闪烁问题
- 修复说说页面的pjax加载问题
- 修复上一篇与下一篇文章的链接
- 增加归属地显示设置
- 增加系统显示设置
- 增加浏览器信息显示设置
- 修复pjax模式下的404跳转
---
### 2024-07-07 表情短代码解析集成
- 1.1.5
- 将表情短代码(如 :smile:)自动解析为图片表情,集成到主题评论内容输出中。
+26 -14
View File
@@ -74,9 +74,6 @@ class Puock {
this.registerModeChangeEvent()
this.eventCommentPageChangeEvent()
this.eventCommentPreSubmit()
this.eventSmiley()
this.eventOpenCommentBox()
this.eventCloseCommentBox()
this.eventSendPostLike()
this.eventPostMainBoxResize()
this.swiperOnceEvent()
@@ -84,6 +81,8 @@ class Puock {
this.detectDevice()
window.addEventListener('resize', ()=>this.detectDevice());
layer.config({shade: 0.5})
// 新增:首次加载时初始化评论相关事件
this.initCommentEvents();
}
pageInit() {
@@ -465,6 +464,8 @@ class Puock {
// $('#post-main, #sidebar').theiaStickySidebar({
// additionalMarginTop: 20
// });
// 新增:pjax切换后重新初始化评论相关事件
this.initCommentEvents();
}
@@ -879,33 +880,37 @@ class Puock {
}
eventOpenCommentBox() {
$(document).on("click", "[id^=comment-reply-]", (e) => {
this.data.comment.replyId = $(this.ct(e)).attr("data-id");
$(document).off("click", ".comment-reply");
$(document).on("click", ".comment-reply", (e) => {
e.preventDefault();
this.data.comment.replyId = $(this.ct(e)).attr("data-coid");
if ($.trim(this.data.comment.replyId) === '') {
this.toast('结构有误', TYPE_DANGER);
return;
}
const cf = $("#comment-form"),
cb = $("#comment-box-" + this.data.comment.replyId);
cf.addClass("box-sw");
cb.removeClass("d-none").append(cf);
commentLi = $(this.ct(e)).closest('.post-comment');
commentLi.append(cf);
$("#comment-cancel").removeClass("d-none");
$("#comment").val("");
$("#comment_parent").val(this.data.comment.replyId);
})
// 滚动至表单
if (cf.length && cf[0].scrollIntoView) {
cf[0].scrollIntoView({behavior: "smooth", block: "center"});
}
});
}
eventCloseCommentBox() {
$(document).off("click", "#comment-cancel");
$(document).on("click", "#comment-cancel", () => {
const cf = $("#comment-form"),
cb = $("#comment-box-" + this.data.comment.replyId);
cb = $(".post-comment .box-sw").parent();
cf.removeClass("box-sw");
cb.addClass("d-none");
$("#comment-form-box").append(cf);
$("#comment-cancel").addClass("d-none");
this.data.comment.replyId = null;
})
});
}
eventSendPostLike() {
@@ -949,11 +954,12 @@ class Puock {
}
eventSmiley() {
$(document).off('click', '.smiley-img');
$(document).on('click', '.smiley-img', (e) => {
const comment = $("#comment");
comment.val(comment.val() + ' ' + $(this.ct(e)).attr("data-id") + ' ');
layer.closeAll();
})
});
}
startLoading() {
@@ -1171,6 +1177,12 @@ class Puock {
return t;
}
// 新增:统一初始化评论相关事件
initCommentEvents() {
this.eventOpenCommentBox();
this.eventCloseCommentBox();
this.eventSmiley();
}
}
jQuery(() => {
+16
View File
@@ -63,4 +63,20 @@ $colors = ['bg-primary', 'bg-secondary', 'bg-success', 'bg-danger', 'bg-warning'
</div>
</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>
+100 -121
View File
@@ -21,7 +21,6 @@
<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: ?>
@@ -60,6 +59,7 @@
<i class="fa-regular fa-face-smile t-md"></i>
</button>
<?php endif; ?>
<input type="hidden" name="parent" id="comment-parent" value="">
<button type="submit" id="comment-submit" class="btn btn-primary btn-ssm">
<i class="fa-regular fa-paper-plane"></i>&nbsp;发布评论
</button>
@@ -69,49 +69,49 @@
</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>
<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> 评论已关闭
@@ -172,21 +172,15 @@
</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)
); ?>
<a rel="nofollow" class="hide-info animated bounceIn c-sub-a t-sm ml-1 comment-reply" href="javascript:void(0);" data-coid="<?php echo $comments->coid; ?>"><span class="comment-reply-text"><i class="fa fa-share-from-square"></i>回复</span></a>
</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();?>
<?php if ($comments->parent) {echo getPermalinkFromCoid($comments->parent);} echo parse_smiley_shortcode($comments->content);?>
<div class="comment-os c-sub">
<?php
<?php
$deviceInfo = getBrowsersInfo($comments->agent);
$icons = getDeviceIcon($deviceInfo);
?>
@@ -199,7 +193,6 @@
: '未知系统';
?>
</span>
<!-- 浏览器信息 -->
<span class="mt10" title="<?php echo $deviceInfo['browser'] . ' ' . $deviceInfo['version']; ?>">
<?php echo $icons['browser']; ?>&nbsp;
@@ -209,12 +202,12 @@
: '未知浏览器';
?>
</span>
<!-- IP 地理位置 -->
<?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; ?>
<?php endif; ?>
</div>
</div>
</div>
@@ -226,71 +219,57 @@
</li>
<?php } ?>
<script>
var TypechoComment = {
dom : function (id) {
return document.getElementById(id);
},
create : function (tag, attr) {
var el = document.createElement(tag);
for (var key in attr) {
el.setAttribute(key, attr[key]);
// 评论局部回复,表单移动到评论下方
document.addEventListener('DOMContentLoaded', function() {
// 监听评论区的回复按钮
document.body.addEventListener('click', function(e) {
var target = e.target;
if (
target.classList.contains('comment-reply') ||
(target.parentNode && target.parentNode.classList && target.parentNode.classList.contains('comment-reply'))
) {
e.preventDefault();
// 兼容span嵌套与a标签直接点击
var replyBtn = target.classList.contains('comment-reply') ? target : target.parentNode;
var commentId = replyBtn.getAttribute('data-coid');
var commentLi = replyBtn.closest('.post-comment');
var respondBox = document.getElementById('comment-form-box');
var commentForm = document.getElementById('comment-form');
var cancelBtn = document.getElementById('comment-cancel');
var parentInput = document.getElementById('comment-parent');
// 记录原位置
if (!document.getElementById('comment-form-place-holder')) {
var holder = document.createElement('div');
holder.id = 'comment-form-place-holder';
respondBox.parentNode.insertBefore(holder, respondBox);
}
// 移动表单
commentLi.appendChild(respondBox);
// 设置parent
if (parentInput) parentInput.value = commentId;
// 展示取消按钮
if(cancelBtn) cancelBtn.classList.remove('d-none');
// 聚焦文本域
var textarea = commentForm.querySelector('textarea');
if (textarea) textarea.focus();
// 滚动至表单
respondBox.scrollIntoView({behavior: "smooth", block: "center"});
return false;
}
return el;
},
reply : function (cid, coid) {
var comment = this.dom(cid), parent = comment.parentNode,
response = this.dom('comment-form-box'), holder = response.parentNode,
form = this.dom('comment-form'), textarea = this.dom('comment'),
submit = this.dom('comment-submit'),
cancel = this.dom('comment-cancel');
if (null != this.dom('comment-form-place-holder')) {
var holder = this.dom('comment-form-place-holder');
// 取消回复
if (target.id === 'comment-cancel') {
e.preventDefault();
var respondBox = document.getElementById('comment-form-box');
var holder = document.getElementById('comment-form-place-holder');
var parentInput = document.getElementById('comment-parent');
if (holder) {
holder.parentNode.insertBefore(respondBox, holder);
holder.parentNode.removeChild(holder);
}
if (parentInput) parentInput.value = '';
target.classList.add('d-none');
return false;
}
// 移动评论表单
if (null == this.dom('comment-form-place-holder')) {
var holder = this.create('div', {
'id': 'comment-form-place-holder'
});
response.parentNode.insertBefore(holder, response);
}
// 修改表单中的内容
comment.appendChild(response);
// 显示取消回复按钮
cancel.style.display = '';
// 记录回复的评论ID
this.dom('comment-parent').value = coid;
// 移动光标到评论框
textarea.focus();
return false;
},
cancelReply : function () {
var response = this.dom('comment-form-box'), holder = this.dom('comment-form-place-holder'),
comment = this.dom('comment-parent');
if (null != holder) {
holder.parentNode.insertBefore(response, holder);
holder.parentNode.removeChild(holder);
}
// 隐藏取消回复按钮
this.dom('comment-cancel').style.display = 'none';
// 清除回复评论ID
comment.value = '';
return false;
}
};
});
});
</script>
+5
View File
@@ -0,0 +1,5 @@
{
"require": {
"overtrue/pinyin": "^4.1"
}
}
Generated
+92
View File
@@ -0,0 +1,92 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1d353c32c45b49f759b47ca83156c5c1",
"packages": [
{
"name": "overtrue/pinyin",
"version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/overtrue/pinyin.git",
"reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/overtrue/pinyin/zipball/4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7",
"reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"brainmaestro/composer-git-hooks": "^2.7",
"friendsofphp/php-cs-fixer": "^2.16",
"phpunit/phpunit": "~8.0"
},
"type": "library",
"extra": {
"hooks": {
"pre-push": [
"composer test",
"composer check-style"
],
"pre-commit": [
"composer test",
"composer fix-style"
]
}
},
"autoload": {
"files": [
"src/const.php"
],
"psr-4": {
"Overtrue\\Pinyin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "overtrue",
"email": "anzhengchao@gmail.com",
"homepage": "http://github.com/overtrue"
}
],
"description": "Chinese to pinyin translator.",
"homepage": "https://github.com/overtrue/pinyin",
"keywords": [
"Chinese",
"Pinyin",
"cn2pinyin"
],
"support": {
"issues": "https://github.com/overtrue/pinyin/issues",
"source": "https://github.com/overtrue/pinyin/tree/4.1.0"
},
"funding": [
{
"url": "https://github.com/overtrue",
"type": "github"
}
],
"time": "2023-04-27T10:17:12+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
}
+1 -2
View File
@@ -85,7 +85,7 @@
"home": "<?php $this->options->siteUrl(); ?>",
"use_post_menu": true,
"is_single": false,
"is_pjax": false,
"is_pjax": true,
"main_lazy_img": true,
"link_blank_open": true,
//"async_view_id": null,
@@ -102,5 +102,4 @@
<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>
+76 -168
View File
@@ -161,6 +161,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['likeup']) && isset($_
}
exit;
}
/**
* 随机封面
*/
@@ -183,128 +184,7 @@ function getPostCover($content, $cid, $fields = null) {
}
}
/**
* 获取上一篇文章
*
* @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() {
@@ -315,6 +195,7 @@ function getAllCommenters() {
$query = $db->select('author, mail, COUNT(*) as comment_count, url')
->from('table.comments')
->where('status = ?', 'approved') // 只统计已通过审核的评论
->where('authorId != ?', 1) // 排除ID为1的管理员
->group('mail')
->order('comment_count', Typecho_Db::SORT_DESC);
@@ -338,6 +219,7 @@ function getAllCommenters() {
return $commenters;
}
/**
* 获取IP归属地
*/
@@ -379,6 +261,11 @@ function format_ip_region($region) {
return $item !== '0' && $item !== '';
});
// 如果第一个元素是"中国",则移除
if (isset($parts[0]) && $parts[0] === '中国') {
array_shift($parts);
}
// 重新拼接
return implode('', $parts);
}
@@ -407,8 +294,7 @@ function get_ip_region($ip) {
* @param string $userAgent 用户代理
* @return string[]
*/
function getBrowsersInfo ($userAgent) {
function getBrowsersInfo($userAgent) {
$deviceInfo = [
"system" => "",
"systemVersion" => "",
@@ -417,7 +303,6 @@ function getBrowsersInfo ($userAgent) {
"device" => "PC"
];
$match = [
// 浏览器 - 国外浏览器
"Safari" => strstr($userAgent, 'Safari') != false ,
@@ -492,7 +377,6 @@ function getBrowsersInfo ($userAgent) {
"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;
@@ -554,9 +438,6 @@ function getBrowsersInfo ($userAgent) {
if ($deviceInfo['systemVersion'] == $userAgent) $deviceInfo['systemVersion'] = '';
}
// if ($deviceInfo['system'] == 'Windows' && $_windowsVersion) $deviceInfo['systemVersion'] = $_windowsVersion;
// 浏览器版本信息
$browsers_360SE = [
108 => '14.0',
@@ -832,53 +713,33 @@ function getPermalinkFromCoid($coid) {
if (empty($row)) return '';
return '<a href="#comment-'.$coid.'" class="c-sub">@'.$row['author'].'</a>';
}
/**
* 全部标签按字母书序排列
*/
// 获取中文字符首字母的函数
// 引入 Composer 自动加载
require __DIR__ . '/vendor/autoload.php';
use Overtrue\Pinyin\Pinyin;
function getFirstChar($str) {
if (empty($str)) return '#';
// 如果是数字
if (is_numeric($str[0])) {
$pinyin = new Pinyin();
$firstChar = mb_substr($str, 0, 1, 'UTF-8');
// 数字
if (is_numeric($firstChar)) {
return '0';
}
// 如果是英文字母
$firstChar = ord($str[0]);
if ($firstChar >= ord('A') && $firstChar <= ord('z')) {
return strtoupper($str[0]);
// 英文字母
if (preg_match('/^[a-zA-Z]$/', $firstChar)) {
return strtoupper($firstChar);
}
// 如果是中文
$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 '#';
// 中文转拼音首字母
$abbr = $pinyin->abbr($firstChar, '');
return strtoupper($abbr[0] ?? '#');
}
/**
@@ -1057,4 +918,51 @@ class AttachmentHelper {
<?php
}
}
/**
* 解析表情短代码为图片
* @param string $content
* @return string
*/
function parse_smiley_shortcode($content) {
$smileys = [
':?:' => 'doubt.png',
':razz:' => 'razz.png',
':sad:' => 'sad.png',
':evil:' => 'evil.png',
':naughty:' => 'naughty.png',
':!:' => 'scare.png',
':smile:' => 'smile.png',
':oops:' => 'oops.png',
':neutral:' => 'neutral.png',
':cry:' => 'cry.png',
':mrgreen:' => 'mrgreen.png',
':grin:' => 'grin.png',
':eek:' => 'eek.png',
':shock:' => 'shock.png',
':???:' => 'bz.png',
':cool:' => 'cool.png',
':lol:' => 'lol.png',
':mad:' => 'mad.png',
':twisted:' => 'twisted.png',
':roll:' => 'roll.png',
':wink:' => 'wink.png',
':idea:' => 'idea.png',
':despise:' => 'despise.png',
':celebrate:' => 'celebrate.png',
':watermelon:' => 'watermelon.png',
':xmas:' => 'xmas.png',
':warn:' => 'warn.png',
':rainbow:' => 'rainbow.png',
':loveyou:' => 'loveyou.png',
':love:' => 'love.png',
':beer:' => 'beer.png',
];
$themeUrl = Helper::options()->themeUrl . '/assets/img/smiley/';
foreach ($smileys as $code => $img) {
$imgTag = '<img class="smiley-img" src="' . $themeUrl . $img . '" alt="' . $code . '" title="表情" style="width:24px;height:24px;vertical-align:middle;" />';
$content = str_replace($code, $imgTag, $content);
}
return $content;
}
?>
+2 -4
View File
@@ -4,7 +4,6 @@
<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 下的文章'),
@@ -17,10 +16,10 @@
<?php $this->options->addhead(); ?>
<style id='puock-inline-css' type='text/css'>
body {
--pk-c-primary: <?php if ($this->options->primaryColor): ?><?php $this->options->primaryColor() ?><?php else: ?>#A7E6F4<?php endif; ?>;
--pk-c-primary: <?php if ($this->options->primaryColor): ?><?php $this->options->primaryColor() ?><?php else: ?>#A7E6F4<?php endif; ?> !important;
}
:root {
--puock-block-not-tran: <?php if ($this->options->blockNotTransparent): ?><?php $this->options->blockNotTransparent() ?><?php else: ?>100<?php endif; ?>%;
--puock-block-not-tran: <?php if ($this->options->blockNotTransparent): ?><?php $this->options->blockNotTransparent() ?><?php else: ?>100<?php endif; ?>% !important;
}
</style>
<?php if ($this->options->icoUrl): ?>
@@ -32,7 +31,6 @@
<!-- 通过自有函数输出HTML头部信息 -->
<?php $this->header(); ?>
</head>
<body class="puock-auto custom-background">
<div>
<div id="header-box" class="animated fadeInDown"></div>
+1 -1
View File
@@ -4,7 +4,7 @@
*
* @package Typecho Pouck Theme
* @author 老孙博客
* @version 1.0.4
* @version 1.1.6
* @link http://www.imsun.org
*/
+9 -17
View File
@@ -15,32 +15,28 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
</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
<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>';
@@ -48,19 +44,15 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
$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'); ?>
?>
</div></div></div></div></div>
<?php $this->need('footer.php'); ?>
+4 -1
View File
@@ -14,7 +14,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
</ol>
</nav>
</div>
<div id="page-links">
<div id="page">
<div class="row row-cols-1">
<div id="posts" class="col-12 animated fadeInLeft ">
<div class="puock-text no-style">
@@ -22,5 +22,8 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
</div>
</div>
</div>
<?php if ($this->allow('comment')): ?>
<?php $this->need('comments.php'); ?>
<?php endif; ?>
</div>
<?php $this->need('footer.php'); ?>
+21 -37
View File
@@ -7,44 +7,28 @@
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>
<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 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>
<?php $this->need('footer.php'); ?>
</div>
</div>
<?php if ($this->allow('comment')): ?>
<?php $this->need('comments.php'); ?>
<?php endif; ?>
</div>
<?php $this->need('footer.php'); ?>
+55 -53
View File
@@ -13,7 +13,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<li class="breadcrumb-item active " aria-current="page"><?php $this->title() ?></li>
</ol>
</nav>
<div id="page-moments">
<div id="page-moments">
<div class="row">
<div id="posts" class="col-lg-8 col-md-12 animated fadeInLeft ">
<?php $tooot = $this->fields->tooot ? $this->fields->tooot : 'https://www.imsun.org/toot.json'; ?>
@@ -21,66 +21,59 @@ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.5/css/lightbox.min.css" integrity="sha512-xtV3HfYNbQXS/1R1jP53KbFcU9WXiSA1RFKzl5hRlJgdOJm4OxHCWYpskm6lN0xp0XtKGpAfVShpbvlFH3MDAA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.5/js/lightbox.min.js" integrity="sha512-KbRFbjA5bwNan6DvPl1ODUolvTTZ/vckssnFhka5cG80JVa5zSlRPCr055xSgU/q6oMIGhZWLhcbgIC0fyw3RQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="tooot"></div>
</div>
</div>
<script>
window.onload = function() {
let offset = 0; // 初始偏移量
const limit = 20; // 每次加载的数量
function fetchAndDisplayToots() {
let offset = 0;
const limit = 20;
function formatHTML(toots) {
let htmlString = '';
let htmlString = '';
toots.forEach(toot => {
// 判断是否存在 reblog
const isReblog = toot.reblog && toot.reblog.content;
const content = isReblog ? toot.reblog.content : toot.content;
const url = isReblog ? toot.reblog.url : toot.url;
const account = isReblog ? toot.reblog.account : toot.account;
const created_at = isReblog ? toot.reblog.created_at : toot.created_at;
const media_attachments = isReblog ? toot.reblog.media_attachments : toot.media_attachments;
let mediaHTML = ''; // 初始化资源相关HTML为空字符串
// 处理媒体附件
if (media_attachments.length > 0) {
let mediaHTML = '';
if (media_attachments && media_attachments.length > 0) {
media_attachments.forEach(attachment => {
if (attachment.type === 'image') {
mediaHTML += `<a href="${attachment.url}" target="_blank" data-lightbox="image-set"><img src="${attachment.preview_url}" class="thumbnail-image img" ></a>`;
}
});
}
// 使用 marked 转换 markdown 内容为 HTML
const htmlContent = marked.parse(content);
// 创建 HTML 字符串
const htmlContent = marked.parse(content || '');
htmlString += `
<div class="mb20 puock-text moments-item">
<div class="row">
<div class="col-12 col-md-1">
<div class="col-12 col-md-1">
<a class="meta ta3" href="${account.url}" target="_blank" rel="nofollow">
<div class="avatar mb10">
<img src='${account.avatar}'
class='lazy md-avatar mt-1'
data-src='${account.avatar}'
alt="${account.display_name}" title="${account.display_name}">
</div>
<div class="t-line-1 info fs12">${account.display_name}</div>
</a>
<div class="avatar mb10">
<img src='${account.avatar}'
class='lazy md-avatar mt-1'
data-src='${account.avatar}'
alt="${account.display_name}" title="${account.display_name}">
</div>
<div class="col-12 col-md-11">
<div class="p-block moment-content-box"> <span class="al"></span>
<div class="mt10 moment-content entry-content show-link-icon">
<p>${htmlContent}
</p>
<div class="resimg">${mediaHTML}</div>
</div>
<div class="mt10 moment-footer p-flex-s-right"> <span class="t-sm c-sub">
<a class="c-sub-a" href="${url}">
<span class="mr-2"><i class="fa-regular fa-clock mr-1"></i>${new Date(created_at).toLocaleString()}</span>
</a>
</span>
</div>
</div>
</div>
<div class="t-line-1 info fs12">${account.display_name}</div>
</a>
</div>
<div class="col-12 col-md-11">
<div class="p-block moment-content-box"> <span class="al"></span>
<div class="mt10 moment-content entry-content show-link-icon">
<p>${htmlContent}</p>
<div class="resimg">${mediaHTML}</div>
</div>
<div class="mt10 moment-footer p-flex-s-right"> <span class="t-sm c-sub">
<a class="c-sub-a" href="${url}">
<span class="mr-2"><i class="fa-regular fa-clock mr-1"></i>${new Date(created_at).toLocaleString()}</span>
</a>
</span>
</div>
</div>
</div>
</div>
</div>
`;
});
return htmlString;
@@ -93,17 +86,26 @@ window.onload = function() {
return [];
});
}
function fetchAndDisplayToots() {
fetchToots().then(data => {
const memosContainer = document.getElementById('tooot');
const tootsToShow = data.slice(offset, offset + limit); // 选择要显示的toots
memosContainer.innerHTML += formatHTML(tootsToShow);
});
}
// 在页面加载完成后获取并展示 toots
fetchAndDisplayToots();
};
</script>
const memosContainer = document.getElementById('tooot');
if (memosContainer) memosContainer.innerHTML = '';
fetchToots().then(data => {
if (!Array.isArray(data)) {
console.error('toot.json is not an array:', data);
return;
}
const tootsToShow = data.slice(offset, offset + limit);
if (memosContainer) memosContainer.innerHTML += formatHTML(tootsToShow);
});
}
// 保证首次和 pjax 都能调用
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", fetchAndDisplayToots);
} else {
fetchAndDisplayToots();
}
document.addEventListener('pjax:end', fetchAndDisplayToots);
</script>
<style>
div pre code {
white-space: pre-wrap;
@@ -161,6 +163,6 @@ img {
}
</style>
<?php $this->need('sidebar.php'); ?>
</div>
</div>
<?php $this->need('footer.php'); ?>
</div>
</div>
<?php $this->need('footer.php'); ?>
+1 -1
View File
@@ -61,4 +61,4 @@ if($days > 180){
<?php endif; ?>
</div>
<?php $this->need('sidebar.php'); ?>
<?php $this->need('footer.php'); ?>
<?php $this->need('footer.php'); ?>
+89 -59
View File
@@ -90,6 +90,7 @@ if($days > 180){
</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">
@@ -120,69 +121,98 @@ if($days > 180){
</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>
<?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">
<div class="col-6 p-border-r-1 p-0">
<?php
// 查询上一篇(比当前文章更早的文章)
$db = Typecho_Db::get();
$prev = $db->fetchRow($db->select('cid', 'title', 'slug', 'created')
->from('table.contents')
->where('created < ?', $this->created) // 比当前文章更早
->where('type = ?', 'post') // 只查询文章,排除页面
->where('status = ?', 'publish') // 只查询已发布的
->order('created', Typecho_Db::SORT_DESC) // 按时间降序(最近的上一篇)
->limit(1));
if ($prev):
// 生成正确链接(兼容伪静态和自定义固定链接)
$prevUrl = Typecho_Router::url('post', $prev, $this->options->index);
?>
<a href="<?php echo $prevUrl; ?>" rel="prev" title="<?php echo $prev['title']; ?>">
<div class="abhl puock-text">
<p class="t-line-1"><?php echo $prev['title']; ?></p>
<span>上一篇</span>
</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> <!--评论上方-->
</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 endif; ?>
</div>
<div class="col-6 p-0">
<?php
// 查询下一篇(比当前文章更新的文章)
$next = $db->fetchRow($db->select('cid', 'title', 'slug', 'created')
->from('table.contents')
->where('created > ?', $this->created) // 比当前文章更新
->where('type = ?', 'post') // 只查询文章,排除页面
->where('status = ?', 'publish') // 只查询已发布的
->order('created', Typecho_Db::SORT_ASC) // 按时间升序(最早的下一条)
->limit(1));
if ($next):
// 生成正确链接(兼容伪静态和自定义固定链接)
$nextUrl = Typecho_Router::url('post', $next, $this->options->index);
?>
<a href="<?php echo $nextUrl; ?>" rel="next" title="<?php echo $next['title']; ?>">
<div class="abhl puock-text">
<p class="t-line-1"><?php echo $next['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 endif; ?>
</div>
</div>
</div>
<!--评论上方-->
<?php $this->need('comments.php'); ?>
<?php if ($this->options->articlefoot): ?>
<!--文章底部广告-->
<div class="puock-text p-block t-md ad-comment-top"><?php $this->options->articlefoot(); ?></div>
<?php endif; ?>
</div>
+12 -15
View File
@@ -138,21 +138,18 @@ if ($totalViews === null) $totalViews = 0;
<div class="mt20">
<?php foreach ($hotPosts as $post): ?>
<?php
// Typecho 1.3.0 兼容处理
$widget = $this->widget('Widget_Archive@post_' . $post['cid'], 'type=post');
// 更可靠的获取文章链接方式
$widget = Typecho_Widget::widget('Widget_Contents_Post_Recent');
$permalink = '';
try {
$widget->setArchiveProperty('cid', $post['cid']);
$widget->setArchiveProperty('title', $post['title']);
$widget->setArchiveProperty('slug', $post['slug']);
$widget->setArchiveProperty('created', $post['created']);
$widget->setArchiveProperty('authorId', $post['authorId']);
$widget->setArchiveProperty('type', $post['type']);
$widget->setArchiveProperty('status', $post['status']);
$widget->setArchiveProperty('commentsNum', $post['commentsNum']);
// 生成正确链接
$permalink = $widget->archiveUrl;
// 方法1:使用Typecho的Router类
$permalink = Typecho_Router::url('post', $post, $this->options->index);
// 方法2:或者使用辅助函数(如果方法1不行)
if (empty($permalink)) {
$widget->push($post);
$permalink = $widget->permalink;
$widget->pop();
}
if (empty($post['title']) || empty($permalink)) {
continue;
}
@@ -177,7 +174,7 @@ if ($totalViews === null) $totalViews = 0;
<?php endif; ?>
<!-- 最近评论 -->
<?php if (!empty($this->options->sidebarBlock) && in_array('ShowRecentComments', $this->options->sidebarBlock)): ?>
<?php
<?php
// 设置参数来排除管理员评论
$comments = \Widget\Comments\Recent::alloc(array(
'ignoreAuthor' => true // 这里添加参数来排除作者/管理员评论
+25
View File
@@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit7f036e4555c1f5c49b96f869c5b4f1bf::getLoader();
+579
View File
@@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
+359
View File
@@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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.
+10
View File
@@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);
+10
View File
@@ -0,0 +1,10 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'dc1275c308c5b416beb314b6317daca2' => $vendorDir . '/overtrue/pinyin/src/const.php',
);
+9
View File
@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);
+10
View File
@@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Overtrue\\Pinyin\\' => array($vendorDir . '/overtrue/pinyin/src'),
);
+50
View File
@@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit7f036e4555c1f5c49b96f869c5b4f1bf
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit7f036e4555c1f5c49b96f869c5b4f1bf', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit7f036e4555c1f5c49b96f869c5b4f1bf', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit7f036e4555c1f5c49b96f869c5b4f1bf::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit7f036e4555c1f5c49b96f869c5b4f1bf::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit7f036e4555c1f5c49b96f869c5b4f1bf
{
public static $files = array (
'dc1275c308c5b416beb314b6317daca2' => __DIR__ . '/..' . '/overtrue/pinyin/src/const.php',
);
public static $prefixLengthsPsr4 = array (
'O' =>
array (
'Overtrue\\Pinyin\\' => 16,
),
);
public static $prefixDirsPsr4 = array (
'Overtrue\\Pinyin\\' =>
array (
0 => __DIR__ . '/..' . '/overtrue/pinyin/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit7f036e4555c1f5c49b96f869c5b4f1bf::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit7f036e4555c1f5c49b96f869c5b4f1bf::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit7f036e4555c1f5c49b96f869c5b4f1bf::$classMap;
}, null, ClassLoader::class);
}
}
+82
View File
@@ -0,0 +1,82 @@
{
"packages": [
{
"name": "overtrue/pinyin",
"version": "4.1.0",
"version_normalized": "4.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/overtrue/pinyin.git",
"reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/overtrue/pinyin/zipball/4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7",
"reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"brainmaestro/composer-git-hooks": "^2.7",
"friendsofphp/php-cs-fixer": "^2.16",
"phpunit/phpunit": "~8.0"
},
"time": "2023-04-27T10:17:12+00:00",
"type": "library",
"extra": {
"hooks": {
"pre-push": [
"composer test",
"composer check-style"
],
"pre-commit": [
"composer test",
"composer fix-style"
]
}
},
"installation-source": "dist",
"autoload": {
"files": [
"src/const.php"
],
"psr-4": {
"Overtrue\\Pinyin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "overtrue",
"email": "anzhengchao@gmail.com",
"homepage": "http://github.com/overtrue"
}
],
"description": "Chinese to pinyin translator.",
"homepage": "https://github.com/overtrue/pinyin",
"keywords": [
"Chinese",
"Pinyin",
"cn2pinyin"
],
"support": {
"issues": "https://github.com/overtrue/pinyin/issues",
"source": "https://github.com/overtrue/pinyin/tree/4.1.0"
},
"funding": [
{
"url": "https://github.com/overtrue",
"type": "github"
}
],
"install-path": "../overtrue/pinyin"
}
],
"dev": true,
"dev-package-names": []
}
+32
View File
@@ -0,0 +1,32 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'overtrue/pinyin' => array(
'pretty_version' => '4.1.0',
'version' => '4.1.0.0',
'reference' => '4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7',
'type' => 'library',
'install_path' => __DIR__ . '/../overtrue/pinyin',
'aliases' => array(),
'dev_requirement' => false,
),
),
);
+26
View File
@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}
+9
View File
@@ -0,0 +1,9 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: overtrue
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
custom: # Replace with a single custom sponsorship URL
+12
View File
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: daily
time: "21:00"
open-pull-requests-limit: 10
ignore:
- dependency-name: phpunit/phpunit
versions:
- ">= 8.a, < 9"
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 安正超
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.
+130
View File
@@ -0,0 +1,130 @@
<h1 align="center">Pinyin</h1>
<p align="center">
[![Build Status](https://travis-ci.org/overtrue/pinyin.svg?branch=master)](https://travis-ci.org/overtrue/pinyin)
[![Latest Stable Version](https://poser.pugx.org/overtrue/pinyin/v/stable.svg)](https://packagist.org/packages/overtrue/pinyin) [![Total Downloads](https://poser.pugx.org/overtrue/pinyin/downloads.svg)](https://packagist.org/packages/overtrue/pinyin) [![Latest Unstable Version](https://poser.pugx.org/overtrue/pinyin/v/unstable.svg)](https://packagist.org/packages/overtrue/pinyin) [![License](https://poser.pugx.org/overtrue/pinyin/license.svg)](https://packagist.org/packages/overtrue/pinyin)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/overtrue/pinyin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/overtrue/pinyin/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/overtrue/pinyin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/overtrue/pinyin/?branch=master)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fovertrue%2Fpinyin.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fovertrue%2Fpinyin?ref=badge_shield)
</p>
:cn: 基于 [CC-CEDICT](http://cc-cedict.org/wiki/) 词典的中文转拼音工具,更准确的支持多音字的汉字转拼音解决方案。
## 安装
使用 Composer 安装:
```
$ composer require "overtrue/pinyin:~4.0"
```
## 使用
可选转换方案:
- 内存型,适用于服务器内存空间较富余,优点:转换快
- 小内存型(默认),适用于内存比较紧张的环境,优点:占用内存小,转换不如内存型快
- I/O型,适用于虚拟机,内存限制比较严格环境。优点:非常微小内存消耗。缺点:转换慢,不如内存型转换快,php >= 5.5
## 可用选项:
| 选项 | 描述 |
| ------------- | ---------------------------------------------------|
| `PINYIN_TONE` | UNICODE 式音调:`měi hǎo` |
| `PINYIN_ASCII_TONE` | 带数字式音调: `mei3 hao3` |
| `PINYIN_NO_TONE` | 无音调:`mei hao` |
| `PINYIN_KEEP_NUMBER` | 保留数字 |
| `PINYIN_KEEP_ENGLISH` | 保留英文 |
| `PINYIN_KEEP_PUNCTUATION` | 保留标点 |
| `PINYIN_UMLAUT_V` | 使用 `v` 代替 `yu`, 例如:吕 `lyu` 将会转为 `lv` |
### 拼音数组
```php
use Overtrue\Pinyin\Pinyin;
// 小内存型
$pinyin = new Pinyin(); // 默认
// 内存型
// $pinyin = new Pinyin('\\Overtrue\\Pinyin\\MemoryFileDictLoader');
// I/O型
// $pinyin = new Pinyin('\\Overtrue\\Pinyin\\GeneratorFileDictLoader');
$pinyin->convert('带着希望去旅行,比到达终点更美好');
// ["dai", "zhe", "xi", "wang", "qu", "lyu", "xing", "bi", "dao", "da", "zhong", "dian", "geng", "mei", "hao"]
$pinyin->convert('带着希望去旅行,比到达终点更美好', PINYIN_TONE);
// ["dài","zhe","xī","wàng","qù","lǚ","xíng","bǐ","dào","dá","zhōng","diǎn","gèng","měi","hǎo"]
$pinyin->convert('带着希望去旅行,比到达终点更美好', PINYIN_ASCII_TONE);
//["dai4","zhe","xi1","wang4","qu4","lyu3","xing2","bi3","dao4","da2","zhong1","dian3","geng4","mei3","hao3"]
```
- 小内存型: 将字典分片载入内存
- 内存型: 将所有字典预先载入内存
- I/O型: 不载入内存,将字典使用文件流打开逐行遍历并运用php5.5生成器(yield)特性分配单行内存
### 生成用于链接的拼音字符串
```php
$pinyin->permalink('带着希望去旅行'); // dai-zhe-xi-wang-qu-lyu-xing
$pinyin->permalink('带着希望去旅行', '.'); // dai.zhe.xi.wang.qu.lyu.xing
```
### 获取首字符字符串
```php
$pinyin->abbr('带着希望去旅行'); // dzxwqlx
$pinyin->abbr('带着希望去旅行', '-'); // d-z-x-w-q-l-x
$pinyin->abbr('你好2018', PINYIN_KEEP_NUMBER); // nh2018
$pinyin->abbr('Happy New Year! 2018', PINYIN_KEEP_ENGLISH); // HNY2018
```
### 翻译整段文字为拼音
将会保留中文字符:`,。 “ ” ` 并替换为对应的英文符号。
```php
$pinyin->sentence('带着希望去旅行,比到达终点更美好!');
// dai zhe xi wang qu lyu xing, bi dao da zhong dian geng mei hao!
$pinyin->sentence('带着希望去旅行,比到达终点更美好!', PINYIN_TONE);
// dài zhe xī wàng qù lǚ xíng, bǐ dào dá zhōng diǎn gèng měi hǎo!
```
### 翻译姓名
姓名的姓的读音有些与普通字不一样,比如 ‘单’ 常见的音为 `dan`,而作为姓的时候读 `shan`
```php
$pinyin->name('单某某'); // ['shan', 'mou', 'mou']
$pinyin->name('单某某', PINYIN_TONE); // ["shàn","mǒu","mǒu"]
```
更多使用请参考 [测试用例](https://github.com/overtrue/pinyin/blob/master/tests/AbstractDictLoaderTestCase.php)。
## 在 Laravel 中使用
独立的包在这里:[overtrue/laravel-pinyin](https://github.com/overtrue/laravel-pinyin)
## Contribution
欢迎提意见及完善补充词库 [`overtrue/pinyin-dictionary-maker`](https://github.com/overtrue/pinyin-dictionary-maker/tree/master/patches) :kiss:
## 参考
- [详细参考资料](https://github.com/overtrue/pinyin-resources)
## PHP 扩展包开发
> 想知道如何从零开始构建 PHP 扩展包?
>
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
# License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fovertrue%2Fpinyin.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fovertrue%2Fpinyin?ref=badge_large)
+68
View File
@@ -0,0 +1,68 @@
{
"name": "overtrue/pinyin",
"description": "Chinese to pinyin translator.",
"keywords": [
"chinese",
"pinyin",
"cn2pinyin"
],
"homepage": "https://github.com/overtrue/pinyin",
"license": "MIT",
"authors": [
{
"name": "overtrue",
"homepage": "http://github.com/overtrue",
"email": "anzhengchao@gmail.com"
}
],
"autoload": {
"psr-4": {
"Overtrue\\Pinyin\\": "src/"
},
"files": ["src/const.php"]
},
"autoload-dev": {
"psr-4": {
"Overtrue\\Pinyin\\Test\\": "tests/"
}
},
"require": {
"php":">=7.1"
},
"require-dev": {
"phpunit/phpunit": "~8.0",
"brainmaestro/composer-git-hooks": "^2.7",
"friendsofphp/php-cs-fixer": "^2.16"
},
"extra": {
"hooks": {
"pre-commit": [
"composer test",
"composer fix-style"
],
"pre-push": [
"composer test",
"composer check-style"
]
}
},
"scripts": {
"post-update-cmd": [
"cghooks update"
],
"post-merge": "composer install",
"post-install-cmd": [
"cghooks add --ignore-lock",
"cghooks update"
],
"cghooks": "vendor/bin/cghooks",
"check-style": "php-cs-fixer fix --using-cache=no --diff --config=.php_cs --dry-run --ansi",
"fix-style": "php-cs-fixer fix --using-cache=no --config=.php_cs --ansi",
"test": "vendor/bin/phpunit --colors=always"
},
"scripts-descriptions": {
"test": "Run all tests.",
"check-style": "Run style checks (only dry run - no fixing!).",
"fix-style": "Run style checks and fix violations."
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
return array (
'万俟' => ' mò qí',
'尉迟' => ' yù chí',
'单于' => ' chán yú',
'不' => ' fǒu',
'沈' => ' shěn',
'称' => ' chēng',
'车' => ' chē',
'万' => ' wàn',
'汤' => ' tāng',
'阿' => ' ā',
'丁' => ' dīng',
'强' => ' qiáng',
'仇' => ' qiú',
'叶' => ' yè',
'阚' => ' kàn',
'乐' => ' yuè',
'乜' => ' niè',
'陆' => ' lù',
'殷' => ' yīn',
'牟' => ' móu',
'区' => ' ōu',
'宿' => ' sù',
'俞' => ' yú',
'余' => ' yú',
'齐' => ' qí',
'许' => ' xǔ',
'信' => ' xìn',
'无' => ' wú',
'浣' => ' wǎn',
'艾' => ' ài',
'浅' => ' qiǎn',
'烟' => ' yān',
'蓝' => ' lán',
'於' => ' yú',
'寻' => ' xún',
'殳' => ' shū',
'思' => ' sī',
'鸟' => ' niǎo',
'卜' => ' bǔ',
'单' => ' shàn',
'南' => ' nán',
'柏' => ' bǎi',
'朴' => ' piáo',
'繁' => ' pó',
'曾' => ' zēng',
'瞿' => ' qú',
'缪' => ' miào',
'石' => ' shí',
'冯' => ' féng',
'覃' => ' qín',
'幺' => ' yāo',
'种' => ' chóng',
'折' => ' shè',
'燕' => ' yān',
'纪' => ' jǐ',
'过' => ' guō',
'华' => ' huà',
'冼' => ' xiǎn',
'秘' => ' bì',
'重' => ' chóng',
'解' => ' xiè',
'那' => ' nā',
'和' => ' hé',
'贾' => ' jiǎ',
'塔' => ' tǎ',
'盛' => ' shèng',
'查' => ' zhā',
'盖' => ' gě',
'居' => ' jū',
'哈' => ' hǎ',
'的' => ' dē',
'薄' => ' bó',
'佴' => ' nài',
'六' => ' lù',
'都' => ' dū',
'翟' => ' zhái',
'扎' => ' zā',
'藏' => ' zàng',
'粘' => ' niàn',
'难' => ' nàn',
'若' => ' ruò',
'貟' => ' yùn',
'贠' => ' yùn',
);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the overtrue/pinyin.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Pinyin;
use Closure;
/**
* Dict loader interface.
*/
interface DictLoaderInterface
{
/**
* Load dict.
*
* <pre>
* [
* '响应时间' => "[\t]xiǎng[\t]yìng[\t]shí[\t]jiān",
* '长篇连载' => '[\t]cháng[\t]piān[\t]lián[\t]zǎi',
* //...
* ]
* </pre>
*
* @param Closure $callback
*/
public function map(Closure $callback);
/**
* Load surname dict.
*
* @param Closure $callback
*/
public function mapSurname(Closure $callback);
}
+73
View File
@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the overtrue/pinyin.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Pinyin;
use Closure;
class FileDictLoader implements DictLoaderInterface
{
/**
* Words segment name.
*
* @var string
*/
protected $segmentName = 'words_%s';
/**
* Dict path.
*
* @var string
*/
protected $path;
/**
* Constructor.
*
* @param string $path
*/
public function __construct($path)
{
$this->path = $path;
}
/**
* Load dict.
*
* @param Closure $callback
*/
public function map(Closure $callback)
{
for ($i = 0; $i < 100; ++$i) {
$segment = $this->path . '/' . sprintf($this->segmentName, $i);
if (file_exists($segment)) {
$dictionary = (array) include $segment;
$callback($dictionary);
}
}
}
/**
* Load surname dict.
*
* @param Closure $callback
*/
public function mapSurname(Closure $callback)
{
$surnames = $this->path . '/surnames';
if (file_exists($surnames)) {
$dictionary = (array) include $surnames;
$callback($dictionary);
}
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
/*
* This file is part of the overtrue/pinyin.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Pinyin;
use Closure;
use SplFileObject;
use Generator;
class GeneratorFileDictLoader implements DictLoaderInterface
{
/**
* Data directory.
*
* @var string
*/
protected $path;
/**
* Words segment name.
*
* @var string
*/
protected $segmentName = 'words_%s';
/**
* SplFileObjects.
*
* @var array
*/
protected static $handles = [];
/**
* surnames.
*
* @var SplFileObject
*/
protected static $surnamesHandle;
/**
* Constructor.
*
* @param string $path
*/
public function __construct($path)
{
$this->path = $path;
for ($i = 0; $i < 100; ++$i) {
$segment = $this->path . '/' . sprintf($this->segmentName, $i);
if (file_exists($segment) && is_file($segment)) {
array_push(static::$handles, $this->openFile($segment));
}
}
}
/**
* Construct a new file object.
*
* @param string $filename file path
* @param string $mode file open mode
*
* @return SplFileObject
*/
protected function openFile($filename, $mode = 'r')
{
return new SplFileObject($filename, $mode);
}
/**
* get Generator syntax.
*
* @param array $handles SplFileObjects
*
* @return Generator
*/
protected function getGenerator(array $handles)
{
foreach ($handles as $handle) {
$handle->seek(0);
while (false === $handle->eof()) {
$string = str_replace(['\'', ' ', PHP_EOL, ','], '', $handle->fgets());
if (false === strpos($string, '=>')) {
continue;
}
list($string, $pinyin) = explode('=>', $string);
yield $string => $pinyin;
}
}
}
/**
* Traverse the stream.
*
* @param Generator $generator
* @param Closure $callback
*
* @author Seven Du <shiweidu@outlook.com>
*/
protected function traversing(Generator $generator, Closure $callback)
{
foreach ($generator as $string => $pinyin) {
$callback([$string => $pinyin]);
}
}
/**
* Load dict.
*
* @param Closure $callback
*/
public function map(Closure $callback)
{
$this->traversing($this->getGenerator(static::$handles), $callback);
}
/**
* Load surname dict.
*
* @param Closure $callback
*/
public function mapSurname(Closure $callback)
{
if (!static::$surnamesHandle instanceof SplFileObject) {
static::$surnamesHandle = $this->openFile($this->path . '/surnames');
}
$this->traversing($this->getGenerator([static::$surnamesHandle]), $callback);
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
/*
* This file is part of the overtrue/pinyin.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Pinyin;
use Closure;
class MemoryFileDictLoader implements DictLoaderInterface
{
/**
* Data directory.
*
* @var string
*/
protected $path;
/**
* Words segment name.
*
* @var string
*/
protected $segmentName = 'words_%s';
/**
* Segment files.
*
* @var array
*/
protected $segments = [];
/**
* Surname cache.
*
* @var array
*/
protected $surnames = [];
/**
* Constructor.
*
* @param string $path
*/
public function __construct($path)
{
$this->path = $path;
for ($i = 0; $i < 100; ++$i) {
$segment = $path . '/' . sprintf($this->segmentName, $i);
if (file_exists($segment)) {
$this->segments[] = (array) include $segment;
}
}
}
/**
* Load dict.
*
* @param Closure $callback
*/
public function map(Closure $callback)
{
foreach ($this->segments as $dictionary) {
$callback($dictionary);
}
}
/**
* Load surname dict.
*
* @param Closure $callback
*/
public function mapSurname(Closure $callback)
{
if (empty($this->surnames)) {
$surnames = $this->path . '/surnames';
if (file_exists($surnames)) {
$this->surnames = (array) include $surnames;
}
}
$callback($this->surnames);
}
}
+343
View File
@@ -0,0 +1,343 @@
<?php
/*
* This file is part of the overtrue/pinyin.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Pinyin;
use InvalidArgumentException;
class Pinyin
{
/**
* Dict loader.
*
* @var \Overtrue\Pinyin\DictLoaderInterface
*/
protected $loader;
/**
* Punctuations map.
*
* @var array
*/
protected $punctuations = [
'' => ',',
'。' => '.',
'' => '!',
'' => '?',
'' => ':',
'“' => '"',
'”' => '"',
'' => "'",
'' => "'",
'_' => '_',
];
/**
* Constructor.
*
* @param string $loaderName
*/
public function __construct($loaderName = null)
{
$this->loader = $loaderName ?: 'Overtrue\\Pinyin\\FileDictLoader';
}
/**
* Convert string to pinyin.
*
* @param string $string
* @param int $option
*
* @return array
*/
public function convert($string, $option = PINYIN_DEFAULT)
{
$pinyin = $this->romanize($string, $option);
return $this->splitWords($pinyin, $option);
}
/**
* Convert string (person name) to pinyin.
*
* @param string $stringName
* @param int $option
*
* @return array
*/
public function name($stringName, $option = PINYIN_NAME)
{
$option = $option | PINYIN_NAME;
$pinyin = $this->romanize($stringName, $option);
return $this->splitWords($pinyin, $option);
}
/**
* Return a pinyin permalink from string.
*
* @param string $string
* @param string $delimiter
* @param int $option
*
* @return string
*/
public function permalink($string, $delimiter = '-', $option = PINYIN_DEFAULT)
{
if (\is_int($delimiter)) {
list($option, $delimiter) = [$delimiter, '-'];
}
if (!in_array($delimiter, ['_', '-', '.', ''], true)) {
throw new InvalidArgumentException("Delimiter must be one of: '_', '-', '', '.'.");
}
return implode($delimiter, $this->convert($string, $option | \PINYIN_KEEP_NUMBER | \PINYIN_KEEP_ENGLISH));
}
/**
* Return first letters.
*
* @param string $string
* @param string $delimiter
* @param int $option
*
* @return string
*/
public function abbr($string, $delimiter = '', $option = PINYIN_DEFAULT)
{
if (\is_int($delimiter)) {
list($option, $delimiter) = [$delimiter, ''];
}
return implode($delimiter, array_map(function ($pinyin) {
return \is_numeric($pinyin) || preg_match('/\d+/', $pinyin) ? $pinyin : mb_substr($pinyin, 0, 1);
}, $this->convert($string, $option | PINYIN_NO_TONE)));
}
/**
* Chinese phrase to pinyin.
*
* @param string $string
* @param string $delimiter
* @param int $option
*
* @return string
*/
public function phrase($string, $delimiter = ' ', $option = PINYIN_DEFAULT)
{
if (\is_int($delimiter)) {
list($option, $delimiter) = [$delimiter, ' '];
}
return implode($delimiter, $this->convert($string, $option));
}
/**
* Chinese to pinyin sentence.
*
* @param string $string
* @param string $delimiter
* @param int $option
*
* @return string
*/
public function sentence($string, $delimiter = ' ', $option = \PINYIN_NO_TONE)
{
if (\is_int($delimiter)) {
list($option, $delimiter) = [$delimiter, ' '];
}
return implode($delimiter, $this->convert($string, $option | \PINYIN_KEEP_PUNCTUATION | \PINYIN_KEEP_ENGLISH | \PINYIN_KEEP_NUMBER));
}
/**
* Loader setter.
*
* @param \Overtrue\Pinyin\DictLoaderInterface $loader
*
* @return $this
*/
public function setLoader(DictLoaderInterface $loader)
{
$this->loader = $loader;
return $this;
}
/**
* Return dict loader,.
*
* @return \Overtrue\Pinyin\DictLoaderInterface
*/
public function getLoader()
{
if (!($this->loader instanceof DictLoaderInterface)) {
$dataDir = dirname(__DIR__) . '/data/';
$loaderName = $this->loader;
$this->loader = new $loaderName($dataDir);
}
return $this->loader;
}
/**
* Convert Chinese to pinyin.
*
* @param string $string
* @param int $option
*
* @return string
*/
protected function romanize($string, $option = \PINYIN_DEFAULT)
{
$string = $this->prepare($string, $option);
$dictLoader = $this->getLoader();
if ($this->hasOption($option, \PINYIN_NAME)) {
$string = $this->convertSurname($string, $dictLoader);
}
$dictLoader->map(function ($dictionary) use (&$string) {
$string = strtr($string, $dictionary);
});
return $string;
}
/**
* Convert Chinese Surname to pinyin.
*
* @param string $string
* @param \Overtrue\Pinyin\DictLoaderInterface $dictLoader
*
* @return string
*/
protected function convertSurname($string, $dictLoader)
{
$dictLoader->mapSurname(function ($dictionary) use (&$string) {
foreach ($dictionary as $surname => $pinyin) {
if (0 === strpos($string, $surname)) {
$string = $pinyin . mb_substr($string, mb_strlen($surname, 'UTF-8'), mb_strlen($string, 'UTF-8') - 1, 'UTF-8');
break;
}
}
});
return $string;
}
/**
* Split pinyin string to words.
*
* @param string $pinyin
* @param string $option
*
* @return array
*/
protected function splitWords($pinyin, $option)
{
$split = array_filter(preg_split('/\s+/i', $pinyin));
if (!$this->hasOption($option, PINYIN_TONE)) {
foreach ($split as $index => $pinyin) {
$split[$index] = $this->formatTone($pinyin, $option);
}
}
return array_values($split);
}
/**
* @param int $option
* @param int $check
*
* @return bool
*/
public function hasOption($option, $check)
{
return ($option & $check) === $check;
}
/**
* Pre-process.
*
* @param string $string
* @param int $option
*
* @return string
*/
protected function prepare($string, $option = \PINYIN_DEFAULT)
{
$string = preg_replace_callback('~[a-z0-9_-]+~i', function ($matches) {
return "\t" . $matches[0];
}, $string);
$regex = ['\x{3007}\x{2E80}-\x{2FFF}\x{3100}-\x{312F}\x{31A0}-\x{31EF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}', '\p{Z}', '\p{M}', "\t"];
if ($this->hasOption($option, \PINYIN_KEEP_NUMBER)) {
\array_push($regex, '0-9');
}
if ($this->hasOption($option, \PINYIN_KEEP_ENGLISH)) {
\array_push($regex, 'a-zA-Z');
}
if ($this->hasOption($option, \PINYIN_KEEP_PUNCTUATION)) {
$punctuations = array_merge($this->punctuations, ["\t" => ' ', ' ' => ' ']);
$string = trim(str_replace(array_keys($punctuations), $punctuations, $string));
\array_push($regex, preg_quote(implode(array_merge(array_keys($this->punctuations), $this->punctuations)), '~'));
}
return preg_replace(\sprintf('~[^%s]~u', implode($regex)), '', $string);
}
/**
* Format.
*
* @param string $pinyin
* @param int $option
*
* @return string
*/
protected function formatTone($pinyin, $option = \PINYIN_NO_TONE)
{
$replacements = [
// mb_chr(593) => 'ɑ' 轻声中除了 `ɑ` 和 `ü` 以外,其它和字母一样
'ɑ' => ['a', 5], 'ü' => ['yu', 5],
'üē' => ['ue', 1], 'üé' => ['ue', 2], 'üě' => ['ue', 3], 'üè' => ['ue', 4],
'ā' => ['a', 1], 'ē' => ['e', 1], 'ī' => ['i', 1], 'ō' => ['o', 1], 'ū' => ['u', 1], 'ǖ' => ['yu', 1],
'á' => ['a', 2], 'é' => ['e', 2], 'í' => ['i', 2], 'ó' => ['o', 2], 'ú' => ['u', 2], 'ǘ' => ['yu', 2],
'ǎ' => ['a', 3], 'ě' => ['e', 3], 'ǐ' => ['i', 3], 'ǒ' => ['o', 3], 'ǔ' => ['u', 3], 'ǚ' => ['yu', 3],
'à' => ['a', 4], 'è' => ['e', 4], 'ì' => ['i', 4], 'ò' => ['o', 4], 'ù' => ['u', 4], 'ǜ' => ['yu', 4],
];
foreach ($replacements as $unicode => $replacement) {
if (false !== strpos($pinyin, $unicode)) {
$umlaut = $replacement[0];
// https://zh.wikipedia.org/wiki/%C3%9C
if ($this->hasOption($option, \PINYIN_UMLAUT_V) && 'yu' == $umlaut) {
$umlaut = 'v';
}
$pinyin = str_replace($unicode, $umlaut, $pinyin) . ($this->hasOption($option, PINYIN_ASCII_TONE) ? $replacement[1] : '');
}
}
return $pinyin;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the overtrue/pinyin.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
define('PINYIN_DEFAULT', 4096);
define('PINYIN_TONE', 2);
define('PINYIN_NO_TONE', 4);
define('PINYIN_ASCII_TONE', 8);
define('PINYIN_NAME', 16);
define('PINYIN_KEEP_NUMBER', 32);
define('PINYIN_KEEP_ENGLISH', 64);
define('PINYIN_UMLAUT_V', 128);
define('PINYIN_KEEP_PUNCTUATION', 256);