diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 007d6d3..975dcdc 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -235,7 +235,9 @@ protected function invokeHook($hook, EntityInterface $entity) { protected function mapToStorageRecord(EntityInterface $entity) { $record = new \stdClass(); foreach ($this->entityInfo['schema_fields_sql']['base table'] as $name) { - $record->$name = $entity->$name->value; + if (isset($entity->$name->value)) { + $record->$name = $entity->$name->value; + } } return $record; } diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 047026a..20ddec0 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -74,11 +74,11 @@ function aggregator_theme() { 'template' => 'aggregator-summary-items', ), 'aggregator_summary_item' => array( - 'variables' => array('item' => NULL), + 'variables' => array('aggregator_item' => NULL, 'view_mode' => NULL), 'file' => 'aggregator.pages.inc', ), 'aggregator_item' => array( - 'variables' => array('item' => NULL), + 'variables' => array('aggregator_item' => NULL, 'view_mode' => NULL), 'file' => 'aggregator.pages.inc', 'template' => 'aggregator-item', ), @@ -94,6 +94,25 @@ function aggregator_theme() { } /** + * Implements hook_entity_info(). + */ +function aggregator_entity_info() { + $items['aggregator_item'] = array( + 'label' => t('Aggregator feed item'), + 'entity class' => 'Drupal\aggregator\Item', + 'controller class' => 'Drupal\aggregator\ItemStorageController', + 'render controller class' => 'Drupal\aggregator\ItemRenderController', + 'base table' => 'aggregator_item', + 'fieldable' => TRUE, + 'entity keys' => array( + 'id' => 'iid', + 'label' => 'title', + ), + ); + return $items; +} + +/** * Implements hook_menu(). */ function aggregator_menu() { @@ -276,7 +295,7 @@ function aggregator_menu() { * An aggregator category title. */ function _aggregator_category_title($category) { - return $category['title']; + return $category->title; } /** @@ -516,16 +535,11 @@ function aggregator_save_feed($edit) { elseif (!empty($edit['fid'])) { $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $edit['fid']))->fetchCol(); if ($iids) { - db_delete('aggregator_category_item') - ->condition('iid', $iids, 'IN') - ->execute(); + entity_delete_multiple('aggregator_item', $iids); } db_delete('aggregator_feed')-> condition('fid', $edit['fid']) ->execute(); - db_delete('aggregator_item') - ->condition('fid', $edit['fid']) - ->execute(); // Make sure there is no active block for this feed. if (module_exists('block')) { db_delete('block') @@ -717,7 +731,7 @@ function aggregator_feed_load($fid) { function aggregator_category_load($cid) { $categories = &drupal_static(__FUNCTION__); if (!isset($categories[$cid])) { - $categories[$cid] = db_query('SELECT * FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $cid))->fetchAssoc(); + $categories[$cid] = db_query('SELECT * FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $cid))->fetchObject(); } return $categories[$cid]; diff --git a/core/modules/aggregator/aggregator.pages.inc b/core/modules/aggregator/aggregator.pages.inc index 78ca5bc..01db794 100644 --- a/core/modules/aggregator/aggregator.pages.inc +++ b/core/modules/aggregator/aggregator.pages.inc @@ -61,7 +61,7 @@ function aggregator_page_source_form($form, $form_state, $feed) { * @ingroup forms */ function aggregator_page_category($category) { - drupal_add_feed('aggregator/rss/' . $category['cid'], config('system.site')->get('name') . ' ' . t('aggregator - @title', array('@title' => $category['title']))); + drupal_add_feed('aggregator/rss/' . $category->cid, config('system.site')->get('name') . ' ' . t('aggregator - @title', array('@title' => $category->title))); // It is safe to include the cid in the query because it's loaded from the // database by aggregator_category_load(). @@ -105,20 +105,17 @@ function aggregator_page_category_form($form, $form_state, $category) { * @return * An array of the feed items. */ -function aggregator_load_feed_items($type, $data = NULL) { - $items = array(); +function aggregator_load_feed_items($type, $data = NULL, $limit = 20) { switch ($type) { case 'sum': $query = db_select('aggregator_item', 'i'); $query->join('aggregator_feed', 'f', 'i.fid = f.fid'); - $query->fields('i'); - $query->addField('f', 'title', 'ftitle'); - $query->addField('f', 'link', 'flink'); + $query->fields('i', array('iid')); break; case 'source': $query = db_select('aggregator_item', 'i'); $query - ->fields('i') + ->fields('i', array('iid')) ->condition('i.fid', $data->fid); break; case 'category': @@ -126,25 +123,19 @@ function aggregator_load_feed_items($type, $data = NULL) { $query->leftJoin('aggregator_item', 'i', 'c.iid = i.iid'); $query->leftJoin('aggregator_feed', 'f', 'i.fid = f.fid'); $query - ->fields('i') - ->condition('cid', $data['cid']); - $query->addField('f', 'title', 'ftitle'); - $query->addField('f', 'link', 'flink'); + ->fields('i', array('iid')) + ->condition('cid', $data->cid); break; } $result = $query ->extend('Drupal\Core\Database\Query\PagerSelectExtender') - ->limit(20) + ->limit($limit) ->orderBy('i.timestamp', 'DESC') ->orderBy('i.iid', 'DESC') ->execute(); - foreach ($result as $item) { - $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->iid))->fetchAll(); - $items[] = $item; - } - + $items = entity_load_multiple('aggregator_item', $result->fetchCol()); return $items; } @@ -172,8 +163,9 @@ function _aggregator_page_list($items, $op, $feed_source = '') { else { // Assemble themed output. $output = $feed_source; - foreach ($items as $item) { - $output .= theme('aggregator_item', array('item' => $item)); + if ($items) { + $rendered_items = entity_view_multiple($items, 'default'); + $output .= drupal_render($rendered_items); } $output = theme('aggregator_wrapper', array('content' => $output)); } @@ -204,10 +196,10 @@ function aggregator_categorize_items($items, $feed_source = '') { $form['categories'] = array( '#tree' => TRUE, ); + $form['items'] = entity_view_multiple($items, 'default'); foreach ($items as $item) { - $form['items'][$item->iid] = array('#markup' => theme('aggregator_item', array('item' => $item))); - $form['categories'][$item->iid] = array(); - $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = :iid', array(':iid' => $item->iid)); + $form['categories'][$item->id()] = array(); + $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = :iid', array(':iid' => $item->id())); $selected = array(); foreach ($categories_result as $category) { if (!$done) { @@ -218,7 +210,7 @@ function aggregator_categorize_items($items, $feed_source = '') { } } $done = TRUE; - $form['categories'][$item->iid] = array( + $form['categories'][$item->id()] = array( '#type' => config('aggregator.settings')->get('source.category_selector'), '#default_value' => $selected, '#options' => $categories, @@ -319,23 +311,23 @@ function template_preprocess_aggregator_wrapper(&$variables) { * @see aggregator-item.tpl.php */ function template_preprocess_aggregator_item(&$variables) { - $item = $variables['item']; + $item = $variables['aggregator_item']; - $variables['feed_url'] = check_url($item->link); - $variables['feed_title'] = check_plain($item->title); - $variables['content'] = aggregator_filter_xss($item->description); + $variables['feed_url'] = check_url($item->link->value); + $variables['feed_title'] = check_plain($item->title->value); + $variables['content'] = aggregator_filter_xss($item->description->value); $variables['source_url'] = ''; $variables['source_title'] = ''; - if (isset($item->ftitle) && isset($item->fid)) { - $variables['source_url'] = url("aggregator/sources/$item->fid"); + if (isset($item->ftitle) && isset($item->fid->value)) { + $variables['source_url'] = url("aggregator/sources/$item->fid->value"); $variables['source_title'] = check_plain($item->ftitle); } - if (date('Ymd', $item->timestamp) == date('Ymd')) { - $variables['source_date'] = t('%ago ago', array('%ago' => format_interval(REQUEST_TIME - $item->timestamp))); + if (date('Ymd', $item->timestamp->value) == date('Ymd')) { + $variables['source_date'] = t('%ago ago', array('%ago' => format_interval(REQUEST_TIME - $item->timestamp->value))); } else { - $variables['source_date'] = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i')); + $variables['source_date'] = format_date($item->timestamp->value, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i')); } $variables['categories'] = array(); @@ -358,9 +350,8 @@ function aggregator_page_sources() { $summary_items = array(); $aggregator_summary_items = config('aggregator.settings')->get('source.list_max'); if ($aggregator_summary_items) { - $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = :fid ORDER BY i.timestamp DESC', 0, $aggregator_summary_items, array(':fid' => $feed->fid)); - foreach ($items as $item) { - $summary_items[] = theme('aggregator_summary_item', array('item' => $item)); + if ($items = aggregator_load_feed_items('source', $feed, $aggregator_summary_items)) { + $summary_items = entity_view_multiple($items, 'summary'); } } $feed->url = url('aggregator/sources/' . $feed->fid); @@ -380,13 +371,12 @@ function aggregator_page_categories() { $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description'); $output = ''; + $summary_items = array(); foreach ($result as $category) { $aggregator_summary_items = config('aggregator.settings')->get('source.list_max'); if ($aggregator_summary_items) { - $summary_items = array(); - $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = :cid ORDER BY i.timestamp DESC', 0, $aggregator_summary_items, array(':cid' => $category->cid)); - foreach ($items as $item) { - $summary_items[] = theme('aggregator_summary_item', array('item' => $item)); + if ($items = aggregator_load_feed_items('category', $category, $aggregator_summary_items)) { + $summary_items = entity_view_multiple($items, 'summary'); } } $category->url = url('aggregator/categories/' . $category->cid); @@ -527,7 +517,11 @@ function theme_aggregator_page_opml($variables) { */ function template_preprocess_aggregator_summary_items(&$variables) { $variables['title'] = check_plain($variables['source']->title); - $variables['summary_list'] = theme('item_list', array('items' => $variables['summary_items'])); + $rendered_items = array(); + foreach (element_children($variables['summary_items']) as $key) { + $rendered_items[] = drupal_render($variables['summary_items'][$key]); + } + $variables['summary_list'] = theme('item_list', array('items' => $rendered_items)); $variables['source_url'] = $variables['source']->url; } @@ -537,19 +531,19 @@ function template_preprocess_aggregator_summary_items(&$variables) { * @see aggregator-summary-item.tpl.php */ function template_preprocess_aggregator_summary_item(&$variables) { - $item = $variables['item']; + $item = $variables['aggregator_item']; - $variables['item_url'] = l(check_plain($item->title), check_url(url($item->link, array('absolute' => TRUE))), array( + $variables['item_url'] = l(check_plain($item->label()), check_url(url($item->link->value, array('absolute' => TRUE))), array( 'attributes' => array( 'class' => array('feed-item-url',), ), )); $variables['item_age'] = theme('datetime', array( 'attributes' => array( - 'datetime' => format_date($item->timestamp, 'html_datetime', '', 'UTC'), + 'datetime' => format_date($item->timestamp->value, 'html_datetime', '', 'UTC'), 'class' => array('feed-item-age',), ), - 'text' => t('%age old', array('%age' => format_interval(REQUEST_TIME - $item->timestamp))), + 'text' => t('%age old', array('%age' => format_interval(REQUEST_TIME - $item->timestamp->value))), 'html' => TRUE, )); } diff --git a/core/modules/aggregator/aggregator.processor.inc b/core/modules/aggregator/aggregator.processor.inc index b333a96..05b9d2b 100644 --- a/core/modules/aggregator/aggregator.processor.inc +++ b/core/modules/aggregator/aggregator.processor.inc @@ -22,26 +22,46 @@ function aggregator_aggregator_process($feed) { if (is_object($feed)) { if (is_array($feed->items)) { foreach ($feed->items as $item) { + // @todo: The default entity render controller always returns an empty + // array, which is ignored in aggregator_save_item() currently. Should + // probably be fixed. + if (empty($item['title'])) { + continue; + } + // Save this item. Try to avoid duplicate entries as much as possible. If // we find a duplicate entry, we resolve it and pass along its ID is such // that we can update it if needed. if (!empty($item['guid'])) { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND guid = :guid", array(':fid' => $feed->fid, ':guid' => $item['guid']))->fetchObject(); + $values = array('fid' => $feed->fid, 'guid' => $item['guid']); } elseif ($item['link'] && $item['link'] != $feed->link && $item['link'] != $feed->url) { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND link = :link", array(':fid' => $feed->fid, ':link' => $item['link']))->fetchObject(); + $values = array('fid' => $feed->fid, 'link' => $item['link']); + } + else { + $values = array('fid' => $feed->fid, 'title' => $item['title']); + } + + // Try to load an existing entry. + if ($entry = entity_load_multiple_by_properties('aggregator_item', $values)) { + $entry = reset($entry); } else { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND title = :title", array(':fid' => $feed->fid, ':title' => $item['title']))->fetchObject(); + $entry = entity_create('aggregator_item', array()); } - if (!$item['timestamp']) { - $item['timestamp'] = isset($entry->timestamp) ? $entry->timestamp : REQUEST_TIME; + if ($item['timestamp']) { + $entry->timestamp->value = $item['timestamp']; } // Make sure the item title and author fit in the 255 varchar column. - $item['title'] = truncate_utf8($item['title'], 255, TRUE, TRUE); - $item['author'] = truncate_utf8($item['author'], 255, TRUE, TRUE); - aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid : ''), 'fid' => $feed->fid, 'timestamp' => $item['timestamp'], 'title' => $item['title'], 'link' => $item['link'], 'author' => $item['author'], 'description' => $item['description'], 'guid' => $item['guid'])); + $entry->title->value = truncate_utf8($item['title'], 255, TRUE, TRUE); + $entry->author->value = truncate_utf8($item['author'], 255, TRUE, TRUE); + + $entry->fid->value = $feed->fid; + $entry->link->value = $item['link']; + $entry->description->value = $item['description']; + $entry->guid->value = $item['guid']; + $entry->save(); } } } @@ -52,14 +72,7 @@ function aggregator_aggregator_process($feed) { */ function aggregator_aggregator_remove($feed) { $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchCol(); - if ($iids) { - db_delete('aggregator_category_item') - ->condition('iid', $iids, 'IN') - ->execute(); - } - db_delete('aggregator_item') - ->condition('fid', $feed->fid) - ->execute(); + entity_delete_multiple('aggregator_item', $iids); drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed->title))); } @@ -139,48 +152,6 @@ function _aggregator_characters($length) { } /** - * Adds/edits/deletes an aggregator item. - * - * @param $edit - * An associative array describing the item to be added/edited/deleted. - */ -function aggregator_save_item($edit) { - if ($edit['title'] && empty($edit['iid'])) { - $edit['iid'] = db_insert('aggregator_item') - ->fields(array( - 'title' => $edit['title'], - 'link' => $edit['link'], - 'author' => $edit['author'], - 'description' => $edit['description'], - 'guid' => $edit['guid'], - 'timestamp' => $edit['timestamp'], - 'fid' => $edit['fid'], - )) - ->execute(); - } - if ($edit['iid'] && !$edit['title']) { - db_delete('aggregator_item') - ->condition('iid', $edit['iid']) - ->execute(); - db_delete('aggregator_category_item') - ->condition('iid', $edit['iid']) - ->execute(); - } - elseif ($edit['title'] && $edit['link']) { - // file the items in the categories indicated by the feed - $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $edit['fid'])); - foreach ($result as $category) { - db_merge('aggregator_category_item') - ->key(array( - 'iid' => $edit['iid'], - 'cid' => $category->cid, - )) - ->execute(); - } - } -} - -/** * Expires items from a feed depending on expiration settings. * * @param $feed @@ -198,12 +169,7 @@ function aggregator_expire($feed) { )) ->fetchCol(); if ($iids) { - db_delete('aggregator_category_item') - ->condition('iid', $iids, 'IN') - ->execute(); - db_delete('aggregator_item') - ->condition('iid', $iids, 'IN') - ->execute(); + entity_delete_multiple('aggregator_item', $iids); } } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Item.php b/core/modules/aggregator/lib/Drupal/aggregator/Item.php new file mode 100644 index 0000000..b70ea1e --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Item.php @@ -0,0 +1,36 @@ +get('iid')->value; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::label(). + */ + public function label($langcode = NULL) { + return $this->title->value; + } +} + diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemRenderController.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemRenderController.php new file mode 100644 index 0000000..298c285 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemRenderController.php @@ -0,0 +1,31 @@ +timestamp->value = REQUEST_TIME; + return $entity; + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad(). + */ + protected function attachLoad(&$queried_entities, $load_revision = FALSE) { + parent::attachLoad($queried_entities, $load_revision); + foreach ($queried_entities as $item) { + $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->id()))->fetchAll(); + } + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete(). + */ + protected function preDelete($entities) { + parent::preDelete($entities); + + db_delete('aggregator_category_item') + ->condition('iid', array_keys($entities), 'IN') + ->execute(); + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + parent::postSave($entity, $update); + + $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $entity->fid->value)); + foreach ($result as $category) { + db_merge('aggregator_category_item') + ->key(array( + 'iid' => $entity->id(), + 'cid' => $category->cid, + )) + ->execute(); + } + } + + /** + * Implements Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + $fields['iid'] = array( + 'label' => t('ID'), + 'description' => t('The ID of the aggregor item.'), + 'type' => 'integer_field', + 'read-only' => TRUE, + ); + $fields['fid'] = array( + 'label' => t('Aggregator feed ID'), + 'description' => t('The ID of the aggregator feed.'), + 'type' => 'integer_field', + ); + $fields['title'] = array( + 'label' => t('Title'), + 'description' => t('The title of the feed item.'), + 'type' => 'string_field', + ); + $fields['link'] = array( + 'label' => t('Link'), + 'description' => t('The link of the feed item.'), + 'type' => 'string_field', + ); + $fields['author'] = array( + 'label' => t('Author'), + 'description' => t('The author of the feed item.'), + 'type' => 'string_field', + ); + $fields['description'] = array( + 'label' => t('Description'), + 'description' => t('The body of the feed item.'), + 'type' => 'string_field', + ); + $fields['timestamp'] = array( + 'label' => t('Posted timestamp'), + 'description' => t('Posted date of the feed item, as a Unix timestamp.'), + 'type' => 'integer_field', + ); + $fields['guid'] = array( + 'label' => t('GUID'), + 'description' => t('Unique identifier for the feed item.'), + 'type' => 'string_field', + ); + return $fields; + } + +}