+
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 78 additions & 40 deletions includes/class-import.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,95 @@
* @author Alex Kirk
*/
class Import {
public static function opml( $opml ) {
$opml = simplexml_load_string( $opml );
if ( ! $opml ) {
return new \WP_Error( 'friends_import_opml_error', __( 'Failed to parse OPML.', 'friends' ) );

private static function get_feed_from_opml_node( $friend ) {
$xml_url = (string) $friend['xmlUrl'];
if ( ! $xml_url ) {
return null;
}

$feeds = array();
foreach ( $opml->body->outline as $outline ) {
$role = (string) $outline['text'];
foreach ( $outline->outline as $friend ) {
$user_login = str_replace( ' ', '-', strtolower( sanitize_user( (string) $friend['text'] ) ) );
$feed = User_Feed::get_by_url( (string) $friend['xmlUrl'] );
if ( $feed instanceof User_Feed ) {
$friend_user = $feed->get_friend_user();
if ( ! isset( $feeds[ $friend_user->user_login ] ) ) {
$feeds[ $friend_user->user_login ] = array();
}
$feeds[ $friend_user->user_login ][] = $feed;
continue;
}
$username = (string) $friend['text'];
if ( ! $username ) {
$username = (string) $friend['title'];
}
if ( ! $username ) {
$username = (string) $friend['htmlUrl'];
$username = preg_replace( '/^https?:\/\//', '', $username );
}
if ( ! $username ) {
$username = (string) $xml_url;
$username = preg_replace( '/^https?:\/\//', '', $username );
}

$friend_user = User::get_by_username( $user_login );
if ( ! $friend_user || is_wp_error( $friend_user ) ) {
$friend_user = Subscription::create(
$user_login,
'subscription',
(string) $friend['htmlUrl'],
(string) $friend['text']
);
}
if ( ! $friend_user instanceof User ) {
continue;
}
$feed = $friend_user->save_feed(
(string) $friend['xmlUrl'],
array(
'active' => true,
'mime-type' => 'atom' === (string) $friend['type'] ? 'application/atom+xml' : 'application/rss+xml',
)
);
if ( ! $feed instanceof User_Feed ) {
continue;
}
$user_login = User::sanitize_username( $username );
$feed = User_Feed::get_by_url( $xml_url );
if ( $feed instanceof User_Feed ) {
return $feed;
}

$friend_user = User::get_by_username( $user_login );
if ( ! $friend_user || is_wp_error( $friend_user ) ) {
$friend_user = Subscription::create(
$user_login,
'subscription',
(string) $friend['htmlUrl'],
(string) $friend['text']
);
}

if ( ! $friend_user instanceof User ) {
return null;
}

$feed = $friend_user->save_feed(
$xml_url,
array(
'active' => true,
'mime-type' => 'atom' === (string) $friend['type'] ? 'application/atom+xml' : 'application/rss+xml',
)
);

if ( ! $feed instanceof User_Feed ) {
if ( is_wp_error( $feed ) && apply_filters( 'friends_debug', false ) ) {
wp_trigger_error( __FUNCTION__, $feed->get_error_message() );

}
return null;
}

return $feed;
}

private static function recurse_into_opml( $feeds, $friend ) {
$xml_url = (string) $friend['xmlUrl'];
if ( $xml_url ) {
$feed = self::get_feed_from_opml_node( $friend );
if ( $feed instanceof User_Feed ) {
$friend_user = $feed->get_friend_user();

if ( ! isset( $feeds[ $friend_user->user_login ] ) ) {
$feeds[ $friend_user->user_login ] = array();
}
$feeds[ $friend_user->user_login ][] = $feed;
}
return $feeds;
}

foreach ( $friend->outline as $child ) {
$feeds = self::recurse_into_opml( $feeds, $child );
}

return $feeds;
}

public static function opml( $opml ) {
$opml = simplexml_load_string( $opml );
if ( ! $opml ) {
return new \WP_Error( 'friends_import_opml_error', __( 'Failed to parse OPML.', 'friends' ) );
}

$feeds = self::recurse_into_opml( array(), $opml->body );

return $feeds;
}
}
8 changes: 5 additions & 3 deletions includes/class-user.php
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ public function save_feeds( $feeds = array() ) {
$errors = new \WP_Error();
foreach ( $feeds as $feed_url => $options ) {
if ( ! is_string( $feed_url ) || ! Friends::check_url( $feed_url ) ) {
$errors->add( 'invalid-url', 'An invalid URL was provided' );
$errors->add( 'invalid-url', 'An invalid URL was provided', $feed_url );
unset( $feeds[ $feed_url ] );
continue;
}
Expand All @@ -406,7 +406,8 @@ public function save_feeds( $feeds = array() ) {

$all_urls = array();
foreach ( wp_get_object_terms( $this->get_object_id(), User_Feed::TAXONOMY ) as $term ) {
$all_urls[ $term->name ] = $term->term_id;
$url = str_replace( '&', '&', $term->name );
$all_urls[ $url ] = $term->term_id;
}

$user_feeds = wp_set_object_terms( $this->get_object_id(), array_keys( array_merge( $all_urls, $feeds ) ), User_Feed::TAXONOMY );
Expand All @@ -415,7 +416,8 @@ public function save_feeds( $feeds = array() ) {
}

foreach ( wp_get_object_terms( $this->get_object_id(), User_Feed::TAXONOMY ) as $term ) {
$all_urls[ $term->name ] = $term->term_id;
$url = str_replace( '&', '&', $term->name );
$all_urls[ $url ] = $term->term_id;
}

foreach ( $feeds as $url => $feed_options ) {
Expand Down
44 changes: 44 additions & 0 deletions tests/data/feedland.opml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
<head>
<title>bph/wordpress</title>
<description>bph's subscription list, wordpress category. List created by feedlandDatabase v0.7.55.</description>
<dateModified>Thu, 28 Nov 2024 16:14:43 GMT</dateModified>
</head>
<body>
<outline type="rss" text="WP Mayor" xmlUrl="https://wpmayor.com/feed/" htmlUrl="https://wpmayor.com/" category="wordpress"/>
<outline type="rss" text="Block themes — WordPress.org" xmlUrl="https://wordpress.org/themes/tags/full-site-editing/feed/" htmlUrl="https://wordpress.org/themes" category="wordpress"/>
<outline type="rss" text="All themes — WordPress.org" xmlUrl="https://wordpress.org/themes/feed/" htmlUrl="https://wordpress.org/themes" category="wordpress"/>
<outline type="rss" text="WP BizDev" xmlUrl="https://wpbiz.dev/feed/" htmlUrl="https://wpbiz.dev/" category="wordpress"/>
<outline type="rss" text="WordPress Guides, Tutorials &amp; Tips - Kinsta® Blog" xmlUrl="https://kinsta.com/blog/feed/" htmlUrl="https://kinsta.com/blog/" category="wordpress"/>
<outline type="rss" text="WebTNG" xmlUrl="https://www.webtng.com/feed/" htmlUrl="https://www.webtng.com/" category="wordpress"/>
<outline type="rss" text="Automattic Design" xmlUrl="https://automattic.design/feed/" htmlUrl="https://automattic.design/" category="wordpress"/>
<outline type="rss" text="WP-CONTENT.CO" xmlUrl="https://wp-content.co/feed" htmlUrl="https://wp-content.co/" category="wordpress"/>
<outline type="rss" text="Brian Coords" xmlUrl="https://www.briancoords.com/feed" htmlUrl="https://www.briancoords.com/" category="wordpress"/>
<outline type="rss" text="WP Plugins RSS" xmlUrl="https://wp-plugins-rss.edavis.workers.dev/feed/" htmlUrl="https://wordpress.org/plugins/browse/new/" category="wordpress"/>
<outline type="rss" text="editortips" xmlUrl="https://editortips.com/feed/" htmlUrl="https://editortips.com/" category="wordpress"/>
<outline type="rss" text="The WooCommerce Developer Blog" xmlUrl="https://developer.woo.com/feed/" htmlUrl="https://developer.woocommerce.com/" category="wordpress"/>
<outline type="rss" text="DevotePress" xmlUrl="https://devotepress.com/feed/" htmlUrl="https://devotepress.com/" category="wordpress"/>
<outline type="rss" text="Rich Tabor" xmlUrl="https://richtabor.com/feed/" htmlUrl="https://rich.blog/" category="wordpress"/>
<outline type="rss" text="Post Status" xmlUrl="https://poststatus.com/feed/" htmlUrl="https://poststatus.com/" category="wordpress"/>
<outline type="rss" text="WPShout" xmlUrl="https://www.codeinwp.com/feed/" htmlUrl="https://wpshout.com/" category="wordpress"/>
<outline type="rss" text="Matt Mullenweg" xmlUrl="https://ma.tt/feed/" htmlUrl="https://ma.tt/" category="wordpress"/>
<outline type="rss" text="WordPress News" xmlUrl="https://wordpress.org/news/feed/" htmlUrl="https://wordpress.org/news" category="wordpress"/>
<outline type="rss" text="Human Made Blog" xmlUrl="https://humanmade.com/blog/feed/" htmlUrl="https://humanmade.com/blog/" category="wordpress"/>
<outline type="rss" text="Builders" xmlUrl="https://wpengine.com/builders/feed/" htmlUrl="https://wpengine.com/builders/" category="wordpress"/>
<outline type="rss" text="Tom McFarlin" xmlUrl="https://tommcfarlin.com/feed/" htmlUrl="https://tommcfarlin.com/" category="wordpress"/>
<outline type="rss" text="Tutorials | Learn WordPress" xmlUrl="https://learn.wordpress.org/tutorials/feed/" htmlUrl="https://learn.wordpress.org/" category="wordpress"/>
<outline type="rss" text="Full Site Editing" xmlUrl="https://fullsiteediting.com/feed/" htmlUrl="https://fullsiteediting.com/" category="wordpress"/>
<outline type="rss" text="Blog – WP Fieldwork" xmlUrl="https://wpfieldwork.com/blog/feed/" htmlUrl="https://wpfieldwork.com/" category="wordpress"/>
<outline type="rss" text="Gutenberg Hub" xmlUrl="https://gutenberghub.com/feed" htmlUrl="https://gutenberghub.com/" category="wordpress"/>
<outline type="rss" text="The WP Minute" xmlUrl="https://thewpminute.com/feed/" htmlUrl="https://thewpminute.com/" category="wordpress"/>
<outline type="rss" text="WordPress Developer Blog" xmlUrl="https://developer.wordpress.org/news/feed/" htmlUrl="https://developer.wordpress.org/news" category="wordpress"/>
<outline type="rss" text="Ollie WordPress Block Theme" xmlUrl="https://olliewp.com/feed/" htmlUrl="https://olliewp.com/" category="wordpress"/>
<outline type="rss" text="WordPress.tv" xmlUrl="https://wordpress.tv/feed/" htmlUrl="https://wordpress.tv/" category="wordpress"/>
<outline type="rss" text="Torque" xmlUrl="https://torquemag.io/feed/" htmlUrl="https://torquemag.io/" category="wordpress"/>
<outline type="rss" text="WordPress – CSS-Tricks" xmlUrl="https://css-tricks.com/tag/wordpress/feed/" htmlUrl="https://css-tricks.com/" category="wordpress"/>
<outline type="rss" text="Blog - Bill Erickson" xmlUrl="https://www.billerickson.net/blog/feed/" htmlUrl="https://www.billerickson.net/blog/" category="wordpress"/>
<outline type="rss" text="WP Builds" xmlUrl="https://wpbuilds.com/feed/podcast/" htmlUrl="https://wpbuilds.com/series/wp-builds/" category="wordpress"/>
<outline type="rss" text="WP Tavern" xmlUrl="https://wptavern.com/feed" htmlUrl="https://wptavern.com/" category="wordpress"/>
</body>
</opml>
37 changes: 37 additions & 0 deletions tests/data/friends.opml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?><opml version="2.0">
<head>
<title>My Friends</title>
<dateCreated>Fri, 29 Nov 2024 06:47:29 +0000</dateCreated>
<ownerName>adm</ownerName>
</head>
<body>
<outline text="Friends">
<outline text="alex" htmlUrl="https://akirk.blog/friends/alex.mu.blog/" title="alex" type="rss" xmlUrl="https://alex.mu.blog/feed/?me=akirk.blog&#038;until=1764398848&#038;auth=$2y$12$H6eft9cdHzvnNopyVzC4C.TS.yuNGL1Qox3nu/qREMI1HqamMTQ7u"/>
<outline text="alex" htmlUrl="https://akirk.blog/friends/alex.mu.blog/" title="alex" type="rss" xmlUrl="https://mastodon.local/@alex"/>
<outline text="Matt on Not-WordPress" htmlUrl="https://akirk.blog/friends/matt.blog/" title="Matt on Not-WordPress" type="rss" xmlUrl="https://matt.blog/feed/?me=akirk.blog&#038;until=1764398848&#038;auth=$2y$12$hwATj8IIKltWpk3GJVPCnOEsgK8vbmk7mgReOzCaO8yh1kPWryaxa"/>
</outline>
<outline text="Subscriptions">
<outline text="Aaron Jorbin" htmlUrl="https://akirk.blog/friends/aaron-jorbin/" title="Aaron Jorbin" type="rss" xmlUrl="https://aaron.jorb.in/feed/"/>
<outline text="Adam Zielinski" htmlUrl="https://akirk.blog/friends/adam-zielinski/" title="Adam Zielinski" type="rss" xmlUrl="https://adamadam.blog/feed/"/>
<outline text="alex" htmlUrl="https://akirk.blog/friends/mastodon.local-users-alex/" title="alex" type="rss" xmlUrl="https://wpfriends.at/feed/"/>
<outline text="Alex Kirk" htmlUrl="https://akirk.blog/friends/alex-kirk/" title="Alex Kirk" type="rss" xmlUrl="https://alex.kirk.at/feed/"/>
<outline text="Andreas Klinger" htmlUrl="https://akirk.blog/friends/andreas-klinger/" title="Andreas Klinger" type="rss" xmlUrl="https://klinger.io/rss.xml"/>
<outline text="Barnaby Walters" htmlUrl="https://akirk.blog/friends/barnaby-walters/" title="Barnaby Walters" type="rss" xmlUrl="https://akirk.blog/friends/barnaby-walters/feed/"/>
<outline text="bengreeley.com" htmlUrl="https://akirk.blog/friends/bengreeley.com/" title="bengreeley.com" type="rss" xmlUrl="https://www.bengreeley.com/feed/"/>
<outline text="Chris Coyier" htmlUrl="https://akirk.blog/friends/chriscoyier.net/" title="Chris Coyier" type="rss" xmlUrl="https://chriscoyier.net/feed/"/>
<outline text="David Bisset" htmlUrl="https://akirk.blog/friends/davidbisset-phpc.social/" title="David Bisset" type="rss" xmlUrl="https://phpc.social/@davidbisset"/>
<outline text="Ingrid Brodnig" htmlUrl="https://akirk.blog/friends/mastodon.social-users-brodnig/" title="Ingrid Brodnig" type="rss" xmlUrl="https://mastodon.social/users/brodnig.rss"/>
<outline text="Jim Nielsen" htmlUrl="https://akirk.blog/friends/jim-nielsen.com/" title="Jim Nielsen" type="rss" xmlUrl="https://blog.jim-nielsen.com/feed.xml"/>
<outline text="Matt Mullenweg" htmlUrl="https://akirk.blog/friends/ma.tt/" title="Matt Mullenweg" type="rss" xmlUrl="https://ma.tt/feed/"/>
<outline text="mkln.org" htmlUrl="https://akirk.blog/friends/mkln.org/" title="mkln.org" type="rss" xmlUrl="https://mkln.org/feed"/>
<outline text="notiz.blog" htmlUrl="https://akirk.blog/friends/notiz.blog/" title="notiz.blog" type="rss" xmlUrl="https://notiz.blog/feed/"/>
<outline text="paolo.blog" htmlUrl="https://akirk.blog/friends/paolo.blog/" title="paolo.blog" type="rss" xmlUrl="https://paolo.blog/feed/"/>
<outline text="pfefferle" htmlUrl="https://akirk.blog/friends/pfefferle-mastodon-social/" title="pfefferle" type="rss" xmlUrl="https://mastodon-social/@pfefferle"/>
<outline text="restofworld.org" htmlUrl="https://akirk.blog/friends/restofworld.org/" title="restofworld.org" type="rss" xmlUrl="https://restofworld.org/feed/latest"/>
<outline text="simonwillison.net" htmlUrl="https://akirk.blog/friends/simonwillison.net/" title="simonwillison.net" type="atom" xmlUrl="https://simonwillison.net/atom/everything/"/>
<outline text="stratechery.com" htmlUrl="https://akirk.blog/friends/stratechery.com/" title="stratechery.com" type="rss" xmlUrl="https://stratechery.com/feed/"/>
<outline text="wptavern.com" htmlUrl="https://akirk.blog/friends/wptavern.com/" title="wptavern.com" type="rss" xmlUrl="https://wptavern.com/feed/"/>
<outline text="xkcd" htmlUrl="https://akirk.blog/friends/xkcd/" title="xkcd" type="rss" xmlUrl="https://xkcd.com/rss.xml"/>
</outline>
</body>
</opml>
28 changes: 28 additions & 0 deletions tests/test-feed.php
Original file line number Diff line number Diff line change
Expand Up @@ -678,4 +678,32 @@ public function test_podcast() {
$this->assertEquals( 'https://podcast.local/2022/10/episode-1/', $post->guid );
$this->assertStringContainsString( 'first-episode.mp3', $post->post_content );
}

public function test_import_feedland_opml() {
add_filter( 'friends_pre_check_url', '__return_true' );

$opml = file_get_contents( __DIR__ . '/data/feedland.opml' );
$feeds = Import::opml( $opml );
$users_created = count( $feeds );
$feeds_imported = 0;
foreach ( $feeds as $user => $user_feeds ) {
$feeds_imported += count( $user_feeds );
}
$this->assertEquals( 34, $users_created );
$this->assertEquals( 34, $feeds_imported );
}

public function test_import_friends_opml() {
add_filter( 'friends_pre_check_url', '__return_true' );

$opml = file_get_contents( __DIR__ . '/data/friends.opml' );
$feeds = Import::opml( $opml );
$users_created = count( $feeds );
$feeds_imported = 0;
foreach ( $feeds as $user => $user_feeds ) {
$feeds_imported += count( $user_feeds );
}
$this->assertEquals( 22, $users_created );
$this->assertEquals( 24, $feeds_imported );
}
}
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载