レポジトリ種類: Mercurial
<?php
namespace Site\Controller;
use Site\Controller\BlogPost;
use Site\Controller\Mods;
use Site\Lib\Markdown;
use Site\Lib\Template;
class Home extends BlogPost {
use Mods;
private array $searchKeywords = [];
//------------------------------------------
// ページ
//------------------------------------------
/**
* ブログ投稿ページ
*
* @param array $params ページ番号等
* @return void
*/
public function show(array $params): void {
try {
$page = isset($_GET['page']) ? $_GET['page'] : 1;
$postsPerPage = 20;
$tmpl = new Template('/');
$pagetit = 'トップページ';
$description = 'テクニカル諏訪子ちゃんの個人ブログ';
$posts = $this->getPosts();
if (!is_array($posts)) $posts = [];
// 検索機能が使用されている場合
if (isset($_GET['q']) && !empty($_GET['q'])) {
$this->searchKeywords = array_map('trim', explode(',', $_GET['q']));
$posts = $this->searchPosts($this->searchKeywords, $posts);
$pagetit = '検索結果: ' . htmlspecialchars($_GET['q']);
// 検索結果にキーワードをハイライト
$posts = $this->highlightKeywords($posts);
}
// ページネーション
$totalPosts = count($posts);
$totalPages = ceil($totalPosts / $postsPerPage);
$page = min($page, $totalPages);
$currentPosts = array_slice(
$posts,
($page - 1) * $postsPerPage,
$postsPerPage
);
$tmpl->assign('currentPage', $page);
$tmpl->assign('totalPages', $totalPages);
$tmpl->assign('posts', $currentPosts);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'blog');
$tmpl->assign('custCss', false);
$tmpl->assign('sns', $this->getSns());
$tmpl->assign('support', $this->getSupport());
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
$tmpl->assign('searchActive', !empty($this->searchKeywords));
$tmpl->addCss('news');
$tmpl->addCss('search');
$tmpl->addCss('pagination');
$tmpl->render('home');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
/**
* ブログ投稿ページ
*
* @param array $params マークダウンファイル等
* @return void
*/
public function article(array $params): void {
$page = '';
if (isset($params['page'])) $page = $params['page'];
try {
$tmpl = new Template('/');
$md = new Markdown($page);
$meta = $md->getMetadata();
$pagetit = $meta->title;
$article = $md->parse();
$description = 'テクニカル諏訪子ちゃんの個人ブログ';
// 検索からの遷移の場合、記事内のキーワードをハイライト
if (isset($_GET['q']) && !empty($_GET['q'])) {
$keywords = array_map('trim', explode(',', $_GET['q']));
$article = $this->highlightTextContent($article, $keywords);
$meta->title = $this->highlightTextContent($meta->title, $keywords);
}
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'blog');
$tmpl->assign('custCss', false);
$tmpl->assign('sns', $this->getSns());
$tmpl->assign('support', $this->getSupport());
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('article', $article);
$tmpl->assign('meta', $meta);
$tmpl->assign('description', $description);
if (isset($meta->css) && !empty($meta->css)) {
foreach ($meta->css as $v) {
$tmpl->addCss($v);
}
}
$tmpl->addCss('news-article');
$tmpl->addCss('search');
$tmpl->render('article');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
//------------------------------------------
// 機能性
//------------------------------------------
/**
* キーワードに基づいて投稿を検索する
*
* @param array $keywords 検索キーワードの配列
* @param array $posts 検索対象の投稿記事の配列
* @return array 検索条件に一致する投稿記事の配列
*/
private function searchPosts(array $keywords, array $posts): array {
if (empty($keywords) || empty($posts)) {
return $posts;
}
$foundPosts = [];
$path = ROOT.'/blog/';
foreach ($posts as $post) {
$matched = false;
// タイトルで検索
foreach ($keywords as $keyword) {
$keyword = trim($keyword);
if (empty($keyword)) continue;
// タイトル内でキーワードが見つかった場合
if (mb_stripos($post['title'], $keyword) !== false) {
$foundPosts[] = $post;
$matched = true;
break;
}
}
// すでにマッチしていれば次の記事へ
if ($matched) continue;
// 記事の本文をチェック
$slug = $post['slug'];
$filePath = $path.$slug.'.md';
if (file_exists($filePath)) {
$content = file_get_contents($filePath);
$parts = explode('----', $content, 2);
if (count($parts) > 1) {
$articleBody = trim($parts[1]);
foreach ($keywords as $keyword) {
$keyword = trim($keyword);
if (empty($keyword)) continue;
// 本文内でキーワードが見つかった場合
if (mb_stripos($articleBody, $keyword) !== false) {
$foundPosts[] = $post;
break;
}
}
}
}
}
return $foundPosts;
}
/**
* 検索結果の投稿内のキーワードをハイライトする
*
* @param array $posts 検索結果の投稿配列
* @return array ハイライト処理後の投稿配列
*/
private function highlightKeywords(array $posts): array {
if (empty($this->searchKeywords) || empty($posts)) {
return $posts;
}
foreach ($posts as &$post) {
// タイトルのハイライト
if (!empty($post['title'])) {
$post['title'] =
$this->highlightTextContent($post['title'], $this->searchKeywords);
}
// プレビューのハイライト
if (!empty($post['preview'])) {
$post['preview'] =
$this->highlightTextContent($post['preview'], $this->searchKeywords);
}
}
return $posts;
}
/**
* テキスト内のキーワードをハイライトする
*
* @param string $text ハイライト対象のテキスト
* @param array $keywords ハイライトするキーワード配列
* @return string ハイライト処理後のテキスト
*/
private function highlightTextContent(string $text, array $keywords): string {
if (empty($keywords) || empty($text)) {
return $text;
}
$highlightedText = $text;
foreach ($keywords as $keyword) {
$keyword = trim($keyword);
if (empty($keyword)) continue;
// キーワードを大文字小文字を区別せずに置換
$highlightedText = preg_replace(
'/('.preg_quote($keyword, '/').')/iu',
'<span class="search-highlight">$1</span>',
$highlightedText
);
}
return $highlightedText;
}
}