monster_menus-9.0.x-dev/modules/rss_page/rss_page.module
modules/rss_page/rss_page.module
<?php
/**
* @file
* Allow the user to create a single page that aggregates multiple RSS feeds.
*/
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Config;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\monster_menus\Constants;
use Drupal\node\NodeInterface;
use Drupal\node\Entity\Node;
use Drupal\user\UserInterface;
use SimplePie\Parse\Date;
use SimplePie\SimplePie;
/**
* Implements hook_help().
*/
function rss_page_help($path, $args = NULL) {
switch ($path) {
case 'admin/modules#description':
return t('Aggregates the contents of multiple RSS feeds in one location.');
case 'admin/help#rss_page':
return t('<p>This is the help text.</p>');
case 'node/add#rss_page':
// This description shows up when users click "create content."
return t('Displays the content of an RSS feed, CMS page, or CMS tag');
}
}
/**
* Implements hook_theme().
*/
function rss_page_theme() {
return [
'rss_feed_list' => [
'variables' => ['mm_list_instance' => '', 'mm_list_class' => '', 'mm_list_desc' => '', 'label_above_list' => ''],
],
];
}
/**
* Implements hook_node_access().
*/
function rss_page_node_access(NodeInterface $node, $op, AccountInterface $account) {
if ((is_string($node) ? $node : $node->bundle()) == 'rss_page') {
if ($op == 'create') {
// Only users with permission to do so may create this node type.
return AccessResult::allowedIfHasPermission($account, 'create rss pages');
}
// Users who create a node may edit or delete it later, assuming they have
// the necessary permissions.
if ($op == 'update' || $op == 'delete') {
if ($account->hasPermission('edit own rss pages') && ($account->id() == $node->getOwnerId())) {
return AccessResult::allowed();
}
}
return AccessResult::neutral();
}
}
/**
* Implements hook_node_links_alter().
*/
function rss_page_node_links_alter(array &$links, NodeInterface $node, array &$context) {
// If the node is one we initiated viewing of and is a homebox block, remove
// any Read More link.
if (!empty($node->__get('no_readmore'))) {
unset($links['node']['#links']['node-readmore']);
}
if (mm_get_setting('rss_page.show_node_permalink')) {
// This must be an active node type.
if (isset($node) && in_array($node->getType(), array_keys(node_type_get_names()))) {
$_mm_mmtid_of_node = &drupal_static('_mm_mmtid_of_node');
mm_active_menu_item();
// This must be on an MM page, not just node/NN.
if (isset($_mm_mmtid_of_node[$node->id()])) {
// Show the permalink for everything but the 403 & 404 pages
$external = '/' . mm_get_current_path();
$path403 = '';
// First, try the original 403 location, saved by the securesite module.
if (mm_module_exists('securesite')) {
$path403 = \Drupal::config('securesite.settings')->get('securesite_403');
}
// Failing that, read the Drupal one
if (empty($path403)) {
$path403 = \Drupal::config('system.site')->get('page.403');
}
if ($external != \Drupal::config('system.site')->get('page.404') && $external != $path403) {
$query = [];
if (!empty($dest = \Drupal::request()->query->get('destination', ''))) {
$query = ['destination' => $dest];
}
$links['rss_page_permalink'] = [
'#theme' => 'links__node__rss_page',
'#attributes' => new Attribute(['class' => ['links', 'inline']]),
'#links' => ['entity.node.canonical' => [
'title' => t('Permalink'),
'url' => Url::fromRoute('entity.node.canonical', ['node' => $node->id()], [
'rel' => 'permalink',
'title' => t('The permanent location of this content'),
'query' => $query]),
]],
];
}
}
}
}
}
function rss_page_mm_config_alter(&$form, Config $settings) {
if (isset($form['mm_node'])) {
$form['mm_node']['rss_page-show_node_permalink'] = [
'#type' => 'checkbox',
'#title' => t('Show a permalink icon with every node'),
'#config_target' => 'monster_menus.settings:rss_page.show_node_permalink',
];
}
}
/**
* Implements hook_form_FORMID_alter().
*/
function rss_page_form_node_rss_page_form_alter(&$form, FormState $form_state) {
/** @var NodeInterface $node */
$node = $form_state->getFormObject()->getEntity();
$form['#title'] = !$node || $node->isNew() ? t('Create an RSS page') : t('Edit an RSS page');
_rss_page_main_form($form, $form_state, $node && $node->__get('rss_feeds') ? $node->__get('rss_feeds') : []);
}
/**
* Implements hook_mm_fix_node_urls_info().
*/
function rss_page_mm_fix_node_urls_info() {
return [
'rss_feed_data' => [
'table' => 'rss_page_feed',
'join on' => "%alias.vid = node_field_data.vid AND %alias.`type` = 'url'",
'table field' => 'data',
'get' => '_rss_page_mm_fix_node_urls_get',
'set' => '_rss_page_mm_fix_node_urls_set',
],
];
}
function _rss_page_mm_fix_node_urls_get($node) {
if ($node->__get('rss_feeds')) {
// There's no provision to modify data in a one-to-many relationship as an
// array, so concatenate it all into a string instead.
$out = [];
foreach ($node->__get('rss_feeds') as $n => $rss) {
if ($rss->type == 'url') {
$out[] = "$n::" . $rss->data;
}
}
return implode('||', $out);
}
return NULL;
}
function _rss_page_mm_fix_node_urls_set($value, $node) {
if ($node->__get('rss_feeds')) {
// Split the string back out into multiple rows.
foreach (explode('||', $value) as $row) {
[$n, $data] = explode('::', $row);
if (isset($node->__get('rss_feeds')[$n]) && $node->__get('rss_feeds')[$n]->type == 'url') {
$node->__get('rss_feeds')[$n]->data = $data;
}
}
}
}
function _rss_page_main_form(&$form, FormStateInterface $form_state, $rss_feeds = []) {
static $instance = 0;
$temp = [0, 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50];
$ilist = array_combine($temp, $temp);
$ilist[0] = t('(none)');
$ilist[-1] = t('(all)');
$form['field_rss_page_items']['widget'][0]['value']['#type'] = 'select';
$form['field_rss_page_items']['widget'][0]['value']['#options'] = $ilist;
$base_weight = $form['body']['#weight'] ?? 0;
$form['feeds-url'] = [
'#type' => 'details',
'#open' => TRUE,
'#title' => t('Enter a URL'),
'#attributes' => ['style' => 'display: none', 'id' => "edit-feeds-url$instance"],
'#weight' => $base_weight + 0.01,
];
$form['feeds-url']['feed-url'] = [
'#type' => 'textfield',
'#title' => t('URL of RSS feed'),
'#description' => t('Enter the full URL, starting with <code>http://</code>, <code>feed://</code>, etc. The feed\'s title will be set automatically the first time you view the resulting page. Click on either button below to return to the main list.'),
'#size' => 50, '#maxlength' => 255,
];
$form['feeds-url']['clear'] = [
'#prefix' => '<div class="tax-clear">',
'#suffix' => '</div>',
];
$form['feeds-url']['clear']['feed-ok'] = [
'#type' => 'submit',
'#value' => t('Done'),
'#button_type' => 'primary',
];
$form['feeds-url']['clear']['feed-cancel'] = [
'#type' => 'submit',
'#value' => t('Cancel'),
];
$have_tax = mm_module_exists('taxonomy') && \Drupal::config('taxonomy.settings')->get('maintain_index_table');
if ($have_tax) {
$form['feeds-tax'] = [
'#type' => 'details',
'#open' => TRUE,
'#title' => t('Choose a term or free tag'),
'#attributes' => ['style' => 'display: none', 'id' => "edit-feeds-tax$instance"],
'#weight' => $base_weight + 0.03,
];
$form['feeds-tax']['tax-id'] = [
'#type' => 'hidden',
];
$form['feeds-tax']['tax-sample'] = [
'#markup' => '<div id="tax-sample-wrapper"><label for="edit-tax-text">' . t('Term/tag:') . '</label><div id="tax-sample"></div></div>',
];
$form['feeds-tax']['tax-text'] = [
'#type' => 'textfield',
'#autocomplete_route_name' => 'rss_page.taxonomy_autocomplete',
'#size' => 35,
'#maxlength' => 100,
'#description' => t('Type part of the tag, then choose a tag from the list of matches.'),
];
$form['feeds-tax']['clear'] = [
'#prefix' => '<div class="tax-clear">',
'#suffix' => '</div>',
];
$form['feeds-tax']['clear']['tax-ok'] = [
'#type' => 'submit',
'#value' => t('Done'),
'#button_type' => 'primary',
];
$form['feeds-tax']['clear']['tax-cancel'] = [
'#type' => 'submit',
'#value' => t('Cancel'),
];
}
$desc = t('%url displays the content of a standard URL-based feed. %page displays the content of a CMS page. %tag displays any CMS content labeled with the indicated tag. After entering a URL or selecting a page or tag, click Done.', [
'%url' => t('RSS feed'),
'%page' => t('Page feed'),
'%tag' => t('Tag feed'),
]);
$form['rss_feeds'] = [
'#type' => 'rss_feed_list',
'#rss_list_url_form_ID' => "edit-feeds-url$instance",
'#rss_list_tax_form_ID' => $have_tax ? "edit-feeds-tax$instance" : '',
'#rss_list_query_form_ID' => '',
'#description' => $desc,
'#default_value' => $rss_feeds,
'#weight' => $base_weight + 0.09,
];
$form['#validate'][] = 'rss_page_validate';
$instance++;
}
/**
* Validate the node form.
*/
function rss_page_validate(array $form, FormState $form_state) {
$feeds = _rss_page_split_feed_list($form_state->getValue('rss_feeds'));
if (!$feeds) {
$form_state->setErrorByName('rss_feeds', t('You must add at least one feed.'));
}
else {
foreach ($feeds as $m) {
if ($m->type == 'cat') {
if (!mm_content_user_can($m->data, Constants::MM_PERMS_READ)) {
$form_state->setErrorByName('rss_feeds', t('You are not allowed to read the page %cat.',
['%cat' => $m->name]));
}
}
elseif ($m->type == 'url' && !UrlHelper::isValid($m->data, TRUE)) {
$form_state->setErrorByName('rss_feeds', t('%url does not seem to be a valid URL.', ['%url' => $m->data]));
}
}
$form_state->getFormObject()->getEntity()->__set('rss_feeds', $feeds);
}
}
function rss_page_rss_page_handled_types() {
return ['rss_page'];
}
function rss_page_get_handled_types() {
static $types;
if (!isset($types)) {
$types = mm_module_invoke_all('rss_page_handled_types');
}
return $types;
}
function rss_page_rss_page_feed_data_info() {
return [
'cat' => function($feed, Connection $db, NodeInterface $node, $index, $mark, $atime, &$newest, &$out, &$new_rss_link) {
$link = NULL;
if (!mm_content_user_can($feed->data, Constants::MM_PERMS_READ)) {
$out['error'] = t('You are not allowed to read the page %cat.', ['%cat' => $feed->name]);
}
else {
$cat = mm_content_get($feed->data);
if ($cat) {
$link = mm_content_get_mmtid_url($feed->data, ['absolute' => TRUE]);
$out = ['items' => [], 'feed_title' => $cat->name, 'show_item_title' => !_rss_page_get_node_value($node, 'show_descr'), 'feed_link' => $link, 'fid' => $feed->fid ?? 0];
_rss_page_feed_from_cat($feed->data, NULL, $node, $index, $mark, $atime, $link, $newest, $out);
}
}
$new_rss_link = Url::fromRoute('monster_menus.show_page_feed',
$link ? $link->getRouteParameters() : ['mm_tree' => mm_home_mmtid()],
$link ? $link->getOptions() : []);
},
'taxon' => function($feed, Connection $db, NodeInterface $node, $index, $mark, $atime, &$newest, &$out, &$new_rss_link) {
if (!\Drupal::config('taxonomy.settings')->get('maintain_index_table') || !preg_match('{(.*?)\s*\((\d+)\)$}', $feed->data, $matches)) {
return;
}
$taxon_tid = $matches[2];
$nids = array_unique(_rss_page_taxonomy_select_nodes($taxon_tid));
$link = NULL;
if ($nids) {
$link = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $taxon_tid], ['absolute' => TRUE]);
$out = [
'items' => [],
'feed_title' => $matches[1],
'show_item_title' => !_rss_page_get_node_value($node, 'show_descr'),
'feed_link' => $link,
'fid' => $feed->fid ?? 0,
];
_rss_page_feed_from_cat(NULL, $nids, $node, $index, $mark, $atime, '', $newest, $out);
}
$new_rss_link = Url::fromRoute('monster_menus.show_page_feed',
$link ? $link->getRouteParameters() : ['mm_tree' => mm_home_mmtid()],
$link ? $link->getOptions() : []);
},
'url' => function($feed, Connection $db, NodeInterface $node, $index, $mark, $atime, &$newest, &$out, &$new_rss_link) {
$sp_feed = new SimplePie();
$sp_feed->set_feed_url($feed->data);
$sp_feed->set_cache_location(\Drupal::config('rss_page.settings')->get('cache_path'));
$sp_feed->set_cache_duration($max_age = \Drupal::config('rss_page.settings')->get('cache_duration'));
$sp_feed->init();
if (!$sp_feed->error) {
$sp_feed->handle_content_type();
}
if (!$sp_feed->error && $sp_feed->data) {
$title = Html::decodeEntities($sp_feed->get_title());
if (empty($feed->name_isset) && $title) {
$feed->name_isset = 1;
$db->update('rss_page_feed')
->fields(['name_isset' => 1, 'name' => $title])
->condition('name_isset', 1, '<>')
->condition('type', 'url')
->condition('data', $feed->data)
->execute();
}
if (!$title) {
$title = $feed->name;
}
$out = [
'items' => [],
'feed_title' => $title,
'show_title' => TRUE,
'feed_link' => Url::fromUri($sp_feed->get_link()),
'fid' => $feed->fid ?? 0,
'img' => '',
'img_title' => '',
'img_width' => 0,
'img_height' => 0,
];
if (_rss_page_get_node_value($node, 'show_feed_img')) {
$out = [
'img' => $sp_feed->get_image_url() ?? '',
'img_title' => Html::escape($sp_feed->get_image_title() ?? ''),
'img_width' => (int) $sp_feed->get_image_width() ?? 0,
'img_height' => (int) $sp_feed->get_image_height() ?? 0,
] + $out;
}
$cache = ['contexts' => [], 'tags' => [], 'max-age' => $max_age];
for ($i = 0; ; $i++) {
$nitems = _rss_page_get_node_value($node, 'items');
if ($nitems >= 0 && count($out['items']) >= $nitems) {
break;
}
if (($item = $sp_feed->get_item($i)) == FALSE) {
break;
}
// Take the max of the updated and published fields
if ($updated = $item->get_item_tags(SimplePie::NAMESPACE_ATOM_10, 'updated')) {
$parser = Date::get();
$updated = $parser->parse($updated[0]['data']);
}
$date = max($item->get_date('U'), $updated);
if ($mark == 'read' && $node->getType() == 'rss_page') {
if ($date > $newest) {
$newest = $date;
}
}
elseif (is_null($atime) || $atime === FALSE || $date > $atime) {
$perma = html_entity_decode($item->get_permalink(), ENT_QUOTES);
$desc = _rss_page_fix_descr($item->get_description(), $node);
if (_rss_page_get_node_value($node, 'show_image')) {
$enclosures = $item->get_enclosures();
if (is_array($enclosures)) {
$enc_html = '';
foreach ($enclosures as $enclosure) {
if ($enclosure->get_type()) {
$enc_title = Html::escape(Html::decodeEntities($item->get_title()));
if (stripos($enclosure->get_type(), 'image') !== FALSE) { //image file
$enc_html .= '<img src="' . UrlHelper::stripDangerousProtocols($enclosure->get_link()) . '" alt="' . $enc_title . '" title="' . $enc_title . '" class="image icon" />';
}
elseif (function_exists('_media_get_icon')) {
$icon = _media_get_icon($enclosure->get_type());
if (empty($icon)) {
$icon = 'genericdoc.gif';
}
$url = base_path() . \Drupal::service('extension.list.module')->getPath('media') . '/icons/' . $icon;
$enc_html .= '<a href="' . UrlHelper::stripDangerousProtocols($enclosure->get_link()) . '"><img src="' . UrlHelper::stripDangerousProtocols($url) . '" alt="' . $enc_title . '" title="' . $enc_title . '" /></a>';
}
}
if ($enc_html) {
$desc = "<p>$desc</p>$enc_html";
}
}
}
}
$desc = '<div class="content">' . $desc . '</div>';
$out['items'][] = (object) [
'feed_index' => $index,
'description' => $desc,
'date' => $date,
'title' => Html::decodeEntities($item->get_title()),
'link' => $perma,
'categories' => $item->get_category(),
'cache' => $cache,
];
}
}
$new_rss_link = Url::fromUri($feed->data);
}
else { // !$feed->data
$out = ['error' => _rss_page_rss_error($feed->data, $sp_feed), 'feed_title' => $feed->name];
}
if (empty($feed->name_isset)) {
$out['feed_title'] = '';
}
},
];
}
function rss_page_get_feed_data($feed, Connection $db, NodeInterface $node, $index, $mark, $atime, &$newest, &$out, &$new_rss_link) {
static $info;
if (!isset($info)) {
$info = mm_module_invoke_all('rss_page_feed_data_info');
}
if (isset($info[$feed->type])) {
$info[$feed->type]($feed, $db, $node, $index, $mark, $atime, $newest, $out, $new_rss_link);
}
}
/**
* Implements hook_ENTITY_TYPE_insert().
*/
function rss_page_node_insert(NodeInterface $node) {
if (in_array($node->getType(), rss_page_get_handled_types())) {
if (!is_array($node->__get('rss_feeds'))) {
$feeds = $node->__get('rss_feeds');
$weight = 0;
$feed_list = [];
foreach (_rss_page_split_feed_list($feeds) as $m) {
$feed_list[] = (object) ['type' => $m->type, 'name' => $m->name, 'data' => $m->data, 'weight' => $weight++];
}
$node->__set('rss_feeds', $feed_list);
}
foreach ($node->__get('rss_feeds') as $feed) {
\Drupal::database()->insert('rss_page_feed')
->fields([
'nid' => $node->id(),
'vid' => $node->getRevisionId(),
'type' => $feed->type,
'data' => $feed->data,
'name_isset' => 0,
'name' => $feed->name,
'weight' => $feed->weight,
])
->execute();
}
}
}
/**
* Implements hook_update().
*/
function rss_page_node_update(NodeInterface $node) {
if (in_array($node->getType(), rss_page_get_handled_types())) {
\Drupal::database()
->query('DELETE {rss_page_feed}, {rss_page_last_read} FROM {rss_page_feed} ' .
'LEFT JOIN {rss_page_last_read} ON {rss_page_feed}.fid={rss_page_last_read}.fid ' .
'WHERE vid=:vid', [':vid' => $node->getRevisionId()]);
rss_page_node_insert($node);
}
}
/**
* Implements hook_node_revision_delete().
*
* When a node revision is deleted, we need to remove the corresponding record
* from our table.
*/
function rss_page_node_revision_delete(NodeInterface $node) {
// Notice that we're matching a single revision based on the node's vid.
\Drupal::database()->delete('rss_page_feed')
->condition('vid', $node->getRevisionId())
->execute();
}
/**
* Implements hook_mm_delete() from mm_content.inc.
*/
function rss_page_mm_delete($mmtids, $nids) {
if (count($nids)) {
\Drupal::database()->query("DELETE f, r FROM {rss_page_feed} f LEFT JOIN {rss_page_last_read} r ON f.fid = r.fid WHERE f.nid IN(:nids[])", [':nids[]' => (array) $nids]);
}
if (count($mmtids)) {
\Drupal::database()->query("DELETE f, r FROM {rss_page_feed} f LEFT JOIN {rss_page_last_read} r ON r.fid = f.fid WHERE f.type = 'cat' AND f.data IN(:mmtids[])", [':mmtids[]' => (array) $mmtids]);
}
}
/**
* Implements hook_ENTITY_TYPE_delete() for node entities.
*/
function rss_page_node_delete(NodeInterface $node) {
if (in_array($node->getType(), rss_page_get_handled_types())) {
\Drupal::database()
->query('DELETE f, r FROM {rss_page_feed} f LEFT JOIN {rss_page_last_read} r ON r.fid = f.fid WHERE nid = :nid', [':nid' => $node->id()]);
}
}
/**
* Implements hook_ENTITY_TYPE_load() for node entities.
*/
function rss_page_node_load(array $nodes) {
foreach (array_keys($nodes) as $nid) {
$node = &$nodes[$nid];
if (in_array($node->getType(), rss_page_get_handled_types())) {
$node->__set('rss_feeds', []);
$select = \Drupal::database()->select('rss_page_feed', 'f');
$select->fields('f', ['fid', 'type', 'data', 'name', 'name_isset', 'weight']);
$select->condition('f.vid', $node->getRevisionId());
$select->orderBy('f.weight');
$feed_result = $select->execute();
foreach ($feed_result as $feed) {
$node->__get('rss_feeds')[] = $feed;
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_view().
*/
function rss_page_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if (in_array($node->getType(), rss_page_get_handled_types())) {
$node->setTitle(_rss_page_add_vars($node->label(), $node));
$nav = _rss_page_block('nav', $node, $rss_link);
$body = _rss_page_block('content', $node, $rss_link, TRUE);
$build['content']['body'] = [
'nav' => $nav['content'],
'body' => $body['content'],
];
$build['content']['rss_link'] = [
'#type' => 'link',
'#title' => t('source'),
'#url' => $rss_link,
];
}
}
/**
* Implements hook_user_delete().
*/
function rss_page_user_delete(UserInterface $account) {
if ($account->isAuthenticated()) {
\Drupal::database()->delete('rss_page_last_read')
->condition('uid', $account->id())
->execute();
}
}
/**
* Implements hook_cron().
*
* Periodically remove old RSS cache files
*/
function rss_page_cron() {
$rss_cache = \Drupal::config('rss_page.settings')->get('cache_path');
foreach (\Drupal::service('file_system')->scanDirectory($rss_cache, '/\.spc$/') as $file) {
if (is_file($file->uri) && mm_request_time() - filemtime($file->uri) > 30 * 24 * 60 * 60) {
\Drupal::service('file_system')->delete($file->uri);
}
}
}
function _rss_page_load_feeds(NodeInterface $node, $one_feed, $mark, &$rss_link) {
$db = Database::getConnection();
$current_uid = \Drupal::currentUser()->id();
$feeds = &drupal_static(__FUNCTION__, []);
if (isset($feeds[$node->uuid()])) {
unset($rss_link);
return $feeds[$node->uuid()];
}
$feeds[$node->uuid()] = [];
if (!is_array($node->__get('rss_feeds'))) {
return [];
}
foreach ($node->__get('rss_feeds')as $f) {
if ($one_feed && $f->fid != $one_feed) {
continue;
}
$out = [];
$newest = 0;
$atime = NULL;
$new_rss_link = NULL;
if (!$mark && $node->getType() == 'rss_page') {
$atime = 0;
if (isset($f->fid)) {
$atime = $db->select('rss_page_last_read', 'r')
->fields('r', ['atime'])
->condition('r.fid', $f->fid)
->condition('r.uid', $current_uid)
->execute()->fetchField();
}
}
rss_page_get_feed_data($f, $db, $node, count($feeds[$node->uuid()]), $mark, $atime, $newest,$out,$new_rss_link);
if ($out) {
if ($node->getType() == 'rss_page' && (!isset($out['error']) || !$out['error']) && $current_uid)
if ($mark == 'read') {
$db->merge('rss_page_last_read')
->keys(['fid' => $out['fid'], 'uid' => $current_uid])
->fields(['atime' => $newest])
->execute();
}
elseif ($mark == 'unread') {
$db->delete('rss_page_last_read')
->condition('fid', $out['fid'])
->execute();
}
$feeds[$node->uuid()][] = (object)$out;
}
$rss_link = !is_null($new_rss_link) && is_null($rss_link) ? $new_rss_link : NULL;
}
return $feeds[$node->uuid()];
}
// split result generated by setHiddenElt in rss_list.js
function _rss_page_split_feed_list($str) {
if (is_array($str)) return $str; // trying to split something already in the right format (rss_page_insert)
$out = [];
$weight = 0;
foreach (mm_ui_parse_repeatlist($str, 3) as $m) {
$data = $m[2];
if ($m[1] == 'cat') {
$data = array_slice(explode('/', $data), -1);
$data = $data[0];
}
$out[] = (object)['name' => $m[0], 'type' => $m[1], 'data' => $data, 'weight' => $weight++];
}
return $out;
}
function _rss_page_item($item, $feed = NULL, Url $base_url = NULL) {
$source = '';
if ($feed && !empty($feed->feed_title) && $base_url && empty($feed->show_item_title)) {
$source = Link::fromTextAndUrl($feed->feed_title, _rss_page_add_to_url($base_url, [
'attributes' => ['class' => ['feed-item-source']],
'query' => _rss_page_get_query_parameters(['byfeed' => $feed->fid], 'bydate'),
]))->toString() . ' - ';
}
if (!$item->date) {
$source_date = '';
}
elseif (date('Ymd', $item->date) == date('Ymd')) {
$source_date = t('@ago ago', ['@ago' => \Drupal::service("date.formatter")->formatInterval(mm_request_time() - $item->date)]);
}
else {
$source_date = mm_format_date($item->date);
}
$out = '';
if ($feed && (!empty($feed->show_title) || !empty($feed->show_item_title))) {
// l() and check_plain() affect &entities; so don't use them here
$t = str_replace(['<', '>'], ['<', '>'], $item->title);
$url = is_object($item->link) ? $item->link : Url::fromUri($item->link);
$out .= '<h3 class="feed-item-title">' . Link::fromTextAndUrl($t, $url)->toString() .
"</h3>\n<div class=\"feed-item-meta\">" .
"$source<span class=\"feed-item-date\">$source_date</span></div>\n";
}
if ($item->description) {
$out .= '<div class="feed-item-body">' . $item->description . "</div>\n";
}
if (!empty($out)) {
return '<div class="feed-item">' . $out . '</div>';
}
return '';
}
function rss_page_default_block_options($in = NULL) {
if (empty($in)) {
$in = (object) [];
}
$opts = rss_page_get_options(); // FIXME
if (isset($in->rss_page)) {
$rss_page = $in->rss_page;
}
else {
$rss_page = $opts;
foreach (array_keys($opts) as $key) {
$inkey = $key;
if (in_array($key, ['items', 'sort'])) {
$inkey = "rss_$key";
}
if (isset($in->$inkey)) {
$rss_page[$key] = $in->$inkey;
}
}
}
return (object) [
'nid' => $in->key ?? 'undefined',
'uid' => \Drupal::currentUser()->id(),
'type' => 'rss_page',
'rss_page' => $rss_page,
'rss_items' => $in->rss_items ?? $opts['field_rss_page_items'],
'rss_sort' => $in->rss_sort ?? $opts['sort'],
'rss_feeds' => $in->rss_feeds ?? [],
'title' => $in->title ?? $in->subject ?? '',
'show_descr' => $in->show_descr ?? $opts['show_descr'],
'show_image' => $in->show_image ?? $opts['show_image'],
'show_feed_img' => $in->show_feed_img ?? $opts['show_feed_img'],
'group_by_key' => $in->group_by_key ?? $opts['group_by_key'],
'condition' => '',
];
}
function _rss_page_block($delta, $node, &$rss_link, $show_add_link = FALSE, $edit = NULL) {
/** @var NodeInterface $node **/
$block = ['content' => [], '#subject' => '', '#count' => 0];
mm_parse_args($mmtids, $oarg_list, $this_tid);
if ($node == NULL) {
if (is_array($edit) && isset($edit['homebox'])) {
$node = rss_page_default_block_options($edit['homebox']);
$node->dups_in_node = TRUE; // only crunch duplicates within this "node"
if ($delta == 'content') {
$block['content'][] = ['#markup' => '<input type="hidden" name="rss-page-block-key" value="' . $edit['homebox']->key . '" />'];
}
}
else if (count($oarg_list) < 2 || $oarg_list[0] != 'node' ||
(count($oarg_list) > 2 && $oarg_list[2] == 'contents') ||
!is_numeric($oarg_list[1]) || !($node = Node::load($oarg_list[1])) ||
!$node->id() || !in_array($node->getType(), rss_page_get_handled_types())) {
return $block;
}
}
if ($node->getType() != 'rss_page' && $delta == 'nav') { // no nav unless rss_page
return $block;
}
$base_url = Url::fromRoute('entity.mm_tree.canonical', ['mm_tree' => $this_tid]);
if (isset($oarg_list[0]) && $oarg_list[0] == 'node' && $node->id()) {
$base_url = Url::fromRoute('entity.node.canonical', ['mm_tree' => $this_tid, 'node' => $node->id()]);
}
$sort = _rss_page_get_node_value($node, 'sort');
if (\Drupal::request()->query->get('bydate', FALSE) !== FALSE) {
$sort = 'date';
}
$one_feed = '';
if (\Drupal::request()->query->has('byfeed')) {
$sort = 'feed-alpha';
$one_feed = \Drupal::request()->query->get('byfeed');
}
$mark = '';
if (\Drupal::request()->query->get('read', FALSE) !== FALSE) {
$mark = 'read';
}
elseif (\Drupal::request()->query->get('unread', FALSE) !== FALSE) {
$mark = 'unread';
}
$feeds = $node->__get('rss_feeds');
if (isset($feeds) && is_string($feeds)) {
$node->__set('rss_feeds', _rss_page_split_feed_list($feeds));
}
if (is_array($edit) && isset($edit['homebox'])) {
$node->__set('no_readmore', TRUE);
}
$feeds = _rss_page_load_feeds($node, $one_feed, $mark, $rss_link);
if ($mark) {
$_rss_page_future_goto = &drupal_static('_rss_page_future_goto');
$_rss_page_future_goto = _rss_page_add_to_url($base_url, [
'absolute' => TRUE,
'query' => _rss_page_get_query_parameters(NULL, ['read', 'unread'])
]);
}
if (count($feeds)) {
if ($sort == 'feed-alpha') {
usort($feeds, function($a, $b) {
$at = empty($a->feed_title) ? '' : $a->feed_title;
$bt = empty($b->feed_title) ? '' : $b->feed_title;
return strcasecmp($at, $bt);
});
}
if ($delta == 'nav') {
$menu = '';
if (\Drupal::currentUser()->id()) {
$menu .= '<li>' . Link::fromTextAndUrl(t('(mark all read)'), _rss_page_add_to_url($base_url, ['query' => _rss_page_get_query_parameters(['read' => 1], 'unread')]))->toString() . "</li>\n";
$menu .= '<li>' . Link::fromTextAndUrl(t('(mark all unread)'), _rss_page_add_to_url($base_url, ['query' => _rss_page_get_query_parameters(['unread' => 1], 'read')]))->toString() . "</li>\n";
}
if ($sort == 'date' || $sort == 'date-reverse') {
$query = (_rss_page_get_node_value($node, 'sort') == 'feed-alpha' || _rss_page_get_node_value($node, 'sort') == 'feed-weight') ? [] : _rss_page_get_query_parameters('byfeed', 'bydate');
$menu .= '<li>' . Link::fromTextAndUrl(t('(sort by feed)'), _rss_page_add_to_url($base_url, ['query' => $query]))->toString() . "</li>\n";
}
else {
$menu .= '<li>' . Link::fromTextAndUrl(t('(sort by date)'), _rss_page_add_to_url($base_url, ['query' => _rss_page_get_query_parameters(['bydate' => 1], ['byfeed', 'read', 'unread'])]))->toString() . "</li>\n";
}
if ($menu) {
$block['content'] = ['#markup' => "<div class=\"item-list\"><ul>$menu</ul></div>"];
$block['#subject'] = Html::escape(_rss_page_add_vars($node->label(), $node));
}
}
elseif ($delta == 'content') {
$block['content']['#attached']['library'][] = 'rss_page/rss_page_css';
$block['content'][] = ['#markup' => '<div class="rss-page">'];
if ($sort == 'date' || $sort == 'date-reverse') {
$items = [];
foreach ($feeds as $f) {
if (!empty($f->error)) {
$block['content'][] = ['#markup' => '<div class="feed-source2">' . _rss_page_feed_title($f) .
'</div><div class="feed-item"><div class="feed-item-meta">' .
$f->error . '</div></div>'];
}
else {
$items = array_merge($items, $f->items);
}
}
usort($items, fn($a, $b) => strcasecmp($b->date, $a->date));
if ($sort == 'date-reverse') {
$items = array_reverse($items);
}
$found = [];
foreach ($items as $item) {
if ($item->link) { // remove duplicates, favoring newer
$link = is_object($item->link) ? $item->link->toString() : $item->link;
if (!empty($found[$link])) {
continue;
}
$found[$link] = TRUE;
}
if ($item->title) {
if (!empty($found[$item->title])) {
continue;
}
$found[$item->title] = TRUE;
}
$out = _rss_page_item($item, $feeds[$item->feed_index], $base_url);
$non_html = trim(strip_tags($out));
if (!empty($non_html)) {
$block['content'][] = ['#markup' => $out];
$block['#count']++;
}
}
}
else { // $sort != 'date'
foreach ($feeds as $f) {
if (!empty($f->error)) {
$block['content'][] = [
'#type' => 'details',
'#title' => _rss_page_feed_title($f),
'#open' => TRUE,
['#markup' => '<div class="feed-item"><div class="feed-item-meta">' . $f->error . '</div></div>',]
];
}
elseif ($node->getType() == 'rss_page' || count($f->items)) {
$fieldset = [
'#type' => 'details',
'#title' => _rss_page_feed_title($f),
'#open' => $node->getType() == 'rss_page' && !$node->isNew(),
];
if ($show_add_link && !empty($f->fid) && function_exists('rss_page_subscribe_button')) {
if ($add_html = rss_page_subscribe_button($f->fid)) {
$fieldset[] = ['#markup' => $add_html];
}
}
if ($f->feed_link) {
$fieldset[] = ['#markup' => '<div class="feed-source">' . Link::fromTextAndUrl(t('source'), $f->feed_link)->toString() . '</div>'];
}
if (!empty($f->img)) {
$width = $f->img_width ?: 50;
$fieldset[] = ['#markup' => "<div class=\"rss-feed-icon\"><img src=\"$f->img\" alt=\"$f->img_title\" width=\"$width\"></div>"];
}
foreach ($f->items as $item) {
$out = _rss_page_item($item, $feeds[$item->feed_index]);
$non_html = trim(strip_tags($out));
if (!empty($non_html)) {
$fieldset[] = ['#markup' => $out];
$block['#count']++;
}
}
$block['content'][] = $fieldset;
}
}
}
$block['content']['#cache'] = ['tags' => [], 'contexts' => [], 'max-age' => Cache::PERMANENT];
foreach ($feeds as $f) {
if (isset($f->items)) {
foreach ($f->items as $item) {
if (isset($item->cache)) {
$block['content']['#cache']['contexts'] = Cache::mergeContexts($block['content']['#cache']['contexts'], $item->cache['contexts']);
$block['content']['#cache']['tags'] = Cache::mergeTags($block['content']['#cache']['tags'], $item->cache['tags']);
$block['content']['#cache']['max-age'] = Cache::mergeMaxAges($block['content']['#cache']['max-age'], $item->cache['max-age']);
}
}
}
}
$block['content'][] = ['#markup' => '</div>']; // aggregator
}
}
else if (isset($edit['homebox'])) {
$block['content']['#attached']['library'][] = 'rss_page/rss_page_css';
$block['#subject'] = t('(untitled)');
// Arguments to formatPlural() don't need t()
$block['content'][] = [
'#markup' => $node->__get('rss_feeds') ?
\Drupal::translation()->formatPlural(count($node->__get('rss_feeds')), 'This feed is empty.', 'The feeds are empty.') :
t('Click on the gear icon to add a feed')
];
$block['#count']++;
}
return $block;
}
function _rss_page_add_to_url(Url $url, array $add) {
$new_url = clone($url);
$new_url->mergeOptions($add);
return $new_url;
}
function _rss_page_fix_descr($desc, $node) {
if (!_rss_page_get_node_value($node, 'show_descr')) {
return '';
}
// Remove IMG and TABLE tags. This has to be done here, not in SimplePie,
// because SP caches the change so it affects all readers of that feed.
if (!_rss_page_get_node_value($node, 'show_image')) {
$desc = preg_replace("{<(img|table|tr|td|th|tbody)((\s*((\w+:)?\w+)\s*=\s*(\"([^\"]*)\"|'([^']*)'|(.*)))*)\s*(/>|>)|</(table|tr|td|th|tbody)>}msiU", ' ', $desc);
}
return Xss::filter($desc, ['a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'p']);
}
function _rss_page_feed_title($feed) {
if (empty($feed->feed_title)) {
return t('Unknown feed');
}
return Html::escape($feed->feed_title) . ' (' . (isset($feed->items) ? count($feed->items) : '0') . ')';
}
function _rss_page_add_vars($str, $node) {
return function_exists('localmods_portal_page_add_vars') ? localmods_portal_page_add_vars($str, $node) : $str;
}
function _rss_page_get_node_value(NodeInterface $node, $key) {
$rss_page = $node->__get('rss_page');
if (!isset($rss_page)) {
$rss_page = [
'items' => $node->get('field_rss_page_items')->getValue()[0]['value'],
'sort' => $node->get('field_rss_page_sort')->getValue()[0]['value'],
'show_feed_img' => FALSE,
'show_descr' => FALSE,
'show_image' => FALSE,
'group_by_key' => FALSE,
];
$options = $node->get('field_rss_page_options')->getValue();
if ($options) {
$okey = key($options);
if (isset($options[$okey]['value'])) {
foreach ($options as $o) {
$rss_page[$o['value']] = TRUE;
}
}
else {
foreach ($options[$okey] as $k => $v) {
$rss_page[$k] = (bool) $v;
}
}
}
$node->__set('rss_page', $rss_page);
}
return $node->__get('rss_page')[$key];
}
function _rss_page_feed_from_cat($tid, $nid, NodeInterface $node, $index, $mark, $atime, $link, &$newest, &$out) {
$showed = &drupal_static(__FUNCTION__);
if (isset($nid)) {
$arr = (array) $nid;
}
elseif (is_null($tid)) {
return;
}
else {
$out['feed_link'] = mm_content_get_mmtid_url($tid);
$arr = Database::getConnection()->query(mm_content_get_accessible_nodes_by_mmtid_query($tid, $count_sql))->fetchCol();
}
foreach ($arr as $n) {
$showed_key = !empty($node->dups_in_node) ? $node->id() . "/$n" : $n;
if (!empty($showed[$showed_key]) || !mm_content_user_can_node($n, Constants::MM_PERMS_READ)) {
continue;
}
$nitems = _rss_page_get_node_value($node, 'items');
if ($nitems >= 0 && count($out['items']) >= $nitems) {
return;
}
/** @var NodeInterface $kidnode */
$kidnode = Node::load($n);
if ($kidnode && $kidnode->id() && $kidnode->getType() != 'redirect' && !in_array($kidnode->getType(), rss_page_get_handled_types())) {
if ($mark == 'read') {
if ($kidnode->getChangedTime() > $newest) {
$newest = $kidnode->getChangedTime();
}
}
elseif (is_null($atime) || $kidnode->getChangedTime() > $atime) {
$cats = [];
if (isset($kidnode->taxonomy)) {
foreach ($kidnode->taxonomy as $t) {
$cats[] = $t->name;
}
}
$ln = Url::fromRoute('entity.node.canonical', ['node' => $kidnode->id()], ['absolute' => TRUE]);
if ($link) {
$ln->setRouteParameter('mm_tree', $link->getRouteParameters()['mm_tree']);
}
$extra = [];
$kidnode->setTitle(mm_ui_hide_node_title($kidnode->label()));
$kidnode->__set('no_readmore', !empty($node->__get('no_readmore')));
$viewed = \Drupal::entityTypeManager()->getViewBuilder('node')->view($kidnode, 'teaser');
$desc = \Drupal::service('renderer')->render($viewed);
$cache = $viewed['#cache'];
$out['items'][] = (object) [
'extra' => $extra,
'feed_index' => $index,
'uid' => $kidnode->__get('show_node_info') ? $kidnode->getOwnerId() : NULL,
'description' => _rss_page_fix_descr($desc, $node),
'date' => $kidnode->getChangedTime(),
'title' => $kidnode->label(),
'link' => $ln,
'categories' => $cats,
'cache' => $cache,
];
$showed[$showed_key] = TRUE;
}
}
else $showed[$showed_key] = TRUE; // slight speed-up
}
}
function _rss_page_rss_error($url, $feed) {
// Truncate a needlessly confusing message.
$feed->error = preg_replace('{A feed with an invalid mime.*$}', '', $feed->error);
$eurl = [':url' => $url, '@err' => $feed->error];
if (!strncmp($feed->error, 'A feed could not be found at ', 29) && str_contains($url, \Drupal::request()->server->get('SERVER_NAME', '')))
return $feed->error . ". \n" . t('To subscribe to a password-protected page inside this site, you should use a "Page Feed", not a URL.');
return strcmp($feed->error, 'Operation timed out') ?
(!str_contains($feed->error, $url) ? t('Error when accessing :url:<br />@err', $eurl) : $feed->error) :
t('The RSS server at :url took too long to respond.', $eurl);
}
function _rss_page_get_query_parameters($add = '', $exclude = []) {
$get = \Drupal::request()->query->all();
if (isset($add)) {
if (!is_array($add)) {
$add = [$add => ''];
}
$get += $add;
}
return UrlHelper::filterQueryParameters($get, (array) $exclude);
}
function _rss_page_taxonomy_select_nodes($tid) {
$query = \Drupal::database()->select('taxonomy_index', 't');
$query->addTag('node_access');
$query->condition('tid', $tid);
$query->leftJoin('mm_recycle', 'r', 'r.id = t.nid AND r.type = :type', [':type' => 'node']);
$query->isNull('r.id');
$query->addField('t', 'nid');
$query->addField('t', 'tid');
$query->orderBy('t.sticky', 'DESC');
$query->addField('t', 'sticky');
$query->orderBy('t.created', 'DESC');
$query->addField('t', 'created');
$nids = $query->execute()->fetchCol();
$nids_visible = [];
foreach ($nids as $nid) {
if (mm_content_user_can_node($nid, Constants::MM_PERMS_READ)) {
$nids_visible[] = $nid;
}
}
return $nids_visible;
}
