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;
|
||||
}
|