mirror of
https://github.com/sorenpeter/timeline.git
synced 2025-12-15 10:57:01 +00:00
615 lines
18 KiB
PHP
615 lines
18 KiB
PHP
<?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);
|
|
}
|
|
|
|
$agentVersion = trim(file_get_contents('./VERSION'));
|
|
|
|
class TwtxtFile {
|
|
public $mainURL = ''; // First found URL
|
|
public $URLs = [];
|
|
public $nick = '';
|
|
public $domain = '';
|
|
public $avatar = '';
|
|
public $emoji = '';
|
|
public $description = '';
|
|
public $lang = 'en'; // Default language
|
|
public $links = [];
|
|
public $following = [];
|
|
public $twts = [];
|
|
}
|
|
|
|
class Twt {
|
|
public $originalTwtStr;
|
|
public $hash;
|
|
public $timestamp;
|
|
public $fullDate;
|
|
public $displayDate;
|
|
public $content;
|
|
public $replyToHash;
|
|
public $mentions;
|
|
public $avatar;
|
|
public $emoji;
|
|
public $nick;
|
|
public $domain;
|
|
public $mainURL;
|
|
public $images = [];
|
|
public $tags = [];
|
|
}
|
|
|
|
# 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*(?<!\S)' . $keyToFind . '\s*=\s*([^#\n]+)/';
|
|
// Fix: not machting with nick as in: `# follow = dbucklin@www.davebucklin.com https://www.davebucklin.com/twtxt.txt?nick=dbucklin`
|
|
//$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 getImagesFromTwt(string $twtString) {
|
|
$pattern = '/(<img[^>]+>)/i';
|
|
preg_match_all($pattern, $twtString, $matches, PREG_SET_ORDER);
|
|
|
|
$result = array();
|
|
|
|
foreach ($matches as $match) {
|
|
$result[] = array($match[0]);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
function getTagsFromTwt(string $twtString) {
|
|
//$pattern = '/(?<!\()\B#\w+(?!\))/iu';
|
|
//$pattern = '/(?<=\B)#(\w+)/';
|
|
$pattern = '/(?<=\B)#([\p{L}\p{N}_]+)/u';
|
|
//$pattern = '/(?<=\s)#(\w+)/';
|
|
//$pattern = '/\s#(\w+)/';
|
|
//$pattern = "/\(#\w{7}\)/";
|
|
//$pattern = '/(?<=\s|^)#(\w+)/';
|
|
// TODO: Fix so it does not match with url#fragments (\B vs \s)
|
|
// But for some reason this does not work: '/(?<!\()\s#\w+(?!\))/iu';
|
|
|
|
preg_match_all($pattern, $twtString, $matches, PREG_SET_ORDER);
|
|
|
|
$result = array();
|
|
|
|
foreach ($matches as $match) {
|
|
$result[] = array($match[0]);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
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>';
|
|
$replacement = '<a href="' . str_replace("/index.php", "", $_SERVER["SCRIPT_NAME"]) . '/profile?url=$2">@$1</a>';
|
|
//$replacement .= '<a href="$2" class="webmention"></a>'; // Adds a hidden link direcly to the twtxt.txt of the mentioned target
|
|
#$twtString = '@<nick https://eapl.mx/twtxt.txt>';
|
|
#$pattern = '/@<([^ ]+) ([^>]+)>/';
|
|
#$replacement = '@$1';
|
|
|
|
/*
|
|
$pattern_only_url = '/@<(https?:\/\/[^>]+)>/';
|
|
if (preg_match($pattern_only_url, $twtString)) {
|
|
$replacement = '<a href="' . str_replace("/index.php", "", $_SERVER["SCRIPT_NAME"]) . '/profile?url=$1">@$1</a>';
|
|
}
|
|
*/
|
|
|
|
$result = preg_replace($pattern, $replacement, $twtString);
|
|
return $result;
|
|
|
|
// from https://github.com/hxii/picoblog/blob/master/picoblog.php
|
|
//$pattern = '/\@<([a-zA-Z0-9\.]+)\W+(https?:\/\/[^>]+)>/';
|
|
//return preg_replace($pattern,'<a href="$2">@$1</a>',$twtString);
|
|
}
|
|
|
|
function replaceLinksFromTwt(string $twtString) {
|
|
|
|
// TODO: Make this NOT match with `inline code` to avoid links in code-snippets
|
|
// 1. Look into how yarnd handles this
|
|
|
|
// Regular expression pattern to match URLs
|
|
//$pattern = '/(?<!\S)(\b(https?|ftp|gemini|spartan|gopher):\/\/\S+|\b(?!:\/\/)\w+(?:\.\w+)+(?:\/\S+)?)(?!\S)/';
|
|
$pattern = '/(?<!\S)(?<!href="|">)(?<!src=\")((http|ftp)+(s)?:\/\/[^<>\s]+)/is';
|
|
|
|
// 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">';
|
|
$replacement = '<a href="$2"><img src="$2" alt="$1"></a>';
|
|
$result = preg_replace($pattern, $replacement, $twtString);
|
|
|
|
return $result;
|
|
}
|
|
|
|
function replaceTagsFromTwt(string $twtString) {
|
|
//$pattern = '/#(\w+)?/';
|
|
//$pattern = '/(?<=\s)#(\w+)/';
|
|
$pattern = '/(?<=\B)#([\p{L}\p{N}_]+)/u';
|
|
|
|
//$replacement = '<a href="#">#\1</a>'; // Dummy link
|
|
$replacement = '<a href="?search=$1" class="tag">#${1}</a>';
|
|
$result = preg_replace($pattern, $replacement, $twtString);
|
|
|
|
return $result;
|
|
}
|
|
|
|
function embedYoutubeFromTwt(string $twtString) {
|
|
|
|
// original regex source: https://gist.github.com/afeld/1254889#gistcomment-1253992
|
|
$pattern = '/(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/mi';
|
|
|
|
if (preg_match_all($pattern, $twtString, $youtubeLinks)) {
|
|
|
|
$youtubeLinks = array_unique($youtubeLinks[1]); // Remove dublicate cause by raw URLs conceverter to links
|
|
|
|
foreach ($youtubeLinks as $videoID) {
|
|
$twtString .= '<iframe loading="lazy" src="https://www.youtube.com/embed/' . $videoID . '" class="embed-video" allow="encrypted-media" title="" allowfullscreen="allowfullscreen" frameborder="0"></iframe>';
|
|
}
|
|
}
|
|
|
|
$result = $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) {
|
|
$cacheFilePath = getCachedFileName($filePath);
|
|
# TODO: Report down URLs and stop loading them after a few tries
|
|
|
|
# Get the last modification time of the local file
|
|
$lastModifiedTime = file_exists($cacheFilePath) ? filemtime($cacheFilePath) : false;
|
|
$lastModifiedHeader = $lastModifiedTime ? gmdate('D, d M Y H:i:s', $lastModifiedTime) . ' GMT' : null;
|
|
|
|
#echo "lastModifiedHeader: $lastModifiedHeader<br>\n";
|
|
|
|
global $config;
|
|
global $agentVersion;
|
|
|
|
# TODO: Check this from the main page, not in this function
|
|
if (!array_key_exists('public_txt_url', $config) || !array_key_exists('public_nick', $config)) {
|
|
die("Check your config.ini file. 'public_txt_url' or 'public_nick' missing");
|
|
}
|
|
|
|
$url = $config['public_txt_url'];
|
|
$nick = $config['public_nick'];
|
|
|
|
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
|
|
die("Check your config.ini file. 'public_txt_url' not valid");
|
|
}
|
|
|
|
# TODO: Add a validation for the nickname. For example at least 1 character.
|
|
// if ($nick === "") {
|
|
// die("Check your config.ini file. 'public_nick' not valid");
|
|
// }
|
|
|
|
|
|
$agentName = 'timeline';
|
|
$userAgentHeader = "User-Agent: $agentName/$agentVersion (+$url; @$nick)\r\n";
|
|
|
|
$header = $lastModifiedHeader ? "If-Modified-Since: $lastModifiedHeader\r\n" : '';
|
|
$header .= $userAgentHeader;
|
|
|
|
# Set up the HTTP context with the 'If-Modified-Since' header
|
|
$options = [
|
|
'http' => [
|
|
'method' => 'GET',
|
|
'header' => $header,
|
|
]
|
|
];
|
|
|
|
$context = stream_context_create($options);
|
|
|
|
$response = @file_get_contents($filePath, false, $context);
|
|
|
|
# Check if HTTP headers are available, usually when the server is available
|
|
if (!isset($http_response_header)) {
|
|
# echo "Failed to fetch headers. No HTTP request was made.\n";
|
|
return;
|
|
}
|
|
|
|
if ($http_response_header) {
|
|
# var_dump($http_response_header);
|
|
|
|
foreach ($http_response_header as $header) {
|
|
# Look for the Last-Modified header
|
|
if (preg_match('/^Last-Modified:\s*(.+)$/i', $header, $matches)) {
|
|
$dateString = $matches[1]; // Extracted date
|
|
# echo "Extracted Date: $dateString\n";
|
|
|
|
// Convert to Unix timestamp
|
|
$lastModifiedTimestamp = strtotime($dateString);
|
|
if ($lastModifiedTimestamp > $lastModifiedTime) {
|
|
# echo "Remote file is newer. Load it!<br>\n";
|
|
} else {
|
|
# echo "Not modified since last request. No update needed.<br>\n";
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Save the content if it was successfully retrieved
|
|
if ($response !== false) {
|
|
file_put_contents($cacheFilePath, $response);
|
|
#echo "File updated successfully.\n";
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Clean up nick if set to something like `@soren@darch.dk` instead of just `soren` - mosty for (re)feeds from Mastodon etc.
|
|
if (str_contains($twtxtData->nick, "@")) {
|
|
$str = $twtxtData->nick;
|
|
$str = ltrim($str, "@");
|
|
$twtxtData->nick = explode("@", $str)[0]; // take the first [0] from splitting the nick at "@"
|
|
}
|
|
|
|
// Fallback for nick and url if not set in twtxt.txt
|
|
if ($twtxtData->nick === "") {
|
|
$str = parse_url($url, PHP_URL_HOST);
|
|
$str = str_replace("www.", "", $str);
|
|
//$str = explode(".", $str)[0]; // take the first [0] from splitting the host at "."
|
|
$twtxtData->nick = $str;
|
|
}
|
|
if ($twtxtData->mainURL === "") {
|
|
$twtxtData->mainURL = $url;
|
|
}
|
|
|
|
// Use only nick as handle if nick and domain is the same
|
|
$twtxtData->domain = parse_url($twtxtData->mainURL, PHP_URL_HOST);
|
|
|
|
if ($twtxtData->nick === $twtxtData->domain) {
|
|
$twtxtData->domain = "";
|
|
} else {
|
|
$twtxtData->domain = "@".$twtxtData->domain;
|
|
}
|
|
|
|
|
|
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); // TODO: Messing up rendering of @mentions #BUG
|
|
|
|
// 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 = str_replace("\u{2028}", "\n", $twtContent);
|
|
|
|
$twtContent = embedYoutubeFromTwt($twtContent);
|
|
|
|
// Get and remove the hash
|
|
$hash = getReplyHashFromTwt($twtContent);
|
|
if ($hash) {
|
|
$twtContent = str_replace("(#$hash)", '', $twtContent);
|
|
}
|
|
|
|
// Convert HTML entities to their corresponding characters
|
|
$twtContent = html_entity_decode($twtContent);
|
|
|
|
// TODO: Make ?tag= filtering feature
|
|
$twtContent = replaceTagsFromTwt($twtContent);
|
|
|
|
// Interpret the content as markdown
|
|
$Parsedown = new Parsedown();
|
|
$twtContent = $Parsedown->text($twtContent);
|
|
|
|
// TODO: Remove obserlete fuctions, or build our own simpler markdown parser?
|
|
//$twtContent = replaceMarkdownLinksFromTwt($twtContent);
|
|
//$twtContent = replaceImagesFromTwt($twtContent);
|
|
//$twtContent = replaceLinksFromTwt($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->timestamp = $timestamp;
|
|
$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;
|
|
$twt->domain = $twtxtData->domain;
|
|
|
|
$twtxtData->twts[$timestamp] = $twt;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|