Files
typecho/var/Widget/Service.php
joyqi 3caebb3b20 v1.3.0 (#1661)
* Add feed widget

* add feed render

* Add CommentPage widget

* New theme (#1390)

* 调整忽略目录

* add theme

* fix theme scss build

Co-authored-by: fen <f3nb0x@gmail.com>

* s/is_writeable/is_writable/g

* New upgrade method

* merge new fixes from master

* add pgsql ssl mode support (ref #1600) (#1623)

* Feat/code refactor (#1626)

* remove all magic methods, add type for class properties

* refactor codes

* fix all

* refactor code

* fix type

* fix all

* fix request is method

* fix all

* fix router

* fix get page

* fix 1.3.0 upgrade

* [feat] support high resolution avatar

* fix types in i18n component

* Implement Ctrl+S or Command+S for save draft (#1628)

* Implement Ctrl+S or Command+S for save draft

* rename

* add Typecho.savePost

* fix upload file size

* add new uploader

* replace new uploader

* fix textarea change

* fix preview

* refactor post edit

* fix issue

* fix page edit

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: joyqi <magike.net@gmail.com>

* fix #1632

* Add svg to image types

* Feat/tree pages (#1646)

* add tree trait

* finish category tree trait

* support select fields

* fix select fields

* refactor admin trait

* fix draft status

* Add new contents type "revision"

* minor refactor

* add more tree view abstracts

* add tree trait to pages

* get ready for tree view pages

* improve page edit

* fix revision

* fix slug

* add router params delegate

* fix params delegate

* fix

* fix

* fix all

* fix all

* fix tree

* fix page link

* fix feed

* fix page

* fix permalink

* fix permalink input

* fix offset query

* Support IDN (#1629)

* Support IDN

* use js

* Optimize code

* Optimize code

* fix URL script

* remove unnecessary use

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>

* fix input element

* fix #1651, close #1653

* Use json instead of serialize (#1624)

* Use json instead of serialize

* Fix Upgrade code

* add tree trait

* finish category tree trait

* support select fields

* fix select fields

* refactor admin trait

* fix draft status

* Add new contents type "revision"

* minor refactor

* add more tree view abstracts

* add tree trait to pages

* get ready for tree view pages

* improve page edit

* fix revision

* fix slug

* add router params delegate

* fix params delegate

* fix

* fix

* fix all

* fix all

* fix tree

* fix page link

* fix feed

* fix page

* fix permalink

* fix permalink input

* fix offset query

* Fix typo

* remove proxy methods

* remove unnecessary useage

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: joyqi <magike.net@gmail.com>

* Fix Prevent XSS vulnerability in default theme (#1654)

* Fix Prevent XSS vulnerability in default theme

* Update var/Typecho/Db/Adapter/Pdo.php

* fix the getter

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>

* add throwCallback to widget response

* fix: cut down fields when selecting recent posts

* fix typo errors

* fix typo errors

* fix http client cookie

* add throw finish

* fix theme lang

* fix default theme

* fix query

* add open graph and twitter card support
add canonical link

* fix canonical link meta

* fix theme classic-22

* remove unnecessary scss file when packaging

* init plugin signal

* improve: remove feather-icon js file

* fix: typo

* improve: post detail layout

* fix tags saving

* improve: nav search

* fix: theme screenshot

* fix: theme page layout

* remove php 7.2/7.3 env

---------

Co-authored-by: fen <f3nb0x@gmail.com>
Co-authored-by: Lu Fei <52o@qq52o.cn>
2023-12-30 23:02:25 +08:00

302 lines
8.5 KiB
PHP

<?php
namespace Widget;
use Typecho\Common;
use Typecho\Http\Client;
use Typecho\Response;
use Typecho\Widget\Exception;
use Widget\Base\Contents;
use Widget\Base\Options as BaseOptions;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 通用异步服务组件
*
* @author qining
* @category typecho
* @package Widget
*/
class Service extends BaseOptions implements ActionInterface
{
/**
* 异步请求
*
* @var array
*/
public array $asyncRequests = [];
/**
* 发送pingback实现
*
* @throws Exception|Client\Exception
*/
public function sendPingHandle()
{
/** 验证权限 */
$data = $this->request->get('@json');
$token = $data['token'] ?? '';
$permalink = $data['permalink'];
$title = $data['title'];
$excerpt = $data['excerpt'];
$response = ['trackback' => [], 'pingback' => []];
if (!Common::timeTokenValidate($token, $this->options->secret, 3) || empty($permalink)) {
throw new Exception(_t('禁止访问'), 403);
}
$this->response->throwFinish();
/** 忽略超时 */
if (function_exists('ignore_user_abort')) {
ignore_user_abort(true);
}
if (function_exists('set_time_limit')) {
set_time_limit(30);
}
if (!empty($data['pingback'])) {
$links = $data['pingback'];
$permalinkPart = parse_url($permalink);
/** 发送pingback */
foreach ($links as $url) {
$urlPart = parse_url($url);
if (isset($urlPart['scheme'])) {
if ('http' != $urlPart['scheme'] && 'https' != $urlPart['scheme']) {
continue;
}
} else {
$urlPart['scheme'] = 'http';
$url = Common::buildUrl($urlPart);
}
if ($permalinkPart['host'] == $urlPart['host'] && $permalinkPart['path'] == $urlPart['path']) {
continue;
}
$spider = Client::get();
if ($spider) {
$spider->setTimeout(10)
->send($url);
if (!($xmlrpcUrl = $spider->getResponseHeader('x-pingback'))) {
if (
preg_match(
"/<link[^>]*rel=[\"']pingback[\"'][^>]*href=[\"']([^\"']+)[\"'][^>]*>/i",
$spider->getResponseBody(),
$out
)
) {
$xmlrpcUrl = $out[1];
}
}
if (!empty($xmlrpcUrl)) {
$response['pingback'][] = $url;
try {
$xmlrpc = new \IXR\Client($xmlrpcUrl);
$xmlrpc->pingback->ping($permalink, $url);
unset($xmlrpc);
} catch (\IXR\Exception $e) {
continue;
}
}
}
unset($spider);
}
}
/** 发送trackback */
if (!empty($data['trackback'])) {
$links = $data['trackback'];
foreach ($links as $url) {
$client = Client::get();
$response['trackback'][] = $url;
if ($client) {
try {
$client->setTimeout(5)
->setData([
'blog_name' => $this->options->title . ' &raquo ' . $title,
'url' => $permalink,
'excerpt' => $excerpt
])
->send($url);
unset($client);
} catch (Client\Exception $e) {
continue;
}
}
}
}
$this->response->throwJson($response);
}
/**
* 发送pingback
* <code>
* $this->sendPing($post);
* </code>
*
* @param Contents $content 内容url
* @param array|null $trackback
*/
public function sendPing(Contents $content, ?array $trackback = null)
{
$this->user->pass('contributor');
if ($client = Client::get()) {
try {
$input = [
'do' => 'ping',
'permalink' => $content->permalink,
'excerpt' => $content->excerpt,
'title' => $content->title,
'token' => Common::timeToken($this->options->secret)
];
if (preg_match_all("|<a[^>]*href=[\"'](.*?)[\"'][^>]*>(.*?)</a>|", $content->content, $matches)) {
$pingback = array_unique($matches[1]);
if (!empty($pingback)) {
$input['pingback'] = $pingback;
}
}
if (!empty($trackback)) {
$input['trackback'] = $trackback;
}
$client->setHeader('User-Agent', $this->options->generator)
->setTimeout(2)
->setJson($input)
->send($this->getServiceUrl('ping'));
} catch (Client\Exception $e) {
return;
}
}
}
/**
* 获取真实的 URL
*
* @param string $do 动作名
* @return string
*/
private function getServiceUrl(string $do): string
{
$url = Common::url('/action/service', $this->options->index);
if (defined('__TYPECHO_SERVICE_URL__')) {
$rootPath = rtrim(parse_url($this->options->rootUrl, PHP_URL_PATH), '/');
$path = parse_url($url, PHP_URL_PATH);
$parts = parse_url(__TYPECHO_SERVICE_URL__);
if (
!empty($parts['path'])
&& $parts['path'] != '/'
&& rtrim($parts['path'], '/') != $rootPath
) {
$path = Common::url($path, $parts['path']);
}
$parts['path'] = $path;
$url = Common::buildUrl($parts);
}
return $url . '?do=' . $do;
}
/**
* 请求异步服务
*
* @param $method
* @param mixed $params
*/
public function requestService($method, ...$params)
{
static $called;
if (!$called) {
Response::getInstance()->addResponder(function () {
if (!empty($this->asyncRequests) && $client = Client::get()) {
try {
$client->setHeader('User-Agent', $this->options->generator)
->setTimeout(2)
->setJson([
'requests' => $this->asyncRequests,
'token' => Common::timeToken($this->options->secret)
])
->send($this->getServiceUrl('async'));
} catch (Client\Exception $e) {
return;
}
}
});
$called = true;
}
$this->asyncRequests[] = [$method, $params];
}
/**
* 执行回调
*
* @throws Exception
*/
public function asyncHandle()
{
/** 验证权限 */
$data = $this->request->get('@json');
$token = $data['token'] ?? '';
if (!Common::timeTokenValidate($token, $this->options->secret, 3)) {
throw new Exception(_t('禁止访问'), 403);
}
$this->response->throwFinish();
/** 忽略超时 */
if (function_exists('ignore_user_abort')) {
ignore_user_abort(true);
}
if (function_exists('set_time_limit')) {
set_time_limit(30);
}
$requests = $data['requests'] ?? null;
$plugin = self::pluginHandle();
if (!empty($requests)) {
foreach ($requests as $request) {
[$method, $params] = $request;
$plugin->call($method, ... $params);
}
}
}
/**
* 异步请求入口
*/
public function action()
{
$this->on($this->request->isPost() && $this->request->is('do=ping'))->sendPingHandle();
$this->on($this->request->isPost() && $this->request->is('do=async'))->asyncHandle();
}
}