Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 355 lines (276 sloc) 11.227 kb
8ff4206 Mohammad Jangda v1
mjangda authored
1 <?php
2 /**
3 * Plugin Name: Liveblog
4 * Description: Blogging: at the speed of live.
5 * Version: 0.1
6 * Author: WordPress.com VIP, Automattic
7 */
8
9 /*
10 TODO (0.1):
b12c4b9 Mohammad Jangda Adding inline comments and TODOs
mjangda authored
11 -- Fix Batcache issues
8ff4206 Mohammad Jangda v1
mjangda authored
12
13 TODO (future):
b12c4b9 Mohammad Jangda Adding inline comments and TODOs
mjangda authored
14 -- PHP and JS Actions/Filters/Triggers
15 -- Change "Read More" to "View Liveblog"
8ff4206 Mohammad Jangda v1
mjangda authored
16 -- Manual refresh button
17 -- Allow marking of liveblog as ended
18 -- Allow comment modifications; need to store modified date as comment_meta
19 -- Drag-and-drop image uploading support
20
21 */
22
675af15 Nikolay Bachiyski Move WPCOM_Liveblog_Entry to a separate file
nb authored
23 require dirname( __FILE__ ) . '/class-wpcom-liveblog-entry.php';
96d01d2 Nikolay Bachiyski Move WPCOM_Liveblog_Entries to its own file
nb authored
24 require dirname( __FILE__ ) . '/class-wpcom-liveblog-entries.php';
25
8ff4206 Mohammad Jangda v1
mjangda authored
26 if ( ! class_exists( 'WPCOM_Liveblog' ) ) :
27
28 class WPCOM_Liveblog {
29
30 const version = 0.1;
31 const key = 'liveblog';
32 const url_endpoint = 'liveblog';
33 const edit_cap = 'publish_posts';
34 const nonce_key = 'liveblog_nonce';
35
8ce95e9 Nikolay Bachiyski Use 3 second refresh
nb authored
36 const refresh_interval = 3; // how often should we refresh
b12c4b9 Mohammad Jangda Adding inline comments and TODOs
mjangda authored
37 const max_retries = 100; // max number of failed tries before polling is disabled
38 const delay_threshold = 10; // how many failed tries after which we should increase the refresh interval
39 const delay_multiplier = 1.5; // by how much should we inscrease the refresh interval
8ff4206 Mohammad Jangda v1
mjangda authored
40
4725359 Nikolay Bachiyski Extract entry template in a separate file
nb authored
41
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
42 static $post_id = null;
43 static $entries = null;
6c66757 Nikolay Bachiyski Do not let the client cache future requests
nb authored
44 static $do_not_cache_response = false;
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
45
8ff4206 Mohammad Jangda v1
mjangda authored
46 function load() {
47 add_action( 'init', array( __CLASS__, 'init' ) );
48 add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
49
50 add_action( 'wp_ajax_liveblog_insert_entry', array( __CLASS__, 'ajax_insert_entry' ) );
51
52 add_filter( 'template_redirect', array( __CLASS__, 'handle_request' ) );
53
54 add_filter( 'comment_class', array( __CLASS__, 'add_comment_class' ) );
55
56 add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_box' ) );
57 add_action( 'save_post', array( __CLASS__, 'save_meta_box' ) );
58 }
59
60 function init() {
b12c4b9 Mohammad Jangda Adding inline comments and TODOs
mjangda authored
61 // TODO filters for time and interval overrides
62
8ff4206 Mohammad Jangda v1
mjangda authored
63 add_rewrite_endpoint( self::url_endpoint, EP_PERMALINK ); // /2012/01/01/post-name/liveblog/123456/ where 123456 is a timestamp
64
65 add_post_type_support( 'post', self::key );
66 }
67
68 function handle_request( $query ) {
69 if ( ! self::is_viewing_liveblog_post() )
70 return;
71
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
72 self::$post_id = get_the_ID();
73 self::$entries = new WPCOM_Liveblog_Entries( self::$post_id, self::key );
74
75 if ( self::is_initial_page_request() ) {
8ff4206 Mohammad Jangda v1
mjangda authored
76 add_filter( 'the_content', array( __CLASS__, 'add_liveblog_to_content' ) );
77 return;
78 }
79
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
80 if ( self::is_entries_ajax_request() ) {
7c3d06f Nikolay Bachiyski Rename the entries AJAX handler to match the other
nb authored
81 self::ajax_entries_between();
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
82 return;
83 }
84
85 wp_safe_redirect( get_permalink() );
86 exit;
8ff4206 Mohammad Jangda v1
mjangda authored
87 }
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
88
7c3d06f Nikolay Bachiyski Rename the entries AJAX handler to match the other
nb authored
89 function ajax_entries_between() {
558008c Nikolay Bachiyski Use the same current timestamp at all places
nb authored
90 $current_timestamp = time();
91
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
92 list( $start_timestamp, $end_timestamp ) = self::get_timestamps_from_query();
93 if ( !$end_timestamp ) {
94 wp_safe_redirect( get_permalink() );
95 }
96
6c66757 Nikolay Bachiyski Do not let the client cache future requests
nb authored
97 if ( $end_timestamp > $current_timestamp ) {
98 self::$do_not_cache_response = true;
99 }
100
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
101 $entries = self::$entries->get_between_timestamps( $start_timestamp, $end_timestamp );
102 if ( !$entries ) {
e977561 Nikolay Bachiyski Get rid of extra json_return() arguments
nb authored
103 self::json_return( array( 'entries' => array(), 'current_timestamp' => $current_timestamp, 'latest_timestamp' => null ) );
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
104 }
d79a82d Nikolay Bachiyski Gather common entry functionality in a new class
nb authored
105
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
106 $latest_timestamp = 0;
d79a82d Nikolay Bachiyski Gather common entry functionality in a new class
nb authored
107
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
108 $entries_for_json = array();
109 foreach( $entries as $entry ) {
d79a82d Nikolay Bachiyski Gather common entry functionality in a new class
nb authored
110 $latest_timestamp = max( $latest_timestamp, $entry->get_timestamp() );
111 $entries_for_json[] = $entry->for_json();
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
112 }
113
114 $result_for_json = array(
115 'entries' => $entries_for_json,
558008c Nikolay Bachiyski Use the same current timestamp at all places
nb authored
116 'current_timestamp' => $current_timestamp,
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
117 'latest_timestamp' => $latest_timestamp,
118 );
119
e977561 Nikolay Bachiyski Get rid of extra json_return() arguments
nb authored
120 self::json_return( $result_for_json );
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
121 }
122
123 function is_viewing_liveblog_post() {
124 return is_single() && self::is_liveblog_post();
125 }
126
127 function is_initial_page_request() {
8ff4206 Mohammad Jangda v1
mjangda authored
128 global $wp_query;
129 // Not using get_query_var since it returns '' for all requests, which is a valid for /post-name/liveblog/
130 return ! isset( $wp_query->query_vars['liveblog'] );
131 }
1b8e8d5 Nikolay Bachiyski Streamline logic for getting recent entries
nb authored
132
133
134 function is_entries_ajax_request() {
135 return (bool)get_query_var( self::url_endpoint );
136 }
137
138 function get_timestamps_from_query() {
139 $timestamps = explode( '/', get_query_var( self::url_endpoint) );
140 if ( 2 != count( $timestamps ) ) {
141 return array( false, false );
142 }
143 $timestamps = array_map( 'intval', $timestamps );
144 return $timestamps;
145 }
146
8ff4206 Mohammad Jangda v1
mjangda authored
147 function ajax_insert_entry() {
b58c3a0 Nikolay Bachiyski Remove leading underscores from function names
nb authored
148 self::ajax_current_user_can_edit_liveblog();
149 self::ajax_check_nonce();
8ff4206 Mohammad Jangda v1
mjangda authored
150
151 $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
152
153 if ( ! $post_id )
d81e042 Nikolay Bachiyski Extract sending errors in its own method
nb authored
154 self::send_error( __( "Sorry, that's an invalid post_id", 'liveblog' ) );
8ff4206 Mohammad Jangda v1
mjangda authored
155
156 $user = wp_get_current_user();
157
158 $entry_content = wp_filter_post_kses( $_POST['entry_content'] ); // these should have the same kses rules as posts
159
160 $entry = array(
161 'comment_post_ID' => $post_id,
162 'comment_content' => $entry_content,
2ba7f86 Nikolay Bachiyski Remove trailing whitespace
nb authored
163
8ff4206 Mohammad Jangda v1
mjangda authored
164 'comment_approved' => self::key,
165 'comment_type' => self::key,
166
167 'user_id' => $user->ID,
168 // TODO: Should we be adding this or generating dynamically?
169 'comment_author' => $user->display_name,
170 'comment_author_email' => $user->user_email,
171 'comment_author_url' => $user->user_url,
172 // Borrowed from core as wp_insert_comment does not generate them
173 'comment_author_IP' => preg_replace( '/[^0-9a-fA-F:., ]/', '',$_SERVER['REMOTE_ADDR'] ),
174 'comment_agent' => substr( $_SERVER['HTTP_USER_AGENT'], 0, 254 ),
175 );
176
976c894 Nikolay Bachiyski Send error if comment wasn't created
nb authored
177 $new_comment_id = wp_insert_comment( $entry );
8ff4206 Mohammad Jangda v1
mjangda authored
178
976c894 Nikolay Bachiyski Send error if comment wasn't created
nb authored
179 if ( !$new_comment_id ) {
d81e042 Nikolay Bachiyski Extract sending errors in its own method
nb authored
180 self::send_error( __( 'Error posting entry!' ) );
976c894 Nikolay Bachiyski Send error if comment wasn't created
nb authored
181 }
a2f3ce4 Nikolay Bachiyski Show inserted entry immediately
nb authored
182
183 $entry = WPCOM_Liveblog_Entry::from_comment( get_comment( $new_comment_id ) );
184
185 // do not send latest_timestamp, because if we send it the client won't try to get older entries, but since we now send only the inserted
186 // one we don't know if there weren't any entries in between
e977561 Nikolay Bachiyski Get rid of extra json_return() arguments
nb authored
187 self::json_return( array( 'entries' => array( $entry->for_json() ), 'current_timestamp' => time(), 'latest_timestamp' => null ) );
8ff4206 Mohammad Jangda v1
mjangda authored
188 }
189
7218300 Nikolay Bachiyski Remove echo functionality for entry_output()
nb authored
190 function entry_output( $entry ) {
8ff4206 Mohammad Jangda v1
mjangda authored
191 }
192
193 function add_comment_class( $classes ) {
194 $classes[] = 'liveblog-entry';
195 return $classes;
196 }
197
198 function enqueue_scripts() {
199 if ( ! self::is_viewing_liveblog_post() )
200 return;
201
202 wp_enqueue_script( 'liveblog', plugins_url( 'js/liveblog.js', __FILE__ ), array( 'jquery' ), self::version, true );
203 wp_enqueue_style( 'liveblog', plugins_url( 'css/liveblog.css', __FILE__ ) );
204
b58c3a0 Nikolay Bachiyski Remove leading underscores from function names
nb authored
205 if ( self::current_user_can_edit_liveblog() )
8ff4206 Mohammad Jangda v1
mjangda authored
206 wp_enqueue_script( 'liveblog-publisher', plugins_url( 'js/liveblog-publisher.js', __FILE__ ), array( 'liveblog' ), self::version, true );
207
c0c3085 Nikolay Bachiyski Add spinner for inserting an entry
nb authored
208 if ( wp_script_is( 'jquery.spin', 'registered' ) ) {
209 wp_enqueue_script( 'jquery.spin' );
210 } else {
211 wp_enqueue_script( 'spin', plugins_url( 'js/spin.js', __FILE__ ), false, '1.2.4' );
212 wp_enqueue_script( 'jquery.spin', plugins_url( 'js/jquery.spin.js', __FILE__ ), array( 'jquery', 'spin' ) );
213 }
214
8ff4206 Mohammad Jangda v1
mjangda authored
215 $liveblog_settings = apply_filters( 'liveblog_settings', array(
216 'key' => self::key,
217 'nonce_key' => self::nonce_key,
218 'permalink' => get_permalink(),
219 'post_id' => get_the_ID(),
c7ad98a Nikolay Bachiyski Don't bother to cache latest entry timestamp
nb authored
220 'latest_entry_timestamp' => self::$entries->get_latest_timestamp(),
8ff4206 Mohammad Jangda v1
mjangda authored
221
222 'refresh_interval' => self::refresh_interval,
223 'max_retries' => self::max_retries,
224 'delay_threshold' => self::delay_threshold,
225 'delay_multiplier' => self::delay_multiplier,
226
227 'ajaxurl' => admin_url( 'admin-ajax.php' ),
228 'entriesurl' => self::get_entries_endpoint_url(),
229
230 // i18n
231 'update_nag_singular' => __( '%d new update', 'liveblog' ), // TODO: is there a better way to do _n via js?
232 'update_nag_plural' => __( '%d new updates', 'liveblog' ),
233 ) );
234 wp_localize_script( 'liveblog', 'liveblog_settings', $liveblog_settings );
235 }
236
237 function add_liveblog_to_content( $content ) {
5539d43 Nikolay Bachiyski Use self::$entries instead of self::get_entries*
nb authored
238 $entries = self::$entries->get( array( 'order' => 'ASC' ) );
8870ab9 Mohammad Jangda Reverse the entries array when adding updates from the server so that we...
mjangda authored
239 $entries = array_reverse( $entries );
cb778e4 Mohammad Jangda Load initial entries server-side rather than via AJAX for SEO benefit
mjangda authored
240
8ff4206 Mohammad Jangda v1
mjangda authored
241 $liveblog_output = '';
5539d43 Nikolay Bachiyski Use self::$entries instead of self::get_entries*
nb authored
242 $liveblog_output .= '<div id="liveblog-'. self::$post_id .'" class="liveblog-container">';
273c386 Nikolay Bachiyski Show a spinner when updating entries
nb authored
243 $liveblog_output .= '<div id="liveblog-update-spinner"></div>';
bce6245 Nikolay Bachiyski Always add to $liveblog_output, don't sometimes assign
nb authored
244 $liveblog_output .= '<div class="liveblog-actions">';
8ff4206 Mohammad Jangda v1
mjangda authored
245 $liveblog_output .= self::get_entry_editor_output();
bce6245 Nikolay Bachiyski Always add to $liveblog_output, don't sometimes assign
nb authored
246 $liveblog_output .= '</div>';
ba2c2ae Mohammad Jangda Switch around ids with classes just to be future-safe. Add a new wrapper...
mjangda authored
247 $liveblog_output .= '<div class="liveblog-entries">';
cb778e4 Mohammad Jangda Load initial entries server-side rather than via AJAX for SEO benefit
mjangda authored
248 foreach ( (array) $entries as $entry ) {
d79a82d Nikolay Bachiyski Gather common entry functionality in a new class
nb authored
249 $liveblog_output .= $entry->render();
cb778e4 Mohammad Jangda Load initial entries server-side rather than via AJAX for SEO benefit
mjangda authored
250 }
251
252 $liveblog_output .= '</div>';
ba2c2ae Mohammad Jangda Switch around ids with classes just to be future-safe. Add a new wrapper...
mjangda authored
253 $liveblog_output .= '</div>';
8ff4206 Mohammad Jangda v1
mjangda authored
254
255 return $content . $liveblog_output;
256 }
257
258 function get_entry_editor_output() {
b58c3a0 Nikolay Bachiyski Remove leading underscores from function names
nb authored
259 if ( ! self::current_user_can_edit_liveblog() )
8ff4206 Mohammad Jangda v1
mjangda authored
260 return;
261
262 $editor_output = '';
263 $editor_output .= '<textarea id="liveblog-form-entry" name="liveblog-form-entry"></textarea>';
890df38 Nikolay Bachiyski Escape translated label in attribute
nb authored
264 $editor_output .= '<input type="button" id="liveblog-form-entry-submit" class="button" value="'. esc_attr__( 'Post Update' ) . '" />';
c0c3085 Nikolay Bachiyski Add spinner for inserting an entry
nb authored
265 $editor_output .= '<span id="liveblog-submit-spinner"></span>';
8ff4206 Mohammad Jangda v1
mjangda authored
266 $editor_output .= wp_nonce_field( self::nonce_key, self::nonce_key, false, false );
267
268 return $editor_output;
269 }
270
271
272 function is_liveblog_post( $post_id = null ) {
273 if ( empty( $post_id ) ) {
274 global $post;
275 $post_id = $post->ID;
276 }
277 return get_post_meta( $post_id, self::key, true );
278 }
279
280 function add_meta_box( $post_type ) {
281 if ( post_type_supports( $post_type, self::key ) )
282 add_meta_box( self::key, __( 'Liveblog', 'liveblog' ), array( __CLASS__, 'display_meta_box' ) );
283 }
284 function display_meta_box( $post ) {
285 ?>
286 <label>
287 <input type="checkbox" name="is-liveblog" id="is-liveblog" value="1" <?php checked( self::is_liveblog_post( $post->ID ) ); ?> />
288 <?php esc_html_e( 'This is a liveblog', 'liveblog' ); ?>
289 </label>
290 <?php
291 wp_nonce_field( 'liveblog_nonce', 'liveblog_nonce', false );
292 }
293 function save_meta_box( $post_id ) {
294 if ( ! isset( $_POST['liveblog_nonce'] ) || ! wp_verify_nonce( $_POST['liveblog_nonce'], 'liveblog_nonce' ) )
295 return;
296
297 if ( isset( $_POST['is-liveblog'] ) )
298 update_post_meta( $post_id, self::key, 1 );
299 else
300 delete_post_meta( $post_id, self::key );
301 }
2ba7f86 Nikolay Bachiyski Remove trailing whitespace
nb authored
302
b897ffd Nikolay Bachiyski Remove arguments of get_entries_endpoint_url()
nb authored
303 function get_entries_endpoint_url() {
304 return trailingslashit( get_permalink( self::$post_id ) . self::url_endpoint );
8ff4206 Mohammad Jangda v1
mjangda authored
305 }
306
b58c3a0 Nikolay Bachiyski Remove leading underscores from function names
nb authored
307 function ajax_current_user_can_edit_liveblog() {
308 if ( ! self::current_user_can_edit_liveblog() ) {
d4aa5ad Nikolay Bachiyski Use double quotes instead of escaping the apostrophe
nb authored
309 self::send_error( __( "Cheatin', uh?", 'liveblog' ) );
8ff4206 Mohammad Jangda v1
mjangda authored
310 }
311 }
b58c3a0 Nikolay Bachiyski Remove leading underscores from function names
nb authored
312 function current_user_can_edit_liveblog() {
8ff4206 Mohammad Jangda v1
mjangda authored
313 return current_user_can( apply_filters( 'liveblog_edit_cap', self::edit_cap ) );
314 }
315
b58c3a0 Nikolay Bachiyski Remove leading underscores from function names
nb authored
316 function ajax_check_nonce( $action = 'liveblog_nonce' ) {
8ff4206 Mohammad Jangda v1
mjangda authored
317 if ( ! isset( $_REQUEST[ self::nonce_key ] ) || ! wp_verify_nonce( $_REQUEST[ self::nonce_key ], $action ) ) {
d81e042 Nikolay Bachiyski Extract sending errors in its own method
nb authored
318 self::send_error( __( 'Sorry, we could not authenticate you.', 'liveblog' ) );
8ff4206 Mohammad Jangda v1
mjangda authored
319 }
320 }
321
e977561 Nikolay Bachiyski Get rid of extra json_return() arguments
nb authored
322 function json_return( $data ) {
8ff4206 Mohammad Jangda v1
mjangda authored
323 $return = json_encode( array(
e977561 Nikolay Bachiyski Get rid of extra json_return() arguments
nb authored
324 'status' => 1,
325 'message' => '',
8ff4206 Mohammad Jangda v1
mjangda authored
326 'data' => $data,
327 ) );
328
f2908df Nikolay Bachiyski Get rid if return functionality in json_return()
nb authored
329 header( 'Content-Type: application/json' );
6c66757 Nikolay Bachiyski Do not let the client cache future requests
nb authored
330 if ( self::$do_not_cache_response ) {
331 nocache_headers();
332 }
f2908df Nikolay Bachiyski Get rid if return functionality in json_return()
nb authored
333 echo $return;
334 exit;
8ff4206 Mohammad Jangda v1
mjangda authored
335 }
336
d81e042 Nikolay Bachiyski Extract sending errors in its own method
nb authored
337 function send_error( $message ) {
338 self::status_header_with_message( 500, $message );
339 exit;
340 }
341
2532397 Nikolay Bachiyski Send HTTP errors instead of the success flag
nb authored
342 function status_header_with_message( $status, $message ) {
343 global $wp_header_to_desc;
344 $status = absint( $status );
345 $official_message = isset( $wp_header_to_desc[$status] )? $wp_header_to_desc[$status] : '';
346 $wp_header_to_desc[$status] = $message;
347 status_header( $status );
348 $wp_header_to_desc[$status] = $official_message;
349 }
350
8ff4206 Mohammad Jangda v1
mjangda authored
351 }
352
353 WPCOM_Liveblog::load();
354 endif;
Something went wrong with that request. Please try again.