PluginProbe
Edit Flow / 0.8.2
Edit Flow v0.8.2
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.2, at modules/notifications/notifications.php

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