rss feed for news, better header handling

This commit is contained in:
Alex Grintsvayg 2016-05-26 13:37:01 -04:00
parent af8c4869e5
commit 67ff1018c3
13 changed files with 253 additions and 33 deletions

View file

@ -1,12 +1,9 @@
<?php
/**
* Description of Controller
*
* @author jeremy
*/
class Controller
{
const HEADER_STATUS = 'Status';
public static function dispatch($uri)
{
try
@ -14,6 +11,9 @@ class Controller
$viewAndParams = static::execute($uri);
$viewTemplate = $viewAndParams[0];
$viewParameters = isset($viewAndParams[1]) ? $viewAndParams[1] : [];
$headers = isset($viewAndParams[2]) ? $viewAndParams[2] : [];
static::sendHeaders($headers);
if ($viewTemplate === null)
{
@ -25,9 +25,15 @@ class Controller
throw new LogicException('All execute methods must return a template.');
}
echo View::render('layout/basic', [
'content' => View::render($viewTemplate, $viewParameters + ['fullPage' => true])
]);
$layout = !(isset($viewParameters['_no_layout']) && $viewParameters['_no_layout']);
unset($viewParameters['_no_layout']);
$layoutParams = isset($viewParameters[View::LAYOUT_PARAMS]) ? $viewParameters[View::LAYOUT_PARAMS] : [];
unset($viewParameters[View::LAYOUT_PARAMS]);
$content = View::render($viewTemplate, $viewParameters + ['fullPage' => true]);
echo $layout ? View::render('layout/basic', ['content' => $content] + $layoutParams) : $content;
}
catch (StopException $e)
{
@ -59,7 +65,7 @@ class Controller
case '/LBRY-deck.pdf':
return static::redirect('https://s3.amazonaws.com/files.lbry.io/LBRY-deck.pdf', 307);
case '/dl/lbry_setup.sh':
return static::redirect('https://raw.githubusercontent.com/lbryio/lbry-setup/master/lbry_setup.sh', 307);
return ['internal/dl-not-supported', ['_no_layout' => true]];
case '/lbry-osx-latest.dmg':
return static::redirect('https://github.com/lbryio/lbry/releases/download/v0.2.4/lbry.0.2.4.dmg', 307);
case '/lbry-linux-latest.deb':
@ -67,10 +73,14 @@ class Controller
case '/art':
return static::redirect('/what');
default:
$blogPattern = '#^/news(/|$)#';
$blogPattern = '#^' . BlogActions::URL_STEM . '(/|$)#';
if (preg_match($blogPattern, $uri))
{
$slug = preg_replace($blogPattern, '', $uri);
if ($slug == BlogActions::RSS_SLUG)
{
return BlogActions::executeRss();
}
return $slug ? BlogActions::executePost($slug) : BlogActions::executeIndex();
}
$noSlashUri = ltrim($uri, '/');
@ -80,29 +90,102 @@ class Controller
}
else
{
return ['page/404', []];
return ['page/404', [], [static::HEADER_STATUS => 404]];
}
}
}
public static function redirect($url, $statusCode = 302)
{
if (empty($url))
if (!$url)
{
throw new InvalidArgumentException('Cannot redirect to an empty URL.');
}
$url = str_replace('&amp;', '&', $url);
$headers = [static::HEADER_STATUS => $statusCode];
if ($statusCode == 201 || ($statusCode >= 300 && $statusCode < 400))
{
header('Location: ' . $url, true, $statusCode);
}
else
{
echo sprintf('<html><head><meta http-equiv="refresh" content="0;url=%s"/></head></html>', htmlspecialchars($url, ENT_QUOTES));
$headers['Location'] = $url;
}
throw new StopException('Time to redirect');
return ['internal/redirect', ['url' => $url], $headers];
}
protected static function sendHeaders(array $headers)
{
if (isset($headers[static::HEADER_STATUS]))
{
$status = 'HTTP/1.0 ' . $headers[static::HEADER_STATUS] . ' ' . static::getStatusTextForCode($headers[static::HEADER_STATUS]);
header($status);
if (substr(php_sapi_name(), 0, 3) == 'cgi')
{
// fastcgi servers cannot send this status information because it was sent by them already due to the HTT/1.0 line
// so we can safely unset them. see ticket #3191
unset($headers[static::HEADER_STATUS]);
}
}
foreach($headers as $name => $value)
{
header($name . ': ' . $value);
}
}
public static function getStatusTextForCode($code)
{
$statusTexts = [
'100' => 'Continue',
'101' => 'Switching Protocols',
'200' => 'OK',
'201' => 'Created',
'202' => 'Accepted',
'203' => 'Non-Authoritative Information',
'204' => 'No Content',
'205' => 'Reset Content',
'206' => 'Partial Content',
'300' => 'Multiple Choices',
'301' => 'Moved Permanently',
'302' => 'Found',
'303' => 'See Other',
'304' => 'Not Modified',
'305' => 'Use Proxy',
'306' => '(Unused)',
'307' => 'Temporary Redirect',
'400' => 'Bad Request',
'401' => 'Unauthorized',
'402' => 'Payment Required',
'403' => 'Forbidden',
'404' => 'Not Found',
'405' => 'Method Not Allowed',
'406' => 'Not Acceptable',
'407' => 'Proxy Authentication Required',
'408' => 'Request Timeout',
'409' => 'Conflict',
'410' => 'Gone',
'411' => 'Length Required',
'412' => 'Precondition Failed',
'413' => 'Request Entity Too Large',
'414' => 'Request-URI Too Long',
'415' => 'Unsupported Media Type',
'416' => 'Requested Range Not Satisfiable',
'417' => 'Expectation Failed',
'419' => 'Authentication Timeout',
'422' => 'Unprocessable Entity',
'426' => 'Upgrade Required',
'429' => 'Too Many Requests',
'500' => 'Internal Server Error',
'501' => 'Not Implemented',
'502' => 'Bad Gateway',
'503' => 'Service Unavailable',
'504' => 'Gateway Timeout',
'505' => 'HTTP Version Not Supported',
];
return isset($statusTexts[$code]) ? $statusTexts[$code] : null;
}
}

View file

@ -3,6 +3,7 @@
class BlogActions extends Actions
{
const URL_STEM = '/news';
const RSS_SLUG = 'rss.xml';
public static function executeIndex()
{
@ -13,7 +14,24 @@ class BlogActions extends Actions
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
return ['blog/index', [
'posts' => $posts,
'page' => $page
'page' => $page,
View::LAYOUT_PARAMS => [
'showRssLink' => true
]
]];
}
public static function executeRss()
{
$posts = Blog::getPosts();
usort($posts, function(Post $a, Post $b) {
return strcasecmp($b->getDate()->format('Y-m-d'), $a->getDate()->format('Y-m-d'));
});
return ['blog/rss', [
'posts' => array_slice($posts, 0, 10),
'_no_layout' => true
], [
'Content-Type' => 'text/xml; charset=utf-8'
]];
}
@ -25,7 +43,10 @@ class BlogActions extends Actions
return ['page/404', []];
}
return ['blog/post', [
'post' => $post
'post' => $post,
View::LAYOUT_PARAMS => [
'showRssLink' => true
]
]];
}

2
dev.sh
View file

@ -1,3 +1,3 @@
#!/bin/bash
php --server localhost:8000 --docroot web/
php --server localhost:8000 --docroot web/ web/index.php

View file

@ -2,7 +2,7 @@
class Post
{
protected $slug, $title, $author, $date, $contentHtml, $cover;
protected $slug, $title, $author, $date, $markdown, $contentText, $contentHtml, $cover;
protected $isCoverLight = false;
public static function fromFile($filename)
@ -14,10 +14,10 @@ class Post
public function __construct($slug, $frontMatter, $markdown)
{
$this->slug = $slug;
$this->markdown = $markdown;
$this->title = isset($frontMatter['title']) ? $frontMatter['title'] : null;
$this->author = isset($frontMatter['author']) ? $frontMatter['author'] : null;
$this->date = isset($frontMatter['date']) ? new DateTime($frontMatter['date']) : null;
$this->contentHtml = ParsedownExtra::instance()->text(trim($markdown));
$this->cover = isset($frontMatter['cover']) ? $frontMatter['cover'] : null;
$this->isCoverLight = isset($frontMatter['cover-light']) && $frontMatter['cover-light'] == 'true';
}
@ -57,8 +57,23 @@ class Post
return $this->isCoverLight;
}
public function getContentText($wordLimit = null, $appendEllipsis = false)
{
if ($this->markdown && !$this->contentText)
{
// $this->contentText = $this->markdownToText(trim($this->markdown));
$this->contentText = html_entity_decode(str_replace('&nbsp;', ' ', strip_tags($this->getContentHtml())), ENT_COMPAT, 'utf-8');
}
return $wordLimit === null ? $this->contentText : $this->limitWords($this->contentText, $wordLimit, $appendEllipsis);
}
public function getContentHtml()
{
if ($this->markdown && !$this->contentHtml)
{
$this->contentHtml = ParsedownExtra::instance()->text(trim($this->markdown));
}
return $this->contentHtml;
}
@ -99,6 +114,24 @@ class Post
}
}
public function getAuthorEmail()
{
switch(strtolower($this->author))
{
case 'jeremy':
return 'jeremy@lbry.io';
case 'mike':
return 'mike@lbry.io';
case 'jimmy':
return 'jimmy@lbry.io';
case 'jack':
return 'jack@lbry.io';
case 'lbry':
default:
return 'hello@lbry.io';
}
}
public function getAuthorPhoto()
{
switch(strtolower($this->author))
@ -138,4 +171,54 @@ class Post
{
return $this->getPostNum() % $maxStyles + 1;
}
protected function markdownToText($markdown)
{
$replacements = [
// '/<(.*?)>/' => '$1', // HTML tags
'/^[=\-]{2,}\s*$/' => '', // setext-style headers
'/\[\^.+?\](\: .*?$)?/' => '', // footnotes
'/\s{0,2}\[.*?\]: .*?$/' => '', // footnotes
'/\!\[.*?\][\[\(].*?[\]\)]/' => '', // images
'/\[(.*?)\][\[\(].*?[\]\)]/' => '$1', // inline links
'/^\s*>/' => '', // blockquotes
'/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/' => '', // reference-style links
'/\n={2,}/' => '\n', // underlined headers
'/^\#{1,6}\s*([^#]*)\s*(\#{1,6})?/m' => '$1', // atx-style headers
'/([\*_]{1,3})(\S.*?\S)\1/' => '$2', // bold/italics
'/~~/' => '', // strikethrough
'/(`{3,})(.*?)\1/m' => '$2', // codeblocks
// '/`{3}.*\n/' => '', // fenced codeblocks
'/^-{3,}\s*$/' => '', // hr
'/`(.+?)`/' => '$1', // inline code
'/\n{2,}/' => '\n\n', // multiple newlines
];
return preg_replace(array_keys($replacements), array_values($replacements), strip_tags($markdown));
}
protected function limitWords($string, $wordLimit, $appendEllipsis = false)
{
$regexp = '/\s+/u';
$words = preg_split($regexp, $string, $wordLimit + 1);
$numWords = count($words);
# TBB: if there are $wordLimit words or less, this check is necessary
# to prevent the last word from being lost.
if ($numWords > $wordLimit)
{
array_pop($words);
}
$string = implode(' ', $words);
if ($appendEllipsis && $numWords > $wordLimit)
{
$ellipsis = '…';
$string .= $ellipsis;
}
return $string;
}
}

View file

@ -10,13 +10,10 @@ function js_end()
ob_end_flush();
}
/**
* Description of View
*
* @author jeremy
*/
class View
{
const LAYOUT_PARAMS = '_layout_params';
protected static $metaDescription = '',
$metaImg = '';

View file

@ -17,4 +17,4 @@ We believe cryptocurrencies and blockchain technology are the key to solving thi
Want to help us get LBRY launched and get in on the crypto action? [Test the LBRY Alpha and fill out our survey to earn 1,000 LBC](https://lbry.io/get).
If you have a strong interest in getting involved in LBRY, [join our Slack channel](https://lbry-slackin.herokuapp.com). Come and join the fun!
If you have a strong interest in getting involved in LBRY, [join our Slack channel](http://slack.lbry.io). Come and join the fun!

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>LBRY News</title>
<link>https://lbry.io<?php echo BlogActions::URL_STEM ?></link>
<description>Recent news about LBRY</description>
<generator>https://github.com/lbryio/lbry.io</generator>
<language>en</language>
<?php //<lastBuildDate>Sat, 07 Sep 2002 09:42:31 GMT</lastBuildDate> ?>
<atom:link href="http://lbry.io<?php echo BlogActions::URL_STEM . '/' . BlogActions::RSS_SLUG ?>" rel="self" type="application/rss+xml" />
<?php foreach ($posts as $post): ?>
<item>
<title><?php echo htmlspecialchars($post->getTitle()) ?></title>
<link>https://lbry.io<?php echo $post->getRelativeUrl() ?></link>
<guid>https://lbry.io<?php echo $post->getRelativeUrl() ?></guid>
<pubDate><?php echo $post->getDate()->format('r') ?></pubDate>
<author><?php echo htmlspecialchars($post->getAuthorEmail()) ?> (<?php echo htmlspecialchars($post->getAuthorName()) ?>)</author>
<description><?php echo htmlspecialchars($post->getContentText(50, true)) ?></description>
<content:encoded><![CDATA[
<?php echo $post->getContentHtml() ?>
]]></content:encoded>
</item>
<?php endforeach ?>
</channel>
</rss>

View file

@ -0,0 +1,2 @@
#!/bin/bash
echo "This install method is no longer supported. Please see https://github.com/lbryio/lbry for more information."

View file

@ -0,0 +1 @@
<html><head><meta http-equiv="refresh" content="0;url=<?php echo htmlspecialchars($url, ENT_QUOTES, 'utf-8')?>"/></head></html>

View file

@ -12,7 +12,7 @@
'LBRY' ?>
<title><?php echo $title ?></title>
<link href='https://fonts.googleapis.com/css?family=Merriweather:300,300italic,700,700italic|Raleway:300,300italic,400,400italic,700' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Raleway:300,300italic,400,400italic,700' rel='stylesheet' type='text/css'>
<link href="/css/all.css" rel="stylesheet" type="text/css" media="screen,print" />
<link rel="apple-touch-icon" sizes="60x60" href="/img/fav/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="114x114" href="/img/fav/apple-touch-icon-114x114.png">
@ -25,6 +25,9 @@
<link rel="icon" type="image/png" href="/img/fav/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/img/fav/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/img/fav/manifest.json">
<?php if(isset($showRssLink) && $showRssLink): ?>
<link rel="alternate" type="application/rss+xml" title="LBRY News" href="<?php echo BlogActions::URL_STEM . '/' . BlogActions::RSS_SLUG ?>" />
<?php endif ?>
<meta name="description" content="<?php echo Response::getMetaDescription() ?>">
<meta name="msapplication-TileColor" content="#155B4A">

View file

@ -4,8 +4,8 @@
</div>
*/ ?>
<div class="spacer1">
<a href="//github.com/lbryio" class="link-primary"><span class="icon-github icon-fw"></span><span class="btn-label">GitHub (source code)</span></a>
<a href="https://github.com/lbryio" class="link-primary"><span class="icon-github icon-fw"></span><span class="btn-label">GitHub (source code)</span></a>
</div>
<div class="spacer1">
<a href="https://lbry-slackin.herokuapp.com" class="link-primary"><span class="icon-slack icon-fw"></span><span class="btn-label">Slack (chat)</span></a>
<a href="http://slack.lbry.io" class="link-primary"><span class="icon-slack icon-fw"></span><span class="btn-label">Slack (chat)</span></a>
</div>

View file

@ -1,5 +1,10 @@
<?php
# Enable PHP dev cli-server
if (php_sapi_name() === 'cli-server' && is_file(__DIR__.preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI'])))
{
return false;
}
include __DIR__ . '/../bootstrap.php';