master
ブランチ master
LittleBeast / src / Site / Controller / Atom.php
6.3 KB、155行 生 Blame 履歴
1<?php
2/*************************************************************
3# 076 License
4
5Copyright (c) テクニカル諏訪子
6
7Permission is hereby granted to any person obtaining a copy of the software
8Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
9sublicense, and/or sell copies of the Software, subject to the following conditions:
10
11 1. **Origin Attribution**:
12 - You must not misrepresent the origin of the Software; you must not claim
13 you created the original Software.
14 - If the Software is used in a product, you must either:
15 a. Provide clear attribution in the product's documentation, user interface,
16 or other visible areas, **OR**
17 b. Pay the original developers a fee they specify in writing.
18 2. **Usage Restriction**:
19 - The Software, or any derivative works, dependencies, or libraries
20 incorporating it, must not be used for censorship or to suppress freedom of
21 speech, expression, or creativity. Prohibited uses include, but are not
22 limited to:
23 - Censorship of so-called "hate speech", visuals, non-mainstream opinions,
24 ideas, or objective reality.
25 - Tools or systems designed to restrict access to information or
26 artistic works.
27 3. **Notice Preservation**:
28 - This license and the above copyright notice must remain intact in all copies
29 of the source code.
30
31THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
32INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
33PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
34HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
35CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
36OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 */
38namespace Site\Controller;
39
40use Site\Controller\BlogPost;
41use Std\Lib\Markdown;
42
43class Atom extends BlogPost {
44 private string $domain = 'technicalsuwako.moe';
45
46 /**
47 * フィードは誰でも読めるので、会員・支援者・スタッフ限定の記事を除外する
48 * (getPosts()はプレビューを隠すだけで、記事そのものは返す)
49 *
50 * @param array $posts 投稿の配列
51 * @return array 公開記事だけの配列
52 */
53 private function publicOnly(array $posts): array {
54 $restricted = ['memberonly', 'subscriberonly', 'staffonly', 'nintendodevonly'];
55 return array_values(array_filter($posts, function($post) use ($restricted): bool {
56 return empty(array_intersect($restricted, $post['category'] ?? []));
57 }));
58 }
59
60 /**
61 * 最新の5記事のAtomフィードを生成する
62 *
63 * @param array $params パラメータ配列
64 * @return void
65 */
66 public function feed(array $params): void {
67 try {
68 $section = (string)($params['section'] ?? 'blog');
69 $blogs = blogs();
70 if (!isset($blogs[$section])) {
71 header('HTTP/1.1 404 Not Found');
72 exit;
73 }
74
75 // 最新の投稿を取得
76 $posts = $this->getPosts("/{$section}/", null);
77
78 // 最新の5件に制限
79 $posts = array_slice($this->publicOnly($posts), 0, 5);
80
81 // サイトのドメインを取得
82 $domain = $_SERVER['HTTP_HOST'];
83 $baseUrl = 'https://'.$domain;
84
85 // 「blog」のフィードのIDとリンクは今まで通り(IDが変わると購読者のリーダーで全部新しい記事になる)
86 $home = $section === 'blog' ? $baseUrl : "{$baseUrl}/{$section}";
87
88 // 現在の日時(RFC3339形式)
89 $published = date('c');
90
91 // XMLヘッダーとコンテンツタイプを設定
92 header('Content-Type: application/atom+xml; charset=utf-8');
93
94 // Atomフィードの開始部分
95 echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
96 echo '<feed xmlns="http://www.w3.org/2005/Atom">'."\n";
97
98 // フィードの基本情報
99 echo ' <title>'.htmlspecialchars($blogs[$section]['title'], ENT_XML1, 'UTF-8').'</title>'."\n";
100 echo ' <link href="'.$home.'" />'."\n";
101 echo ' <link href="'.$baseUrl.'/'.$section.'.atom" rel="self" />'."\n";
102 echo ' <id>'.$home.'/</id>'."\n";
103 echo ' <published>'.$published.'</published>'."\n";
104 echo ' <updated>'.$published.'</updated>'."\n";
105 echo ' <author>'."\n";
106 echo ' <name>'.SITEINFO['title'].'</name>'."\n";
107 echo ' </author>'."\n";
108
109 // 各エントリー(記事)
110 foreach ($posts as $post) {
111 // 記事の本文を取得(プレーンテキスト)
112 $path = ROOT."/{$section}/".$post['slug'].'.md';
113 $content = '';
114 $postPublished = date('c', strtotime($post['date']));
115
116 if (file_exists($path)) {
117 $fileContent = file_get_contents($path);
118 $parts = explode('----', $fileContent, 2);
119 if (count($parts) > 1) {
120 // 本文をHTMLとして準備
121 $md = new Markdown($post['slug'], "/{$section}/");
122 $content = $md->parse();
123 // HTMLタグを取り除かないようにCDATAで囲む
124 $content = '<![CDATA['.$content.']]>';
125 }
126 }
127
128 echo ' <entry>'."\n";
129 echo ' <title>'.htmlspecialchars($post['title']).'</title>'."\n";
130 echo ' <link href="'.$baseUrl.'/'.$section.'/'.$post['slug'].'" />'."\n";
131 echo ' <id>'.$baseUrl.'/'.$section.'/'.$post['slug'].'</id>'."\n";
132 echo ' <published>'.$postPublished.'</published>'."\n";
133
134 // カテゴリ(タグ)
135 if (isset($post['category']) && is_array($post['category'])) {
136 foreach ($post['category'] as $category) {
137 echo ' <category term="'.htmlspecialchars($category).'" />'."\n";
138 }
139 }
140
141 // 本文(要約または全文)
142 echo ' <content type="html">'.$content.'</content>'."\n";
143 echo ' </entry>'."\n";
144 }
145
146 // フィードの終了
147 echo '</feed>';
148 exit;
149 } catch (\Exception $e) {
150 header('Content-Type: text/plain; charset=utf-8');
151 echo 'フィードの作成に失敗: '.$e->getMessage();
152 exit;
153 }
154 }
155}