PuSHPress - Version 0.1.7.2

Version Description

  • Make sure to only output the hub information in feeds
  • Bump tested value up to 3.6
Download this release

Release Info

Developer josephscott
Plugin Icon wp plugin PuSHPress
Version 0.1.7.2
Comparing to
See all releases

Version 0.1.7.2

Files changed (4) hide show
  1. class-pushpress.php +326 -0
  2. pushpress.php +22 -0
  3. readme.txt +77 -0
  4. send-ping.php +97 -0
class-pushpress.php ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class PuSHPress {
3
+ var $http_timeout;
4
+ var $http_user_agent;
5
+
6
+ function PuSHPress( ) { }
7
+
8
+ function init( ) {
9
+ // Let other plugins modify various options
10
+ $this->http_timeout = apply_filters( 'pushpress_http_timeout', 5 );
11
+ $this->http_user_agent = apply_filters( 'pushpress_http_timeout', 'WordPress/PuSHPress ' . PUSHPRESS_VERSION );
12
+
13
+ // Make sure the hubs get listed in the RSS2 and Atom feeds
14
+ add_action( 'rss2_head', array( &$this, 'hub_link_rss2' ) );
15
+ add_action( 'atom_head', array( &$this, 'hub_link_atom' ) );
16
+
17
+ // Make the built in hub URL work
18
+ add_action( 'parse_request', array( &$this, 'parse_wp_request' ) );
19
+
20
+ // Send out fat pings when a new post is published
21
+ add_action( 'publish_post', array( &$this, 'publish_post' ) );
22
+ }
23
+
24
+
25
+
26
+
27
+ function add_callback( $feed_url, $callback, $secret ) {
28
+ $subs = get_option( 'pushpress_subscribers' );
29
+ $subs[$feed_url][$callback] = array(
30
+ 'is_active' => TRUE,
31
+ 'secret' => $secret,
32
+ 'start_date' => gmdate( 'Y-m-d H:i:s' ),
33
+ 'unsubscribe' => FALSE,
34
+ );
35
+ update_option( 'pushpress_subscribers', $subs );
36
+ }
37
+
38
+ function check_required_fields( ) {
39
+ // required fields must have values
40
+ if ( empty( $_POST['hub_callback'] ) )
41
+ $this->return_error( 'hub.callback is empty' );
42
+
43
+ if ( empty( $_POST['hub_mode'] ) )
44
+ $this->return_error( 'hub.mode is empty' );
45
+
46
+ if ( empty( $_POST['hub_topic'] ) )
47
+ $this->return_error( 'hub.topic is empty' );
48
+
49
+ if ( empty( $_POST['hub_verify'] ) )
50
+ $this->return_error( 'hub.verify is empty' );
51
+
52
+ // mode has a small set of valid values
53
+ $_POST['hub_mode'] = strtolower( $_POST['hub_mode'] );
54
+ if ( $_POST['hub_mode'] != 'subscribe' ) {
55
+ if ( $_POST['hub_mode'] != 'unsubscribe' ) {
56
+ $this->return_error( 'hub.mode is invalid' );
57
+ }
58
+ }
59
+ }
60
+
61
+ // removes scheme, sets up URLs to be prefix matched
62
+ function normalize_url( $url ) {
63
+ if ( is_array( $url ) )
64
+ return array_map( array( $this, __FUNCTION__ ), $url );
65
+
66
+ $url = preg_replace( '#^https?://#', '', $url );
67
+
68
+ // To normalize query params, we would normally need to sort them.
69
+ // The spec says order matters, though. That makes it easier for us.
70
+ @list( $path, $query ) = explode( '?', $url );
71
+ $query = empty( $query ) ? '' : rtrim( $query, '&' ) . '&';
72
+ return rtrim( $path, '/' ) . "/?$query";
73
+ }
74
+
75
+ function feed_urls() {
76
+ return array(
77
+ get_bloginfo( 'rss2_url' ),
78
+ get_bloginfo( 'atom_url' ),
79
+ );
80
+ }
81
+
82
+ function check_topic( ) {
83
+ $allowed = FALSE;
84
+
85
+ $feed_urls = $this->feed_urls();
86
+
87
+ $allowed_bases = $this->normalize_url( $feed_urls );
88
+ $check_hub_topic = $this->normalize_url( trim( stripslashes( $_POST['hub_topic'] ) ) );
89
+
90
+ foreach ( $allowed_bases as $k => $allowed_base ) {
91
+ if ( $check_hub_topic == $allowed_base ) {
92
+ $allowed = $feed_urls[$k];
93
+ break;
94
+ }
95
+ }
96
+
97
+ if ( $allowed === FALSE ) {
98
+ do_action( 'pushpress_topic_failure' );
99
+ if ( is_ssl() ) {
100
+ foreach ( $feed_urls as $k => $url )
101
+ $feed_urls[$k] = str_replace( 'https://', 'http://', $url );
102
+ }
103
+
104
+ $msg = 'hub_topic - ' . $_POST['hub_topic'];
105
+ $msg .= ' - is value is not allowed. ';
106
+ $msg .= 'You may only subscribe to ' . $feed_urls[0];
107
+ $msg .= ' or ' . $feed_urls[1];
108
+
109
+ $this->return_error( $msg );
110
+ }
111
+
112
+ return $allowed;
113
+ }
114
+
115
+ function get_subscribers( $feed_url ) {
116
+ /*
117
+ array(
118
+ _rss_url_ => array(
119
+ _callback_url_ => array(
120
+ 'is_active' => true/false,
121
+ 'secret' => _string_,
122
+ 'start_date' => _date_gmt_,
123
+ 'unsubscribe' => true/false
124
+ )
125
+ ),
126
+ _atom_url_ => array(
127
+ _callback_url_ => array(
128
+ 'is_active' => true/false,
129
+ 'secret' => _string_,
130
+ 'start_date' => _date_gmt_,
131
+ 'unsubscribe' => true/false
132
+ )
133
+ )
134
+ )
135
+ */
136
+
137
+ $subs = get_option( 'pushpress_subscribers' );
138
+
139
+ if ( isset( $subs[$feed_url] ) ) {
140
+ return $subs[$feed_url];
141
+ } else {
142
+ return FALSE;
143
+ }
144
+ }
145
+
146
+ function hub_link_atom( ) {
147
+ $hubs = apply_filters( 'pushpress_hubs', array(
148
+ get_bloginfo( 'url' ) . '/?pushpress=hub'
149
+ ) );
150
+
151
+ foreach ( (array) $hubs as $hub ) {
152
+ echo "\t<link rel='hub' href='{$hub}' />\n";
153
+ }
154
+ }
155
+
156
+ function hub_link_rss2( ) {
157
+ if ( is_feed() ) {
158
+ $hubs = apply_filters( 'pushpress_hubs', array(
159
+ get_bloginfo( 'url' ) . '/?pushpress=hub'
160
+ ) );
161
+
162
+ foreach ( (array) $hubs as $hub ) {
163
+ echo "\t<atom:link rel='hub' href='{$hub}'/>\n";
164
+ }
165
+ }
166
+ }
167
+
168
+ function hub_request( ) {
169
+ $this->check_required_fields( );
170
+ $this->check_topic( );
171
+
172
+ if ( $_POST['hub_mode'] == 'unsubscribe' ) {
173
+ do_action( 'pushpress_unsubscribe_request' );
174
+ $this->verify_request( );
175
+ $this->unsubscribe_callback( $_POST['hub_topic'], $_POST['hub_callback'] );
176
+ $this->return_ok( );
177
+ }
178
+
179
+ if ( $_POST['hub_mode'] == 'subscribe' ) {
180
+ do_action( 'pushpress_subscribe_request' );
181
+ $this->verify_request( );
182
+ }
183
+
184
+ $subs = $this->get_subscribers( $_POST['hub_topic'] );
185
+
186
+ $secret = '';
187
+ if ( !empty( $_POST['hub_secret'] ) )
188
+ $secret = $_POST['hub_secret'];
189
+
190
+ $this->add_callback( $_POST['hub_topic'], $_POST['hub_callback'], $secret );
191
+ $this->return_ok( );
192
+ }
193
+
194
+ function parse_wp_request( $wp ) {
195
+ if ( !empty( $_GET['pushpress'] ) && $_GET['pushpress'] == 'hub' ) {
196
+ $this->hub_request( );
197
+ exit;
198
+ }
199
+ }
200
+
201
+ function publish_post( $post_id ) {
202
+ $subs = $this->get_subscribers( get_bloginfo( 'rss2_url' ) );
203
+ foreach ( (array) $subs as $callback => $data ) {
204
+ if ( $data['is_active'] == FALSE )
205
+ continue;
206
+
207
+ $this->schedule_ping( $callback, $post_id, 'rss2', $data['secret'] );
208
+ }
209
+
210
+ $subs = $this->get_subscribers( get_bloginfo( 'atom_url' ) );
211
+ foreach ( (array) $subs as $callback => $data ) {
212
+ if ( $data['is_active'] == FALSE )
213
+ continue;
214
+
215
+ $this->schedule_ping( $callback, $post_id, 'atom', $data['secret'] );
216
+ }
217
+ }
218
+
219
+ function return_ok( ) {
220
+ header( 'Content-Type: text/plain; charset=utf-8' );
221
+ header( 'HTTP/1.0 204 No Content' );
222
+ exit;
223
+ }
224
+
225
+ function return_error( $msg ) {
226
+ header( 'Content-Type: text/plain; charset=utf-8' );
227
+ header( 'HTTP/1.0 400 Bad Request' );
228
+ echo $msg;
229
+ exit;
230
+ }
231
+
232
+ function schedule_ping( $callback, $post_id, $feed_type, $secret ) {
233
+ wp_schedule_single_event(
234
+ time( ) - 1,
235
+ 'pushpress_scheduled_ping',
236
+ array( $callback, $post_id, $feed_type, $secret )
237
+ );
238
+ }
239
+
240
+ function unsubscribe_callback( $feed_url, $callback ) {
241
+ $subs = get_option( 'pushpress_subscribers' );
242
+ $subs[$feed_url][$callback]['is_active'] = FALSE;
243
+ $subs[$feed_url][$callback]['unsubscribe'] = TRUE;
244
+ update_option( 'pushpress_subscribers', $subs );
245
+ }
246
+
247
+ function verify_request( ) {
248
+ $challenge = uniqid( mt_rand( ), TRUE );
249
+ $challenge .= uniqid( mt_rand( ), TRUE );
250
+
251
+ $hub_vars = 'hub.lease_seconds=315360000'; // 10 years
252
+ $hub_vars .= '&hub.mode=' . urlencode( $_POST['hub_mode'] );
253
+ $hub_vars .= '&hub.topic=' . urlencode( $_POST['hub_topic'] );
254
+ $hub_vars .= '&hub.challenge=' . urlencode( $challenge );
255
+
256
+ if ( !empty( $_POST['hub_verify_token'] ) ) {
257
+ do_action( 'pushpress_include_verify_token' );
258
+ $hub_vars .= '&hub.verify_token=';
259
+ $hub_vars .= urlencode( $_POST['hub_verify_token'] );
260
+ }
261
+
262
+ $callback = parse_url( $_POST['hub_callback'] );
263
+
264
+ $url = $callback['scheme'] . '://';
265
+
266
+ if ( !empty( $callback['user'] ) ) {
267
+ $url .= $callback['user'];
268
+
269
+ if ( !empty( $callback['pass'] ) )
270
+ $url .= ':' . $callback['pass'];
271
+
272
+ $url .= '@';
273
+ }
274
+
275
+ $url .= $callback['host'];
276
+
277
+ $port = 80;
278
+ if ( !empty( $callback['port'] ) )
279
+ $port = (int) $callback['port'];
280
+
281
+ if ( $callback['scheme'] == 'https' )
282
+ $port = 443;
283
+
284
+ $url .= ':' . $port;
285
+
286
+ $path = '/';
287
+ if ( !empty( $callback['path'] ) ) {
288
+ $path = str_replace( '@', '', $callback['path'] );
289
+ if ( $path{0} != '/' ) {
290
+ $path = '/' . $path;
291
+ }
292
+
293
+ $url .= $path;
294
+ }
295
+
296
+ if ( !empty( $callback['query'] ) ) {
297
+ $url .= '?' . $callback['query'];
298
+ $url .= '&' . $hub_vars;
299
+ } else {
300
+ $url .= '?' . $hub_vars;
301
+ }
302
+
303
+ $response = wp_remote_get( $url, array(
304
+ 'sslverify' => FALSE,
305
+ 'timeout' => $this->http_timeout,
306
+ 'user-agent' => $this->http_user_agent,
307
+ ) );
308
+
309
+ // look for failure indicators
310
+ if ( is_wp_error( $response ) ) {
311
+ do_action( 'pushpress_verify_http_failure' );
312
+ $this->return_error('"Error verifying callback URL - ' . $response->get_error_message() );
313
+ }
314
+
315
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
316
+ if ( $status_code < 200 || $status_code > 299 ) {
317
+ do_action( 'pushpress_verify_not_2xx_failure' );
318
+ $this->return_error( "Error verifying callback URL, HTTP status code: {$status_code}" );
319
+ }
320
+
321
+ if ( trim( wp_remote_retrieve_body( $response ) ) != $challenge ) {
322
+ do_action( 'pushpress_verify_challenge_failure' );
323
+ $this->return_error( 'Error verifying callback URL, the challenge token did not match' );
324
+ }
325
+ } // function verify_request
326
+ } // class PuSHPress
pushpress.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: PuSHPress
4
+ Plugin URI:
5
+ Description: PubSubHubbub plugin for WordPress that includes the hub
6
+ Version: 0.1.7.2
7
+ Author: Joseph Scott
8
+ Author URI:
9
+ License: GPLv2
10
+ Site Wide Only: true
11
+ */
12
+ require_once dirname( __FILE__ ) . '/class-pushpress.php';
13
+ require_once dirname( __FILE__ ) . '/send-ping.php';
14
+
15
+ define( 'PUSHPRESS_VERSION', '0.1.7.2' );
16
+
17
+ if ( !defined( 'PUSHPRESS_CLASS' ) )
18
+ define( 'PUSHPRESS_CLASS', 'PuSHPress' );
19
+
20
+ $pushpress_class = PUSHPRESS_CLASS;
21
+ $pushpress = new $pushpress_class( );
22
+ $pushpress->init( );
readme.txt ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Plugin Name ===
2
+ Contributors: josephscott, automattic, westi
3
+ Plugin Name: PushPress
4
+ Tags: pubsubhubbub, push, WordPress.com
5
+ Requires at least: 2.9
6
+ Tested up to: 4.1
7
+ License: GPLv2
8
+
9
+ Add PubSubHubbub support to your WordPress site, with a built in hub.
10
+
11
+ == Description ==
12
+
13
+ This plugin adds PubSubHubbub ( PuSH ) support to your WordPress powered site. The main difference between this plugin and others is that it includes the hub features of PuSH, built right in. This means the updates will be sent directly from WordPress to your PuSH subscribers.
14
+
15
+ == Installation ==
16
+
17
+ 1. Upload `pushpress.zip` to your plugins directory ( usally `/wp-content/plugins/` )
18
+ 2. Unzip the `pushpress.zip` file
19
+ 3. Activate the plugin through the 'Plugins' menu in WordPress
20
+
21
+ == Frequently Asked Question ==
22
+
23
+ = How is this plugin different from other PubSubHubbub plugins? =
24
+
25
+ Other plugins use 3rd party hubs to relay updates out to subscribers. This plugin has a built in hub, allowing WordPress to send out the updates directly.
26
+
27
+ = Is there anything to configure? =
28
+
29
+ No, once the plugin is activated it takes care of the rest.
30
+
31
+ == Changelog ==
32
+
33
+ = 0.1.7.2 =
34
+ * Make sure to only output the hub information in feeds
35
+ * Bump tested value up to 3.6
36
+
37
+ = 0.1.7.1 =
38
+ * Use get_error_message() from the WP HTTP API ( Andrew Nacin )
39
+ * Bump tested value up to 3.2
40
+
41
+ = 0.1.7 =
42
+ * Fix typo during error handling (reported by John Godley)
43
+ * Improve HTTP error detection ( Andrew Nacin )
44
+ * Make sure the channel title in pings matches the channel title in the regular feed ( reported by Hugo Hallqvist )
45
+ * Normalize feed URLs ( Mike Adams )
46
+ * Make sure the WP environment has no one logged in when querying post data for pings ( Mike Adams )
47
+
48
+ = 0.1.6 =
49
+ * Force enclosure processing to happen before sending out a ping
50
+ * Make the plugin site wide for WPMU/multi-site installs
51
+
52
+ = 0.1.5 =
53
+ * When sending out pings we need to make sure that the PuSHPress
54
+ options have been initialized
55
+ * Apply the hub array filter later in the process, as part of
56
+ the feed head filter
57
+ * Verify unsubscribe requests (noticed by James Holderness)
58
+
59
+ = 0.1.4 =
60
+ * Be more flexible dealing with trailing slash vs. no trailing slash
61
+
62
+ = 0.1.3 =
63
+ * Suspend should really be unsubscribe
64
+
65
+ = 0.1.2 =
66
+ * Look for WP_Error being returned when sending a ping
67
+
68
+ = 0.1.1 =
69
+ * Initial release
70
+
71
+ == Upgrade Notice ==
72
+
73
+ = 0.1.2 =
74
+ Improved error checking
75
+
76
+ = 0.1.1 =
77
+ New PubSubHubbub plugin
send-ping.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ add_action( 'pushpress_scheduled_ping', 'pushpress_send_ping', 10, 4 );
3
+ if ( !function_exists( 'pushpress_send_ping' ) ) {
4
+ function pushpress_send_ping( $callback, $post_id, $feed_type, $secret ) {
5
+ global $pushpress, $current_user;
6
+
7
+ // Do all WP_Query calcs and send feeds as logged-out user.
8
+ $old_user_id = $current_user->ID;
9
+ wp_set_current_user( 0 );
10
+
11
+ // Need to make sure that the PuSHPress options are initialized
12
+ $pushpress->init( );
13
+
14
+ do_action( 'pushpress_send_ping' );
15
+
16
+ $remote_opt = array(
17
+ 'headers' => array(
18
+ 'format' => $feed_type
19
+ ),
20
+ 'sslverify' => FALSE,
21
+ 'timeout' => $pushpress->http_timeout,
22
+ 'user-agent' => $pushpress->http_user_agent
23
+ );
24
+
25
+ $post = get_post( $post_id );
26
+ $post_status_obj = get_post_status_object( $post->post_status );
27
+ if ( !$post_status_obj->public ) {
28
+ do_action( 'pushpress_nonpublic_post', $post_id );
29
+ wp_set_current_user( $old_user_id );
30
+ return false;
31
+ }
32
+ do_enclose( $post->post_content, $post_id );
33
+ update_postmeta_cache( array( $post_id ) );
34
+
35
+ // make sure the channel title stays consistent
36
+ // without this it would append the post title as well
37
+ add_filter( 'wp_title', '__return_false', 999 );
38
+
39
+ query_posts( "p={$post_id}" );
40
+ ob_start( );
41
+
42
+ $feed_url = FALSE;
43
+ if ( $feed_type == 'rss2' ) {
44
+ do_action( 'pushpress_send_ping_rss2' );
45
+ $feed_url = get_bloginfo( 'rss2_url' );
46
+
47
+ $remote_opt['headers']['Content-Type'] = 'application/rss+xml';
48
+ $remote_opt['headers']['Content-Type'] .= '; charset=' . get_option( 'blog_charset' );
49
+
50
+ @load_template( ABSPATH . WPINC . '/feed-rss2.php' );
51
+ } elseif ( $feed_type == 'atom' ) {
52
+ do_action( 'pushpress_send_ping_atom' );
53
+ $feed_url = get_bloginfo( 'atom_url' );
54
+
55
+ $remote_opt['headers']['Content-Type'] = 'application/atom+xml';
56
+ $remote_opt['headers']['Content-Type'] .= '; charset=' . get_option( 'blog_charset' );
57
+
58
+ @load_template( ABSPATH . WPINC . '/feed-atom.php' );
59
+ }
60
+
61
+ $remote_opt['body'] = ob_get_contents( );
62
+ ob_end_clean( );
63
+
64
+ // Figure out the signatur header if we have a secret on
65
+ // on file for this callback
66
+ if ( !empty( $secret ) ) {
67
+ $remote_opt['headers']['X-Hub-Signature'] = 'sha1=' . hash_hmac(
68
+ 'sha1', $remote_opt['body'], $secret
69
+ );
70
+ }
71
+
72
+ $response = wp_remote_post( $callback, $remote_opt );
73
+
74
+ // look for failures
75
+ if ( is_wp_error( $response ) ) {
76
+ do_action( 'pushpress_ping_wp_error' );
77
+ wp_set_current_user( $old_user_id );
78
+ return FALSE;
79
+ }
80
+
81
+ if ( isset( $response->errors['http_request_failed'][0] ) ) {
82
+ do_action( 'pushpress_ping_http_failure' );
83
+ wp_set_current_user( $old_user_id );
84
+ return FALSE;
85
+ }
86
+
87
+ $status_code = (int) $response['response']['code'];
88
+ if ( $status_code < 200 || $status_code > 299 ) {
89
+ do_action( 'pushpress_ping_not_2xx_failure' );
90
+ $pushpress->unsubscribe_callback( $feed_url, $callback );
91
+ wp_set_current_user( $old_user_id );
92
+ return FALSE;
93
+ }
94
+
95
+ wp_set_current_user( $old_user_id );
96
+ } // function send_ping
97
+ } // if !function_exists