HEX
Server: Apache/2.4.65 (Debian)
System: Linux 88f31f35b0b8 6.1.0-38-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.147-1 (2025-08-02) x86_64
User: www-data (33)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/publishpress/modules/notifications/notifications.php
<?php
/**
 * @package PublishPress
 * @author  PublishPress
 *
 * Copyright (c) 2022 PublishPress
 *
 * ------------------------------------------------------------------------------
 * Based on Edit Flow
 * Author: Daniel Bachhuber, Scott Bressler, Mohammad Jangda, Automattic, and
 * others
 * Copyright (c) 2009-2016 Mohammad Jangda, Daniel Bachhuber, et al.
 * ------------------------------------------------------------------------------
 *
 * This file is part of PublishPress
 *
 * PublishPress is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option ) any later version.
 *
 * PublishPress is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PublishPress.  If not, see <http://www.gnu.org/licenses/>.
 */

if (! defined('PP_NOTIFICATION_USE_CRON')) {
    define('PP_NOTIFICATION_USE_CRON', false);
}

use PublishPress\Notifications\Traits\Dependency_Injector;

if (! class_exists('PP_Notifications')) {
    /**
     * Class PP_Notifications
     * Notifications for PublishPress and more
     */
    #[\AllowDynamicProperties]
    class PP_Notifications extends PP_Module
    {
        use Dependency_Injector;

        const MODULE_NAME = 'notifications';

        const MENU_SLUG = 'pp-notifications';

        // Taxonomy name used to store users which will be notified for changes in the posts.
        public $notify_user_taxonomy = 'pp_notify_user';

        // Taxonomy name used to store roles which will be notified for changes in the posts.
        public $notify_role_taxonomy = 'pp_notify_role';

        // Taxonomy name used to store groups which will be notified for changes in the posts.
        public $notify_group_taxonomy = 'pp_notify_group';

        // Taxonomy name used to store emails which will be notified for changes in the posts.
        public $notify_email_taxonomy = 'pp_notify_email';

        public $module;

        public $edit_post_subscriptions_cap = 'edit_post_subscriptions';

        /**
         * Register the module with PublishPress but don't do anything else
         */
        public function __construct()
        {
            // Register the module with PublishPress
            $this->module_url = $this->get_module_url(__FILE__);
            $args = [
                'title' => __('Notifications', 'publishpress'),
                'short_description' => false,
                'extended_description' => false,
                'module_url' => $this->module_url,
                'icon_class' => 'dashicons dashicons-email',
                'slug' => 'notifications',
                'default_options' => [
                    'enabled' => 'on',
                    'post_types' => [
                        'post' => 'on',
                        'page' => 'on',
                    ],
                    'notify_author_by_default' => '1',
                    'notify_current_user_by_default' => '1',
                    'blacklisted_taxonomies' => '',
                ],
                'configure_page_cb' => 'print_configure_view',
                'post_type_support' => 'pp_notification',
                'autoload' => false,
                'settings_help_tab' => [
                    'id' => 'pp-notifications-overview',
                    'title' => __('Overview', 'publishpress'),
                    '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 roles.</p><p>When enabled, notifications can be sent when a post changes status or an editorial comment is left by a writer or an editor.</p>',
                        'publishpress'
                    ),
                ],
                'settings_help_sidebar' => __(
                    '<p><strong>For more information:</strong></p><p><a href="https://publishpress.com/features/notifications/">Notifications Documentation</a></p><p><a href="https://github.com/ostraining/PublishPress">PublishPress on Github</a></p>',
                    'publishpress'
                ),
                'options_page' => true,
            ];
            $this->module = PublishPress()->register_module('notifications', $args);
        }

        /**
         * Initialize the notifications class if the plugin is enabled
         */
        public function init()
        {
            // Register our taxonomies for managing relationships
            $this->register_taxonomies();

            $this->setDefaultCapabilities();

            // Allow users to use a different user capability for editing post subscriptions
            $this->edit_post_subscriptions_cap = apply_filters(
                'pp_edit_post_subscriptions_cap',
                $this->edit_post_subscriptions_cap
            );

            if (is_admin()) {
                // Set up metabox and related actions
                add_action('add_meta_boxes', [$this, 'add_post_meta_box']);

                add_action('admin_init', [$this, 'register_settings']);

                // Javascript and CSS if we need it
                add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
                add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_styles']);

                // Add a "Notify" link to posts
                if (apply_filters('pp_notifications_show_notify_link', true)) {
                    // A little extra JS for the Notify button
                    add_action('admin_head', [$this, 'action_admin_head_notify_js']);
                    // Manage Posts
                    add_filter('post_row_actions', [$this, 'filter_post_row_actions'], 10, 2);
                    add_filter('page_row_actions', [$this, 'filter_post_row_actions'], 10, 2);
                    // Calendar and Content Overview
                    add_filter('pp_calendar_item_actions', [$this, 'filter_post_row_actions'], 10, 2);
                    add_filter('pp_story_budget_item_actions', [$this, 'filter_post_row_actions'], 10, 2);
                }

                /*add_filter(
                    'publishpress_calendar_get_post_data',
                    [$this, 'filterCalendarGetPostData'],
                    10,
                    2
                );*/

                // Ajax for saving notification updates
                add_action('wp_ajax_pp_notifications_user_post_subscription', [$this, 'handle_user_post_subscription']);
            }

            // Saving post actions
            // self::save_post_subscriptions() is hooked into transition_post_status so we can ensure role data
            // is properly saved before sending notifs
            add_action(
                'transition_post_status',
                [$this, 'notification_status_change'],
                PP_NOTIFICATION_PRIORITY_STATUS_CHANGE,
                3
            );

            add_filter(
                'pp_notification_auto_subscribe_post_author',
                [$this, 'filter_pp_notification_auto_subscribe_post_author'],
                10,
                2
            );
            add_filter(
                'pp_notification_auto_subscribe_current_user',
                [$this, 'filter_pp_notification_auto_subscribe_current_user'],
                10,
                2
            );

            add_action('pp_post_insert_editorial_comment', [$this, 'notification_comment']);
            add_action('delete_user', [$this, 'delete_user_action']);
            add_action('pp_send_scheduled_notification', [$this, 'send_single_email'], 10, 5);

            add_action('save_post', [$this, 'action_save_post'], 10);

            add_action('pp_send_notification_status_update', [$this, 'send_notification_status_update']);
            add_action('pp_send_notification_comment', [$this, 'send_notification_comment']);
        }

        /**
         * Load the capabilities onto users the first time the module is run
         *
         * @since 0.7
         */
        public function install()
        {
            // Considering we could be moving from Edit Flow, we need to migrate the following users.
            $this->migrateLegacyFollowingTerms();
        }

        /**
         * Upgrade our data in case we need to
         *
         * @since 0.7
         */
        public function upgrade($previous_version)
        {
            global $publishpress;

            // Upgrade path to v0.7
            if (version_compare($previous_version, '0.7', '<')) {
                // Migrate whether notifications were enabled or not
                if ($enabled = get_option('publishpress_notifications_enabled')) {
                    $enabled = 'on';
                } else {
                    $enabled = 'off';
                }
                $publishpress->update_module_option($this->module->name, 'enabled', $enabled);
                delete_option('publishpress_notifications_enabled');
                // Migrate whether to always notify the admin
                // @todo: Remove after sometime. The setting always notify admin was removed.
                if ($always_notify_admin = get_option('publishpress_always_notify_admin')) {
                    $always_notify_admin = 'on';
                } else {
                    $always_notify_admin = 'off';
                }
                $publishpress->update_module_option($this->module->name, 'always_notify_admin', $always_notify_admin);
                delete_option('publishpress_always_notify_admin');

                // Technically we've run this code before so we don't want to auto-install new data
                $publishpress->update_module_option($this->module->name, 'loaded_once', true);
            }

            if (version_compare($previous_version, '1.10', '<=')) {
                $this->migrateLegacyFollowingTerms();
            }
        }

        private function setDefaultCapabilities()
        {
            $role = get_role('administrator');

            $capabilities = [
                'edit_pp_notif_workflow',
                'read_pp_notif_workflow',
                'delete_pp_notif_workflow',
                'edit_pp_notif_workflows',
                'edit_others_pp_notif_workflows',
                'publish_pp_notif_workflows',
                'read_private_pp_notif_workflows',
                'edit_pp_notif_workflows',
            ];

            foreach ($capabilities as $capability) {
                $role->add_cap($capability);
            }
        }


        protected function migrateLegacyFollowingTerms()
        {
            global $wpdb;

            // Migrate Following Users
            $wpdb->query(
                $wpdb->prepare(
                    "UPDATE {$wpdb->prefix}term_taxonomy SET taxonomy = %s WHERE taxonomy = 'following_users'",
                    $this->notify_user_taxonomy
                )
            );
        }

        /**
         * Register the taxonomies we use to manage relationships
         *
         * @since 0.7
         *
         * @uses  register_taxonomy()
         */
        public function register_taxonomies()
        {
            // Load the currently supported post types so we only register against those
            $supported_post_types = $this->get_post_types_for_module($this->module);

            $args = [
                'hierarchical' => false,
                'update_count_callback' => '_update_post_term_count',
                'label' => false,
                'query_var' => false,
                'rewrite' => false,
                'public' => false,
                'show_ui' => false,
            ];

            register_taxonomy(
                $this->notify_user_taxonomy,
                $supported_post_types,
                wp_parse_args(
                    [
                        'label' => __('Notify User', 'publishpress'),
                        'labels' => [
                            'name' => __('Notify Users', 'publishpress'),
                            'singular_name' => __('Notify User', 'publishpress'),
                        ],
                    ],
                    $args
                )
            );

            register_taxonomy(
                $this->notify_role_taxonomy,
                $supported_post_types,
                wp_parse_args(
                    [
                        'label' => __('Notify Role', 'publishpress'),
                        'labels' => [
                            'name' => __('Notify Roles', 'publishpress'),
                            'singular_name' => __('Notify Role', 'publishpress'),
                        ],
                    ],
                    $args
                )
            );

            if (defined('PRESSPERMIT_VERSION')) {
                register_taxonomy(
                    $this->notify_group_taxonomy,
                    $supported_post_types,
                    wp_parse_args(
                        [
                            'label' => __('Notification Group', 'publishpress'),
                            'labels' => [
                                'name' => __('Notification Groups', 'publishpress'),
                                'singular_name' => __('Notification Group', 'publishpress'),
                            ],
                        ],
                        $args
                    )
                );
            }

            register_taxonomy(
                $this->notify_email_taxonomy,
                $supported_post_types,
                wp_parse_args(
                    [
                        'label' => __('Notify Email', 'publishpress'),
                        'labels' => [
                            'name' => __('Notify Emails', 'publishpress'),
                            'singular_name' => __('Notify Email', 'publishpress'),
                        ],
                    ],
                    $args
                )
            );
        }

        /**
         * Enqueue necessary admin scripts
         *
         * @since 0.7
         *
         * @uses  wp_enqueue_script()
         */
        public function enqueue_admin_scripts()
        {
            if ($this->is_whitelisted_functional_view()) {

                wp_enqueue_script(
                    'publishpress-select2',
                    PUBLISHPRESS_URL . 'common/libs/select2/js/select2-full.min.js',
                    ['jquery'],
                    PUBLISHPRESS_VERSION
                );

                wp_enqueue_script(
                    'publishpress-notifications-js',
                    $this->module_url . 'assets/notifications.js',
                    [
                        'jquery',
                        'publishpress-select2'
                    ],
                    PUBLISHPRESS_VERSION,
                    true
                );
            }
        }

        /**
         * Whether or not the current page is a user-facing PublishPress View
         *
         * @param string $module_name (Optional) Module name to check against
         *
         * @since 0.7
         *
         * @todo  Think of a creative way to make this work
         *
         */
        protected function is_whitelisted_functional_view($module_name = null)
        {
            global $current_screen;

            if (! is_object($current_screen)) {
                return false;
            }

            return $current_screen->base === 'post';
        }

        /**
         * Enqueue necessary admin styles, but only on the proper pages
         *
         * @since 0.7
         *
         * @uses  wp_enqueue_style()
         */
        public function enqueue_admin_styles()
        {
            if ($this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view()) {
                wp_enqueue_style('jquery-listfilterizer');
                wp_enqueue_style(
                    'publishpress-notifications-css',
                    $this->module->module_url . 'assets/notifications.css',
                    false,
                    PUBLISHPRESS_VERSION
                );

                wp_enqueue_style(
                    'publishpress-select2',
                    PUBLISHPRESS_URL . 'common/libs/select2/css/select2-full.min.css',
                    false,
                    PUBLISHPRESS_VERSION
                );
            }
        }

        /**
         * JS required for the Notify link to work
         *
         * @since 0.8
         */
        public function action_admin_head_notify_js()
        {
            ?>
            <script type='text/javascript'>
                (function ($) {
                    $(document).ready(function ($) {
                        /**
                         * Action to Notify / Stop Notifying posts on the manage posts screen
                         */
                        $('.wp-list-table, #pp-calendar-view, #pp-story-budget-wrap').on('click', '.pp_notify_link a', function (e) {

                            e.preventDefault();

                            var link = $(this);

                            $.ajax({
                                type: 'GET',
                                url: link.attr('href'),
                                success: function (data) {
                                    if ('success' === data.status) {
                                        link.attr('href', data.message.link);
                                        link.attr('title', data.message.title);
                                        link.text(data.message.text);
                                    }
                                    // @todo expose the error somehow
                                }
                            });

                            return false;
                        });
                    });
                })(jQuery);
            </script>
            <?php
        }

        /**
         * Add a "Notify" link to supported post types Manage Posts view
         *
         * @param array $actions Any existing item actions
         * @param int|object $post Post id or object
         *
         * @return array     $actions   The follow link has been appended
         * @since 0.8
         *
         */
        public function filter_post_row_actions($actions, $post)
        {
            $post = get_post($post);

            if (! in_array($post->post_type, $this->get_post_types_for_module($this->module))) {
                return $actions;
            }

            if (! current_user_can($this->edit_post_subscriptions_cap) && ! current_user_can(
                    'edit_post',
                    $post->ID
                )) {
                return $actions;
            }

            $parts = $this->get_notify_action_parts($post);
            $actions['pp_notify_link'] = '<a title="' . esc_attr($parts['title']) . '" href="' . esc_url(
                    $parts['link']
                ) . '">' . $parts['text'] . '</a>';

            return $actions;
        }

        /**
         * Get an action parts for a user to set Notify or Stop Notify for a post
         *
         * @since 0.8
         */
        private function get_notify_action_parts($post)
        {
            $args = [
                'action' => 'pp_notifications_user_post_subscription',
                'post_id' => $post->ID,
            ];

            $user_to_notify = $this->get_users_to_notify($post->ID);

            if (in_array(wp_get_current_user()->user_login, $user_to_notify)) {
                $args['method'] = 'stop_notifying';
                $title_text = __('Click to stop being notified on updates for this post', 'publishpress');
                $link_text = __('Stop notifying me', 'publishpress');
            } else {
                $args['method'] = 'start_notifying';
                $title_text = __('Click to start being notified on updates for this post', 'publishpress');
                $link_text = __('Notify me', 'publishpress');
            }

            // wp_nonce_url() has encoding issues: http://core.trac.wordpress.org/ticket/20771
            $args['_wpnonce'] = wp_create_nonce('pp_notifications_user_post_subscription');

            return [
                'title' => $title_text,
                'text' => $link_text,
                'link' => add_query_arg($args, admin_url('admin-ajax.php')),
            ];
        }

        /**
         * Add the subscriptions meta box to relevant post types
         */
        public function add_post_meta_box()
        {
            if (! current_user_can($this->edit_post_subscriptions_cap)) {
                return;
            }

            $role_post_types = $this->get_post_types_for_module($this->module);

            foreach ($role_post_types as $post_type) {
                add_meta_box(
                    'publishpress-notifications',
                    __('Notifications', 'publishpress'),
                    [$this, 'notifications_meta_box'],
                    $post_type,
                    'side',
                    'high'
                );
            }
        }

        public function getPostID($post)
        {
            return $post->ID;
        }

        /**
         * Outputs box used to subscribe users and roles to Posts
         *
         * @todo add_cap to set subscribers for posts; default to Admin and editors
         */
        public function notifications_meta_box()
        {
            global $post;

            $followersWorkflows = $this->get_workflows_related_to_followers();
            $activeWorkflows = $this->get_workflows_related_to_post($post);

            $followersWorkflows = array_map([$this, 'getPostID'], $followersWorkflows);

            $postType = get_post_type_object($post->post_type);

            $notify_me_style = empty($followersWorkflows) ? 'display: none;' : '';
            ?>
            <div id="pp_post_notify_box">
                <a name="subscriptions"></a>
                <div style="<?php echo esc_attr($notify_me_style); ?>">
                    <p>
                        <?php
                        esc_html_e(
                            'Enter users, roles, or email addresses that should receive notifications for this post.',
                            'publishpress'
                        ); ?><?php
                        if (! empty($followersWorkflows)) : ?><a class="notification-followers" href="javascript:void(0);" title="<?php _e('Show active notifications', 'publishpress');?>">&sup1;</a><?php
                        endif; ?>
                    </p>

                    <div id="pp_post_notify_users_box">
                        <?php
                        $users_to_notify = $this->get_users_to_notify($post->ID, 'id');
                        $roles_to_notify = $this->get_roles_to_notify($post->ID, 'slugs');
                        $groups_to_notify = $this->get_groups_to_notify($post->ID, 'slugs');
                        $emails_to_notify = $this->get_emails_to_notify($post->ID);

                        $selected = array_merge($users_to_notify, $roles_to_notify, $groups_to_notify, $emails_to_notify);

                        $select_form_args = [
                            'list_class' => 'pp_post_notify_list',
                        ];
                        $this->users_select_form($selected, $select_form_args); ?>

                    </div>

                    <style>
                        a.notification-followers, a.notification-followers:active, a.notification-followers:visited, a.notification-followers:link {
                            text-decoration: none;
                        }
                    </style>

                    <script type="text/javascript">
                    /* <![CDATA[ */
                    jQuery(document).ready(function ($) {
                        $('#pp_post_notify_box a.notification-followers').on('click', function() {
                            $('div.pp_post_notify_workflows').show();
                        });
                    });
                    /* ]]> */
                    </script>

                    <?php
                    if (empty($followersWorkflows)) : ?>
                        <p class="no-workflows"><?php
                            echo esc_html__(
                                'This won\'t have any effect unless you have at least one workflow targeting the "Users who selected "Notify me" for the content" option.',
                                'publishpress'
                            ); ?></p>
                    <?php
                    endif; ?>
                </div>

                <?php
                if (current_user_can('edit_pp_notif_workflows')) : ?>
                    <div class="pp_post_notify_workflows" style="display: none">
                        <?php
                        if (! empty($activeWorkflows)) : ?>
                            <h3><?php
                                echo esc_html__('Active Notifications', 'publishpress'); ?></h3>

                            <ul>
                                <?php
                                foreach ($activeWorkflows as $workflow) : ?>
                                    <li>
                                        <a href="<?php
                                        echo esc_url(
                                            admin_url(
                                                'post.php?post=' . $workflow->workflow_post->ID . '&action=edit&classic-editor'
                                            )
                                        ); ?>"
                                           target="_blank">
                                            <?php
                                            echo esc_html($workflow->workflow_post->post_title); ?><?php
                                            if (in_array(
                                                $workflow->workflow_post->ID,
                                                $followersWorkflows
                                            )): ?>&sup1;<?php
                                            endif; ?>
                                        </a>
                                    </li>
                                <?php
                                endforeach; ?>
                            </ul>

                            <?php if (!empty($followersWorkflows)):?>
                            <p>
                                &sup1; = <?php _e('Workflow has users enrolled', 'publishpress');?>
                            </p>
                            <?php endif;?>
                        <?php
                        else: ?>
                            <p class="no-workflows"><?php
                                echo sprintf(
                                    esc_html__(
                                        'No active notifications found for this %s.',
                                        'publishpress'
                                    ),
                                    esc_html($postType->labels->singular_name)
                                ); ?></p>
                        <?php
                        endif; ?>
                    </div>
                <?php
                endif; ?>

                <?php
                /**
                 * @param WP_Post $post
                 */
                do_action('publishpress_notif_post_metabox', $post);
                ?>

                <div class="clear"></div>

                <?php
                // Extra protection against autosaves
                ?>
                <input type="hidden" name="pp_save_notify" value="1"/>

                <?php
                wp_nonce_field('save_roles', 'pp_notifications_nonce', false); ?>
            </div>

            <?php
        }

        /**
         * Return workflows with the "Notify to followers" set as true.
         *
         * @return array
         */
        protected function get_workflows_related_to_followers()
        {
            $publishpress = PublishPress();

            $meta_query = [
                'relation' => 'OR',
                [
                    'key' => '_psppno_tofollower',
                    'value' => 1,
                    'compare' => '=',
                ],
            ];

            $workflows = $publishpress->improved_notifications->get_workflows($meta_query);

            return $workflows;
        }

        /**
         * Return workflows where the current post type is selected.
         *
         * @param $post
         *
         * @return mixed
         *
         * @throws Exception
         */
        protected function get_workflows_related_to_post($post)
        {
            $workflows_controller = $this->get_service('workflows_controller');

            $post_status = apply_filters('publishpress_notifications_status', $post->post_status, $post);

            $args = [
                'event' => '',
                'params' => [
                    'post_id' => $post->ID,
                    'new_status' => $post_status,
                    'old_status' => $post_status,
                    'ignore_event' => true,
                ],
            ];

            return apply_filters('publishpress_post_notification_get_workflows', $workflows_controller->get_filtered_workflows($args), $post);
        }

        public function action_save_post($postId)
        {
            if (! isset($_POST['pp_notifications_nonce']) || ! wp_verify_nonce(
                    sanitize_text_field($_POST['pp_notifications_nonce']),
                    'save_roles'
                )) {
                return;
            }

            // Remove current users
            $terms = get_the_terms($postId, $this->notify_user_taxonomy);
            $users = [];
            if (! empty($terms)) {
                foreach ($terms as $term) {
                    $users[] = $term->term_id;
                }
            }
            wp_remove_object_terms($postId, $users, $this->notify_user_taxonomy);


            // Remove current roles
            $terms = get_the_terms($postId, $this->notify_role_taxonomy);
            $roles = [];
            if (! empty($terms)) {
                foreach ($terms as $term) {
                    $roles[] = $term->term_id;
                }
            }
            wp_remove_object_terms($postId, $roles, $this->notify_role_taxonomy);

            if (defined('PRESSPERMIT_VERSION')) {
                // Remove current groups
                $terms = get_the_terms($postId, $this->notify_group_taxonomy);
                $groups = [];
                if (! empty($terms)) {
                    foreach ($terms as $term) {
                        $groups[] = $term->term_id;
                    }
                }
                wp_remove_object_terms($postId, $groups, $this->notify_group_taxonomy);
            }

            // Remove current emails
            $terms = get_the_terms($postId, $this->notify_email_taxonomy);
            $emails = [];
            if (! empty($terms)) {
                foreach ($terms as $term) {
                    $emails[] = $term->term_id;
                }
            }
            wp_remove_object_terms($postId, $emails, $this->notify_email_taxonomy);

            if (apply_filters('pp_notification_auto_subscribe_current_user', true)) {
                if (! isset($_POST['to_notify'])) {
                    $_POST['to_notify'] = [];
                }
                if (! array_search(get_current_user_id(), $_POST['to_notify'])) {
                    $_POST['to_notify'][] = get_current_user_id();
                }
            }

            if (isset($_POST['to_notify'])) {
                foreach ($_POST['to_notify'] as $id) {
                    if (is_numeric($id)) {
                        // User id
                        $this->post_set_users_to_notify($postId, (int)$id, true);
                    } else {
                        $id = sanitize_text_field($id);

                        // Is an email address?
                        if (strpos($id, '@') > 0) {
                            $this->post_set_emails_to_notify($postId, $id, true);

                        } elseif (0 === strpos($id, 'group-')) {
                            $this->post_set_groups_to_notify($postId, $id, true);
                        } else {
                            // Role name
                            $this->post_set_roles_to_notify($postId, $id, true);
                        }
                    }
                }
            }
        }

        /**
         * Handle a request to update a user's post subscription
         *
         * @since 0.8
         */
        public function handle_user_post_subscription()
        {
            if (! isset($_GET['_wpnonce'])
                || ! wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'pp_notifications_user_post_subscription')
            ) {
                $this->print_ajax_response('error', $this->module->messages['nonce-failed']);
            }

            if (! isset($_GET['method']) || ! current_user_can($this->edit_post_subscriptions_cap)) {
                $this->print_ajax_response('error', $this->module->messages['invalid-permissions']);
            }

            if (! isset($_GET['post_id']) || empty((int)$_GET['post_id'])) {
                $this->print_ajax_response('error', $this->module->messages['missing-post']);
            }

            $post = get_post((int)$_GET['post_id']);

            if (! $post) {
                $this->print_ajax_response('error', $this->module->messages['missing-post']);
            }

            if ('start_notifying' === $_GET['method']) {
                $retval = $this->post_set_users_to_notify($post, get_current_user_id());
            } else {
                $retval = $this->post_set_users_stop_notify($post, get_current_user_id());
            }

            if (is_wp_error($retval)) {
                $this->print_ajax_response('error', $retval->get_error_message());
            }

            $this->print_ajax_response('success', (object )$this->get_notify_action_parts($post));
        }

        /**
         * @param $default
         * @param $context
         *
         * @return bool
         */
        public function filter_pp_notification_auto_subscribe_post_author($default, $context)
        {
            if (! isset($this->module->options->notify_author_by_default)) {
                return $default;
            }

            return (bool)$this->module->options->notify_author_by_default;
        }

        /**
         * @param $default
         *
         * @return bool
         */
        public function filter_pp_notification_auto_subscribe_current_user($default)
        {
            if (! isset($this->module->options->notify_current_user_by_default)) {
                return $default;
            }

            return (bool)$this->module->options->notify_current_user_by_default;
        }

        public function filterCalendarGetPostData($postData, $post)
        {
            if (! current_user_can($this->edit_post_subscriptions_cap)) {
                return $postData;
            }

            $user_to_notify = $this->get_users_to_notify($post->ID);

            if (in_array(wp_get_current_user()->user_login, $user_to_notify)) {
                $link = [
                    'args' => ['method' => 'stop_notifying'],
                    'label' => __('Stop notifying me', 'publishpress'),
                    'title' => __('Click to stop being notified on updates for this post', 'publishpress'),
                ];
            } else {
                $link = [
                    'args' => ['method' => 'start_notifying'],
                    'label' => __('Notify me', 'publishpress'),
                    'title' => __('Click to start being notified on updates for this post', 'publishpress'),
                ];
            }

            $link['action'] = 'pp_notifications_user_post_subscription';
            $link['args']['_wpnonce'] = wp_create_nonce('pp_notifications_user_post_subscription');
            $link['args']['post_id'] = $post->ID;

            $postData['links']['notify'] = $link;

            return $postData;
        }

        /**
         * Set up and send post status change a notification
         */
        public function notification_status_change($new_status, $old_status, $post)
        {
            global $publishpress;


            // Kill switch for notification
            if (! apply_filters(
                    'pp_notification_status_change',
                    $new_status,
                    $old_status,
                    $post
                ) || ! apply_filters(
                    "pp_notification_{$post->post_type}_status_change",
                    $new_status,
                    $old_status,
                    $post
                )) {
                return false;
            }

            $supported_post_types = $this->get_post_types_for_module($this->module);
            if (! in_array($post->post_type, $supported_post_types)) {
                return;
            }

            // No need to notify if it's a revision, auto-draft, or if post status wasn't changed
            $ignored_statuses = apply_filters(
                'pp_notification_ignored_statuses',
                [$old_status, 'inherit', 'auto-draft'],
                $post->post_type
            );

            if (! in_array($new_status, $ignored_statuses)) {
                $args = [
                    'new_status' => $new_status,
                    'old_status' => $old_status,
                    'post' => $post,
                ];

                do_action('pp_send_notification_status_update', $args);
            }
        }

        /**
         * Set up and set editorial comment notification email
         */
        public function notification_comment($comment)
        {
            $post = get_post($comment->comment_post_ID);

            $supported_post_types = $this->get_post_types_for_module($this->module);
            if (! in_array($post->post_type, $supported_post_types)) {
                return;
            }

            // Kill switch for notification
            if (! apply_filters('pp_notification_editorial_comment', $comment, $post)) {
                return false;
            }

            $current_user = wp_get_current_user();

            $post_id = $post->ID;
            $post_type = get_post_type_object($post->post_type)->labels->singular_name;
            $post_title = pp_draft_or_post_title($post_id);

            // Set the post author to be notified for the post but make it filterable
            if (apply_filters('pp_notification_auto_subscribe_post_author', true, 'comment')) {
                $this->post_set_users_to_notify($post, (int )$post->post_author);
            }

            $blogname = get_option('blogname');

            // Send the notification
            $args = [
                'blogname' => $blogname,
                'post' => $post,
                'post_title' => $post_title,
                'post_id' => $post_id,
                'post_type' => $post_type,
                'current_user' => $current_user,
                'comment' => $comment,
            ];

            do_action('pp_send_notification_comment', $args);
        }

        private function get_notification_footer($post)
        {
            $body = "";
            $body .= "\r\n--------------------\r\n";
            $body .= sprintf(
                __('You are receiving this email because you are subscribed to "%s".', 'publishpress'),
                pp_draft_or_post_title($post->ID)
            );
            $body .= "\r\n";
            // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date
            $body .= sprintf(__('This email was sent %s.', 'publishpress'), date('r'));
            // phpcs:enable
            $body .= "\r\n \r\n";
            $body .= get_option('blogname') . " | " . get_bloginfo('url') . " | " . admin_url('/') . "\r\n";

            return $body;
        }

        /**
         * send_email()
         *
         * @return array
         */
        public function send_email($action, $post, $subject, $message, $message_headers = '', $recipients = null, $attachments = [])
        {
            $deliveryResult = [];

            if (is_null($recipients)) {
                // Get list of email recipients -- set them CC
                $recipients = $this->_get_notification_recipients($post, true);
            }

            if ($recipients && ! is_array($recipients)) {
                $recipients = explode(',', $recipients);
            }

            $subject = apply_filters('pp_notification_send_email_subject', $subject, $action, $post);
            $message = apply_filters('pp_notification_send_email_message', $message, $action, $post);
            $message_headers = apply_filters(
                'pp_notification_send_email_message_headers',
                $message_headers,
                $action,
                $post
            );

            if (PP_NOTIFICATION_USE_CRON) {
                $this->schedule_emails($recipients, $subject, $message, $message_headers, 1, $attachments);
            } elseif (! empty($recipients)) {
                foreach ($recipients as $recipient) {
                    $deliveryResult[$recipient] = $this->send_single_email(
                        $recipient,
                        $subject,
                        $message,
                        $message_headers,
                        $attachments
                    );
                }
            }

            return $deliveryResult;
        }

        /**
         * Schedules emails to be sent in succession
         *
         * @param mixed $recipients Individual email or array of emails
         * @param string $subject Subject of the email
         * @param string $message Body of the email
         * @param string $message_headers . (optional ) Message headers
         * @param int $time_offset (optional ) Delay in seconds per email
         * @param array $attachments . (optional ) Message attachments
         */
        private function schedule_emails($recipients, $subject, $message, $message_headers = '', $time_offset = 1, $attachments = [])
        {
            $recipients = (array)$recipients;

            $send_time = time();

            foreach ($recipients as $recipient) {
                wp_schedule_single_event(
                    $send_time,
                    'pp_send_scheduled_notification',
                    [$recipient, $subject, $message, $message_headers, $attachments]
                );
                $send_time += $time_offset;
            }
        }

        /**
         * Sends an individual email
         *
         * @param mixed $to Email to send to
         * @param string $subject Subject of the email
         * @param string $message Body of the email
         * @param string $message_headers . (optional ) Message headers
         * @param array $attachments . (optional ) Message attachments
         *
         * @return bool
         */
        public function send_single_email($to, $subject, $message, $message_headers = '', $attachments = [])
        {
            return wp_mail($to, $subject, $message, $message_headers, $attachments);
        }

        /**
         * Returns a list of recipients for a given post
         *
         * @param $post   object
         * @param $string bool Whether to return recipients as comma-delimited string or array
         *
         * @return string|array
         */
        private function _get_notification_recipients($post, $string = false)
        {
            $post_id = $post->ID;
            if (! $post_id) {
                return [];
            }

            $authors = [];
            $admins = [];
            $role_users = [];

            // Get users and roles to notify
            $roles = $this->get_roles_to_notify($post_id, 'slugs');
            foreach ((array )$roles as $role_id) {
                $users = get_users(
                    [
                        'role' => $role_id,
                    ]
                );

                if (! empty($users)) {
                    foreach ($users as $user) {
                        if (is_user_member_of_blog($user->ID)) {
                            $role_users[] = $user->user_email;
                        }
                    }
                }
            }

            $users = $this->get_users_to_notify($post_id, 'user_email');

            // Merge arrays and filter any duplicates
            $recipients = array_merge($authors, $admins, $users, $role_users);
            $recipients = array_unique($recipients);

            // Process the recipients for this email to be sent
            foreach ($recipients as $key => $user_email) {
                // Get rid of empty email entries
                if (empty($recipients[$key])) {
                    unset($recipients[$key]);
                }
                // Don't send the email to the current user unless we've explicitly indicated they should receive it
                if (false === apply_filters(
                        'publishpress_notify_current_user',
                        false
                    ) && wp_get_current_user()->user_email == $user_email) {
                    unset($recipients[$key]);
                }
            }

            // Filter to allow further modification of recipients
            $recipients = apply_filters('pp_notification_recipients', $recipients, $post, $string);

            // If string set to true, return comma-delimited
            if ($string && is_array($recipients)) {
                return implode(',', $recipients);
            } else {
                return $recipients;
            }
        }

        /**
         * Set a user or users to be notified for a post
         *
         * @param int|object $post Post object or ID
         * @param string|array $users User or users to subscribe to post updates
         * @param bool $append Whether users should be added to pp_notify_user list or replace existing list
         *
         * @return true|WP_Error     $response  True on success, WP_Error on failure
         */
        private function post_set_users_to_notify($post, $users, $append = true)
        {
            $post = get_post($post);
            if (! $post) {
                return new WP_Error('missing-post', $this->module->messages['missing-post']);
            }

            if (! is_array($users)) {
                $users = [$users];
            }

            $user_terms = [];

            foreach ($users as $user) {
                if (is_int($user)) {
                    $user = get_user_by('id', $user);
                } elseif (is_string($user)) {
                    $user = get_user_by('login', $user);
                }

                if (! is_object($user)) {
                    continue;
                }

                $name = $user->user_login;

                // Add user as a term if they don't exist
                $term = $this->add_term_if_not_exists($name, $this->notify_user_taxonomy);

                if (! is_wp_error($term)) {
                    $user_terms[] = $name;
                }
            }

            $set = wp_set_object_terms($post->ID, $user_terms, $this->notify_user_taxonomy, $append);

            if (is_wp_error($set)) {
                return $set;
            } else {
                return true;
            }
        }

        /**
         * Set a role or roles to be notified for a post
         *
         * @param int|object $post Post object or ID
         * @param string|array $roles Role or roles to subscribe to post updates
         * @param bool $append Whether roles should be added to pp_notify_role list or replace existing list
         *
         * @return true|WP_Error     $response  True on success, WP_Error on failure
         */
        private function post_set_roles_to_notify($post, $roles, $append = true)
        {
            $post = get_post($post);
            if (! $post) {
                return new WP_Error('missing-post', $this->module->messages['missing-post']);
            }

            if (! is_array($roles)) {
                $roles = [$roles];
            }

            $role_terms = [];

            foreach ($roles as $role) {
                $role = get_role($role);

                if (! is_object($role)) {
                    continue;
                }

                // Add user as a term if they don't exist
                $term = $this->add_term_if_not_exists($role->name, $this->notify_role_taxonomy);

                if (! is_wp_error($term)) {
                    $role_terms[] = $role->name;
                }
            }

            $set = wp_set_object_terms($post->ID, $role_terms, $this->notify_role_taxonomy, $append);

            if (is_wp_error($set)) {
                return $set;
            } else {
                return true;
            }
        }

        /**
         * Set a group or groups to be notified for a post
         *
         * @param int|object $post Post object or ID
         * @param string|array $groups Group or groups to subscribe to post updates
         * @param bool $append Whether groups should be added to pp_notify_role list or replace existing list
         *
         * @return true|WP_Error     $response  True on success, WP_Error on failure
         */
        private function post_set_groups_to_notify($post, $groups, $append = true)
        {
            $post = get_post($post);
            if (! $post) {
                return new WP_Error('missing-post', $this->module->messages['missing-post']);
            }

            if (!class_exists('PublishPress\Permissions\API')) {
                return;
            }

            if (! is_array($groups)) {
                $groups = [$groups];
            }

            $role_terms = [];

            foreach ($groups as $group_id) {
                if (0 === strpos($group_id, 'group-')) {
                    $group_id = str_replace('group-', '', $group_id);
                }

                if ($group = \PublishPress\Permissions\API::getGroup($group_id)) {
                    // Add user as a term if they don't exist
                    $slug = 'group-' . $group_id;
                    $term = $this->add_term_if_not_exists($group->name, $this->notify_group_taxonomy, compact('slug'));

                    if (! is_wp_error($term)) {
                        $role_terms[] = $slug;
                    }
                }
            }

            $set = wp_set_object_terms($post->ID, $role_terms, $this->notify_group_taxonomy, $append);

            if (is_wp_error($set)) {
                return $set;
            } else {
                return true;
            }
        }

        /**
         * Set a non-user or non-users to be notified for a post
         *
         * @param int|object $post Post object or ID
         * @param string|array $emails Role or roles to subscribe to post updates
         * @param bool $append Whether roles should be added to pp_notify_role list or replace existing list
         *
         * @return true|WP_Error     $response  True on success, WP_Error on failure
         */
        private function post_set_emails_to_notify($post, $emails, $append = true)
        {
            $post = get_post($post);
            if (! $post) {
                return new WP_Error('missing-post', $this->module->messages['missing-post']);
            }

            if (! is_array($emails)) {
                $emails = [$emails];
            }

            $email_terms = [];

            foreach ($emails as $string) {
                // Do we have the name/email separator?
                $separatorPos = strpos($string, '/');
                if ($separatorPos > 0) {
                    $email = trim(substr($string, $separatorPos + 1, strlen($string)));
                } else {
                    $email = $string;
                }

                // Do we have a valid email?
                $email = sanitize_email($email);

                if (empty($email)) {
                    continue;
                }

                // Add the email as a term if they don't exist
                $term = $this->add_term_if_not_exists($string, $this->notify_email_taxonomy);

                if (! is_wp_error($term)) {
                    $email_terms[] = $string;
                }
            }

            $set = wp_set_object_terms($post->ID, $email_terms, $this->notify_email_taxonomy, $append);

            if (is_wp_error($set)) {
                return $set;
            } else {
                return true;
            }
        }

        /**
         * Removes user from pp_notify_user taxonomy for the given Post,
         * so they no longer receive future notifications.
         *
         * @param object $post Post object or ID
         * @param int|string|array $users One or more users to stop being notified for the post
         *
         * @return true|WP_Error     $response  True on success, WP_Error on failure
         */
        private function post_set_users_stop_notify($post, $users)
        {
            $post = get_post($post);
            if (! $post) {
                return new WP_Error('missing-post', $this->module->messages['missing-post']);
            }

            if (! is_array($users)) {
                $users = [$users];
            }

            $terms = get_the_terms($post->ID, $this->notify_user_taxonomy);
            if (is_wp_error($terms)) {
                return $terms;
            }

            $user_terms = wp_list_pluck($terms, 'slug');
            foreach ($users as $user) {
                if (is_int($user)) {
                    $user = get_user_by('id', $user);
                } elseif (is_string($user)) {
                    $user = get_user_by('login', $user);
                }

                if (! is_object($user)) {
                    continue;
                }

                $key = array_search($user->user_login, $user_terms);
                if (false !== $key) {
                    unset($user_terms[$key]);
                }
            }
            $set = wp_set_object_terms($post->ID, $user_terms, $this->notify_user_taxonomy, false);

            if (is_wp_error($set)) {
                return $set;
            } else {
                return true;
            }
        }

        /**
         * Removes users that are deleted from receiving future notifications (i.e. makes them out of notify list for posts FOREVER! )
         *
         * @param $id int ID of the user
         */
        public function delete_user_action($id)
        {
            if (! $id) {
                return;
            }

            // get user data
            $user = get_userdata($id);

            if ($user) {
                // Delete term from the pp_notify_user taxonomy
                $notify_user_term = get_term_by('name', $user->user_login, $this->notify_user_taxonomy);
                if ($notify_user_term) {
                    wp_delete_term($notify_user_term->term_id, $this->notify_user_taxonomy);
                }
            }

            return;
        }

        /**
         * Add user as a term if they aren't already
         *
         * @param $term     string term to be added
         * @param $taxonomy string taxonomy to add term to
         *
         * @return WP_error if insert fails, true otherwise
         */
        private function add_term_if_not_exists($term, $taxonomy, $args = [])
        {
            if (! term_exists($term, $taxonomy)) {
                if (empty($args)) {
                    $args = ['slug' => sanitize_title($term)];
                }

                return wp_insert_term($term, $taxonomy, $args);
            }

            return true;
        }

        /**
         * Gets a list of the users to be notified for the specified post
         *
         * @param int $post_id The ID of the post
         * @param string $return The field to return
         *
         * @return array $users Users to notify for the specified posts
         */
        public function get_users_to_notify($post_id, $return = 'user_login')
        {
            // Get pp_notify_user terms for the post
            $users = wp_get_object_terms($post_id, $this->notify_user_taxonomy, ['fields' => 'names']);

            // Don't have any users to notify
            if (! $users || is_wp_error($users)) {
                return [];
            }

            // if just want user_login, return as is
            if ($return == 'user_login') {
                return $users;
            }

            foreach ((array )$users as $key => $user) {
                switch ($user) {
                    case is_int($user):
                        $search = 'id';
                        break;
                    case is_email($user):
                        $search = 'email';
                        break;
                    default:
                        $search = 'login';
                        break;
                }
                $new_user = get_user_by($search, $user);
                if (! $new_user || ! is_user_member_of_blog($new_user->ID)) {
                    unset($users[$key]);
                    continue;
                }
                switch ($return) {
                    case 'user_login':
                        $users[$key] = $new_user->user_login;
                        break;
                    case 'id':
                        $users[$key] = $new_user->ID;
                        break;
                    case 'user_email':
                        $users[$key] = $new_user->user_email;
                        break;
                    case 'object':
                        $users[$key] = $new_user;
                        break;
                }
            }
            if (! $users || is_wp_error($users)) {
                $users = [];
            }

            return $users;
        }

        /**
         * Gets a list of the emails that should be notified for the specified post
         *
         * @param int $post_id
         *
         * @return array $roles All of the role slugs
         */
        public function get_emails_to_notify($post_id)
        {
            $emails = wp_get_object_terms($post_id, $this->notify_email_taxonomy);

            $list = [];
            if (! empty($emails)) {
                foreach ($emails as $email) {
                    $list[] = $email->name;
                }
            }

            return $list;
        }

        /**
         * Gets a list of the roles that should be notified for the specified post
         *
         * @param int $post_id
         * @param string $return
         *
         * @return array $roles All of the role slugs
         */
        public function get_roles_to_notify($post_id, $return = 'all')
        {
            // Workaround for the fact that get_object_terms doesn't return just slugs
            if ($return == 'slugs') {
                $fields = 'all';
            } else {
                $fields = $return;
            }

            $roles = wp_get_object_terms($post_id, $this->notify_role_taxonomy, ['fields' => $fields]);

            if ($return == 'slugs') {
                $slugs = [];
                foreach ($roles as $role) {
                    $slugs[] = $role->slug;
                }
                $roles = $slugs;
            }

            return $roles;
        }

        /**
         * Gets a list of the groups that should be notified for the specified post
         *
         * @param int $post_id
         * @param string $return
         *
         * @return array $groups All of the role slugs
         */
        public function get_groups_to_notify($post_id, $return = 'all')
        {
            // Workaround for the fact that get_object_terms doesn't return just slugs
            $fields = ($return == 'slugs') ? 'all' : $return;

            $group_terms = wp_get_object_terms($post_id, $this->notify_group_taxonomy, compact('fields'));

            if (!is_array($group_terms)) {
                $group_terms = [];
            } else {
                if ('slugs' == $return) {
                    $group_terms = array_column($group_terms, 'slug');
                }
            }

            return $group_terms;
        }

        /**
         * Gets a list of posts that a user is selected to be notified
         *
         * @param string|int $user user_login or id of user
         * @param array $args
         *
         * @return array $posts Posts a user is selected to be notified
         */
        public function get_user_to_notify_posts($user = 0, $args = null)
        {
            if (! $user) {
                $user = (int )wp_get_current_user()->ID;
            }

            if (is_int($user)) {
                $user = get_userdata($user)->user_login;
            }

            $post_args = [
                'tax_query' => [
                    [
                        'taxonomy' => $this->notify_user_taxonomy,
                        'field' => 'slug',
                        'terms' => $user,
                    ],
                ],
                'posts_per_page' => '10',
                'orderby' => 'modified',
                'order' => 'DESC',
                'post_status' => 'any',
            ];
            $post_args = apply_filters('pp_user_to_notify_posts_query_args', $post_args);
            $posts = get_posts($post_args);

            return $posts;
        }

        /**
         * Register settings for notifications so we can partially use the Settings API
         * (We use the Settings API for form generation, but not saving )
         *
         * @since 0.7
         */
        public function register_settings()
        {
            add_settings_section(
                $this->module->options_group_name . '_general',
                false,
                '__return_false',
                $this->module->options_group_name
            );
            add_settings_field(
                'post_types',
                __('Show the "Notify me" and "Stop notifying me" links for these post types:', 'publishpress'),
                [$this, 'settings_post_types_option'],
                $this->module->options_group_name,
                $this->module->options_group_name . '_general'
            );

            add_settings_field(
                'email_from',
                __('Email from:', 'publishpress'),
                [$this, 'settings_email_from_option'],
                $this->module->options_group_name,
                $this->module->options_group_name . '_general'
            );

            add_settings_field(
                'notify_author_by_default',
                __('Always notify the author of the content:', 'publishpress'),
                [$this, 'settings_notify_author_by_default_option'],
                $this->module->options_group_name,
                $this->module->options_group_name . '_general'
            );

            add_settings_field(
                'notify_current_user_by_default',
                __('Always notify users who have edited the content:', 'publishpress'),
                [$this, 'settings_notify_current_user_by_default_option'],
                $this->module->options_group_name,
                $this->module->options_group_name . '_general'
            );

            add_settings_field(
                'blacklisted_taxonomies',
                __('Blacklisted taxonomies for Notifications', 'publishpress'),
                [$this, 'settings_blacklisted_taxonomies_option'],
                $this->module->options_group_name,
                $this->module->options_group_name . '_general'
            );
        }

        /**
         * Chose the post types for notifications
         *
         * @since 0.7
         */
        public function settings_post_types_option()
        {
            global $publishpress;
            $publishpress->settings->helper_option_custom_post_type($this->module);
        }

        public function get_email_from()
        {
            if (! isset($this->module->options->email_from_name)) {
                $name = get_bloginfo('name');
            } else {
                $name = $this->module->options->email_from_name;
            }

            if (! isset($this->module->options->email_from)) {
                $email = get_bloginfo('admin_email');
            } else {
                $email = $this->module->options->email_from;
            }

            return [
                'name' => $name,
                'email' => $email,
            ];
        }

        /**
         * Field to customize the email for the "email from".
         */
        public function settings_email_from_option()
        {
            $email_from = $this->get_email_from();

            echo '<input
                    id="' . esc_attr($this->module->slug) . '_email_from_name"
                    type="text"
                    style="min-width: 300px"
                    placeholder="' . esc_attr(get_bloginfo('name')) . '"
                    name="' . esc_attr($this->module->options_group_name) . '[email_from_name]"
                    value="' . esc_attr($email_from['name']) . '" />
                </label>';
            echo '<br />';
            echo '<input
                    id="' . esc_attr($this->module->slug) . '_email_from"
                    type="email"
                    style="min-width: 300px"
                    placeholder="' . esc_attr(get_bloginfo('admin_email')) . '"
                    name="' . esc_attr($this->module->options_group_name) . '[email_from]"
                    value="' . esc_attr($email_from['email']) . '" />
                </label>';
        }

        /**
         * Field to choose between auto select author for notifications.
         */
        public function settings_notify_author_by_default_option()
        {
            $checked = '1';
            if (isset($this->module->options->notify_author_by_default)) {
                $checked = $this->module->options->notify_author_by_default;
            }

            echo '<input
                    id="' . esc_attr($this->module->slug) . '_notify_author_by_default"
                    type="checkbox"
                    name="' . esc_attr($this->module->options_group_name) . '[notify_author_by_default]"
                    value="1" ' . ((bool)$checked ? 'checked="checked"' : '') . '/>';
        }

        /**
         * Field to choose between auto select current user for notifications.
         */
        public function settings_notify_current_user_by_default_option()
        {
            $checked = '1';
            if (isset($this->module->options->notify_current_user_by_default)) {
                $checked = $this->module->options->notify_current_user_by_default;
            }

            echo '<input
                    id="' . esc_attr($this->module->slug) . '_notify_current_user_by_default"
                    type="checkbox"
                    name="' . esc_attr($this->module->options_group_name) . '[notify_current_user_by_default]"
                    value="1" ' . ((bool)$checked ? 'checked="checked"' : '') . '/>';
        }

        public function settings_blacklisted_taxonomies_option()
        {
            $blacklisted_taxonomies = isset($this->module->options->blacklisted_taxonomies)
                ? $this->module->options->blacklisted_taxonomies
                : '';
            ?>
            <div style="max-width: 300px;">
                <input
                        type="text"
                        id="<?php
                        echo esc_attr($this->module->slug); ?>_blacklisted_taxonomies"
                        name="<?php
                        echo esc_attr($this->module->options_group_name); ?>[blacklisted_taxonomies]"
                        value="<?php
                        echo esc_attr($blacklisted_taxonomies); ?>"
                        placeholder="<?php
                        esc_html_e('slug1,slug2', 'publishpress'); ?>"
                        style="width: 100%;"
                />

                <div style="margin-top: 5px;">
                    <p><?php
                        esc_html_e(
                            'Add a list of taxonomy-slugs separated by comma that should not be loaded by the Taxonomy content filter when adding a new Notification Workflow.',
                            'publishpress'
                        ); ?></p>
                </div>
            </div>
            <?php
        }

        /**
         * Validate our user input as the settings are being saved
         *
         * @since 0.7
         */
        public function settings_validate($new_options)
        {
            // Whitelist validation for the post type options
            if (! isset($new_options['post_types'])) {
                $new_options['post_types'] = [];
            }
            $new_options['post_types'] = $this->clean_post_type_options(
                $new_options['post_types'],
                $this->module->post_type_support
            );

            if (isset($new_options['email_from'])) {
                $new_options['email_from_name'] = filter_var($new_options['email_from_name'], FILTER_SANITIZE_STRING);
                $new_options['email_from'] = filter_var($new_options['email_from'], FILTER_SANITIZE_EMAIL);
            }


            if (isset($new_options['notify_author_by_default'])) {
                $new_options['notify_author_by_default'] = (bool)$new_options['notify_author_by_default'] ? '1' : '0';
            } else {
                $new_options['notify_author_by_default'] = '0';
            }


            if (isset($new_options['notify_current_user_by_default'])) {
                $new_options['notify_current_user_by_default'] = (bool)$new_options['notify_current_user_by_default'] ? '1' : '0';
            } else {
                $new_options['notify_current_user_by_default'] = '0';
            }

            return $new_options;
        }

        /**
         * Settings page for notifications
         *
         * @since 0.7
         */
        public function print_configure_view()
        {
            global $publishpress; ?>
            <form class="basic-settings"
                  action="<?php
                  echo esc_url(menu_page_url($this->module->settings_slug, false)); ?>" method="post">
                <?php
                settings_fields($this->module->options_group_name); ?>
                <?php
                do_settings_sections($this->module->options_group_name); ?>

                <?php
                foreach ($publishpress->class_names as $slug => $class_name) {
                    $mod_data = $publishpress->$slug->module;

                    if ($mod_data->autoload
                        || $mod_data->slug === $this->module->slug
                        || ! isset($mod_data->notification_options)
                        || $mod_data->options->enabled != 'on') {
                        continue;
                    }

                    echo '<input name="publishpress_module_name[]" type="hidden" value="' . esc_attr(
                            $mod_data->name
                        ) . '" />';

                    $publishpress->$slug->print_configure_view();
                } ?>

                <?php
                echo '<input name="publishpress_module_name[]" type="hidden" value="' . esc_attr($this->module->name) . '" />'; ?>
                <?php
                wp_nonce_field('edit-publishpress-settings');

                submit_button(null, 'primary', 'submit', false); ?>
            </form>
            <?php
        }

        /**
         * Gets a simple phrase containing the formatted date and time that the post is scheduled for.
         *
         * @param obj $post Post object
         *
         * @return str    $scheduled_datetime The scheduled datetime in human-readable format
         * @since 0.8
         *
         */
        private function get_scheduled_datetime($post)
        {
            $scheduled_ts = strtotime($post->post_date);

            $date = date_i18n(get_option('date_format'), $scheduled_ts);
            $time = date_i18n(get_option('time_format'), $scheduled_ts);

            return sprintf(__('%1$s at %2$s', 'publishpress'), $date, $time);
        }

        public function send_notification_status_update($args)
        {
            $post = $args['post'];
            $new_status = apply_filters('publishpress_notifications_status', $args['new_status'], $post);
            $old_status = apply_filters('publishpress_notifications_status', $args['old_status'], $post);

            // Get current user
            $current_user = wp_get_current_user();

            // Get current user
            $current_user = wp_get_current_user();

            $post_author = get_userdata($post->post_author);

            $blogname = get_option('blogname');

            $body = '';

            $post_id = $post->ID;
            $post_title = pp_draft_or_post_title($post_id);
            $post_type = get_post_type_object($post->post_type)->labels->singular_name;

            if (0 != $current_user->ID) {
                $current_user_display_name = $current_user->display_name;
                $current_user_email = sprintf('(%s )', $current_user->user_email);
            } else {
                $current_user_display_name = __('WordPress Scheduler', 'publishpress');
                $current_user_email = '';
            }

            // Email subject and first line of body
            // Set message subjects according to what action is being taken on the Post
            if ($old_status == 'new' || $old_status == 'auto-draft') {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(
                    __('[%1$s] New %2$s Created: "%3$s"', 'publishpress'),
                    $blogname,
                    $post_type,
                    $post_title
                );
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
                $body .= sprintf(
                        __('A new %1$s (#%2$s "%3$s" ) was created by %4$s %5$s', 'publishpress'),
                        $post_type,
                        $post_id,
                        $post_title,
                        $current_user->display_name,
                        $current_user->user_email
                    ) . "\r\n";
            } elseif ($new_status == 'trash') {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(
                    __('[%1$s] %2$s Trashed: "%3$s"', 'publishpress'),
                    $blogname,
                    $post_type,
                    $post_title
                );
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
                $body .= sprintf(
                        __('%1$s #%2$s "%3$s" was moved to the trash by %4$s %5$s', 'publishpress'),
                        $post_type,
                        $post_id,
                        $post_title,
                        $current_user_display_name,
                        $current_user_email
                    ) . "\r\n";
            } elseif ($old_status == 'trash') {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(
                    __('[%1$s] %2$s Restored (from Trash ): "%3$s"', 'publishpress'),
                    $blogname,
                    $post_type,
                    $post_title
                );
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
                $body .= sprintf(
                        __('%1$s #%2$s "%3$s" was restored from trash by %4$s %5$s', 'publishpress'),
                        $post_type,
                        $post_id,
                        $post_title,
                        $current_user_display_name,
                        $current_user_email
                    ) . "\r\n";
            } elseif ($new_status == 'future') {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(__('[%1$s] %2$s Scheduled: "%3$s"'), $blogname, $post_type, $post_title);
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email 6. scheduled date  */
                $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";
            } elseif ($new_status == 'publish') {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(
                    __('[%1$s] %2$s Published: "%3$s"', 'publishpress'),
                    $blogname,
                    $post_type,
                    $post_title
                );
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
                $body .= sprintf(
                        __('%1$s #%2$s "%3$s" was published by %4$s %5$s', 'publishpress'),
                        $post_type,
                        $post_id,
                        $post_title,
                        $current_user_display_name,
                        $current_user_email
                    ) . "\r\n";
            } elseif ($old_status == 'publish') {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(
                    __('[%1$s] %2$s Unpublished: "%3$s"', 'publishpress'),
                    $blogname,
                    $post_type,
                    $post_title
                );
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
                $body .= sprintf(
                        __('%1$s #%2$s "%3$s" was unpublished by %4$s %5$s', 'publishpress'),
                        $post_type,
                        $post_id,
                        $post_title,
                        $current_user_display_name,
                        $current_user_email
                    ) . "\r\n";
            } else {
                /* translators: 1: site name, 2: post type, 3. post title */
                $subject = sprintf(
                    __('[%1$s] %2$s Status Changed for "%3$s"', 'publishpress'),
                    $blogname,
                    $post_type,
                    $post_title
                );
                /* translators: 1: post type, 2: post id, 3. post title, 4. user name, 5. user email */
                $body .= sprintf(
                        __('Status was changed for %1$s #%2$s "%3$s" by %4$s %5$s', 'publishpress'),
                        $post_type,
                        $post_id,
                        $post_title,
                        $current_user_display_name,
                        $current_user_email
                    ) . "\r\n";
            }

            /* translators: 1: date, 2: time, 3: timezone */
            $body .= sprintf(
                    __('This action was taken on %1$s at %2$s %3$s', 'publishpress'),
                    date_i18n(get_option('date_format')),
                    date_i18n(get_option('time_format')),
                    get_option('timezone_string')
                ) . "\r\n";

            $old_status_friendly_name = $this->get_post_status_friendly_name($old_status);
            $new_status_friendly_name = $this->get_post_status_friendly_name($new_status);

            // Email body
            $body .= "\r\n";
            /* translators: 1: old status, 2: new status */
            $body .= sprintf(
                __('%1$s => %2$s', 'publishpress'),
                $old_status_friendly_name,
                $new_status_friendly_name
            );
            $body .= "\r\n\r\n";

            $body .= "--------------------\r\n\r\n";

            $body .= sprintf(__('== %s Details ==', 'publishpress'), $post_type) . "\r\n";
            $body .= sprintf(__('Title: %s', 'publishpress'), $post_title) . "\r\n";
            if (! empty($post_author)) {
                /* translators: 1: author name, 2: author email */
                $body .= sprintf(
                        __('Author: %1$s (%2$s )', 'publishpress'),
                        $post_author->display_name,
                        $post_author->user_email
                    ) . "\r\n";
            }

            $admin_path = 'post.php?post=' . $post_id . '&action=edit';
            $edit_link = htmlspecialchars_decode(admin_url($admin_path));
            if ($new_status != 'publish') {
                $view_link = add_query_arg(['preview' => 'true'], wp_get_shortlink($post_id));
            } else {
                $view_link = htmlspecialchars_decode(get_permalink($post_id));
            }
            $body .= "\r\n";
            $body .= __('== Actions ==', 'publishpress') . "\r\n";
            $body .= sprintf(
                    __('Add editorial comment: %s', 'publishpress'),
                    $edit_link . '#editorialcomments/add'
                ) . "\r\n";
            $body .= sprintf(__('Edit: %s', 'publishpress'), $edit_link) . "\r\n";
            $body .= sprintf(__('View: %s', 'publishpress'), $view_link) . "\r\n";

            $body .= $this->get_notification_footer($post);

            $this->send_email('status-change', $post, $subject, $body);
        }

        public function send_notification_comment($args)
        {
            /* translators: 1: blog name, 2: post title */
            $subject = sprintf(
                __('[%1$s] New Editorial Comment: "%2$s"', 'publishpress'),
                $args['blogname'],
                $args['post_title']
            );

            /* translators: 1: post id, 2: post title, 3. post type */
            $body = sprintf(
                    __('A new editorial comment was added to %3$s #%1$s "%2$s"', 'publishpress'),
                    $args['post_id'],
                    $args['post_title'],
                    $args['post_type']
                ) . "\r\n\r\n";
            /* translators: 1: comment author, 2: author email, 3: date, 4: time */
            $body .= sprintf(
                    __('%1$s (%2$s ) said on %3$s at %4$s:', 'publishpress'),
                    $args['current_user']->display_name,
                    $args['current_user']->user_email,
                    mysql2date(get_option('date_format'), $args['comment']->comment_date),
                    mysql2date(get_option('time_format'), $args['comment']->comment_date)
                ) . "\r\n";
            $body .= "\r\n" . $args['comment']->comment_content . "\r\n";

            // @TODO: mention if it was a reply
            /*
            if( $parent ) {

            }
            */

            $body .= "\r\n--------------------\r\n";

            $admin_path = 'post.php?post=' . $args['post_id'] . '&action=edit';
            $edit_link = htmlspecialchars_decode(admin_url($admin_path));
            $view_link = htmlspecialchars_decode(get_permalink($args['post_id']));

            $body .= "\r\n";
            $body .= __('== Actions ==', 'publishpress') . "\r\n";
            $body .= sprintf(
                    __('Reply: %s', 'publishpress'),
                    $edit_link . '#editorialcomments/reply/' . $args['comment']->comment_ID
                ) . "\r\n";
            $body .= sprintf(
                    __('Add editorial comment: %s', 'publishpress'),
                    $edit_link . '#editorialcomments/add'
                ) . "\r\n";
            $body .= sprintf(__('Edit: %s', 'publishpress'), $edit_link) . "\r\n";
            $body .= sprintf(__('View: %s', 'publishpress'), $view_link) . "\r\n";

            $body .= "\r\n" . sprintf(
                    __('You can see all editorial comments on this %s here: ', 'publishpress'),
                    $args['post_type']
                ) . "\r\n";
            $body .= $edit_link . "#editorialcomments" . "\r\n\r\n";

            $body .= $this->get_notification_footer($args['post']);

            //attach files to email
            $attachments = [];
            $comment_files = get_comment_meta($args['comment']->comment_ID, '_pp_editorial_comment_files', true);
            if (!empty($comment_files)) {
                $comment_files = explode(" ", $comment_files);
                $comment_files = array_filter($comment_files);
                foreach ($comment_files as $comment_file_id) {
                    $media_file = wp_get_attachment_url($comment_file_id);
                    if (!is_wp_error($media_file) && !empty($media_file)) {
                        $attachments[] = $media_file;
                    }
                }
            }

            $this->send_email('comment', $args['post'], $subject, $body, '', null, $attachments);
        }

        public static function getOption($option_name)
        {
            $is_module_enabled = PP_Module::isPublishPressModuleEnabled(self::MODULE_NAME);
            if (! $is_module_enabled) {
                return null;
            }

            global $publishpress;

            $module_options = $publishpress->{self::MODULE_NAME}->module->options;
            if (! isset($module_options->{$option_name})) {
                return null;
            }

            return $module_options->{$option_name};
        }
    }
}