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(['<', '>'], ['&lt;', '&gt;'], $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;
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc