-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathfunctions.php
More file actions
1769 lines (1541 loc) · 70.1 KB
/
Copy pathfunctions.php
File metadata and controls
1769 lines (1541 loc) · 70.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?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'];
}
// 生成 sitemap.xml
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);
}
// 生成绝对路径的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, '/');
}
// 获取 sitemap 展示样式表的绝对地址,浏览器打开 sitemap.xml 时会以表格形式展示。
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);
}
// 获取首页 og:image 对应的网站标识图,写入 sitemap 首页的 image:image。
function oneblogSitemapHomeImage($siteUrl) {
$options = Helper::options();
$image = $options->Webthumb ?: rtrim($options->themeUrl, '/') . '/static/img/logo.png';
return oneblogSitemapUrl($image, $siteUrl);
}
// 为文章路由补齐分类占位符,避免自定义固定链接中的 {category} 原样出现在 sitemap。
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;
}
// 清理和验证URL,确保其为有效的绝对URL
function oneblogSitemapCleanUrl($url) {
$url = trim((string) $url);
if ($url === '') return '';
$url = str_replace('&', '&', $url);
return preg_match('#^https?://#i', $url) ? $url : '';
}
// 获取内容的最后修改时间,优先使用 modified 字段,如果没有则使用 created 字段
function oneblogSitemapLastmod($row) {
$modified = isset($row['modified']) ? (int) $row['modified'] : 0;
$created = isset($row['created']) ? (int) $row['created'] : 0;
return $modified > 0 ? $modified : $created;
}
// 将时间戳格式化为符合 sitemap 要求的 ISO 8601 格式
function oneblogSitemapFormatLastmod($time) {
$time = (int) $time;
return date('c', $time > 0 ? $time : time());
}
// 为 sitemap 复用 showThumbnail() 构造轻量内容对象
function oneblogSitemapWidget($row, $thumb = '') {
return (object) [
'content' => $row['text'] ?? '',
'fields' => (object) ['thumb' => $thumb]
];
}
// 批量预取自定义字段 thumb,供 sitemap 复用 showThumbnail() 时避免逐篇查询
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;
}
// 批量获取文章所属分类,并生成父子分类目录,供 {category} 固定链接占位符使用。
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;
}
// 获取全部分类数据,支持为子分类生成 parent/child 形式的目录。
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;
}
// 根据分类 mid 递归生成目录,最多向上追溯 20 层以避免异常循环。
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);
}
// 批量获取分类或标签的相关信息和最后修改时间,用于 sitemap 的分类和标签列表
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'
));
}
// 检测内容是否变化以决定是否需要更新 sitemap
function oneblogSitemapMetaPath() {
return rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.meta';
}
// 控制 sitemap 检测频率,避免频繁检测导致性能问题
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, '
. 'SUM(cid) AS cid_sum, '
. 'SUM(created) AS created_sum, '
. 'SUM(modified) AS modified_sum '
. '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, '
. 'MAX(CASE WHEN c.modified > 0 THEN c.modified ELSE c.created END) AS latest, '
. 'SUM(DISTINCT m.mid) AS mid_sum, '
. 'SUM(DISTINCT COALESCE(m.parent, 0)) AS parent_sum, '
. 'SUM(DISTINCT COALESCE(m.order, 0)) AS order_sum, '
. 'SUM(DISTINCT COALESCE(m.count, 0)) AS count_sum '
. '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]));
}
// 安全地引用字符串,避免SQL注入风险
function oneblogSitemapQuote($value) {
return "'" . str_replace("'", "''", (string) $value) . "'";
}
// 更新 sitemap.xml 文件,如果发生异常则记录错误日志
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;
}
}
// 在脚本结束时检查是否需要更新 sitemap,如果需要则执行更新
function oneblogSitemapShutdown() {
if (!empty($GLOBALS['oneblog_sitemap_needs_update']) && !oneblogSitemapUpdate()) {
@unlink(oneblogSitemapCheckPath());
}
}
// 获取 sitemap 检测更新的时间间隔,默认为 86400 秒(24小时),可以通过后台设置调整,但不允许小于 1 秒以避免性能问题
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');
$requestUrl = Typecho_Request::getInstance()->getRequestUrl();
$security = Helper::security();
$token = isset($_POST['_']) ? (string) $_POST['_'] : '';
$validTokens = [$security->getToken($requestUrl)];
if (!empty($_SERVER['HTTP_REFERER'])) {
$validTokens[] = $security->getToken($_SERVER['HTTP_REFERER']);
}
if ($token === '' || !in_array($token, $validTokens, true)) {
echo json_encode(['success'=>false, 'message'=>'安全令牌无效,请刷新页面后重试'], JSON_UNESCAPED_UNICODE);
exit;
}
$user = Typecho_Widget::widget('Widget_User');
if (!$user->hasLogin()) {
echo json_encode(['success'=>false, 'message'=>'请先登录后台'], JSON_UNESCAPED_UNICODE);
exit;
}
$theTheme = 'OneBlog';
$db = Typecho_Db::get();
$uploadDir = Helper::options()->uploadDir ?: 'usr/uploads';
$uploadDir = rtrim($uploadDir, '/\\');
$absUploadDir = __TYPECHO_ROOT_DIR__ . '/' . $uploadDir;
$uploadDirReady = is_dir($absUploadDir) || @mkdir($absUploadDir, 0755, true);
$backPath = $absUploadDir . '/BackupSetting_' . $theTheme . '.txt';
clearstatcache(true, $absUploadDir);
clearstatcache(true, $backPath);
$ret = ['success'=>false, 'message'=>'未知错误'];
$action = isset($_POST['action']) ? $_POST['action'] : '';
if ($action === 'oneblog_theme_backup') {
if (!$uploadDirReady) {
$ret = ['success'=>false, 'message'=>'备份失败,uploads 目录不存在且无法自动创建'];
} elseif (!file_exists($backPath) && !is_writable($absUploadDir)) {
$ret = ['success'=>false, 'message'=>'备份失败,uploads 目录不可写'];
} elseif (file_exists($backPath) && !is_writable($backPath)) {
$ret = ['success'=>false, 'message'=>'备份失败,备份文件不可写,请检查 BackupSetting_OneBlog.txt 权限'];
} else {
$themeConfStr = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:' . $theTheme))['value'];
$ok = @file_put_contents($backPath, $themeConfStr, LOCK_EX);
$ret = $ok !== false
? ['success'=>true, 'message'=>'备份成功']
: ['success'=>false, 'message'=>'备份失败,请检查 uploads 目录、备份文件权限或磁盘空间'];
}
} elseif ($action === 'oneblog_theme_restore') {
if (!file_exists($backPath)) {
$ret = ['success'=>false, 'message'=>'未找到备份文件,无法恢复'];
} elseif (!is_readable($backPath)) {
$ret = ['success'=>false, 'message'=>'恢复失败,备份文件不可读,请检查 BackupSetting_OneBlog.txt 权限'];
} else {
$str = @file_get_contents($backPath);
if ($str === false) {
echo json_encode(['success'=>false, 'message'=>'恢复失败,读取备份文件异常'], JSON_UNESCAPED_UNICODE);
exit;
}
$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'=>'恢复失败,数据库操作异常'];
}
}
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 = is_readable($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); ?>;
window.oneblogAdminToken = <?php echo json_encode(Helper::security()->getToken(Typecho_Request::getInstance()->getRequestUrl())); ?>;
</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">轻论坛</a>讨论交流。本主题的介绍、后续的更新或周边插件的开发更新,会优先发布在<a href="https://litebbs.com" target="_blank">轻论坛</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
$CSS = new Typecho_Widget_Helper_Form_Element_Textarea('CSS',NULL,NULL,_t('自定义CSS'),_t('可以填写css,覆盖默认的样式,本css优先级最高。')
);
$form->addInput($CSS);
// 自定义JS
$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() {
static $cachedMenu = null;
if ($cachedMenu !== null) return $cachedMenu;
$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 $cachedMenu = [
'hasIcon' => $hasIcon ? $hasIcon : '',
'noIcon' => $noIcon ? $noIcon : ''
];
}
//PC端右键菜单数据格式化
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;
}
//首页置顶banner文章 极致优化 只查询1次
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;
}
// 查询文章数最多的前10个标签
function getTopTags() {
static $cachedHtml = null;
if ($cachedHtml !== null) {
echo $cachedHtml;
return;
}
$db = Typecho_Db::get();
// 直接查询前10个热门标签的名称和slug
$query = $db->select('name', 'slug')
->from('table.metas')
->where('type = ?', 'tag')
->order('count', Typecho_Db::SORT_DESC)
->limit(10);
$tags = $db->fetchAll($query);
// 输出标签链接
$html = '';
foreach ($tags as $tag) {
$tagUrl = Typecho_Common::url('tag/' . urlencode($tag['slug']), Helper::options()->index);
$html .= '<a href="' . $tagUrl . '"># ' . htmlspecialchars($tag['name']) . '</a>' . "\n";
}
$cachedHtml = $html;
echo $html;
}
//文章内图片标签自动解析为灯箱效果
//文章内图片标签自动解析为灯箱效果
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);
//.svg后缀不加灯箱效果
if ($path && strtolower(pathinfo($path, PATHINFO_EXTENSION)) === 'svg') {
return $imgTag;
}
//.no-lightbox 类排除灯箱效果
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) {
// null、false等都转为空字符串,防止报错
$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];
$src = htmlspecialchars($emojiPath . $emojiName . '.svg', ENT_QUOTES, 'UTF-8');
$alt = htmlspecialchars($emojiName, ENT_QUOTES, 'UTF-8');
return '<img class="biaoqing" src="' . $src . '" alt="' . $alt . '">';
}, $content);
}
//无插件阅读数,cookie保证阅读量真实性
// Internal runtime helpers for schema compatibility, counters, and fragment cache.
function oneblogEnsureColumn($table, $column, $definition) {
static $cache = [];
if (!preg_match('/^[a-z0-9_]+$/i', $table) || !preg_match('/^[a-z0-9_]+$/i', $column)) {
return false;
}
$key = $table . '.' . $column;
if (array_key_exists($key, $cache)) {
return $cache[$key];
}
$db = Typecho_Db::get();
try {
$db->fetchRow($db->select($column)->from('table.' . $table)->limit(1));
return $cache[$key] = true;
} catch (Throwable $e) {
} catch (Exception $e) {
}
try {
$db->query('ALTER TABLE `' . $db->getPrefix() . $table . '` ADD `' . $column . '` ' . $definition);
return $cache[$key] = true;
} catch (Throwable $e) {
error_log('OneBlog ensure column failed: ' . $table . '.' . $column . ' ' . $e->getMessage());
} catch (Exception $e) {
error_log('OneBlog ensure column failed: ' . $table . '.' . $column . ' ' . $e->getMessage());
}
return $cache[$key] = false;
}
function oneblogIncrementColumn($table, $column, $whereColumn, $whereValue) {
if (
!preg_match('/^[a-z0-9_]+$/i', $table) ||
!preg_match('/^[a-z0-9_]+$/i', $column) ||
!preg_match('/^[a-z0-9_]+$/i', $whereColumn)
) {
return false;
}
$db = Typecho_Db::get();
$whereValue = (int) $whereValue;
try {
$query = $db->update('table.' . $table);
if (method_exists($query, 'expression')) {
$result = $db->query(
$query->expression($column, $column . ' + 1')
->where($whereColumn . ' = ?', $whereValue)
);
return $result !== false;
}
} catch (Throwable $e) {
} catch (Exception $e) {
}
try {
$sql = 'UPDATE `' . $db->getPrefix() . $table . '` SET `' . $column . '` = `' . $column . '` + 1 WHERE `' . $whereColumn . '` = ' . $whereValue;
return $db->query($sql) !== false;
} catch (Throwable $e) {
error_log('OneBlog increment failed: ' . $table . '.' . $column . ' ' . $e->getMessage());
} catch (Exception $e) {