2025.1.3 更新
nodeloc support 安装(测试版),实现左侧最下方的友情赞助
composer require nodeloc/support-widget:dev-main
增加外链跳转确认,我在pay-to-see插件里直接加的。
https://sx.sd/d/305
具体代码为:加到vendor/ziiven/flarum-pay-to-see/src/ProcessContent.php下面,改后的完整代码为:
<?php
namespace Ziven\pay2see;
use Flarum\Locale\Translator;
use Ziven\pay2see\Model\PaidDiscussion;
class ProcessContent{
protected $translator;
public function __construct(Translator $translator){
$this->translator = $translator;
}
public function __invoke($serializer, $model, $attributes){
if (isset($attributes["contentHtml"])) {
$newContent = $attributes['contentHtml'];
$discussionID = $model->discussion_id;
$actor = $serializer->getActor();
$currentUserID = $actor->id;
$discussionOwnerID = $model->discussion->user_id;
$discussionCost = $model->discussion->pay2see_cost;
$postOwnerID = $model->user_id;
if($postOwnerID!==$discussionOwnerID || !isset($discussionCost)){
}else{
$isPaid = false;
$paidContentText = $this->translator->trans('pay-to-see.forum.pay_to_see_content');
$allowBypassPay2See = $actor->can('pay2see.allowBypassPay2See', $model->user);
if($model->number==1){
$count = PaidDiscussion::where(['user_id'=>$currentUserID,'discussion_id'=>$discussionID])->count();
$serializer->is_paid = $isPaid = $count>0;
}else{
$isPaid = property_exists($serializer, 'is_paid')?$serializer->is_paid:false;
}
if($isPaid===true || $currentUserID===$discussionOwnerID || $allowBypassPay2See){
$paidContentClassName = "pay2see_gray";
$paidHeaderClassName = "pay2see_header_gray";
$paidContentText = $this->translator->trans('pay-to-see.forum.pay_to_see_owner_bypass');
if($currentUserID!==$discussionOwnerID && $isPaid===true){
$paidContentText = $this->translator->trans('pay-to-see.forum.purchased_content');
$paidContentClassName = "pay2see_green";
$paidHeaderClassName = "pay2see_header_green";
}
if($isPaid===false && $allowBypassPay2See){
$paidContentText = $this->translator->trans('pay-to-see.forum.pay_to_see_content_bypass');
}
$attributes['contentHtml'] = $newContent = str_replace('[pay]','<div class="'.$paidContentClassName.'"><div class="'.$paidHeaderClassName.'">'.$paidContentText.'</div>',$newContent);
$attributes['contentHtml'] = $newContent = str_replace('[/pay]','</div>',$newContent);
}else{
$replacement = '<blockquote style="text-align:center;padding:30px"><div><p>'.$this->translator->trans('pay-to-see.forum.pay_to_see_content').'</p></div></blockquote>';
$attributes['contentHtml'] = self::replaceContent($newContent,"[pay]","[/pay]",$replacement);
}
}
}
return $attributes;
}
public function replaceContent($str, $start, $end, $replacement) {
$start = preg_quote($start, '/');
$end = preg_quote($end, '/');
$regex = "/$start(.*?)$end/is";
return preg_replace($regex,$replacement,$str);
}
public function getContent($string, $start, $end){
$start = preg_quote($start, '/');
$end = preg_quote($end, '/');
$regex = "/$start(.*?)$end/is";
preg_match($regex, $string, $matches);
return $matches[1];
}
}
//*增加外链确认引用
class FormatContent
{
public function __invoke($serializer, $model, $attributes)
{
if (isset($attributes["contentHtml"])) {
$newHTML = $attributes["contentHtml"];
// 检查 HTML 内容是否为空
if (!is_null($newHTML)) {
// 使用正则表达式匹配所有的链接
$newHTML = preg_replace_callback(
'/<a\s+(?:[^>]*?\s+)?href=["\']([^"\']+)["\']([^>]*)>(.*?)<\/a>/is',
function ($matches) {
$url = $matches[1]; // 获取链接的 href 值
$attributes = $matches[2]; // 获取链接的其他属性
$text = $matches[3]; // 获取链接的文本
// 判断是否为站内链接或内部链接
if ($this->isExternalLink($url)) {
// 对非站内链接添加跳转提示
$redirectUrl = '/goto/' . urlencode($url);
return "<a href=\"{$redirectUrl}\" {$attributes} target=\"_blank\" rel=\"noopener noreferrer\">{$text}</a>";
}
// 保持原样处理站内链接
return $matches[0];
},
$newHTML
);
}
$attributes['contentHtml'] = $newHTML;
}
return $attributes;
}
/**
* 检查是否为外部链接
*
* @param string $url URL地址
* @return bool 如果是外部链接返回 true,否则返回 false
*/
private function isExternalLink($url)
{
// 如果 URL 为空,返回 false
if (empty($url)) {
return false;
}
// 处理特殊协议
if (preg_match('/^(javascript|mailto|tel|sms|file):/i', $url)) {
return false;
}
// 如果是以 // 开头的协议相对 URL
if (strpos($url, '//') === 0) {
$url = 'http:' . $url;
}
// 获取当前请求的主机名
$currentHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
$currentHost = rtrim($currentHost, '/');
// 解析 URL
$urlParts = parse_url($url);
// 如果 URL 无法解析或没有 host 部分(相对路径),认为是内部链接
if ($urlParts === false || empty($urlParts['host'])) {
return false;
}
// 标准化主机名(移除 www. 前缀并转为小写)
$currentHost = strtolower(preg_replace('/^www\./i', '', $currentHost));
$urlHost = strtolower(preg_replace('/^www\./i', '', $urlParts['host']));
// 检查域名是否在允许的内部域名列表中
$internalDomains = [
$currentHost,
// 可以添加其他允许的域名
's.sx.sd',
// 'otherdomain.com'
];
return !in_array($urlHost, $internalDomains, true);
}
//*外链确认引用完成
}
在 vendor/ziiven/flarum-pay-to-see/extend.php 里边Inject一下,改后完整代码为
<?php
use Flarum\Extend;
use Flarum\Api\Serializer\BasicPostSerializer;
use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Discussion\Event\Saving as DiscussionSaving;
use Flarum\Post\Event\Saving as PostSaving;
use Ziven\pay2see\ProcessContent;
use Ziven\pay2see\AddDiscussionPaidAttributes;
use Ziven\pay2see\Controller\PayToSeePurchaseController;
use Ziven\pay2see\Controller\ListPayToSeePurchasedUserController;
use Ziven\pay2see\Controller\PayToSeeSetController;
use Ziven\pay2see\Listeners\AddPayToSeeDiscussion;
use Ziven\pay2see\Listeners\AddPayToSeePost;
use Ziven\pay2see\Notification\PayToSeeBlueprint;
use Ziven\pay2see\FormatContent; //*增加外链确认引用
$extend = [
(new Extend\Frontend('admin'))->js(__DIR__.'/js/dist/admin.js')->css(__DIR__.'/less/admin.less'),
(new Extend\Frontend('forum'))->js(__DIR__ . '/js/dist/forum.js')->css(__DIR__.'/less/forum.less'),
(new Extend\Locales(__DIR__ . '/locale')),
(new Extend\Routes('api'))
->post('/pay2seePurchase', 'pay2see.create', PayToSeePurchaseController::class)
->get('/pay2seePurchase', 'pay2see.purchased', ListPayToSeePurchasedUserController::class)
->post('/pay2seeSet', 'pay2see.set', PayToSeeSetController::class),
(new Extend\Event())
->listen(DiscussionSaving::class, AddPayToSeeDiscussion::class)
->listen(PostSaving::class, AddPayToSeePost::class),
(new Extend\ApiSerializer(DiscussionSerializer::class))
->attributes(AddDiscussionPaidAttributes::class)
->attribute('pay2seeCount', function (DiscussionSerializer $serializer, $discussion) {
return $discussion->pay2see_count;
}),
(new Extend\ApiSerializer(BasicPostSerializer::class))
->attributes(ProcessContent::class),
(new Extend\ApiSerializer(ForumSerializer::class))
->attribute('allowUsePay2See', function (ForumSerializer $serializer) {
return $serializer->getActor()->hasPermission("pay2see.allowUsePay2See");
})
->attribute('allowSetPay2See', function (ForumSerializer $serializer) {
return $serializer->getActor()->hasPermission("pay2see.allowSetPay2See");
})
->attribute('allowPurchasePay2See', function (ForumSerializer $serializer) {
return $serializer->getActor()->hasPermission("pay2see.allowPurchasePay2See");
}),
(new Extend\Settings())
->default('pay2see.pay2seeAllowTags', [])
->serializeToForum('pay2seeAllowTags', 'pay2see.pay2seeAllowTags')
->serializeToForum('pay2seeContentBadge', 'pay2see.pay2seeContentBadge', 'strval'),
(new Extend\Notification())
->type(PayToSeeBlueprint::class, DiscussionSerializer::class, ['alert']),
//*增加外链确认引用
(new Extend\ApiSerializer(PostSerializer::class))
->attributes(FormatContent::class),
(new Extend\ApiSerializer(BasicPostSerializer::class))
->attributes(FormatContent::class),
//*外链确认引用完成
];
return $extend;
public目录下建一个goto目录,新建一个index.php文件,内容为:
<?php
// 获取跳转目标 URL
$redirectUrl = $_SERVER['REQUEST_URI'];
$redirectUrl = str_replace('/goto/', '', $redirectUrl);
// 初始化错误消息和解码后的链接变量
$errorMessage = null;
$decodedLink = null;
// 检查 URL 是否为空
if (empty($redirectUrl)) {
$errorMessage = "跳转链接无效或为空。请检查您的链接。";
$redirectUrl = "#"; // 默认值
} else {
// 对链接进行解码
$decodedLink = urldecode($redirectUrl);
// 验证解码后的链接是否为有效的 URL
if (filter_var($decodedLink, FILTER_VALIDATE_URL)) {
// 确保链接是外部链接
$host = parse_url($decodedLink, PHP_URL_HOST);
if ($host === $_SERVER['HTTP_HOST']) {
$errorMessage = "不支持站内链接跳转。";
$redirectUrl = "#"; // 不合法时默认值
} else {
$redirectUrl = $decodedLink; // 合法的外部链接
}
} else {
$errorMessage = "无效的 URL,请检查链接格式。";
$redirectUrl = "#"; // 无效链接时默认值
}
}
// 对最终链接进行 HTML 特殊字符编码,确保安全
$redirectUrl = htmlspecialchars($redirectUrl, ENT_QUOTES, 'UTF-8');
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>跳转提示</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #009966;
background-image: linear-gradient(135deg, #009966, #006644);
color: #ffffff;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.container {
text-align: center;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
width: 90%;
max-width: 480px;
box-sizing: border-box;
}
.container h1 {
font-size: 22px;
margin-bottom: 10px;
}
.container p {
font-size: 14px;
margin-bottom: 20px;
word-wrap: break-word;
word-break: break-all;
}
.container a {
display: inline-block;
background-color: #FF9933;
color: #ffffff;
text-decoration: none;
font-size: 14px;
font-weight: bold;
padding: 10px 20px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.container a:hover {
background-color: #cc7a29;
}
.container .small-text {
font-size: 12px;
margin-top: 15px;
color: rgba(255, 255, 255, 0.8);
}
.error-message {
color: #FF0000;
font-size: 16px;
font-weight: bold;
margin-bottom: 20px;
}
/* 支持老旧浏览器的渐变背景 */
@supports not (background: linear-gradient(135deg, #009966, #006644)) {
body {
background-color: #009966;
}
}
</style>
</head>
<body>
<div class="container">
<h1>跳转提示</h1>
<?php if (isset($errorMessage)): ?>
<div class="error-message"><?= $errorMessage ?></div>
<?php else: ?>
<p>您正在离开本站并访问以下链接:</p>
<p><strong><?= $redirectUrl ?></strong></p>
<a href="<?= $redirectUrl ?>" rel="noopener noreferrer">继续访问</a>
<?php endif; ?>
<p class="small-text">如果您不想访问此链接,请关闭此页面。</p>
</div>
</body>
</html>
最后,最重要的一步就是,伪静态
location /goto/ {
rewrite ^/goto/(https?.+)$ /goto/index.php?url=$1 last;
}