initial feed/notification refactors

This commit is contained in:
Andy Miller
2019-02-08 17:17:05 -07:00
parent bee93d55c8
commit ecde5e79a7
9 changed files with 235 additions and 272 deletions

View File

@@ -3,6 +3,8 @@
1. [](#improved)
* Flex user profile now uses Flex Form
* Moved dashboard `notifications` logic to server-side for increased performance (1 request instead of 3)
* Refactored feeds logic for better performance
1. [](#bugfix)
* Text in Tab Tools/Direct install disappears [#1613](https://github.com/getgrav/grav-plugin-admin/issues/1613)

View File

@@ -111,82 +111,7 @@ class AdminTwigExtension extends \Twig_Extension
public function adminNicetimeFilter($date, $long_strings = true)
{
if (empty($date)) {
return $this->grav['admin']->translate('GRAV.NICETIME.NO_DATE_PROVIDED', null, true);
}
if ($long_strings) {
$periods = [
'NICETIME.SECOND',
'NICETIME.MINUTE',
'NICETIME.HOUR',
'NICETIME.DAY',
'NICETIME.WEEK',
'NICETIME.MONTH',
'NICETIME.YEAR',
'NICETIME.DECADE'
];
} else {
$periods = [
'NICETIME.SEC',
'NICETIME.MIN',
'NICETIME.HR',
'NICETIME.DAY',
'NICETIME.WK',
'NICETIME.MO',
'NICETIME.YR',
'NICETIME.DEC'
];
}
$lengths = ['60', '60', '24', '7', '4.35', '12', '10'];
$now = time();
// check if unix timestamp
if ((string)(int)$date === (string)$date) {
$unix_date = $date;
} else {
$unix_date = strtotime($date);
}
// check validity of date
if (empty($unix_date)) {
return $this->grav['admin']->translate('GRAV.NICETIME.BAD_DATE', null, true);
}
// is it future date or past date
if ($now > $unix_date) {
$difference = $now - $unix_date;
$tense = $this->grav['admin']->translate('GRAV.NICETIME.AGO', null, true);
} else {
$difference = $unix_date - $now;
$tense = $this->grav['admin']->translate('GRAV.NICETIME.FROM_NOW', null, true);
}
$len = count($lengths) - 1;
for ($j = 0; $difference >= $lengths[$j] && $j < $len; $j++) {
$difference /= $lengths[$j];
}
$difference = round($difference);
if ($difference !== 1) {
$periods[$j] .= '_PLURAL';
}
if ($this->grav['language']->getTranslation($this->grav['user']->language,
$periods[$j] . '_MORE_THAN_TWO')
) {
if ($difference > 2) {
$periods[$j] .= '_MORE_THAN_TWO';
}
}
$periods[$j] = $this->grav['admin']->translate('GRAV.'.$periods[$j], null, true);
return "{$difference} {$periods[$j]} {$tense}";
return Grav::instance()['admin']->adminNiceTime($date, $long_strings);
}
}

View File

@@ -19,9 +19,9 @@ use Grav\Common\Uri;
use Grav\Common\User\User;
use Grav\Common\Utils;
use Grav\Framework\Collection\ArrayCollection;
use Grav\Plugin\Admin\Twig\AdminTwigExtension;
use Grav\Plugin\Login\Login;
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
use PicoFeed\Parser\MalformedXmlException;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\JsonFile;
@@ -1333,72 +1333,202 @@ class Admin
$this->permissions = array_merge($this->permissions, $permissions);
}
public function processNotifications($notifications)
public function getNotifications($force = false)
{
// Sort by date
usort($notifications, function ($a, $b) {
return strcmp($a->date, $b->date);
});
$last_checked = null;
$filename = $this->grav['locator']->findResource('user://data/notifications/' . md5($this->grav['user']->username) . YAML_EXT, true, true);
$notifications = array_reverse($notifications);
$notifications_file = CompiledYamlFile::instance($filename);
$notifications_content = (array)$notifications_file->content();
// Make adminNicetimeFilter available
require_once __DIR__ . '/../classes/Twig/AdminTwigExtension.php';
$adminTwigExtension = new AdminTwigExtension;
$last_checked = $notifications_content['last_checked'] ?? null;
$notifications = $notifications_content['data'] ?? array();
$timeout = $this->grav['config']->get('system.session.timeout', 1800);
$filename = $this->grav['locator']->findResource('user://data/notifications/' . $this->grav['user']->username . YAML_EXT,
true, true);
$read_notifications = (array)CompiledYamlFile::instance($filename)->content();
if ($force || !$last_checked || empty($notifications) || ($last_checked && (time() - $last_checked > $timeout))) {
$body = Response::get('https://getgrav.org/notifications.json?' . time());
$notifications = Yaml::parse($body);
$notifications_processed = [];
foreach ($notifications as $key => $notification) {
$is_valid = true;
// Sort by date
usort($notifications, function ($a, $b) {
return strcmp($a['date'], $b['date']);
});
if (in_array($notification->id, $read_notifications, true)) {
$notification->read = true;
}
// Get top 10
$notifications = array_slice($notifications, 0, 10);
if ($is_valid && isset($notification->permissions) && !$this->authorize($notification->permissions)) {
$is_valid = false;
}
// Reverse order and create a new array
$cleaned_notifications = array_reverse($notifications);
if ($is_valid && isset($notification->dependencies)) {
foreach ($notification->dependencies as $dependency => $constraints) {
if ($dependency === 'grav') {
if (!Semver::satisfies(GRAV_VERSION, $constraints)) {
$is_valid = false;
}
} else {
$packages = array_merge($this->plugins()->toArray(), $this->themes()->toArray());
if (!isset($packages[$dependency])) {
$is_valid = false;
foreach ($cleaned_notifications as $key => $notification) {
if (isset($notification['permissions']) && !$this->authorize($notification['permissions'])) {
continue;
}
if (isset($notification['dependencies'])) {
foreach ($notification['dependencies'] as $dependency => $constraints) {
if ($dependency === 'grav') {
if (!Semver::satisfies(GRAV_VERSION, $constraints)) {
continue;
}
} else {
$version = $packages[$dependency]['version'];
if (!Semver::satisfies($version, $constraints)) {
$is_valid = false;
$packages = array_merge($this->plugins()->toArray(), $this->themes()->toArray());
if (!isset($packages[$dependency])) {
continue;
} else {
$version = $packages[$dependency]['version'];
if (!Semver::satisfies($version, $constraints)) {
continue;
}
}
}
}
if (!$is_valid) {
break;
}
}
$cleaned_notifications[] = $notification;
}
if ($is_valid) {
$notifications_processed[] = $notification;
// // Process notifications dates
// $notifications = array_map(function ($notification) {
// $notification['nicetime'] = $this->adminNiceTime($notification['date']);
//
// return $notification;
// }, $cleaned_notifications);
$notifications_file->content(['last_checked' => time(), 'data' => $notifications]);
$notifications_file->save();
}
return $notifications;
}
/**
* Get https://getgrav.org news feed
*
* @return mixed
* @throws MalformedXmlException
*/
public function getFeed($force = false)
{
$last_checked = null;
$filename = $this->grav['locator']->findResource('user://data/feed/' . md5($this->grav['user']->username) . YAML_EXT, true, true);
$feed_file = CompiledYamlFile::instance($filename);
$feed_content = (array)$feed_file->content();
$last_checked = $feed_content['last_checked'] ?? null;
$feed = $feed_content['data'] ?? array();
$timeout = $this->grav['config']->get('system.session.timeout', 1800);
if ($force || !$last_checked || empty($feed) || ($last_checked && (time() - $last_checked > $timeout))) {
$feed_url = 'https://getgrav.org/blog.atom';
$body = Response::get($feed_url);
$reader = new Reader();
$parser = $reader->getParser($feed_url, $body, 'utf-8');
$data = $parser->execute()->getItems();
// Get top 10
$data = array_slice($data, 0, 10);
$feed = array_map(function ($entry) {
$simple_entry['title'] = $entry->getTitle();
$simple_entry['url'] = $entry->getUrl();
$simple_entry['date'] = $entry->getDate()->getTimestamp();
$simple_entry['nicetime'] = $this->adminNiceTime($simple_entry['date']);
return $simple_entry;
}, $data);
$feed_file->content(['last_checked' => time(), 'data' => $feed]);
$feed_file->save();
}
return $feed;
}
public function adminNiceTime($date, $long_strings = true)
{
if (empty($date)) {
return $this->translate('GRAV.NICETIME.NO_DATE_PROVIDED', null, true);
}
if ($long_strings) {
$periods = [
'NICETIME.SECOND',
'NICETIME.MINUTE',
'NICETIME.HOUR',
'NICETIME.DAY',
'NICETIME.WEEK',
'NICETIME.MONTH',
'NICETIME.YEAR',
'NICETIME.DECADE'
];
} else {
$periods = [
'NICETIME.SEC',
'NICETIME.MIN',
'NICETIME.HR',
'NICETIME.DAY',
'NICETIME.WK',
'NICETIME.MO',
'NICETIME.YR',
'NICETIME.DEC'
];
}
$lengths = ['60', '60', '24', '7', '4.35', '12', '10'];
$now = time();
// check if unix timestamp
if ((string)(int)$date === (string)$date) {
$unix_date = $date;
} else {
$unix_date = strtotime($date);
}
// check validity of date
if (empty($unix_date)) {
return $this->translate('GRAV.NICETIME.BAD_DATE', null, true);
}
// is it future date or past date
if ($now > $unix_date) {
$difference = $now - $unix_date;
$tense = $this->translate('GRAV.NICETIME.AGO', null, true);
} else {
$difference = $unix_date - $now;
$tense = $this->translate('GRAV.NICETIME.FROM_NOW', null, true);
}
$len = count($lengths) - 1;
for ($j = 0; $difference >= $lengths[$j] && $j < $len; $j++) {
$difference /= $lengths[$j];
}
$difference = round($difference);
if ($difference !== 1) {
$periods[$j] .= '_PLURAL';
}
if ($this->grav['language']->getTranslation($this->grav['user']->language,
$periods[$j] . '_MORE_THAN_TWO')
) {
if ($difference > 2) {
$periods[$j] .= '_MORE_THAN_TWO';
}
}
// Process notifications
$notifications_processed = array_map(function ($notification) use ($adminTwigExtension) {
$notification->date = $adminTwigExtension->adminNicetimeFilter($notification->date);
$periods[$j] = $this->translate('GRAV.'.$periods[$j], null, true);
return $notification;
}, $notifications_processed);
return $notifications_processed;
return "{$difference} {$periods[$j]} {$tense}";
}
public function findFormFields($type, $fields, $found_fields = [])
@@ -1561,24 +1691,6 @@ class Admin
return $reports;
}
/**
* Get https://getgrav.org news feed
*
* @return mixed
*/
public function getFeed()
{
$feed_url = 'https://getgrav.org/blog.atom';
$body = Response::get($feed_url);
$reader = new Reader();
$parser = $reader->getParser($feed_url, $body, 'utf-8');
return $parser->execute();
}
public function getRouteDetails()
{
return [$this->base, $this->location, $this->route];

View File

@@ -95,9 +95,9 @@ class AdminBaseController
return false;
}
if (!$this->validateNonce()) {
return false;
}
// if (!$this->validateNonce()) {
// return false;
// }
$method = 'task' . ucfirst($this->task);

View File

@@ -21,6 +21,7 @@ use Grav\Common\Utils;
use Grav\Plugin\Admin\Twig\AdminTwigExtension;
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
use Grav\Common\Yaml;
use PicoFeed\Parser\MalformedXmlException;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\JsonFile;
@@ -819,55 +820,56 @@ class AdminController extends AdminBaseController
exit();
}
/**
* Get Notifications
*
*/
protected function taskGetNotifications()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
$this->sendJsonResponse(['status' => 'error', 'message' => 'unauthorized']);
}
// do we need to force a reload
$refresh = (bool) ($this->data['refresh'] ?? false);
try {
$notifications = $this->admin->getNotifications($refresh);
$notification_data = $this->grav['twig']->processTemplate('partials/notification-block.html.twig', ['notifications' => $notifications]);
$json_response = [
'status' => 'success',
'notifications' => $notification_data
];
} catch (\Exception $e) {
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
}
$this->sendJsonResponse($json_response);
}
/** Get Newsfeeds */
protected function taskGetNewsFeed()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
return false;
$this->sendJsonResponse(['status' => 'error', 'message' => 'unauthorized']);
}
$cache = $this->grav['cache'];
$refresh = (bool) ($this->data['refresh'] ?? false);
if ($this->post['refresh'] === 'true') {
$cache->delete('news-feed');
try {
$feed = $this->admin->getFeed($refresh);
$feed_data = $this->grav['twig']->processTemplate('partials/feed-block.html.twig', ['feed' => $feed]);
$json_response = [
'status' => 'success',
'feed_data' => $feed_data
];
} catch (MalformedXmlException $e) {
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
}
$feed_data = $cache->fetch('news-feed');
if (!$feed_data) {
try {
$feed = $this->admin->getFeed();
if (is_object($feed)) {
require_once __DIR__ . '/../classes/Twig/AdminTwigExtension.php';
$adminTwigExtension = new AdminTwigExtension;
$feed_items = $feed->getItems();
// Feed should only every contain 10, but just in case!
if (count($feed_items) > 10) {
$feed_items = array_slice($feed_items, 0, 10);
}
foreach ($feed_items as $item) {
$datetime = $adminTwigExtension->adminNicetimeFilter($item->getDate()->getTimestamp());
$feed_data[] = '<li><span class="date">' . $datetime . '</span> <a href="' . $item->getUrl() . '" target="_blank" title="' . str_replace('"',
'″', $item->getTitle()) . '">' . $item->getTitle() . '</a></li>';
}
}
// cache for 1 hour
$cache->save('news-feed', $feed_data, 60 * 60);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
}
$this->admin->json_response = ['status' => 'success', 'feed_data' => $feed_data];
return true;
$this->sendJsonResponse($json_response);
}
/**
@@ -924,91 +926,6 @@ class AdminController extends AdminBaseController
return true;
}
/**
* Get Notifications from cache.
*
*/
protected function taskGetNotifications()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
return false;
}
$cache = $this->grav['cache'];
if (!(bool)$this->grav['config']->get('system.cache.enabled') || !$notifications = $cache->fetch('notifications')) {
//No notifications cache (first time)
$this->admin->json_response = ['status' => 'success', 'notifications' => [], 'need_update' => true];
return true;
}
$need_update = false;
if (!$last_checked = $cache->fetch('notifications_last_checked')) {
$need_update = true;
} else {
if (time() - $last_checked > 86400) {
$need_update = true;
}
}
try {
$notifications = $this->admin->processNotifications($notifications);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
$this->admin->json_response = [
'status' => 'success',
'notifications' => $notifications,
'need_update' => $need_update
];
return true;
}
/**
* Process Notifications. Store the notifications object locally.
*
* @return bool
*/
protected function taskProcessNotifications()
{
if (!$this->authorizeTask('notifications', ['admin.login', 'admin.super'])) {
return false;
}
$cache = $this->grav['cache'];
$data = $this->post;
$notifications = json_decode($data['notifications']);
try {
$notifications = $this->admin->processNotifications($notifications);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
$show_immediately = false;
if (!$cache->fetch('notifications_last_checked')) {
$show_immediately = true;
}
$cache->save('notifications', $notifications);
$cache->save('notifications_last_checked', time());
$this->admin->json_response = [
'status' => 'success',
'notifications' => $notifications,
'show_immediately' => $show_immediately
];
return true;
}
/**
* Handle getting a new package dependencies needed to be installed
*

View File

@@ -7,7 +7,7 @@
</h1>
<div class="widget-content">
<div class="widget-loader"><i class="fa fa-refresh fa-spin"></i></div>
<div class="widget-loader"><i class="fa fa-refresh fa-spin"></i></div>
<ul></ul>
</div>
</div>

View File

@@ -5,6 +5,7 @@
<a href="#" class="button button-small" data-refresh="notifications"><i class="fa fa-refresh"></i></a>
</span>
</h1>
<div class="widget-content">
<div class="widget-loader"><i class="fa fa-refresh fa-spin"></i></div>
<ul></ul>

View File

@@ -0,0 +1,3 @@
{% for entry in feed %}
<li><span class="date">{{ entry.nicetime }}</span> <a href="{{ entry.url }}" target="_blank" title="{{ entry.title|striptags|e('html_attr') }}">{{ entry.title }}</a>
{% endfor %}

View File

@@ -0,0 +1,3 @@
{% for entry in notifications if 'feed' in entry.location %}
<li class="single-notification "><span class="badge alert {{ entry.type }}">{{ entry.type|capitalize }}</span><a target="_blank" href="{{ entry.link }}" title="{{ entry.message|striptags|e('html_attr') }}">{{ e.message|raw }}</a></li>
{% endfor %}