Skip to content

Commit 9c7b286

Browse files
committed
Add WordPress core feeds adapter and PHP 8 polyfill
Introduces a new WordPress adapter to provide read-only channels for WordPress news and events feeds. Adds a compatibility shim for str_starts_with for PHP < 8.0. Registers the adapter and includes the new compat file in the plugin bootstrap.
1 parent 16d5633 commit 9c7b286

4 files changed

Lines changed: 231 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
/**
3+
* WordPress Core Feeds Adapter for Microsub.
4+
*
5+
* @package Microsub
6+
*/
7+
8+
namespace Microsub\Adapters;
9+
10+
use Microsub\Adapter;
11+
12+
/**
13+
* WordPress Core Adapter.
14+
*
15+
* Provides read-only channels for core WordPress news and events feeds.
16+
*/
17+
class WordPress extends Adapter {
18+
19+
/**
20+
* Adapter identifier.
21+
*
22+
* @var string
23+
*/
24+
protected $id = 'wordpress';
25+
26+
/**
27+
* Adapter name.
28+
*
29+
* @var string
30+
*/
31+
protected $name = 'WordPress Core';
32+
33+
/**
34+
* News feed URL.
35+
*
36+
* @var string
37+
*/
38+
protected $news_feed = 'https://wordpress.org/news/feed/';
39+
40+
/**
41+
* Get list of channels.
42+
*
43+
* @param array $channels Current channels array from other adapters.
44+
* @param int $user_id The user ID.
45+
* @return array Array of channels with 'uid' and 'name'.
46+
*/
47+
public function get_channels( $channels, $user_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
48+
$channels[] = array(
49+
'uid' => 'wp-dashboard',
50+
'name' => \__( 'WordPress Events and News', 'microsub' ),
51+
);
52+
53+
$channels[] = array(
54+
'uid' => 'wp-news',
55+
'name' => \__( 'WordPress News', 'microsub' ),
56+
);
57+
58+
return $channels;
59+
}
60+
61+
/**
62+
* Get timeline entries for a channel.
63+
*
64+
* @param array $result Current result with 'items' from other adapters.
65+
* @param string $channel Channel UID.
66+
* @param array $args Query arguments (after, before, limit).
67+
* @return array Timeline data with 'items' and optional 'paging'.
68+
*/
69+
public function get_timeline( $result, $channel, $args ) {
70+
if ( 'wp-dashboard' !== $channel && 'wp-news' !== $channel ) {
71+
return $result;
72+
}
73+
74+
$limit = isset( $args['limit'] ) ? \absint( $args['limit'] ) : 20;
75+
76+
$news_items = $this->get_news_items( $limit );
77+
78+
if ( 'wp-news' === $channel ) {
79+
$result['items'] = \array_merge( $result['items'], $news_items );
80+
return $result;
81+
}
82+
83+
$events = $this->get_events_items( $limit );
84+
$combined = \array_merge( $news_items, $events );
85+
$combined = $this->sort_items_by_date( $combined );
86+
$result['items'] = \array_merge( $result['items'], \array_slice( $combined, 0, $limit ) );
87+
88+
return $result;
89+
}
90+
91+
/**
92+
* Get list of followed feeds (none for core channels).
93+
*
94+
* @param array $result Current result from other adapters.
95+
* @param string $channel Channel UID.
96+
* @param int $user_id The user ID.
97+
* @return array Array of feed objects with 'type' and 'url'.
98+
*/
99+
public function get_following( $result, $channel, $user_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
100+
// These channels are read-only; nothing to follow.
101+
return $result;
102+
}
103+
104+
/**
105+
* Read WordPress.org news feed items.
106+
*
107+
* @param int $limit Maximum items to return.
108+
* @return array
109+
*/
110+
protected function get_news_items( $limit ) {
111+
if ( ! \function_exists( 'fetch_feed' ) ) {
112+
require_once ABSPATH . WPINC . '/feed.php';
113+
}
114+
115+
$feed = \fetch_feed( $this->news_feed );
116+
117+
if ( \is_wp_error( $feed ) ) {
118+
return array();
119+
}
120+
121+
$max = $feed->get_item_quantity( $limit );
122+
$items = $feed->get_items( 0, $max );
123+
$list = array();
124+
125+
foreach ( $items as $item ) {
126+
$url = $item->get_permalink();
127+
$content = $item->get_content();
128+
$list[] = array(
129+
'type' => 'entry',
130+
'_id' => 'news-' . \md5( $url ),
131+
'name' => $item->get_title(),
132+
'url' => $url,
133+
'published' => $item->get_date( \DATE_ATOM ),
134+
'content' => array(
135+
'html' => $content,
136+
'text' => \wp_strip_all_tags( $content ),
137+
),
138+
);
139+
}
140+
141+
return $list;
142+
}
143+
144+
/**
145+
* Read WordPress community events.
146+
*
147+
* @param int $limit Maximum items to return.
148+
* @return array
149+
*/
150+
protected function get_events_items( $limit ) {
151+
if ( ! \function_exists( 'wp_get_community_events' ) ) {
152+
require_once ABSPATH . 'wp-admin/includes/dashboard.php';
153+
}
154+
155+
if ( ! \function_exists( 'wp_get_community_events' ) ) {
156+
return array();
157+
}
158+
159+
$events_response = \wp_get_community_events(
160+
array(
161+
'number' => $limit,
162+
)
163+
);
164+
165+
if ( \is_wp_error( $events_response ) || empty( $events_response['events'] ) ) {
166+
return array();
167+
}
168+
169+
$items = array();
170+
171+
foreach ( $events_response['events'] as $event ) {
172+
$url = $event['url'] ?? '';
173+
$time = $event['date'] ?? ( $event['start'] ?? '' );
174+
$items[] = array(
175+
'type' => 'entry',
176+
'_id' => 'event-' . \md5( $url ?: ( $event['title'] ?? '' ) . $time ),
177+
'name' => $event['title'] ?? '',
178+
'url' => $url,
179+
'published' => $time,
180+
'content' => array(
181+
'text' => $event['description'] ?? '',
182+
),
183+
);
184+
}
185+
186+
return $items;
187+
}
188+
189+
/**
190+
* Sort items by published date (newest first).
191+
*
192+
* @param array $items Items to sort.
193+
* @return array
194+
*/
195+
protected function sort_items_by_date( $items ) {
196+
\usort(
197+
$items,
198+
function ( $a, $b ) {
199+
$date_a = isset( $a['published'] ) ? \strtotime( $a['published'] ) : 0;
200+
$date_b = isset( $b['published'] ) ? \strtotime( $b['published'] ) : 0;
201+
return $date_b - $date_a;
202+
}
203+
);
204+
205+
return $items;
206+
}
207+
}

includes/class-microsub.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ public function register_hooks() {
8989
* Register built-in adapters.
9090
*/
9191
public function register_adapters() {
92+
// Register WordPress core feeds adapter.
93+
$adapter = new Adapters\WordPress();
94+
$adapter->register();
95+
9296
// Register Friends adapter if Friends plugin is installed.
9397
if ( \defined( 'FRIENDS_VERSION' ) ) {
9498
$adapter = new Adapters\Friends();

includes/compat.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
/**
3+
* Compatibility shims.
4+
*
5+
* @package Microsub
6+
*/
7+
8+
if ( ! function_exists( 'str_starts_with' ) ) {
9+
/**
10+
* Polyfill for str_starts_with for PHP < 8.0.
11+
*
12+
* @param string $haystack Full string.
13+
* @param string $needle Prefix to check.
14+
* @return bool Whether the string starts with the prefix.
15+
*/
16+
function str_starts_with( $haystack, $needle ) {
17+
return 0 === strncmp( $haystack, $needle, strlen( $needle ) );
18+
}
19+
}

microsub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
\define( 'MICROSUB_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
2929

3030
require_once __DIR__ . '/includes/class-autoloader.php';
31+
require_once __DIR__ . '/includes/compat.php';
3132

3233
// Register the autoloader.
3334
Autoloader::register_path( __NAMESPACE__, __DIR__ . '/includes' );

0 commit comments

Comments
 (0)