diff --git a/lib/load.php b/lib/load.php
index ee1c973f99568..f86960535d6fa 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -71,6 +71,9 @@
if ( ! function_exists( 'render_block_core_latest_posts' ) ) {
require dirname( __FILE__ ) . '/../packages/block-library/src/latest-posts/index.php';
}
+if ( ! function_exists( 'render_block_core_rss' ) ) {
+ require dirname( __FILE__ ) . '/../packages/block-library/src/rss/index.php';
+}
if ( ! function_exists( 'render_block_core_shortcode' ) ) {
require dirname( __FILE__ ) . '/../packages/block-library/src/shortcode/index.php';
}
diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss
index eb2dea826189b..9fb94f40bdef7 100644
--- a/packages/block-library/src/editor.scss
+++ b/packages/block-library/src/editor.scss
@@ -22,6 +22,7 @@
@import "./preformatted/editor.scss";
@import "./pullquote/editor.scss";
@import "./quote/editor.scss";
+@import "./rss/editor.scss";
@import "./shortcode/editor.scss";
@import "./spacer/editor.scss";
@import "./subhead/editor.scss";
diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js
index ecb354f8c373e..5dd3fee7b2a29 100644
--- a/packages/block-library/src/index.js
+++ b/packages/block-library/src/index.js
@@ -38,6 +38,7 @@ import * as nextpage from './nextpage';
import * as preformatted from './preformatted';
import * as pullquote from './pullquote';
import * as reusableBlock from './block';
+import * as rss from './rss';
import * as separator from './separator';
import * as shortcode from './shortcode';
import * as spacer from './spacer';
@@ -85,6 +86,7 @@ export const registerCoreBlocks = () => {
nextpage,
preformatted,
pullquote,
+ rss,
separator,
reusableBlock,
spacer,
diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js
new file mode 100644
index 0000000000000..9ebfa94491533
--- /dev/null
+++ b/packages/block-library/src/rss/edit.js
@@ -0,0 +1,169 @@
+/**
+ * WordPress dependencies
+ */
+import { Component, Fragment } from '@wordpress/element';
+import {
+ Button,
+ Disabled,
+ PanelBody,
+ Placeholder,
+ RangeControl,
+ ServerSideRender,
+ TextControl,
+ ToggleControl,
+ Toolbar,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import {
+ BlockControls,
+ InspectorControls,
+} from '@wordpress/editor';
+
+const DEFAULT_MIN_ITEMS = 1;
+const DEFAULT_MAX_ITEMS = 10;
+
+class RSSEdit extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.state = {
+ editing: ! this.props.attributes.feedURL,
+ };
+
+ this.toggleAttribute = this.toggleAttribute.bind( this );
+ this.onSubmitURL = this.onSubmitURL.bind( this );
+ }
+
+ toggleAttribute( propName ) {
+ return () => {
+ const value = this.props.attributes[ propName ];
+ const { setAttributes } = this.props;
+
+ setAttributes( { [ propName ]: ! value } );
+ };
+ }
+
+ onSubmitURL( event ) {
+ event.preventDefault();
+
+ const { feedURL } = this.props.attributes;
+ if ( feedURL ) {
+ this.setState( { editing: false } );
+ }
+ }
+
+ render() {
+ const {
+ blockLayout,
+ columns,
+ displayAuthor,
+ displayExcerpt,
+ displayDate,
+ excerptLength,
+ feedURL,
+ itemsToShow,
+ } = this.props.attributes;
+ const { setAttributes } = this.props;
+
+ if ( this.state.editing ) {
+ return (
+
+
+
+ );
+ }
+
+ const toolbarControls = [
+ {
+ icon: 'edit',
+ title: __( 'Edit RSS URL' ),
+ onClick: () => this.setState( { editing: true } ),
+ },
+ {
+ icon: 'list-view',
+ title: __( 'List View' ),
+ onClick: () => setAttributes( { blockLayout: 'list' } ),
+ isActive: blockLayout === 'list',
+ },
+ {
+ icon: 'grid-view',
+ title: __( 'Grid View' ),
+ onClick: () => setAttributes( { blockLayout: 'grid' } ),
+ isActive: blockLayout === 'grid',
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+ setAttributes( { itemsToShow: value } ) }
+ min={ DEFAULT_MIN_ITEMS }
+ max={ DEFAULT_MAX_ITEMS }
+ />
+
+
+
+ { displayExcerpt &&
+ setAttributes( { excerptLength: value } ) }
+ min={ 0 }
+ max={ 100 }
+ />
+ }
+ { blockLayout === 'grid' &&
+ setAttributes( { columns: value } ) }
+ min={ 2 }
+ max={ 6 }
+ />
+ }
+
+
+
+
+
+
+ );
+ }
+}
+
+export default RSSEdit;
diff --git a/packages/block-library/src/rss/editor.scss b/packages/block-library/src/rss/editor.scss
new file mode 100644
index 0000000000000..8632c5e101b00
--- /dev/null
+++ b/packages/block-library/src/rss/editor.scss
@@ -0,0 +1,6 @@
+.block-editor .wp-block-rss {
+ padding-left: 2.5em;
+ &.is-grid {
+ padding-left: 0;
+ }
+}
diff --git a/packages/block-library/src/rss/index.js b/packages/block-library/src/rss/index.js
new file mode 100644
index 0000000000000..6a0f916c7d6e5
--- /dev/null
+++ b/packages/block-library/src/rss/index.js
@@ -0,0 +1,33 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import edit from './edit';
+
+export const name = 'core/rss';
+
+export const settings = {
+ title: __( 'RSS' ),
+
+ description: __( 'Display entries from any RSS or Atom feed.' ),
+
+ icon: 'rss',
+
+ category: 'widgets',
+
+ keywords: [ __( 'atom' ), __( 'feed' ) ],
+
+ supports: {
+ html: false,
+ },
+
+ edit,
+
+ save() {
+ return null;
+ },
+};
diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php
new file mode 100644
index 0000000000000..b5fe3abaf563b
--- /dev/null
+++ b/packages/block-library/src/rss/index.php
@@ -0,0 +1,137 @@
+
' . __( 'RSS Error:' ) . ' ' . $rss->get_error_message() . '
';
+ }
+
+ if ( ! $rss->get_item_quantity() ) {
+ // PHP 5.2 compatibility. See: http://simplepie.org/wiki/faq/i_m_getting_memory_leaks.
+ $rss->__destruct();
+ unset( $rss );
+
+ return '' . __( 'An error has occurred, which probably means the feed is down. Try again later.' ) . '
';
+ }
+
+ $rss_items = $rss->get_items( 0, $attributes['itemsToShow'] );
+ $list_items = '';
+ foreach ( $rss_items as $item ) {
+ $title = esc_html( trim( strip_tags( $item->get_title() ) ) );
+ if ( empty( $title ) ) {
+ $title = __( '(Untitled)' );
+ }
+ $link = $item->get_link();
+ $link = esc_url( $link );
+ if ( $link ) {
+ $title = "{$title}";
+ }
+ $title = "";
+
+ $date = '';
+ if ( $attributes['displayDate'] ) {
+ $date = $item->get_date( 'U' );
+
+ if ( $date ) {
+ $date = sprintf(
+ ' ',
+ date_i18n( get_option( 'c' ), $date ),
+ date_i18n( get_option( 'date_format' ), $date )
+ );
+ }
+ }
+
+ $author = '';
+ if ( $attributes['displayAuthor'] ) {
+ $author = $item->get_author();
+ if ( is_object( $author ) ) {
+ $author = $author->get_name();
+ $author = '';
+ }
+ }
+
+ $excerpt = '';
+ if ( $attributes['displayExcerpt'] ) {
+ $excerpt = html_entity_decode( $item->get_description(), ENT_QUOTES, get_option( 'blog_charset' ) );
+ $excerpt = esc_attr( wp_trim_words( $excerpt, $attributes['excerptLength'], ' […]' ) );
+
+ // Change existing [...] to […].
+ if ( '[...]' == substr( $excerpt, -5 ) ) {
+ $excerpt = substr( $excerpt, 0, -5 ) . '[…]';
+ }
+
+ $excerpt = '';
+ }
+
+ $list_items .= "";
+ }
+
+ $classes = 'grid' === $attributes['blockLayout'] ? ' is-grid columns-' . $attributes['columns'] : '';
+ $list_items_markup = "";
+
+ // PHP 5.2 compatibility. See: http://simplepie.org/wiki/faq/i_m_getting_memory_leaks.
+ $rss->__destruct();
+ unset( $rss );
+
+ return $list_items_markup;
+}
+
+/**
+ * Registers the `core/rss` block on server.
+ */
+function register_block_core_rss() {
+ register_block_type( 'core/rss',
+ array(
+ 'attributes' => array(
+ 'columns' => array(
+ 'type' => 'number',
+ 'default' => 2,
+ ),
+ 'blockLayout' => array(
+ 'type' => 'string',
+ 'default' => 'list',
+ ),
+ 'feedURL' => array(
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ 'itemsToShow' => array(
+ 'type' => 'number',
+ 'default' => 5,
+ ),
+ 'displayExcerpt' => array(
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
+ 'displayAuthor' => array(
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
+ 'displayDate' => array(
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
+ 'excerptLength' => array(
+ 'type' => 'number',
+ 'default' => 55,
+ ),
+ ),
+ 'render_callback' => 'render_block_core_rss',
+ )
+ );
+}
+
+add_action( 'init', 'register_block_core_rss' );
diff --git a/packages/block-library/src/rss/style.scss b/packages/block-library/src/rss/style.scss
new file mode 100644
index 0000000000000..a2ad8d4060dff
--- /dev/null
+++ b/packages/block-library/src/rss/style.scss
@@ -0,0 +1,35 @@
+.wp-block-rss {
+ &.alignleft {
+ /*rtl:ignore*/
+ margin-right: 2em;
+ }
+ &.alignright {
+ /*rtl:ignore*/
+ margin-left: 2em;
+ }
+ &.is-grid {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0;
+ list-style: none;
+
+ li {
+ margin: 0 16px 16px 0;
+ width: 100%;
+ }
+ }
+
+ @include break-small {
+ @for $i from 2 through 6 {
+ &.columns-#{ $i } li {
+ width: calc(( 100% / #{ $i } ) - 16px);
+ }
+ }
+ }
+}
+
+.wp-block-rss__item-publish-date,
+.wp-block-rss__item-author {
+ color: $dark-gray-300;
+ font-size: $default-font-size;
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 9d9b2379696b2..09d3a2125e378 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -15,6 +15,7 @@
@import "./paragraph/style.scss";
@import "./pullquote/style.scss";
@import "./quote/style.scss";
+@import "./rss/style.scss";
@import "./separator/style.scss";
@import "./subhead/style.scss";
@import "./table/style.scss";
diff --git a/test/integration/full-content/fixtures/core__rss.html b/test/integration/full-content/fixtures/core__rss.html
new file mode 100644
index 0000000000000..ce652528301c6
--- /dev/null
+++ b/test/integration/full-content/fixtures/core__rss.html
@@ -0,0 +1 @@
+
diff --git a/test/integration/full-content/fixtures/core__rss.json b/test/integration/full-content/fixtures/core__rss.json
new file mode 100644
index 0000000000000..89de06cd2d78d
--- /dev/null
+++ b/test/integration/full-content/fixtures/core__rss.json
@@ -0,0 +1,10 @@
+[
+ {
+ "clientId": "_clientId_0",
+ "name": "core/rss",
+ "isValid": true,
+ "attributes": {},
+ "innerBlocks": [],
+ "originalContent": ""
+ }
+]
diff --git a/test/integration/full-content/fixtures/core__rss.parsed.json b/test/integration/full-content/fixtures/core__rss.parsed.json
new file mode 100644
index 0000000000000..bdc596b09a5a1
--- /dev/null
+++ b/test/integration/full-content/fixtures/core__rss.parsed.json
@@ -0,0 +1,26 @@
+[
+ {
+ "blockName": "core/rss",
+ "attrs": {
+ "postLayout": "grid",
+ "feedURL": "https://wordpress.org/news/",
+ "postsToShow": 4,
+ "displayExcerpt": true,
+ "displayAuthor": true,
+ "displayDate": true,
+ "excerptLength": 20
+ },
+ "innerBlocks": [],
+ "innerHTML": "",
+ "innerContent": []
+ },
+ {
+ "blockName": null,
+ "attrs": {},
+ "innerBlocks": [],
+ "innerHTML": "\n",
+ "innerContent": [
+ "\n"
+ ]
+ }
+]
diff --git a/test/integration/full-content/fixtures/core__rss.serialized.html b/test/integration/full-content/fixtures/core__rss.serialized.html
new file mode 100644
index 0000000000000..c6d9be22646e7
--- /dev/null
+++ b/test/integration/full-content/fixtures/core__rss.serialized.html
@@ -0,0 +1 @@
+