diff --git a/.gitignore b/.gitignore
index 07fe371..207052b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
vendor
settings.php
nbproject/private
-*.sync-conflict*
\ No newline at end of file
+*.sync-conflict*
+/database.mwb.bak
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index 7cba5c4..ea9ed74 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "static/css/material-color"]
path = static/css/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
diff --git a/action.php b/action.php
index 67b230b..28f6029 100644
--- a/action.php
+++ b/action.php
@@ -35,4 +35,8 @@ switch ($VARS['action']) {
session_destroy();
header('Location: index.php?logout=1');
die("Logged out.");
+ case "thumbnail":
+ header("Content-Type: image/jpeg");
+ // TODO
+ break;
}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index fb37c32..9655a01 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
- "name": "netsyms/business-app-template",
- "description": "Template for a webapp integrated with an AccountHub server.",
+ "name": "netsyms/todaystream",
+ "description": "News and weather app.",
"type": "project",
"require": {
"catfan/medoo": "^1.5",
diff --git a/composer.lock b/composer.lock
index 8d36028..5f8a417 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,21 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "5c7439c6e041764f2f6b0270a95ab3ae",
- "content-hash": "e4e700119f47d2f68b0ed82abaf8c5c6",
+ "content-hash": "5642e5d15688c9510f4b0d1b4ceed67e",
"packages": [
{
"name": "catfan/medoo",
- "version": "v1.5.7",
+ "version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/catfan/Medoo.git",
- "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf"
+ "reference": "53a02b300d673f716cb06bf0e24fd774ec53939f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf",
- "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf",
+ "url": "https://api.github.com/repos/catfan/Medoo/zipball/53a02b300d673f716cb06bf0e24fd774ec53939f",
+ "reference": "53a02b300d673f716cb06bf0e24fd774ec53939f",
"shasum": ""
},
"require": {
@@ -64,7 +63,7 @@
"sql",
"sqlite"
],
- "time": "2018-06-14 18:59:08"
+ "time": "2018-12-08T20:24:23+00:00"
},
{
"name": "guzzlehttp/guzzle",
@@ -129,7 +128,7 @@
"rest",
"web service"
],
- "time": "2018-04-22 15:46:56"
+ "time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
@@ -180,36 +179,37 @@
"keywords": [
"promise"
],
- "time": "2016-12-20 10:07:11"
+ "time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "1.4.2",
+ "version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
+ "reference": "9f83dded91781a01c63574e387eaa769be769115"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
- "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
+ "reference": "9f83dded91781a01c63574e387eaa769be769115",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
- "psr/http-message": "~1.0"
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.0"
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "1.5-dev"
}
},
"autoload": {
@@ -239,13 +239,14 @@
"keywords": [
"http",
"message",
+ "psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
- "time": "2017-03-20 17:10:46"
+ "time": "2018-12-04T20:46:45+00:00"
},
{
"name": "psr/http-message",
@@ -295,7 +296,47 @@
"request",
"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": [],
diff --git a/database.mwb b/database.mwb
new file mode 100644
index 0000000..26402ce
Binary files /dev/null and b/database.mwb differ
diff --git a/langs/en/categories.json b/langs/en/categories.json
new file mode 100644
index 0000000..5395e91
--- /dev/null
+++ b/langs/en/categories.json
@@ -0,0 +1,9 @@
+{
+ "business": "Business",
+ "entertainment": "Entertainment",
+ "general": "General",
+ "health": "Health",
+ "science": "Science",
+ "sports": "Sports",
+ "technology": "Technology"
+}
diff --git a/lib/ApiFetcher.lib.php b/lib/ApiFetcher.lib.php
new file mode 100644
index 0000000..37f9189
--- /dev/null
+++ b/lib/ApiFetcher.lib.php
@@ -0,0 +1,85 @@
+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]);
+ }
+
+}
diff --git a/lib/News.lib.php b/lib/News.lib.php
new file mode 100644
index 0000000..5a1bca7
--- /dev/null
+++ b/lib/News.lib.php
@@ -0,0 +1,37 @@
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/lib/NewsCategory.lib.php b/lib/NewsCategory.lib.php
new file mode 100644
index 0000000..0deb6ef
--- /dev/null
+++ b/lib/NewsCategory.lib.php
@@ -0,0 +1,123 @@
+ "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";
+ }
+ }
+
+}
diff --git a/lib/NewsItem.lib.php b/lib/NewsItem.lib.php
new file mode 100644
index 0000000..ba39699
--- /dev/null
+++ b/lib/NewsItem.lib.php
@@ -0,0 +1,61 @@
+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;
+ }
+
+}
diff --git a/lib/NewsSource.lib.php b/lib/NewsSource.lib.php
new file mode 100644
index 0000000..4883fd8
--- /dev/null
+++ b/lib/NewsSource.lib.php
@@ -0,0 +1,27 @@
+items;
+ }
+
+ /**
+ * Fetch news items from this source.
+ */
+ function loadItems() {
+ // Do nothing, because this is a dummy source
+ }
+
+}
\ No newline at end of file
diff --git a/lib/NewsSource_NewsAPI.lib.php b/lib/NewsSource_NewsAPI.lib.php
new file mode 100644
index 0000000..b8ae4d0
--- /dev/null
+++ b/lib/NewsSource_NewsAPI.lib.php
@@ -0,0 +1,66 @@
+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);
+ }
+
+}
diff --git a/lib/NewsSource_Reddit.lib.php b/lib/NewsSource_Reddit.lib.php
new file mode 100644
index 0000000..e6d4a59
--- /dev/null
+++ b/lib/NewsSource_Reddit.lib.php
@@ -0,0 +1,72 @@
+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);
+ }
+
+}
diff --git a/lib/Thumbnail.lib.php b/lib/Thumbnail.lib.php
new file mode 100644
index 0000000..2f0addf
--- /dev/null
+++ b/lib/Thumbnail.lib.php
@@ -0,0 +1,60 @@
+org.netbeans.modules.php.project
+ get($cat->toString()); ?> +
+getSource()); ?>
+ + + getImage())) { ?> + + +getSource()); ?>
+ +