PluginProbe
Edit Flow / 0.8
Edit Flow v0.8
0.11.1 0.11.0 0.7.2 0.7.3 0.7.4 0.7.5 0.7.6 0.8 0.8.1 0.8.2 0.9 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 trunk 0.1.5 0.10.0 0.10.1 0.10.2 All 44 releases
edit-flow / modules / notifications / notifications.php

notifications.php in Edit Flow 0.8, at modules/notifications/notifications.php

1,147 lines 41.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * class EF_Notifications
4 * Email notifications for Edit Flow and more
5 */
6
7 if( ! defined( 'EF_NOTIFICATION_USE_CRON' ) )
8 define( 'EF_NOTIFICATION_USE_CRON', false );
9
10 if ( !class_exists('EF_Notifications') ) {
11
12 class EF_Notifications extends EF_Module {
13
14 // Taxonomy name used to store users following posts
15 var $following_users_taxonomy = 'following_users';
16 // Taxonomy name used to store user groups following posts
17 var $following_usergroups_taxonomy = EF_User_Groups::taxonomy_key;
18
19 var $module;
20
21 var $edit_post_subscriptions_cap = 'edit_post_subscriptions';
22
23 /**
24 * Register the module with Edit Flow but don't do anything else
25 */
26 function __construct () {
27 global $edit_flow;
28
29 // Register the module with Edit Flow
30 $this->module_url = $this->get_module_url( __FILE__ );
31 $args = array(
32 'title' => __( 'Notifications', 'edit-flow' ),
33 'short_description' => __( 'Update your team of important changes to your content.', 'edit-flow' ),
34 'extended_description' => __( 'With email notifications, you can keep everyone updated about what’s happening with a given content. Each status change or editorial comment sends out an email notification to users subscribed to a post. User groups can be used to manage who receives notifications on what.', 'edit-flow' ),
35 'module_url' => $this->module_url,
36 'img_url' => $this->module_url . 'lib/notifications_s128.png',
37 'slug' => 'notifications',
38 'default_options' => array(
39 'enabled' => 'on',
40 'post_types' => array(
41 'post' => 'on',
42 'page' => 'on',
43 ),
44 'always_notify_admin' => 'off',
45 ),
46 'configure_page_cb' => 'print_configure_view',
47 'post_type_support' => 'ef_notification',
48 'autoload' => false,
49 'settings_help_tab' => array(
50 'id' => 'ef-notifications-overview',
51 'title' => __('Overview', 'edit-flow'),
52 'content' => __('<p>Notifications ensure you keep up to date with progress your most important content. Users can be subscribed to notifications on a post one by one or by selecting user groups.</p><p>When enabled, email notifications can be sent when a post changes status or an editorial comment is left by a writer or an editor.</p>', 'edit-flow'),
53 ),
54 'settings_help_sidebar' => __( '<p><strong>For more information:</strong></p><p><a href="http://editflow.org/features/notifications/">Notifications Documentation</a></p><p><a href="http://wordpress.org/tags/edit-flow?forum_id=10">Edit Flow Forum</a></p><p><a href="https://github.com/danielbachhuber/Edit-Flow">Edit Flow on Github</a></p>', 'edit-flow' ),
55 );
56 $this->module = EditFlow()->register_module( 'notifications', $args );
57
58 }
59
60 /**
61 * Initialize the notifications class if the plugin is enabled
62 */
63 function init() {
64
65 // Register our taxonomies for managing relationships
66 $this->register_taxonomies();
67
68 // Allow users to use a different user capability for editing post subscriptions
69 $this->edit_post_subscriptions_cap = apply_filters( 'ef_edit_post_subscriptions_cap', $this->edit_post_subscriptions_cap );
70
71 // Set up metabox and related actions
72 add_action( 'add_meta_boxes', array( $this, 'add_post_meta_box' ) );
73
74 // Saving post actions
75 // self::save_post_subscriptions() is hooked into transition_post_status so we can ensure usergroup data
76 // is properly saved before sending notifs
77 add_action( 'transition_post_status', array( $this, 'save_post_subscriptions' ), 0, 3 );
78 add_action( 'transition_post_status', array( $this, 'notification_status_change' ), 10, 3 );
79 add_action( 'ef_post_insert_editorial_comment', array( $this, 'notification_comment') );
80 add_action( 'delete_user', array($this, 'delete_user_action') );
81 add_action( 'ef_send_scheduled_email', array( $this, 'send_single_email' ), 10, 4 );
82
83 add_action( 'admin_init', array( $this, 'register_settings' ) );
84
85 // Javascript and CSS if we need it
86 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
87 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
88
89 // Add a "Follow" link to posts
90 if ( apply_filters( 'ef_notifications_show_follow_link', true ) ) {
91 // A little extra JS for the follow button
92 add_action( 'admin_head', array( $this, 'action_admin_head_follow_js' ) );
93 // Manage Posts
94 add_filter( 'post_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
95 add_filter( 'page_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
96 // Calendar and Story Budget
97 add_filter( 'ef_calendar_item_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
98 add_filter( 'ef_story_budget_item_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
99 }
100
101 //Ajax for saving notifiction updates
102 add_action( 'wp_ajax_save_notifications', array( $this, 'ajax_save_post_subscriptions' ) );
103 add_action( 'wp_ajax_ef_notifications_user_post_subscription', array( $this, 'handle_user_post_subscription' ) );
104
105 }
106
107 /**
108 * Load the capabilities onto users the first time the module is run
109 *
110 * @since 0.7
111 */
112 function install() {
113
114 // Add necessary capabilities to allow management of notifications
115 $notifications_roles = array(
116 'administrator' => array('edit_post_subscriptions'),
117 'editor' => array('edit_post_subscriptions'),
118 'author' => array('edit_post_subscriptions'),
119 );
120
121 foreach ( $notifications_roles as $role => $caps ) {
122 $this->add_caps_to_role( $role, $caps );
123 }
124
125 }
126
127 /**
128 * Upgrade our data in case we need to
129 *
130 * @since 0.7
131 */
132 function upgrade( $previous_version ) {
133 global $edit_flow;
134
135 // Upgrade path to v0.7
136 if ( version_compare( $previous_version, '0.7' , '<' ) ) {
137 // Migrate whether notifications were enabled or not
138 if ( $enabled = get_option( 'edit_flow_notifications_enabled' ) )
139 $enabled = 'on';
140 else
141 $enabled = 'off';
142 $edit_flow->update_module_option( $this->module->name, 'enabled', $enabled );
143 delete_option( 'edit_flow_notifications_enabled' );
144 // Migrate whether to always notify the admin
145 if ( $always_notify_admin = get_option( 'edit_flow_always_notify_admin' ) )
146 $always_notify_admin = 'on';
147 else
148 $always_notify_admin = 'off';
149 $edit_flow->update_module_option( $this->module->name, 'always_notify_admin', $always_notify_admin );
150 delete_option( 'edit_flow_always_notify_admin' );
151
152 // Technically we've run this code before so we don't want to auto-install new data
153 $edit_flow->update_module_option( $this->module->name, 'loaded_once', true );
154 }
155
156 }
157
158 /**
159 * Register the taxonomies we use to manage relationships
160 *
161 * @since 0.7
162 *
163 * @uses register_taxonomy()
164 */
165 function register_taxonomies() {
166
167 // Load the currently supported post types so we only register against those
168 $supported_post_types = $this->get_post_types_for_module( $this->module );
169
170 $args = array(
171 'hierarchical' => false,
172 'update_count_callback' => '_update_post_term_count',
173 'label' => false,
174 'query_var' => false,
175 'rewrite' => false,
176 'public' => false,
177 'show_ui' => false
178 );
179 register_taxonomy( $this->following_users_taxonomy, $supported_post_types, $args );
180 }
181
182 /**
183 * Enqueue necessary admin scripts
184 *
185 * @since 0.7
186 *
187 * @uses wp_enqueue_script()
188 */
189 function enqueue_admin_scripts() {
190
191 if ( $this->is_whitelisted_functional_view() ) {
192 wp_enqueue_script( 'jquery-listfilterizer' );
193 wp_enqueue_script( 'jquery-quicksearch' );
194 wp_enqueue_script( 'edit-flow-notifications-js', $this->module_url . 'lib/notifications.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch' ), EDIT_FLOW_VERSION, true );
195 }
196 }
197
198 /**
199 * Enqueue necessary admin styles, but only on the proper pages
200 *
201 * @since 0.7
202 *
203 * @uses wp_enqueue_style()
204 */
205 function enqueue_admin_styles() {
206
207 if ( $this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view() ) {
208 wp_enqueue_style( 'jquery-listfilterizer' );
209 wp_enqueue_style( 'edit-flow-notifications-css', $this->module->module_url . 'lib/notifications.css', false, EDIT_FLOW_VERSION );
210 }
211 }
212
213 /**
214 * JS required for the Follow link to work
215 *
216 * @since 0.8
217 */
218 public function action_admin_head_follow_js() {
219 ?>
220 <script type='text/Javascript'>
221 jQuery(document).ready(function($) {
222 /**
223 * Action to Follow / Unfollow posts on the manage posts screen
224 */
225 $('.wp-list-table, #ef-calendar-view, #ef-story-budget-wrap').on( 'click', '.ef_follow_link a', function(e){
226
227 e.preventDefault();
228
229 var link = $(this);
230
231 $.ajax({
232 type : 'GET',
233 url : link.attr( 'href' ),
234 success : function( data ) {
235 if ( 'success' == data.status ) {
236 link.attr( 'href', data.message.link );
237 link.attr( 'title', data.message.title );
238 link.text( data.message.text );
239 }
240 // @todo expose the error somehow
241 }
242 });
243 return false;
244 });
245 });
246 </script><?php
247 }
248
249 /**
250 * Add a "Follow" link to supported post types Manage Posts view
251 *
252 * @since 0.8
253 *
254 * @param array $actions Any existing item actions
255 * @param int|object $post Post id or object
256 * @return array $actions The follow link has been appended
257 */
258 public function filter_post_row_actions( $actions, $post ) {
259
260 $post = get_post( $post );
261
262 if ( ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) )
263 return $actions;
264
265 if ( ! current_user_can( $this->edit_post_subscriptions_cap ) || ! current_user_can( 'edit_post', $post->ID ) )
266 return $actions;
267
268 $parts = $this->get_follow_action_parts( $post );
269 $actions['ef_follow_link'] = '<a title="' . esc_attr( $parts['title'] ) . '" href="' . esc_url( $parts['link'] ) . '">' . $parts['text'] . '</a>';
270
271 return $actions;
272 }
273
274 /**
275 * Get an action parts for a user to follow or unfollow a post
276 *
277 * @since 0.8
278 */
279 private function get_follow_action_parts( $post ) {
280
281 $args = array(
282 'action' => 'ef_notifications_user_post_subscription',
283 'post_id' => $post->ID,
284 );
285 $following_users = $this->get_following_users( $post->ID );
286 if ( in_array( wp_get_current_user()->user_login, $following_users ) ) {
287 $args['method'] = 'unfollow';
288 $title_text = __( 'Click to unfollow updates to this post', 'edit-flow' );
289 $follow_text = __( 'Following', 'edit-flow' );
290 } else {
291 $args['method'] = 'follow';
292 $title_text = __( 'Follow updates to this post', 'edit-flow' );
293 $follow_text = __( 'Follow', 'edit-flow' );
294 }
295
296 // wp_nonce_url() has encoding issues: http://core.trac.wordpress.org/ticket/20771
297 $args['_wpnonce'] = wp_create_nonce( 'ef_notifications_user_post_subscription' );
298
299 return array(
300 'title' => $title_text,
301 'text' => $follow_text,
302 'link' => add_query_arg( $args, admin_url( 'admin-ajax.php' ) ),
303 );
304 }
305
306 /**
307 * Add the subscriptions meta box to relevant post types
308 */
309 function add_post_meta_box() {
310
311 if ( !current_user_can( $this->edit_post_subscriptions_cap ) )
312 return;
313
314 $usergroup_post_types = $this->get_post_types_for_module( $this->module );
315 foreach ( $usergroup_post_types as $post_type ) {
316 add_meta_box( 'edit-flow-notifications', __( 'Notifications', 'edit-flow'), array( $this, 'notifications_meta_box'), $post_type, 'advanced' );
317 }
318 }
319
320 /**
321 * Outputs box used to subscribe users and usergroups to Posts
322 *
323 * @todo add_cap to set subscribers for posts; default to Admin and editors
324 */
325 function notifications_meta_box() {
326 global $post, $post_ID, $edit_flow;
327
328 ?>
329 <div id="ef-post_following_box">
330 <a name="subscriptions"></a>
331
332 <p><?php _e( 'Select the users and user groups that should receive notifications when the status of this post is updated or when an editorial comment is added.', 'edit-flow' ); ?></p>
333 <div id="ef-post_following_users_box">
334 <h4><?php _e( 'Users', 'edit-flow' ); ?></h4>
335 <?php
336 $followers = $this->get_following_users( $post->ID, 'id' );
337 $select_form_args = array(
338 'list_class' => 'ef-post_following_list',
339 );
340 $this->users_select_form( $followers, $select_form_args ); ?>
341 </div>
342
343 <?php if ( $this->module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) ): ?>
344 <div id="ef-post_following_usergroups_box">
345 <h4><?php _e('User Groups', 'edit-flow') ?></h4>
346 <?php
347 $following_usergroups = $this->get_following_usergroups( $post->ID, 'ids' );
348 $edit_flow->user_groups->usergroups_select_form( $following_usergroups ); ?>
349 </div>
350 <?php endif; ?>
351 <div class="clear"></div>
352 <input type="hidden" name="ef-save_followers" value="1" /> <?php // Extra protection against autosaves ?>
353 <?php wp_nonce_field('save_user_usergroups', 'ef_notifications_nonce', false); ?>
354 </div>
355
356 <?php
357 }
358
359 /**
360 * Called when a notification editorial metadata checkbox is checked. Handles saving of a user/usergroup to a post.
361 */
362 function ajax_save_post_subscriptions() {
363 global $edit_flow;
364
365 // Verify nonce
366 if ( !wp_verify_nonce( $_POST['_nonce'], 'save_user_usergroups') )
367 die( __( "Nonce check failed. Please ensure you can add users or user groups to a post.", 'edit-flow' ) );
368
369 $post_id = (int)$_POST['post_id'];
370 $post = get_post( $post_id );
371 $user_usergroup_ids = array_map( 'intval', $_POST['user_group_ids'] );
372 if( ( !wp_is_post_revision( $post_id ) && !wp_is_post_autosave( $post_id ) ) && current_user_can( $this->edit_post_subscriptions_cap ) ) {
373 if( $_POST['ef_notifications_name'] === 'ef-selected-users[]' ) {
374 $this->save_post_following_users( $post, $user_usergroup_ids );
375 }
376 else if ( $_POST['ef_notifications_name'] == 'following_usergroups[]' ) {
377 if ( $this->module_enabled( 'user_groups' ) && in_array( get_post_type( $post_id ), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) ) {
378 $this->save_post_following_usergroups( $post, $user_usergroup_ids );
379 }
380 }
381 }
382 die();
383 }
384
385 /**
386 * Handle a request to update a user's post subscription
387 *
388 * @since 0.8
389 */
390 public function handle_user_post_subscription() {
391
392 if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'ef_notifications_user_post_subscription' ) )
393 $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
394
395 if ( ! current_user_can( $this->edit_post_subscriptions_cap ) )
396 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
397
398 $post = get_post( ( $post_id = $_GET['post_id'] ) );
399
400 if ( ! $post )
401 $this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
402
403 if ( 'follow' == $_GET['method'] )
404 $retval = $this->follow_post_user( $post, get_current_user_id() );
405 else
406 $retval = $this->unfollow_post_user( $post, get_current_user_id() );
407
408 if ( is_wp_error( $retval ) )
409 $this->print_ajax_response( 'error', $retval->get_error_message() );
410
411 $this->print_ajax_response( 'success', (object)$this->get_follow_action_parts( $post ) );
412 }
413
414
415 /**
416 * Called when post is saved. Handles saving of user/usergroup followers
417 *
418 * @param int $post ID of the post
419 */
420 function save_post_subscriptions( $new_status, $old_status, $post ) {
421 global $edit_flow;
422 // only if has edit_post_subscriptions cap
423 if( ( !wp_is_post_revision($post) && !wp_is_post_autosave($post) ) && isset($_POST['ef-save_followers']) && current_user_can( $this->edit_post_subscriptions_cap ) ) {
424 $users = isset( $_POST['ef-selected-users'] ) ? $_POST['ef-selected-users'] : array();
425 $usergroups = isset( $_POST['following_usergroups'] ) ? $_POST['following_usergroups'] : array();
426 $this->save_post_following_users( $post, $users );
427 if ( $this->module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) )
428 $this->save_post_following_usergroups( $post, $usergroups );
429 }
430
431 }
432
433 /**
434 * Sets users to follow specified post
435 *
436 * @param int $post ID of the post
437 */
438 function save_post_following_users( $post, $users = null ) {
439 if( !is_array( $users ) )
440 $users = array();
441
442 // Add current user to following users
443 $user = wp_get_current_user();
444 if ( $user && apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'subscription_action' ) )
445 $users[] = $user->ID;
446
447 // Add post author to following users
448 if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'subscription_action' ) )
449 $users[] = $post->post_author;
450
451 $users = array_unique( array_map( 'intval', $users ) );
452
453 $follow = $this->follow_post_user( $post, $users, false );
454
455 }
456
457 /**
458 * Sets usergroups to follow specified post
459 *
460 * @param int $post ID of the post
461 * @param array $usergroups Usergroups to follow posts
462 */
463 function save_post_following_usergroups( $post, $usergroups = null ) {
464
465 if( !is_array($usergroups) ) $usergroups = array();
466 $usergroups = array_map( 'intval', $usergroups );
467
468 $follow = $this->follow_post_usergroups($post, $usergroups, false);
469 }
470
471 /**
472 * Set up and send post status change notification email
473 */
474 function notification_status_change( $new_status, $old_status, $post ) {
475 global $edit_flow;
476
477 // Kill switch for notification
478 if ( ! apply_filters( 'ef_notification_status_change', $new_status, $old_status, $post ) || ! apply_filters( "ef_notification_{$post->post_type}_status_change", $new_status, $old_status, $post ) )
479 return false;
480
481 $supported_post_types = $this->get_post_types_for_module( $this->module );
482 if ( !in_array( $post->post_type, $supported_post_types ) )
483 return;
484
485 // No need to notify if it's a revision, auto-draft, or if post status wasn't changed
486 $ignored_statuses = apply_filters( 'ef_notification_ignored_statuses', array( $old_status, 'inherit', 'auto-draft' ), $post->post_type );
487
488 if ( !in_array( $new_status, $ignored_statuses ) ) {
489
490 // Get current user
491 $current_user = wp_get_current_user();
492
493 $post_author = get_userdata( $post->post_author );
494 //$duedate = $edit_flow->post_metadata->get_post_meta($post->ID, 'duedate', true);
495
496 $blogname = get_option('blogname');
497
498 $body = '';
499
500 $post_id = $post->ID;
501 $post_title = ef_draft_or_post_title( $post_id );
502 $post_type = get_post_type_object( $post->post_type )->labels->singular_name;
503
504 if( 0 != $current_user->ID ) {
505 $current_user_display_name = $current_user->display_name;
506 $current_user_email = sprintf( '(%s)', $current_user->user_email );
507 } else {
508 $current_user_display_name = __( 'WordPress Scheduler', 'edit-flow' );
509 $current_user_email = '';
510 }
511
512 // Email subject and first line of body
513 // Set message subjects according to what action is being taken on the Post
514 if ( $old_status == 'new' || $old_status == 'auto-draft' ) {
515 /* translators: 1: site name, 2: post type, 3. post title */
516 $subject = sprintf( __( '[%1$s] New %2$s Created: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
517 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
518 $body .= sprintf( __( 'A new %1$s (#%2$s "%3$s") was created by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user->display_name, $current_user->user_email ) . "\r\n";
519 } else if ( $new_status == 'trash' ) {
520 /* translators: 1: site name, 2: post type, 3. post title */
521 $subject = sprintf( __( '[%1$s] %2$s Trashed: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
522 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
523 $body .= sprintf( __( '%1$s #%2$s "%3$s" was moved to the trash by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
524 } else if ( $old_status == 'trash' ) {
525 /* translators: 1: site name, 2: post type, 3. post title */
526 $subject = sprintf( __( '[%1$s] %2$s Restored (from Trash): "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
527 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
528 $body .= sprintf( __( '%1$s #%2$s "%3$s" was restored from trash by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
529 } else if ( $new_status == 'future' ) {
530 /* translators: 1: site name, 2: post type, 3. post title */
531 $subject = sprintf( __('[%1$s] %2$s Scheduled: "%3$s"'), $blogname, $post_type, $post_title );
532 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email 6. scheduled date */
533 $body .= sprintf( __( '%1$s #%2$s "%3$s" was scheduled by %4$s %5$s. It will be published on %6$s' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email, $this->get_scheduled_datetime( $post ) ) . "\r\n";
534 } else if ( $new_status == 'publish' ) {
535 /* translators: 1: site name, 2: post type, 3. post title */
536 $subject = sprintf( __( '[%1$s] %2$s Published: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
537 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
538 $body .= sprintf( __( '%1$s #%2$s "%3$s" was published by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
539 } else if ( $old_status == 'publish' ) {
540 /* translators: 1: site name, 2: post type, 3. post title */
541 $subject = sprintf( __( '[%1$s] %2$s Unpublished: "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
542 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
543 $body .= sprintf( __( '%1$s #%2$s "%3$s" was unpublished by %4$s %5$s', 'edit-flow' ), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
544 } else {
545 /* translators: 1: site name, 2: post type, 3. post title */
546 $subject = sprintf( __( '[%1$s] %2$s Status Changed for "%3$s"', 'edit-flow' ), $blogname, $post_type, $post_title );
547 /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
548 $body .= sprintf( __( 'Status was changed for %1$s #%2$s "%3$s" by %4$s %5$s', 'edit-flow'), $post_type, $post_id, $post_title, $current_user_display_name, $current_user_email ) . "\r\n";
549 }
550
551 /* translators: 1: date, 2: time, 3: timezone */
552 $body .= sprintf( __( 'This action was taken on %1$s at %2$s %3$s', 'edit-flow' ), date_i18n( get_option( 'date_format' ) ), date_i18n( get_option( 'time_format' ) ), get_option( 'timezone_string' ) ) . "\r\n";
553
554 $old_status_friendly_name = $this->get_post_status_friendly_name( $old_status );
555 $new_status_friendly_name = $this->get_post_status_friendly_name( $new_status );
556
557 // Email body
558 $body .= "\r\n";
559 /* translators: 1: old status, 2: new status */
560 $body .= sprintf( __( '%1$s => %2$s', 'edit-flow' ), $old_status_friendly_name, $new_status_friendly_name );
561 $body .= "\r\n\r\n";
562
563 $body .= "--------------------\r\n\r\n";
564
565 $body .= sprintf( __( '== %s Details ==', 'edit-flow' ), $post_type ) . "\r\n";
566 $body .= sprintf( __( 'Title: %s', 'edit-flow' ), $post_title ) . "\r\n";
567 /* translators: 1: author name, 2: author email */
568 $body .= sprintf( __( 'Author: %1$s (%2$s)', 'edit-flow' ), $post_author->display_name, $post_author->user_email ) . "\r\n";
569
570 $edit_link = htmlspecialchars_decode( get_edit_post_link( $post_id ) );
571 if ( $new_status != 'publish' ) {
572 $view_link = add_query_arg( array( 'preview' => 'true' ), wp_get_shortlink( $post_id ) );
573 } else {
574 $view_link = htmlspecialchars_decode( get_permalink( $post_id ) );
575 }
576 $body .= "\r\n";
577 $body .= __( '== Actions ==', 'edit-flow' ) . "\r\n";
578 $body .= sprintf( __( 'Add editorial comment: %s', 'edit-flow' ), $edit_link . '#editorialcomments/add' ) . "\r\n";
579 $body .= sprintf( __( 'Edit: %s', 'edit-flow' ), $edit_link ) . "\r\n";
580 $body .= sprintf( __( 'View: %s', 'edit-flow' ), $view_link ) . "\r\n";
581
582 $body .= $this->get_notification_footer($post);
583
584 $this->send_email( 'status-change', $post, $subject, $body );
585 }
586
587 }
588
589 /**
590 * Set up and set editorial comment notification email
591 */
592 function notification_comment( $comment ) {
593
594 $post = get_post($comment->comment_post_ID);
595
596 $supported_post_types = $this->get_post_types_for_module( $this->module );
597 if ( !in_array( $post->post_type, $supported_post_types ) )
598 return;
599
600 // Kill switch for notification
601 if ( ! apply_filters( 'ef_notification_editorial_comment', $comment, $post ) )
602 return false;
603
604 $user = get_userdata( $post->post_author );
605 $current_user = wp_get_current_user();
606
607 $post_id = $post->ID;
608 $post_type = get_post_type_object( $post->post_type )->labels->singular_name;
609 $post_title = ef_draft_or_post_title( $post_id );
610
611 // Check if this a reply
612 //$parent_ID = isset( $comment->comment_parent_ID ) ? $comment->comment_parent_ID : 0;
613 //if($parent_ID) $parent = get_comment($parent_ID);
614
615 // Set user to follow post, but make it filterable
616 if ( apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'comment' ) )
617 $this->follow_post_user($post, (int) $current_user->ID);
618
619 // Set the post author to follow the post but make it filterable
620 if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'comment' ) )
621 $this->follow_post_user( $post, (int) $post->post_author );
622
623 $blogname = get_option('blogname');
624
625 /* translators: 1: blog name, 2: post title */
626 $subject = sprintf( __( '[%1$s] New Editorial Comment: "%2$s"', 'edit-flow' ), $blogname, $post_title );
627
628 /* translators: 1: post id, 2: post title, 3. post type */
629 $body = sprintf( __( 'A new editorial comment was added to %3$s #%1$s "%2$s"', 'edit-flow' ), $post_id, $post_title, $post_type ) . "\r\n\r\n";
630 /* translators: 1: comment author, 2: author email, 3: date, 4: time */
631 $body .= sprintf( __( '%1$s (%2$s) said on %3$s at %4$s:', 'edit-flow' ), $current_user->display_name, $current_user->user_email, mysql2date(get_option('date_format'), $comment->comment_date), mysql2date(get_option('time_format'), $comment->comment_date) ) . "\r\n";
632 $body .= "\r\n" . $comment->comment_content . "\r\n";
633
634 // @TODO: mention if it was a reply
635 /*
636 if($parent) {
637
638 }
639 */
640
641 $body .= "\r\n--------------------\r\n";
642
643 $edit_link = htmlspecialchars_decode( get_edit_post_link( $post_id ) );
644 $view_link = htmlspecialchars_decode( get_permalink( $post_id ) );
645
646 $body .= "\r\n";
647 $body .= __( '== Actions ==', 'edit-flow' ) . "\r\n";
648 $body .= sprintf( __( 'Reply: %s', 'edit-flow' ), $edit_link . '#editorialcomments/reply/' . $comment->comment_ID ) . "\r\n";
649 $body .= sprintf( __( 'Add editorial comment: %s', 'edit-flow' ), $edit_link . '#editorialcomments/add' ) . "\r\n";
650 $body .= sprintf( __( 'Edit: %s', 'edit-flow' ), $edit_link ) . "\r\n";
651 $body .= sprintf( __( 'View: %s', 'edit-flow' ), $view_link ) . "\r\n";
652
653 $body .= "\r\n" . sprintf( __( 'You can see all editorial comments on this %s here: ', 'edit-flow' ), $post_type ). "\r\n";
654 $body .= $edit_link . "#editorialcomments" . "\r\n\r\n";
655
656 $body .= $this->get_notification_footer($post);
657
658 $this->send_email( 'comment', $post, $subject, $body );
659 }
660
661 function get_notification_footer( $post ) {
662 $body = "";
663 $body .= "\r\n--------------------\r\n";
664 $body .= sprintf( __( 'You are receiving this email because you are subscribed to "%s".', 'edit-flow' ), ef_draft_or_post_title ($post->ID ) );
665 $body .= "\r\n";
666 $body .= sprintf( __( 'This email was sent %s.', 'edit-flow' ), date( 'r' ) );
667 $body .= "\r\n \r\n";
668 $body .= get_option('blogname') ." | ". get_bloginfo('url') . " | " . admin_url('/') . "\r\n";
669 return $body;
670 }
671
672 /**
673 * send_email()
674 */
675 function send_email( $action, $post, $subject, $message, $message_headers = '' ) {
676
677 // Get list of email recipients -- set them CC
678 $recipients = $this->_get_notification_recipients( $post, true );
679
680 if( $recipients && ! is_array( $recipients ) )
681 $recipients = explode( ',', $recipients );
682
683 $subject = apply_filters( 'ef_notification_send_email_subject', $subject, $action, $post );
684 $message = apply_filters( 'ef_notification_send_email_message', $message, $action, $post );
685 $message_headers = apply_filters( 'ef_notification_send_email_message_headers', $message_headers, $action, $post );
686
687 if( EF_NOTIFICATION_USE_CRON ) {
688 $this->schedule_emails( $recipients, $subject, $message, $message_headers );
689 } else if ( !empty( $recipients ) ) {
690 foreach( $recipients as $recipient ) {
691 $this->send_single_email( $recipient, $subject, $message, $message_headers );
692 }
693 }
694 }
695
696 /**
697 * Schedules emails to be sent in succession
698 *
699 * @param mixed $recipients Individual email or array of emails
700 * @param string $subject Subject of the email
701 * @param string $message Body of the email
702 * @param string $message_headers. (optional) Message headers
703 * @param int $time_offset (optional) Delay in seconds per email
704 */
705 function schedule_emails( $recipients, $subject, $message, $message_headers = '', $time_offset = 1 ) {
706 $recipients = (array) $recipients;
707
708 $send_time = time();
709
710 foreach( $recipients as $recipient ) {
711 wp_schedule_single_event( $send_time, 'ef_send_scheduled_email', array( $recipient, $subject, $message, $message_headers ) );
712 $send_time += $time_offset;
713 }
714
715 }
716
717 /**
718 * Sends an individual email
719 *
720 * @param mixed $to Email to send to
721 * @param string $subject Subject of the email
722 * @param string $message Body of the email
723 * @param string $message_headers. (optional) Message headers
724 */
725 function send_single_email( $to, $subject, $message, $message_headers = '' ) {
726 wp_mail( $to, $subject, $message, $message_headers );
727 }
728
729 /**
730 * Returns a list of recipients for a given post
731 *
732 * @param $post object
733 * @param $string bool Whether to return recipients as comma-delimited string or array
734 * @return string or array of recipients to receive notification
735 */
736 private function _get_notification_recipients( $post, $string = false ) {
737 global $edit_flow;
738
739 $post_id = $post->ID;
740 if( !$post_id ) return;
741
742 $authors = array();
743 $admins = array();
744 $recipients = array();
745
746 // Email all admins, if enabled
747 if( 'on' == $this->module->options->always_notify_admin )
748 $admins[] = get_option('admin_email');
749
750 $usergroup_users = array();
751 if ( $this->module_enabled( 'user_groups' ) ) {
752 // Get following users and usergroups
753 $usergroups = $this->get_following_usergroups( $post_id, 'ids' );
754 foreach( (array)$usergroups as $usergroup_id ) {
755 $usergroup = $edit_flow->user_groups->get_usergroup_by( 'id', $usergroup_id );
756 foreach( (array)$usergroup->user_ids as $user_id ) {
757 $usergroup_user = get_user_by( 'id', $user_id );
758 if ( $usergroup_user && is_user_member_of_blog( $user_id ) )
759 $usergroup_users[] = $usergroup_user->user_email;
760 }
761 }
762 }
763
764 $users = $this->get_following_users( $post_id, 'user_email' );
765
766 // Merge arrays and filter any duplicates
767 $recipients = array_merge( $authors, $admins, $users, $usergroup_users );
768 $recipients = array_unique( $recipients );
769
770 // Process the recipients for this email to be sent
771 foreach( $recipients as $key => $user_email ) {
772 // Get rid of empty email entries
773 if ( empty( $recipients[$key] ) )
774 unset( $recipients[$key] );
775 // Don't send the email to the current user unless we've explicitly indicated they should receive it
776 if ( false === apply_filters( 'ef_notification_email_current_user', false ) && wp_get_current_user()->user_email == $user_email )
777 unset( $recipients[$key] );
778 }
779
780 // Filter to allow further modification of recipients
781 $recipients = apply_filters( 'ef_notification_recipients', $recipients, $post, $string );
782
783 // If string set to true, return comma-delimited
784 if ( $string && is_array( $recipients ) ) {
785 return implode( ',', $recipients );
786 } else {
787 return $recipients;
788 }
789 }
790
791 /**
792 * Set a user or users to follow a post
793 *
794 * @param int|object $post Post object or ID
795 * @param string|array $users User or users to subscribe to post updates
796 * @param bool $append Whether users should be added to following_users list or replace existing list
797 *
798 * @return true|WP_Error $response True on success, WP_Error on failure
799 */
800 function follow_post_user( $post, $users, $append = true ) {
801
802 $post = get_post( $post );
803 if ( ! $post )
804 return new WP_Error( 'missing-post', $this->module->messages['missing-post'] );
805
806 if ( ! is_array( $users ) )
807 $users = array( $users );
808
809 $user_terms = array();
810 foreach( $users as $user ) {
811
812 if ( is_int( $user ) )
813 $user = get_user_by( 'id', $user );
814 elseif ( is_string( $user ) )
815 $user = get_user_by( 'login', $user );
816
817 if ( ! is_object( $user ) )
818 continue;
819
820 $name = $user->user_login;
821
822 // Add user as a term if they don't exist
823 $term = $this->add_term_if_not_exists( $name, $this->following_users_taxonomy );
824
825 if ( ! is_wp_error( $term ) ) {
826 $user_terms[] = $name;
827 }
828 }
829 $set = wp_set_object_terms( $post->ID, $user_terms, $this->following_users_taxonomy, $append );
830
831 if ( is_wp_error( $set ) )
832 return $set;
833 else
834 return true;
835 }
836
837 /**
838 * Removes user from following_users taxonomy for the given Post,
839 * so they no longer receive future notifications.
840 *
841 * @param object $post Post object or ID
842 * @param int|string|array $users One or more users to unfollow from the post
843 * @return true|WP_Error $response True on success, WP_Error on failure
844 */
845 function unfollow_post_user( $post, $users ) {
846
847 $post = get_post( $post );
848 if ( ! $post )
849 return new WP_Error( 'missing-post', $this->module->messages['missing-post'] );
850
851 if ( ! is_array( $users ) )
852 $users = array( $users );
853
854 $terms = get_the_terms( $post->ID, $this->following_users_taxonomy );
855 if ( is_wp_error( $terms ) )
856 return $terms;
857
858 $user_terms = wp_list_pluck( $terms, 'slug' );
859 foreach( $users as $user ) {
860
861 if ( is_int( $user ) )
862 $user = get_user_by( 'id', $user );
863 elseif ( is_string( $user ) )
864 $user = get_user_by( 'login', $user );
865
866 if ( ! is_object( $user ) )
867 continue;
868
869 $key = array_search( $user->user_login, $user_terms );
870 if ( false !== $key )
871 unset( $user_terms[$key] );
872 }
873 $set = wp_set_object_terms( $post->ID, $user_terms, $this->following_users_taxonomy, false );
874
875 if ( is_wp_error( $set ) )
876 return $set;
877 else
878 return true;
879 }
880
881 /**
882 * follow_post_usergroups()
883 *
884 */
885 function follow_post_usergroups( $post, $usergroups = 0, $append = true ) {
886 if ( !$this->module_enabled( 'user_groups' ) )
887 return;
888
889 $post_id = ( is_int($post) ) ? $post : $post->ID;
890 if( !is_array($usergroups) )
891 $usergroups = array($usergroups);
892
893 // make sure each usergroup id is an integer and not a number stored as a string
894 foreach( $usergroups as $key => $usergroup ) {
895 $usergroups[$key] = intval($usergroup);
896 }
897
898 wp_set_object_terms( $post_id, $usergroups, $this->following_usergroups_taxonomy, $append );
899 return;
900 }
901
902 /**
903 * Removes users that are deleted from receiving future notifications (i.e. makes them unfollow posts FOREVER!)
904 *
905 * @param $id int ID of the user
906 */
907 function delete_user_action( $id ) {
908 if( !$id ) return;
909
910 // get user data
911 $user = get_userdata($id);
912
913 if( $user ) {
914 // Delete term from the following_users taxonomy
915 $user_following_term = get_term_by('name', $user->user_login, $this->following_users_taxonomy);
916 if( $user_following_term ) wp_delete_term($user_following_term->term_id, $this->following_users_taxonomy);
917 }
918 return;
919 }
920
921 /**
922 * Add user as a term if they aren't already
923 * @param $term string term to be added
924 * @param $taxonomy string taxonomy to add term to
925 * @return WP_error if insert fails, true otherwise
926 */
927 function add_term_if_not_exists( $term, $taxonomy ) {
928 if ( !term_exists($term, $taxonomy) ) {
929 $args = array( 'slug' => sanitize_title($term) );
930 return wp_insert_term( $term, $taxonomy, $args );
931 }
932 return true;
933 }
934
935 /**
936 * Gets a list of the users following the specified post
937 *
938 * @param int $post_id The ID of the post
939 * @param string $return The field to return
940 * @return array $users Users following the specified posts
941 */
942 function get_following_users( $post_id, $return = 'user_login' ) {
943
944 // Get following_users terms for the post
945 $users = wp_get_object_terms( $post_id, $this->following_users_taxonomy, array('fields' => 'names') );
946
947 // Don't have any following users
948 if( !$users || is_wp_error($users) )
949 return array();
950
951 // if just want user_login, return as is
952 if ( $return == 'user_login' )
953 return $users;
954
955 foreach( (array)$users as $key => $user ) {
956 switch( $user ) {
957 case is_int( $user ):
958 $search = 'id';
959 break;
960 case is_email( $user ):
961 $search = 'email';
962 break;
963 default:
964 $search = 'login';
965 break;
966 }
967 $new_user = get_user_by( $search, $user );
968 if ( ! $new_user || ! is_user_member_of_blog( $new_user->ID ) )
969 continue;
970 switch( $return ) {
971 case 'user_login':
972 $users[$key] = $new_user->user_login;
973 break;
974 case 'id':
975 $users[$key] = $new_user->ID;
976 break;
977 case 'user_email':
978 $users[$key] = $new_user->user_email;
979 break;
980 }
981 }
982 if( !$users || is_wp_error($users) )
983 $users = array();
984 return $users;
985
986 }
987
988 /**
989 * Gets a list of the usergroups that are following specified post
990 *
991 * @param int $post_id
992 * @return array $usergroups All of the usergroup slugs
993 */
994 function get_following_usergroups( $post_id, $return = 'all' ) {
995 global $edit_flow;
996
997 // Workaround for the fact that get_object_terms doesn't return just slugs
998 if( $return == 'slugs' )
999 $fields = 'all';
1000 else
1001 $fields = $return;
1002
1003 $usergroups = wp_get_object_terms( $post_id, $this->following_usergroups_taxonomy, array( 'fields' => $fields ) );
1004
1005 if( $return == 'slugs' ) {
1006 $slugs = array();
1007 foreach($usergroups as $usergroup) {
1008 $slugs[] = $usergroup->slug;
1009 }
1010 $usergroups = $slugs;
1011 }
1012 return $usergroups;
1013 }
1014
1015 /**
1016 * Gets a list of posts that a user is following
1017 *
1018 * @param string|int $user user_login or id of user
1019 * @param array $args
1020 * @return array $posts Posts a user is following
1021 */
1022 function get_user_following_posts( $user = 0, $args = null ) {
1023 if ( !$user )
1024 $user = (int) wp_get_current_user()->ID;
1025
1026 if ( is_int($user) )
1027 $user = get_userdata($user)->user_login;
1028
1029 $post_args = array(
1030 'tax_query' => array(
1031 array(
1032 'taxonomy' => $this->following_users_taxonomy,
1033 'field' => 'slug',
1034 'terms' => $user,
1035 )
1036 ),
1037 'posts_per_page' => '10',
1038 'orderby' => 'modified',
1039 'order' => 'DESC',
1040 'post_status' => 'any',
1041 );
1042 $post_args = apply_filters( 'ef_user_following_posts_query_args', $post_args );
1043 $posts = get_posts( $post_args );
1044 return $posts;
1045
1046 }
1047
1048 /**
1049 * Register settings for notifications so we can partially use the Settings API
1050 * (We use the Settings API for form generation, but not saving)
1051 *
1052 * @since 0.7
1053 */
1054 function register_settings() {
1055 add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name );
1056 add_settings_field( 'post_types', __( 'Post types for notifications:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );
1057 add_settings_field( 'always_notify_admin', __( 'Always notify blog admin', 'edit-flow' ), array( $this, 'settings_always_notify_admin_option'), $this->module->options_group_name, $this->module->options_group_name . '_general' );
1058 }
1059
1060 /**
1061 * Chose the post types for notifications
1062 *
1063 * @since 0.7
1064 */
1065 function settings_post_types_option() {
1066 global $edit_flow;
1067 $edit_flow->settings->helper_option_custom_post_type( $this->module );
1068 }
1069
1070 /**
1071 * Option for whether the blog admin email address should be always notified or not
1072 *
1073 * @since 0.7
1074 */
1075 function settings_always_notify_admin_option() {
1076 $options = array(
1077 'off' => __( 'Disabled', 'edit-flow' ),
1078 'on' => __( 'Enabled', 'edit-flow' ),
1079 );
1080 echo '<select id="always_notify_admin" name="' . $this->module->options_group_name . '[always_notify_admin]">';
1081 foreach ( $options as $value => $label ) {
1082 echo '<option value="' . esc_attr( $value ) . '"';
1083 echo selected( $this->module->options->always_notify_admin, $value );
1084 echo '>' . esc_html( $label ) . '</option>';
1085 }
1086 echo '</select>';
1087 }
1088
1089 /**
1090 * Validate our user input as the settings are being saved
1091 *
1092 * @since 0.7
1093 */
1094 function settings_validate( $new_options ) {
1095
1096 // Whitelist validation for the post type options
1097 if ( !isset( $new_options['post_types'] ) )
1098 $new_options['post_types'] = array();
1099 $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support );
1100
1101 // Whitelist validation for the 'always_notify_admin' options
1102 if ( !isset( $new_options['always_notify_admin'] ) || $new_options['always_notify_admin'] != 'on' )
1103 $new_options['always_notify_admin'] = 'off';
1104
1105 return $new_options;
1106
1107 }
1108
1109 /**
1110 * Settings page for notifications
1111 *
1112 * @since 0.7
1113 */
1114 function print_configure_view() {
1115 ?>
1116 <form class="basic-settings" action="<?php echo esc_url( menu_page_url( $this->module->settings_slug, false ) ); ?>" method="post">
1117 <?php settings_fields( $this->module->options_group_name ); ?>
1118 <?php do_settings_sections( $this->module->options_group_name ); ?>
1119 <?php
1120 echo '<input id="edit_flow_module_name" name="edit_flow_module_name" type="hidden" value="' . esc_attr( $this->module->name ) . '" />';
1121 ?>
1122 <p class="submit"><?php submit_button( null, 'primary', 'submit', false ); ?><a class="cancel-settings-link" href="<?php echo EDIT_FLOW_SETTINGS_PAGE; ?>"><?php _e( 'Back to Edit Flow', 'edit-flow' ); ?></a></p>
1123 </form>
1124 <?php
1125 }
1126
1127 /**
1128 * Gets a simple phrase containing the formatted date and time that the post is scheduled for.
1129 *
1130 * @since 0.8
1131 *
1132 * @param obj $post Post object
1133 * @return str $scheduled_datetime The scheduled datetime in human-readable format
1134 */
1135 private function get_scheduled_datetime( $post ) {
1136
1137 $scheduled_ts = strtotime( $post->post_date_gmt );
1138
1139 $date = date_i18n( get_option( 'date_format' ), $scheduled_ts );
1140 $time = date_i18n( get_option( 'time_format' ), $scheduled_ts );
1141
1142 return sprintf( __( '%1$s at %2$s', 'edit-flow' ), $date, $time );
1143 }
1144 }
1145
1146 }
1147