S2OJ/web/app/models/UOJBlogEditor.php
Baoshuo a97a05553b
All checks were successful
continuous-integration/drone/push Build is passing
chore: remove table-striped class
2022-10-08 08:50:23 +08:00

274 lines
7.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
class UOJBlogEditor {
public $type = 'blog';
public $name;
public $blog_url;
public $save;
public $cur_data = array();
public $post_data = array();
public $show_editor = true;
public $show_tags = true;
public $label_text = array(
'title' => '标题',
'tags' => '标签(多个标签用逗号隔开)',
'content' => '内容',
'view blog' => '查看博客',
'blog visibility' => '博客可见性',
'private' => '未公开',
'public' => '公开'
);
public $validator = array();
function __construct() {
global $REQUIRE_LIB;
$REQUIRE_LIB['blog-editor'] = '';
$this->validator = array(
'title' => function(&$title) {
if ($title == '') {
return '标题不能为空';
}
if (strlen($title) > 100) {
return '标题不能超过 100 个字节';
}
if (HTML::escape($title) === '') {
return '无效编码';
}
return '';
},
'content_md' => function(&$content_md) {
if (strlen($content_md) > 1000000) {
return '内容过长';
}
return '';
},
'tags' => function(&$tags) {
$tags = str_replace('', ',', $tags);
$tags_raw = explode(',', $tags);
if (count($tags_raw) > 10) {
return '标签个数不能超过10';
}
$tags = array();
foreach ($tags_raw as $tag) {
$tag = trim($tag);
if (strlen($tag) == 0) {
continue;
}
if (strlen($tag) > 30) {
return '标签 “' . HTML::escape($tag) .'” 太长';
}
if (in_array($tag, $tags, true)) {
return '标签 “' . HTML::escape($tag) .'” 重复出现';
}
$tags[] = $tag;
}
return '';
}
);
}
public function validate($name) {
if (!isset($_POST["{$this->name}_{$name}"])) {
return '不能为空';
}
$this->post_data[$name] = $_POST["{$this->name}_{$name}"];
$val = $this->validator[$name];
return $val($this->post_data[$name]);
}
private function receivePostData() {
$errors = array();
$keys = array('title');
if ($this->show_tags) {
$keys[] = 'tags';
}
if ($this->show_editor) {
$keys[] = 'content_md';
}
foreach ($keys as $name) {
$cur_err = $this->validate($name);
if ($cur_err) {
$errors[$name] = $cur_err;
}
}
if ($errors) {
die(json_encode($errors));
}
crsf_defend();
$this->post_data['is_hidden'] = isset($_POST["{$this->name}_is_hidden"]) ? 1 : 0;
$purifier = HTML::pruifier();
$this->post_data['title'] = HTML::escape($this->post_data['title']);
if ($this->show_editor) {
if ($this->type == 'blog') {
$content_md = $_POST[$this->name . '_content_md'];
try {
$v8 = new V8Js('POST');
$v8->content_md = $this->post_data['content_md'];
$v8->executeString(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/js/marked.js'), 'marked.js');
$this->post_data['content'] = $v8->executeString('marked(POST.content_md)');
} catch (V8JsException $e) {
die(json_encode(array('content_md' => '未知错误')));
}
if (preg_match('/^.*<!--.*readmore.*-->.*$/m', $this->post_data['content'], $matches, PREG_OFFSET_CAPTURE)) {
$content_less = substr($this->post_data['content'], 0, $matches[0][1]);
$content_more = substr($this->post_data['content'], $matches[0][1] + strlen($matches[0][0]));
$this->post_data['content'] = $purifier->purify($content_less).'<!-- readmore -->'.$purifier->purify($content_more);
} else {
$this->post_data['content'] = $purifier->purify($this->post_data['content']);
}
} elseif ($this->type == 'slide') {
$content_array = yaml_parse($this->post_data['content_md']);
if ($content_array === false || !is_array($content_array)) {
die(json_encode(array('content_md' => '不合法的 YAML 格式')));
}
try {
$v8 = new V8Js('PHP');
$v8->executeString(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/js/marked.js'), 'marked.js');
$v8->executeString(<<<EOD
marked.setOptions({
getLangClass: function(lang) {
lang = lang.toLowerCase();
switch (lang) {
case 'c': return 'c';
case 'c++': return 'cpp';
case 'pascal': return 'pascal';
default: return lang;
}
},
getElementClass: function(tok) {
switch (tok.type) {
case 'list_item_start':
return 'fragment';
case 'loose_item_start':
return 'fragment';
default:
return null;
}
}
})
EOD
);
} catch (V8JsException $e) {
die(json_encode(array('content_md' => '未知错误')));
}
$marked = function($md) use ($v8, $purifier) {
try {
$v8->md = $md;
return $purifier->purify($v8->executeString('marked(PHP.md)'));
} catch (V8JsException $e) {
die(json_encode(array('content_md' => '未知错误')));
}
};
$config = array();
$this->post_data['content'] = '';
foreach ($content_array as $slide_name => $slide_content) {
if (is_array($slide_content) && is_array($slide_content['config'])) {
foreach (array('theme') as $config_key) {
if (is_string($slide_content['config'][$config_key]) && strlen($slide_content['config'][$config_key]) <= 30) {
$config[$config_key] = $slide_content['config'][$config_key];
}
}
continue;
}
$this->post_data['content'] .= '<section>';
if (is_string($slide_content)) {
$this->post_data['content'] .= $marked($slide_content);
} elseif (is_array($slide_content)) {
if (is_array($slide_content['children'])) {
foreach ($slide_content['children'] as $cslide_name => $cslide_content) {
$this->post_data['content'] .= '<section>';
$this->post_data['content'] .= $marked($cslide_content);
$this->post_data['content'] .= '</section>';
}
}
}
$this->post_data['content'] .= "</section>\n";
}
$this->post_data['content'] = json_encode($config) . "\n" . $this->post_data['content'];
}
}
}
public function handleSave() {
global $REQUIRE_LIB;
$save = $this->save;
$this->receivePostData();
$ret = $save($this->post_data);
if (!$ret) {
$ret = array();
}
if (isset($_POST['need_preview'])) {
ob_start();
if ($this->type == 'blog') {
$req_lib = array('mathjax' => '');
if (isset($REQUIRE_LIB['bootstrap5'])) {
$req_lib['bootstrap5'] = '';
$req_lib['hljs'] = '';
} else {
$req_lib['shjs'] = '';
}
echoUOJPageHeader('博客预览', array('ShowPageHeader' => false, 'REQUIRE_LIB' => $req_lib));
if (!isset($REQUIRE_LIB['bootstrap5'])) {
echo '<link rel="stylesheet" type="text/css" href="' . HTML::url('/css/markdown.css') . '">';
}
echo '<article class="markdown-body">';
echo $this->post_data['content'];
echo '</article>';
if (isset($REQUIRE_LIB['bootstrap5'])) {
echo <<<EOD
<script>
$(document).ready(function() {
$('.markdown-body table').each(function() {
$(this).addClass('table table-bordered');
});
});
</script>
EOD;
}
echoUOJPageFooter(array('ShowPageFooter' => false));
} elseif ($this->type == 'slide') {
uojIncludeView('slide', array_merge(
UOJContext::pageConfig(),
array(
'PageTitle' => '幻灯片预览',
'content' => $this->post_data['content']
)
));
}
$ret['html'] = ob_get_contents();
ob_end_clean();
}
die(json_encode($ret));
}
public function runAtServer() {
if (isset($_POST["save-{$this->name}"])) {
$this->handleSave();
}
}
public function printHTML() {
global $REQUIRE_LIB;
uojIncludeView('blog-editor', array('editor' => $this, 'REQUIRE_LIB' => $REQUIRE_LIB));
}
}