* 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>
205 lines
6.0 KiB
PHP
205 lines
6.0 KiB
PHP
<?php
|
||
|
||
namespace Utils;
|
||
|
||
/**
|
||
* AutoP
|
||
*
|
||
* @copyright Copyright (c) 2012 Typecho Team. (http://typecho.org)
|
||
* @author Joyqi <magike.net@gmail.com>
|
||
* @license GNU General Public License 2.0
|
||
*/
|
||
class AutoP
|
||
{
|
||
// 作为段落的标签
|
||
private const BLOCK = 'p|pre|div|blockquote|form|ul|ol|dd|table|ins|h1|h2|h3|h4|h5|h6';
|
||
|
||
/**
|
||
* 唯一id
|
||
*
|
||
* @access private
|
||
* @var integer
|
||
*/
|
||
private int $uniqueId = 0;
|
||
|
||
/**
|
||
* 存储的段落
|
||
*
|
||
* @access private
|
||
* @var array
|
||
*/
|
||
private array $blocks = [];
|
||
|
||
/**
|
||
* 替换段落的回调函数
|
||
*
|
||
* @param array $matches 匹配值
|
||
* @return string
|
||
*/
|
||
public function replaceBlockCallback(array $matches): string
|
||
{
|
||
$tagMatch = '|' . $matches[1] . '|';
|
||
$text = $matches[4];
|
||
|
||
switch (true) {
|
||
/** 用br处理换行 */
|
||
case false !== strpos(
|
||
'|li|dd|dt|td|p|a|span|cite|strong|sup|sub|small|del|u|i|b|ins|h1|h2|h3|h4|h5|h6|',
|
||
$tagMatch
|
||
):
|
||
$text = nl2br(trim($text));
|
||
break;
|
||
/** 用段落处理换行 */
|
||
case false !== strpos('|div|blockquote|form|', $tagMatch):
|
||
$text = $this->cutByBlock($text);
|
||
if (false !== strpos($text, '</p><p>')) {
|
||
$text = $this->fixParagraph($text);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/** 没有段落能力的标签 */
|
||
if (false !== strpos('|a|span|font|code|cite|strong|sup|sub|small|del|u|i|b|', $tagMatch)) {
|
||
$key = '<b' . $matches[2] . '/>';
|
||
} else {
|
||
$key = '<p' . $matches[2] . '/>';
|
||
}
|
||
|
||
$this->blocks[$key] = "<{$matches[1]}{$matches[3]}>{$text}</{$matches[1]}>";
|
||
return $key;
|
||
}
|
||
|
||
/**
|
||
* 用段落方法处理换行
|
||
*
|
||
* @param string $text
|
||
* @return string
|
||
*/
|
||
private function cutByBlock(string $text): string
|
||
{
|
||
$space = "( | )";
|
||
$text = str_replace("\r\n", "\n", trim($text));
|
||
$text = preg_replace("/{$space}*\n{$space}*/is", "\n", $text);
|
||
$text = preg_replace("/\s*<p:([0-9]{4})\/>\s*/is", "</p><p:\\1/><p>", $text);
|
||
$text = preg_replace("/\n{2,}/", "</p><p>", $text);
|
||
$text = nl2br($text);
|
||
$text = preg_replace("/(<p>)?\s*<p:([0-9]{4})\/>\s*(<\/p>)?/is", "<p:\\2/>", $text);
|
||
$text = preg_replace("/<p>{$space}*<\/p>/is", '', $text);
|
||
$text = preg_replace("/\s*<p>\s*$/is", '', $text);
|
||
$text = preg_replace("/^\s*<\/p>\s*/is", '', $text);
|
||
return $text;
|
||
}
|
||
|
||
/**
|
||
* 修复段落开头和结尾
|
||
*
|
||
* @param string $text
|
||
* @return string
|
||
*/
|
||
private function fixParagraph(string $text): string
|
||
{
|
||
$text = trim($text);
|
||
if (!preg_match("/^<(" . self::BLOCK . ")(\s|>)/i", $text)) {
|
||
$text = '<p>' . $text;
|
||
}
|
||
|
||
if (!preg_match("/<\/(" . self::BLOCK . ")>$/i", $text)) {
|
||
$text = $text . '</p>';
|
||
}
|
||
|
||
return $text;
|
||
}
|
||
|
||
/**
|
||
* 自动分段
|
||
*
|
||
* @param string $text
|
||
* @return string
|
||
*/
|
||
public function parse(string $text): string
|
||
{
|
||
/** 重置计数器 */
|
||
$this->uniqueId = 0;
|
||
$this->blocks = [];
|
||
|
||
/** 将已有的段落后面的换行处理掉 */
|
||
$text = preg_replace(["/<\/p>\s+<p(\s*)/is", "/\s*<br\s*\/?>\s*/is"], ["</p><p\\1", "<br />"], trim($text));
|
||
|
||
/** 将所有非自闭合标签解析为唯一的字符串 */
|
||
$foundTagCount = 0;
|
||
$textLength = strlen($text);
|
||
$uniqueIdList = [];
|
||
|
||
if (preg_match_all("/<\/\s*([a-z0-9]+)>/is", $text, $matches, PREG_OFFSET_CAPTURE)) {
|
||
foreach ($matches[0] as $key => $match) {
|
||
$tag = $matches[1][$key][0];
|
||
|
||
$leftOffset = $match[1] - $textLength;
|
||
$posSingle = strrpos($text, '<' . $tag . '>', $leftOffset);
|
||
$posFix = strrpos($text, '<' . $tag . ' ', $leftOffset);
|
||
$pos = false;
|
||
|
||
switch (true) {
|
||
case (false !== $posSingle && false !== $posFix):
|
||
$pos = max($posSingle, $posFix);
|
||
break;
|
||
case false === $posSingle && false !== $posFix:
|
||
$pos = $posFix;
|
||
break;
|
||
case false !== $posSingle && false === $posFix:
|
||
$pos = $posSingle;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (false !== $pos) {
|
||
$uniqueId = $this->makeUniqueId();
|
||
$uniqueIdList[$uniqueId] = $tag;
|
||
$tagLength = strlen($tag);
|
||
|
||
$text = substr_replace($text, $uniqueId, $pos + 1 + $tagLength, 0);
|
||
$text = substr_replace(
|
||
$text,
|
||
$uniqueId,
|
||
$match[1] + 7 + $foundTagCount * 10 + $tagLength,
|
||
0
|
||
); // 7 = 5 + 2
|
||
$foundTagCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach ($uniqueIdList as $uniqueId => $tag) {
|
||
$text = preg_replace_callback(
|
||
"/<({$tag})({$uniqueId})([^>]*)>(.*)<\/\\1\\2>/is",
|
||
[$this, 'replaceBlockCallback'],
|
||
$text,
|
||
1
|
||
);
|
||
}
|
||
|
||
$text = $this->cutByBlock($text);
|
||
$blocks = array_reverse($this->blocks);
|
||
|
||
foreach ($blocks as $blockKey => $blockValue) {
|
||
$text = str_replace($blockKey, $blockValue, $text);
|
||
}
|
||
|
||
return $this->fixParagraph($text);
|
||
}
|
||
|
||
/**
|
||
* 生成唯一的id, 为了速度考虑最多支持1万个tag的处理
|
||
*
|
||
* @return string
|
||
*/
|
||
private function makeUniqueId(): string
|
||
{
|
||
return ':' . str_pad($this->uniqueId ++, 4, '0', STR_PAD_LEFT);
|
||
}
|
||
}
|
||
|