diff --git a/modules/calendar/calendar.php b/modules/calendar/calendar.php index 08c806ec..2a983d58 100644 --- a/modules/calendar/calendar.php +++ b/modules/calendar/calendar.php @@ -91,6 +91,8 @@ class PP_Calendar extends PP_Module * @var [type] */ public $module; + + public $module_url; /** * [$start_date description] @@ -157,6 +159,42 @@ class PP_Calendar extends PP_Module * @var \PublishPress\Utility\Date */ private $dateUtil; + + /** + * @var array + */ + public $filters; + + /** + * @var array + */ + public $form_filters = []; + + /** + * @var array + */ + public $form_filter_list = []; + + /** + * [$user_filters description] + * + * @var [type] + */ + public $user_filters; + + /** + * Custom methods + * + * @var array + */ + private $terms_options = []; + + /** + * [$content_calendar_datas description] + * + * @var [type] + */ + public $content_calendar_datas; /** * Construct the PP_Calendar class @@ -176,15 +214,16 @@ public function __construct() 'post_type_support' => 'pp_calendar', 'default_options' => [ 'enabled' => 'on', - 'post_types' => [ - 'post' => 'on', - 'page' => 'off', - ], 'ics_subscription' => 'on', + 'post_types' => $this->pre_select_all_post_types(), + 'ics_subscription' => 'on', 'ics_subscription_public_visibility' => 'off', 'ics_secret_key' => wp_generate_password(), 'show_posts_publish_time' => ['publish' => 'on', 'future' => 'on'], 'default_publish_time' => '', 'show_calendar_posts_full_title' => 'off', + // Leave default as non array to confirm if user save settings or not + 'content_calendar_filters' => '', + 'content_calendar_custom_filters' => '', ], 'messages' => [ 'post-date-updated' => __('Post date updated.', 'publishpress'), @@ -648,65 +687,9 @@ public function enqueue_admin_scripts() /* * Filters */ - $userFilters = $this->get_filters(); - $calendar_request_args = []; - $calendar_request_filter = []; - - if (isset($userFilters['post_status'])) { - $postStatus = sanitize_text_field($userFilters['post_status']); - - if (! empty($postStatus)) { - $calendar_request_args['post_status'] = $postStatus; - $calendar_request_filter['post_status'] = $postStatus; - } - } - - if (isset($userFilters['cat']) && !empty($userFilters['cat'])) { - $category = (int) $userFilters['cat']; - $categoryData = get_term_by('ID', $category, 'category'); - - if (isset($categoryData->slug)) { - $calendar_request_args = $this->addTaxQueryToArgs('category', $categoryData->slug, $calendar_request_args); - $calendar_request_filter['category'] = ['value' => $categoryData->slug, 'text' => $categoryData->name]; - } - } - - if (isset($userFilters['tag']) && !empty($userFilters['tag'])) { - $postTag = (int) $userFilters['tag']; - $tagData = get_term_by('ID', $postTag, 'post_tag'); - - if (isset($tagData->slug)) { - $calendar_request_args = $this->addTaxQueryToArgs('post_tag', $tagData->slug, $calendar_request_args); - $calendar_request_filter['post_tag'] = ['value' => $tagData->slug, 'text' => $tagData->name]; - } - } - - if (isset($userFilters['author']) && !empty($userFilters['author'])) { - $postAuthor = (int)$userFilters['author']; - $authorData = get_user_by('ID', $postAuthor); - - if (isset($authorData->ID)) { - $calendar_request_args['author'] = $authorData->ID; - $calendar_request_filter['post_author'] = ['value' => $authorData->ID, 'text' => $authorData->display_name]; - } - } - - if (isset($userFilters['cpt'])) { - $postType = sanitize_key($userFilters['cpt']); - - if (!empty($postType)) { - $calendar_request_args['post_type'] = $postType; - $calendar_request_filter['post_type'] = $postType; - } - } - - if (isset($userFilters['weeks'])) { - $weeks = sanitize_key($userFilters['weeks']); - - if (! empty($weeks)) { - $calendar_request_filter['weeks'] = $weeks; - } - } + $userFilters = $this->get_filters(); + $calendar_request_args = $userFilters; + $calendar_request_filter = $userFilters; $maxVisibleItemsOption = isset($this->module->options->max_visible_posts_per_date) && ! empty($this->default_max_visible_posts_per_date) ? (int)$this->module->options->max_visible_posts_per_date : $this->default_max_visible_posts_per_date; @@ -1203,6 +1186,7 @@ public function get_filters() 'tag' => '', 'author' => '', 'start_date' => date('Y-m-d', current_time('timestamp')), + 'me_mode' => '', ]; $old_filters = array_merge($default_filters, (array)$old_filters); @@ -1226,6 +1210,10 @@ public function get_filters() // Set the start date as the beginning of the week, according to blog settings $filters['start_date'] = $this->get_beginning_of_week($filters['start_date']); + if (!empty($filters['me_mode'])) { + $filters['author'] = $current_user->ID; + } + $filters = apply_filters('pp_calendar_filter_values', $filters, $old_filters); $this->update_user_meta($current_user->ID, self::USERMETA_KEY_PREFIX . 'filters', $filters); @@ -1274,6 +1262,920 @@ protected function get_selected_post_types() return $return; } + public function content_calendar_filters() + { + $select_filter_names = []; + + $editorial_metadata = $this->terms_options; + + foreach ($this->filters as $filter_key => $filter_label) { + if (array_key_exists($filter_key, $editorial_metadata) && $editorial_metadata[$filter_key]['type'] === 'date') { + $select_filter_names[$filter_key . '_start'] = $filter_key . '_start'; + $select_filter_names[$filter_key . '_end'] = $filter_key . '_end'; + $select_filter_names[$filter_key . '_start_hidden'] = $filter_key . '_start_hidden'; + $select_filter_names[$filter_key . '_end_hidden'] = $filter_key . '_end_hidden'; + } + $select_filter_names[$filter_key] = $filter_key; + } + + return apply_filters('PP_Content_Calendar_filter_names', $select_filter_names); + } + + public function content_calendar_filter_options($select_id, $select_name, $filters) + { + + if (array_key_exists($select_id, $this->terms_options)) { + $select_id = 'metadata_key'; + } + + if (array_key_exists($select_id, $this->content_calendar_datas['taxonomies']) && taxonomy_exists($select_id)) { + $select_id = 'taxonomy'; + } + + $filter_label = ''; + $selected_value = ''; + + ob_start(); + + switch ($select_id) { + case 'post_status': + $post_statuses = $this->get_post_statuses(); + $filter_label = esc_html__('Post Status', 'publishpress'); + ?> + + label); + ?> + + + + + + + + terms_options[$select_name]; + + $metadata_type = $metadata_term['type']; + $selected_value = $metadata_value; + $filter_label = $metadata_term['name']; + + ?> +
+
+ +
+
+ + +
+ 'filter-submit']); ?> +
+ + + + + + + + + ', + esc_attr($metadata_start_name), + esc_attr($metadata_start_value), + esc_attr(pp_convert_date_format_to_jqueryui_datepicker('Y-m-d')), + '' + ); + printf( + '', + esc_attr($metadata_start_name), + esc_attr($metadata_start_value_hidden) + ); + ?> +
+ ', + esc_attr($metadata_end_name), + esc_attr($metadata_end_value), + esc_attr(pp_convert_date_format_to_jqueryui_datepicker('Y-m-d')), + '' + ); + printf( + '', + esc_attr($metadata_end_name), + esc_attr($metadata_end_value_hidden) + ); + ?> +
+ 'filter-submit']); ?> +
+ display_name; + } + } + $user_dropdown_args = [ + 'show_option_all' => $metadata_term['name'], + 'name' => $select_name, + 'selected' => $metadata_value, + 'class' => 'pp-custom-select2' + ]; + $user_dropdown_args = apply_filters('pp_editorial_metadata_user_dropdown_args', $user_dropdown_args); + wp_dropdown_users($user_dropdown_args); + } elseif ($metadata_type === 'checkbox') { + if ($metadata_value == '1') { + $selected_value = esc_html__('Checked', 'publishpress'); + } else { + $selected_value = ''; + } + ?> + + + /> +
+ 'filter-submit']); ?> +
+
'; + break; + + default: + if (array_key_exists($select_name, $this->form_filter_list)) { + $selected_value_meta = isset($filters[$select_name]) ? sanitize_text_field($filters[$select_name]) : ''; + $filter_label = $this->filters[$select_name]; + $selected_value = $selected_value_meta; + + if (strpos($select_name, "ppch_co_checklist_") === 0) { + ?> + + +
+
+ +
+
+ + +
+ 'filter-submit']); ?> +
+ +
+
+ $selected_value, 'filter_label' => $filter_label, 'html' => ob_get_clean()]; + } + + private function meta_query_operator_label($operator = false) { + $operators = [ + 'equals' => 'Equals (=)', + 'not_equals' => 'Does not equal (!=)', + 'greater_than' => 'Greater than (>)', + 'greater_than_or_equals' => 'Greater than or equals (>=)', + 'less_than' => 'Less than (<)', + 'less_than_or_equals' => 'Less than or equals (<=)', + 'like' => 'Like/Contains', + 'not_like' => 'Not Like', + 'not_exists' => 'Not Exists/Empty', + ]; + + if ($operator) { + $return = array_key_exists($operator, $operators) ? $operators[$operator] : $operator; + } else { + $return = $operators; + } + + return $return; + } + + private function meta_query_operator_symbol($operator = false) { + $operators = [ + 'equals' => '=', + 'not_equals' => '!=', + 'greater_than' => '>', + 'greater_than_or_equals' => '>=', + 'less_than' => '<', + 'less_than_or_equals' => '<=', + 'like' => 'LIKE', + 'not_like' => 'NOT LIKE', + 'not_exists' => 'NOT EXISTS', + ]; + + if ($operator) { + $return = array_key_exists($operator, $operators) ? $operators[$operator] : $operator; + } else { + $return = $operators; + } + + return $return; + } + + /** + * Return calendar filters + * @return string + */ + public function get_calendar_filters() { + ob_start(); + ?> +
+
+ user_filters['me_mode']; + $active_me_mode = !empty($me_mode) ? 'active-filter' : ''; + ?> +
+ +
+ +
+ +
+ +
+
+
+ +
+
+
+
+
+ + +
+ user_filters['weeks']) ? (int)$this->user_filters['weeks'] : self::DEFAULT_NUM_WEEKS; + $modal_id++; + ?> + + + content_calendar_filters() as $select_id => $select_name) { + $modal_id++; + $filter_data = $this->content_calendar_filter_options($select_id, $select_name, $this->user_filters); + $active_class = !empty($filter_data['selected_value']) ? 'active-filter' : ''; + $button_label = $filter_data['filter_label']; + $button_label .= !empty($filter_data['selected_value']) ? ': ' . $filter_data['selected_value'] : ''; + ?> + + + + + + + + +
+
+ +
+ + + + + content_calendar_filters() as $select_id => $select_name) { + echo ''; + } ?> +
+ update_module_option($this->module->name, 'content_calendar_filters', $content_calendar_filters); + $publishpress->update_module_option($this->module->name, 'content_calendar_custom_filters', $content_calendar_custom_filters); + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo pp_planner_admin_notice(esc_html__('Filter updated successfully.', 'publishpress')); + } + } + + /** + * Retrieve wordpress registered taxonomy + * + * Private taxonomy are excluded + */ + public function get_all_taxonomies() + { + + //category and post tag are not included in public taxonomy + $category = get_taxonomies(['name' => 'category'], 'objects'); + $post_tag = get_taxonomies(['name' => 'post_tag'], 'objects'); + + $public = get_taxonomies(['_builtin' => false, 'public' => true], 'objects'); + + $taxonomies = array_merge($category, $post_tag, $public); + + return $taxonomies; + } + + /** + * Get content calendar data that's required on the + * content calendar page + * + * @return array + */ + public function get_content_calendar_datas() { + global $wpdb; + + if (is_array($this->content_calendar_datas)) { + return $this->content_calendar_datas; + } + + $datas = []; + + // add all meta keys + $datas['meta_keys'] = $wpdb->get_col("SELECT DISTINCT meta_key FROM $wpdb->postmeta WHERE 1=1 ORDER BY meta_key ASC"); + + // add editorial fields + if (class_exists('PP_Editorial_Metadata')) { + $additional_terms = get_terms( + [ + 'taxonomy' => PP_Editorial_Metadata::metadata_taxonomy, + 'orderby' => 'name', + 'order' => 'asc', + 'hide_empty' => 0, + 'parent' => 0, + 'fields' => 'all', + ] + ); + + $metadatas = []; + foreach ($additional_terms as $term) { + if (! is_object($term) || $term->taxonomy !== PP_Editorial_Metadata::metadata_taxonomy) { + continue; + } + $metadatas[$term->slug] = $term->name; + + $term_options = $this->get_unencoded_description($term->description); + $term_options['name'] = $term->name; + $term_options['slug'] = $term->slug; + $this->terms_options[$term->slug] = $term_options; + } + + $datas['editorial_metadata'] = $metadatas; + } + + // add taxononomies + $taxonomies = $this->get_all_taxonomies(); + $all_taxonomies = []; + foreach ($taxonomies as $taxonomy) { + if (in_array($taxonomy->name, ['post_status', 'post_status_core_wp_pp', 'post_visibility_pp'])) { + continue; + } + $all_taxonomies[$taxonomy->name] = $taxonomy->label;// . ' (' . $taxonomy->name . ')'; + } + $datas['taxonomies'] = $all_taxonomies; + + // Add content calendar filters content + $content_calendar_filters = $this->module->options->content_calendar_filters; + $content_calendar_custom_filters = $this->module->options->content_calendar_custom_filters; + + $datas['content_calendar_filters'] = is_array($content_calendar_filters) ? $content_calendar_filters : [ + 'post_status' => esc_html__('Status', 'publishpress'), + 'author' => esc_html__('Author', 'publishpress'), + 'cpt' => esc_html__('Post Type', 'publishpress') + ]; + $datas['content_calendar_custom_filters'] = is_array($content_calendar_custom_filters) ? $content_calendar_custom_filters : []; + + /** + * @param array $datas + * + * @return $datas + */ + $datas = apply_filters('publishpress_content_calendar_datas', $datas); + + $this->content_calendar_datas = $datas; + + return $datas; + } + + + /** + * Get content calendar form filters + * + * @return array + */ + public function get_content_calendar_form_filters() { + + if (!empty($this->form_filters)) { + return $this->form_filters; + } + + $content_calendar_datas = $this->content_calendar_datas; + + $filters = []; + // custom filters + $filters['custom'] = [ + 'title' => esc_html__('Custom filters', 'publishpress'), + 'message' => esc_html__('Click the "Add New" button to create new filters.', 'publishpress'), + 'filters' => $content_calendar_datas['content_calendar_custom_filters'] + ]; + + // default filters + $filters['default'] = [ + 'title' => esc_html__('Inbuilt filters', 'publishpress'), + 'filters' => [ + 'post_status' => esc_html__('Post Status', 'publishpress'), + 'author' => esc_html__('Author', 'publishpress'), + 'cpt' => esc_html__('Post Type', 'publishpress') + ] + ]; + + // editorial fields filters + if (isset($content_calendar_datas['editorial_metadata'])) { + $filters['editorial_metadata'] = [ + 'title' => esc_html__('Editorial Fields', 'publishpress'), + 'message' => esc_html__('You do not have any editorial fields enabled', 'publishpress'), + 'filters' => $content_calendar_datas['editorial_metadata'] + ]; + } + + $filters['taxonomies'] = [ + 'title' => esc_html__('Taxonomies', 'publishpress'), + 'message' => esc_html__('You do not have any public taxonomies', 'publishpress'), + 'filters' => $content_calendar_datas['taxonomies'] + ]; + + /** + * @param array $filters + * @param array $content_calendar_datas + * + * @return $filters + */ + $filters = apply_filters('publishpress_content_calendar_form_filters', $filters, $content_calendar_datas); + + $this->form_filters = $filters; + + return $filters; + } + + + public function content_calendar_customize_filter_form() { + + ob_start(); + + $content_calendar_datas = $this->content_calendar_datas; + $enabled_filters = array_keys($content_calendar_datas['content_calendar_filters']); + $filters = $this->form_filters; + $meta_keys = $content_calendar_datas['meta_keys']; + + $all_filters = []; + ?> +
+ + +
+
+
+
+
+
+
+

+
+
+ $filter_datas) : + $filter_index++; + ?> +
+
+ +
+ +
+ +
+ + + + +
+ + $filter_label) : + $active_class = (in_array($filter_name, $enabled_filters)) ? 'active-item' : ''; + $input_name = (in_array($filter_name, $enabled_filters)) ? 'content_calendar_filters['. $filter_name .']' : ''; + + $all_filters[$filter_name] = [ + 'filter_label' => $filter_label, + 'filter_group' => $filter_group + ]; + ?> +
+ + + + +
+ +
+
+ +
+
+
()
+
+ +
+ +
+ +
+ + + +
+
+ + +
+
+ get_post_types_for_module($this->module); + // update content calendar form action + $this->update_content_calendar_form_action(); + + // Get content calendar data + $this->content_calendar_datas = $this->get_content_calendar_datas(); + + $filters = $this->content_calendar_datas['content_calendar_filters']; + /** + * @param array $filters + * + * @return array + */ + $this->filters = apply_filters('publishpress_content_calendar_filters', $filters); + + $this->form_filters = $this->get_content_calendar_form_filters(); + $this->form_filter_list = array_merge(...array_values(array_column($this->form_filters, 'filters'))); // Get filters either from $_GET or from user settings - $filters = $this->get_filters(); + $this->user_filters = $this->get_filters(); // Total number of weeks to display on the calendar. Run it through a filter in case we want to override the // user's standard - $this->total_weeks = empty($filters['weeks']) ? self::DEFAULT_NUM_WEEKS : $filters['weeks']; + $this->total_weeks = empty($this->user_filters['weeks']) ? self::DEFAULT_NUM_WEEKS : $this->user_filters['weeks']; - $this->start_date = $filters['start_date']; + $this->start_date = $this->user_filters['start_date']; // Get the custom description for this page $description = ''; @@ -1466,6 +2383,10 @@ class="button-primary"> echo '

'; } ?> +
+ get_calendar_filters(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> +
+
@@ -1494,7 +2415,19 @@ class="button-primary">
+ wp_create_nonce('publishpress-calendar-get-data'), + 'moduleUrl' => $this->module_url, + ] + ); + $publishpress->settings->print_default_footer($publishpress->modules->calendar); // phpcs:enable } @@ -2137,8 +3070,6 @@ public function getCalendarDataForMultipleWeeks($args = [], $context = 'dashboar $supported_post_types = $this->get_post_types_for_module($this->module); $defaults = [ 'post_status' => null, - 'cat' => null, - 'tag' => null, 'author' => null, 'post_type' => $supported_post_types, 'posts_per_page' => -1, @@ -2147,6 +3078,197 @@ public function getCalendarDataForMultipleWeeks($args = [], $context = 'dashboar $args = array_merge($defaults, $args); + if (isset($args['s']) && ! empty($args['s'])) { + $args['s'] = sanitize_text_field($args['s']); + } + + $current_user_id = get_current_user_id(); + + $this->user_filters = $this->get_filters(); + + // Get content calendar data + $this->content_calendar_datas = $this->get_content_calendar_datas(); + + $filters = $this->content_calendar_datas['content_calendar_filters']; + /** + * @param array $filters + * + * @return array + */ + $this->filters = apply_filters('publishpress_content_calendar_filters', $filters); + + $enabled_filters = array_keys($this->filters); + $editorial_metadata = $this->terms_options; + + if (!empty($args['cpt'])) { + $args['post_type'] = $args['cpt']; + } + + + if (empty($args['post_type']) || ! in_array($args['post_type'], $supported_post_types)) { + $args['post_type'] = $supported_post_types; + } + + //remove inactive builtin filter + if (!in_array('cpt', $enabled_filters)) { + // show all post type + $args['post_type'] = $supported_post_types; + } + + if (!in_array('author', $enabled_filters)) { + unset($args['author']); + } + + $meta_query = $tax_query = ['relation' => 'AND']; + $metadata_filter = $taxonomy_filter = false; + $checklists_filters = []; + + // apply enabled filter + foreach ($enabled_filters as $enabled_filter) { + if (array_key_exists($enabled_filter, $editorial_metadata)) { + //metadata field filter + $meta_key = $enabled_filter; + $metadata_term = $editorial_metadata[$meta_key]; + unset($args[$enabled_filter]); + if ($metadata_term['type'] === 'date') { + $date_type_metaquery = []; + + if (! empty($this->user_filters[$meta_key . '_start'])) { + $date_type_metaquery[] = strtotime($this->user_filters[$meta_key . '_start_hidden']); + } + if (! empty($this->user_filters[$meta_key . '_end'])) { + $date_type_metaquery[] = strtotime($this->user_filters[$meta_key . '_end_hidden']); + } + if (count($date_type_metaquery) === 2) { + $metadata_filter = true; + $compare = 'BETWEEN'; + $meta_value = $date_type_metaquery; + } elseif (count($date_type_metaquery) === 1) { + $metadata_filter = true; + $compare = '='; + $meta_value = $date_type_metaquery[0]; + } + + if (!empty($date_type_metaquery)) { + $metadata_filter = true; + $meta_query[] = array( + 'key' => '_pp_editorial_meta_' . $metadata_term['type'] . '_' . $metadata_term['slug'], + 'value' => $meta_value, + 'compare' => $compare + ); + } + + } elseif (! empty($this->user_filters[$meta_key])) { + if ($metadata_term['type'] === 'date') { + continue; + } else { + $meta_value = sanitize_text_field($this->user_filters[$meta_key]); + } + + $compare = '='; + if ($metadata_term['type'] === 'paragraph' + || ($metadata_term['type'] === 'select' && isset($metadata_term->select_type) && $metadata_term['select_type'] === 'multiple') + ) { + $compare = 'LIKE'; + } + $metadata_filter = true; + $meta_query[] = array( + 'key' => '_pp_editorial_meta_' . $metadata_term['type'] . '_' . $metadata_term['slug'], + 'value' => $meta_value, + 'compare' => $compare + ); + } + + } elseif( + in_array($enabled_filter, $this->content_calendar_datas['meta_keys']) + && ( + isset($this->user_filters[$enabled_filter]) + && + ( + !empty($this->user_filters[$enabled_filter]) + || $this->user_filters[$enabled_filter] == '0' + || ( + !empty($this->user_filters[$enabled_filter . '_operator']) + && $this->user_filters[$enabled_filter . '_operator'] === 'not_exists' + ) + ) + ) + ) { + // metakey filter + unset($args[$enabled_filter]); + $meta_value = sanitize_text_field($this->user_filters[$enabled_filter]); + $meta_operator = !empty($this->user_filters[$enabled_filter . '_operator']) ? $this->user_filters[$enabled_filter . '_operator'] : 'equals'; + $compare = $this->meta_query_operator_symbol($meta_operator); + + $metadata_filter = true; + + if ($meta_operator == 'not_exists') { + $meta_query[] = array( + 'relation' => 'OR', + array( + 'key' => $enabled_filter, + 'compare' => 'NOT EXISTS' + ), + array( + 'key' => $enabled_filter, + 'value' => '', + 'compare' => '=' + ) + ); + } else { + $meta_query[] = array( + 'key' => $enabled_filter, + 'value' => $meta_value, + 'compare' => $compare + ); + } + } elseif (in_array($enabled_filter, ['ppch_co_yoast_seo__yoast_wpseo_linkdex', 'ppch_co_yoast_seo__yoast_wpseo_content_score']) && !empty($this->user_filters[$enabled_filter]) && array_key_exists($enabled_filter, $this->form_filter_list) && class_exists('WPSEO_Meta')) { + // yoast seo filter + unset($args[$enabled_filter]); + $meta_value = sanitize_text_field($this->user_filters[$enabled_filter]); + $meta_key = str_replace('ppch_co_yoast_seo_', '', $enabled_filter); + $meta_operator = !empty($this->user_filters[$enabled_filter . '_operator']) ? $this->user_filters[$enabled_filter . '_operator'] : 'equals'; + $compare = $this->meta_query_operator_symbol($meta_operator); + $metadata_filter = true; + $meta_query[] = array( + 'key' => $meta_key, + 'value' => $meta_value, + 'compare' => $compare + ); + + } elseif(array_key_exists($enabled_filter, $this->content_calendar_datas['taxonomies']) && !empty($this->user_filters[$enabled_filter])) { + //taxonomy filter + unset($args[$enabled_filter]); + $tax_value = sanitize_text_field($this->user_filters[$enabled_filter]); + $taxonomy_filter = true; + $tax_query[] = array( + 'taxonomy' => $enabled_filter, + 'field' => 'slug', + 'terms' => [$tax_value], + 'include_children' => true, + 'operator' => 'IN', + ); + } elseif(!empty($this->user_filters[$enabled_filter]) && strpos($enabled_filter, "ppch_co_checklist_") === 0 && array_key_exists($enabled_filter, $this->form_filter_list)) { + // checklists filter + /** + * TODO: Implement metaquery filter when checklists started storing checklists status in meta_key + */ + unset($args[$enabled_filter]); + $meta_value = sanitize_text_field($this->user_filters[$enabled_filter]); + $meta_key = str_replace('ppch_co_checklist_', '', $enabled_filter); + $checklists_filters[$meta_key] = $meta_value; + } + + } + + if ($metadata_filter) { + $args['meta_query'] = $meta_query; + } + + if ($taxonomy_filter) { + $args['tax_query'] = $tax_query; + } + // Unpublished as a status is just an array of everything but 'publish' if ($args['post_status'] == 'unpublish') { $args['post_status'] = ''; @@ -2160,25 +3282,21 @@ public function getCalendarDataForMultipleWeeks($args = [], $context = 'dashboar $args['post_status'] .= ', future'; } } - // The WP functions for printing the category and author assign a value of 0 to the default - // options, but passing this to the query is bad (trashed and auto-draft posts appear!), so - // unset those arguments. - if ($args['cat'] === '0') { + // unset legacy options + if (isset($args['cat'])) { unset($args['cat']); } - if ($args['tag'] === '0') { - unset($args['tag']); - } else { - $args['tag_id'] = $args['tag']; + if (isset($args['tag'])) { unset($args['tag']); } - if ($args['author'] === '0') { - unset($args['author']); + if (!empty($args['me_mode']) && !empty($current_user_id)) { + $args['author'] = $current_user_id; } - if (empty($args['post_type']) || ! in_array($args['post_type'], $supported_post_types)) { - $args['post_type'] = $supported_post_types; + // Filter by post_author if it's set + if (isset($args['author']) && empty($args['author'])) { + unset($args['author']); } // Filter for an end user to implement any of their own query args @@ -2187,15 +3305,39 @@ public function getCalendarDataForMultipleWeeks($args = [], $context = 'dashboar if (isset($this->module->options->sort_by)) { add_filter('posts_orderby', [$this, 'filterPostsOrderBy'], 10); } - +write_log($args); $post_results = new WP_Query($args); $posts = []; while ($post_results->have_posts()) { $post_results->the_post(); global $post; - $key_date = date('Y-m-d', strtotime($post->post_date)); - $posts[$key_date][] = $post; + + $add_post = true; + + if (!empty($checklists_filters)) { + $post_checklists = apply_filters('publishpress_checklists_requirement_list', [], $post); + foreach ($checklists_filters as $checklists_filter_name => $checklists_filter_check) { + if (!array_key_exists($checklists_filter_name, $post_checklists)) { + // post that doesn't have this requirement shouldn't show? + $add_post = false; + } elseif ($checklists_filter_check == 'passed' && empty($post_checklists[$checklists_filter_name]['status'])) { + // filter posts that failed when condition is passed + $add_post = false; + } elseif ($checklists_filter_check == 'failed' && !empty($post_checklists[$checklists_filter_name]['status'])) { + // filter out post that passed when condition is failed + $add_post = false; + } + } + } + + if ($add_post) { + /** + * TODO: Should we require posts like x2 if results is empty due to $add_post been false for all? + */ + $key_date = date('Y-m-d', strtotime($post->post_date)); + $posts[$key_date][] = $post; + } } if (isset($this->module->options->sort_by)) { @@ -3259,7 +4401,6 @@ public function fetchCalendarDataJson() $beginningDate = $this->get_beginning_of_week(sanitize_text_field($_GET['start_date'])); $endingDate = $this->get_ending_of_week($beginningDate, 'Y-m-d', (int)$_GET['number_of_weeks']); - $args = []; $request_filter = [ 'weeks' => self::DEFAULT_NUM_WEEKS, 'post_status' => '', @@ -3270,67 +4411,16 @@ public function fetchCalendarDataJson() 'start_date' => date('Y-m-d', current_time('timestamp')), ]; + $clean_args = map_deep($_GET, 'sanitize_text_field'); + + $request_filter = array_merge($request_filter, $clean_args); /* * Filters */ - if (isset($_GET['post_status'])) { - $postStatus = sanitize_text_field($_GET['post_status']); - - if (! empty($postStatus)) { - $args['post_status'] = $postStatus; - $request_filter['post_status'] = $postStatus; - } - } - - if (isset($_GET['category'])) { - $category = sanitize_key($_GET['category']); - - if (! empty($category)) { - $categoryData = get_term_by('slug', $category, 'category'); - $args = $this->addTaxQueryToArgs('category', $category, $args); - $request_filter['cat'] = isset($categoryData->term_id) ? $categoryData->term_id : ''; - } - } - - if (isset($_GET['post_tag'])) { - $postTag = sanitize_key($_GET['post_tag']); - - if (! empty($postTag)) { - $tag = get_term_by('slug', $postTag, 'post_tag'); - $args = $this->addTaxQueryToArgs('post_tag', $postTag, $args); - $request_filter['tag'] = isset($tag->term_id) ? $tag->term_id : ''; - } - } - - if (isset($_GET['post_author'])) { - $postAuthor = (int)$_GET['post_author']; - - if (! empty($postAuthor)) { - $args['author'] = $postAuthor; - $request_filter['author'] = $postAuthor; - } - } - - if (isset($_GET['post_type'])) { - $postType = sanitize_key($_GET['post_type']); - - if (! empty($postType)) { - $args['post_type'] = $postType; - $request_filter['cpt'] = $postType; - } - } - - if (isset($_GET['weeks'])) { - $weeks = sanitize_key($_GET['weeks']); - - if (! empty($weeks)) { - $request_filter['weeks'] = $weeks; - } - } $request_filter['start_date'] = $beginningDate; //update filters - $this->update_user_filters($request_filter); + $args = $this->update_user_filters($request_filter); wp_send_json( $this->getCalendarData($beginningDate, $endingDate, $args), @@ -3351,6 +4441,17 @@ public function update_user_filters($request_filter) $filters = []; $old_filters = $this->get_user_meta($current_user->ID, self::USERMETA_KEY_PREFIX . 'filters', true); + // Get content calendar data + $this->content_calendar_datas = $this->get_content_calendar_datas(); + + $filters = $this->content_calendar_datas['content_calendar_filters']; + /** + * @param array $filters + * + * @return array + */ + $this->filters = apply_filters('publishpress_content_calendar_filters', $filters); + $default_filters = [ 'weeks' => self::DEFAULT_NUM_WEEKS, 'post_status' => '', @@ -3362,8 +4463,15 @@ public function update_user_filters($request_filter) ]; $old_filters = array_merge($default_filters, (array)$old_filters); + $this->filters = array_merge([ + 'weeks' => __('Weeks', 'publishpress'), + 'start_date' => __('Start Date', 'publishpress'), + 'me_mode' => __('Me Mode', 'publishpress'), + 's' => __('Search', 'publishpress'), + ], $this->filters); + // Sanitize and validate any newly added filters - foreach ($old_filters as $key => $old_value) { + /* foreach ($old_filters as $key => $old_value) { if (isset($request_filter[$key]) && false !== ($new_value = $this->sanitize_filter( $key, sanitize_text_field($request_filter[$key]) @@ -3372,7 +4480,45 @@ public function update_user_filters($request_filter) } else { $filters[$key] = $old_value; } + }*/ + + + + $editorial_metadata = $this->terms_options; + + foreach ($this->filters as $filter_key => $filter_label) { + if (array_key_exists($filter_key, $editorial_metadata)) { + //add metadata to filter + $meta_term = $editorial_metadata[$filter_key]; + $meta_term_type = $meta_term['type']; + if ($meta_term_type === 'checkbox') { + if (! isset($_GET[$filter_key])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $check_value = null; + } else { + $check_value = absint($this->filter_get_param($filter_key, $request_filter)); + } + $filters[$filter_key] = $check_value; + } elseif ($meta_term_type === 'date') { + $filters[$filter_key] = $this->filter_get_param_text($filter_key, $request_filter); + $filters[$filter_key . '_start'] = $this->filter_get_param_text($filter_key . '_start', $request_filter); + $filters[$filter_key . '_end'] = $this->filter_get_param_text($filter_key . '_end', $request_filter); + $filters[$filter_key . '_start_hidden'] = $this->filter_get_param_text($filter_key . '_start_hidden', $request_filter); + $filters[$filter_key . '_end_hidden'] = $this->filter_get_param_text($filter_key . '_end_hidden', $request_filter); + } elseif ($meta_term_type === 'user') { + if (empty($filters['me_mode'])) { + $filters[$filter_key] = $this->filter_get_param_text($filter_key, $request_filter); + } + } else { + $filters[$filter_key] = $this->filter_get_param_text($filter_key, $request_filter); + } + } else { + // other filters + $filters[$filter_key] = $this->filter_get_param_text($filter_key, $request_filter); + if (in_array($filter_key, $this->content_calendar_datas['meta_keys']) || in_array($filter_key, ['ppch_co_yoast_seo__yoast_wpseo_linkdex', 'ppch_co_yoast_seo__yoast_wpseo_content_score'])) { + $filters[$filter_key . '_operator'] = $this->filter_get_param_text($filter_key . '_operator', $request_filter); + } } + } // Fix start_date, if no specific date was set if (! isset($request_filter['start_date'])) { @@ -3852,8 +4998,6 @@ private function getCalendarData($beginningDate, $endingDate, $args = []) $post_query_args = [ 'post_status' => null, 'post_type' => null, - 'cat' => null, - 'tag' => null, 'author' => null, 'date_query' => [ 'column' => 'post_date', @@ -3897,6 +5041,55 @@ private function getCalendarData($beginningDate, $endingDate, $args = []) return $data; } + /** + * + * @param string $param The parameter to look for in $_GET + * + * @return mixed null if the parameter is not set in $_GET, empty string if the parameter is empty in $_GET, + * or a sanitized version of the parameter from $_GET if set and not empty + */ + public function filter_get_param($param, $request_filter = false) + { + if (!$request_filter) { + $request_filter = $_GET; + } + + // Sure, this could be done in one line. But we're cooler than that: let's make it more readable! + if (! isset($request_filter[$param])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return null; + } elseif (empty($request_filter[$param])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return ''; + } + + return sanitize_key($request_filter[$param]); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + /** + * This function is an alternative to filter_get_param() that's stripping out date characters + * + * @param string $param The parameter to look for in $_GET + * + * @return mixed null if the parameter is not set in $_GET, empty string if the parameter is empty in $_GET, + * or a sanitized version of the parameter from $_GET if set and not empty + */ + public function filter_get_param_text($param, $request_filter = false) + { + if (!$request_filter) { + $request_filter = $_GET; + } + + // Sure, this could be done in one line. But we're cooler than that: let's make it more readable! + if (! isset($request_filter[$param])) { + return null; + } elseif ($request_filter[$param] == '0') { + return 0; + } elseif (empty($request_filter[$param])) { + return ''; + } + + return sanitize_text_field($request_filter[$param]); + } + /** * Sanitize a $_GET or similar filter being used on the calendar * @@ -3946,7 +5139,7 @@ public function sanitize_filter($key, $dirty_value) return empty($weeks) ? self::DEFAULT_NUM_WEEKS : $weeks; break; default: - return false; + return sanitize_text_field($dirty_value); break; } } diff --git a/modules/calendar/lib/async-calendar/js/AsyncCalendar.jsx b/modules/calendar/lib/async-calendar/js/AsyncCalendar.jsx index 010e45c4..3cfdcb26 100644 --- a/modules/calendar/lib/async-calendar/js/AsyncCalendar.jsx +++ b/modules/calendar/lib/async-calendar/js/AsyncCalendar.jsx @@ -11,25 +11,9 @@ const $ = jQuery; export default function AsyncCalendar(props) { const theme = (props.theme || 'light'); - - let statusValue = (props.requestFilter.post_status) ? props.requestFilter.post_status : ''; - let typesValue = (props.requestFilter.post_type) ? props.requestFilter.post_type : ''; let weeksValue = (props.requestFilter.weeks) ? props.requestFilter.weeks : props.numberOfWeeksToDisplay; - let categoryValue = ''; - if (props.requestFilter.category && props.requestFilter.category.value) { - categoryValue = props.requestFilter.category.value; - } - - let postTagValue = ''; - if (props.requestFilter.post_tag && props.requestFilter.post_tag.value) { - postTagValue = props.requestFilter.post_tag.value; - } - - let authorValue = ''; - if (props.requestFilter.post_author && props.requestFilter.post_author.value) { - authorValue = props.requestFilter.post_author.value; - } + let calendarFiltersValue = (props.requestFilter) ? props.requestFilter : {}; const [firstDateToDisplay, setFirstDateToDisplay] = React.useState(getBeginDateOfWeekByDate(props.firstDateToDisplay, props.weekStartsOnSunday)); const [numberOfWeeksToDisplay, setNumberOfWeeksToDisplay] = React.useState(weeksValue); @@ -37,18 +21,15 @@ export default function AsyncCalendar(props) { const [isLoading, setIsLoading] = React.useState(false); const [isDragging, setIsDragging] = React.useState(false); const [message, setMessage] = React.useState(); - const [filterStatus, setFilterStatus] = React.useState(statusValue); - const [filterCategory, setFilterCategory] = React.useState(categoryValue); - const [filterTag, setFilterTag] = React.useState(postTagValue); - const [filterAuthor, setFilterAuthor] = React.useState(authorValue); - const [filterPostType, setFilterPostType] = React.useState(typesValue); const [filterWeeks, setFilterWeeks] = React.useState(weeksValue); + const [searchText, setSearchText] = React.useState(''); const [openedItemId, setOpenedItemId] = React.useState(); const [openedItemData, setOpenedItemData] = React.useState([]); const [openedItemRefreshCount, setOpenedItemRefreshCount] = React.useState(0); const [refreshCount, setRefreshCount] = React.useState(0); const [hoveredDate, setHoveredDate] = React.useState(); const [formDate, setFormDate] = React.useState(); + const [calendarFilter, setCalendarFilter] = React.useState(calendarFiltersValue); const DRAG_AND_DROP_HOVERING_CLASS = 'publishpress-calendar-day-hover'; @@ -59,10 +40,92 @@ export default function AsyncCalendar(props) { return props.ajaxUrl + '?action=' + action + '&nonce=' + props.nonce + query; } + + const onFilterSelectChange = (event) => { + let selectElement = event.target; + let selectName = selectElement.name; + let selectValue = selectElement.value; + let elementModal = selectElement.closest('.content-calendar-modal'); + let previousElement = elementModal.previousElementSibling; + if (!selectValue || selectValue == '') { + previousElement.classList.remove('active-filter'); + previousElement.innerHTML = previousElement.getAttribute('data-label'); + } else { + let selectedOptionText = selectElement.selectedOptions[0].text; + previousElement.classList.add('active-filter'); + previousElement.innerHTML = previousElement.getAttribute('data-label') + ': ' + selectedOptionText; + elementModal.style.display = 'none'; + } + onFilterEventCallback(selectName, selectValue); + } + + const onMeModeClick = (event) => { + let new_value = ''; + if (event.target.classList.contains('active-filter')) { + new_value = 0; + event.target.classList.remove('active-filter'); + } else { + new_value = 1; + event.target.classList.add('active-filter'); + } + + onFilterEventCallback('me_mode', new_value); + + document.querySelector('#filter_author').value = ''; + document.querySelector('#pp-content-filters #content_calendar_me_mode').value = new_value; + }; + + const onSearchClick = (event) => { + let selectElement = event.target; + let elementParent = selectElement.closest('.search-bar'); + let value = elementParent.querySelector('#co-searchbox-search-input').value; + setSearchText(value); + } + + const onFilterApplyClick = (event) => { + event.preventDefault(); + + let elementModal = event.target.closest('.content-calendar-modal'); + let previousElement = elementModal.previousElementSibling; + let inputs = elementModal.querySelectorAll('input:not([type="submit"]), select, textarea'); + let all_inputs = []; + let selected_values = ''; + inputs.forEach(input => { + let inputName = input.name; + let inputValue = input.value; + + if (input.type === 'checkbox') { + inputValue = input.checked ? '1' : ''; + selected_values += inputValue; + } else if (input.type === 'select-one') { + selected_values += ' : ' + input.selectedOptions[0].text; + } else if (input.type !== 'hidden' && inputValue && inputValue !== '') { + selected_values += ' : ' + inputValue; + } + all_inputs.push(inputName); + + onFilterEventCallback(inputName, inputValue); + }); + + if (!selected_values || selected_values == '') { + previousElement.classList.remove('active-filter'); + previousElement.innerHTML = previousElement.getAttribute('data-label'); + } else { + previousElement.classList.add('active-filter'); + previousElement.innerHTML = previousElement.getAttribute('data-label') + selected_values; + } + + elementModal.style.display = 'none'; + }; + const addEventListeners = () => { document.addEventListener('keydown', onDocumentKeyDown); $(document).on('publishpress_calendar:close_popup', onCloseItemPopup); + $(document).on('change', '#pp-content-filters select:not(.non-trigger-select)', onFilterSelectChange); + $(document).on('click', '.metadata-item-filter .filter-apply input[type=submit]', onFilterApplyClick); + $(document).on('click', '.pp-content-calendar-manage .search-bar input[type=submit]', onSearchClick); + $(document).on('click', '.pp-content-calendar-manage .me-mode-action', onMeModeClick); } const removeEventListeners = () => { @@ -90,28 +153,15 @@ export default function AsyncCalendar(props) { let dataUrl = getUrl(props.actionGetData, '&start_date=' + getDateAsStringInWpFormat(getBeginDateOfWeekByDate(firstDateToDisplay, props.weekStartsOnSunday)) + '&number_of_weeks=' + numberOfWeeksToDisplay); - if (filterStatus) { - dataUrl += '&post_status=' + filterStatus; - } - - if (filterCategory) { - dataUrl += '&category=' + filterCategory; + if (calendarFilter && Object.keys(calendarFilter).length > 0) { + let calendarFilterParams = new URLSearchParams(calendarFilter); + dataUrl += '&' + calendarFilterParams.toString(); } - if (filterTag) { - dataUrl += '&post_tag=' + filterTag; - } - - if (filterAuthor) { - dataUrl += '&post_author=' + filterAuthor; - } - - if (filterPostType) { - dataUrl += '&post_type=' + filterPostType; - } - - if (filterWeeks) { - dataUrl += '&weeks=' + filterWeeks; + if (searchText) { + dataUrl += '&s=' + searchText; + } else { + dataUrl += '&s='; } fetch(dataUrl) @@ -190,6 +240,9 @@ export default function AsyncCalendar(props) { }; const moveCalendarItemToANewDate = (itemDate, itemIndex, newYear, newMonth, newDay) => { + if (!itemsByDate[itemDate]) { + return; + } let item = itemsByDate[itemDate][itemIndex]; setIsLoading(true); @@ -341,25 +394,10 @@ export default function AsyncCalendar(props) { } const onFilterEventCallback = (filterName, value) => { - if ('status' === filterName) { - setFilterStatus(value); - } - - if ('category' === filterName) { - setFilterCategory(value); - } - - if ('tag' === filterName) { - setFilterTag(value); - } - - if ('author' === filterName) { - setFilterAuthor(value); - } - - if ('postType' === filterName) { - setFilterPostType(value); - } + setCalendarFilter(prevCalendarFilter => ({ + ...prevCalendarFilter, + [filterName]: value + })); if ('weeks' === filterName) { value = parseInt(value); @@ -474,11 +512,8 @@ export default function AsyncCalendar(props) { firstDateToDisplay, numberOfWeeksToDisplay, filterWeeks, - filterAuthor, - filterTag, - filterCategory, - filterStatus, - filterPostType, + searchText, + calendarFilter, refreshCount ] ); @@ -490,11 +525,8 @@ export default function AsyncCalendar(props) { firstDateToDisplay, numberOfWeeksToDisplay, filterWeeks, - filterAuthor, - filterTag, - filterCategory, - filterStatus, - filterPostType, + searchText, + calendarFilter, refreshCount ] ); @@ -517,16 +549,6 @@ export default function AsyncCalendar(props) { return (
- - e.length)&&(t=e.length);for(var a=0,n=new Array(t);a1?a-1:0),r=1;r/gm),Y=d(/\${[\w\W]*}/gm),V=d(/^data-[\-\w.\u00B7-\uFFFF]/),Q=d(/^aria-[\-\w]+$/),$=d(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),K=d(/^(?:\w+script|data):/i),J=d(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),X=d(/^html$/i),Z=function(){return"undefined"==typeof window?null:window},ee=function(t,a){if("object"!==e(t)||"function"!=typeof t.createPolicy)return null;var n=null;a.currentScript&&a.currentScript.hasAttribute("data-tt-policy-suffix")&&(n=a.currentScript.getAttribute("data-tt-policy-suffix"));var r="dompurify"+(n?"#"+n:"");try{return t.createPolicy(r,{createHTML:function(e){return e},createScriptURL:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}};return function t(){var a=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Z(),n=function(e){return t(e)};if(n.version="2.4.7",n.removed=[],!a||!a.document||9!==a.document.nodeType)return n.isSupported=!1,n;var o=a.document,l=a.document,i=a.DocumentFragment,s=a.HTMLTemplateElement,c=a.Node,u=a.Element,d=a.NodeFilter,m=a.NamedNodeMap,f=void 0===m?a.NamedNodeMap||a.MozNamedAttrMap:m,h=a.HTMLFormElement,g=a.DOMParser,y=a.trustedTypes,A=u.prototype,te=O(A,"cloneNode"),ae=O(A,"nextSibling"),ne=O(A,"childNodes"),re=O(A,"parentNode");if("function"==typeof s){var oe=l.createElement("template");oe.content&&oe.content.ownerDocument&&(l=oe.content.ownerDocument)}var le=ee(y,o),ie=le?le.createHTML(""):"",se=l,ce=se.implementation,ue=se.createNodeIterator,pe=se.createDocumentFragment,de=se.getElementsByTagName,me=o.importNode,fe={};try{fe=D(l).documentMode?l.documentMode:{}}catch(e){}var he={};n.isSupported="function"==typeof re&&ce&&void 0!==ce.createHTMLDocument&&9!==fe;var ge,ye,ve=W,be=B,Ee=Y,we=V,Re=Q,ke=K,Ce=J,xe=$,Se=null,_e=N({},[].concat(r(j),r(F),r(L),r(M),r(U))),Te=null,Ae=N({},[].concat(r(H),r(G),r(q),r(z))),Ne=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),De=null,Oe=null,je=!0,Fe=!0,Le=!1,Ie=!0,Me=!1,Pe=!1,Ue=!1,He=!1,Ge=!1,qe=!1,ze=!1,We=!0,Be=!1,Ye="user-content-",Ve=!0,Qe=!1,$e={},Ke=null,Je=N({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Xe=null,Ze=N({},["audio","video","img","source","image","track"]),et=null,tt=N({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),at="http://www.w3.org/1998/Math/MathML",nt="http://www.w3.org/2000/svg",rt="http://www.w3.org/1999/xhtml",ot=rt,lt=!1,it=null,st=N({},[at,nt,rt],R),ct=["application/xhtml+xml","text/html"],ut="text/html",pt=null,dt=l.createElement("form"),mt=function(e){return e instanceof RegExp||e instanceof Function},ft=function(t){pt&&pt===t||(t&&"object"===e(t)||(t={}),t=D(t),ge=ge=-1===ct.indexOf(t.PARSER_MEDIA_TYPE)?ut:t.PARSER_MEDIA_TYPE,ye="application/xhtml+xml"===ge?R:w,Se="ALLOWED_TAGS"in t?N({},t.ALLOWED_TAGS,ye):_e,Te="ALLOWED_ATTR"in t?N({},t.ALLOWED_ATTR,ye):Ae,it="ALLOWED_NAMESPACES"in t?N({},t.ALLOWED_NAMESPACES,R):st,et="ADD_URI_SAFE_ATTR"in t?N(D(tt),t.ADD_URI_SAFE_ATTR,ye):tt,Xe="ADD_DATA_URI_TAGS"in t?N(D(Ze),t.ADD_DATA_URI_TAGS,ye):Ze,Ke="FORBID_CONTENTS"in t?N({},t.FORBID_CONTENTS,ye):Je,De="FORBID_TAGS"in t?N({},t.FORBID_TAGS,ye):{},Oe="FORBID_ATTR"in t?N({},t.FORBID_ATTR,ye):{},$e="USE_PROFILES"in t&&t.USE_PROFILES,je=!1!==t.ALLOW_ARIA_ATTR,Fe=!1!==t.ALLOW_DATA_ATTR,Le=t.ALLOW_UNKNOWN_PROTOCOLS||!1,Ie=!1!==t.ALLOW_SELF_CLOSE_IN_ATTR,Me=t.SAFE_FOR_TEMPLATES||!1,Pe=t.WHOLE_DOCUMENT||!1,Ge=t.RETURN_DOM||!1,qe=t.RETURN_DOM_FRAGMENT||!1,ze=t.RETURN_TRUSTED_TYPE||!1,He=t.FORCE_BODY||!1,We=!1!==t.SANITIZE_DOM,Be=t.SANITIZE_NAMED_PROPS||!1,Ve=!1!==t.KEEP_CONTENT,Qe=t.IN_PLACE||!1,xe=t.ALLOWED_URI_REGEXP||xe,ot=t.NAMESPACE||rt,Ne=t.CUSTOM_ELEMENT_HANDLING||{},t.CUSTOM_ELEMENT_HANDLING&&mt(t.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ne.tagNameCheck=t.CUSTOM_ELEMENT_HANDLING.tagNameCheck),t.CUSTOM_ELEMENT_HANDLING&&mt(t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ne.attributeNameCheck=t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),t.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ne.allowCustomizedBuiltInElements=t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Me&&(Fe=!1),qe&&(Ge=!0),$e&&(Se=N({},r(U)),Te=[],!0===$e.html&&(N(Se,j),N(Te,H)),!0===$e.svg&&(N(Se,F),N(Te,G),N(Te,z)),!0===$e.svgFilters&&(N(Se,L),N(Te,G),N(Te,z)),!0===$e.mathMl&&(N(Se,M),N(Te,q),N(Te,z))),t.ADD_TAGS&&(Se===_e&&(Se=D(Se)),N(Se,t.ADD_TAGS,ye)),t.ADD_ATTR&&(Te===Ae&&(Te=D(Te)),N(Te,t.ADD_ATTR,ye)),t.ADD_URI_SAFE_ATTR&&N(et,t.ADD_URI_SAFE_ATTR,ye),t.FORBID_CONTENTS&&(Ke===Je&&(Ke=D(Ke)),N(Ke,t.FORBID_CONTENTS,ye)),Ve&&(Se["#text"]=!0),Pe&&N(Se,["html","head","body"]),Se.table&&(N(Se,["tbody"]),delete De.tbody),p&&p(t),pt=t)},ht=N({},["mi","mo","mn","ms","mtext"]),gt=N({},["foreignobject","desc","title","annotation-xml"]),yt=N({},["title","style","font","a","script"]),vt=N({},F);N(vt,L),N(vt,I);var bt=N({},M);N(bt,P);var Et=function(e){var t=re(e);t&&t.tagName||(t={namespaceURI:ot,tagName:"template"});var a=w(e.tagName),n=w(t.tagName);return!!it[e.namespaceURI]&&(e.namespaceURI===nt?t.namespaceURI===rt?"svg"===a:t.namespaceURI===at?"svg"===a&&("annotation-xml"===n||ht[n]):Boolean(vt[a]):e.namespaceURI===at?t.namespaceURI===rt?"math"===a:t.namespaceURI===nt?"math"===a&>[n]:Boolean(bt[a]):e.namespaceURI===rt?!(t.namespaceURI===nt&&!gt[n])&&!(t.namespaceURI===at&&!ht[n])&&!bt[a]&&(yt[a]||!vt[a]):!("application/xhtml+xml"!==ge||!it[e.namespaceURI]))},wt=function(e){E(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ie}catch(t){e.remove()}}},Rt=function(e,t){try{E(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){E(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Te[e])if(Ge||qe)try{wt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},kt=function(e){var t,a;if(He)e=""+e;else{var n=k(e,/^[\r\n\t ]+/);a=n&&n[0]}"application/xhtml+xml"===ge&&ot===rt&&(e=''+e+"");var r=le?le.createHTML(e):e;if(ot===rt)try{t=(new g).parseFromString(r,ge)}catch(e){}if(!t||!t.documentElement){t=ce.createDocument(ot,"template",null);try{t.documentElement.innerHTML=lt?ie:r}catch(e){}}var o=t.body||t.documentElement;return e&&a&&o.insertBefore(l.createTextNode(a),o.childNodes[0]||null),ot===rt?de.call(t,Pe?"html":"body")[0]:Pe?t.documentElement:o},Ct=function(e){return ue.call(e.ownerDocument||e,e,d.SHOW_ELEMENT|d.SHOW_COMMENT|d.SHOW_TEXT,null,!1)},xt=function(e){return e instanceof h&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof f)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},St=function(t){return"object"===e(c)?t instanceof c:t&&"object"===e(t)&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},_t=function(e,t,a){he[e]&&v(he[e],(function(e){e.call(n,t,a,pt)}))},Tt=function(e){var t;if(_t("beforeSanitizeElements",e,null),xt(e))return wt(e),!0;if(_(/[\u0080-\uFFFF]/,e.nodeName))return wt(e),!0;var a=ye(e.nodeName);if(_t("uponSanitizeElement",e,{tagName:a,allowedTags:Se}),e.hasChildNodes()&&!St(e.firstElementChild)&&(!St(e.content)||!St(e.content.firstElementChild))&&_(/<[/\w]/g,e.innerHTML)&&_(/<[/\w]/g,e.textContent))return wt(e),!0;if("select"===a&&_(/