色々修正
このコミットが含まれているのは:
コミット
8ef1dae28a
191
common.php
191
common.php
|
@ -1,10 +1,13 @@
|
|||
<?php
|
||||
// 初期化
|
||||
|
||||
if (BIBIS_DEBUG) {
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
// 定数
|
||||
define('GUEST_ID', '-');
|
||||
define('NOREPLY_ID', '-');
|
||||
define('GUESTNAME', 'GUEST');
|
||||
|
@ -13,199 +16,15 @@ define('DELETEDTEXT', 'この書き込みは削除されました。');
|
|||
define('ACCEPT_IMAGE_TYPE', ['image/png', 'image/jpeg', 'image/gif']);
|
||||
define('SESSION_NAME', 'bibis');
|
||||
|
||||
// PHP の設定
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
ini_set('zlib.output_compression', 1);
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
|
||||
if (OPEN_BASEDIR != '') {
|
||||
ini_set('open_basedir', OPEN_BASEDIR);
|
||||
}
|
||||
|
||||
// セッション開始
|
||||
session_name(SESSION_NAME);
|
||||
session_start(['cookie_httponly' => true]);
|
||||
set_csrf_token();
|
||||
|
||||
// HTTP
|
||||
|
||||
function get_fp() {
|
||||
return md5(
|
||||
($_SERVER['HTTP_USER_AGENT'] ?? '')
|
||||
. ($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')
|
||||
. ($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '')
|
||||
. ($_SERVER['HTTP_ACCEPT'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
function has_cookie() {
|
||||
return isset($_COOKIE['bibis']);
|
||||
}
|
||||
|
||||
function on_error($code, $errors) {
|
||||
http_response_code(400);
|
||||
if (function_exists('bibis_http_header')) { bibis_http_header(); }
|
||||
|
||||
$view['errors'] = $errors;
|
||||
require(__DIR__ . '/view/header.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
function output_html($view, $_components) {
|
||||
if (function_exists('bibis_http_header')) { bibis_http_header(); }
|
||||
|
||||
foreach ($_components as $_name) {
|
||||
require(__DIR__ . "/view/$_name");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// CSRF token
|
||||
|
||||
function set_csrf_token() {
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
|
||||
function get_csrf_token() {
|
||||
if (!REQUIRE_COOKIE && !isset($_SESSION['user'])) {
|
||||
$date = date('YmdH');
|
||||
return $date . get_fp();
|
||||
}
|
||||
return $_SESSION['csrf_token'] ?? null;
|
||||
|
||||
}
|
||||
|
||||
function get_csrf_token_2($force_cookie = false) {
|
||||
if (!REQUIRE_COOKIE && !isset($_SESSION['user'])) {
|
||||
$date = date('YmdH', strtotime('-1 hour'));
|
||||
return $date . get_fp();
|
||||
}
|
||||
return $_SESSION['csrf_token'] ?? null;
|
||||
}
|
||||
|
||||
function get_csrf_token_hashed($prefix = '', $token = null) {
|
||||
if ($token === null) { $token = get_csrf_token(); }
|
||||
if ($token === null) { return null; }
|
||||
return md5($prefix . $token);
|
||||
}
|
||||
|
||||
function check_csrf_token() {
|
||||
$hashed = get_csrf_token_hashed();
|
||||
$hashed_2 = get_csrf_token_hashed('', get_csrf_token_2());
|
||||
if ($hashed === null || $hashed_2 === null) { return ['CSRF トークンが不正。要再試行。']; }
|
||||
$token_key = get_csrf_token_hashed('csrf_token');
|
||||
$csrf_token = $_POST[$token_key] ?? '';
|
||||
if ($csrf_token !== $hashed && $csrf_token !== $hashed_2) { return ['CSRF トークンが不正。要再試行。']; }
|
||||
return [];
|
||||
}
|
||||
|
||||
function output_csrf_token_hidden() {
|
||||
$hashed = get_csrf_token_hashed();
|
||||
if ($hashed === null) { return ''; }
|
||||
$token_key = get_csrf_token_hashed('csrf_token');
|
||||
return '<input type="hidden" name="' . htmlspecialchars($token_key) . '" value="' . $hashed . '">';
|
||||
}
|
||||
|
||||
// String
|
||||
|
||||
function anchor_to_link($s, $thread_id) {
|
||||
return preg_replace(
|
||||
'/>>([1-9]+[0-9]{0,10})/',
|
||||
'<a href="' . sitebase('post/?id=') . $thread_id . '&res=\1">>>\1</a>',
|
||||
$s
|
||||
);
|
||||
}
|
||||
|
||||
function url_to_link($s) {
|
||||
return preg_replace(
|
||||
'/((http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?)/',
|
||||
'<a href="\1" rel="noopener noreferrer">\1</a>',
|
||||
$s
|
||||
);
|
||||
}
|
||||
|
||||
function mbtrim($s) {
|
||||
return preg_replace('/^\s+|\s+$/u', '', $s);
|
||||
}
|
||||
|
||||
function twochan_trip($tripkey) {
|
||||
|
||||
// twochan_trip('atomikka') -> '◆.../5Betyws'
|
||||
// Thanks: https://refirio.org/view/62
|
||||
// Test data: https://trip-table.kokage.cc/sample01.php
|
||||
|
||||
$tripkey = mb_convert_encoding($tripkey, 'sjis-win');
|
||||
$salt = substr($tripkey . 'H.', 1, 2);
|
||||
$salt = preg_replace('/[^\.-z]/', '.', $salt);
|
||||
$salt = strtr($salt, ':;<=>?@[\\]^_`', 'ABCDEFGabcdef');
|
||||
|
||||
$trip = crypt($tripkey, $salt);
|
||||
$trip = substr($trip, -10);
|
||||
|
||||
return '◆' . $trip;
|
||||
}
|
||||
|
||||
function parse_key_value($s, $key_list) {
|
||||
$result = [];
|
||||
$lines = explode(PHP_EOL, $s);
|
||||
foreach ($lines as $line) {
|
||||
foreach ($key_list as $key) {
|
||||
if (strpos($line, "{$key}=") === 0) {
|
||||
$value = explode("{$key}=", $line, 2)[1] ?? null;
|
||||
$result[$key] = $value;
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
$key = null;
|
||||
}
|
||||
$lines = null;
|
||||
$line = null;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// File
|
||||
|
||||
function mkdir_p($path, $permission = 0700) {
|
||||
if (file_exists($path)) { return true; }
|
||||
return mkdir($path, $permission);
|
||||
}
|
||||
|
||||
function get_image_type($s) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$type = finfo_buffer($finfo, $s);
|
||||
if (isset($type) && in_array($type, ACCEPT_IMAGE_TYPE)) {
|
||||
return $type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function sitebase($path = '') {
|
||||
return SITEBASE . $path;
|
||||
}
|
||||
|
||||
function get_style_css() {
|
||||
if (THEME === null || THEME === '') { return sitebase('style.css'); }
|
||||
return sitebase('theme/' . THEME);
|
||||
}
|
||||
|
||||
// Limit and permission
|
||||
|
||||
function is_logged_in() {
|
||||
return isset($_SESSION['user']);
|
||||
}
|
||||
|
||||
function can_post() {
|
||||
if (!post_limited()) { return false; }
|
||||
if (!ENABLE_GUEST && !is_logged_in()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
function post_limited() {
|
||||
return ENABLE_POST && (count_post_today() < POST_LIMIT_PER_DAY) && (count_post() < POST_LIMIT);
|
||||
}
|
||||
|
||||
function can_regist() {
|
||||
return ENABLE_REGISTER;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
// require: PASSWORD_SOLT, and PASSWORD_ITER, and DATA_ROOT
|
||||
// config.php の項目に対するデフォルト値を定義する
|
||||
// ただし次の項目は必須: PASSWORD_SOLT, and PASSWORD_ITER, and DATA_ROOT
|
||||
|
||||
default_config('OPEN_BASEDIR', null);
|
||||
default_config('USERS_TSV', DATA_ROOT . 'users.tsv');
|
||||
|
|
181
data-post.php
181
data-post.php
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Data controller (post section)
|
||||
// 投稿 (Post) 関連の関数
|
||||
|
||||
// [投稿ファイルについて]
|
||||
// 投稿ファイルは POST_DIR (data/post) フォルダーに保存される。
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
// [添附ファイルについて]
|
||||
// 添附ファイルは ATTACHMENT_DIR (data/attachment) フォルダーに gzip 形式で保存される。
|
||||
// ファイル名の形式: {id}.gz
|
||||
/// ファイル名の形式: {id}.gz
|
||||
// idにはランダムな英數字が入る。
|
||||
|
||||
// [聯投防止について]
|
||||
|
@ -37,21 +37,20 @@
|
|||
$post_title_cache = [];
|
||||
|
||||
function load_postfile($filepath) {
|
||||
$text = file_get_contents($filepath);
|
||||
|
||||
$text = file_get_contents($filepath);
|
||||
$split = preg_split('/^-$/m', $text, 2);
|
||||
$text = null;
|
||||
unset($text);
|
||||
|
||||
$head = mbtrim($split[0]);
|
||||
$body = mbtrim($split[1] ?? '');
|
||||
$split = null;
|
||||
unset($split);
|
||||
|
||||
$parsed = parse_key_value($head, ['title', 'attachment_id']);
|
||||
$head = null;
|
||||
unset($head);
|
||||
|
||||
$title = $parsed['title'] ?? '';
|
||||
$attachment_id = $parsed['attachment_id'] ?? '';
|
||||
$parsed = null;
|
||||
unset($parsed);
|
||||
|
||||
$deleted = $body === '';
|
||||
if ($deleted) {
|
||||
|
@ -75,8 +74,7 @@ function get_post_metadata($file, $deleted = false) {
|
|||
$format = 'Y-m-d';
|
||||
if (!HIDE_TIME || $is_future) { $format .= ' H:i'; }
|
||||
$time = $date->format($format);
|
||||
$format = null;
|
||||
$date = null;
|
||||
unset($format, $date);
|
||||
|
||||
// TODO:これだとファイル名がマッチしない時に予期せぬ結果になる
|
||||
|
||||
|
@ -88,22 +86,18 @@ function get_post_metadata($file, $deleted = false) {
|
|||
if ($thread_id === NOREPLY_ID) { $thread_id = ''; }
|
||||
|
||||
// 投稿ユーザーID: ".us{$id}"
|
||||
$userid = '';
|
||||
$userid = GUEST_ID;
|
||||
if (!$deleted) {
|
||||
$userid = preg_replace('/.+\.us([^.]+)\..+/', '\1', $file);
|
||||
}
|
||||
if ($userid === GUEST_ID) {
|
||||
$is_guest = $userid === GUEST_ID;
|
||||
if ($is_guest) {
|
||||
$userid = '';
|
||||
}
|
||||
$is_guest = $userid === '';
|
||||
|
||||
$thread_title = '';
|
||||
if ($thread_id != '') {
|
||||
if ($thread_id != '') {
|
||||
$thread_title = load_post_title_by_id($thread_id);
|
||||
}
|
||||
|
||||
if ($thread_id != '' && $thread_title == '') {
|
||||
$thread_title = '無題#' . mb_substr($thread_id, 0, 7);
|
||||
}
|
||||
|
||||
$detail_url = sitebase('post/?id=' . $id);
|
||||
|
@ -123,18 +117,18 @@ function load_res_num($id) {
|
|||
$file = basename($filepath);
|
||||
$meta = get_post_metadata($file);
|
||||
$thread_id = $meta['thread_id'];
|
||||
$meta = null;
|
||||
unset($meta);
|
||||
if ($thread_id <= '') { return 1; }
|
||||
|
||||
$pattern = "2*Z*.th{$thread_id}.txt";
|
||||
$list = glob(POST_DIR . $pattern);
|
||||
$search = ".id{$id}.";
|
||||
$i = 0;
|
||||
foreach ($list as $i => $filepath) {
|
||||
foreach ($list as $i => $filepath) {
|
||||
if (strpos(basename($filepath), $search) > 0) {
|
||||
return $i + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // fallback
|
||||
}
|
||||
|
@ -147,14 +141,16 @@ function load_post($filepath) {
|
|||
$title = $detail['title'];
|
||||
$body_raw = $detail['body'];
|
||||
$attachment_id = $detail['attachment_id'];
|
||||
$detail = null;
|
||||
unset($detail);
|
||||
|
||||
$metadata = get_post_metadata(basename($filepath), $deleted);
|
||||
if ($metadata['thread_id'] == '' && $title == '') {
|
||||
$title = post_default_title($metadata['id']);
|
||||
}
|
||||
$userid = $metadata['userid'];
|
||||
|
||||
$users = load_users();
|
||||
$username = GUESTNAME;
|
||||
|
||||
$username = GUESTNAME;
|
||||
if ($deleted) {
|
||||
$username = DELETEDNAME;
|
||||
}
|
||||
|
@ -193,9 +189,9 @@ function load_post_by_id($id) {
|
|||
}
|
||||
|
||||
function search_post($options = []) {
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
|
||||
$includes_future = $options['includes_future'] ?? false;
|
||||
$pagesize = $options['pagesize'] ?? 0;
|
||||
|
@ -206,7 +202,7 @@ function search_post($options = []) {
|
|||
$key = $options['key'] ?? '';
|
||||
$value = $options['value'] ?? '';
|
||||
$page = $options['page'] ?? 1;
|
||||
$options = null;
|
||||
unset($options);
|
||||
|
||||
$pattern = '2*Z*.txt';
|
||||
if ($key === 'userid' && $value != '' && $value !== 'tl') {
|
||||
|
@ -217,9 +213,7 @@ function search_post($options = []) {
|
|||
$pattern = "2*Z*.th{$value}.txt";
|
||||
$thread = true;
|
||||
}
|
||||
|
||||
$key = null;
|
||||
$value = null;
|
||||
unset($key, $value);
|
||||
|
||||
$now = date('Y-m-d\\TH:i:s\\Z');
|
||||
$files = glob(POST_DIR . $pattern);
|
||||
|
@ -243,8 +237,7 @@ function search_post($options = []) {
|
|||
else {
|
||||
$last_page = 0;
|
||||
}
|
||||
if ($page < 1) { $page = 1; }
|
||||
if ($page > $last_page) { $page = $last_page; }
|
||||
$page = max(1, min($last_page, $page));
|
||||
|
||||
if ($has_paging) {
|
||||
$tail = array_slice($files, ($page - 1) * $pagesize, $pagesize);
|
||||
|
@ -252,14 +245,13 @@ function search_post($options = []) {
|
|||
else {
|
||||
$tail = $files;
|
||||
}
|
||||
$files = null;
|
||||
unset($files);
|
||||
|
||||
$post_list = [];
|
||||
foreach ($tail as $filepath) {
|
||||
$post_list[] = load_post($filepath);
|
||||
}
|
||||
$filepath = null;
|
||||
$tail = null;
|
||||
unset($filepath, $tail);
|
||||
|
||||
$has_pager = $last_page > 1;
|
||||
$pager = [];
|
||||
|
@ -310,12 +302,14 @@ function add_post($userid, $title, $body, $attachment_id, $file_hash, &$post_id_
|
|||
if ($error) { return ['すでに同じ画像有り(削除済みを含む)。連投防止。']; }
|
||||
}
|
||||
|
||||
if (!mkdir_p(POST_DIR)) {
|
||||
die("mkdir_pを実行に失敗しました。");
|
||||
}
|
||||
if (!mkdir_p(POST_DIR)) {
|
||||
die('mkdir_pを実行に失敗しました。');
|
||||
}
|
||||
|
||||
$result = file_put_contents(POST_DIR . $file, $text, LOCK_EX);
|
||||
if ($result === false) { return ['書き込みの保存に失敗。']; }
|
||||
if ($result === false) {
|
||||
die('書き込みの保存に失敗しました。');
|
||||
}
|
||||
|
||||
touch(POST_DIR . $file, $timestamp, $timestamp);
|
||||
chmod(POST_DIR . $file, 0644);
|
||||
|
@ -334,11 +328,11 @@ function add_post($userid, $title, $body, $attachment_id, $file_hash, &$post_id_
|
|||
function check_uploaded_image($key) {
|
||||
$size = $_FILES[$key]['size'] ?? 0;
|
||||
if ($size <= 0) { return ['画像ファイルが不正。']; }
|
||||
else if ($size > 1024 * 500) { ['画像ファイルは 500 kb 以内。']; }
|
||||
else if ($size > 1024 * 500) { ['画像ファイルは 500 kb 以内。']; }
|
||||
|
||||
$buffer = file_get_contents($_FILES[$key]['tmp_name']);
|
||||
$type = get_image_type($buffer);
|
||||
$buffer = null;
|
||||
unset($buffer);
|
||||
if (!isset($type)) { return ['画像ファイルが不正。']; }
|
||||
|
||||
return [];
|
||||
|
@ -350,14 +344,19 @@ function save_uploaded_image($key, $attachment_id) {
|
|||
if (!isset($type)) { on_error(400, ['画像ファイルが不正。']); }
|
||||
|
||||
if (!$result = mkdir_p(ATTACHMENT_DIR, 0700)) {
|
||||
return ['フォルダの作成に失敗。'];
|
||||
}
|
||||
return ['フォルダの作成に失敗。'];
|
||||
}
|
||||
|
||||
$target = ATTACHMENT_DIR . $attachment_id . '.gz';
|
||||
if (!$gz = gzopen($target, 'w1')) { return ['画像の書き込みに失敗。']; }
|
||||
if (!$gz = gzopen($target, 'w1')) {
|
||||
die('画像の書き込みに失敗しました。');
|
||||
}
|
||||
|
||||
$result = gzwrite($gz, $buffer);
|
||||
if ($result === false) { return ['画像の書き込みに失敗。']; }
|
||||
if ($result === false) {
|
||||
die('画像の書き込みに失敗しました。');
|
||||
}
|
||||
|
||||
gzclose($gz);
|
||||
chmod($target, 0777);
|
||||
|
||||
|
@ -369,7 +368,7 @@ function delete_post($id, $hard = false) {
|
|||
if (sizeof($files) !== 1) { return ['書き込みのファイルが存在しない。']; }
|
||||
|
||||
$filepath = $files[0];
|
||||
$files = null;
|
||||
unset($files);
|
||||
|
||||
$attachment_id = load_postfile($filepath)['attachment_id'];
|
||||
|
||||
|
@ -378,8 +377,9 @@ function delete_post($id, $hard = false) {
|
|||
if ($attachment_id) {
|
||||
$attachment_path = ATTACHMENT_DIR . $attachment_id . '.gz';
|
||||
if (file_exists($attachment_path)) {
|
||||
$result = unlink($attachment_path);
|
||||
if (!$result) { return ['ファイルの削除に失敗。']; }
|
||||
if (!unlink($attachment_path)) {
|
||||
die('ファイルの削除に失敗。');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,7 +388,9 @@ function delete_post($id, $hard = false) {
|
|||
} else {
|
||||
$result = file_put_contents($filepath, '', LOCK_EX);
|
||||
}
|
||||
if ($result === false) { return ['書き込みの削除に失敗。']; }
|
||||
if ($result === false) {
|
||||
return die('書き込みの削除に失敗。');
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
@ -403,7 +405,8 @@ function load_threads() {
|
|||
$metadata = get_post_metadata(basename($filepath));
|
||||
$is_future = $metadata['is_future'];
|
||||
$thread_id = $metadata['thread_id'];
|
||||
$metadata = null;
|
||||
unset($metadata);
|
||||
|
||||
if ($is_future) { continue; }
|
||||
if ($thread_id === '') { continue; }
|
||||
|
||||
|
@ -416,12 +419,7 @@ function load_threads() {
|
|||
if ($parent['deleted']) { continue; }
|
||||
|
||||
$thread = ['count' => 2];
|
||||
if (($parent['title'] ?? '') != '') {
|
||||
$thread['title'] = $parent['title'];
|
||||
}
|
||||
else {
|
||||
$thread['title'] = '無題#' . mb_substr($thread_id, 0, 7);
|
||||
}
|
||||
$thread['title'] = $parent['title'] ?? '';
|
||||
$thread['detail_url'] = $parent['detail_url'];
|
||||
|
||||
$text = preg_replace("/[\n\r]+/", ' ', $parent['body_raw']);
|
||||
|
@ -429,36 +427,34 @@ function load_threads() {
|
|||
if (mb_strlen($text) > 30) {
|
||||
$thread['sammary'] .= '(...)';
|
||||
}
|
||||
$text = null;
|
||||
unset($text);
|
||||
|
||||
$threads[$thread_id] = $thread;
|
||||
$thread = null;
|
||||
unset($thread);
|
||||
}
|
||||
$files = null;
|
||||
$filepath = null;
|
||||
unset($files, $filepath);
|
||||
|
||||
return $threads;
|
||||
}
|
||||
|
||||
function make_repeating_info($body, $datetime, $file_hash = '') {
|
||||
if ($file_hash != '') {
|
||||
return ['hash' => $file_fash, 'limit' => mb_substr($datetime, 0, 10)];
|
||||
if ($file_hash != '') {
|
||||
return ['hash' => $file_fash, 'limit' => mb_substr($datetime, 0, 10)];
|
||||
}
|
||||
|
||||
$letter_only = preg_replace('/[^\p{Ll}\p{Lt}\p{Lo}\p{Lu}\p{N}]/u', '', $body);
|
||||
if (mb_strlen($letter_only) > 0) { $body = $letter_only; }
|
||||
|
||||
$hash = md5($body);
|
||||
|
||||
$len = mb_strlen($body);
|
||||
if ($len <= 2) { $size = 16; }
|
||||
elseif ($len <= 30) { $size = 13; }
|
||||
elseif ($len <= 40) { $size = 10; }
|
||||
else { $size = 7; }
|
||||
$limit = mb_substr($datetime, 0, $size);
|
||||
|
||||
return [
|
||||
'hash' => $hash,
|
||||
'limit' => mb_substr($datetime, 0, $size),
|
||||
];
|
||||
return compact('hash', 'limit');
|
||||
}
|
||||
|
||||
function add_repeating_info($repeating_info) {
|
||||
|
@ -470,14 +466,14 @@ function add_repeating_info($repeating_info) {
|
|||
}
|
||||
|
||||
function check_repeating_info($body, $datetime, $file_hash = '') {
|
||||
if (!file_exists(LIMIT_TSV)) {
|
||||
die(LIMIT_TSV.": ファイルを見つけられません。");
|
||||
}
|
||||
|
||||
if (!file_exists(LIMIT_TSV)) {
|
||||
touch(LIMIT_TSV, time());
|
||||
chmod(LIMIT_TSV, 0644);
|
||||
}
|
||||
$hash = make_repeating_info($body, $datetime, $file_hash)['hash'];
|
||||
$text = file_get_contents(LIMIT_TSV) ?? '';
|
||||
$lines = explode(PHP_EOL, $text);
|
||||
$text = null;
|
||||
unset($text);
|
||||
|
||||
$out = '';
|
||||
$count = 0;
|
||||
|
@ -492,9 +488,10 @@ function check_repeating_info($body, $datetime, $file_hash = '') {
|
|||
$count += 1;
|
||||
}
|
||||
}
|
||||
unset($array);
|
||||
unset($line);
|
||||
}
|
||||
|
||||
unset($array);
|
||||
unset($line);
|
||||
}
|
||||
|
||||
unset($lines);
|
||||
|
||||
|
@ -506,28 +503,28 @@ function check_repeating_info($body, $datetime, $file_hash = '') {
|
|||
// Util
|
||||
|
||||
function count_post() {
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
|
||||
$pattern = '2*Z*.txt';
|
||||
$files = glob(POST_DIR . '2*Z*.txt');
|
||||
$size = sizeof($files);
|
||||
$files = null;
|
||||
unset($files);
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
function count_post_today() {
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
|
||||
$date = date('Y-m-d');
|
||||
$pattern = "{$date}T*Z.*.txt";
|
||||
$files = glob(POST_DIR . $pattern);
|
||||
$size = sizeof($files);
|
||||
$files = null;
|
||||
unset($files);
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
@ -545,9 +542,9 @@ function filepath_by_post_id($id) {
|
|||
}
|
||||
|
||||
function load_post_title_by_id($id) {
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
if (!file_exists(POST_DIR)) {
|
||||
die(POST_DIR.': ディレクトリは存在しません。');
|
||||
}
|
||||
|
||||
global $post_title_cache;
|
||||
|
||||
|
@ -555,14 +552,18 @@ function load_post_title_by_id($id) {
|
|||
|
||||
$files = glob(POST_DIR . '2*Z*.id' . $id . '.*.txt');
|
||||
if (sizeof($files) != 1) {
|
||||
$files = null;
|
||||
unset($files);
|
||||
$post_title_cache[$id] = '';
|
||||
return '';
|
||||
}
|
||||
|
||||
$filepath = $files[0];
|
||||
$files = null;
|
||||
$post_title_cache[$id] = load_postfile($filepath)['title'] ?? '';
|
||||
unset($files);
|
||||
$title = load_postfile($filepath)['title'] ?? '';
|
||||
if ($title === '') {
|
||||
$title = post_default_title($id);
|
||||
}
|
||||
$post_title_cache[$id] = $title;
|
||||
|
||||
return $post_title_cache[$id];
|
||||
return $title;
|
||||
}
|
||||
|
|
79
data.php
79
data.php
|
@ -1,11 +1,11 @@
|
|||
<?php
|
||||
// Data controller
|
||||
// データ関連の関数
|
||||
|
||||
// Post
|
||||
// 投稿 (Post)
|
||||
|
||||
require_once(__DIR__ . '/data-post.php');
|
||||
|
||||
// User
|
||||
// 利用者 (User)
|
||||
|
||||
// ユーザー情報は USER_TSV (data/user.tsv) に保存される。
|
||||
// 内容はタブ区切り・ヘッダー行は無い。
|
||||
|
@ -31,7 +31,7 @@ function load_users() {
|
|||
$users_cache[$user['id']] = $user;
|
||||
unset($user);
|
||||
}
|
||||
unset($rows);
|
||||
unset($rows, $row);
|
||||
|
||||
return $users_cache;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ function load_users_with_special() {
|
|||
function update_user($user) {
|
||||
// 注意:ここを変更したら、必ず、ユーザーが消えない事をテストすること。
|
||||
|
||||
if (!$user) { return ['ユーザーが不正。']; }
|
||||
if (!$user) { return ['ユーザーが不正。']; }
|
||||
if (!isset($user['id'])) { return ['ユーザーIDが不正。']; }
|
||||
$id = $user['id'];
|
||||
$username = $user['username'];
|
||||
|
@ -59,7 +59,7 @@ function update_user($user) {
|
|||
if ($prev === false) { $prev = ''; }
|
||||
|
||||
$rows = explode(PHP_EOL, $prev);
|
||||
$prev = null;
|
||||
unset($prev);
|
||||
|
||||
$output = '';
|
||||
$search_id = $id . "\t";
|
||||
|
@ -103,9 +103,9 @@ function update_user($user) {
|
|||
}
|
||||
|
||||
function add_user($user) {
|
||||
if (!file_exists(USERS_TSV)) {
|
||||
die(USERS_TSV.': ファイルを見つけられません。');
|
||||
}
|
||||
if (!file_exists(USERS_TSV)) {
|
||||
die(USERS_TSV.': ファイルを見つけられません。');
|
||||
}
|
||||
|
||||
$id = $user['id'];
|
||||
$username = $user['username'];
|
||||
|
@ -116,37 +116,40 @@ function add_user($user) {
|
|||
unset($match);
|
||||
|
||||
$hash_password = hash_password($password);
|
||||
$record = "{$id}\t{$username}\t{$hash_password}\t" . PHP_EOL;
|
||||
$result = false;
|
||||
$record = "{$id}\t{$username}\t{$hash_password}\t" . PHP_EOL;
|
||||
$result = false;
|
||||
|
||||
if (is_writable(USERS_TSV)) {
|
||||
$result = file_put_contents(USERS_TSV, $record, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
if ($result === false) { return ['ファイルの書き込みに失敗。']; }
|
||||
if (is_writable(USERS_TSV)) {
|
||||
$result = file_put_contents(USERS_TSV, $record, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
if ($result === false) {
|
||||
die('ファイルの書き込みに失敗しました。');
|
||||
}
|
||||
chmod(USERS_TSV, 644);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Profile (bio)
|
||||
|
||||
// 利用者のプロフィール (Profile)
|
||||
// プロフィールは PROFILE_DIR (data/profile) 配下に {ID}.txt のファイル名で保存される。
|
||||
// 先頭に「-」だけの行があり、それより後の行がプロフィール本文になる。
|
||||
// 将来的に「-」より前の行にユーザーごとの設定値などを記録する予定。
|
||||
|
||||
function load_profile($id) {
|
||||
if ($id === 'tl') { return ['bio' => '']; }
|
||||
if (!file_exists(PROFILE_DIR)) {
|
||||
if (!mkdir_p(PROFILE_DIR, 755)) die(PROFILE_DIR.'を作成に失敗。');
|
||||
}
|
||||
if (!file_exists(PROFILE_DIR.$id.'.txt')) {
|
||||
touch(PROFILE_DIR.$id.'.txt', time());
|
||||
}
|
||||
if (!file_exists(PROFILE_DIR)) {
|
||||
if (!mkdir_p(PROFILE_DIR, 755)) {
|
||||
die(PROFILE_DIR.'を作成に失敗。');
|
||||
}
|
||||
}
|
||||
if (!file_exists(PROFILE_DIR.$id.'.txt')) {
|
||||
touch(PROFILE_DIR.$id.'.txt', time());
|
||||
}
|
||||
|
||||
$profile = '';
|
||||
if (is_writable(PROFILE_DIR.$id.'.txt')) {
|
||||
$profile = file_get_contents(PROFILE_DIR . $id . '.txt') ?? '';
|
||||
}
|
||||
$profile = '';
|
||||
if (is_writable(PROFILE_DIR.$id.'.txt')) {
|
||||
$profile = file_get_contents(PROFILE_DIR . $id . '.txt') ?? '';
|
||||
}
|
||||
|
||||
$split = preg_split('/^-$/m', $profile, 2);
|
||||
$bio = mbtrim($split[1] ?? '');
|
||||
|
@ -156,15 +159,19 @@ function load_profile($id) {
|
|||
|
||||
function save_profile($id, $profile) {
|
||||
$bio = $profile['bio'] ?? '';
|
||||
if (!mkdir_p(PROFILE_DIR)) {
|
||||
die(PROFILE_DIR.'を作成に失敗。');
|
||||
}
|
||||
|
||||
if (!mkdir_p(PROFILE_DIR)) { return ['フォルダーの作成に失敗。']; }
|
||||
$result = file_put_contents(PROFILE_DIR . $id . '.txt', '-' . PHP_EOL . $bio . PHP_EOL);
|
||||
if ($result === false) { return ['ファイルの書き込みに失敗。']; }
|
||||
if ($result === false) {
|
||||
die(PROFILE_DIR.$id.'を作成に失敗。');
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Auth
|
||||
// 認証
|
||||
|
||||
function auth_user($id, $password) {
|
||||
$id = trim($id);
|
||||
|
@ -184,12 +191,12 @@ function find_user_row($id) {
|
|||
$users = file_get_contents(USERS_TSV) ?? '';
|
||||
$rows = explode(PHP_EOL, $users);
|
||||
|
||||
$search_id = $id . "\t";
|
||||
foreach ($rows as $row) {
|
||||
if (stripos($row, $search_id) === 0) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
$search_id = $id . "\t";
|
||||
foreach ($rows as $row) {
|
||||
if (stripos($row, $search_id) === 0) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
<?php
|
||||
require_once(__DIR__ . '/../../require.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
if (!ENABLE_ATTACHMENT) { return on_error(404, ['Not Found.']); }
|
||||
if (!ENABLE_ATTACHMENT) { return on_error(404, ['Not Found.']); }
|
||||
|
||||
$id = $_GET['id'] ?? '';
|
||||
if ($id == '' || !preg_match('/^[a-z0-9]{32}$/', $id)) { return on_error(400, ['URLが不正。']); }
|
||||
$id = $_GET['id'] ?? '';
|
||||
if ($id == '' || !preg_match('/^[a-z0-9]{32}$/', $id)) { return on_error(400, ['URLが不正。']); }
|
||||
|
||||
$filepath = ATTACHMENT_DIR . $id . '.gz';
|
||||
if (!file_exists($filepath)) { return on_error(404, ['Not Found.']); }
|
||||
$filepath = ATTACHMENT_DIR . $id . '.gz';
|
||||
if (!file_exists($filepath)) { return on_error(404, ['Not Found.']); }
|
||||
|
||||
ob_start();
|
||||
readgzfile($filepath);
|
||||
$buffer = ob_get_clean();
|
||||
$type = get_image_type($buffer);
|
||||
if (!isset($type)) { return on_error(500, ['ファイルが不正。']); }
|
||||
ob_start();
|
||||
readgzfile($filepath);
|
||||
$buffer = ob_get_clean();
|
||||
$type = get_image_type($buffer);
|
||||
if (!isset($type)) { return on_error(500, ['ファイルが不正。']); }
|
||||
|
||||
$php_time = filemtime(__FILE__);
|
||||
$attachment_time = filemtime($filepath);
|
||||
$etag = '"' . $php_time . '.' . $attachment_time . '"';
|
||||
$php_time = filemtime(__FILE__);
|
||||
$attachment_time = filemtime($filepath);
|
||||
$etag = '"' . $php_time . '.' . $attachment_time . '"';
|
||||
|
||||
header('Cache-Control: max-age=86400');
|
||||
header("ETag: {$etag}");
|
||||
header('Cache-Control: max-age=86400');
|
||||
header("ETag: {$etag}");
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||
if ($_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
|
||||
header('HTTP/1.1 304 Not Modified', true, 304);
|
||||
exit();
|
||||
}
|
||||
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||
if ($_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
|
||||
header('HTTP/1.1 304 Not Modified', true, 304);
|
||||
exit();
|
||||
}
|
||||
|
||||
header("Content-Type: {$type}");
|
||||
echo $buffer;
|
||||
}
|
||||
|
||||
header("Content-Type: {$type}");
|
||||
echo $buffer;
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
<?php
|
||||
require_once(__DIR__ . '/../require.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$result = search_post([
|
||||
'page' => $page,
|
||||
'pagesize' => POSTS_PER_PAGE,
|
||||
'pager_prefix' => '?page=',
|
||||
]);
|
||||
$view['post_list'] = $result['post_list'];
|
||||
$view['pager'] = $result['pager'];
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$result = search_post([
|
||||
'page' => $page,
|
||||
'pagesize' => POSTS_PER_PAGE,
|
||||
'pager_prefix' => '?page=',
|
||||
]);
|
||||
$view['post_list'] = $result['post_list'];
|
||||
$view['pager'] = $result['pager'];
|
||||
|
||||
output_html($view, ['header.php', 'post-form.php', 'timeline.php', 'post-list.php', 'pager.php']);
|
||||
}
|
||||
output_html($view, ['header.php', 'post-form.php', 'timeline.php', 'post-list.php', 'pager.php']);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<?php
|
||||
require_once(__DIR__ . '/../../require.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$view['threads'] = load_threads();
|
||||
output_html($view, ['header.php', 'thread.php']);
|
||||
}
|
||||
$view['threads'] = load_threads();
|
||||
output_html($view, ['header.php', 'thread.php']);
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<?php
|
||||
require_once(__DIR__ . '/../../require.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$user_list = load_users();
|
||||
ksort($user_list, SORT_STRING | SORT_FLAG_CASE);
|
||||
$view['user_list'] = $user_list;
|
||||
$user_list = load_users();
|
||||
ksort($user_list, SORT_STRING | SORT_FLAG_CASE);
|
||||
$view['user_list'] = $user_list;
|
||||
|
||||
output_html($view, ['header.php', 'user-list.php']);
|
||||
}
|
||||
output_html($view, ['header.php', 'user-list.php']);
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
<?php
|
||||
require_once(__DIR__ . '/../../require.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$id = '' . ($_GET['id'] ?? '');
|
||||
if ($id !== 'tl' && (!strlen($id) || validate_register_id($id))) { return on_error(400, ['不正なリクエスト。']); }
|
||||
$id = '' . ($_GET['id'] ?? '');
|
||||
if ($id !== 'tl' && (!strlen($id) || validate_register_id($id))) { return on_error(400, ['不正なリクエスト。']); }
|
||||
|
||||
$users = load_users_with_special();
|
||||
if (!isset($users[$id])) { return on_error(400, ['存在しない ID']); }
|
||||
$users = load_users_with_special();
|
||||
if (!isset($users[$id])) { return on_error(400, ['存在しない ID']); }
|
||||
|
||||
$view['user'] = $users[$id];
|
||||
$users = null;
|
||||
$view['user'] = $users[$id];
|
||||
$users = null;
|
||||
|
||||
$profile = load_profile($id);
|
||||
$bio = $profile['bio'] ?? '';
|
||||
$bio = url_to_link(nl2br(htmlspecialchars($bio), false));
|
||||
$view['bio'] = $bio;
|
||||
$profile = null;
|
||||
$profile = load_profile($id);
|
||||
$bio = $profile['bio'] ?? '';
|
||||
$bio = url_to_link(nl2br(htmlspecialchars($bio), false));
|
||||
$view['bio'] = $bio;
|
||||
$profile = null;
|
||||
|
||||
$page = max(1, $_GET['page'] ?? 0);
|
||||
$result = search_post([
|
||||
'key' => 'userid',
|
||||
'value' => $id,
|
||||
'page' => $page,
|
||||
'pagesize' => POSTS_PER_PAGE,
|
||||
'pager_prefix' => "?id={$id}&page=",
|
||||
'includes_future' => ($id === ($_SESSION['user']['id'] ?? '')),
|
||||
]);
|
||||
$view['post_list'] = $result['post_list'];
|
||||
$view['post_count'] = $result['total'];
|
||||
$view['pager'] = $result['pager'];
|
||||
$result = null;
|
||||
$page = max(1, $_GET['page'] ?? 0);
|
||||
$result = search_post([
|
||||
'key' => 'userid',
|
||||
'value' => $id,
|
||||
'page' => $page,
|
||||
'pagesize' => POSTS_PER_PAGE,
|
||||
'pager_prefix' => "?id={$id}&page=",
|
||||
'includes_future' => ($id === ($_SESSION['user']['id'] ?? '')),
|
||||
]);
|
||||
$view['post_list'] = $result['post_list'];
|
||||
$view['post_count'] = $result['total'];
|
||||
$view['pager'] = $result['pager'];
|
||||
$result = null;
|
||||
|
||||
output_html($view, ['header.php', 'user.php', 'post-list.php', 'pager.php']);
|
||||
}
|
||||
output_html($view, ['header.php', 'user.php', 'post-list.php', 'pager.php']);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
require_once(__DIR__ . '/config.php');
|
||||
require_once(__DIR__ . '/config-default.php');
|
||||
require_once(__DIR__ . '/util.php');
|
||||
require_once(__DIR__ . '/common.php');
|
||||
require_once(__DIR__ . '/data.php');
|
||||
require_once(__DIR__ . '/validate.php');
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
// 共通処理
|
||||
|
||||
// HTTP関連
|
||||
|
||||
function get_fp() {
|
||||
return md5(
|
||||
($_SERVER['HTTP_USER_AGENT'] ?? '')
|
||||
. ($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')
|
||||
. ($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '')
|
||||
. ($_SERVER['HTTP_ACCEPT'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
function has_cookie() {
|
||||
return isset($_COOKIE['bibis']);
|
||||
}
|
||||
|
||||
function on_error($code, $errors) {
|
||||
http_response_code(400);
|
||||
if (function_exists('bibis_http_header')) { bibis_http_header(); }
|
||||
|
||||
$view['errors'] = $errors;
|
||||
require(__DIR__ . '/view/header.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
function output_html($view, $_components) {
|
||||
if (function_exists('bibis_http_header')) { bibis_http_header(); }
|
||||
|
||||
foreach ($_components as $_name) {
|
||||
require(__DIR__ . "/view/$_name");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// CSRFトークン関連
|
||||
|
||||
function set_csrf_token() {
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
|
||||
function get_csrf_token() {
|
||||
if (!REQUIRE_COOKIE && !isset($_SESSION['user'])) {
|
||||
$date = date('YmdH');
|
||||
return $date . get_fp();
|
||||
}
|
||||
return $_SESSION['csrf_token'] ?? null;
|
||||
|
||||
}
|
||||
|
||||
function get_csrf_token_2($force_cookie = false) {
|
||||
if (!REQUIRE_COOKIE && !isset($_SESSION['user'])) {
|
||||
$date = date('YmdH', strtotime('-1 hour'));
|
||||
return $date . get_fp();
|
||||
}
|
||||
return $_SESSION['csrf_token'] ?? null;
|
||||
}
|
||||
|
||||
function get_csrf_token_hashed($prefix = '', $token = null) {
|
||||
if ($token === null) { $token = get_csrf_token(); }
|
||||
if ($token === null) { return null; }
|
||||
return md5($prefix . $token);
|
||||
}
|
||||
|
||||
function check_csrf_token() {
|
||||
$hashed = get_csrf_token_hashed();
|
||||
$hashed_2 = get_csrf_token_hashed('', get_csrf_token_2());
|
||||
if ($hashed === null || $hashed_2 === null) { return ['CSRF トークンが不正。要再試行。']; }
|
||||
$token_key = get_csrf_token_hashed('csrf_token');
|
||||
$csrf_token = $_POST[$token_key] ?? '';
|
||||
if ($csrf_token !== $hashed && $csrf_token !== $hashed_2) { return ['CSRF トークンが不正。要再試行。']; }
|
||||
return [];
|
||||
}
|
||||
|
||||
function output_csrf_token_hidden() {
|
||||
$hashed = get_csrf_token_hashed();
|
||||
if ($hashed === null) { return ''; }
|
||||
$token_key = get_csrf_token_hashed('csrf_token');
|
||||
return '<input type="hidden" name="' . htmlspecialchars($token_key) . '" value="' . $hashed . '">';
|
||||
}
|
||||
|
||||
// 文字列関連
|
||||
|
||||
function post_default_title($id) {
|
||||
return '無題#' . mb_substr($id, 0, 7);
|
||||
}
|
||||
|
||||
function anchor_to_link($s, $thread_id) {
|
||||
return preg_replace(
|
||||
'/>>([1-9]+[0-9]{0,10})/',
|
||||
'<a href="' . sitebase('post/?id=') . $thread_id . '&res=\1">>>\1</a>',
|
||||
$s
|
||||
);
|
||||
}
|
||||
|
||||
function url_to_link($s) {
|
||||
return preg_replace(
|
||||
'/((http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?)/',
|
||||
'<a href="\1" rel="noopener noreferrer">\1</a>',
|
||||
$s
|
||||
);
|
||||
}
|
||||
|
||||
function mbtrim($s) {
|
||||
return preg_replace('/^\s+|\s+$/u', '', $s);
|
||||
}
|
||||
|
||||
function twochan_trip($tripkey) {
|
||||
|
||||
// twochan_trip('atomikka') -> '◆.../5Betyws'
|
||||
// Thanks: https://refirio.org/view/62
|
||||
// Test data: https://trip-table.kokage.cc/sample01.php
|
||||
|
||||
$tripkey = mb_convert_encoding($tripkey, 'sjis-win');
|
||||
$salt = substr($tripkey . 'H.', 1, 2);
|
||||
$salt = preg_replace('/[^\.-z]/', '.', $salt);
|
||||
$salt = strtr($salt, ':;<=>?@[\\]^_`', 'ABCDEFGabcdef');
|
||||
|
||||
$trip = crypt($tripkey, $salt);
|
||||
$trip = substr($trip, -10);
|
||||
|
||||
return '◆' . $trip;
|
||||
}
|
||||
|
||||
function parse_key_value($s, $key_list) {
|
||||
$result = [];
|
||||
$lines = explode(PHP_EOL, $s);
|
||||
foreach ($lines as $line) {
|
||||
foreach ($key_list as $key) {
|
||||
if (strpos($line, "{$key}=") === 0) {
|
||||
$result[$key] = explode("{$key}=", $line, 2)[1] ?? null;
|
||||
}
|
||||
}
|
||||
unset($key);
|
||||
}
|
||||
unset($lines, $line);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ファイル関連
|
||||
|
||||
function mkdir_p($path, $permission = 0700) {
|
||||
if (file_exists($path)) { return true; }
|
||||
return mkdir($path, $permission);
|
||||
}
|
||||
|
||||
function get_image_type($s) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$type = finfo_buffer($finfo, $s);
|
||||
if (isset($type) && in_array($type, ACCEPT_IMAGE_TYPE)) {
|
||||
return $type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function sitebase($path = '') {
|
||||
return SITEBASE . $path;
|
||||
}
|
||||
|
||||
function get_style_css() {
|
||||
if (THEME === null || THEME === '') { return sitebase('style.css'); }
|
||||
return sitebase('theme/' . THEME);
|
||||
}
|
||||
|
||||
// 制限・権限
|
||||
|
||||
function is_logged_in() {
|
||||
return isset($_SESSION['user']);
|
||||
}
|
||||
|
||||
function can_post() {
|
||||
if (!post_limited()) { return false; }
|
||||
if (!ENABLE_GUEST && !is_logged_in()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
function post_limited() {
|
||||
return ENABLE_POST && (count_post_today() < POST_LIMIT_PER_DAY) && (count_post() < POST_LIMIT);
|
||||
}
|
||||
|
||||
function can_regist() {
|
||||
return ENABLE_REGISTER;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Validation, and sanitize
|
||||
// バリデーションやサニタイズの関数
|
||||
|
||||
function validate_register_id($s) {
|
||||
$s = mbtrim($s);
|
||||
|
@ -75,9 +75,8 @@ function sanitize_post_body($s) {
|
|||
return sanitize_multiline($s);
|
||||
}
|
||||
|
||||
// Common
|
||||
|
||||
// See: https://www.php.net/manual/ja/regexp.reference.unicode.php
|
||||
// 共通
|
||||
// 正規表現の内訳: https://www.php.net/manual/ja/regexp.reference.unicode.php
|
||||
|
||||
function sanitize_multiline($s) {
|
||||
$s = preg_replace('/[^\p{L}\{M}\p{N}\p{P}\p{Sc}\p{S}\p{Z}\012\015\040-\176]/u', '', $s);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// HTML Header, and global navigation
|
||||
// 共通ヘッダー
|
||||
|
||||
$view['login_user'] = $_SESSION['user'] ?? null;
|
||||
$view['messages'] = $_SESSION['messages'] ?? null;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Login form
|
||||
// ログイン画面
|
||||
?>
|
||||
<h2>ログイン・Login</h2>
|
||||
<form action="<?= sitebase('login/') ?>" method="POST">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Pager
|
||||
// ページャー
|
||||
?>
|
||||
<?php if (isset($view['pager'])): ?>
|
||||
<ul class="pager">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Single post
|
||||
// 単独の投稿
|
||||
|
||||
function view_post($post, $options = []) {
|
||||
$res_num = $options['res_num'] ?? 0;
|
||||
|
@ -62,7 +62,7 @@ function view_post($post, $options = []) {
|
|||
<dt>件名:<b><?= $title ?></b></dt>
|
||||
<?php endif; ?>
|
||||
<dt><?= $res_num > 0 ? "{$res_num} " : '' ?><?= $html_user ?> <?= $html_time ?></dt>
|
||||
<?php if ($link_to_thread && $thread_id > ''): ?>
|
||||
<?php if (!$is_single && $link_to_thread && $thread_id != ''): ?>
|
||||
<dd>RE: <a href="<?= $thread_url ?>"><?= $thread_title ?></a></dd>
|
||||
<?php endif; ?>
|
||||
<dd><?= $body ?>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Confirm (delete post)
|
||||
// 削除画面
|
||||
|
||||
require __DIR__ . '/post-common.php';
|
||||
?>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Post form
|
||||
// 投稿フォーム
|
||||
$value = '';
|
||||
if (($view['res_num'] ?? 0) >= 2) {
|
||||
$value = '>>' . $view['res_num'] . PHP_EOL;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<?php
|
||||
// 投稿一覧
|
||||
|
||||
require __DIR__ . '/post-common.php';
|
||||
|
||||
if (!($view['post_list'] ?? false)) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Post/thread detail
|
||||
// 投稿詳細
|
||||
require __DIR__ . '/post-common.php';
|
||||
|
||||
$post = $view['post'] ?? [];
|
||||
|
@ -16,10 +16,8 @@ $total_view = $total > 1 ? " ($total)" : '';
|
|||
<h2>件名:<?= htmlspecialchars("$title{$total_view}") ?></h2>
|
||||
<?php elseif ($thread_id == ''): ?>
|
||||
<h2><?= htmlspecialchars('無題#' . mb_substr($post_id, 0, 7) . $total_view) ?></h2>
|
||||
<?php elseif ($thread_title != ''): ?>
|
||||
<?php else: ?>
|
||||
<h2>RE:<a href="<?= $thread_url ?>"><?= htmlspecialchars($thread_title) ?></a></h2>
|
||||
<?php elseif ($thread_id != ''): ?>
|
||||
<h2>RE:<a href="<?= $thread_url ?>"><?= htmlspecialchars('無題#' . mb_substr($thread_id, 0, 7)) ?></a></h2>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
view_post($post, ['res_num' => $thread_id != '' ? 0 : 1, 'is_single' => true, 'link_to_thread' => true, 'res_num' => $view['res_num'] ?? 1]);
|
||||
|
@ -28,4 +26,4 @@ view_post($post, ['res_num' => $thread_id != '' ? 0 : 1, 'is_single' => true, 'l
|
|||
foreach ($reply_list as $i => $reply) {
|
||||
view_post($reply, ['res_num' => $i + 2]);
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
<?php
|
||||
// 投稿フォーム
|
||||
?>
|
||||
<?php if (isset($view['form']['thread_id']) && $view['form']['thread_id'] >= 0): ?>
|
||||
<h2 id="REPLY_FORM">返信</h2>
|
||||
<?php endif; ?>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
// 投稿一覧
|
||||
$reply_list_mode = isset($view['reply_list_mode']) && $view['reply_list_mode'];
|
||||
?>
|
||||
<?php if (isset($view['post_list'])): ?>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Register form (stop)
|
||||
// 新規登録 (停止中)
|
||||
?>
|
||||
<h2>新規登録・Register</h2>
|
||||
<ul>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Register form
|
||||
// 新規登録
|
||||
?>
|
||||
<h2>新規登録・Register</h2>
|
||||
<form action="<?= sitebase('register/') ?>" method="POST">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// Settings form
|
||||
// 設定
|
||||
?>
|
||||
<h2>設定(ユーザー情報変更)・Settings</h2>
|
||||
<p>パスワード変更を希望しない場合、新パスワードの欄を空にしてください。</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// List of threads
|
||||
// スレッド一覧
|
||||
?>
|
||||
<h2>スレッド一覧・Threads (<?= sizeof($view['threads'] ?? []) ?>)</h2>
|
||||
<p>返信が1件以上あるスレッドの一覧。
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// List of users
|
||||
// 利用者一覧
|
||||
// TODO: できればユーザー詳細のURLは /public/user-list/index.php で生成したい。
|
||||
?>
|
||||
<h2>利用者一覧・Users (<?= sizeof($view['user_list'] ?? []) ?>)</h2>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
// User profile
|
||||
// 利用者のプロフィール
|
||||
?>
|
||||
<dl>
|
||||
<dt><b><?= htmlspecialchars($view['user']['username'] ?? '') ?></b> @<?= htmlspecialchars($view['user']['id']) ?></dt>
|
||||
|
|
読み込み中…
新しいイシューから参照