Add image thumbnail cache, do lots of adjustments

master
Skylar Ittner 5 years ago
parent ac1e937dfd
commit 0489caabca

3
.gitignore vendored

@ -2,4 +2,5 @@ vendor
settings.php
nbproject/private
*.sync-conflict*
/database.mwb.bak
/database.mwb.bak
/cache/thumb/*.jpg

11
cache/.htaccess vendored

@ -0,0 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Redirect 404'd thumbnail requests to a script that will generate them.
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)/?$ handle_404.php?file=$1 [QSA,L]

@ -0,0 +1,31 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
require_once __DIR__ . "/../required.php";
$fileparts = explode(".", end(explode("/", $_GET['file'])));
if (count($fileparts) != 3 || !preg_match("/[0-9]+/", $fileparts[1]) || $fileparts[2] != "jpg") {
http_response_code(403);
die("Invalid filename.");
}
if (!preg_match("/^[A-Za-z0-9\-!_]+$/", $fileparts[0])) {
http_response_code(403);
die("Encoded image URL invalid, refusing to parse.");
}
$url = Base64::decode($fileparts[0]);
if (!filter_var($url, FILTER_VALIDATE_URL)) {
http_response_code(403);
die("Invalid URL.");
}
//header("Content-Type: image/jpeg");
echo Thumbnail::addThumbnailToCache($url, (int) $fileparts[1]);

@ -0,0 +1,3 @@
This folder is for the thumbnail cache.
This file is so Git doesn't remove the folder.

@ -5,5 +5,6 @@
"health": "Health",
"science": "Science",
"sports": "Sports",
"technology": "Technology"
"technology": "Technology",
"social": "Social"
}

@ -1,4 +1,5 @@
{
"Home": "Home",
"Form": "Form"
"Overview": "Overview",
"News": "News",
"Headlines": "Headlines"
}

@ -0,0 +1,53 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* Alternative Base64 encoding/decoding that's safe for filenames and URLs.
*/
class Base64 {
const STANDARD_CHARS = ['+', '/', '='];
const ALTERNATE_CHARS = ['-', '!', '_'];
/**
* Encode $data into "alternate" Base64.
* @param mixed $data
* @return string
*/
public static function encode($data): string {
return self::toAlternate(base64_encode($data));
}
/**
* Decode "alternate" Base64 into the original data.
* @param string $base64
* @return mixed
*/
public static function decode(string $base64) {
return base64_decode(self::toStandard($base64));
}
/**
* Convert "alternate" Base64 into standard Base64.
* @param string $base64
* @return string
*/
public static function toStandard(string $base64): string {
return str_replace(self::ALTERNATE_CHARS, self::STANDARD_CHARS, $base64);
}
/**
* Convert standard Base64 into URL and filename safe "alternate" Base64.
* @param string $base64
* @return string
*/
public static function toAlternate(string $base64): string {
return str_replace(self::STANDARD_CHARS, self::ALTERNATE_CHARS, $base64);
}
}

@ -18,6 +18,7 @@ class NewsCategory {
const SCIENCE = 16;
const SPORTS = 32;
const TECHNOLOGY = 64;
const SOCIAL = 128;
const CATEGORIES = [
self::BUSINESS => "Business",
@ -26,7 +27,8 @@ class NewsCategory {
self::HEALTH => "Health",
self::SCIENCE => "Science",
self::SPORTS => "Sports",
self::TECHNOLOGY => "Technology"
self::TECHNOLOGY => "Technology",
self::SOCIAL => "Social"
];
public function __construct(int $category) {
@ -61,6 +63,8 @@ class NewsCategory {
return "sports";
case self::TECHNOLOGY:
return "technology";
case self::SOCIAL:
return "social";
default:
return "";
}
@ -91,6 +95,9 @@ class NewsCategory {
case "tech":
$cat = self::TECHNOLOGY;
break;
case "social":
$cat = self::SOCIAL;
break;
}
return new NewsCategory($cat);
}
@ -115,6 +122,8 @@ class NewsCategory {
return "fas fa-futbol";
case self::TECHNOLOGY:
return "fas fa-laptop";
case self::SOCIAL:
return "fas fa-share-alt";
default:
return "fas fa-newspaper";
}

@ -41,7 +41,7 @@ class NewsSource_NewsAPI extends NewsSource {
$params['category'] = $category->toString();
}
$items = [];
$json = ApiFetcher::get($url, $params, ["apiKey" => $apikey]);
$json = ApiFetcher::get($url, $params, ["apiKey" => $apikey], "+1 hour");
$data = json_decode($json, TRUE);
if ($data['status'] != "ok") {
return [];

@ -15,13 +15,13 @@ class NewsSource_Reddit extends NewsSource {
}
function loadItems() {
$this->loadSubreddit("news+worldnews+UpliftingNews", new NewsCategory(NewsCategory::GENERAL));
$this->loadSubreddit("popular/top", new NewsCategory(NewsCategory::SOCIAL));
//$this->loadSubreddit("technology", new NewsCategory(NewsCategory::TECHNOLOGY));
}
private function loadSubreddit(string $subreddit, NewsCategory $category) {
$items = [];
$json = ApiFetcher::get("https://www.reddit.com/r/$subreddit.json", ["limit" => "50"]);
$json = ApiFetcher::get("https://www.reddit.com/r/$subreddit.json", ["limit" => "50", "sort" => "top", "t" => "day"]);
$news = json_decode($json, TRUE)['data']['children'];
foreach ($news as $d) {
$n = $d['data'];
@ -58,6 +58,8 @@ class NewsSource_Reddit extends NewsSource {
$source = $n['domain'];
}
$source .= " via <a href=\"https://reddit.com$n[permalink]\" target=\"_BLANK\">" . $n['subreddit_name_prefixed'] . "</a>";
$timestamp = time();
if (!empty($n['created'])) {
$timestamp = $n['created'];

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
use \SKleeschulte\Base32;
class Thumbnail {
/**
@ -29,7 +31,6 @@ class Thumbnail {
$output = imagecreatetruecolor($width, $height);
imagecopyresampled($output, $image, 0, 0, 0, 0, $width, $height, ImageSX($image), ImageSY($image));
ob_end_flush();
ob_start();
imagejpeg($output, null, 75);
$imagedata = ob_get_contents();
@ -40,21 +41,26 @@ class Thumbnail {
}
/**
* Encodes some data to base64.
* @param type $img
* Make a thumbnail, save it to the disk cache, and return a url relative to
* the app root.
* @param string $url
* @param int $width
* @param int,bool $height
* @return string
*/
static function imgToBase64($img): string {
return base64_encode($img);
static function getThumbnailCacheURL(string $url, int $width = 150, $height = true): string {
$encodedfilename = Base64::encode($url);
$path = "cache/thumb/$encodedfilename.$width.jpg";
return $path;
}
/**
* Get the base64 data: URI for a jpeg image
* @param type $img
* @return string
*/
static function jpegToBase64URI($img): string {
return "data:image/jpeg;base64," . self::imgToBase64($img);
static function addThumbnailToCache(string $url, int $width = 150, $height = true) {
$encodedfilename = Base64::encode($url);
$path = "cache/thumb/$encodedfilename.$width.jpg";
$image = self::getThumbnailFromUrl($url, $width, $height);
file_put_contents(__DIR__ . "/../$path", $image);
return $image;
}
}

@ -7,5 +7,5 @@
*/
class Weather {
}

@ -0,0 +1,11 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Weather_DarkSky extends Weather {
}

@ -1,26 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* This file demonstrates creating a form with the FormBuilder class.
*/
$form = new FormBuilder("Sample Form", "fas fa-code", "", "GET");
$form->setID("sampleform");
$form->addHiddenInput("page", "form");
$form->addInput("name", "John", "text", true, null, null, "Your name", "fas fa-user", 6, 5, 20, "John(ny)?|Steve", "Invalid name, please enter John, Johnny, or Steve.");
$form->addInput("location", "", "select", true, null, ["1" => "Here", "2" => "There"], "Location", "fas fa-map-marker");
$form->addInput("textbox", "Hello world", "textarea", true, null, null, "Text area", "fas fa-font");
$form->addInput("box", "1", "checkbox", true, null, null, "I agree to the terms of service");
$form->addButton("Submit", "fas fa-save", null, "submit", "savebtn");
$form->generate();

@ -16,39 +16,36 @@ foreach ($newsitems as $item) {
}
?>
<p class="lead">
<i class="fas fa-newspaper fa-fw"></i> <?php $Strings->get("Headlines"); ?>
</p>
<?php
foreach ($itemsbycategory as $category => $items) {
$cat = NewsCategory::fromString($category);
?>
<p class="lead">
<i class="<?php echo $cat->getIcon(); ?> fa-fw"></i> <?php $Strings->get($cat->toString()); ?>
</p>
<div class="list-group mb-4">
<?php
shuffle($items);
$count = 0;
foreach ($items as $item) {
if ($count >= 5) {
break;
}
$count++;
?>
<div class="list-group-item d-flex justify-content-between">
<a class="text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
<h4>
<?php echo htmlentities($item->getHeadline()); ?>
</h4>
<p class="text-muted"><?php echo htmlentities($item->getSource()); ?></p>
</a>
<?php if (!empty($item->getImage())) { ?>
<img src="<?php echo Thumbnail::jpegToBase64URI(Thumbnail::getThumbnailFromUrl($item->getImage(), 100)); ?>" alt="" class="img-fluid">
<?php } ?>
</div>
<?php
?>
<div class="list-group mb-4">
<?php
$count = 0;
foreach ($itemsbycategory["general"] as $item) {
if ($count >= 6) {
break;
}
$count++;
?>
</div>
<?php
}
<div class="list-group-item d-flex justify-content-between">
<a class="text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
<h4>
<?php echo htmlentities($item->getHeadline()); ?>
</h4>
<p class="text-muted"><?php echo htmlentities($item->getSource()); ?></p>
</a>
<?php if (!empty($item->getImage())) { ?>
<img src="<?php echo Thumbnail::getThumbnailCacheURL($item->getImage(), 100); ?>" alt="" class="img-fluid">
<?php } ?>
</div>
<?php
}
?>
</div>
<?php
?>

@ -10,9 +10,19 @@ $newsitems = News::getItems();
<div class="btn-toolbar mb-4" role="toolbar">
<div class="btn-group btn-group-toggle">
<?php foreach (NewsCategory::CATEGORIES as $cat) { ?>
<?php
foreach (NewsCategory::CATEGORIES as $cat) {
$category = NewsCategory::fromString($cat);
?>
<label class="btn btn-secondary">
<input type="radio" name="newscategory" id="category-btn-<?php echo strtolower($cat); ?>" value="<?php echo strtolower($cat); ?>" autocomplete="off"> <?php $Strings->get($cat); ?>
<input
type="radio"
name="newscategory"
id="category-btn-<?php echo $category->toString(); ?>"
value="<?php echo $category->toString(); ?>"
autocomplete="off" />
<i class="<?php echo $category->getIcon(); ?>"></i>
<?php $Strings->get($category->toString()); ?>
</label>
<?php } ?>
</div>
@ -24,14 +34,24 @@ $newsitems = News::getItems();
<div class="col-12 col-sm-6 col-md-6 col-lg-4 px-1 m-0 grid__brick" data-groups='["<?php echo $item->getCategory()->toString(); ?>"]'>
<div class="card mb-2">
<?php if (!empty($item->getImage())) { ?>
<img src="<?php echo $item->getImage(); ?>" class="card-img-top" alt="">
<a href="<?php echo $item->getURL(); ?>" target="_BLANK">
<img src="<?php
if (strpos($item->getImage(), "preview.redd.it") !== false) {
echo $item->getImage();
} else {
echo Thumbnail::getThumbnailCacheURL($item->getImage(), 500);
}
?>" class="card-img-top" alt="">
</a>
<?php } ?>
<a class="card-body text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
<h4 class="card-title">
<?php echo htmlentities($item->getHeadline()); ?>
</h4>
<p class="small"><?php echo htmlentities($item->getSource()); ?></p>
</a>
<div class="card-body">
<a class="text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
<h4 class="card-title">
<?php echo htmlentities($item->getHeadline()); ?>
</h4>
</a>
<p class="small"><?php echo $item->getSource(); ?></p>
</div>
</div>
</div>
<?php } ?>

@ -40,6 +40,10 @@ $SETTINGS = [
"apikeys" => [
"newsapi.org" => "",
"darksky.net" => "",
"twitter.com" => [
"consumer_key" => "",
"consumer_secret" => ""
]
],
// Which data sources to use
"sources" => [
@ -59,7 +63,7 @@ $SETTINGS = [
// Language to use for localization. See langs folder to add a language.
"language" => "en",
// Shown in the footer of all the pages.
"footer_text" => "Data sources: <a href=\"https://newsapi.org\">NewsAPI.org</a>, <a href=\"https://reddit.com\">Reddit</a>, <a href=\"https://darksky.net\">Dark Sky</a>",
"footer_text" => "News sources: <a href=\"https://newsapi.org\">NewsAPI.org</a>, <a href=\"https://reddit.com\">Reddit.com</a>. Weather <a href=\"https://darksky.net/poweredby/\">powered by Dark Sky</a>.",
// Also shown in the footer, but with "Copyright <current_year>" in front.
"copyright" => "Netsyms Technologies",
// Base URL for building links relative to the location of the app.

@ -16,4 +16,6 @@ $("input[name=newscategory]").on("change", function () {
setInterval(function () {
window.shuffleInstance.layout();
}, 500);
}, 500);
window.shuffleInstance.filter("general");
Loading…
Cancel
Save