1<?php 2 3namespace Drupal\aggregator\Plugin\aggregator\processor; 4 5use Drupal\aggregator\Entity\Item; 6use Drupal\aggregator\FeedInterface; 7use Drupal\aggregator\FeedStorageInterface; 8use Drupal\aggregator\ItemStorageInterface; 9use Drupal\aggregator\Plugin\AggregatorPluginSettingsBase; 10use Drupal\aggregator\Plugin\ProcessorInterface; 11use Drupal\Component\Utility\Unicode; 12use Drupal\Core\Config\ConfigFactoryInterface; 13use Drupal\Core\Datetime\DateFormatterInterface; 14use Drupal\Core\Form\ConfigFormBaseTrait; 15use Drupal\Core\Form\FormStateInterface; 16use Drupal\Core\Messenger\MessengerInterface; 17use Drupal\Core\Plugin\ContainerFactoryPluginInterface; 18use Drupal\Core\Url; 19use Symfony\Component\DependencyInjection\ContainerInterface; 20 21/** 22 * Defines a default processor implementation. 23 * 24 * Creates lightweight records from feed items. 25 * 26 * @AggregatorProcessor( 27 * id = "aggregator", 28 * title = @Translation("Default processor"), 29 * description = @Translation("Creates lightweight records from feed items.") 30 * ) 31 */ 32class DefaultProcessor extends AggregatorPluginSettingsBase implements ProcessorInterface, ContainerFactoryPluginInterface { 33 34 use ConfigFormBaseTrait; 35 36 /** 37 * Contains the configuration object factory. 38 * 39 * @var \Drupal\Core\Config\ConfigFactoryInterface 40 */ 41 protected $configFactory; 42 43 /** 44 * The entity storage for items. 45 * 46 * @var \Drupal\aggregator\ItemStorageInterface 47 */ 48 protected $itemStorage; 49 50 /** 51 * The date formatter service. 52 * 53 * @var \Drupal\Core\Datetime\DateFormatterInterface 54 */ 55 protected $dateFormatter; 56 57 /** 58 * The messenger. 59 * 60 * @var \Drupal\Core\Messenger\MessengerInterface 61 */ 62 protected $messenger; 63 64 /** 65 * Constructs a DefaultProcessor object. 66 * 67 * @param array $configuration 68 * A configuration array containing information about the plugin instance. 69 * @param string $plugin_id 70 * The plugin_id for the plugin instance. 71 * @param mixed $plugin_definition 72 * The plugin implementation definition. 73 * @param \Drupal\Core\Config\ConfigFactoryInterface $config 74 * The configuration factory object. 75 * @param \Drupal\aggregator\ItemStorageInterface $item_storage 76 * The entity storage for feed items. 77 * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter 78 * The date formatter service. 79 * @param \Drupal\Core\Messenger\MessengerInterface $messenger 80 * The messenger. 81 */ 82 public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config, ItemStorageInterface $item_storage, DateFormatterInterface $date_formatter, MessengerInterface $messenger) { 83 $this->configFactory = $config; 84 $this->itemStorage = $item_storage; 85 $this->dateFormatter = $date_formatter; 86 $this->messenger = $messenger; 87 // @todo Refactor aggregator plugins to ConfigEntity so merging 88 // the configuration here is not needed. 89 parent::__construct($configuration + $this->getConfiguration(), $plugin_id, $plugin_definition); 90 } 91 92 /** 93 * {@inheritdoc} 94 */ 95 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { 96 return new static( 97 $configuration, 98 $plugin_id, 99 $plugin_definition, 100 $container->get('config.factory'), 101 $container->get('entity_type.manager')->getStorage('aggregator_item'), 102 $container->get('date.formatter'), 103 $container->get('messenger') 104 ); 105 } 106 107 /** 108 * {@inheritdoc} 109 */ 110 protected function getEditableConfigNames() { 111 return ['aggregator.settings']; 112 } 113 114 /** 115 * {@inheritdoc} 116 */ 117 public function buildConfigurationForm(array $form, FormStateInterface $form_state) { 118 $config = $this->config('aggregator.settings'); 119 $processors = $config->get('processors'); 120 $info = $this->getPluginDefinition(); 121 $counts = [3, 5, 10, 15, 20, 25]; 122 $items = array_map(function ($count) { 123 return $this->formatPlural($count, '1 item', '@count items'); 124 }, array_combine($counts, $counts)); 125 $intervals = [3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800]; 126 $period = array_map([$this->dateFormatter, 'formatInterval'], array_combine($intervals, $intervals)); 127 $period[FeedStorageInterface::CLEAR_NEVER] = t('Never'); 128 129 $form['processors'][$info['id']] = []; 130 // Only wrap into details if there is a basic configuration. 131 if (isset($form['basic_conf'])) { 132 $form['processors'][$info['id']] = [ 133 '#type' => 'details', 134 '#title' => t('Default processor settings'), 135 '#description' => $info['description'], 136 '#open' => in_array($info['id'], $processors), 137 ]; 138 } 139 140 $form['processors'][$info['id']]['aggregator_summary_items'] = [ 141 '#type' => 'select', 142 '#title' => t('Number of items shown in listing pages'), 143 '#default_value' => $config->get('source.list_max'), 144 '#empty_value' => 0, 145 '#options' => $items, 146 ]; 147 148 $form['processors'][$info['id']]['aggregator_clear'] = [ 149 '#type' => 'select', 150 '#title' => t('Discard items older than'), 151 '#default_value' => $config->get('items.expire'), 152 '#options' => $period, 153 '#description' => t('Requires a correctly configured <a href=":cron">cron maintenance task</a>.', [':cron' => Url::fromRoute('system.status')->toString()]), 154 ]; 155 156 $lengths = [0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000]; 157 $options = array_map(function ($length) { 158 return ($length == 0) ? t('Unlimited') : $this->formatPlural($length, '1 character', '@count characters'); 159 }, array_combine($lengths, $lengths)); 160 161 $form['processors'][$info['id']]['aggregator_teaser_length'] = [ 162 '#type' => 'select', 163 '#title' => t('Length of trimmed description'), 164 '#default_value' => $config->get('items.teaser_length'), 165 '#options' => $options, 166 '#description' => t('The maximum number of characters used in the trimmed version of content.'), 167 ]; 168 return $form; 169 } 170 171 /** 172 * {@inheritdoc} 173 */ 174 public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { 175 $this->configuration['items']['expire'] = $form_state->getValue('aggregator_clear'); 176 $this->configuration['items']['teaser_length'] = $form_state->getValue('aggregator_teaser_length'); 177 $this->configuration['source']['list_max'] = $form_state->getValue('aggregator_summary_items'); 178 // @todo Refactor aggregator plugins to ConfigEntity so this is not needed. 179 $this->setConfiguration($this->configuration); 180 } 181 182 /** 183 * {@inheritdoc} 184 */ 185 public function process(FeedInterface $feed) { 186 if (!is_array($feed->items)) { 187 return; 188 } 189 foreach ($feed->items as $item) { 190 // @todo The default entity view builder always returns an empty 191 // array, which is ignored in aggregator_save_item() currently. Should 192 // probably be fixed. 193 if (empty($item['title'])) { 194 continue; 195 } 196 197 // Save this item. Try to avoid duplicate entries as much as possible. If 198 // we find a duplicate entry, we resolve it and pass along its ID is such 199 // that we can update it if needed. 200 if (!empty($item['guid'])) { 201 $values = ['fid' => $feed->id(), 'guid' => $item['guid']]; 202 } 203 elseif ($item['link'] && $item['link'] != $feed->link && $item['link'] != $feed->url) { 204 $values = ['fid' => $feed->id(), 'link' => $item['link']]; 205 } 206 else { 207 $values = ['fid' => $feed->id(), 'title' => $item['title']]; 208 } 209 210 // Try to load an existing entry. 211 if ($entry = $this->itemStorage->loadByProperties($values)) { 212 $entry = reset($entry); 213 } 214 else { 215 $entry = Item::create(['langcode' => $feed->language()->getId()]); 216 } 217 if ($item['timestamp']) { 218 $entry->setPostedTime($item['timestamp']); 219 } 220 221 // Make sure the item title and author fit in the 255 varchar column. 222 $entry->setTitle(Unicode::truncate($item['title'], 255, TRUE, TRUE)); 223 $entry->setAuthor(Unicode::truncate($item['author'], 255, TRUE, TRUE)); 224 225 $entry->setFeedId($feed->id()); 226 $entry->setLink($item['link']); 227 $entry->setGuid($item['guid']); 228 229 $description = ''; 230 if (!empty($item['description'])) { 231 $description = $item['description']; 232 } 233 $entry->setDescription($description); 234 235 $entry->save(); 236 } 237 } 238 239 /** 240 * {@inheritdoc} 241 */ 242 public function delete(FeedInterface $feed) { 243 if ($items = $this->itemStorage->loadByFeed($feed->id())) { 244 $this->itemStorage->delete($items); 245 } 246 // @todo This should be moved out to caller with a different message maybe. 247 $this->messenger->addStatus(t('The news items from %site have been deleted.', ['%site' => $feed->label()])); 248 } 249 250 /** 251 * Implements \Drupal\aggregator\Plugin\ProcessorInterface::postProcess(). 252 * 253 * Expires items from a feed depending on expiration settings. 254 */ 255 public function postProcess(FeedInterface $feed) { 256 $aggregator_clear = $this->configuration['items']['expire']; 257 258 if ($aggregator_clear != FeedStorageInterface::CLEAR_NEVER) { 259 // Delete all items that are older than flush item timer. 260 $age = REQUEST_TIME - $aggregator_clear; 261 $result = $this->itemStorage->getQuery() 262 ->accessCheck(FALSE) 263 ->condition('fid', $feed->id()) 264 ->condition('timestamp', $age, '<') 265 ->execute(); 266 if ($result) { 267 $entities = $this->itemStorage->loadMultiple($result); 268 $this->itemStorage->delete($entities); 269 } 270 } 271 } 272 273 /** 274 * {@inheritdoc} 275 */ 276 public function getConfiguration() { 277 return $this->configFactory->get('aggregator.settings')->get(); 278 } 279 280 /** 281 * {@inheritdoc} 282 */ 283 public function setConfiguration(array $configuration) { 284 $config = $this->config('aggregator.settings'); 285 foreach ($configuration as $key => $value) { 286 $config->set($key, $value); 287 } 288 $config->save(); 289 } 290 291} 292