<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit;
* Theme:OneBlog
* Updated: 2026-05-02
* Author: ©彼岸临窗 onenote.io
* 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。
* 本主题已取得软件著作权(登记号:2025SR0334142)和外观设计专利(专利号:第7121519号),请严格遵循GPL-2.0协议使用本主题及源码。
*/
function parseThemeVersion() {
$indexFile = __DIR__ . '/index.php';
$content = file_get_contents($indexFile);
preg_match('/\* @version\s+([0-9.]+)/', $content, $matches);
return $matches[1] ?? '1.0.0';
}
function oneblogFonts() {
return [
'default' => [
'name' => 'Default',
'css' => '',
'family' => ''
],
'hwmct' => [
'name' => '汇文明朝体',
'css' => 'https://cn-font.claude-code-best.win/packages/hwmct/dist/%E6%B1%87%E6%96%87%E6%98%8E%E6%9C%9D%E4%BD%93/result.css',
'family' => 'Huiwen-mincho'
],
'syst' => [
'name' => '思源宋体',
'css' => 'https://cn-font.claude-code-best.win/packages/syst/dist/SourceHanSerifCN/result.css',
'family' => 'Source Han Serif CN VF'
],
'stdgt' => [
'name' => '上图东观体',
'css' => 'https://cn-font.claude-code-best.win/packages/stdgt/dist/上图东观体-常规/result.css',
'family' => 'STDongGuanTi'
],
'xwwk' => [
'name' => '霞鹜文楷',
'css' => 'https://cn-font.claude-code-best.win/packages/lxgwwenkaibright/dist/LXGWBright-Medium/result.css',
'family' => 'LXGW Bright Medium'
],
'xwxzs' => [
'name' => '霞鹜新致宋',
'css' => 'https://cn-font.claude-code-best.win/packages/LxgwNeoZhiSong/dist/LXGWNeoZhiSong/result.css',
'family' => 'LXGW Neo ZhiSong'
],
'yxzk' => [
'name' => '原俠正楷',
'css' => 'https://cn-font.claude-code-best.win/packages/GuanKiapTsingKhai/dist/GuanKiapTsingKhai/result.css',
'family' => 'GuanKiapTsingKhai'
],
'yxk' => [
'name' => '月星楷',
'css' => 'https://cn-font.claude-code-best.win/packages/moon-stars-kai/dist/MoonStarsKai-Regular/result.css',
'family' => 'Moon Stars Kai'
],
'yjyhpws' => [
'name' => '极影毁片文宋',
'css' => 'https://cn-font.claude-code-best.win/packages/jyhpws/dist/极影毁片文宋/result.css',
'family' => '极影毁片文宋 Medium'
]
];
}
function oneblogFontSet() {
$fonts = oneblogFonts();
$key = Helper::options()->FontFamily ?: 'default';
return $fonts[$key] ?? $fonts['default'];
}
function oneblogSitemapBuild() {
$options = Helper::options();
$db = Typecho_Db::get();
$siteUrl = rtrim($options->siteUrl, '/');
$output = rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.xml';
$seen = [];
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->formatOutput = true;
$xml->appendChild($xml->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="' . oneblogSitemapStylesheetUrl($siteUrl) . '"'));
$urlset = $xml->createElement('urlset');
$urlset->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
$urlset->setAttribute('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1');
$xml->appendChild($urlset);
$homeImage = oneblogSitemapHomeImage($siteUrl);
oneblogSitemapAddUrl($xml, $urlset, $seen, $siteUrl . '/', time(), 'daily', '1.0', $homeImage ? [$homeImage] : []);
$posts = $db->fetchAll(
$db->select('cid', 'slug', 'created', 'modified', 'text')
->from('table.contents')
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('(password IS NULL OR password = ?) ', '')
->order('created', Typecho_Db::SORT_DESC)
->limit(10000)
);
$postCids = array_column($posts, 'cid');
$thumbs = oneblogSitemapThumbs($postCids);
$postCategories = oneblogSitemapPostCategories($postCids);
foreach ($posts as $row) {
$row = oneblogSitemapPostRouteRow($row, $postCategories[(int) $row['cid']] ?? null);
$postPermalink = Typecho_Router::url('post', $row, $options->index);
$postPermalink = oneblogSitemapUrl($postPermalink, $siteUrl);
$image = oneblogSitemapUrl((string) showThumbnail(oneblogSitemapWidget($row, $thumbs[$row['cid']] ?? ''), false), $siteUrl);
oneblogSitemapAddUrl($xml, $urlset, $seen, $postPermalink, oneblogSitemapLastmod($row), 'weekly', '0.8', $image ? [$image] : []);
}
$pages = $db->fetchAll(
$db->select('cid', 'slug', 'created', 'modified', 'text')
->from('table.contents')
->where('type = ?', 'page')
->where('status = ?', 'publish')
->where('(password IS NULL OR password = ?) ', '')
->order('created', Typecho_Db::SORT_DESC)
);
$thumbs = oneblogSitemapThumbs(array_column($pages, 'cid'));
foreach ($pages as $row) {
$pagePermalink = Typecho_Router::url('page', $row, $options->index);
$pagePermalink = oneblogSitemapUrl($pagePermalink, $siteUrl);
$image = oneblogSitemapUrl((string) showThumbnail(oneblogSitemapWidget($row, $thumbs[$row['cid']] ?? ''), false), $siteUrl);
oneblogSitemapAddUrl($xml, $urlset, $seen, $pagePermalink, oneblogSitemapLastmod($row), 'monthly', '0.7', $image ? [$image] : []);
}
foreach (oneblogSitemapMetas('category') as $row) {
$categoryPermalink = Typecho_Router::url('category', $row, $options->index);
oneblogSitemapAddUrl($xml, $urlset, $seen, oneblogSitemapUrl($categoryPermalink, $siteUrl), oneblogSitemapLastmod($row), 'weekly', '0.6');
}
foreach (oneblogSitemapMetas('tag') as $row) {
$tagPermalink = Typecho_Router::url('tag', $row, $options->index);
oneblogSitemapAddUrl($xml, $urlset, $seen, oneblogSitemapUrl($tagPermalink, $siteUrl), oneblogSitemapLastmod($row), 'weekly', '0.5');
}
$saved = $xml->save($output) !== false;
if ($saved) {
@file_put_contents(oneblogSitemapMetaPath(), oneblogSitemapSign());
@touch(oneblogSitemapCheckPath());
}
return $saved;
}
function oneblogSitemapAddUrl($xml, $urlset, &$seen, $loc, $lastmod, $changefreq, $priority, $images = []) {
$loc = oneblogSitemapCleanUrl($loc);
if ($loc === '' || isset($seen[$loc])) {
return;
}
$seen[$loc] = true;
$url = $xml->createElement('url');
$locNode = $xml->createElement('loc');
$locNode->appendChild($xml->createTextNode($loc));
$url->appendChild($locNode);
$url->appendChild($xml->createElement('lastmod', oneblogSitemapFormatLastmod($lastmod)));
$url->appendChild($xml->createElement('changefreq', $changefreq));
$url->appendChild($xml->createElement('priority', $priority));
foreach (array_unique(array_filter($images)) as $image) {
$image = oneblogSitemapCleanUrl($image);
if ($image === '') continue;
$imageNode = $xml->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:image');
$imageLoc = $xml->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:loc');
$imageLoc->appendChild($xml->createTextNode($image));
$imageNode->appendChild($imageLoc);
$url->appendChild($imageNode);
}
$urlset->appendChild($url);
}
function oneblogSitemapUrl($url, $siteUrl) {
$url = trim((string) $url);
if ($url === '') return '';
if (strpos($url, '//') === 0) {
return (parse_url($siteUrl, PHP_URL_SCHEME) ?: 'https') . ':' . $url;
}
if (preg_match('#^https?://#i', $url)) {
return $url;
}
return rtrim($siteUrl, '/') . '/' . ltrim($url, '/');
}
function oneblogSitemapStylesheetUrl($siteUrl) {
$root = rtrim(str_replace('\\', '/', __TYPECHO_ROOT_DIR__), '/');
$themeDir = str_replace('\\', '/', __DIR__);
$relative = 'usr/themes/OneBlog/static/sitemap.xsl';
if ($root !== '' && strpos($themeDir, $root . '/') === 0) {
$relative = ltrim(substr($themeDir, strlen($root)), '/');
$relative = rtrim($relative, '/') . '/static/sitemap.xsl';
}
return oneblogSitemapUrl($relative, $siteUrl);
}
function oneblogSitemapHomeImage($siteUrl) {
$options = Helper::options();
$image = $options->Webthumb ?: rtrim($options->themeUrl, '/') . '/static/img/logo.png';
return oneblogSitemapUrl($image, $siteUrl);
}
function oneblogSitemapPostRouteRow($row, $category) {
if (!empty($category['directory'])) {
$row['category'] = $category['directory'];
} elseif (!empty($category['slug'])) {
$row['category'] = $category['slug'];
} elseif (!empty($category['mid'])) {
$row['category'] = (string) $category['mid'];
}
if (!empty($category['slug'])) {
$row['categorySlug'] = $category['slug'];
}
return $row;
}
function oneblogSitemapCleanUrl($url) {
$url = trim((string) $url);
if ($url === '') return '';
$url = str_replace('&', '&', $url);
return preg_match('#^https?://#i', $url) ? $url : '';
}
function oneblogSitemapLastmod($row) {
$modified = isset($row['modified']) ? (int) $row['modified'] : 0;
$created = isset($row['created']) ? (int) $row['created'] : 0;
return $modified > 0 ? $modified : $created;
}
function oneblogSitemapFormatLastmod($time) {
$time = (int) $time;
return date('c', $time > 0 ? $time : time());
}
function oneblogSitemapWidget($row, $thumb = '') {
return (object) [
'content' => $row['text'] ?? '',
'fields' => (object) ['thumb' => $thumb]
];
}
function oneblogSitemapThumbs($cids) {
$cids = array_values(array_unique(array_filter(array_map('intval', (array) $cids))));
if (empty($cids)) return [];
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$rows = $db->fetchAll($db->query(
'SELECT cid, str_value FROM `' . $prefix . 'fields` WHERE name = ' . oneblogSitemapQuote('thumb') . ' AND cid IN (' . implode(',', $cids) . ')'
));
$siteUrl = rtrim(Helper::options()->siteUrl, '/');
$thumbs = [];
foreach ($rows as $row) {
if (empty($row['str_value'])) continue;
$thumbs[(int) $row['cid']] = oneblogSitemapUrl($row['str_value'], $siteUrl);
}
return $thumbs;
}
function oneblogSitemapPostCategories($cids) {
$cids = array_values(array_unique(array_filter(array_map('intval', (array) $cids))));
if (empty($cids)) return [];
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$rows = $db->fetchAll($db->query(
'SELECT r.cid, m.mid, m.name, m.slug, m.parent, m.order '
. 'FROM `' . $prefix . 'relationships` r '
. 'INNER JOIN `' . $prefix . 'metas` m ON r.mid = m.mid '
. 'WHERE m.type = ' . oneblogSitemapQuote('category') . ' '
. 'AND r.cid IN (' . implode(',', $cids) . ') '
. 'ORDER BY r.cid ASC, m.order ASC, m.mid ASC'
));
if (empty($rows)) return [];
$metas = oneblogSitemapCategoryMetas();
$categories = [];
foreach ($rows as $row) {
$cid = (int) $row['cid'];
if (isset($categories[$cid])) continue;
$mid = (int) $row['mid'];
$row['directory'] = oneblogSitemapCategoryDirectory($mid, $metas);
$categories[$cid] = $row;
}
return $categories;
}
function oneblogSitemapCategoryMetas() {
static $metas = null;
if ($metas !== null) return $metas;
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$rows = $db->fetchAll($db->query(
'SELECT mid, slug, parent FROM `' . $prefix . 'metas` WHERE type = ' . oneblogSitemapQuote('category')
));
$metas = [];
foreach ($rows as $row) {
$metas[(int) $row['mid']] = [
'slug' => trim((string) ($row['slug'] ?? '')),
'parent' => (int) ($row['parent'] ?? 0)
];
}
return $metas;
}
function oneblogSitemapCategoryDirectory($mid, $metas) {
$parts = [];
$visited = [];
while ($mid > 0 && isset($metas[$mid]) && !isset($visited[$mid]) && count($parts) < 20) {
$visited[$mid] = true;
$slug = trim((string) $metas[$mid]['slug']);
if ($slug !== '') {
array_unshift($parts, $slug);
}
$mid = (int) $metas[$mid]['parent'];
}
return implode('/', $parts);
}
function oneblogSitemapMetas($type) {
$type = $type === 'tag' ? 'tag' : 'category';
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
return $db->fetchAll($db->query(
'SELECT m.mid, m.name, m.slug, m.type, MAX(c.modified) AS modified, MAX(c.created) AS created, COUNT(c.cid) AS post_count '
. 'FROM `' . $prefix . 'metas` m '
. 'INNER JOIN `' . $prefix . 'relationships` r ON m.mid = r.mid '
. 'INNER JOIN `' . $prefix . 'contents` c ON r.cid = c.cid '
. 'WHERE m.type = ' . oneblogSitemapQuote($type) . ' '
. 'AND c.type = ' . oneblogSitemapQuote('post') . ' '
. 'AND c.status = ' . oneblogSitemapQuote('publish') . ' '
. 'AND (c.password IS NULL OR c.password = ' . oneblogSitemapQuote('') . ') '
. 'GROUP BY m.mid, m.name, m.slug, m.type '
. 'ORDER BY modified DESC, created DESC'
));
}
function oneblogSitemapMetaPath() {
return rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.meta';
}
function oneblogSitemapCheckPath() {
return rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.check';
}
function oneblogSitemapSign() {
static $sign = null;
if ($sign !== null) return $sign;
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$contents = $db->fetchRow($db->query(
'SELECT COUNT(*) AS total, '
. 'MAX(CASE WHEN modified > 0 THEN modified ELSE created END) AS latest, '
. 'GROUP_CONCAT(cid ORDER BY cid) AS cids '
. 'FROM `' . $prefix . 'contents` '
. 'WHERE (type = ' . oneblogSitemapQuote('post') . ' OR type = ' . oneblogSitemapQuote('page') . ') '
. 'AND status = ' . oneblogSitemapQuote('publish') . ' '
. 'AND (password IS NULL OR password = ' . oneblogSitemapQuote('') . ')'
));
$metas = $db->fetchRow($db->query(
'SELECT COUNT(DISTINCT m.mid) AS total, '
. "GROUP_CONCAT(DISTINCT CONCAT(m.mid, ':', m.type, ':', m.slug, ':', COALESCE(m.parent, 0)) ORDER BY m.mid SEPARATOR ',') AS metas "
. 'FROM `' . $prefix . 'metas` m '
. 'INNER JOIN `' . $prefix . 'relationships` r ON m.mid = r.mid '
. 'INNER JOIN `' . $prefix . 'contents` c ON r.cid = c.cid '
. 'WHERE (m.type = ' . oneblogSitemapQuote('category') . ' OR m.type = ' . oneblogSitemapQuote('tag') . ') '
. 'AND c.type = ' . oneblogSitemapQuote('post') . ' '
. 'AND c.status = ' . oneblogSitemapQuote('publish') . ' '
. 'AND (c.password IS NULL OR c.password = ' . oneblogSitemapQuote('') . ')'
));
$homeImage = oneblogSitemapHomeImage(rtrim(Helper::options()->siteUrl, '/'));
return $sign = md5(json_encode(['sitemap_v3', $contents, $metas, $homeImage]));
}
function oneblogSitemapQuote($value) {
return "'" . str_replace("'", "''", (string) $value) . "'";
}
function oneblogSitemapUpdate() {
try {
return oneblogSitemapBuild();
} catch (Throwable $e) {
error_log('OneBlog sitemap update failed: ' . $e->getMessage());
} catch (Exception $e) {
error_log('OneBlog sitemap update failed: ' . $e->getMessage());
}
return false;
}
function oneblogSitemapCheck() {
static $checked = false;
if ($checked) return;
$checked = true;
$checkPath = oneblogSitemapCheckPath();
if (file_exists($checkPath) && time() - (int) filemtime($checkPath) < oneblogSitemapInterval()) {
return;
}
@touch($checkPath);
$sitemapPath = rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.xml';
if (!file_exists($sitemapPath)) {
oneblogSitemapQueue();
return;
}
try {
$metaPath = oneblogSitemapMetaPath();
if (!file_exists($metaPath) || trim((string) @file_get_contents($metaPath)) !== oneblogSitemapSign()) {
oneblogSitemapQueue();
}
} catch (Throwable $e) {
error_log('OneBlog sitemap stale check failed: ' . $e->getMessage());
} catch (Exception $e) {
error_log('OneBlog sitemap stale check failed: ' . $e->getMessage());
}
}
function oneblogSitemapQueue() {
static $registered = false;
$GLOBALS['oneblog_sitemap_needs_update'] = true;
if (!$registered) {
register_shutdown_function('oneblogSitemapShutdown');
$registered = true;
}
}
function oneblogSitemapShutdown() {
if (!empty($GLOBALS['oneblog_sitemap_needs_update']) && !oneblogSitemapUpdate()) {
@unlink(oneblogSitemapCheckPath());
}
}
function oneblogSitemapInterval() {
$interval = (int) (Helper::options()->sitemapInterval ?: 86400);
return $interval > 0 ? $interval : 86400;
}
oneblogSitemapCheck();
function themeConfig($form) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['_ajax'])) {
if (ob_get_length()) ob_clean();
header('Content-Type:application/json; charset=utf-8');
$theTheme = 'OneBlog';
$db = Typecho_Db::get();
$uploadDir = Helper::options()->uploadDir ?: 'usr/uploads';
$uploadDir = rtrim($uploadDir, '/\\');
$absUploadDir = __TYPECHO_ROOT_DIR__ . '/' . $uploadDir;
if (!is_dir($absUploadDir)) {
@mkdir($absUploadDir, 0755, true);
}
$backPath = $absUploadDir . '/BackupSetting_' . $theTheme . '.txt';
$ret = ['success'=>false, 'message'=>'未知错误'];
if ($_POST['action'] === 'oneblog_theme_backup') {
$themeConfStr = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:' . $theTheme))['value'];
$ok = file_put_contents($backPath, $themeConfStr);
$ret = $ok !== false
? ['success'=>true, 'message'=>'备份成功']
: ['success'=>false, 'message'=>'备份失败,uploads 目录不可写'];
} elseif ($_POST['action'] === 'oneblog_theme_restore') {
if (file_exists($backPath)) {
$str = file_get_contents($backPath);
$updateThemeConQuery = $db->update('table.options')->rows(['value'=>$str])->where('name=?', 'theme:' . $theTheme);
$ok = $db->query($updateThemeConQuery);
$ret = $ok !== false
? ['success'=>true, 'message'=>'恢复成功']
: ['success'=>false, 'message'=>'恢复失败,数据库操作异常'];
} else {
$ret = ['success'=>false, 'message'=>'未找到备份文件,无法恢复'];
}
}
echo json_encode($ret);
exit;
}
$theTheme = 'OneBlog';
$db = Typecho_Db::get();
$uploadDir = Helper::options()->uploadDir ?: 'usr/uploads';
$uploadDir = rtrim($uploadDir, '/\\');
$absUploadDir = __TYPECHO_ROOT_DIR__ . '/' . $uploadDir;
if (!is_dir($absUploadDir)) {
@mkdir($absUploadDir, 0755, true);
}
$backPath = $absUploadDir . '/BackupSetting_' . $theTheme . '.txt';
$themeConfStr = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:' . $theTheme))['value'];
$backstr = file_exists($backPath) ? file_get_contents($backPath) : '';?>
<link rel="stylesheet" href="https://cncdn.cc/oneblog/3.7.0/admin.css" type="text/css" />
<script src="https://cncdn.cc/jquery/3.7.1/dist/jquery.min.js" type="text/javascript"></script>
<script src="https://cncdn.cc/layer/3.1.1/layer.js" type="text/javascript"></script>
<script>
window.oneblogFontConfigs = <?php echo json_encode(oneblogFonts(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
</script>
<script src="<?php echo Helper::options()->themeUrl('static/js/admin.js'); ?>" type="text/javascript"></script>
<div class="OneBlog"><h3>OneBlog 主题设置</h3></div>
<div id="tab-container">
<ul id="tab-nav"></ul>
<div id="tab-content">
<div id="tab1" class="tab-pane active">
<h2>OneBlog V<?php echo parseThemeVersion();?></h2>
<p>本主题精心打磨多年,且持续优化,现免费开源,致敬互联网社区开源精神,也致敬热爱生活和记录的我们。</p>
<p>使用教程请前往<b></b>主题文档</b>:<a href="https://docs.onenote.io" target="_blank">docs.onenote.io</a> 获取,主题最新版本请前往Github仓库:<a href="https://github.com/cnxcdn/OneBlog" target="_blank">OneBlog(最新)</a> 或 <a href="https://gitcode.com/cncdn/OneBlog" target="_blank">国内镜像仓库(延迟一天同步)</a>查看,记得★Star,既是对作者的支持,也方便记住来时的路。本主题几乎所有代码都清晰地注释了,因此博友们完全可以以OneBlog为基础二次开发或单独开发属于自己的主题,但希望大家注明来源,保留基本的版权信息。</p>
<p>本主题目前仅有QQ交流群:<b>939170079</b>,其他均不是官方群组,此外,还可以通过<a href="https://litebbs.com" target="_blank">LiteBBS</a>讨论交流。本主题的介绍、后续的更新或周边插件的开发更新,除了QQ群公告,还会同步发布在<a href="https://litebbs.com" target="_blank">LiteBBS</a>,欢迎大家参与讨论。</p>
<div class="backup">
<div class="backup-listen">
<b>主题设置备份与恢复:</b>
<?php if (strcmp($backstr, $themeConfStr) === 0): ?>
当前的配置信息与备份信息一致,无需备份或恢复。
<?php else: ?>
未备份或备份数据与当前设置不一致。<br>
<div class="backupbtn">若以备份数据为准,建议:<button id="restorebtn" type="button">恢复主题配置</button></div>
<div class="backupbtn">若以当前设置为准,建议:<button id="backupbtn" type="button">备份主题配置</button></div>
<?php endif; ?>
</div>
</div>
<p>主题图标库(可直接引用,如 "iconfont icon-home"):</p>
<div class="icon-list" id="iconList"></div>
</div>
</div>
</div>
<?php
//—————————————————————————————————————— 基础设置 ——————————————————————————————————————
//LOGO风格
$logoStyle = new Typecho_Widget_Helper_Form_Element_Radio('logoStyle', array('text' => '文字logo','logo' => '图片logo'),'text', 'LOGO风格', '选择文字风格则logo处显示网站名称,选择图片风格则需要在下方设置logo地址');
$form->addInput($logoStyle);
//网站logo
$logo = new Typecho_Widget_Helper_Form_Element_Text('logo', NULL, NULL, _t('深色版LOGO'), _t('请输入深色logo图片的url,填写后会显示在PC首页和移动端顶栏,建议尺寸:300×83'));
$form->addInput($logo);
//夜间模式下的logo
$logoWhite = new Typecho_Widget_Helper_Form_Element_Text('logoWhite', NULL, NULL, _t('浅色版LOGO'), _t('请输入浅白色logo图片的url,填写后会显示在黑夜模式下的移动端顶栏,建议尺寸:300×83'));
$form->addInput($logoWhite);
//网站slogan
$slogan = new Typecho_Widget_Helper_Form_Element_Text('slogan', NULL, NULL, _t('网站slogan'), _t('一句话介绍网站,填写后会显示在独立页面的顶栏和首页的标题中。'));
$form->addInput($slogan);
//网站favicon
$Favicon = new Typecho_Widget_Helper_Form_Element_Text('Favicon', NULL, NULL, _t('Favicon'), _t('请输入网站favicon图片的url。'));
$form->addInput($Favicon);
//自定义菜单
$MenuSet = new Typecho_Widget_Helper_Form_Element_Textarea('MenuSet',NULL,NULL,_t('自定义菜单'),_t('每行一个菜单项,菜单项的参数用英文逗号隔开。格式:菜单项名称,链接,图标类名<br>示例:<br>首页,/,iconfont icon-home<br>相册,/photos,iconfont icon-pic')
);
$form->addInput($MenuSet);
//首页杂志效果开关
$switch = new Typecho_Widget_Helper_Form_Element_Radio('switch', array('on' => '显示','off' => '不显示'),'on', '首页是否显示Banner文章', '选择开启则需要填写下方的文章cid;PC端会在首页顶部显示杂志效果文章,移动端会在首页顶部显示幻灯片自动切换。');
$form->addInput($switch);
//首页杂志效果文章
$Banner = new Typecho_Widget_Helper_Form_Element_Text('Banner', NULL, NULL, _t('首页banner文章cid'), _t('用英文逗号隔开,限3个,填需要显示在banner区域三篇文章的cid。'));
$form->addInput($Banner);
//移动端标签归档页背景图
$Tagbg = new Typecho_Widget_Helper_Form_Element_Text('Tagbg', NULL, NULL, _t('标签页背景图'), _t('填写后会在移动端标签归档页的顶部背景区域(分类归档页的背景图片直接在分类描述中填写图片链接即可)。'));
$form->addInput($Tagbg);
//网站标识图
$Webthumb = new Typecho_Widget_Helper_Form_Element_Text('Webthumb', NULL, NULL, _t('网站标识图'), _t('请填写图片地址,用于SEO优化,建议尺寸:1280×720'));
$form->addInput($Webthumb);
//建站年份
$Webtime = new Typecho_Widget_Helper_Form_Element_Text('Webtime', NULL, NULL, _t('建站年份'), _t('填写后显示在网站底栏,格式:2016,如果是今年刚建站,请勿填写。'));
$form->addInput($Webtime);
//ICP备案号
$ICP = new Typecho_Widget_Helper_Form_Element_Text('ICP', NULL, NULL, _t('ICP备案号'), _t('如需要显示,请填写网站备案号。'));
$form->addInput($ICP);
//公安备案号
$WA = new Typecho_Widget_Helper_Form_Element_Text('WA', NULL, NULL, _t('公安备案号'), _t('如需要显示,请填写公安备案号,跳转链接请自行在footer.php中修改。'));
$form->addInput($WA);
//—————————————————————————————————————— 高级设置 ——————————————————————————————————————
// 添加自定义 DNS 预解析域名字段
$dnsPrefetch = new Typecho_Widget_Helper_Form_Element_Textarea('dnsPrefetch',NULL,NULL,_t('DNS预解析域名'),_t('请输入需要预解析的域名,每行一个。例如:<br>https://onenote.io<br>https://cdn.onenote.io')
);
$form->addInput($dnsPrefetch);
// Sitemap 检测更新频率
$sitemapInterval = new Typecho_Widget_Helper_Form_Element_Text('sitemapInterval', NULL, '86400', _t('Sitemap检测更新频率'), _t('单位为秒。填写 10 则每 10 秒检测一次公开内容是否变化;留空或小于 1 时默认 86400 秒。'));
$form->addInput($sitemapInterval);
// 缩略图参数
$imgSmall = new Typecho_Widget_Helper_Form_Element_Text('imgSmall', NULL, NULL, _t('缩略图参数'), _t('填写服务端支持的缩略图参数(如 !small),需搭配 CDN 或云存储图片处理功能使用。<br>留空则显示原图,填写后文章列表的缩略图会自动携带该参数,请确保文章内的图片支持缩略图处理。'));
$form->addInput($imgSmall);
// 代码块美化
$BeCode = new Typecho_Widget_Helper_Form_Element_Radio('BeCode', array('on' => '开启','off' => '不开启'),'on','代码块美化', '默认开启,开启后会美化代码区域,技术博客请开启,否则代码块会显示异常,纯生活记录类博客建议关闭。');
$form->addInput($BeCode);
// 随机高清文艺图片源
$RandomIMG = new Typecho_Widget_Helper_Form_Element_Radio('RandomIMG', array('oneblog' => '主题图库','off' => '关闭'),'off','随机高清缩略图', '设置后文章列表页在文章没有任何图片且没有单独设置封面时显示随机缩略图,如果想让文章详情页显示封面图,请编辑文章时填写自定义字段[文章封面]。');
$form->addInput($RandomIMG);
// 自动夜间模式
$AutoNightMode = new Typecho_Widget_Helper_Form_Element_Radio('AutoNightMode', array('on' => '开启','off' => '关闭'),'off','自动夜间模式', '开启后,网站将在每天 19:00 至次日 05:00 自动启用夜间模式,其他时间自动关闭。');
$form->addInput($AutoNightMode);
// Cloudflare Turnstile 前端 Site Key
$cfSiteKey = new Typecho_Widget_Helper_Form_Element_Text('CFSiteKey', NULL, '', _t('Cloudflare Turnstile SiteKey'), _t('填写 Turnstile 的 sitekey(用于前端)。留空则不启用 CF。'));
$form->addInput($cfSiteKey);
// Cloudflare Turnstile Secret(用于服务器端验证)
$cfSecret = new Typecho_Widget_Helper_Form_Element_Text('CFSecret', NULL, '', _t('Cloudflare Turnstile Secret'), _t('填写 Turnstile 的 secret(用于服务器端验证)。请妥善保管,不要公开。'));
$form->addInput($cfSecret);
// 评论极验验证
$GeetestID = new Typecho_Widget_Helper_Form_Element_Text('GeetestID', NULL, NULL, _t('极验ID'), _t('如需开启评论提交前的极验验证,请填写极验后台生成的 验证ID'));
$form->addInput($GeetestID);
$GeetestKEY = new Typecho_Widget_Helper_Form_Element_Text('GeetestKEY', NULL, NULL, _t('极验KEY'), _t('如需开启评论提交前的极验验证,请填写极验后台生成的 验证KEY'));
$form->addInput($GeetestKEY);
//—————————————————————————————————————— 社交按钮 ——————————————————————————————————————
$QQ = new Typecho_Widget_Helper_Form_Element_Text('QQ', NULL, NULL, _t('QQ'), _t('请填写完整的QQ群描述或QQ号描述,输入的内容会直接作为弹框消息显示。'));
$form->addInput($QQ);
$Weixin = new Typecho_Widget_Helper_Form_Element_Text('Weixin', NULL, NULL, _t('微信公众号'), _t('请填写微信公众号或个人微信的二维码图片url,格式为:https://。'));
$form->addInput($Weixin);
$Email = new Typecho_Widget_Helper_Form_Element_Text('Email', NULL, NULL, _t('邮箱'), _t('请填写站长邮箱。'));
$form->addInput($Email);
$Github = new Typecho_Widget_Helper_Form_Element_Text('Github', NULL, NULL, _t('Github'), _t('请填写Github地址。'));
$form->addInput($Github);
//—————————————————————————————————————— 自定义样式 ——————————————————————————————————————
// 网站字体
$fontOptions = [];
foreach (oneblogFonts() as $key => $font) {
$fontOptions[$key] = $font['name'];
}
$FontFamily = new Typecho_Widget_Helper_Form_Element_Radio('FontFamily', $fontOptions, 'default', _t('文章字体'), _t('选择后将在文章列表页和详情页应用该字体。'));
$form->addInput($FontFamily);
$CSS = new Typecho_Widget_Helper_Form_Element_Textarea('CSS',NULL,NULL,_t('自定义CSS'),_t('可以填写css,覆盖默认的样式,本css优先级最高。')
);
$form->addInput($CSS);
$JS = new Typecho_Widget_Helper_Form_Element_Textarea('JS',NULL,NULL,_t('自定义JS'),_t('请输入自定义js代码,填写后会直接加载至页脚。')
);
$form->addInput($JS);
$themeColor = new Typecho_Widget_Helper_Form_Element_Text('themeColor',NULL,'#ff5050',_t('主题色'),_t('请选择主题色调。')
);
$form->addInput($themeColor);
}
function themeFields($layout) { ?>
<link rel="stylesheet" href="https://cncdn.cc/oneblog/3.7.0/admin.css" type="text/css" />
<?php
$thumb = new Typecho_Widget_Helper_Form_Element_Text('thumb', NULL, NULL, _t('封面图片'), _t('此处填写后会让文章/独立页面详情样式显示为有封面图的样式效果,文章列表也会出现封面缩略图,搜索引擎抓取的也是该封面图。'));
$thumb->input->setAttribute('class', 'full-width-input');
$layout->addItem($thumb);
$origin = new Typecho_Widget_Helper_Form_Element_Text('origin', NULL, NULL, _t('图片来源'), _t('请输入图片的版权所有人名称或网站名(如:Unsplash)'));
$origin->input->setAttribute('class', 'full-width-input');
$layout->addItem($origin);
$author = new Typecho_Widget_Helper_Form_Element_Text('author', NULL, NULL, _t('作者'), _t('不填则默认为原创文章,作者为账号本人。'));
$author->input->setAttribute('class', 'full-width-input');
$layout->addItem($author);
}
function CustomMenu() {
$menuItems = Typecho_Widget::widget('Widget_Options')->MenuSet;
$hasIcon = '';
$noIcon = '';
if (!empty($menuItems)) {
$lines = explode("\n", $menuItems);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
@list($name, $url, $icon, $target) = array_pad(array_map('trim', explode(',', $line)), 4, '');
if (empty($name) || empty($url)) continue;
$targetAttr = ($target === '_blank') ? ' target="_blank" rel="noopener"' : '';
$hasIcon .= sprintf(
'<li><a href="%s"%s><i class="%s"></i>%s</a></li>',//移动端菜单格式
htmlspecialchars($url),
$targetAttr,
htmlspecialchars($icon ?? ''),
htmlspecialchars($name)
);
$noIcon .= sprintf(
'<a href="%s"%s>%s</a>',//PC端底部菜单格式
htmlspecialchars($url),
$targetAttr,
htmlspecialchars($name)
);
}
}
return [
'hasIcon' => $hasIcon ? $hasIcon : '',
'noIcon' => $noIcon ? $noIcon : ''
];
}
function getNZMenuData() {
static $cachedData = null;
if ($cachedData !== null) return $cachedData;
$menuItems = Typecho_Widget::widget('Widget_Options')->MenuSet;
$data = [];
if (!empty($menuItems)) {
foreach (explode("\n", $menuItems) as $line) {
$line = trim($line);
if ($line === '') continue;
$parts = array_map('trim', explode(',', $line, 4));
if (count($parts) < 3) continue;
$name = $parts[0] ?? '';
$url = $parts[1] ?? '';
$icon = $parts[2] ?? '';
$target = ($parts[3] ?? '') === '_blank' ? '_blank' : '';
if ($name === '' || $url === '') continue;
$data[] = [
'name' => htmlspecialchars($name, ENT_QUOTES, 'UTF-8'),
'url' => htmlspecialchars($url, ENT_QUOTES, 'UTF-8'),
'icon' => htmlspecialchars($icon, ENT_QUOTES, 'UTF-8'),
'target' => $target
];
}
}
return $cachedData = $data;
}
function get_banner_data($options) {
if ($options->switch != 'on') return [];
$cids = array_filter(
array_slice(
preg_split('/[,\s]+/', $options->Banner ?? '', -1, PREG_SPLIT_NO_EMPTY),
0, 3
),
function($v) { return ctype_digit((string)$v) && $v > 0; }
);
if (!$cids) return [];
$db = Typecho_Db::get();
$cid_str = implode(',', $cids);
$is_mysql = stripos($db->getAdapterName(), 'mysql') !== false;
$query = $db->select()
->from('table.contents')
->where("cid IN ($cid_str)")
->where('type = ?', 'post');
if ($is_mysql) {
$query->order("FIELD(cid, $cid_str)");
}
$results = $db->fetchAll($query);
if (!$is_mysql && count($results) > 1) {
usort($results, function($a, $b) use ($cids) {
return array_search($a['cid'], $cids) - array_search($b['cid'], $cids);
});
}
return $results;
}
function getTopTags() {
$db = Typecho_Db::get();
$query = $db->select('name', 'slug')
->from('table.metas')
->where('type = ?', 'tag')
->order('count', Typecho_Db::SORT_DESC)
->limit(10);
$tags = $db->fetchAll($query);
foreach ($tags as $tag) {
$tagUrl = Typecho_Common::url('tag/' . urlencode($tag['slug']), Helper::options()->index);
echo '<a href="' . $tagUrl . '"># ' . htmlspecialchars($tag['name']) . '</a>';
echo "\n";
}
}
function AutoLightbox($content) {
if (empty($content) || stripos($content, '<img') === false) {
return $content;
}
$pattern = '/<img\b[^>]*src=["\']([^"\']+)["\'][^>]*>/i';
return preg_replace_callback($pattern, function ($matches) {
$imgTag = $matches[0];
$src = $matches[1];
$path = parse_url($src, PHP_URL_PATH);
if ($path && strtolower(pathinfo($path, PATHINFO_EXTENSION)) === 'svg') {
return $imgTag;
}
if (preg_match('/class=["\'][^"\']*\bno-lightbox\b[^"\']*["\']/i', $imgTag)) {
return $imgTag;
}
if (preg_match('/data-fancybox/i', $imgTag)) {
return $imgTag;
}
return '<a data-fancybox="gallery" href="' . htmlspecialchars($src, ENT_QUOTES) . '">' . $imgTag . '</a>';
}, $content);
}
function parseEmojis($content) {
$content = (string)$content;
$emojiPath = Helper::options()->siteUrl.'usr/themes/OneBlog/static/img/emoji/';
return preg_replace_callback('/\[emoji:([a-zA-Z0-9_]+)\]/', function($matches) use ($emojiPath) {
$emojiName = $matches[1];
return '<img class="biaoqing" src="' . $emojiPath . $emojiName . '.svg" alt="' . $emojiName . '">';
}, $content);
}
function get_post_view($archive) {
$cid = $archive->cid;
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
try {
$db->fetchRow($db->select()->from('table.contents')->where('cid = ?', $cid));
} catch (Typecho_Db_Exception $e) {
try {
$db->query('ALTER TABLE ' . $prefix . 'contents ADD views INT DEFAULT 0;');
} catch (Typecho_Db_Exception $e) {
}
}
$fieldCheck = $db->fetchRow($db->select()->from('table.contents')->where('cid = ?', $cid));
if (!array_key_exists('views', $fieldCheck)) {
try {
$db->query('ALTER TABLE ' . $prefix . 'contents ADD views INT DEFAULT 0;');
} catch (Typecho_Db_Exception $e) {
echo 0;
return;
}
}
$row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
$currentViews = (int) $row['views'];
$shouldCount = false;
$views = [];
if ($archive->is('single')) {
$cookieData = Typecho_Cookie::get('extend_contents_views');
$views = $cookieData ? explode(',', $cookieData) : [];
if (!in_array($cid, $views)) {
$shouldCount = true;
$db->query($db->update('table.contents')
->rows(['views' => $currentViews + 1])
->where('cid = ?', $cid));
$views[] = $cid;
Typecho_Cookie::set('extend_contents_views', implode(',', $views));
}
}
$displayViews = $currentViews + ($shouldCount ? 1 : 0);
echo formatNum($displayViews);
}
function formatNum($num) {
if ($num >= 100000) {
$num = '10w+';
} elseif ($num >= 10000) {
$num = round($num / 10000 * 100) / 100 ;
$num = round($num,1).' w';
} elseif($num >= 1000) {
$num = round($num / 1000 * 100) / 100 ;
$num = round($num,1). ' k';
} else {
$num = $num;
}
return $num;
}
function time_ago($date) {
$timestamp = strtotime($date->format('Y-m-d H:i:s'));
$current_time = time();
$time_diff = $current_time - $timestamp;
if ($time_diff < 60) {
return $time_diff . "秒前";
} elseif ($time_diff < 3600) {
return floor($time_diff / 60) . "分钟前";
} elseif ($time_diff < 86400) {
return floor($time_diff / 3600) . "小时前";
} elseif ($time_diff < 2592000) {
return floor($time_diff / 86400) . "天前";
} elseif ($time_diff < 31536000) {
return floor($time_diff / 2592000) . "个月前";
} elseif ($time_diff < 94608000) {
return floor($time_diff / 31536000) . "年前";
} else {
return $date->format('Y/m/d');
}
}
function timer_start(){
global $timestart;
$mtime = explode( ' ', microtime() );
$timestart = $mtime[1] + $mtime[0];
return true;
}
timer_start();
function timer_stop($display = 0, $precision = 3){
global $timestart, $timeend;
$mtime = explode( ' ', microtime() );
$timeend = $mtime[1] + $mtime[0];
$timetotal = number_format( $timeend - $timestart, $precision );
$r = $timetotal < 1 ? $timetotal . " s" : $timetotal . " s";
if($display){echo $r;}
return $r;
}
function art_count ($cid){
$db=Typecho_Db::get ();
$rs=$db->fetchRow ($db->select ('table.contents.text')->from ('table.contents')->where ('table.contents.cid=?',$cid)->order ('table.contents.cid',Typecho_Db::SORT_ASC)->limit (1));
echo mb_strlen($rs['text'], 'UTF-8');
}
function dengji($i) {
$db = Typecho_Db::get();
$adminAuthorId = 1;
if (empty($i)) {
$admin = $db->fetchRow($db->select('mail')->from('table.users')->where('uid = ?', $adminAuthorId));
$i = $admin ? $admin['mail'] : null;
}
if (empty($i)) {
echo '<span class="level">Lv.1</span>';
return;
}
$author = $db->fetchRow($db->select('authorId')->from('table.comments')->where('mail = ?', $i)->limit(1));
$authorId = $author ? $author['authorId'] : null;
if ($authorId == $adminAuthorId) {
echo '<span class="level owner">博主</span>';
return;
}
$mail = $db->fetchRow($db->select(array('COUNT(cid)' => 'rbq'))->from('table.comments')->where('mail = ?', $i)->where('authorId = ?', '0'));
$rbq = $mail ? $mail['rbq'] : 0;
if ($rbq < 3) {
echo '<span class="level">Lv.1</span>';
} elseif ($rbq < 10) {
echo '<span class="level">Lv.2</span>';
} elseif ($rbq < 20) {
echo '<span class="level">Lv.3</span>';
} elseif ($rbq < 30) {
echo '<span class="level">Lv.4</span>';
} elseif ($rbq < 40) {
echo '<span class="level">Lv.5</span>';
} else {
echo '<span class="level owner">知己</span>';
}
}
function getGravatar($email, $s = 96, $d = 'mp', $r = 'g', $img = false, $atts = array()){
preg_match_all('/((\d)*)@qq.com/', $email, $vai);
if (empty($vai['1']['0'])) {
$url = 'https://weavatar.com/avatar/';
$url .= md5(strtolower(trim($email)));
$url .= "?s=$s&d=$d&r=$r";
if ($img) {
$url = '<img src="' . $url . '"';
foreach ($atts as $key => $val)
$url .= ' ' . $key . '="' . $val . '"';
$url .= ' />';
}
}else{
$url = 'https://q2.qlogo.cn/headimg_dl?dst_uin='.$vai['1']['0'].'&spec=100';
}
return $url;
}
function showThumbnail($widget, $allowRandom = true){
if ($widget->fields->thumb) {
return $widget->fields->thumb;
}
$content = $widget->content ?? '';
preg_match_all('/<img.*?src=["\'](.*?)["\']/', $content, $matches);
if (isset($matches[1][0])) {
return $matches[1][0];
}
if ($allowRandom && Helper::options()->RandomIMG == 'oneblog'){
$randomParam = '?t=' . time() . rand(1, 1000);
return Helper::options()->themeUrl . '/api/img.php' . $randomParam;
}
return;
}
function themeInit($archive) {
if ($archive->request->is("commentLike=dz")) {
commentLikes($archive);
}
if ($archive->request->isPost() &&
!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
Typecho_Plugin::factory('Widget_Feedback')->finishComment = function($comment) {
if (ob_get_length()) ob_clean();
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'success' => true,
'message' => $comment->status === 'waiting' ? '评论提交成功,请等待审核' : '评论发表成功',
'coid' => $comment->coid
], JSON_UNESCAPED_UNICODE);
exit;
};
}
}
function commentLikesNum($coid, &$record = NULL){
$db = Typecho_Db::get();
$callback = array(
'likes' => 0,
'recording' => false
);
if (array_key_exists('likes', $data = $db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $coid)))) {
$callback['likes'] = $data['likes'];
} else {
$db->query('ALTER TABLE `' . $db->getPrefix() . 'comments` ADD `likes` INT(10) NOT NULL DEFAULT 0;');
}
if (empty($recording = Typecho_Cookie::get('__typecho_comment_likes_record'))) {
Typecho_Cookie::set('__typecho_comment_likes_record', '[]');
} else {
$callback['recording'] = is_array($record = json_decode($recording)) && in_array($coid, $record);
}
return $callback;
}
function commentLikes($archive){
$archive->response->setStatus(200);
$_POST['coid'];
$_POST['behavior'];
$loginState = Typecho_Widget::widget('Widget_User')->hasLogin();
$res1 = commentLikesNum($_POST['coid'], $record);
$num = 0;
if(!empty($_POST['coid']) && !empty($_POST['behavior'])){
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$coid = (int)$_POST['coid'];
if (!array_key_exists('likes', $db->fetchRow($db->select()->from('table.comments')))) {
$db->query('ALTER TABLE `' . $prefix . 'comments` ADD `likes` INT(30) DEFAULT 0;');
}
$row = $db->fetchRow($db->select('likes')->from('table.comments')->where('coid = ?', $coid));
$updateRows = $db->query($db->update('table.comments')->rows(array('likes' => (int) $row['likes'] + 1))->where('coid = ?', $coid));
if($updateRows){
$num = $row['likes'] + 1;
$state = "success";
array_push($record, $coid);
Typecho_Cookie::set('__typecho_comment_likes_record', json_encode($record));
}else{
$num = $row['likes'];
$state = "error";
}
}else{
$state = 'Illegal request';
}
$archive->response->throwJson(array(
"state" => $state,
"num" => $num
));
}
function MemosList($comments, $user) { ?>
<li class="animate__animated animate__fadeIn">
<div id="<?php echo $comments->theId(); ?>">
<div class="user">
<?php
$email = $comments->mail;
$imgUrl = getGravatar($email);
echo '<img class="avatar" src="' . $imgUrl . '">';
?>
<div class="user-info">
<span class="name"><?php echo $comments->author(); ?></span>
<span class="date lite-black"><?php echo $comments->date('Y-m-d H:i'); ?></span>
</div>
</div>
<?php echo $comments->content(); ?>
<?php if (isMemosImageEnabled()) : ?>
<?php $imgs = getMemosImages($comments->coid); ?>
<?php if (!empty($imgs)) : ?>
<?php $count = count($imgs); ?>
<div class="memos-img-grid grid-<?php echo $count; ?>">
<?php foreach ($imgs as $img) : ?>
<?php $thumb = getMemosThumbUrl($img); ?>
<div class="memos-img-item">
<a href="<?php echo htmlspecialchars($img); ?>"
data-fancybox="memos-<?php echo $comments->coid; ?>"
data-caption="<?php echo $comments->content(); ?>">
<img class="lazy-load"
src="<?php echo htmlspecialchars($thumb); ?>"
data-src="<?php echo htmlspecialchars($thumb); ?>">
</a>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
<?php
$commentLikes = commentLikesNum($comments->coid);
$likes = $commentLikes['likes'];
$recording = $commentLikes['recording'];
?>
<div class="commentLike">
<a class="commentLikeOpt"
id="commentLikeOpt-<?php echo $comments->coid; ?>"
href="javascript:;"
data-coid="<?php echo $comments->coid; ?>"
data-recording="<?php echo $recording; ?>">
<i id="commentLikeI-<?php echo $comments->coid; ?>"
class="<?php echo $recording ? 'iconfont icon-liked red' : 'iconfont icon-like red'; ?>"></i>
<span class="lite-black" id="commentLikeSpan-<?php echo $comments->coid; ?>"><?php echo $likes; ?></span>
</a>
</div>
</div>
</li>
<?php }
/**
* 判断插件是否启用
*/
function isMemosImageEnabled()
{
$export = Typecho_Plugin::export();
return isset($export['activated']['MemosImage']);
}
* 获取微语图片(插件启用且表存在时才返回)
*/
function getMemosImages($coid)
{
if (!isMemosImageEnabled()) {
return [];
}
$db = Typecho_Db::get();
$table = $db->getPrefix() . 'memos_img';
$exists = $db->fetchRow($db->query("SHOW TABLES LIKE '{$table}'"));
if (!$exists) {
return [];
}
$row = $db->fetchRow(
$db->select('imgs')->from('table.memos_img')->where('coid = ?', $coid)
);
if (!$row || empty($row['imgs'])) {
return [];
}
$imgs = json_decode($row['imgs'], true);
return is_array($imgs) ? array_slice($imgs, 0, 9) : [];
}
* 生成缩略图 URL
*/
function getMemosThumbUrl($url)
{
$append = '';
try {
$plugin = Helper::options()->plugin('MemosImage');
$append = (string)($plugin->cosThumbAppend ?? '');
} catch (Throwable $e) {}
$isLocalUpload = (strpos($url, '/usr/uploads/') !== false);
if ($isLocalUpload) {
$pi = pathinfo($url);
if (empty($pi['extension'])) {
return $url;
}
return $pi['dirname'] . '/' . $pi['filename'] . '-s.' . $pi['extension'];
}
return $append !== '' ? ($url . $append) : $url;
}
function oneblog_comment_submit(){
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
if (!isset($_POST['text']) && !isset($_POST['author'])) return;
foreach ($_POST as $k => &$v) {
if (is_string($v)) {
$v = str_replace(["\r", "\n", "\0"], '', $v);
}
}
unset($v);
if (isset($_POST['url'])) {
$url = trim($_POST['url']);
if ($url === '') {
$_POST['url'] = '';
} else {
$hasBadChars = preg_match('/[\"\';<>\(\)\[\]\{\}]/', $url);
$badProtocol = preg_match('#^(javascript|data|vbscript):#i', $url);
$allowedScheme = preg_match('#^(https?://|//)#i', $url);
if ($hasBadChars || $badProtocol || !$allowedScheme) {
$_POST['url'] = '';
} else {
$_POST['url'] = $url;
}
}
}
foreach (['author', 'mail'] as $k) {
if (isset($_POST[$k])) {
$_POST[$k] = preg_replace('/[\"\<\>]/', '', trim((string)$_POST[$k]));
}
}
foreach (['author','mail','url','text'] as $k) {
if (isset($_REQUEST[$k])) {
$_REQUEST[$k] = $_POST[$k] ?? $_REQUEST[$k];
}
}
}
oneblog_comment_submit();
function comment_author_link($comments) {
$author = htmlspecialchars($comments->author, ENT_QUOTES, 'UTF-8');
$url = '';
if (is_object($comments) && isset($comments->url)) {
$url = trim($comments->url);
} elseif (is_array($comments) && isset($comments['url'])) {
$url = trim($comments['url']);
}
if ($url !== '') {
$href = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
return '<a href="' . $href . '" target="_blank">' . $author . '</a>';
}
return $author;
}
function oneblog_abort($msg) {
if (
!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
) {
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'success' => false,
'message' => $msg
], JSON_UNESCAPED_UNICODE);
exit;
}
exit('<script>alert(' . json_encode($msg) . ');history.back();</script>');
}
function verify_cf_token($token, $secret, $remoteIp = null) {
if (empty($token)) return ['success' => false];
$post = [
'secret' => $secret,
'response' => $token
];
if ($remoteIp) $post['remoteip'] = $remoteIp;
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($post),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$resp = curl_exec($ch);
curl_close($ch);
return json_decode($resp, true) ?: ['success' => false];
}
function oneblog_check_captcha($comment, $post, $result) {
$options = Helper::options();
$user = Typecho_Widget::widget('Widget_User');
if ($user->hasLogin()) {
return $comment;
}
if (! empty($options->CFSiteKey) && !empty($options->CFSecret)) {
if (empty($_POST['cf_token'])) {
oneblog_abort('请先完成安全验证');
}
$res = verify_cf_token(
$_POST['cf_token'],
$options->CFSecret,
$_SERVER['REMOTE_ADDR'] ?? null
);
if (empty($res['success'])) {
oneblog_abort('安全验证未通过,请重试');
}
return $comment;
}
if (! empty($options->GeetestID) && !empty($options->GeetestKEY)) {
foreach (['lot_number','captcha_output','pass_token','gen_time'] as $k) {
if (empty($_POST[$k])) {
oneblog_abort('请完成极验验证');
}
}
$sign = hash_hmac('sha256', $_POST['lot_number'], $options->GeetestKEY);
$query = [
'lot_number' => $_POST['lot_number'],
'captcha_output' => $_POST['captcha_output'],
'pass_token' => $_POST['pass_token'],
'gen_time' => $_POST['gen_time'],
'sign_token' => $sign
];
$url = 'https://gcaptcha4.geetest.com/validate?captcha_id=' . $options->GeetestID;
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($query),
'timeout' => 5
]
]);
$resp = @file_get_contents($url, false, $ctx);
$json = json_decode($resp, true);
if (empty($json['result']) || $json['result'] !== 'success') {
oneblog_abort('极验验证未通过');
}
return $comment;
}
return $comment;
}
Typecho_Plugin::factory('Widget_Feedback')->comment = 'oneblog_check_captcha';
function CatInfo($description, $defaultImage = '') {
if (empty($defaultImage)) {
$defaultImage = Helper::options()->themeUrl . '/static/img/bg.jpg';
}
$imageUrl = $defaultImage;
$textDescription = '';
if (!empty($description)) {
if (preg_match('/https?:\/\/[^\s]+/', $description, $matches)) {
$imageUrl = htmlspecialchars($matches[0]);
}
$textDescription = trim(preg_replace('/https?:\/\/[^\s]+/', '', $description));
if (empty($textDescription)) {
$textDescription = '暂无关于该分类的介绍';
}
} else {
$textDescription = '暂无关于该分类的介绍';
}
return [
'img' => $imageUrl,
'info' => $textDescription
];
}
function redirect_404(){
$request = Typecho_Request::getInstance();
$pathInfo = $request->getPathInfo();
if (preg_match('/^\/(attachment\/\d+|author\/\w+)/i', $pathInfo)) {
$options = Typecho_Widget::widget('Widget_Options');
$url = $options->siteUrl . '404';
header("Location: $url");
exit;
}
}
Typecho_Plugin::factory('Widget_Archive')->beforeRender = 'redirect_404';