ミラーる
This commit is contained in:
@@ -1,94 +1,94 @@
|
||||
<?php
|
||||
namespace Site\Controller;
|
||||
|
||||
use Site\Controller\BlogPost;
|
||||
use Site\Lib\Markdown;
|
||||
|
||||
class Atom extends BlogPost {
|
||||
private string $domain = 'technicalsuwako.moe';
|
||||
|
||||
/**
|
||||
* 最新の5記事のAtomフィードを生成する
|
||||
*
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function feed(array $params): void {
|
||||
try {
|
||||
// 最新の投稿を取得
|
||||
$posts = $this->getPosts('/blog/');
|
||||
// 最新の5件に制限
|
||||
$posts = array_slice($posts, 0, 5);
|
||||
|
||||
// サイトのドメインを取得
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
$baseUrl = 'https://'.$domain;
|
||||
|
||||
// 現在の日時(RFC3339形式)
|
||||
$published = date('c');
|
||||
|
||||
// XMLヘッダーとコンテンツタイプを設定
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
// Atomフィードの開始部分
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
|
||||
echo '<feed xmlns="http://www.w3.org/2005/Atom">'."\n";
|
||||
|
||||
// フィードの基本情報
|
||||
echo ' <title>'.SITEINFO['title'].'</title>'."\n";
|
||||
echo ' <link href="'.$baseUrl.'" />'."\n";
|
||||
echo ' <link href="'.$baseUrl.'/blog.atom" rel="self" />'."\n";
|
||||
echo ' <id>'.$baseUrl.'/</id>'."\n";
|
||||
echo ' <published>'.$published.'</published>'."\n";
|
||||
echo ' <updated>'.$published.'</updated>'."\n";
|
||||
echo ' <author>'."\n";
|
||||
echo ' <name>'.SITEINFO['title'].'</name>'."\n";
|
||||
echo ' </author>'."\n";
|
||||
|
||||
// 各エントリー(記事)
|
||||
foreach ($posts as $post) {
|
||||
// 記事の本文を取得(プレーンテキスト)
|
||||
$path = ROOT.'/blog/'.$post['slug'].'.md';
|
||||
$content = '';
|
||||
$postPublished = date('c', strtotime($post['date']));
|
||||
|
||||
if (file_exists($path)) {
|
||||
$fileContent = file_get_contents($path);
|
||||
$parts = explode('----', $fileContent, 2);
|
||||
if (count($parts) > 1) {
|
||||
// 本文をHTMLとして準備
|
||||
$md = new Markdown($post['slug'], '/blog/');
|
||||
$content = $md->parse();
|
||||
// HTMLタグを取り除かないようにCDATAで囲む
|
||||
$content = '<![CDATA['.$content.']]>';
|
||||
}
|
||||
}
|
||||
|
||||
echo ' <entry>'."\n";
|
||||
echo ' <title>'.htmlspecialchars($post['title']).'</title>'."\n";
|
||||
echo ' <link href="'.$baseUrl.'/blog/'.$post['slug'].'" />'."\n";
|
||||
echo ' <id>'.$baseUrl.'/blog/'.$post['slug'].'</id>'."\n";
|
||||
echo ' <published>'.$postPublished.'</published>'."\n";
|
||||
|
||||
// カテゴリ(タグ)
|
||||
if (isset($post['category']) && is_array($post['category'])) {
|
||||
foreach ($post['category'] as $category) {
|
||||
echo ' <category term="'.htmlspecialchars($category).'" />'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 本文(要約または全文)
|
||||
echo ' <content type="html">'.$content.'</content>'."\n";
|
||||
echo ' </entry>'."\n";
|
||||
}
|
||||
|
||||
// フィードの終了
|
||||
echo '</feed>';
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フィードの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php
|
||||
namespace Site\Controller;
|
||||
|
||||
use Site\Controller\BlogPost;
|
||||
use Site\Lib\Markdown;
|
||||
|
||||
class Atom extends BlogPost {
|
||||
private string $domain = 'technicalsuwako.moe';
|
||||
|
||||
/**
|
||||
* 最新の5記事のAtomフィードを生成する
|
||||
*
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function feed(array $params): void {
|
||||
try {
|
||||
// 最新の投稿を取得
|
||||
$posts = $this->getPosts('/blog/');
|
||||
// 最新の5件に制限
|
||||
$posts = array_slice($posts, 0, 5);
|
||||
|
||||
// サイトのドメインを取得
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
$baseUrl = 'https://'.$domain;
|
||||
|
||||
// 現在の日時(RFC3339形式)
|
||||
$published = date('c');
|
||||
|
||||
// XMLヘッダーとコンテンツタイプを設定
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
// Atomフィードの開始部分
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
|
||||
echo '<feed xmlns="http://www.w3.org/2005/Atom">'."\n";
|
||||
|
||||
// フィードの基本情報
|
||||
echo ' <title>'.SITEINFO['title'].'</title>'."\n";
|
||||
echo ' <link href="'.$baseUrl.'" />'."\n";
|
||||
echo ' <link href="'.$baseUrl.'/blog.atom" rel="self" />'."\n";
|
||||
echo ' <id>'.$baseUrl.'/</id>'."\n";
|
||||
echo ' <published>'.$published.'</published>'."\n";
|
||||
echo ' <updated>'.$published.'</updated>'."\n";
|
||||
echo ' <author>'."\n";
|
||||
echo ' <name>'.SITEINFO['title'].'</name>'."\n";
|
||||
echo ' </author>'."\n";
|
||||
|
||||
// 各エントリー(記事)
|
||||
foreach ($posts as $post) {
|
||||
// 記事の本文を取得(プレーンテキスト)
|
||||
$path = ROOT.'/blog/'.$post['slug'].'.md';
|
||||
$content = '';
|
||||
$postPublished = date('c', strtotime($post['date']));
|
||||
|
||||
if (file_exists($path)) {
|
||||
$fileContent = file_get_contents($path);
|
||||
$parts = explode('----', $fileContent, 2);
|
||||
if (count($parts) > 1) {
|
||||
// 本文をHTMLとして準備
|
||||
$md = new Markdown($post['slug'], '/blog/');
|
||||
$content = $md->parse();
|
||||
// HTMLタグを取り除かないようにCDATAで囲む
|
||||
$content = '<![CDATA['.$content.']]>';
|
||||
}
|
||||
}
|
||||
|
||||
echo ' <entry>'."\n";
|
||||
echo ' <title>'.htmlspecialchars($post['title']).'</title>'."\n";
|
||||
echo ' <link href="'.$baseUrl.'/blog/'.$post['slug'].'" />'."\n";
|
||||
echo ' <id>'.$baseUrl.'/blog/'.$post['slug'].'</id>'."\n";
|
||||
echo ' <published>'.$postPublished.'</published>'."\n";
|
||||
|
||||
// カテゴリ(タグ)
|
||||
if (isset($post['category']) && is_array($post['category'])) {
|
||||
foreach ($post['category'] as $category) {
|
||||
echo ' <category term="'.htmlspecialchars($category).'" />'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 本文(要約または全文)
|
||||
echo ' <content type="html">'.$content.'</content>'."\n";
|
||||
echo ' </entry>'."\n";
|
||||
}
|
||||
|
||||
// フィードの終了
|
||||
echo '</feed>';
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フィードの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
<?php
|
||||
namespace Site\Controller;
|
||||
|
||||
class BlogPost {
|
||||
/**
|
||||
* ブログ投稿を取得する
|
||||
*
|
||||
* @return array 投稿の配列
|
||||
*/
|
||||
public function getPosts(string $section): array {
|
||||
$path = ROOT.$section;
|
||||
$posts = [];
|
||||
|
||||
if (!is_dir($path)) return $posts;
|
||||
$files = glob($path.'/*.md');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$content = file_get_contents($file);
|
||||
$parts = explode('----', $content, 2);
|
||||
if (count($parts) != 2) continue;
|
||||
|
||||
$metadata = [];
|
||||
$meta = explode("\n", trim($parts[0]));
|
||||
|
||||
foreach ($meta as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) continue;
|
||||
|
||||
$colonPos = strpos($line, ':');
|
||||
if ($colonPos === false) continue;
|
||||
|
||||
$key = trim(substr($line, 0, $colonPos));
|
||||
$value = trim(substr($line, $colonPos + 1));
|
||||
$value = trim($value, '"\'');
|
||||
|
||||
if ($key == 'category') {
|
||||
$metadata[$key] = array_map('trim', explode(',', $value));
|
||||
} else {
|
||||
$metadata[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$articleBody = trim($parts[1]);
|
||||
$preview = mb_substr(strip_tags($articleBody), 0, 50).'...';
|
||||
$slug = basename($file, '.md');
|
||||
|
||||
$posts[] = [
|
||||
'title' => $metadata['title'] ?? '',
|
||||
'date' => $metadata['date'] ?? '',
|
||||
'thumbnail' => $metadata['thumbnail'] ?? '',
|
||||
'thumborient' => $metadata['thumborient'] ?? '',
|
||||
'category' => $metadata['category'] ?? [],
|
||||
'uuid' => $metadata['uuid'] ?? '',
|
||||
'preview' => $preview,
|
||||
'slug' => $slug,
|
||||
];
|
||||
}
|
||||
|
||||
// 日付でソート(新しい順)
|
||||
usort($posts, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
||||
<?php
|
||||
namespace Site\Controller;
|
||||
|
||||
class BlogPost {
|
||||
/**
|
||||
* ブログ投稿を取得する
|
||||
*
|
||||
* @return array 投稿の配列
|
||||
*/
|
||||
public function getPosts(string $section): array {
|
||||
$path = ROOT.$section;
|
||||
$posts = [];
|
||||
|
||||
if (!is_dir($path)) return $posts;
|
||||
$files = glob($path.'/*.md');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$content = file_get_contents($file);
|
||||
$parts = explode('----', $content, 2);
|
||||
if (count($parts) != 2) continue;
|
||||
|
||||
$metadata = [];
|
||||
$meta = explode("\n", trim($parts[0]));
|
||||
|
||||
foreach ($meta as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) continue;
|
||||
|
||||
$colonPos = strpos($line, ':');
|
||||
if ($colonPos === false) continue;
|
||||
|
||||
$key = trim(substr($line, 0, $colonPos));
|
||||
$value = trim(substr($line, $colonPos + 1));
|
||||
$value = trim($value, '"\'');
|
||||
|
||||
if ($key == 'category') {
|
||||
$metadata[$key] = array_map('trim', explode(',', $value));
|
||||
} else {
|
||||
$metadata[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$articleBody = trim($parts[1]);
|
||||
$preview = mb_substr(strip_tags($articleBody), 0, 50).'...';
|
||||
$slug = basename($file, '.md');
|
||||
|
||||
$posts[] = [
|
||||
'title' => $metadata['title'] ?? '',
|
||||
'date' => $metadata['date'] ?? '',
|
||||
'thumbnail' => $metadata['thumbnail'] ?? '',
|
||||
'thumborient' => $metadata['thumborient'] ?? '',
|
||||
'category' => $metadata['category'] ?? [],
|
||||
'uuid' => $metadata['uuid'] ?? '',
|
||||
'preview' => $preview,
|
||||
'slug' => $slug,
|
||||
];
|
||||
}
|
||||
|
||||
// 日付でソート(新しい順)
|
||||
usort($posts, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +1,151 @@
|
||||
<?php
|
||||
namespace Site\Controller;
|
||||
|
||||
use Site\Controller\BlogPost;
|
||||
use Site\Controller\Mods;
|
||||
use Site\Lib\Activitypub;
|
||||
|
||||
class Fediverse extends BlogPost {
|
||||
use Mods;
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apfinger(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/jrd+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getWebfinger();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apactor(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getActor();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apinbox(array $params): void {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('HTTP/1.1 405 Method Not Allowed');
|
||||
header('Allow: POST');
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = file_get_contents('php://input');
|
||||
$activity = json_decode($input, true);
|
||||
if (!$activity || !isset($activity['type'])) {
|
||||
header('HTTP/1.1 400 Bad Request');
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode(['error' => '不正なアクティビティ']);
|
||||
exit;
|
||||
}
|
||||
|
||||
logger(\LogType::ActivityPub, "受付に入れた:".json_encode($activity));
|
||||
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
$ap->postInbox($activity);
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apactivity(array $params): void {
|
||||
$uuid = '';
|
||||
if (isset($params['uuid'])) $uuid = $params['uuid'];
|
||||
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$posts = $this->getPosts('/blog/');
|
||||
$ap = new Activitypub($posts);
|
||||
echo $ap->getActivity($uuid);
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apoutbox(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$posts = $this->getPosts('/blog/');
|
||||
$ap = new Activitypub($posts);
|
||||
echo $ap->getOutbox();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apfollowers(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getFollowers();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apfollowing(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getFollowing();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php
|
||||
namespace Site\Controller;
|
||||
|
||||
use Site\Controller\BlogPost;
|
||||
use Site\Controller\Mods;
|
||||
use Site\Lib\Activitypub;
|
||||
|
||||
class Fediverse extends BlogPost {
|
||||
use Mods;
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apfinger(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/jrd+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getWebfinger();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apactor(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getActor();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apinbox(array $params): void {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('HTTP/1.1 405 Method Not Allowed');
|
||||
header('Allow: POST');
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = file_get_contents('php://input');
|
||||
$activity = json_decode($input, true);
|
||||
if (!$activity || !isset($activity['type'])) {
|
||||
header('HTTP/1.1 400 Bad Request');
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode(['error' => '不正なアクティビティ']);
|
||||
exit;
|
||||
}
|
||||
|
||||
logger(\LogType::ActivityPub, "受付に入れた:".json_encode($activity));
|
||||
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
$ap->postInbox($activity);
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apactivity(array $params): void {
|
||||
$uuid = '';
|
||||
if (isset($params['uuid'])) $uuid = $params['uuid'];
|
||||
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$posts = $this->getPosts('/blog/');
|
||||
$ap = new Activitypub($posts);
|
||||
echo $ap->getActivity($uuid);
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apoutbox(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$posts = $this->getPosts('/blog/');
|
||||
$ap = new Activitypub($posts);
|
||||
echo $ap->getOutbox();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apfollowers(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getFollowers();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params パラメータ配列
|
||||
* @return void
|
||||
*/
|
||||
public function apfollowing(array $params): void {
|
||||
try {
|
||||
header('Content-Type: application/activity+json');
|
||||
$ap = new Activitypub();
|
||||
echo $ap->getFollowing();
|
||||
exit;
|
||||
} catch (\Exception $e) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'フェディバースの作成に失敗: '.$e->getMessage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +1,130 @@
|
||||
<?php
|
||||
namespace Site\Lib;
|
||||
|
||||
class DiffViewer {
|
||||
private $diffContent;
|
||||
|
||||
public function __construct(string $filePath) {
|
||||
if (!file_exists($filePath)) {
|
||||
throw new \Exception("Diff file not found: $filePath");
|
||||
}
|
||||
$this->diffContent = file_get_contents($filePath);
|
||||
}
|
||||
|
||||
public function displaySideBySide(): string {
|
||||
$lines = explode("\n", $this->diffContent);
|
||||
$fileDiffs = [];
|
||||
$currentFile = null;
|
||||
$hunk = [];
|
||||
$lineNumbers = ['left' => 0, 'right' => 0];
|
||||
$currentLeftLines = [];
|
||||
$currentRightLines = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// ファイルヘッダーの処理
|
||||
if (preg_match('/^---\s+(.+)/', $line, $matches)) {
|
||||
// ファイルを処理する場合、データを保存する
|
||||
if ($currentFile !== null) {
|
||||
$this->processHunk($hunk, $currentLeftLines, $currentRightLines, $lineNumbers);
|
||||
$fileDiffs[$currentFile] = [
|
||||
'leftLines' => $currentLeftLines,
|
||||
'rightLines' => $currentRightLines
|
||||
];
|
||||
$hunk = [];
|
||||
$currentLeftLines = [];
|
||||
$currentRightLines = [];
|
||||
$lineNumbers = ['left' => 0, 'right' => 0];
|
||||
}
|
||||
$currentFile = $matches[1];
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^\+\+\+\s+(.+)/', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ハンクヘッダーの処理 (例:@@ -10,6 +10,7 @@)
|
||||
if (preg_match('/^@@\s+-(\d+),\d+\s+\+(\d+),\d+\s+@@/', $line, $matches)) {
|
||||
$this->processHunk($hunk, $currentLeftLines, $currentRightLines, $lineNumbers);
|
||||
$hunk = [];
|
||||
$lineNumbers['left'] = (int)$matches[1];
|
||||
$lineNumbers['right'] = (int)$matches[2];
|
||||
continue;
|
||||
}
|
||||
|
||||
// ハンクでの行列の集まり
|
||||
if (substr($line, 0, 1) === '-' || substr($line, 0, 1) === '+' || substr($line, 0, 1) === ' ') {
|
||||
$hunk[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
// 最後のハンク・ファイルの処理
|
||||
if ($currentFile !== null) {
|
||||
$this->processHunk($hunk, $currentLeftLines, $currentRightLines, $lineNumbers);
|
||||
$fileDiffs[$currentFile] = [
|
||||
'leftLines' => $currentLeftLines,
|
||||
'rightLines' => $currentRightLines
|
||||
];
|
||||
}
|
||||
|
||||
// 各ファイルにHTMLの出力の作成
|
||||
$html = '';
|
||||
foreach ($fileDiffs as $fileName => $diff) {
|
||||
$html .= "<h2>ファイル: ".htmlspecialchars($fileName)."</h2>\n";
|
||||
$html .= $this->generateHtml($diff['leftLines'], $diff['rightLines']);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function processHunk(array $hunk, array &$leftLines, array &$rightLines, array &$lineNumbers): void {
|
||||
foreach ($hunk as $line) {
|
||||
$prefix = substr($line, 0, 1);
|
||||
$content = substr($line, 1);
|
||||
|
||||
if ($prefix === '-') {
|
||||
$leftLines[] = ['content' => htmlspecialchars($content), 'type' => 'removed', 'line' => $lineNumbers['left']];
|
||||
$lineNumbers['left']++;
|
||||
} elseif ($prefix === '+') {
|
||||
$rightLines[] = ['content' => htmlspecialchars($content), 'type' => 'added', 'line' => $lineNumbers['right']];
|
||||
$lineNumbers['right']++;
|
||||
} elseif ($prefix === ' ') {
|
||||
// 両側のコンテキストは同じ行列があるかの確認
|
||||
while ($lineNumbers['left'] < $lineNumbers['right']) {
|
||||
$leftLines[] = ['content' => '', 'type' => 'empty', 'line' => $lineNumbers['left']];
|
||||
$lineNumbers['left']++;
|
||||
}
|
||||
while ($lineNumbers['right'] < $lineNumbers['left']) {
|
||||
$rightLines[] = ['content' => '', 'type' => 'empty', 'line' => $lineNumbers['right']];
|
||||
$lineNumbers['right']++;
|
||||
}
|
||||
$leftLines[] = ['content' => htmlspecialchars($content), 'type' => 'context', 'line' => $lineNumbers['left']];
|
||||
$rightLines[] = ['content' => htmlspecialchars($content), 'type' => 'context', 'line' => $lineNumbers['right']];
|
||||
$lineNumbers['left']++;
|
||||
$lineNumbers['right']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateHtml(array $leftLines, array $rightLines): string {
|
||||
$html = '<table class="diff-table">';
|
||||
$html .= '<tr class="diff-header"><th colspan="2">前</th><th colspan="2">新</th></tr>';
|
||||
|
||||
$maxLines = max(count($leftLines), count($rightLines));
|
||||
for ($i = 0; $i < $maxLines; $i++) {
|
||||
$left = isset($leftLines[$i]) ? $leftLines[$i] : ['content' => '', 'type' => 'empty', 'line' => ''];
|
||||
$right = isset($rightLines[$i]) ? $rightLines[$i] : ['content' => '', 'type' => 'empty', 'line' => ''];
|
||||
|
||||
$html .= '<tr>';
|
||||
// 左(変更前)
|
||||
$html .= '<td class="line-number">' . ($left['line'] ?: ' ') . '</td>';
|
||||
$html .= '<td class="' . $left['type'] . '">' . ($left['content'] ?: ' ') . '</td>';
|
||||
// 右(変更後)
|
||||
$html .= '<td class="line-number">' . ($right['line'] ?: ' ') . '</td>';
|
||||
$html .= '<td class="' . $right['type'] . '">' . ($right['content'] ?: ' ') . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
$html .= '</table>';
|
||||
return $html;
|
||||
}
|
||||
<?php
|
||||
namespace Site\Lib;
|
||||
|
||||
class DiffViewer {
|
||||
private $diffContent;
|
||||
|
||||
public function __construct(string $filePath) {
|
||||
if (!file_exists($filePath)) {
|
||||
throw new \Exception("Diff file not found: $filePath");
|
||||
}
|
||||
$this->diffContent = file_get_contents($filePath);
|
||||
}
|
||||
|
||||
public function displaySideBySide(): string {
|
||||
$lines = explode("\n", $this->diffContent);
|
||||
$fileDiffs = [];
|
||||
$currentFile = null;
|
||||
$hunk = [];
|
||||
$lineNumbers = ['left' => 0, 'right' => 0];
|
||||
$currentLeftLines = [];
|
||||
$currentRightLines = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// ファイルヘッダーの処理
|
||||
if (preg_match('/^---\s+(.+)/', $line, $matches)) {
|
||||
// ファイルを処理する場合、データを保存する
|
||||
if ($currentFile !== null) {
|
||||
$this->processHunk($hunk, $currentLeftLines, $currentRightLines, $lineNumbers);
|
||||
$fileDiffs[$currentFile] = [
|
||||
'leftLines' => $currentLeftLines,
|
||||
'rightLines' => $currentRightLines
|
||||
];
|
||||
$hunk = [];
|
||||
$currentLeftLines = [];
|
||||
$currentRightLines = [];
|
||||
$lineNumbers = ['left' => 0, 'right' => 0];
|
||||
}
|
||||
$currentFile = $matches[1];
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^\+\+\+\s+(.+)/', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ハンクヘッダーの処理 (例:@@ -10,6 +10,7 @@)
|
||||
if (preg_match('/^@@\s+-(\d+),\d+\s+\+(\d+),\d+\s+@@/', $line, $matches)) {
|
||||
$this->processHunk($hunk, $currentLeftLines, $currentRightLines, $lineNumbers);
|
||||
$hunk = [];
|
||||
$lineNumbers['left'] = (int)$matches[1];
|
||||
$lineNumbers['right'] = (int)$matches[2];
|
||||
continue;
|
||||
}
|
||||
|
||||
// ハンクでの行列の集まり
|
||||
if (substr($line, 0, 1) === '-' || substr($line, 0, 1) === '+' || substr($line, 0, 1) === ' ') {
|
||||
$hunk[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
// 最後のハンク・ファイルの処理
|
||||
if ($currentFile !== null) {
|
||||
$this->processHunk($hunk, $currentLeftLines, $currentRightLines, $lineNumbers);
|
||||
$fileDiffs[$currentFile] = [
|
||||
'leftLines' => $currentLeftLines,
|
||||
'rightLines' => $currentRightLines
|
||||
];
|
||||
}
|
||||
|
||||
// 各ファイルにHTMLの出力の作成
|
||||
$html = '';
|
||||
foreach ($fileDiffs as $fileName => $diff) {
|
||||
$html .= "<h2>ファイル: ".htmlspecialchars($fileName)."</h2>\n";
|
||||
$html .= $this->generateHtml($diff['leftLines'], $diff['rightLines']);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function processHunk(array $hunk, array &$leftLines, array &$rightLines, array &$lineNumbers): void {
|
||||
foreach ($hunk as $line) {
|
||||
$prefix = substr($line, 0, 1);
|
||||
$content = substr($line, 1);
|
||||
|
||||
if ($prefix === '-') {
|
||||
$leftLines[] = ['content' => htmlspecialchars($content), 'type' => 'removed', 'line' => $lineNumbers['left']];
|
||||
$lineNumbers['left']++;
|
||||
} elseif ($prefix === '+') {
|
||||
$rightLines[] = ['content' => htmlspecialchars($content), 'type' => 'added', 'line' => $lineNumbers['right']];
|
||||
$lineNumbers['right']++;
|
||||
} elseif ($prefix === ' ') {
|
||||
// 両側のコンテキストは同じ行列があるかの確認
|
||||
while ($lineNumbers['left'] < $lineNumbers['right']) {
|
||||
$leftLines[] = ['content' => '', 'type' => 'empty', 'line' => $lineNumbers['left']];
|
||||
$lineNumbers['left']++;
|
||||
}
|
||||
while ($lineNumbers['right'] < $lineNumbers['left']) {
|
||||
$rightLines[] = ['content' => '', 'type' => 'empty', 'line' => $lineNumbers['right']];
|
||||
$lineNumbers['right']++;
|
||||
}
|
||||
$leftLines[] = ['content' => htmlspecialchars($content), 'type' => 'context', 'line' => $lineNumbers['left']];
|
||||
$rightLines[] = ['content' => htmlspecialchars($content), 'type' => 'context', 'line' => $lineNumbers['right']];
|
||||
$lineNumbers['left']++;
|
||||
$lineNumbers['right']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateHtml(array $leftLines, array $rightLines): string {
|
||||
$html = '<table class="diff-table">';
|
||||
$html .= '<tr class="diff-header"><th colspan="2">前</th><th colspan="2">新</th></tr>';
|
||||
|
||||
$maxLines = max(count($leftLines), count($rightLines));
|
||||
for ($i = 0; $i < $maxLines; $i++) {
|
||||
$left = isset($leftLines[$i]) ? $leftLines[$i] : ['content' => '', 'type' => 'empty', 'line' => ''];
|
||||
$right = isset($rightLines[$i]) ? $rightLines[$i] : ['content' => '', 'type' => 'empty', 'line' => ''];
|
||||
|
||||
$html .= '<tr>';
|
||||
// 左(変更前)
|
||||
$html .= '<td class="line-number">' . ($left['line'] ?: ' ') . '</td>';
|
||||
$html .= '<td class="' . $left['type'] . '">' . ($left['content'] ?: ' ') . '</td>';
|
||||
// 右(変更後)
|
||||
$html .= '<td class="line-number">' . ($right['line'] ?: ' ') . '</td>';
|
||||
$html .= '<td class="' . $right['type'] . '">' . ($right['content'] ?: ' ') . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
$html .= '</table>';
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,64 @@
|
||||
<?php
|
||||
namespace Site\Test;
|
||||
|
||||
require_once __DIR__.'/../../../autoload.php';
|
||||
|
||||
use Site\Lib\Tester;
|
||||
use Site\Lib\Mysql;
|
||||
|
||||
$test = new Tester([
|
||||
'colorOutput' => true,
|
||||
'verboseOutput' => true
|
||||
]);
|
||||
|
||||
$test->describe('パケットのデバッグ', function($test): void {
|
||||
try {
|
||||
$db = new Mysql();
|
||||
|
||||
$db->setDebug(true);
|
||||
$db->connect();
|
||||
|
||||
$result = $db->query('SELECT * FROM user WHERE id = 1');
|
||||
|
||||
foreach ($result['rows'] as $row) {
|
||||
echo "ユーザー名: ".$row['nickname']."\n";
|
||||
}
|
||||
|
||||
$db->savePacketLogToFile('mysql_log.txt');
|
||||
$db->close();
|
||||
} catch (\Exception $e) {
|
||||
echo 'エラー: '.$e->getMessage()."\n";
|
||||
}
|
||||
});
|
||||
|
||||
$test->describe('プリペアドステートメント', function($test): void {
|
||||
try {
|
||||
$db = new Mysql();
|
||||
$db->connect();
|
||||
|
||||
// データの入り
|
||||
$stmt = $db->prepare('INSERT INTO users (name, age) VALUES (?, ?)');
|
||||
$test->assertTrue($stmt);
|
||||
|
||||
$db->execute($stmt, ['山田太郎', 25]);
|
||||
// TODO: assert
|
||||
|
||||
$close = $db->demolish($stmt);
|
||||
$this->assertTrue($close);
|
||||
|
||||
// データの受け取り
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE age > ?');
|
||||
$test->assertTrue($stmt);
|
||||
|
||||
$res = $db->execute($stmt, [20]);
|
||||
// TODO: assert
|
||||
print_r($res);
|
||||
|
||||
$close = $db->demolish($stmt);
|
||||
$this->assertTrue($close);
|
||||
|
||||
$db->close();
|
||||
} catch (\Exception $e) {
|
||||
echo 'エラー: '.$e->getMessage()."\n";
|
||||
}
|
||||
<?php
|
||||
namespace Site\Test;
|
||||
|
||||
require_once __DIR__.'/../../../autoload.php';
|
||||
|
||||
use Site\Lib\Tester;
|
||||
use Site\Lib\Mysql;
|
||||
|
||||
$test = new Tester([
|
||||
'colorOutput' => true,
|
||||
'verboseOutput' => true
|
||||
]);
|
||||
|
||||
$test->describe('パケットのデバッグ', function($test): void {
|
||||
try {
|
||||
$db = new Mysql();
|
||||
|
||||
$db->setDebug(true);
|
||||
$db->connect();
|
||||
|
||||
$result = $db->query('SELECT * FROM user WHERE id = 1');
|
||||
|
||||
foreach ($result['rows'] as $row) {
|
||||
echo "ユーザー名: ".$row['nickname']."\n";
|
||||
}
|
||||
|
||||
$db->savePacketLogToFile('mysql_log.txt');
|
||||
$db->close();
|
||||
} catch (\Exception $e) {
|
||||
echo 'エラー: '.$e->getMessage()."\n";
|
||||
}
|
||||
});
|
||||
|
||||
$test->describe('プリペアドステートメント', function($test): void {
|
||||
try {
|
||||
$db = new Mysql();
|
||||
$db->connect();
|
||||
|
||||
// データの入り
|
||||
$stmt = $db->prepare('INSERT INTO users (name, age) VALUES (?, ?)');
|
||||
$test->assertTrue($stmt);
|
||||
|
||||
$db->execute($stmt, ['山田太郎', 25]);
|
||||
// TODO: assert
|
||||
|
||||
$close = $db->demolish($stmt);
|
||||
$this->assertTrue($close);
|
||||
|
||||
// データの受け取り
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE age > ?');
|
||||
$test->assertTrue($stmt);
|
||||
|
||||
$res = $db->execute($stmt, [20]);
|
||||
// TODO: assert
|
||||
print_r($res);
|
||||
|
||||
$close = $db->demolish($stmt);
|
||||
$this->assertTrue($close);
|
||||
|
||||
$db->close();
|
||||
} catch (\Exception $e) {
|
||||
echo 'エラー: '.$e->getMessage()."\n";
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user