mirror of
https://github.com/sorenpeter/timeline.git
synced 2025-12-15 10:57:01 +00:00
Second commit
This commit is contained in:
parent
5f945df6d5
commit
49307f74c0
29 changed files with 2371 additions and 30 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -2,4 +2,6 @@
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Icon?
|
Icon?
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
*.txt
|
||||||
|
private/cache/
|
||||||
82
add_url.php
Normal file
82
add_url.php
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
// TODO: Give a warning if the file is not found
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
if ($config['debug_mode']) {
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once('session.php');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['valid_session'])) {
|
||||||
|
$secretKey = $config['totp_secret'];
|
||||||
|
$cookieVal = decodeCookie($secretKey);
|
||||||
|
|
||||||
|
if ($cookieVal === false) { # Valid cookie ?
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['submit'])) {
|
||||||
|
$new_post = filter_input(INPUT_POST, 'new_post');
|
||||||
|
// Replace new lines for Line separator character (U+2028)
|
||||||
|
$new_post = str_replace("\n", "\u{2028}", $new_post);
|
||||||
|
$new_post = str_replace("\r", '', $new_post);
|
||||||
|
|
||||||
|
if ($new_post) {
|
||||||
|
// Check if we have a point to insert the next Twt
|
||||||
|
define('NEW_TWT_MARKER', "#~~~#\n");
|
||||||
|
$contents = file_get_contents($txt_file_path);
|
||||||
|
|
||||||
|
if (!date_default_timezone_set($timezone)) {
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
$twt = date('c') . "\t$new_post\n";
|
||||||
|
|
||||||
|
if (strpos($contents, NEW_TWT_MARKER) !== false) {
|
||||||
|
// Add the previous marker
|
||||||
|
$twt = NEW_TWT_MARKER . $twt;
|
||||||
|
$contents = str_replace(NEW_TWT_MARKER, $twt, $contents);
|
||||||
|
} else {
|
||||||
|
$contents .= $twt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add error handling if write to the file fails
|
||||||
|
// For example due to permissions problems
|
||||||
|
$file_write_result = file_put_contents($txt_file_path, $contents);
|
||||||
|
|
||||||
|
header('Refresh:0; url=.');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
echo "Oops something went wrong...\n\nCheck the error_log in the server";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else { ?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>twtxt</title>
|
||||||
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href=".">twtxt</a></h1>
|
||||||
|
<form method="POST" class="column">
|
||||||
|
<div id="login">
|
||||||
|
<?php if ($invalidURL) { ?>
|
||||||
|
<div class="alert">URL is invalid, check it!</div><br>
|
||||||
|
<?php } ?>
|
||||||
|
<label for="fname">URL to twtxt.txt file</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" id="url" name="url" class="input" autocomplete="off"><br>
|
||||||
|
<input type="submit" value="Add URL" class="btn">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php } ?>
|
||||||
88
follow.php
Normal file
88
follow.php
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
// TODO: Give a warning if the file is not found
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
if ($config['debug_mode']) {
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
$txt_file_path = $config['txt_file_path'];
|
||||||
|
|
||||||
|
require_once('session.php');
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (!has_valid_session()) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (isset($_POST['url'])) {
|
||||||
|
$url = trim(filter_input(INPUT_POST, 'url'));
|
||||||
|
$nick = trim(filter_input(INPUT_POST, 'nick'));
|
||||||
|
|
||||||
|
if (!$url or !$nick) {
|
||||||
|
die('Fill url and nick.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
|
||||||
|
die('Not a valid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($txt_file_path)) {
|
||||||
|
die('twtxt.txt file does not exist. Check your config.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($txt_file_path);
|
||||||
|
|
||||||
|
# Insert new follows before the marker #~~~#
|
||||||
|
define('NEW_TWT_MARKER', "#~~~#\n");
|
||||||
|
|
||||||
|
$follow_str_to_insert = "# follow = $nick $url";
|
||||||
|
|
||||||
|
if (strpos($contents, NEW_TWT_MARKER) !== false) {
|
||||||
|
// Add the previous marker
|
||||||
|
// Take note that doesn't not work if twtxt file has CRLF line ending
|
||||||
|
// (which is wrong anyway)
|
||||||
|
$follow_str = $follow_str_to_insert . "\n" . NEW_TWT_MARKER;
|
||||||
|
$contents = str_replace(NEW_TWT_MARKER, $follow_str, $contents);
|
||||||
|
} else {
|
||||||
|
die('Could not insert the follower into the twtxt.txt file. Check that the marker exists.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add error handling if write to the file fails
|
||||||
|
// For example due to permissions problems
|
||||||
|
// https://www.w3docs.com/snippets/php/how-can-i-handle-the-warning-of-file-get-contents-function-in-php.html
|
||||||
|
$file_write_result = file_put_contents($txt_file_path, $contents);
|
||||||
|
|
||||||
|
header('Refresh:0; url=.');
|
||||||
|
exit;
|
||||||
|
} else { ?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>twtxt</title>
|
||||||
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href=".">twtxt</a></h1>
|
||||||
|
<form method="POST" class="column">
|
||||||
|
<div id="follow">
|
||||||
|
<label for="url">URL to follow</label>
|
||||||
|
<br>
|
||||||
|
<input type="url" id="url" name="url" class="input" size="50" autocomplete="off" required>
|
||||||
|
<br>
|
||||||
|
<label for="nick">Nick</label>
|
||||||
|
<br>
|
||||||
|
<input type="text" id="nick" name="nick" class="input" size="50" autocomplete="off" required>
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Follow" class="btn">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php } ?>
|
||||||
59
index.php
59
index.php
|
|
@ -1,23 +1,46 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
$request = $_SERVER['REQUEST_URI'];
|
//require_once("router.php");
|
||||||
$viewDir = '/views/';
|
//require_once("views/home.php");
|
||||||
|
require_once("partials/base.php");
|
||||||
|
|
||||||
switch ($request) {
|
?>
|
||||||
case '':
|
|
||||||
case '/':
|
|
||||||
require __DIR__ . $viewDir . 'home.php';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '/login':
|
<html>
|
||||||
require __DIR__ . $viewDir . 'login.php';
|
<head>
|
||||||
break;
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||||
|
<title>Timeline</title>
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
|
||||||
case '/timeline':
|
<!-- PHP: GET HEADER --><?php include 'partials/header.php';?>
|
||||||
require __DIR__ . $viewDir . 'timeline.php';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
<!-- PHP: GET PROFILE CARD -->
|
||||||
http_response_code(404);
|
<?php
|
||||||
require __DIR__ . $viewDir . '404.php';
|
if (!empty($_GET['twts'])) { // Show twts for some user
|
||||||
}
|
$twtsURL = $_GET['twts'];
|
||||||
|
|
||||||
|
// TODO: Give a propper error if feed is not valid
|
||||||
|
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
|
||||||
|
die('Not a valid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
// $parsedTwtxtFile = getTwtsFromTwtxtString($twtsURL);
|
||||||
|
if (!is_null($parsedTwtxtFile)) {
|
||||||
|
$parsedTwtxtFiles[$parsedTwtxtFile->mainURL] = $parsedTwtxtFile;
|
||||||
|
include 'partials/profile.php';
|
||||||
|
}
|
||||||
|
} ?>
|
||||||
|
|
||||||
|
<main class="timeline">
|
||||||
|
|
||||||
|
<!-- PHP: GET TIMELIE --><?php include 'partials/timeline.php'?>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- PHP: GET FOOTER --><?php include 'partials/footer.php';?>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
||||||
81
libs/Base32.php
Normal file
81
libs/Base32.php
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
// https://github.com/bbars/utils/blob/master/php-base32-encode-decode/Base32.php
|
||||||
|
class Base32
|
||||||
|
{
|
||||||
|
const BITS_5_RIGHT = 31;
|
||||||
|
const CHARS = 'abcdefghijklmnopqrstuvwxyz234567'; // lower-case
|
||||||
|
|
||||||
|
public static function encode($data, $padRight = false)
|
||||||
|
{
|
||||||
|
$dataSize = strlen($data);
|
||||||
|
$res = '';
|
||||||
|
$remainder = 0;
|
||||||
|
$remainderSize = 0;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $dataSize; $i++)
|
||||||
|
{
|
||||||
|
$b = ord($data[$i]);
|
||||||
|
$remainder = ($remainder << 8) | $b;
|
||||||
|
$remainderSize += 8;
|
||||||
|
while ($remainderSize > 4)
|
||||||
|
{
|
||||||
|
$remainderSize -= 5;
|
||||||
|
$c = $remainder & (self::BITS_5_RIGHT << $remainderSize);
|
||||||
|
$c >>= $remainderSize;
|
||||||
|
$res .= static::CHARS[$c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($remainderSize > 0)
|
||||||
|
{
|
||||||
|
// remainderSize < 5:
|
||||||
|
$remainder <<= (5 - $remainderSize);
|
||||||
|
$c = $remainder & self::BITS_5_RIGHT;
|
||||||
|
$res .= static::CHARS[$c];
|
||||||
|
}
|
||||||
|
if ($padRight)
|
||||||
|
{
|
||||||
|
$padSize = (8 - ceil(($dataSize % 5) * 8 / 5)) % 8;
|
||||||
|
$res .= str_repeat('=', $padSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decode($data)
|
||||||
|
{
|
||||||
|
$data = rtrim($data, "=\x20\t\n\r\0\x0B");
|
||||||
|
$dataSize = strlen($data);
|
||||||
|
$buf = 0;
|
||||||
|
$bufSize = 0;
|
||||||
|
$res = '';
|
||||||
|
$charMap = array_flip(str_split(static::CHARS)); // char=>value map
|
||||||
|
$charMap += array_flip(str_split(strtoupper(static::CHARS))); // add upper-case alternatives
|
||||||
|
|
||||||
|
for ($i = 0; $i < $dataSize; $i++)
|
||||||
|
{
|
||||||
|
$c = $data[$i];
|
||||||
|
if (!isset($charMap[$c]))
|
||||||
|
{
|
||||||
|
if ($c == " " || $c == "\r" || $c == "\n" || $c == "\t")
|
||||||
|
continue; // ignore these safe characters
|
||||||
|
throw new Exception('Encoded string contains unexpected char #'.ord($c)." at offset $i (using improper alphabet?)");
|
||||||
|
}
|
||||||
|
$b = $charMap[$c];
|
||||||
|
$buf = ($buf << 5) | $b;
|
||||||
|
$bufSize += 5;
|
||||||
|
if ($bufSize > 7)
|
||||||
|
{
|
||||||
|
$bufSize -= 8;
|
||||||
|
$b = ($buf & (0xff << $bufSize)) >> $bufSize;
|
||||||
|
$res .= chr($b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Base32hex extends Base32
|
||||||
|
{
|
||||||
|
const CHARS = '0123456789abcdefghijklmnopqrstuv'; // lower-case
|
||||||
|
}
|
||||||
40
libs/gallery.php
Normal file
40
libs/gallery.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gallery render of images posted to a twtxt.txt -- by darch.dk (2022)
|
||||||
|
https://www.reddit.com/r/css/comments/8vmo4u/problem_fitting_image_to_css_grid/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Load twtxt.txt file
|
||||||
|
//$filecontent = file('../../twtxt.txt');
|
||||||
|
$filecontent = file($twtxt);
|
||||||
|
|
||||||
|
// Only posts containing images
|
||||||
|
$img_pattern = '/!\[(.*?)\]\((.*?)\)/'; // Look for posts with images in markdown tags: 
|
||||||
|
$img_posts = preg_grep($img_pattern, $filecontent);
|
||||||
|
rsort($img_posts);
|
||||||
|
//echo 'img_posts: '.count($img_posts).'<hr>'; // FOR DEBUGING
|
||||||
|
//echo '<hr><pre>'; print_r($img_posts); echo '</pre>'; // FOR DEBUGING
|
||||||
|
|
||||||
|
// Loop through each post and extract date and entry text:
|
||||||
|
foreach($img_posts as $post){
|
||||||
|
|
||||||
|
$date = preg_filter('/^(?<date>[^\t]+)\t(?<entry>.+)/', '\1', $post) ;
|
||||||
|
$entry = preg_filter('/^(?<date>[^\t]+)\t(?<entry>.+)/', '\2', $post) ;
|
||||||
|
$text_only = preg_filter('/!\[(.*?)\]\((.*?)\)/', '\1', $entry); // this gives the post without the markdown img links (not sure why, but it works)
|
||||||
|
$text_only = trim($text_only);
|
||||||
|
$text_only = strip_tags($text_only);
|
||||||
|
|
||||||
|
preg_match_all('/!\[(?<alt>.*?)\]\((?<url>.*?)\)/', $entry, $img_array);
|
||||||
|
//echo '<pre>'; print_r($img_array); echo '</pre>'; // FOR DEBUGING
|
||||||
|
|
||||||
|
foreach($img_array[url] as $img => $val) {
|
||||||
|
$url = $img_array[url][$img];
|
||||||
|
//$alt = $img_array[alt][$img];
|
||||||
|
//echo '<a href="https://example.com/pixelblog/?id='.$date.'"><img src="'.$url.'" alt="'.$text_only.'" title="'.$text_only.'" loading=lazy></a>';
|
||||||
|
echo '<a href="'.$base_url.'?id='.$date.'"><img src="'.$base_url.'system/thumb.php?src='.$url.'&size=600x&crop=1" alt="'.$text_only.'" title="'.$text_only.'" loading=lazy></a>';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
52
libs/hash.php
Normal file
52
libs/hash.php
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
|
require_once('libs/Base32.php');
|
||||||
|
# twtxt Hash extension
|
||||||
|
# https://dev.twtxt.net/doc/twthashextension.html
|
||||||
|
|
||||||
|
function getHashFromTwt(string $twt, string $url): string {
|
||||||
|
$explodedLine = explode("\t", $twt);
|
||||||
|
//print_r($explodedLine);
|
||||||
|
|
||||||
|
if (count($explodedLine) >= 2) {
|
||||||
|
$dateStr = $explodedLine[0];
|
||||||
|
$twtContent = $explodedLine[1];
|
||||||
|
|
||||||
|
// dateStrings without timezone should be assumed as UTC
|
||||||
|
$dt = new DateTime($dateStr);
|
||||||
|
|
||||||
|
// Getting the new formatted datetime
|
||||||
|
//$dateStr = $dt->format(DateTime::ATOM); // Updated ISO8601
|
||||||
|
$dateStr = $dt->format(DateTime::RFC3339);
|
||||||
|
$dateStr = str_replace('+00:00', 'Z', $dateStr);
|
||||||
|
$dateStr = str_replace('-00:00', 'Z', $dateStr);
|
||||||
|
|
||||||
|
$hashPayload = "$url\n$dateStr\n$twtContent";
|
||||||
|
|
||||||
|
// Default to 32 bytes
|
||||||
|
// https://www.php.net/manual/en/function.sodium-crypto-generichash.php
|
||||||
|
$hashBytes = sodium_crypto_generichash($hashPayload);
|
||||||
|
$hashStr = substr(Base32::encode($hashBytes), -7);
|
||||||
|
|
||||||
|
return $hashStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'INVALID';
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidHashes() {
|
||||||
|
$url = 'http://magical.fish:70/feeds/twtxt/twtxt.txt';
|
||||||
|
|
||||||
|
$twt = "2023-06-17T00:33:32-06:00\tSun's out funs out!";
|
||||||
|
$expectedHash = 'ujcbz3q';
|
||||||
|
|
||||||
|
assert(getHashFromTwt($twt, $url) === $expectedHash);
|
||||||
|
|
||||||
|
$twt = "2023-06-20T07:51:48-06:00\tWhat a way to go.";
|
||||||
|
$expectedHash = 'f7hzthq';
|
||||||
|
assert(getHashFromTwt($twt, $url) === $expectedHash);
|
||||||
|
|
||||||
|
echo 'Asserts passed';
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
$password = $config['password'];
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
if(isset($_POST['submit_pass']) && $_POST['pass'])
|
if(isset($_POST['submit_pass']) && $_POST['pass'])
|
||||||
{
|
{
|
||||||
$pass=$_POST['pass'];
|
$pass=$_POST['pass'];
|
||||||
if($pass=="æøå123")
|
if($pass=="$password")
|
||||||
{
|
{
|
||||||
$_SESSION['password']=$pass;
|
$_SESSION['password']=$pass;
|
||||||
}
|
}
|
||||||
415
libs/twtxt.php
Normal file
415
libs/twtxt.php
Normal file
|
|
@ -0,0 +1,415 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
if ($config['debug_mode']) {
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
ini_set('display_startup_errors', '1');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TwtxtFile {
|
||||||
|
public $mainURL = ''; // First found URL
|
||||||
|
public $URLs = [];
|
||||||
|
public $nick = '';
|
||||||
|
public $avatar = '';
|
||||||
|
public $emoji = '';
|
||||||
|
public $description = '';
|
||||||
|
public $lang = 'en'; // Default language
|
||||||
|
public $links = [];
|
||||||
|
public $following = [];
|
||||||
|
public $twts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Twt {
|
||||||
|
public $originalTwtStr;
|
||||||
|
public $hash;
|
||||||
|
public $fullDate;
|
||||||
|
public $displayDate;
|
||||||
|
public $content;
|
||||||
|
public $replyToHash;
|
||||||
|
public $mentions;
|
||||||
|
public $avatar;
|
||||||
|
public $emoji;
|
||||||
|
public $nick;
|
||||||
|
public $mainURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
# https://stackoverflow.com/a/39360281/13173382
|
||||||
|
# Confirm that this temorary fix is not skipping something
|
||||||
|
/*
|
||||||
|
stream_context_set_default([
|
||||||
|
'ssl' => [
|
||||||
|
'peer_name' => 'generic-server',
|
||||||
|
'verify_peer' => FALSE,
|
||||||
|
'verify_peer_name' => FALSE,
|
||||||
|
'allow_self_signed' => TRUE
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
curl_setopt($curl, CURLOPT_SSLVERSION, 4);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function searches for a key-value pair in a string and returns the value if found.
|
||||||
|
*
|
||||||
|
* @param keyToFind The key we want to find in the string.
|
||||||
|
* @param string The string in which to search for the key-value pair.
|
||||||
|
*
|
||||||
|
* @return the value of the key that matches the given keyToFind in the given string. If a match is
|
||||||
|
* found, the function returns the value of the key as a string after trimming any whitespace. If no
|
||||||
|
* match is found, the function returns null.
|
||||||
|
*/
|
||||||
|
function getSingleParameter($keyToFind, $string) {
|
||||||
|
if (!str_contains($string, $keyToFind)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/\s*' . $keyToFind . '\s*=\s*([^#\n]+)/';
|
||||||
|
//$pattern = '/\s*' . $keyToFind . '\s*=\s*([^\s#]+)/'; // Only matches the first word
|
||||||
|
preg_match($pattern, $string, $matches);
|
||||||
|
|
||||||
|
if (isset($matches[1])) {
|
||||||
|
return trim($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDoubleParameter($keywordToFind, $string) {
|
||||||
|
// Returns string or null
|
||||||
|
$pattern = '/#\s*' . preg_quote($keywordToFind, '/') . '\s*=\s*(\S+)\s*(\S+)/';
|
||||||
|
// Matches "# <keyword> = <value> <value>"
|
||||||
|
preg_match($pattern, $string, $matches);
|
||||||
|
|
||||||
|
if (isset($matches[1]) && isset($matches[2])) {
|
||||||
|
$result = array($matches[1], $matches[2]);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReplyHashFromTwt(string $twtString): string {
|
||||||
|
// Extract the text between parentheses using regular expressions
|
||||||
|
$pattern = '/\(#([^\)]+)\)/'; // Matches "(#<text>)"
|
||||||
|
preg_match($pattern, $twtString, $matches);
|
||||||
|
|
||||||
|
if (isset($matches[1])) {
|
||||||
|
$textBetweenParentheses = $matches[1];
|
||||||
|
return $textBetweenParentheses;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMentionsFromTwt(string $twtString) {
|
||||||
|
$pattern = '/@<([^>]+)\s([^>]+)>/'; // Matches "@<nick url>"
|
||||||
|
preg_match_all($pattern, $twtString, $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$nick = $match[1];
|
||||||
|
$url = $match[2];
|
||||||
|
$result[] = array("nick" => $nick, "url" => $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceMentionsFromTwt(string $twtString): string {
|
||||||
|
// Example input: 'Hello @<eapl.mx https://eapl.mx/twtxt.txt>, how are you? @<nick https://server.com/something/twtxt.txt>';
|
||||||
|
// Example output: Hello <a href="?url=https://eapl.mx/twtxt.txt">@eapl.mx@eapl.mx/twtxt.txt</a>, how are you? <a href="?url=https://server.com/something/twtxt.txt">@nick@server.com/something/twtxt.txt</a>
|
||||||
|
|
||||||
|
#$pattern = '/@<([^ ]+)\s([^>]+)>/';
|
||||||
|
#$replacement = '<a href="?url=$2">@$1</a>';
|
||||||
|
#$twtString = '@<nick https://eapl.mx/twtxt.txt>';
|
||||||
|
$pattern = '/@<([^ ]+) ([^>]+)>/';
|
||||||
|
$replacement = '@$1';
|
||||||
|
|
||||||
|
$result = preg_replace($pattern, $replacement, $twtString);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceLinksFromTwt(string $twtString) {
|
||||||
|
// Regular expression pattern to match URLs
|
||||||
|
$pattern = '/(?<!\S)(\b(https?|ftp|gemini|spartan|gopher):\/\/\S+|\b(?!:\/\/)\w+(?:\.\w+)+(?:\/\S+)?)(?!\S)/';
|
||||||
|
|
||||||
|
// Replace URLs with clickable links
|
||||||
|
$replacement = '<a href="$1">$1</a>';
|
||||||
|
$result = preg_replace($pattern, $replacement, $twtString);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceMarkdownLinksFromTwt(string $twtString) {
|
||||||
|
$pattern = '/\[([^\]]+)\]\(([^)]+)\)/';
|
||||||
|
|
||||||
|
$replacement = '<a href="$2">$1</a>';
|
||||||
|
$result = preg_replace($pattern, $replacement, $twtString);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceImagesFromTwt(string $twtString) {
|
||||||
|
$pattern = '/!\[(.*?)\]\((.*?)\)/';
|
||||||
|
$replacement = '<img src="$2" alt="$1">';
|
||||||
|
$result = preg_replace($pattern, $replacement, $twtString);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimeElapsedString($timestamp, $full = false) {
|
||||||
|
$now = new DateTime;
|
||||||
|
$ago = new DateTime;
|
||||||
|
$ago->setTimestamp($timestamp);
|
||||||
|
|
||||||
|
$agoText = 'ago';
|
||||||
|
if ($now < $ago) {
|
||||||
|
$agoText = 'in the future';
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $now->diff($ago);
|
||||||
|
|
||||||
|
//$diff->w = floor($diff->d / 7);
|
||||||
|
$w = floor($diff->d / 7);
|
||||||
|
$d = $diff->d - ($w * 7);
|
||||||
|
//$diff->d -= $diff->w * 7;
|
||||||
|
|
||||||
|
$string = array(
|
||||||
|
'y' => 'year',
|
||||||
|
'm' => 'month',
|
||||||
|
'w' => 'week',
|
||||||
|
'd' => 'day',
|
||||||
|
'h' => 'hour',
|
||||||
|
'i' => 'minute',
|
||||||
|
's' => 'second',
|
||||||
|
);
|
||||||
|
foreach ($string as $k => &$v) { // k is key, and v is value... Obviously
|
||||||
|
if ($k === 'w') {
|
||||||
|
if ($w) {
|
||||||
|
$v = $w . ' ' . $v . ($w > 1 ? 's' : '');
|
||||||
|
} else {
|
||||||
|
unset($string[$k]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($diff->$k) {
|
||||||
|
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
|
||||||
|
} else {
|
||||||
|
unset($string[$k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$full) $string = array_slice($string, 0, 1);
|
||||||
|
return $string ? implode(', ', $string) . " $agoText" : 'just now';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedFileContentsOrUpdate($fileURL, $cacheDurationSecs = 15) {
|
||||||
|
# TODO: Process the Warning
|
||||||
|
# Warning: file_get_contents(https://eapl.mx/twtxt.net):
|
||||||
|
# failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in
|
||||||
|
|
||||||
|
$cacheFilePath = getCachedFileName($fileURL);
|
||||||
|
|
||||||
|
// Check if cache file exists and it's not expired
|
||||||
|
if (file_exists($cacheFilePath) && (time() - filemtime($cacheFilePath)) < $cacheDurationSecs) {
|
||||||
|
return file_get_contents($cacheFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// File doesn't exist in cache or has expired, so fetch and cache it
|
||||||
|
$contents = file_get_contents($fileURL);
|
||||||
|
file_put_contents($cacheFilePath, $contents);
|
||||||
|
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedFileContents($filePath) {
|
||||||
|
$cacheFile = getCachedFileName($filePath);
|
||||||
|
|
||||||
|
// Check if cache file exists and it's not expired
|
||||||
|
if (file_exists($cacheFile)) {
|
||||||
|
return file_get_contents($cacheFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCachedFile($filePath, $cacheDurationSecs = 15) {
|
||||||
|
$cacheFilePath = getCachedFileName($filePath);
|
||||||
|
|
||||||
|
// File doesn't exist in cache or has expired, so fetch and cache it
|
||||||
|
// TODO: Seems it's not working right!
|
||||||
|
$fileDoesntExist = !file_exists($cacheFilePath);
|
||||||
|
$fileIsOld = false;
|
||||||
|
if (!$fileDoesntExist) {
|
||||||
|
$fileIsOld = !((time() - filemtime($cacheFilePath)) < $cacheDurationSecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fileDoesntExist || $fileIsOld) {
|
||||||
|
#echo "Loading Cached file $cacheFilePath<br>\n";
|
||||||
|
$contents = @file_get_contents($filePath);
|
||||||
|
|
||||||
|
if ($contents === false) {
|
||||||
|
// File loaded with errors, skip saving it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($cacheFilePath, $contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTwtsFromTwtxtString($url) {
|
||||||
|
$fileContent = getCachedFileContents($url);
|
||||||
|
|
||||||
|
if (is_null($fileContent)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$fileContent = mb_convert_encoding($fileContent, 'UTF-8');
|
||||||
|
|
||||||
|
$fileLines = explode("\n", $fileContent);
|
||||||
|
|
||||||
|
$twtxtData = new TwtxtFile();
|
||||||
|
|
||||||
|
foreach ($fileLines as $currentLine) {
|
||||||
|
// Remove empty lines
|
||||||
|
if (empty($currentLine)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($currentLine, '#')) {
|
||||||
|
// Check if comments (starting with #) have some metadata
|
||||||
|
if (!is_null(getSingleParameter('url', $currentLine))) {
|
||||||
|
$currentURL = getSingleParameter('url', $currentLine);
|
||||||
|
|
||||||
|
if (empty($twtxtData->URLs)) {
|
||||||
|
$twtxtData->mainURL = $currentURL;
|
||||||
|
}
|
||||||
|
$twtxtData->URLs[] = $currentURL;
|
||||||
|
}
|
||||||
|
if (!is_null(getSingleParameter('nick', $currentLine))) {
|
||||||
|
$twtxtData->nick = getSingleParameter('nick', $currentLine);
|
||||||
|
}
|
||||||
|
if (!is_null(getSingleParameter('avatar', $currentLine))) {
|
||||||
|
$twtxtData->avatar = getSingleParameter('avatar', $currentLine);
|
||||||
|
}
|
||||||
|
if (!is_null(getSingleParameter('emoji', $currentLine))) {
|
||||||
|
$twtxtData->emoji = getSingleParameter('emoji', $currentLine);
|
||||||
|
}
|
||||||
|
if (!is_null(getSingleParameter('lang', $currentLine))) {
|
||||||
|
$twtxtData->lang = getSingleParameter('lang', $currentLine);
|
||||||
|
}
|
||||||
|
if (!is_null(getSingleParameter('description', $currentLine))) {
|
||||||
|
$twtxtData->description = getSingleParameter('description', $currentLine);
|
||||||
|
// TODO - FIX BUG: only takes first word!
|
||||||
|
}
|
||||||
|
if (!is_null(getSingleParameter('follow', $currentLine))) {
|
||||||
|
$twtxtData->following[] = getSingleParameter('follow', $currentLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!str_starts_with($currentLine, '#')) {
|
||||||
|
$explodedLine = explode("\t", $currentLine);
|
||||||
|
if (count($explodedLine) >= 2) {
|
||||||
|
$dateStr = $explodedLine[0];
|
||||||
|
$twtContent = $explodedLine[1];
|
||||||
|
|
||||||
|
$twtContent = replaceMentionsFromTwt($twtContent);
|
||||||
|
|
||||||
|
// Convert HTML problematic characters
|
||||||
|
$twtContent = htmlentities($twtContent);
|
||||||
|
|
||||||
|
// Replace the Line separator character (U+2028)
|
||||||
|
// \u2028 is \xE2 \x80 \xA8 in UTF-8
|
||||||
|
// Check here: https://www.mclean.net.nz/ucf/
|
||||||
|
//$twtContent = str_replace("\xE2\x80\xA8", "<br>\n", $twtContent);
|
||||||
|
|
||||||
|
// For some reason I was having trouble finding this nomenclature
|
||||||
|
// that's why I leave the UTF-8 representation for future reference
|
||||||
|
$twtContent = str_replace("\u{2028}", "\n<br>\n", $twtContent);
|
||||||
|
$twtContent = replaceLinksFromTwt($twtContent);
|
||||||
|
$twtContent = replaceImagesFromTwt($twtContent);
|
||||||
|
$twtContent = replaceMarkdownLinksFromTwt($twtContent);
|
||||||
|
|
||||||
|
// Get and remote the hash
|
||||||
|
$hash = getReplyHashFromTwt($twtContent);
|
||||||
|
if ($hash) {
|
||||||
|
$twtContent = str_replace("(#$hash)", '', $twtContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Get mentions
|
||||||
|
$mentions = getMentionsFromTwt($twtContent);
|
||||||
|
|
||||||
|
|
||||||
|
// Get Lang metadata
|
||||||
|
|
||||||
|
if (($timestamp = strtotime($dateStr)) === false) {
|
||||||
|
//echo "The string ($dateStr) is incorrect";
|
||||||
|
// Incorrect date string, skip this twt
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$displayDate = getTimeElapsedString($timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Only 1 twt by second is allowed here
|
||||||
|
$twt = new Twt();
|
||||||
|
|
||||||
|
$twt->originalTwtStr = $currentLine;
|
||||||
|
$twt->hash = getHashFromTwt($currentLine, $twtxtData->mainURL);
|
||||||
|
$twt->fullDate = date('j F Y h:i:s A', $timestamp) . ' (UTC)';
|
||||||
|
$twt->displayDate = $displayDate;
|
||||||
|
$twt->content = $twtContent;
|
||||||
|
$twt->replyToHash = $hash;
|
||||||
|
$twt->mentions = $mentions;
|
||||||
|
$twt->avatar = $twtxtData->avatar;
|
||||||
|
$twt->emoji = $twtxtData->emoji;
|
||||||
|
$twt->nick = $twtxtData->nick;
|
||||||
|
$twt->mainURL = $twtxtData->mainURL;
|
||||||
|
|
||||||
|
$twtxtData->twts[$timestamp] = $twt;
|
||||||
|
// TODO: Interpret the content as markdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $twtxtData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertFollowingURL($urlString) {
|
||||||
|
// Check if it's a valid URL
|
||||||
|
// Retrieve the nickname, if didn't find a nick, ask for one
|
||||||
|
|
||||||
|
$originalCode = '
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
#~~~#
|
||||||
|
consectetur adipiscing elit.';
|
||||||
|
|
||||||
|
$text = '#~~~#';
|
||||||
|
$newText = '123' . PHP_EOL . $text;
|
||||||
|
$result = str_replace('#~~~#', $newText, $originalCode);
|
||||||
|
|
||||||
|
echo $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedFileName($filePath) {
|
||||||
|
return __DIR__ . '/../private/cache/' . hash('sha256', $filePath); // TODO: make better path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('str_starts_with')) {
|
||||||
|
function str_starts_with($haystack, $needle) {
|
||||||
|
return (string)$needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!function_exists('str_ends_with')) {
|
||||||
|
function str_ends_with($haystack, $needle) {
|
||||||
|
return $needle !== '' && substr($haystack, -strlen($needle)) === (string)$needle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!function_exists('str_contains')) {
|
||||||
|
function str_contains($haystack, $needle) {
|
||||||
|
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
load_twt_files.php
Normal file
61
load_twt_files.php
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
# Gets the followers from an URL and then gets all the Followers twtxt.txt files
|
||||||
|
# Intended to be run in the background
|
||||||
|
|
||||||
|
require_once("libs/session.php"); // TODO: Move all to base.php
|
||||||
|
require_once('libs/twtxt.php');
|
||||||
|
require_once('libs/hash.php');
|
||||||
|
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
$max_execution_time = intval($config['max_execution_time']);
|
||||||
|
if ($max_execution_time < 1) {
|
||||||
|
$max_execution_time = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('max_execution_time', $max_execution_time);
|
||||||
|
|
||||||
|
#ob_start();
|
||||||
|
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
$url = $config['public_txt_url'];
|
||||||
|
|
||||||
|
if (!empty($_GET['url'])) {
|
||||||
|
$url = $_GET['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
|
||||||
|
die('Not a valid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
#echo "Loading URL: $url<br>\n<br>\n";
|
||||||
|
#ob_flush();
|
||||||
|
|
||||||
|
const DEBUG_TIME_SECS = 300;
|
||||||
|
const PRODUCTION_TIME_SECS = 5;
|
||||||
|
$fileContent = getCachedFileContentsOrUpdate($url, PRODUCTION_TIME_SECS);
|
||||||
|
$fileContent = mb_convert_encoding($fileContent, 'UTF-8');
|
||||||
|
|
||||||
|
$fileLines = explode("\n", $fileContent);
|
||||||
|
|
||||||
|
$twtFollowingList = [];
|
||||||
|
foreach ($fileLines as $currentLine) {
|
||||||
|
if (str_starts_with($currentLine, '#')) {
|
||||||
|
if (!is_null(getDoubleParameter('follow', $currentLine))) {
|
||||||
|
$twtFollowingList[] = getDoubleParameter('follow', $currentLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load all the files
|
||||||
|
# Save a flag to know it's loading files in the background
|
||||||
|
foreach ($twtFollowingList as $following) {
|
||||||
|
#echo "Updating: $following[1]<br>\n";
|
||||||
|
#ob_flush();
|
||||||
|
updateCachedFile($following[1]);
|
||||||
|
}
|
||||||
|
#echo 'Finished';
|
||||||
|
#ob_flush();
|
||||||
|
|
||||||
|
header('Location: .');
|
||||||
|
exit();
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
require_once("partials/session.php"); // TODO: Move all to base.php
|
require_once("libs/session.php"); // TODO: Move all to base.php
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
|
|
@ -12,21 +12,26 @@
|
||||||
<body >
|
<body >
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
if($_SESSION['password']=="æøå123") // TODO: Replace by var from config.ini
|
//$config = parse_ini_file('private/config.ini');
|
||||||
{
|
//$password = $config['password'];
|
||||||
?>
|
|
||||||
|
if( isset($_SESSION['password'])) {
|
||||||
|
if($_SESSION['password']=="$password")
|
||||||
|
{
|
||||||
|
header("Location: /");
|
||||||
|
die();
|
||||||
|
?>
|
||||||
<h1>You are loggged in now</h1>
|
<h1>You are loggged in now</h1>
|
||||||
<form method="post" action="" id="logout_form">
|
<form method="post" action="" id="logout_form">
|
||||||
<input type="submit" name="page_logout" value="LOGOUT" style="background-color: #000; color: #f00; font-family: monospace;">
|
<input type="submit" name="page_logout" value="LOGOUT" style="background-color: #000; color: #f00; font-family: monospace;">
|
||||||
</form>
|
</form>
|
||||||
<?php
|
|
||||||
} else {
|
<?php } } else { ?>
|
||||||
?>
|
|
||||||
<h1>Log in:</h1>
|
<h1>Log in:</h1>
|
||||||
<form method="post" action="" id="login_form">
|
<form method="post" action="" id="login_form">
|
||||||
<input type="password" name="pass" placeholder="*******"><br>
|
<input type="password" name="pass"><br>
|
||||||
<input type="submit" name="submit_pass" value="Login">
|
<input type="submit" name="submit_pass" value="Login">
|
||||||
<p><font style="color:red;"><?php echo $error;?></font></p>
|
<p><font style="color:red;"><?php if(isset($error)) {echo $error;}?></font></p>
|
||||||
</form>
|
</form>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
101
new_twt.php
Normal file
101
new_twt.php
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
// TODO: Give a warning if the file is not found
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
if ($config['debug_mode']) {
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
$txt_file_path = $config['txt_file_path'];
|
||||||
|
$public_txt_url = $config['public_txt_url'];
|
||||||
|
$timezone = $config['timezone'];
|
||||||
|
|
||||||
|
require_once('libs/session.php');
|
||||||
|
|
||||||
|
|
||||||
|
if (!has_valid_session()) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$textareaValue = '';
|
||||||
|
if (isset($_GET['hash'])) {
|
||||||
|
$hash = $_GET['hash'];
|
||||||
|
$textareaValue = "(#$hash) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['submit'])) {
|
||||||
|
$new_post = filter_input(INPUT_POST, 'new_post');
|
||||||
|
$new_post = trim($new_post);
|
||||||
|
|
||||||
|
// Replace new lines for Line separator character (U+2028)
|
||||||
|
$new_post = str_replace("\n", "\u{2028}", $new_post);
|
||||||
|
// Remove Carriage return if needed
|
||||||
|
$new_post = str_replace("\r", '', $new_post);
|
||||||
|
|
||||||
|
// TODO: If twt is emply, show an error
|
||||||
|
/*
|
||||||
|
if ($new_post) {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check if we have a point to insert the next Twt
|
||||||
|
define('NEW_TWT_MARKER', "#~~~#\n");
|
||||||
|
|
||||||
|
if (!file_exists($txt_file_path)) {
|
||||||
|
echo 'twtxt.txt file does not exist. Check your config.';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($txt_file_path);
|
||||||
|
|
||||||
|
if (!date_default_timezone_set($timezone)) {
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
$twt = date('c') . "\t$new_post\n";
|
||||||
|
|
||||||
|
if (strpos($contents, NEW_TWT_MARKER) !== false) {
|
||||||
|
// Add the previous marker
|
||||||
|
// Take note that doesn't not work if twtxt file has CRLF line ending
|
||||||
|
// (which is wrong anyway)
|
||||||
|
$twt = NEW_TWT_MARKER . $twt;
|
||||||
|
$contents = str_replace(NEW_TWT_MARKER, $twt, $contents);
|
||||||
|
} else {
|
||||||
|
// Fall back if the marker is not found.
|
||||||
|
$contents .= $twt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add error handling if write to the file fails
|
||||||
|
// For example due to permissions problems
|
||||||
|
// https://www.w3docs.com/snippets/php/how-can-i-handle-the-warning-of-file-get-contents-function-in-php.html
|
||||||
|
$file_write_result = file_put_contents($txt_file_path, $contents);
|
||||||
|
|
||||||
|
header('Refresh:0; url=.');
|
||||||
|
exit;
|
||||||
|
} else { ?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>twtxt</title>
|
||||||
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href=".">twtxt</a></h1>
|
||||||
|
<form method="POST" class="column">
|
||||||
|
<div id="posting">
|
||||||
|
<textarea class="textinput" id="new_post" name="new_post"
|
||||||
|
rows="4" cols="100" autofocus required
|
||||||
|
placeholder="Your twt"><?= $textareaValue ?></textarea>
|
||||||
|
<br>
|
||||||
|
<input class="btn" type="submit" value="Post" name="submit">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php } ?>
|
||||||
137
partials/base.php
Normal file
137
partials/base.php
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
<?php
|
||||||
|
# Shows the timeline for a user
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
#
|
||||||
|
# url(string): Gets
|
||||||
|
# Default: public_txt_url in config.ini
|
||||||
|
#
|
||||||
|
# timeline_url(string) = Gets the timeline for that specificed URL (twtxt)
|
||||||
|
# Default: public_txt_url in config.ini
|
||||||
|
#
|
||||||
|
# page(int):
|
||||||
|
# Default: Page 1 of N
|
||||||
|
# If page is higher than N, shows nothing
|
||||||
|
#
|
||||||
|
# hash(string) =
|
||||||
|
#
|
||||||
|
|
||||||
|
require_once("libs/session.php"); // TODO: Move all to base.php
|
||||||
|
require_once('libs/twtxt.php');
|
||||||
|
require_once('libs/hash.php');
|
||||||
|
|
||||||
|
const TWTS_PER_PAGE = 50;
|
||||||
|
|
||||||
|
// TODO: Move twts per page to config.ini
|
||||||
|
// Add a fallback if the number tis invalid (it should be between 1 and 999)
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
//$url = $config['public_txt_url'];
|
||||||
|
|
||||||
|
|
||||||
|
// HACKED by sp@darch.dk
|
||||||
|
if(!empty($_GET['list'])) {
|
||||||
|
$url = "https://darch.dk/twtxt-lists/".$_GET['list'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$url = $config['public_txt_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if(isset($_GET['selectList'])){
|
||||||
|
if(!empty($_GET['lists'])) {
|
||||||
|
$url = "https://darch.dk/twtxt-lists/".$_GET['lists'];
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// $url = $config['public_txt_url'];
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
$url = $config['public_txt_url'];
|
||||||
|
//$url = "https://darch.dk/twtxt-lists/twtxt.txt";
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
|
if (!empty($_GET['url'])) {
|
||||||
|
$url = $_GET['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
|
||||||
|
die('Not a valid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
//$validSession = has_valid_session();
|
||||||
|
//echo("Valid session: $validSession");
|
||||||
|
|
||||||
|
$cacheRefreshTime = $config['cache_refresh_time'];
|
||||||
|
$fileContent = getCachedFileContentsOrUpdate($url, $cacheRefreshTime);
|
||||||
|
|
||||||
|
if ($fileContent === false) {
|
||||||
|
die("$url couldn't be retrieved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileContent = mb_convert_encoding($fileContent, 'UTF-8');
|
||||||
|
$fileLines = explode("\n", $fileContent);
|
||||||
|
$twtFollowingList = [];
|
||||||
|
|
||||||
|
if (!empty($_GET['twts'])) { // Show twts for some user
|
||||||
|
$twtsURL = $_GET['twts'];
|
||||||
|
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
|
||||||
|
die('Not a valid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsedTwtxtFile = getTwtsFromTwtxtString($twtsURL);
|
||||||
|
if (!is_null($parsedTwtxtFile)) {
|
||||||
|
$parsedTwtxtFiles[$parsedTwtxtFile->mainURL] = $parsedTwtxtFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Show timeline for the URL
|
||||||
|
$parsedTwtxtFiles = [];
|
||||||
|
foreach ($fileLines as $currentLine) {
|
||||||
|
if (str_starts_with($currentLine, '#')) {
|
||||||
|
if (!is_null(getDoubleParameter('follow', $currentLine))) {
|
||||||
|
$follow = getDoubleParameter('follow', $currentLine);
|
||||||
|
$twtFollowingList[] = $follow;
|
||||||
|
|
||||||
|
// Read the parsed files if in Cache
|
||||||
|
$followURL = $follow[1];
|
||||||
|
$parsedTwtxtFile = getTwtsFromTwtxtString($followURL);
|
||||||
|
if (!is_null($parsedTwtxtFile)) {
|
||||||
|
$parsedTwtxtFiles[$parsedTwtxtFile->mainURL] = $parsedTwtxtFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$twts = [];
|
||||||
|
|
||||||
|
# Combine all the followers twts
|
||||||
|
foreach ($parsedTwtxtFiles as $currentTwtFile) {
|
||||||
|
if (!is_null($currentTwtFile)) {
|
||||||
|
$twts += $currentTwtFile->twts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['hash'])) {
|
||||||
|
$hash = $_GET['hash'];
|
||||||
|
$twts = array_filter($twts, function($twt) use ($hash) {
|
||||||
|
return $twt->hash === $hash || $twt->replyToHash === $hash;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
krsort($twts, SORT_NUMERIC);
|
||||||
|
|
||||||
|
if (!empty($_GET['hash'])) {
|
||||||
|
$twts = array_reverse($twts, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = 1;
|
||||||
|
if (!empty($_GET['page'])) {
|
||||||
|
$page = intval($_GET['page']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$startingTwt = (($page - 1) * TWTS_PER_PAGE);
|
||||||
|
$twts = array_slice($twts, $startingTwt, TWTS_PER_PAGE);
|
||||||
3
partials/footer.php
Normal file
3
partials/footer.php
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<footer><center><small>
|
||||||
|
© 2023 Søren Peter Mørch
|
||||||
|
</small></center></footer>
|
||||||
21
partials/header.php
Normal file
21
partials/header.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".">🧶 Timeline</a></li>
|
||||||
|
<li><a href="load_twt_files.php?url=<?= $url ?>">Refresh</a></li>
|
||||||
|
<?php //if ($validSession) { // TODO: Make login seqcure ?>
|
||||||
|
<?php if( isset($_SESSION['password'])) {
|
||||||
|
if($_SESSION['password']=="$password") { // Hacky login ?>
|
||||||
|
<li><a href="new_twt.php">New post</a></li>
|
||||||
|
<li><a href="follow.php">Add feed</a></li>
|
||||||
|
<li><form method="post" action="" id="logout_form">
|
||||||
|
<input type="submit" name="page_logout" value="Log out" class="link-btn">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<?php } } else { ?>
|
||||||
|
<li><a href="login.php">Log in</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><?php include 'partials/listSelect.php'; ?></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
41
partials/listSelect.php
Normal file
41
partials/listSelect.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!-- List UI -->
|
||||||
|
<form action="" method="get">
|
||||||
|
<!-- Select a list: -->
|
||||||
|
<select name="list" onchange="this.form.submit()">
|
||||||
|
<option value="twtxt.txt" selected>twtxt.txt (Main)</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if( isset($_SESSION['password'])) {
|
||||||
|
if($_SESSION['password']=="$password") { // Hacky login
|
||||||
|
|
||||||
|
// Private lists
|
||||||
|
echo "<option disabled>Private Lists:</option>";
|
||||||
|
foreach (glob("private/twtxt-*.txt") as $filename) {
|
||||||
|
if($filename == $_GET['lists']) $attr="selected";
|
||||||
|
else $attr = "";
|
||||||
|
$listName = $filename;
|
||||||
|
$listName = str_replace("private/twtxt-", "", $listName);
|
||||||
|
$listName = str_replace("_", " ", $listName);
|
||||||
|
$listName = str_replace(".txt", "", $listName);
|
||||||
|
echo "<option value='{$filename}' {$attr}>$listName</option>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public Lists
|
||||||
|
echo "<option disabled>Public Lists:</option>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (glob("lists/twtxt-*.txt") as $filename) {
|
||||||
|
if($filename == $_GET['lists']) $attr="selected";
|
||||||
|
else $attr = "";
|
||||||
|
$listName = $filename;
|
||||||
|
$listName = str_replace("lists/twtxt-", "", $listName);
|
||||||
|
$listName = str_replace("_", " ", $listName);
|
||||||
|
$listName = str_replace(".txt", "", $listName);
|
||||||
|
echo "<option value='{$filename}' {$attr}>$listName</option>";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
<noscript><button type="submit">View list</button></noscript>
|
||||||
|
</form>
|
||||||
0
partials/nav-bar.php
Normal file
0
partials/nav-bar.php
Normal file
41
partials/profile.php
Normal file
41
partials/profile.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Get info about profile from URL as an objects
|
||||||
|
if (!empty($_GET['twts'])) {
|
||||||
|
$url = $twtsURL;
|
||||||
|
}
|
||||||
|
$profile = getTwtsFromTwtxtString($url);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="profile">
|
||||||
|
|
||||||
|
<a href="<?= $profile->mainURL ?>">
|
||||||
|
<img class="avatar" src="<?= $profile->avatar ?>" alt="" loading="lazy">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<a href="<?= $profile->mainURL ?>" class="author">
|
||||||
|
<strong><?= $profile->nick ?></strong>@<?= parse_url($profile->mainURL, PHP_URL_HOST); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<blockquote><?= $profile->description ?></blockquote>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="">Posts</a></li>
|
||||||
|
<li><a href="">Replies</a></li>
|
||||||
|
<li><a href="">Gallery</a></li>
|
||||||
|
<li><a target="_blank" href="<?= $profile->mainURL ?>"></i>twtxt.txt</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- <aside>
|
||||||
|
<small>
|
||||||
|
<a href="https://yarn.social" class="button">How to follow...</a>
|
||||||
|
</small>
|
||||||
|
</aside> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
37
partials/timeline.php
Normal file
37
partials/timeline.php
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!-- <center>
|
||||||
|
<?php if (!empty($_GET['twts'])) { ?>
|
||||||
|
<em>Twts for <a href="<?= $twtsURL ?>"><?= $twtsURL ?></a></em>
|
||||||
|
<?php } else { ?>
|
||||||
|
<em>Timeline for <a href="<?= $url ?>"><?= $url ?></a></em>
|
||||||
|
<?php } ?>
|
||||||
|
</center> -->
|
||||||
|
|
||||||
|
<?php foreach ($twts as $twt) { ?>
|
||||||
|
|
||||||
|
<article class="post-entry">
|
||||||
|
<a href="?twts=<?= $twt->mainURL ?>">
|
||||||
|
<img src='<?= $twt->avatar ?>' class="avatar" onerror="this.onerror=null;this.src='imgs/image_not_found.png';">
|
||||||
|
</a>
|
||||||
|
<div class="">
|
||||||
|
<a href="?twts=<?= $twt->mainURL ?>" class="author">
|
||||||
|
<strong><?= $twt->nick ?></strong>@<?= parse_url($twt->mainURL, PHP_URL_HOST); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="twt-msg">
|
||||||
|
<?= $twt->content ?>
|
||||||
|
<?php foreach ($twt->mentions as $mention) { ?>
|
||||||
|
<br><?= $mention['nick'] ?>(<?= $mention['url'] ?>)
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
<?php if($twt->replyToHash) { ?>
|
||||||
|
<a href="?hash=<?= $twt->replyToHash?>">Conversation</a> |
|
||||||
|
<?php } ?>
|
||||||
|
<a href="new_twt.php?hash=<?= $twt->hash ?>">Reply</a>
|
||||||
|
<a href='?hash=<?= $twt->hash ?>' class="right"><span title="<?= $twt->fullDate ?> "><?= $twt->displayDate ?></span></a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<?php } ?>
|
||||||
0
php_server.sh
Normal file → Executable file
0
php_server.sh
Normal file → Executable file
43
private/config.ini
Normal file
43
private/config.ini
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
; timeline / twtxt-php
|
||||||
|
; Copy this file into a config.ini file and edit the following settings
|
||||||
|
|
||||||
|
[main_settings]
|
||||||
|
; Enable to display PHP errors
|
||||||
|
; true or false
|
||||||
|
debug_mode = false
|
||||||
|
|
||||||
|
; Time to wait before reloading URLs
|
||||||
|
cache_refresh_time = 15
|
||||||
|
|
||||||
|
; Max execution time to avoid running to the infinite
|
||||||
|
max_execution_time = 300
|
||||||
|
|
||||||
|
; Check that your current user has permissions for this file
|
||||||
|
; Check also the user owner is correct, www-data for instance
|
||||||
|
; TODO: Implement a way to know we have access to this file
|
||||||
|
; since there are many different conditions for not having
|
||||||
|
; access.
|
||||||
|
txt_file_path = "twtxt.txt"
|
||||||
|
|
||||||
|
; Full URL for your public twtxt.txt file
|
||||||
|
public_txt_url = "https://darch.dk/twtxt-lists/twtxt.txt"
|
||||||
|
public_avatar = "https://darch.dk/twtxt-lists/avatar.png"
|
||||||
|
public_nick = "Timeline-DEV"
|
||||||
|
|
||||||
|
; Check available timezones here:
|
||||||
|
; https://www.php.net/manual/en/timezones.php
|
||||||
|
timezone = "Europe/Copenhagen"
|
||||||
|
|
||||||
|
twts_per_page = 50
|
||||||
|
|
||||||
|
[security]
|
||||||
|
; Generate it with the TOTP module
|
||||||
|
totp_digits = 10
|
||||||
|
totp_secret = "LZM25BDJPRVTNFZQBDOPQKFSKUAAS6BI"
|
||||||
|
|
||||||
|
; It's recommended that your site is hosted on HTTPS
|
||||||
|
; In case it's in HTTP (not secure), set this to false
|
||||||
|
secure_cookies = true
|
||||||
|
|
||||||
|
; Simple password for unnamed user
|
||||||
|
password = ""
|
||||||
101
session.php
Normal file
101
session.php
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
const COOKIE_NAME = 'remember_user';
|
||||||
|
const ENCRYPTION_METHOD = 'aes-256-cbc';
|
||||||
|
|
||||||
|
session_start([
|
||||||
|
'name' => 'twtxt_session',
|
||||||
|
'use_strict_mode' => true,
|
||||||
|
'cookie_httponly' => true,
|
||||||
|
'cookie_secure' => $config['secure_cookies'],
|
||||||
|
'sid_length' => 64,
|
||||||
|
'sid_bits_per_character' => 6,
|
||||||
|
'cookie_samesite' => 'Strict', // Not compatible with PHP lower than 7.3
|
||||||
|
]);
|
||||||
|
|
||||||
|
function has_valid_session() {
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
$secretKey = $config['password'];
|
||||||
|
|
||||||
|
if (isset($_SESSION['valid_session'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cookieVal = decodeCookie($secretKey);
|
||||||
|
if ($cookieVal === false) {
|
||||||
|
#echo "Invalid cookie";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encrypt(string $data, string $key, string $method): string {
|
||||||
|
$ivSize = openssl_cipher_iv_length($method);
|
||||||
|
$iv = openssl_random_pseudo_bytes($ivSize);
|
||||||
|
$encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
|
||||||
|
# PHP 8.2 - Deprecated: implode():
|
||||||
|
# Passing null to parameter #1 ($separator) of type array|string is deprecated
|
||||||
|
//$encrypted = strtoupper(implode(null, unpack('H*', $encrypted)));
|
||||||
|
$encrypted = strtoupper(implode(unpack('H*', $encrypted)));
|
||||||
|
|
||||||
|
return $encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrypt(string $data, string $key, string $method): string {
|
||||||
|
$data = pack('H*', $data);
|
||||||
|
$ivSize = openssl_cipher_iv_length($method);
|
||||||
|
$iv = openssl_random_pseudo_bytes($ivSize);
|
||||||
|
$decrypted = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
|
||||||
|
|
||||||
|
return trim($decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLoginSuccess($secretKey) {
|
||||||
|
// Set a cookie to remember the user
|
||||||
|
$_SESSION['valid_session'] = true;
|
||||||
|
|
||||||
|
// Set a cookie value to remember the user
|
||||||
|
$encoded_cookie_value = generateCookieValue('admin', $secretKey);
|
||||||
|
$cookie_expiry = time() + (30 * 24 * 60 * 60); // 30 days
|
||||||
|
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
setcookie(COOKIE_NAME, $encoded_cookie_value, [
|
||||||
|
'expires' => $cookie_expiry,
|
||||||
|
'secure' => $config['secure_cookies'],
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCookieValue($username, $secretKey) {
|
||||||
|
$key = bin2hex($secretKey);
|
||||||
|
|
||||||
|
$encrypted = encrypt($username, $key, ENCRYPTION_METHOD);
|
||||||
|
return $encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeCookie($secretKey) {
|
||||||
|
// Retrieve the encoded cookie name
|
||||||
|
if (!isset($_COOKIE[COOKIE_NAME])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$encoded_cookie_value = $_COOKIE[COOKIE_NAME];
|
||||||
|
$key = bin2hex($secretKey);
|
||||||
|
|
||||||
|
$config = parse_ini_file('private/config.ini');
|
||||||
|
|
||||||
|
// Extend expiry by 30 days
|
||||||
|
$cookie_expiry = time() + (30 * 24 * 60 * 60);
|
||||||
|
setcookie(COOKIE_NAME, $encoded_cookie_value, [
|
||||||
|
'expires' => $cookie_expiry,
|
||||||
|
'secure' => $config['secure_cookies'],
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$decrypted = decrypt($encoded_cookie_value, $key, ENCRYPTION_METHOD);
|
||||||
|
return $decrypted;
|
||||||
|
}
|
||||||
261
style.css
Normal file
261
style.css
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
* {
|
||||||
|
/* border: thin solid pink;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* from PaperMod: https://github.com/adityatelange/hugo-PaperMod/tree/54a3c6073518005182f3c3250ddb7e8c0cacd7ad/assets/css */
|
||||||
|
:root {
|
||||||
|
--gap: 1rem;
|
||||||
|
--content-gap: 20px;
|
||||||
|
--nav-width: 1024px;
|
||||||
|
--main-width: 720px;
|
||||||
|
--header-height: 60px;
|
||||||
|
--footer-height: 60px;
|
||||||
|
--radius: 8px;
|
||||||
|
--theme: rgb(255, 255, 255);
|
||||||
|
--entry: rgb(255, 255, 255);
|
||||||
|
--primary: rgb(30, 30, 30);
|
||||||
|
--secondary: rgb(108, 108, 108);
|
||||||
|
--tertiary: rgb(214, 214, 214);
|
||||||
|
--content: rgb(31, 31, 31);
|
||||||
|
--hljs-bg: rgb(28, 29, 33);
|
||||||
|
--code-bg: rgb(245, 245, 245);
|
||||||
|
--border: rgb(238, 238, 238);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--code-bg);
|
||||||
|
font-family: sans-serif;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 1rem auto;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, button, body, h1, h2, h3, h4, h5, h6 {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Forms (from https://adi.tilde.institute/default.css/) ==================== */
|
||||||
|
|
||||||
|
form {
|
||||||
|
align-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea, select {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
/* margin: .5ex 0 1ex 0;*/
|
||||||
|
margin-top: -0.5rem;
|
||||||
|
padding: 1ex .5rem;
|
||||||
|
border: thin solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
/*width: 100%;*/
|
||||||
|
font-size: 1em;
|
||||||
|
background-color: var(--entry);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="file"] {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox], input[type=radio] {
|
||||||
|
display: inline;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--input-box);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="submit"],
|
||||||
|
a.button, button {
|
||||||
|
background-color: var(--pod-color);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: thin solid var(--pod-color);
|
||||||
|
color: var(--button-text);
|
||||||
|
display: block;
|
||||||
|
/* margin-top: -1rem;*/
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 1ex 1rem;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary {
|
||||||
|
width: 100%;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: thin solid var(--border-color);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hr {
|
||||||
|
background: var(--border-color);
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: calc(var(--nav-width) + var(--gap) * 2);
|
||||||
|
margin-inline-start: auto;
|
||||||
|
margin-inline-end: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li {
|
||||||
|
display: inline-block;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav form {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .link-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: bold;
|
||||||
|
/* text-decoration: underline;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: contain;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.avatar {
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.author {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
padding: 0 1rem;
|
||||||
|
display: grid;
|
||||||
|
/* grid-template-columns: 4rem 3fr 1fr;*/
|
||||||
|
grid-template-columns: 4rem 1fr;
|
||||||
|
grid-gap: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile img.avatar {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
/* border-radius: 100%;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .author {
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.profile blockquote {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile nav a {
|
||||||
|
font-size: small;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
background-color: var(--entry);
|
||||||
|
color: var(--secondary);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border: thin solid var(--border);
|
||||||
|
margin: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.post-entry {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--gap);
|
||||||
|
grid-template-columns: 48px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
article .twt-msg {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article .twt-msg img {
|
||||||
|
margin: 0.25rem -0.25rem;
|
||||||
|
border: thin solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
article .twt-msg > img:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article .twt-msg > img:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article small {
|
||||||
|
padding-left: 0.15rem ;
|
||||||
|
}
|
||||||
|
|
||||||
|
article small .right{
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.pagnation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: thin solid var(--border);
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
559
style_pixelblog.css
Normal file
559
style_pixelblog.css
Normal file
|
|
@ -0,0 +1,559 @@
|
||||||
|
/* == Variables == */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--roundness: 0.25rem;
|
||||||
|
--pod-color: #222;
|
||||||
|
/*--pod-muted: #3623BD;*/
|
||||||
|
/*--bg-body: #fff;*/
|
||||||
|
/*--bg-post: #F7F7F7;*/
|
||||||
|
/*--bg-code: #eee;*/
|
||||||
|
/*--code-color: #D22F27;*/
|
||||||
|
/*--text-color: #444;*/
|
||||||
|
/*--text-small: #aaa;*/
|
||||||
|
/*--icon-color: #888;*/
|
||||||
|
--link-color: blue;
|
||||||
|
--link-visited: purple;
|
||||||
|
--link-active: red;
|
||||||
|
/*--link-nav: #222;*/
|
||||||
|
/*--input-box: #fff;*/
|
||||||
|
--input-border: #ccc;
|
||||||
|
--button-text: #fff;
|
||||||
|
--border-color: #ccc;
|
||||||
|
--warning: #941100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Meta Classes ======================================== */
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float: left;
|
||||||
|
/*text-align: left;*/
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
float: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
/*text-align: right;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.avoidwrap {
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullwidth {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minimal grid system with auto-layout columns (from pico.css) */
|
||||||
|
|
||||||
|
/* .grid {
|
||||||
|
grid-column-gap: 1rem;
|
||||||
|
grid-row-gap: 1rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin: 0; }
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(0%, 1fr)); } }
|
||||||
|
.grid > * {
|
||||||
|
min-width: 0; }*/
|
||||||
|
|
||||||
|
/* == Typography ============================ */
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 85ch;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
background-color: var(--bg-body);
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
body, input {
|
||||||
|
font-family: system-ui, -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; /* To use the system default font. */
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--link-color);
|
||||||
|
/*text-decoration: none;*/
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: var(--primary);
|
||||||
|
/*color: var(--link-visited);*/
|
||||||
|
/*text-decoration: none;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Icons ======================== */
|
||||||
|
|
||||||
|
.ti {
|
||||||
|
color: var(--icon-color);
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Header / Navigation ======================= */
|
||||||
|
|
||||||
|
.edit {
|
||||||
|
background-color: yellow;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: block;
|
||||||
|
/*margin-top: 0.5rem;*/
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a,
|
||||||
|
header a:visited {
|
||||||
|
color: var(--pod-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
nav ul li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0.2rem;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a, nav a:visited {
|
||||||
|
color: var(--link-nav);
|
||||||
|
/*text-decoration: none;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Forms (from https://adi.tilde.institute/default.css/) ==================== */
|
||||||
|
|
||||||
|
form {
|
||||||
|
align-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea, select {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
/* margin: .5ex 0 1ex 0;*/
|
||||||
|
/* padding: 1ex .5em;*/
|
||||||
|
/* padding: 0.25rem;*/
|
||||||
|
border: thin solid var(--input-border);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
/*width: 100%;*/
|
||||||
|
font-size: 1em;
|
||||||
|
background-color: var(--input-box);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="file"] {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox], input[type=radio] {
|
||||||
|
display: inline;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--input-box);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="submit"],
|
||||||
|
a.button, button {
|
||||||
|
background-color: var(--pod-color);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
border: thin solid var(--pod-color);
|
||||||
|
color: var(--button-text);
|
||||||
|
display: block;
|
||||||
|
/* margin-top: -1rem;*/
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 1ex 1rem;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary {
|
||||||
|
width: 100%;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: thin solid var(--border-color);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hr {
|
||||||
|
background: var(--border-color);
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar img {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Timeline styline ========================== */
|
||||||
|
|
||||||
|
/* Post with outline */
|
||||||
|
article.h-entry {
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: var(--bg-post);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
/*border: solid thin var(--border-color);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-author {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
/*align-items: center;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
article .p-name {
|
||||||
|
display: block;
|
||||||
|
left: 0px !important;
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*font-size: 34px;*/
|
||||||
|
font-size: 1.2rem;
|
||||||
|
/*color: #1095c1;*/
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.p-name, a.p-name:visited {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--link-nav);
|
||||||
|
}
|
||||||
|
|
||||||
|
article .p-org,
|
||||||
|
article .p-summary a em,
|
||||||
|
article .p-summary p a em {
|
||||||
|
font-style: normal;
|
||||||
|
left: 0px !important;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--text-small);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-summary {
|
||||||
|
/*padding: 0.5rem;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.author a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.author nav ul li {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Profile ===================== */
|
||||||
|
|
||||||
|
/* .profileCard {
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: var(--bg-post);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
border: solid thin var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard .grid {
|
||||||
|
grid-template-columns: 3fr 1fr;
|
||||||
|
grid-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard img {
|
||||||
|
height: 4.5rem;
|
||||||
|
width: 4.5rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard div.bio {
|
||||||
|
margin-top: -0.75em;
|
||||||
|
margin-left: 1.7em;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard a.u-url.p-name {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard .p-org {
|
||||||
|
color: var(--text-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard blockquote {
|
||||||
|
margin: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard aside {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard small.compact {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard summary {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard details {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard a.button {
|
||||||
|
color: var(--button-text);
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-weight: 600;*/
|
||||||
|
/*color: var(--bg-post);*/
|
||||||
|
/*background-color: var(--button-bg);*/
|
||||||
|
/*border-radius: var(--roundness);*/
|
||||||
|
/*border: solid thin #FFF; var(--bg-body);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .profileCard a.button.off {
|
||||||
|
background-color: var(--pod-color);
|
||||||
|
color: var(--button-text);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/* == Footer ===================== */
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-top: 1px solid var(--pod-color);
|
||||||
|
padding: 0.25rem;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer nav ul li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a, footer a:visited,
|
||||||
|
footer a .ti, footer a:visited .ti {
|
||||||
|
color: var(--pod-color);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Styling (from: vanillacss.com) */
|
||||||
|
@media screen and (max-width: 85ch) {
|
||||||
|
|
||||||
|
table { table-layout: auto; }
|
||||||
|
|
||||||
|
.right { float: none; }
|
||||||
|
|
||||||
|
/*.twt-hash { float: right; }*/
|
||||||
|
|
||||||
|
.profileCard .grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.pod-menu ul.right {
|
||||||
|
display: grid;
|
||||||
|
/*grid-template-columns: auto auto auto; */
|
||||||
|
font-size: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.user-menu {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.user-menu ul {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.toolbar-nav ul.right {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.toolbar-nav #post,
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer nav ul {
|
||||||
|
margin: 0.25rem auto;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* === Picoblog.css by darch.dk */
|
||||||
|
|
||||||
|
main li {
|
||||||
|
/*background-color: var(--bg-post); */
|
||||||
|
margin-left: -2.5rem;
|
||||||
|
list-style: none;
|
||||||
|
/*border: solid thin var(--border-color);*/
|
||||||
|
/*border-radius: var(--roundness);*/
|
||||||
|
padding: 10px;
|
||||||
|
/*margin-bottom: 0.5rem;*/
|
||||||
|
/*border-top: solid thin var(--border-color);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
li a.date {
|
||||||
|
display: block;
|
||||||
|
font-size: small;
|
||||||
|
text-align: right;
|
||||||
|
color: var(--text-small);
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-bottom: solid thin var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
li a.date:hover {
|
||||||
|
/*color: var(--link-active);*/
|
||||||
|
/*border-color: var(--link-active);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* http://jsbin.com/giqovotudi/edit?html,css,output */
|
||||||
|
|
||||||
|
p.grid { /*https://smolcss.dev/*/
|
||||||
|
--min: 15ch;
|
||||||
|
--gap: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
gap: var(--gap);
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
p.grid > a.image {
|
||||||
|
flex: 1 1 var(--min);
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
/*margin-top: 0.5rem;*/
|
||||||
|
max-width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
/* border: thin solid var(--border-color);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* == Timeline ===================== */
|
||||||
|
|
||||||
|
/*.timeline a img {
|
||||||
|
display: block;
|
||||||
|
cursor:zoom-in;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
p.grid a img {
|
||||||
|
margin: -0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 760px) {
|
||||||
|
body {
|
||||||
|
/* Center body in page */
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: -0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-left: -2.5rem;
|
||||||
|
/*margin-right: 0.5rem;*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Gallery ===================== */
|
||||||
|
|
||||||
|
.gallery {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto ;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 0.75rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
/*grid-auto-rows: 225px;*/
|
||||||
|
/*grid-auto-flow: dense;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery a img {
|
||||||
|
/* https://web.dev/aspect-ratio/ */
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 100%;
|
||||||
|
/*height: 100%;*/
|
||||||
|
/*object-fit: cover;*/
|
||||||
|
/*background-color: var(--border-color);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* == Posting / Upload ===================== */
|
||||||
|
|
||||||
|
.upload .ti,
|
||||||
|
.posting .ti {
|
||||||
|
color: var(--code-color);
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload img {
|
||||||
|
width: 12ch;
|
||||||
|
height: initial;
|
||||||
|
/*margin: 0.25rem auto;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload code {
|
||||||
|
padding: 0.25rem;
|
||||||
|
color: var(--code-color);
|
||||||
|
background-color: var(--input-box);
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload a.button {
|
||||||
|
width: fit-content;
|
||||||
|
block-size: fit-content;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
}
|
||||||
31
wip_todo/router.php
Normal file
31
wip_todo/router.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$request = $_SERVER['REQUEST_URI'];
|
||||||
|
$viewDir = '/views/';
|
||||||
|
|
||||||
|
switch ($request) {
|
||||||
|
case '':
|
||||||
|
case '/':
|
||||||
|
require __DIR__ . $viewDir . 'home.php';
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case '/login':
|
||||||
|
// require __DIR__ . $viewDir . 'login.php';
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case '/following':
|
||||||
|
// require __DIR__ . $viewDir . 'following.php';
|
||||||
|
// break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// [PHP FOR BEGINNERS #4 - Create a dynamic Router - YouTube](https://www.youtube.com/watch?v=eaHBK2XJ5Io)
|
||||||
|
$filename = __DIR__ . $viewDir . $request . ".php";
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
require $filename;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
echo "<h1>Oops!</h1>";
|
||||||
|
//require __DIR__ . $viewDir . '404.php';
|
||||||
|
}
|
||||||
32
wip_todo/views/following.php
Normal file
32
wip_todo/views/following.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php require_once("partials/base.php"); ?>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||||
|
<title><?=$title?> - Timeline</title>
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
|
||||||
|
<!-- PHP: GET HEADER --><?php include 'partials/header.php';?>
|
||||||
|
|
||||||
|
|
||||||
|
<strong>Following: <?php echo count($twtFollowingList); ?> feeds</strong>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th></th><th>Nick</th><th>URL</th></tr>
|
||||||
|
<?php foreach ($twtFollowingList as $currentFollower) { ?>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><a href="?url=<?= $currentFollower[1] ?>"><?= $currentFollower[0] ?></a></td>
|
||||||
|
<td><?= $currentFollower[1] ?>
|
||||||
|
<!-- <?php if ($validSession) { ?> -->
|
||||||
|
<!-- <a href="?remove_url=<?= $currentFollower[1] ?>">Remove</a> -->
|
||||||
|
<!-- <?php } ?> -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php } ?>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
wip_todo/views/gallery.php
Normal file
28
wip_todo/views/gallery.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!-- PHP: GET CONFIG --><?php include '../config.php' ?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" type="text/css" media="all" href="../layout/default.css?v=1.1">
|
||||||
|
<link rel="stylesheet" type="text/css" media="all" href="../custom.css?v=1.1">
|
||||||
|
<title><?=$title?> - Gallery</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- PHP: GET HEADER --><?php include $base_path.'layout/header.php';?>
|
||||||
|
|
||||||
|
<!-- PHP: GET PROFILE CARD --><?php include $base_path.'layout/profile.php';?>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="gallery">
|
||||||
|
|
||||||
|
<?php include $base_path.'system/gallery.php' ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PHP: GET FOOTER --><?php include $base_path.'layout/footer.php';?>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
wip_todo/views/home.php
Normal file
53
wip_todo/views/home.php
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php require_once("partials/base.php"); ?>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||||
|
<title><?=$title?> - Timeline</title>
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
|
||||||
|
<nav class="edit">
|
||||||
|
<?php
|
||||||
|
// $config = parse_ini_file('private/config.ini');
|
||||||
|
// $password = $config['password'];
|
||||||
|
|
||||||
|
if($_SESSION['password']=="$password")
|
||||||
|
{
|
||||||
|
?>
|
||||||
|
<small>You are loggged in</small>
|
||||||
|
|
||||||
|
<form method="post" action="" id="logout_form">
|
||||||
|
<input type="submit" name="page_logout" value="Log out" class="right">
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
} else {
|
||||||
|
?>
|
||||||
|
<small><a href="/login">Log in</a></small>
|
||||||
|
<?php } ?>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- PHP: GET HEADER --><?php include 'partials/header.php';?>
|
||||||
|
|
||||||
|
<!-- PHP: GET PROFILE CARD --><?php include 'partials/profile.php';?>
|
||||||
|
|
||||||
|
<main class="timeline">
|
||||||
|
|
||||||
|
<center><h3>
|
||||||
|
<?php if (!empty($_GET['twts'])) { ?>
|
||||||
|
<em>Twts for <a href="<?= $twtsURL ?>"><?= $twtsURL ?></a></em>
|
||||||
|
<?php } else { ?>
|
||||||
|
<em>Timeline for <a href="<?= $url ?>"><?= $url ?></a></em>
|
||||||
|
<?php } ?>
|
||||||
|
</h3></center>
|
||||||
|
|
||||||
|
<!-- PHP: GET TIMELIE --><?php include 'partials/timeline.php'?>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- PHP: GET FOOTER --><?php include 'partials/footer.php';?>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in a new issue