feat: commit initial
BIN
assets/bg1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
assets/bg2.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
assets/bg3.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
assets/fanstuffgarden.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/pics.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/picsfooter.png
Normal file
After Width: | Height: | Size: 702 B |
BIN
favicon.png
Normal file
After Width: | Height: | Size: 740 B |
273
feed.php
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS for PHP - small and easy-to-use library for consuming an RSS Feed
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2008 David Grudl
|
||||||
|
* @license New BSD License
|
||||||
|
* @version 1.5
|
||||||
|
*/
|
||||||
|
class Feed
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
public static $cacheExpire = '1 day';
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public static $cacheDir;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public static $userAgent = 'FeedFetcher-Google';
|
||||||
|
|
||||||
|
/** @var SimpleXMLElement */
|
||||||
|
protected $xml;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads RSS or Atom feed.
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @return Feed
|
||||||
|
* @throws FeedException
|
||||||
|
*/
|
||||||
|
public static function load($url, $user = null, $pass = null)
|
||||||
|
{
|
||||||
|
$xml = self::loadXml($url, $user, $pass);
|
||||||
|
if ($xml->channel) {
|
||||||
|
return self::fromRss($xml);
|
||||||
|
} else {
|
||||||
|
return self::fromAtom($xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads RSS feed.
|
||||||
|
* @param string RSS feed URL
|
||||||
|
* @param string optional user name
|
||||||
|
* @param string optional password
|
||||||
|
* @return Feed
|
||||||
|
* @throws FeedException
|
||||||
|
*/
|
||||||
|
public static function loadRss($url, $user = null, $pass = null)
|
||||||
|
{
|
||||||
|
return self::fromRss(self::loadXml($url, $user, $pass));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads Atom feed.
|
||||||
|
* @param string Atom feed URL
|
||||||
|
* @param string optional user name
|
||||||
|
* @param string optional password
|
||||||
|
* @return Feed
|
||||||
|
* @throws FeedException
|
||||||
|
*/
|
||||||
|
public static function loadAtom($url, $user = null, $pass = null)
|
||||||
|
{
|
||||||
|
return self::fromAtom(self::loadXml($url, $user, $pass));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function fromRss(SimpleXMLElement $xml)
|
||||||
|
{
|
||||||
|
if (!$xml->channel) {
|
||||||
|
throw new FeedException('Invalid feed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
self::adjustNamespaces($xml);
|
||||||
|
|
||||||
|
foreach ($xml->channel->item as $item) {
|
||||||
|
// converts namespaces to dotted tags
|
||||||
|
self::adjustNamespaces($item);
|
||||||
|
|
||||||
|
// generate 'url' & 'timestamp' tags
|
||||||
|
$item->url = (string) $item->link;
|
||||||
|
if (isset($item->{'dc:date'})) {
|
||||||
|
$item->timestamp = strtotime($item->{'dc:date'});
|
||||||
|
} elseif (isset($item->pubDate)) {
|
||||||
|
$item->timestamp = strtotime($item->pubDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$feed = new self;
|
||||||
|
$feed->xml = $xml->channel;
|
||||||
|
return $feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function fromAtom(SimpleXMLElement $xml)
|
||||||
|
{
|
||||||
|
if (!in_array('http://www.w3.org/2005/Atom', $xml->getDocNamespaces(), true)
|
||||||
|
&& !in_array('http://purl.org/atom/ns#', $xml->getDocNamespaces(), true)
|
||||||
|
) {
|
||||||
|
throw new FeedException('Invalid feed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate 'url' & 'timestamp' tags
|
||||||
|
foreach ($xml->entry as $entry) {
|
||||||
|
$entry->url = (string) $entry->link['href'];
|
||||||
|
$entry->timestamp = strtotime($entry->updated);
|
||||||
|
}
|
||||||
|
$feed = new self;
|
||||||
|
$feed->xml = $xml;
|
||||||
|
return $feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns property value. Do not call directly.
|
||||||
|
* @param string tag name
|
||||||
|
* @return SimpleXMLElement
|
||||||
|
*/
|
||||||
|
public function __get($name)
|
||||||
|
{
|
||||||
|
return $this->xml->{$name};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets value of a property. Do not call directly.
|
||||||
|
* @param string property name
|
||||||
|
* @param mixed property value
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __set($name, $value)
|
||||||
|
{
|
||||||
|
throw new Exception("Cannot assign to a read-only property '$name'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a SimpleXMLElement into an array.
|
||||||
|
* @param SimpleXMLElement
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray(SimpleXMLElement $xml = null)
|
||||||
|
{
|
||||||
|
if ($xml === null) {
|
||||||
|
$xml = $this->xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$xml->children()) {
|
||||||
|
return (string) $xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
$arr = [];
|
||||||
|
foreach ($xml->children() as $tag => $child) {
|
||||||
|
if (count($xml->$tag) === 1) {
|
||||||
|
$arr[$tag] = $this->toArray($child);
|
||||||
|
} else {
|
||||||
|
$arr[$tag][] = $this->toArray($child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load XML from cache or HTTP.
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @return SimpleXMLElement
|
||||||
|
* @throws FeedException
|
||||||
|
*/
|
||||||
|
private static function loadXml($url, $user, $pass)
|
||||||
|
{
|
||||||
|
$e = self::$cacheExpire;
|
||||||
|
$cacheFile = self::$cacheDir . '/feed.' . md5(serialize(func_get_args())) . '.xml';
|
||||||
|
|
||||||
|
if (self::$cacheDir
|
||||||
|
&& (time() - @filemtime($cacheFile) <= (is_string($e) ? strtotime($e) - time() : $e))
|
||||||
|
&& $data = @file_get_contents($cacheFile)
|
||||||
|
) {
|
||||||
|
// ok
|
||||||
|
} elseif ($data = trim(self::httpRequest($url, $user, $pass))) {
|
||||||
|
if (self::$cacheDir) {
|
||||||
|
file_put_contents($cacheFile, $data);
|
||||||
|
}
|
||||||
|
} elseif (self::$cacheDir && $data = @file_get_contents($cacheFile)) {
|
||||||
|
// ok
|
||||||
|
} else {
|
||||||
|
throw new FeedException('Cannot load feed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SimpleXMLElement($data, LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOCDATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process HTTP request.
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @return string|false
|
||||||
|
* @throws FeedException
|
||||||
|
*/
|
||||||
|
private static function httpRequest($url, $user, $pass)
|
||||||
|
{
|
||||||
|
if (extension_loaded('curl')) {
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt($curl, CURLOPT_URL, $url);
|
||||||
|
if ($user !== null || $pass !== null) {
|
||||||
|
curl_setopt($curl, CURLOPT_USERPWD, "$user:$pass");
|
||||||
|
}
|
||||||
|
curl_setopt($curl, CURLOPT_USERAGENT, self::$userAgent); // some feeds require a user agent
|
||||||
|
curl_setopt($curl, CURLOPT_HEADER, false);
|
||||||
|
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($curl, CURLOPT_ENCODING, '');
|
||||||
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // no echo, just return result
|
||||||
|
curl_setopt($curl, CURLOPT_USERAGENT, '');
|
||||||
|
if (!ini_get('open_basedir')) {
|
||||||
|
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // sometime is useful :)
|
||||||
|
}
|
||||||
|
$result = curl_exec($curl);
|
||||||
|
return curl_errno($curl) === 0 && curl_getinfo($curl, CURLINFO_HTTP_CODE) === 200
|
||||||
|
? $result
|
||||||
|
: false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$context = null;
|
||||||
|
if ($user !== null && $pass !== null) {
|
||||||
|
$options = [
|
||||||
|
'http' => [
|
||||||
|
'method' => 'GET',
|
||||||
|
'header' => 'Authorization: Basic ' . base64_encode($user . ':' . $pass) . "\r\n",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$context = stream_context_create($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_get_contents($url, false, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates better accessible namespaced tags.
|
||||||
|
* @param SimpleXMLElement
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function adjustNamespaces($el)
|
||||||
|
{
|
||||||
|
foreach ($el->getNamespaces(true) as $prefix => $ns) {
|
||||||
|
if ($prefix === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$children = $el->children($ns);
|
||||||
|
foreach ($children as $tag => $content) {
|
||||||
|
$el->{$prefix . ':' . $tag} = $content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception generated by Feed.
|
||||||
|
*/
|
||||||
|
class FeedException extends Exception
|
||||||
|
{
|
||||||
|
}
|
BIN
fonts/teko-bold-webfont.woff
Normal file
BIN
fonts/teko-light-webfont.woff
Normal file
BIN
fonts/teko-medium-webfont.woff
Normal file
BIN
fonts/teko-regular-webfont.woff
Normal file
BIN
fonts/teko-semibold-webfont.woff
Normal file
90
index.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
|
if (!ini_get('date.timezone')) {
|
||||||
|
date_default_timezone_set('Europe/Paris');
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
|
||||||
|
require_once 'feed.php';
|
||||||
|
|
||||||
|
$rssSonic = Feed::loadRss('https://shaarli.kazhnuz.space/feed/rss?&searchtags=Sonic');
|
||||||
|
$rssSEGA = Feed::loadRss('https://shaarli.kazhnuz.space/feed/rss?&searchtags=SEGA');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Press Garden Zone</title>
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
<link rel="icon" href="./favicon.png">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#" class="active" id="link-sonic">Sonic</a></li>
|
||||||
|
<li><a href="#" id="link-sega">SEGA</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<h1 id="head">Press Garden Zone</h1>
|
||||||
|
<section class="shown" id="section-sonic">
|
||||||
|
<?php $i = 1; ?>
|
||||||
|
<?php foreach ($rssSonic->item as $item): ?>
|
||||||
|
<?php if ($i <= 8) { ?>
|
||||||
|
<article>
|
||||||
|
<h2><a href="<?php echo htmlspecialchars($item->url) ?>"><?php echo htmlspecialchars($item->title) ?></a>
|
||||||
|
<small><?php echo date('j.n.Y H:i', (int) $item->timestamp) ?></small></h2>
|
||||||
|
<div><?php echo $item->description ?></div>
|
||||||
|
</article>
|
||||||
|
<?php } ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<div class="btn-area"><a class="btn" href="https://shaarli.kazhnuz.space/?searchtags=Sonic"> Plus ancien articles </a></div>
|
||||||
|
</section>
|
||||||
|
<section id="section-sega">
|
||||||
|
<?php foreach ($rssSEGA->item as $item): ?>
|
||||||
|
<article>
|
||||||
|
<h2><a href="<?php echo htmlspecialchars($item->url) ?>"><?php echo htmlspecialchars($item->title) ?></a>
|
||||||
|
<small><?php echo date('j.n.Y H:i', (int) $item->timestamp) ?></small></h2>
|
||||||
|
<div><?php echo $item->description ?></div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<div class="btn-area"><a class="btn" href="https://shaarli.kazhnuz.space/?searchtags=SEGA"> Plus ancien articles </a></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>Press Garden est une petite page servant juste à afficher les flux RSS Sonic et SEGA du <a href="https://shaarli.kazhnuz.space/">shaarli de Kazhnuz</a>.</p>
|
||||||
|
<p>Ce site est hébergé par Fanstuff Garden.</p>
|
||||||
|
<p><a href="https://fanstuff.garden"><img src="assets/fanstuffgarden.gif" /></a></p>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
const sectionSonic = document.getElementById('section-sonic');
|
||||||
|
const linkSonic = document.getElementById('link-sonic');
|
||||||
|
const sectionSEGA = document.getElementById('section-sega');
|
||||||
|
const linkSEGA = document.getElementById('link-sega');
|
||||||
|
linkSonic.addEventListener('click', function () {
|
||||||
|
linkSEGA.classList.remove('active');
|
||||||
|
linkSonic.classList.add('active');
|
||||||
|
|
||||||
|
sectionSEGA.classList.remove('shown');
|
||||||
|
sectionSonic.classList.add('shown');
|
||||||
|
});
|
||||||
|
linkSEGA.addEventListener('click', function () {
|
||||||
|
linkSEGA.classList.add('active');
|
||||||
|
linkSonic.classList.remove('active');
|
||||||
|
|
||||||
|
sectionSEGA.classList.add('shown');
|
||||||
|
sectionSonic.classList.remove('shown');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
162
style.css
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Teko';
|
||||||
|
src: url('fonts/teko-light-webfont.woff');
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Teko';
|
||||||
|
src: url('fonts/teko-regular-webfont.woff');
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Teko';
|
||||||
|
src: url('fonts/teko-medium-webfont.woff');
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Teko';
|
||||||
|
src: url('fonts/teko-semibold-webfont.woff');
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Teko';
|
||||||
|
src: url('fonts/teko-bold-webfont.woff');
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-image: url("assets/bg3.png"), url("assets/bg2.png"), url("assets/bg1.png");
|
||||||
|
background-attachment: fixed, fixed, fixed;
|
||||||
|
font-family: Inter,Cantarell,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,'Fira Sans','Droid Sans','Helvetica Neue',sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.5rem 1rem 1rem 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.3);
|
||||||
|
border-left: 8px solid #e67700;
|
||||||
|
}
|
||||||
|
|
||||||
|
article:last-child {
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav {
|
||||||
|
background-color: #1971c2;
|
||||||
|
padding: 0.4rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
background-image: url("assets/pics.png"), url("assets/picsfooter.png");
|
||||||
|
background-repeat: repeat-x, repeat-x;
|
||||||
|
background-position: top center, bottom center;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
color:white;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Teko', sans-serif;
|
||||||
|
font-size: 3.5rem;
|
||||||
|
text-shadow: -2px -2px 0 #000,
|
||||||
|
0 -2px 0 #000,
|
||||||
|
2px -2px 0 #000,
|
||||||
|
2px 0 0 #000,
|
||||||
|
2px 2px 0 #000,
|
||||||
|
0 2px 0 #000,
|
||||||
|
-2px 2px 0 #000,
|
||||||
|
-2px 0 0 #000,
|
||||||
|
.05em .1em 0 #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
section:not(.shown) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: white!important;
|
||||||
|
padding: 0.8rem 1.2rem;
|
||||||
|
display: block;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover {
|
||||||
|
background-color:rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a.active {
|
||||||
|
background-color:rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
color: white;
|
||||||
|
background-color: #343a40;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: auto;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color:#a5d8ff!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-area {
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: #e67700;
|
||||||
|
padding: 0.8rem 1.2rem;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none!important;
|
||||||
|
border-radius: 9999px;
|
||||||
|
box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #f08c00;
|
||||||
|
}
|