From ecde5e79a7ba931397a6a980f2c01506757dedfd Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Fri, 8 Feb 2019 17:17:05 -0700 Subject: [PATCH] initial feed/notification refactors --- CHANGELOG.md | 2 + classes/Twig/AdminTwigExtension.php | 77 +----- classes/admin.php | 244 +++++++++++++----- classes/adminbasecontroller.php | 6 +- classes/admincontroller.php | 169 +++--------- .../partials/dashboard-feed.html.twig | 2 +- .../dashboard-notifications.html.twig | 1 + .../templates/partials/feed-block.html.twig | 3 + .../partials/notification-block.html.twig | 3 + 9 files changed, 235 insertions(+), 272 deletions(-) create mode 100644 themes/grav/templates/partials/feed-block.html.twig create mode 100644 themes/grav/templates/partials/notification-block.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 37246538..c66fa0bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/classes/Twig/AdminTwigExtension.php b/classes/Twig/AdminTwigExtension.php index 2e9e8152..8b909d6c 100644 --- a/classes/Twig/AdminTwigExtension.php +++ b/classes/Twig/AdminTwigExtension.php @@ -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); } } diff --git a/classes/admin.php b/classes/admin.php index ba9d0f8e..65f49382 100644 --- a/classes/admin.php +++ b/classes/admin.php @@ -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]; diff --git a/classes/adminbasecontroller.php b/classes/adminbasecontroller.php index d4f135b4..fce580e6 100644 --- a/classes/adminbasecontroller.php +++ b/classes/adminbasecontroller.php @@ -95,9 +95,9 @@ class AdminBaseController return false; } - if (!$this->validateNonce()) { - return false; - } +// if (!$this->validateNonce()) { +// return false; +// } $method = 'task' . ucfirst($this->task); diff --git a/classes/admincontroller.php b/classes/admincontroller.php index 78a4d666..2e5276cc 100644 --- a/classes/admincontroller.php +++ b/classes/admincontroller.php @@ -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[] = '
  • ' . $datetime . ' getTitle()) . '">' . $item->getTitle() . '
  • '; - } - } - - // 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 * diff --git a/themes/grav/templates/partials/dashboard-feed.html.twig b/themes/grav/templates/partials/dashboard-feed.html.twig index 794165ef..46620889 100644 --- a/themes/grav/templates/partials/dashboard-feed.html.twig +++ b/themes/grav/templates/partials/dashboard-feed.html.twig @@ -7,7 +7,7 @@
    -
    +
    diff --git a/themes/grav/templates/partials/dashboard-notifications.html.twig b/themes/grav/templates/partials/dashboard-notifications.html.twig index bf35da24..092258f9 100644 --- a/themes/grav/templates/partials/dashboard-notifications.html.twig +++ b/themes/grav/templates/partials/dashboard-notifications.html.twig @@ -5,6 +5,7 @@ +
    diff --git a/themes/grav/templates/partials/feed-block.html.twig b/themes/grav/templates/partials/feed-block.html.twig new file mode 100644 index 00000000..52bd2c9e --- /dev/null +++ b/themes/grav/templates/partials/feed-block.html.twig @@ -0,0 +1,3 @@ +{% for entry in feed %} +
  • {{ entry.nicetime }} {{ entry.title }} +{% endfor %} diff --git a/themes/grav/templates/partials/notification-block.html.twig b/themes/grav/templates/partials/notification-block.html.twig new file mode 100644 index 00000000..98f01fff --- /dev/null +++ b/themes/grav/templates/partials/notification-block.html.twig @@ -0,0 +1,3 @@ +{% for entry in notifications if 'feed' in entry.location %} +
  • {{ entry.type|capitalize }}{{ e.message|raw }}
  • +{% endfor %}