PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.9
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.9
1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 0.8.6 All 33 releases
desktop-mode / includes / about-feed.php

about-feed.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.9, at includes/about-feed.php

185 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — About-tab journal feed.
4 *
5 * Fetches the public OpenStation RSS feed on demand, normalizes it to
6 * plain-text card data, and serves it through an authenticated admin-AJAX
7 * request. Keeping the request lazy means opening the shell never waits on
8 * the remote blog; the network is touched only when someone visits About.
9 *
10 * @package OpenStation
11 */
12
13 defined( 'ABSPATH' ) || exit;
14
15 const OPENSTATION_ABOUT_FEED_URL = 'https://openstation.blog/feed/';
16 const OPENSTATION_ABOUT_SITE_URL = 'https://openstation.blog/';
17 const OPENSTATION_ABOUT_FEED_CACHE_KEY = 'desktop_mode_about_feed_v1';
18 const OPENSTATION_ABOUT_FEED_STALE_KEY = 'desktop_mode_about_feed_stale_v1';
19 const OPENSTATION_ABOUT_FEED_FAILURE_KEY = 'desktop_mode_about_feed_failure_v1';
20
21 /**
22 * Give this one feed a shorter cache than WordPress' default 12 hours.
23 *
24 * The callback is installed only around our own `fetch_feed()` call and
25 * removed immediately afterwards, so dashboard RSS widgets keep their own
26 * cache policy.
27 *
28 * @param int $seconds Existing cache lifetime.
29 * @param string $url Feed URL.
30 * @return int Cache lifetime in seconds.
31 */
32 function openstation_about_feed_cache_lifetime( $seconds, $url ) {
33 return OPENSTATION_ABOUT_FEED_URL === $url ? 30 * MINUTE_IN_SECONDS : $seconds;
34 }
35
36 /**
37 * Bound the cold-feed request so an unavailable journal cannot pin the tab.
38 *
39 * @param SimplePie $feed Feed parser instance.
40 * @param string $url Feed URL.
41 */
42 function openstation_about_feed_options( $feed, $url ) {
43 if ( OPENSTATION_ABOUT_FEED_URL === $url && is_callable( array( $feed, 'set_timeout' ) ) ) {
44 $feed->set_timeout( 5 );
45 }
46 }
47
48 /**
49 * Collapse remote feed text to one safe, readable line.
50 *
51 * @param mixed $value Remote feed value.
52 * @return string Plain text.
53 */
54 function openstation_about_feed_text( $value ) {
55 $text = html_entity_decode(
56 wp_strip_all_tags( (string) $value, true ),
57 ENT_QUOTES | ENT_HTML5,
58 'UTF-8'
59 );
60 $text = preg_replace( '/\s+/u', ' ', $text );
61 return sanitize_text_field( trim( (string) $text ) );
62 }
63
64 /**
65 * Convert a parsed SimplePie feed into the small JSON shape the About tab uses.
66 *
67 * No remote HTML crosses the boundary: titles, author names and excerpts are
68 * flattened to text, URLs pass through WordPress' URL sanitizer, and the list
69 * is capped before it reaches the browser.
70 *
71 * @param SimplePie $feed Parsed feed instance.
72 * @return array Normalized feed payload.
73 */
74 function openstation_normalize_about_feed( $feed ) {
75 $items = array();
76 $feed_items = $feed->get_items( 0, 5 );
77
78 foreach ( $feed_items as $item ) {
79 $title = openstation_about_feed_text( $item->get_title() );
80 $url = esc_url_raw( (string) $item->get_permalink() );
81 if ( '' === $title || '' === $url ) {
82 continue;
83 }
84
85 $author = $item->get_author();
86 $author_name = $author ? openstation_about_feed_text( $author->get_name() ) : '';
87 $excerpt = openstation_about_feed_text( $item->get_description() );
88
89 $items[] = array(
90 'title' => $title,
91 'url' => $url,
92 'author' => $author_name,
93 'publishedAt' => sanitize_text_field( (string) $item->get_date( 'c' ) ),
94 'excerpt' => wp_trim_words( $excerpt, 36, '' ),
95 );
96 }
97
98 $title = openstation_about_feed_text( $feed->get_title() );
99 $description = openstation_about_feed_text( $feed->get_description() );
100 $home_url = esc_url_raw( (string) $feed->get_link() );
101
102 return array(
103 'title' => '' !== $title ? $title : 'OpenStation',
104 'description' => '' !== $description
105 ? $description
106 : 'A public dev diary — building a desktop OS for wp-admin.',
107 'homeUrl' => '' !== $home_url ? $home_url : OPENSTATION_ABOUT_SITE_URL,
108 'feedUrl' => OPENSTATION_ABOUT_FEED_URL,
109 'items' => $items,
110 'stale' => false,
111 );
112 }
113
114 /**
115 * Return the cached journal payload, fetching RSS only when the cache is cold.
116 *
117 * A last-known-good copy survives for a week. If the blog has a temporary
118 * outage, the About tab can still show useful posts and quietly mark the data
119 * stale instead of replacing the whole page with an error.
120 *
121 * @return array|WP_Error Feed payload or a private fetch error.
122 */
123 function openstation_get_about_feed() {
124 $cached = get_transient( OPENSTATION_ABOUT_FEED_CACHE_KEY );
125 if ( is_array( $cached ) ) {
126 return $cached;
127 }
128
129 $stale = get_transient( OPENSTATION_ABOUT_FEED_STALE_KEY );
130 if ( false !== get_transient( OPENSTATION_ABOUT_FEED_FAILURE_KEY ) ) {
131 if ( is_array( $stale ) ) {
132 $stale['stale'] = true;
133 return $stale;
134 }
135 return new WP_Error( 'openstation_about_feed_unavailable' );
136 }
137
138 require_once ABSPATH . WPINC . '/feed.php';
139 add_filter( 'wp_feed_cache_transient_lifetime', 'openstation_about_feed_cache_lifetime', 10, 2 );
140 add_action( 'wp_feed_options', 'openstation_about_feed_options', 10, 2 );
141 $feed = fetch_feed( OPENSTATION_ABOUT_FEED_URL );
142 remove_filter( 'wp_feed_cache_transient_lifetime', 'openstation_about_feed_cache_lifetime', 10 );
143 remove_action( 'wp_feed_options', 'openstation_about_feed_options', 10 );
144
145 if ( is_wp_error( $feed ) ) {
146 set_transient( OPENSTATION_ABOUT_FEED_FAILURE_KEY, '1', 5 * MINUTE_IN_SECONDS );
147 if ( is_array( $stale ) ) {
148 $stale['stale'] = true;
149 return $stale;
150 }
151 return new WP_Error( 'openstation_about_feed_unavailable' );
152 }
153
154 $payload = openstation_normalize_about_feed( $feed );
155 set_transient( OPENSTATION_ABOUT_FEED_CACHE_KEY, $payload, 30 * MINUTE_IN_SECONDS );
156 set_transient( OPENSTATION_ABOUT_FEED_STALE_KEY, $payload, WEEK_IN_SECONDS );
157 delete_transient( OPENSTATION_ABOUT_FEED_FAILURE_KEY );
158 return $payload;
159 }
160
161 /**
162 * Serve the latest OpenStation journal posts to the current shell user.
163 */
164 function openstation_ajax_about_feed() {
165 check_ajax_referer( 'openstation_about_feed', 'nonce' );
166
167 if ( ! current_user_can( 'read' ) || ! openstation_is_enabled() ) {
168 wp_send_json_error(
169 array( 'message' => __( 'You cannot load OpenStation updates.', 'desktop-mode' ) ),
170 403
171 );
172 }
173
174 $feed = openstation_get_about_feed();
175 if ( is_wp_error( $feed ) ) {
176 wp_send_json_error(
177 array( 'message' => __( 'The OpenStation journal is temporarily unavailable.', 'desktop-mode' ) ),
178 502
179 );
180 }
181
182 wp_send_json_success( $feed );
183 }
184 add_action( 'wp_ajax_openstation_about_feed', 'openstation_ajax_about_feed' );
185