Add basic news reader functionality
This commit is contained in:
parent
7d30251cd6
commit
ac1e937dfd
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ vendor
|
|||||||
settings.php
|
settings.php
|
||||||
nbproject/private
|
nbproject/private
|
||||||
*.sync-conflict*
|
*.sync-conflict*
|
||||||
|
/database.mwb.bak
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
|||||||
[submodule "static/css/material-color"]
|
[submodule "static/css/material-color"]
|
||||||
path = static/css/material-color
|
path = static/css/material-color
|
||||||
url = https://source.netsyms.com/Netsyms/Material-Color
|
url = https://source.netsyms.com/Netsyms/Material-Color
|
||||||
|
[submodule "static/Shuffle"]
|
||||||
|
path = static/Shuffle
|
||||||
|
url = https://source.netsyms.com/Mirrors/Vestride_Shuffle.git
|
||||||
|
@ -35,4 +35,8 @@ switch ($VARS['action']) {
|
|||||||
session_destroy();
|
session_destroy();
|
||||||
header('Location: index.php?logout=1');
|
header('Location: index.php?logout=1');
|
||||||
die("Logged out.");
|
die("Logged out.");
|
||||||
|
case "thumbnail":
|
||||||
|
header("Content-Type: image/jpeg");
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "netsyms/business-app-template",
|
"name": "netsyms/todaystream",
|
||||||
"description": "Template for a webapp integrated with an AccountHub server.",
|
"description": "News and weather app.",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"require": {
|
"require": {
|
||||||
"catfan/medoo": "^1.5",
|
"catfan/medoo": "^1.5",
|
||||||
|
77
composer.lock
generated
77
composer.lock
generated
@ -4,21 +4,20 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"hash": "5c7439c6e041764f2f6b0270a95ab3ae",
|
"content-hash": "5642e5d15688c9510f4b0d1b4ceed67e",
|
||||||
"content-hash": "e4e700119f47d2f68b0ed82abaf8c5c6",
|
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "catfan/medoo",
|
"name": "catfan/medoo",
|
||||||
"version": "v1.5.7",
|
"version": "v1.6.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/catfan/Medoo.git",
|
"url": "https://github.com/catfan/Medoo.git",
|
||||||
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf"
|
"reference": "53a02b300d673f716cb06bf0e24fd774ec53939f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf",
|
"url": "https://api.github.com/repos/catfan/Medoo/zipball/53a02b300d673f716cb06bf0e24fd774ec53939f",
|
||||||
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf",
|
"reference": "53a02b300d673f716cb06bf0e24fd774ec53939f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -64,7 +63,7 @@
|
|||||||
"sql",
|
"sql",
|
||||||
"sqlite"
|
"sqlite"
|
||||||
],
|
],
|
||||||
"time": "2018-06-14 18:59:08"
|
"time": "2018-12-08T20:24:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/guzzle",
|
"name": "guzzlehttp/guzzle",
|
||||||
@ -129,7 +128,7 @@
|
|||||||
"rest",
|
"rest",
|
||||||
"web service"
|
"web service"
|
||||||
],
|
],
|
||||||
"time": "2018-04-22 15:46:56"
|
"time": "2018-04-22T15:46:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/promises",
|
"name": "guzzlehttp/promises",
|
||||||
@ -180,36 +179,37 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"promise"
|
"promise"
|
||||||
],
|
],
|
||||||
"time": "2016-12-20 10:07:11"
|
"time": "2016-12-20T10:07:11+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/psr7",
|
"name": "guzzlehttp/psr7",
|
||||||
"version": "1.4.2",
|
"version": "1.5.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/psr7.git",
|
"url": "https://github.com/guzzle/psr7.git",
|
||||||
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
|
"reference": "9f83dded91781a01c63574e387eaa769be769115"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
|
"url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
|
||||||
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
|
"reference": "9f83dded91781a01c63574e387eaa769be769115",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.4.0",
|
"php": ">=5.4.0",
|
||||||
"psr/http-message": "~1.0"
|
"psr/http-message": "~1.0",
|
||||||
|
"ralouphie/getallheaders": "^2.0.5"
|
||||||
},
|
},
|
||||||
"provide": {
|
"provide": {
|
||||||
"psr/http-message-implementation": "1.0"
|
"psr/http-message-implementation": "1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "~4.0"
|
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.4-dev"
|
"dev-master": "1.5-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@ -239,13 +239,14 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"http",
|
"http",
|
||||||
"message",
|
"message",
|
||||||
|
"psr-7",
|
||||||
"request",
|
"request",
|
||||||
"response",
|
"response",
|
||||||
"stream",
|
"stream",
|
||||||
"uri",
|
"uri",
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"time": "2017-03-20 17:10:46"
|
"time": "2018-12-04T20:46:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/http-message",
|
"name": "psr/http-message",
|
||||||
@ -295,7 +296,47 @@
|
|||||||
"request",
|
"request",
|
||||||
"response"
|
"response"
|
||||||
],
|
],
|
||||||
"time": "2016-08-06 14:39:51"
|
"time": "2016-08-06T14:39:51+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ralouphie/getallheaders",
|
||||||
|
"version": "2.0.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||||
|
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
|
||||||
|
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~3.7.0",
|
||||||
|
"satooshi/php-coveralls": ">=1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/getallheaders.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ralph Khattar",
|
||||||
|
"email": "ralph.khattar@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A polyfill for getallheaders.",
|
||||||
|
"time": "2016-02-11T07:05:27+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
BIN
database.mwb
Normal file
BIN
database.mwb
Normal file
Binary file not shown.
9
langs/en/categories.json
Normal file
9
langs/en/categories.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"business": "Business",
|
||||||
|
"entertainment": "Entertainment",
|
||||||
|
"general": "General",
|
||||||
|
"health": "Health",
|
||||||
|
"science": "Science",
|
||||||
|
"sports": "Sports",
|
||||||
|
"technology": "Technology"
|
||||||
|
}
|
85
lib/ApiFetcher.lib.php
Normal file
85
lib/ApiFetcher.lib.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?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 ApiFetcher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a URL. Caches and returns the result, but if the cache has a recent copy
|
||||||
|
* return that instead of making the request.
|
||||||
|
*
|
||||||
|
* Basically, the goal is to stay within the free tier of paid APIs :)
|
||||||
|
*
|
||||||
|
* @global Medoo $database
|
||||||
|
* @param string $url
|
||||||
|
* @param array $params key=>value array of URL parameters
|
||||||
|
* @param array $extraParams key=>value array of URL parameters that will be ignored when checking the cache
|
||||||
|
* @param string $expires When the fetched resource should expire from the cache. A string parsable by strtotime()
|
||||||
|
* @return string The content of the requested URL.
|
||||||
|
*/
|
||||||
|
static function get(string $url, array $params = [], array $extraParams = [], string $expires = "+15 minutes"): string {
|
||||||
|
global $database;
|
||||||
|
|
||||||
|
// Delete stale records
|
||||||
|
$database->delete("requestcache", ["expires[<=]" => date("Y-m-d H:i:s")]);
|
||||||
|
|
||||||
|
// Make sure the params are in the same order every time
|
||||||
|
ksort($params);
|
||||||
|
|
||||||
|
$urlparams = [];
|
||||||
|
foreach ($params as $k => $v) {
|
||||||
|
$urlparams[] = urlencode($k) . "=" . urlencode($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the URL that will be cached
|
||||||
|
$cacheurl = $url . "?" . implode("&", $urlparams);
|
||||||
|
|
||||||
|
foreach ($extraParams as $k => $v) {
|
||||||
|
$urlparams[] = urlencode($k) . "=" . urlencode($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($database->has("requestcache", ["AND" => ["url" => $cacheurl, "expires[>]" => date("Y-m-d H:i:s")]])) {
|
||||||
|
return $database->get("requestcache", "content", ["AND" => ["url" => $cacheurl, "expires[>]" => date("Y-m-d H:i:s")]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the actual URL that will be requested
|
||||||
|
$requesturl = $url . "?" . implode("&", $urlparams);
|
||||||
|
|
||||||
|
$content = file_get_contents($requesturl);
|
||||||
|
|
||||||
|
// Only insert into db if it didn't fail horribly
|
||||||
|
if ($content !== FALSE) {
|
||||||
|
$database->insert("requestcache", ["url" => $cacheurl, "expires" => date("Y-m-d H:i:s", strtotime($expires)), "content" => $content]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the given URL from the cache.
|
||||||
|
*
|
||||||
|
* @global Medoo $database
|
||||||
|
* @param string $url
|
||||||
|
* @param array $params key=>value array of URL parameters
|
||||||
|
*/
|
||||||
|
static function removeFromCache(string $url = "", array $params = []) {
|
||||||
|
global $database;
|
||||||
|
|
||||||
|
// Make sure the params are in the same order every time
|
||||||
|
ksort($params);
|
||||||
|
|
||||||
|
$urlparams = [];
|
||||||
|
foreach ($params as $k => $v) {
|
||||||
|
$urlparams[] = urlencode($k) . "=" . urlencode($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheurl = $url . "?" . implode("&", $urlparams);
|
||||||
|
|
||||||
|
$database->delete("requestcache", ["url" => $cacheurl]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
lib/News.lib.php
Normal file
37
lib/News.lib.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load news items from multiple sources.
|
||||||
|
*/
|
||||||
|
class News {
|
||||||
|
|
||||||
|
static $items = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load news items from each source.
|
||||||
|
* @param array $sources
|
||||||
|
*/
|
||||||
|
static function load(array $sources) {
|
||||||
|
foreach ($sources as $s) {
|
||||||
|
$class = "NewsSource_$s";
|
||||||
|
$source = new $class();
|
||||||
|
$source->loadItems();
|
||||||
|
News::$items = array_merge(News::$items, $source->getItems());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all news items from all sources loaded.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
static function getItems() {
|
||||||
|
return News::$items;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
lib/NewsCategory.lib.php
Normal file
123
lib/NewsCategory.lib.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?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 NewsCategory {
|
||||||
|
|
||||||
|
private $category;
|
||||||
|
|
||||||
|
const NONE_ALL = -1;
|
||||||
|
const BUSINESS = 1;
|
||||||
|
const ENTERTAINMENT = 2;
|
||||||
|
const GENERAL = 4;
|
||||||
|
const HEALTH = 8;
|
||||||
|
const SCIENCE = 16;
|
||||||
|
const SPORTS = 32;
|
||||||
|
const TECHNOLOGY = 64;
|
||||||
|
|
||||||
|
const CATEGORIES = [
|
||||||
|
self::BUSINESS => "Business",
|
||||||
|
self::ENTERTAINMENT => "Entertainment",
|
||||||
|
self::GENERAL => "General",
|
||||||
|
self::HEALTH => "Health",
|
||||||
|
self::SCIENCE => "Science",
|
||||||
|
self::SPORTS => "Sports",
|
||||||
|
self::TECHNOLOGY => "Technology"
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(int $category) {
|
||||||
|
$this->category = $category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the category as an int corresponding to one of the constants.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function get(): int {
|
||||||
|
return $this->category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string representation of the category.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function toString(): string {
|
||||||
|
switch ($this->category) {
|
||||||
|
case self::BUSINESS:
|
||||||
|
return "business";
|
||||||
|
case self::ENTERTAINMENT:
|
||||||
|
return "entertainment";
|
||||||
|
case self::GENERAL:
|
||||||
|
return "general";
|
||||||
|
case self::HEALTH:
|
||||||
|
return "health";
|
||||||
|
case self::SCIENCE:
|
||||||
|
return "science";
|
||||||
|
case self::SPORTS:
|
||||||
|
return "sports";
|
||||||
|
case self::TECHNOLOGY:
|
||||||
|
return "technology";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromString(string $category): NewsCategory {
|
||||||
|
$cat = self::NONE_ALL;
|
||||||
|
switch (strtolower($category)) {
|
||||||
|
case "business":
|
||||||
|
$cat = self::BUSINESS;
|
||||||
|
break;
|
||||||
|
case "entertainment":
|
||||||
|
$cat = self::ENTERTAINMENT;
|
||||||
|
break;
|
||||||
|
case "general":
|
||||||
|
$cat = self::GENERAL;
|
||||||
|
break;
|
||||||
|
case "health":
|
||||||
|
$cat = self::HEALTH;
|
||||||
|
break;
|
||||||
|
case "science":
|
||||||
|
$cat = self::SCIENCE;
|
||||||
|
break;
|
||||||
|
case "sports":
|
||||||
|
$cat = self::SPORTS;
|
||||||
|
break;
|
||||||
|
case "technology":
|
||||||
|
case "tech":
|
||||||
|
$cat = self::TECHNOLOGY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new NewsCategory($cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a suitable FontAwesome 5 icon for the category.
|
||||||
|
* @return string CSS classes, such as "fas fa-icon".
|
||||||
|
*/
|
||||||
|
public function getIcon(): string {
|
||||||
|
switch ($this->category) {
|
||||||
|
case self::BUSINESS:
|
||||||
|
return "fas fa-briefcase";
|
||||||
|
case self::ENTERTAINMENT:
|
||||||
|
return "fas fa-tv";
|
||||||
|
case self::GENERAL:
|
||||||
|
return "fas fa-info-circle";
|
||||||
|
case self::HEALTH:
|
||||||
|
return "fas fa-heartbeat";
|
||||||
|
case self::SCIENCE:
|
||||||
|
return "fas fa-atom";
|
||||||
|
case self::SPORTS:
|
||||||
|
return "fas fa-futbol";
|
||||||
|
case self::TECHNOLOGY:
|
||||||
|
return "fas fa-laptop";
|
||||||
|
default:
|
||||||
|
return "fas fa-newspaper";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
61
lib/NewsItem.lib.php
Normal file
61
lib/NewsItem.lib.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?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 NewsItem {
|
||||||
|
|
||||||
|
private $img = "";
|
||||||
|
private $headline = "";
|
||||||
|
private $source = "";
|
||||||
|
private $via = "";
|
||||||
|
private $url = "";
|
||||||
|
private $timestamp = 0;
|
||||||
|
private $category;
|
||||||
|
|
||||||
|
function __construct(string $headline = "", string $img = "", string $url = "", string $source = "", string $via = "", int $timestamp = 0, $category = null) {
|
||||||
|
$this->headline = $headline;
|
||||||
|
$this->img = $img;
|
||||||
|
$this->url = $url;
|
||||||
|
$this->source = $source;
|
||||||
|
$this->via = $via;
|
||||||
|
$this->timestamp = $timestamp;
|
||||||
|
if (is_null($category)) {
|
||||||
|
$this->category = new NewsCategory(NewsCategory::GENERAL);
|
||||||
|
} else {
|
||||||
|
$this->category = $category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImage(): string {
|
||||||
|
return $this->img;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getURL(): string {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeadline(): string {
|
||||||
|
return $this->headline;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSource(): string {
|
||||||
|
return $this->source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVia(): string {
|
||||||
|
return $this->via;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimestamp(): int {
|
||||||
|
return $this->timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCategory(): \NewsCategory {
|
||||||
|
return $this->category;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
lib/NewsSource.lib.php
Normal file
27
lib/NewsSource.lib.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for news sources.
|
||||||
|
*/
|
||||||
|
class NewsSource {
|
||||||
|
|
||||||
|
private $items = [];
|
||||||
|
|
||||||
|
function getItems(): array {
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch news items from this source.
|
||||||
|
*/
|
||||||
|
function loadItems() {
|
||||||
|
// Do nothing, because this is a dummy source
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
lib/NewsSource_NewsAPI.lib.php
Normal file
66
lib/NewsSource_NewsAPI.lib.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?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 NewsSource_NewsAPI extends NewsSource {
|
||||||
|
|
||||||
|
private $items = [];
|
||||||
|
|
||||||
|
function getItems(): array {
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::BUSINESS)));
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::ENTERTAINMENT)));
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::GENERAL)));
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::HEALTH)));
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::SCIENCE)));
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::SPORTS)));
|
||||||
|
$this->loadHeadlines((new NewsCategory(NewsCategory::TECHNOLOGY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadHeadlines($category = null, string $country = "us", $apikey = null) {
|
||||||
|
global $SETTINGS;
|
||||||
|
if (is_null($category)) {
|
||||||
|
$category = new NewsCategory(NewsCategory::GENERAL);
|
||||||
|
}
|
||||||
|
if (is_null($apikey)) {
|
||||||
|
$apikey = $SETTINGS['apikeys']['newsapi.org'];
|
||||||
|
}
|
||||||
|
$url = "https://newsapi.org/v2/top-headlines";
|
||||||
|
$params = [];
|
||||||
|
if (!empty($country)) {
|
||||||
|
$params['country'] = $country;
|
||||||
|
}
|
||||||
|
if (!empty($category->toString())) {
|
||||||
|
$params['category'] = $category->toString();
|
||||||
|
}
|
||||||
|
$items = [];
|
||||||
|
$json = ApiFetcher::get($url, $params, ["apiKey" => $apikey]);
|
||||||
|
$data = json_decode($json, TRUE);
|
||||||
|
if ($data['status'] != "ok") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$articles = $data['articles'];
|
||||||
|
foreach ($articles as $n) {
|
||||||
|
$title = $n['title'];
|
||||||
|
$image = $n['urlToImage'];
|
||||||
|
if (is_null($image)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$url = $n['url'];
|
||||||
|
$source = $n['source']['name'];
|
||||||
|
$timestamp = strtotime($n['publishedAt']);
|
||||||
|
|
||||||
|
$items[] = new NewsItem($title, $image, $url, $source, "NewsAPI.org", $timestamp, $category);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->items = array_merge($this->items, $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
lib/NewsSource_Reddit.lib.php
Normal file
72
lib/NewsSource_Reddit.lib.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?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 NewsSource_Reddit extends NewsSource {
|
||||||
|
|
||||||
|
private $items = [];
|
||||||
|
|
||||||
|
function getItems(): array {
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
$this->loadSubreddit("news+worldnews+UpliftingNews", new NewsCategory(NewsCategory::GENERAL));
|
||||||
|
//$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"]);
|
||||||
|
$news = json_decode($json, TRUE)['data']['children'];
|
||||||
|
foreach ($news as $d) {
|
||||||
|
$n = $d['data'];
|
||||||
|
|
||||||
|
// Ignore non-linky or NSFW posts
|
||||||
|
if ($n['is_self']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($n['is_video']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($n['over_18']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (empty($n['url'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbnail image
|
||||||
|
$image = "";
|
||||||
|
if (isset($n['thumbnail']) && $n['thumbnail'] != "default") {
|
||||||
|
$image = $n['thumbnail'];
|
||||||
|
}
|
||||||
|
if (isset($n['preview']['images'][0]['resolutions'])) {
|
||||||
|
if (isset($n['preview']['images'][0]['resolutions'][2]['url'])) {
|
||||||
|
$image = $n['preview']['images'][0]['resolutions'][2]['url'];
|
||||||
|
} else if (isset($n['preview']['images'][0]['resolutions'][1]['url'])) {
|
||||||
|
$image = $n['preview']['images'][0]['resolutions'][1]['url'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = "reddit.com";
|
||||||
|
if (!empty($n['domain'])) {
|
||||||
|
$source = $n['domain'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = time();
|
||||||
|
if (!empty($n['created'])) {
|
||||||
|
$timestamp = $n['created'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$items[] = new NewsItem($n['title'], $image, $n['url'], $source, "reddit", $timestamp, $category);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->items = array_merge($this->items, $items);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
lib/Thumbnail.lib.php
Normal file
60
lib/Thumbnail.lib.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?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 Thumbnail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches an image URL, resizes it, and returns JPEG thumbnail data.
|
||||||
|
* Based on https://stackoverflow.com/a/29024968
|
||||||
|
* @param string $url
|
||||||
|
* @param int $width
|
||||||
|
* @param int,bool $height
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
static function getThumbnailFromUrl(string $url, int $width = 150, $height = true) {
|
||||||
|
|
||||||
|
// download and create gd image
|
||||||
|
$image = imagecreatefromstring(file_get_contents($url));
|
||||||
|
|
||||||
|
// calculate resized ratio
|
||||||
|
// Note: if $height is set to TRUE then we automatically calculate the height based on the ratio
|
||||||
|
$height = $height === true ? (imagesy($image) * $width / imagesx($image)) : $height;
|
||||||
|
|
||||||
|
// create image
|
||||||
|
$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();
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
// return resized image
|
||||||
|
return $imagedata; // if you need to use it
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes some data to base64.
|
||||||
|
* @param type $img
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
static function imgToBase64($img): string {
|
||||||
|
return base64_encode($img);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
lib/Weather.lib.php
Normal file
11
lib/Weather.lib.php
Normal file
@ -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 {
|
||||||
|
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<type>org.netbeans.modules.php.project</type>
|
<type>org.netbeans.modules.php.project</type>
|
||||||
<configuration>
|
<configuration>
|
||||||
<data xmlns="http://www.netbeans.org/ns/php-project/1">
|
<data xmlns="http://www.netbeans.org/ns/php-project/1">
|
||||||
<name>BusinessAppTemplate</name>
|
<name>TodayStream</name>
|
||||||
</data>
|
</data>
|
||||||
</configuration>
|
</configuration>
|
||||||
</project>
|
</project>
|
||||||
|
25
pages.php
25
pages.php
@ -7,19 +7,24 @@
|
|||||||
// List of pages and metadata
|
// List of pages and metadata
|
||||||
define("PAGES", [
|
define("PAGES", [
|
||||||
"home" => [
|
"home" => [
|
||||||
"title" => "Home",
|
"title" => "Overview",
|
||||||
"navbar" => true,
|
"navbar" => true,
|
||||||
"icon" => "fas fa-home"
|
"icon" => "fas fa-home",
|
||||||
|
"scripts" => [
|
||||||
|
"static/Shuffle/dist/shuffle.min.js",
|
||||||
|
"static/js/home.js"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"news" => [
|
||||||
|
"title" => "News",
|
||||||
|
"navbar" => true,
|
||||||
|
"icon" => "fas fa-newspaper",
|
||||||
|
"scripts" => [
|
||||||
|
"static/Shuffle/dist/shuffle.min.js",
|
||||||
|
"static/js/news.js"
|
||||||
|
]
|
||||||
],
|
],
|
||||||
"404" => [
|
"404" => [
|
||||||
"title" => "404 error"
|
"title" => "404 error"
|
||||||
],
|
|
||||||
"form" => [
|
|
||||||
"title" => "Form",
|
|
||||||
"navbar" => true,
|
|
||||||
"icon" => "fas fa-file-alt",
|
|
||||||
"scripts" => [
|
|
||||||
"static/js/form.js"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]);
|
]);
|
@ -1,6 +1,54 @@
|
|||||||
<?php
|
<?php
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/*
|
||||||
|
* 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
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
News::load($SETTINGS["sources"]["news"]);
|
||||||
|
|
||||||
|
$newsitems = News::getItems();
|
||||||
|
|
||||||
|
// Sort by category
|
||||||
|
$itemsbycategory = [];
|
||||||
|
foreach ($newsitems as $item) {
|
||||||
|
$itemsbycategory[$item->getCategory()->toString()][] = $item;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?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>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<h1>Hello World</h1>
|
|
41
pages/news.php
Normal file
41
pages/news.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?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/. */
|
||||||
|
|
||||||
|
News::load($SETTINGS["sources"]["news"]);
|
||||||
|
|
||||||
|
$newsitems = News::getItems();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="btn-toolbar mb-4" role="toolbar">
|
||||||
|
<div class="btn-group btn-group-toggle">
|
||||||
|
<?php foreach (NewsCategory::CATEGORIES as $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); ?>
|
||||||
|
</label>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" id="news-grid">
|
||||||
|
|
||||||
|
<?php foreach ($newsitems as $item) { ?>
|
||||||
|
<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="">
|
||||||
|
<?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>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="col-1 sizer-element"></div>
|
||||||
|
|
||||||
|
</div>
|
@ -19,14 +19,14 @@ $SETTINGS = [
|
|||||||
// See http://medoo.in/api/new for info
|
// See http://medoo.in/api/new for info
|
||||||
"database" => [
|
"database" => [
|
||||||
"type" => "mysql",
|
"type" => "mysql",
|
||||||
"name" => "app",
|
"name" => "todaystream",
|
||||||
"server" => "localhost",
|
"server" => "localhost",
|
||||||
"user" => "app",
|
"user" => "",
|
||||||
"password" => "",
|
"password" => "",
|
||||||
"charset" => "utf8"
|
"charset" => "utf8"
|
||||||
],
|
],
|
||||||
// Name of the app.
|
// Name of the app.
|
||||||
"site_title" => "Web App Template",
|
"site_title" => "TodayStream",
|
||||||
// Settings for connecting to the AccountHub server.
|
// Settings for connecting to the AccountHub server.
|
||||||
"accounthub" => [
|
"accounthub" => [
|
||||||
// URL for the API endpoint
|
// URL for the API endpoint
|
||||||
@ -36,6 +36,21 @@ $SETTINGS = [
|
|||||||
// API key
|
// API key
|
||||||
"key" => "123"
|
"key" => "123"
|
||||||
],
|
],
|
||||||
|
// API keys for data sources
|
||||||
|
"apikeys" => [
|
||||||
|
"newsapi.org" => "",
|
||||||
|
"darksky.net" => "",
|
||||||
|
],
|
||||||
|
// Which data sources to use
|
||||||
|
"sources" => [
|
||||||
|
"news" => [
|
||||||
|
"NewsAPI",
|
||||||
|
"Reddit"
|
||||||
|
],
|
||||||
|
"weather" => [
|
||||||
|
"DarkSky"
|
||||||
|
]
|
||||||
|
],
|
||||||
// List of required user permissions to access this app.
|
// List of required user permissions to access this app.
|
||||||
"permissions" => [
|
"permissions" => [
|
||||||
],
|
],
|
||||||
@ -44,7 +59,7 @@ $SETTINGS = [
|
|||||||
// Language to use for localization. See langs folder to add a language.
|
// Language to use for localization. See langs folder to add a language.
|
||||||
"language" => "en",
|
"language" => "en",
|
||||||
// Shown in the footer of all the pages.
|
// Shown in the footer of all the pages.
|
||||||
"footer_text" => "",
|
"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>",
|
||||||
// Also shown in the footer, but with "Copyright <current_year>" in front.
|
// Also shown in the footer, but with "Copyright <current_year>" in front.
|
||||||
"copyright" => "Netsyms Technologies",
|
"copyright" => "Netsyms Technologies",
|
||||||
// Base URL for building links relative to the location of the app.
|
// Base URL for building links relative to the location of the app.
|
||||||
|
1
static/Shuffle
Submodule
1
static/Shuffle
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit c742bfaefc8908170825227a7385f20e0ba5da37
|
19
static/js/news.js
Normal file
19
static/js/news.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
window.shuffleInstance = new window.Shuffle(document.getElementById('news-grid'), {
|
||||||
|
itemSelector: '.grid__brick',
|
||||||
|
sizer: '.sizer-element'
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[name=newscategory]").on("change", function () {
|
||||||
|
window.shuffleInstance.filter($(this).val());
|
||||||
|
$(this).button('toggle');
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
window.shuffleInstance.layout();
|
||||||
|
}, 500);
|
Loading…
x
Reference in New Issue
Block a user