File "hustle-entries-admin.php"
Full Path: /home/londdqdw/public_html/06/wp-content/plugins/wordpress-popup/inc/hustle-entries-admin.php
File size: 29.85 KB
MIME-type: text/x-php
Charset: utf-8
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Hustle_Entries_Admin
*
* @package Hustle
*/
/**
* Class Hustle_Entries_Admin
* Handle the email lists.
*
* @since 4.0
*/
class Hustle_Entries_Admin extends Hustle_Admin_Page_Abstract {
/**
* Module types with titles.
*
* @since 4.3.1
* @var array
*/
private $module_types;
/**
* Merged default parameter with $_REQUEST
*
* @since 4.0
* @var array
*/
private $screen_params = array();
/**
* Current module model
*
* @since 4.0
* @var null|Hustle_Module_Model
*/
private $module = null;
/**
* Current module_id
*
* @since 4.0
* @var int
*/
protected $module_id = 0;
/**
* Entries array.
*
* @since 4.0
* @var string
*/
private $entries = array();
/**
* Page number
*
* @since 4.0
* @var int
*/
protected $page_number = 1;
/**
* Get pagination limit
*
* @var int
*/
protected $per_page;
/**
* Total Entries
*
* @since 4.0
* @var int
*/
protected $total_entries = 0;
/**
* Total filterd Entries
*
* @since 4.0
* @var int
*/
protected $filtered_total_entries = 0;
/**
* Registered addons
*
* @since 4.0
* @var Hustle_Provider_Abstract[]
*/
private static $registered_addons = null;
/**
* Filters to be used
*
* [key=>value]
* ['search'=>'search term']
*
* @since 4.0
* @var array
*/
public $filters = array();
/**
* Order to be used
*
* [key=>order]
* ['entry_date' => 'ASC']
*
* @since 4.0
* @var array
*/
public $order = array();
/**
* Nested Mappers
*
* @since 4.0
* @var array
*/
protected $fields_mappers = array();
/**
* Init
*/
public function init() {
$this->page = 'hustle_entries';
/* translators: Plugin name */
$this->page_title = sprintf( __( '%s Email Lists', 'hustle' ), Opt_In_Utils::get_plugin_name() );
$this->page_menu_title = __( 'Email Lists', 'hustle' );
$this->page_capability = 'hustle_access_emails';
$this->page_template_path = 'admin/entries';
// Show the first page if current page doesn't have entries.
add_filter( 'removable_query_args', array( $this, 'maybe_remove_paged' ) );
}
/**
* Remove paged get attribute if there aren't entries and it's not the first page
*
* @since 4.3.1
* @param array $removable_query_args URL query args to be removed.
* @return array
*/
public function maybe_remove_paged( $removable_query_args ) {
$paged = filter_input( INPUT_GET, 'paged', FILTER_VALIDATE_INT );
if ( $paged && 1 !== $paged && 'hustle_entries' === $this->current_page ) {
$per_page = Hustle_Settings_Admin::get_per_page( 'submission' );
$offset = ( $paged - 1 ) * $per_page;
$module_id = filter_input( INPUT_GET, 'module_id', FILTER_VALIDATE_INT );
$total_entries = Hustle_Entry_Model::count_entries( $module_id );
if ( $total_entries <= $offset ) {
$_SERVER['REQUEST_URI'] = remove_query_arg( 'paged' );
$removable_query_args[] = 'paged';
unset( $_GET['paged'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
}
return $removable_query_args;
}
/**
* Get the arguments used when rendering the main page.
*
* @since 4.0.1
* @return array
*/
public function get_page_template_args() {
$types = $this->get_module_types();
$module = $this->get_module_model();
$filter_types = array(
'search_email',
'order_by',
'date_range',
);
$is_filtered = false;
foreach ( $filter_types as $type ) {
$is_filtered = $is_filtered || filter_input( INPUT_GET, $type, FILTER_SANITIZE_SPECIAL_CHARS );
}
if ( $module && $module->active ) {
$integrations = $module->get_integrations_settings()->to_array();
$no_local_list = false === strpos( $integrations['active_integrations'], 'local_list' );
} else {
$no_local_list = false;
}
return array(
'module' => $module,
'entries' => $this->get_entries(),
'global_entries' => Hustle_Entry_Model::global_count_entries(),
'module_name' => ! empty( $module->module_type ) && isset( $types[ $module->module_type ] ) ? $types[ $module->module_type ] : '',
'is_module_selected' => (bool) $this->get_current_module_id(),
'is_filtered' => $is_filtered,
'no_local_list' => $no_local_list,
);
}
/**
* Enqueue scripts
*/
public function current_page_loaded() {
parent::current_page_loaded();
$this->before_render();
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
/**
* Enqueue scripts for the submissions page.
*
* @since 4.2.0
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_script(
'hustle-entries-moment',
Opt_In::$plugin_url . 'assets/js/vendor/moment.min.js',
array( 'jquery' ),
Opt_In::VERSION,
true
);
wp_enqueue_script(
'hustle-entries-datepicker-range',
Opt_In::$plugin_url . 'assets/js/vendor/daterangepicker.min.js',
array( 'jquery', 'hustle-entries-moment' ),
'3.0.5',
true
);
}
/**
* Register the js variables to be localized for this page.
*
* @since 4.3.1
*
* @return array
*/
protected function get_vars_to_localize() {
$current_array = parent::get_vars_to_localize();
// These labels are used in getDaterangepickerRanges(), entries.js.
// These keys must match the keys from there.
$datepicker_ranges = array(
'today' => __( 'Today', 'hustle' ),
'yesterday' => __( 'Yesterday', 'hustle' ),
'last_seven_days' => __( 'Last 7 Days', 'hustle' ),
'last_thirty_days' => __( 'Last 30 Days', 'hustle' ),
'this_month' => __( 'This Month', 'hustle' ),
'last_month' => __( 'Last Month', 'hustle' ),
);
$current_array['daterangepicker'] = array(
'daysOfWeek' => Hustle_Time_Helper::get_week_days( 'min' ),
'monthNames' => Hustle_Time_Helper::get_months(),
'ranges' => $datepicker_ranges,
);
return $current_array;
}
/**
* Populating the current page parameters
*
* @since 4.0.0
*/
public function populate_screen_params() {
$screen_params = array(
'module_type' => 'popup',
'module_id' => 0,
);
$this->screen_params = array_merge( $screen_params, $_REQUEST );// phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
/**
* Executed Action before rendering the page.
*
* @since 4.0
*/
public function before_render() {
$this->populate_screen_params();
$this->prepare_entries_page();
$this->export();
}
/**
* Get the module types for the entries page.
*
* @since 4.0.0
*
* @return array
*/
public function get_module_types() {
if ( empty( $this->module_types ) ) {
$module_types = Hustle_Data::get_module_types();
$types_with_title = array();
foreach ( $module_types as $type ) {
if ( Hustle_Model::SOCIAL_SHARING_MODULE === $type ) {
continue;
}
$types_with_title[ $type ] = Opt_In_Utils::get_module_type_display_name( $type, false, true );
}
$this->module_types = $types_with_title;
}
return $this->module_types;
}
/**
* Prepare entries
*
* @since 4.0
*/
private function prepare_entries_page() {
$this->module = $this->get_module_model();
// Module not found.
if ( ! $this->module ) {
// if module_id available remove it from request, and redirect.
if ( $this->get_current_module_id() ) {
$url = remove_query_arg( 'module_id' );
if ( wp_safe_redirect( $url ) ) {
exit;
}
}
} else {
// as page's before_render().
$this->prepare_page();
}
}
/**
* Return the modules of the current type.
*
* @since 4.0
*
* @return array Hustle_Module_Model[]
*/
public function get_modules() {
$module_types = $this->get_module_types();
$current_type = $this->get_current_module_type();
$module_type = isset( $module_types[ $current_type ] ) ? $current_type : 'popup';
$args = array(
'module_type' => $module_type,
'module_mode' => 'optin',
);
$modules = Hustle_Module_Collection::instance()->get_all( null, $args );
return $modules;
}
/**
* Get module model if the requested module_id is available and matches module_type
*
* @since 4.0
*
* @return bool|Hustle_Module_Model|null
*/
public function get_module_model() {
if ( $this->get_current_module_id() ) {
$module = new Hustle_Module_Model( $this->get_current_module_id() );
if ( is_wp_error( $module ) ) {
return null;
}
if ( ! $module instanceof Hustle_Module_Model ) {
return null;
}
if ( $module->module_type !== $this->get_current_module_type() ) {
return null;
}
return $module;
}
return null;
}
/**
* Get current module type
*
* @since 4.0
*
* @return mixed
*/
public function get_current_module_type() {
return $this->screen_params['module_type'];
}
/**
* Get current module id
*
* @since 4.0
*
* @return mixed
*/
public function get_current_module_id() {
return $this->screen_params['module_id'];
}
// ====================
/**
* Get Entries
*
* @since 4.0
* @return array
*/
public function get_entries() {
return $this->entries;
}
/**
* Get Page Number
*
* @since 4.0
* @return int
*/
public function get_page_number() {
return $this->page_number;
}
/**
* Get Per Page
*
* @since 1.0
* @return int
*/
public function get_per_page() {
return $this->per_page;
}
/**
* The total filtered entries
*
* @since 4.0
* @return int
*/
public function filtered_total_entries() {
return $this->filtered_total_entries;
}
/**
* Prepare email list page
* admin_page_entries as 'before_render'
*/
private function prepare_page() {
$this->module_id = (int) $this->module->module_id;
$this->parse_filters();
$this->parse_order();
$this->per_page = Hustle_Settings_Admin::get_per_page( 'submission' );
// don't use filter_input() here, because of see maybe_remove_paged() method.
$pagenum = ! empty( $_GET['paged'] ) ? (int) $_GET['paged'] : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->page_number = max( 1, $pagenum );
/**
* Fires on custom form page entries render before request and result processed
*
* @since 4.0
*/
do_action( 'hustle_admin_page_entries', $this->module_id, $this->module, $pagenum );
$this->process_request();
$this->prepare_results();
}
/**
* Process the current request.
*
* @since 4.0
*/
private function process_request() {
$nonce = filter_input( INPUT_POST, 'hustle_nonce', FILTER_SANITIZE_SPECIAL_CHARS );
if ( ! wp_verify_nonce( $nonce, 'hustle_entries_request' ) ) {
return;
}
if ( ! current_user_can( 'hustle_access_emails' ) ) {
return;
}
$action = filter_input( INPUT_POST, 'hustle_action', FILTER_SANITIZE_SPECIAL_CHARS );
if ( empty( $action ) ) {
$action = filter_input( INPUT_POST, 'hustle_action_bottom', FILTER_SANITIZE_SPECIAL_CHARS );
}
switch ( $action ) {
case 'delete':
$entry_id = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT );
if ( ! $entry_id ) {
return;
}
Hustle_Entry_Model::delete_by_entry( $this->module_id, $entry_id );
break;
case 'delete-all':
$entries = filter_input( INPUT_POST, 'ids', FILTER_SANITIZE_SPECIAL_CHARS );
if ( ! empty( $entries ) ) {
$entries = explode( ',', $entries );
Hustle_Entry_Model::delete_by_entries( $this->module_id, $entries );
}
break;
default:
return;
}
$url_params = array(
'page' => isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : 'hustle_entries',
'module_type' => isset( $_REQUEST['module_type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['module_type'] ) ) : '',
'module_id' => $this->module_id,
'paged' => isset( $_REQUEST['paged'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['paged'] ) ) : 1,
);
$url = add_query_arg( $url_params, 'admin.php' );
wp_safe_redirect( $url ); // Redirect to the first entry page.
exit;
}
/**
* Prepare the entries to be shown.
*
* @since 4.0
*/
private function prepare_results() {
if ( is_object( $this->module ) ) {
$paged = $this->page_number;
$per_page = $this->per_page;
$offset = ( $paged - 1 ) * $per_page;
$this->total_entries = Hustle_Entry_Model::count_entries( $this->module_id );
$args = array(
'module_id' => $this->module_id,
'per_page' => $per_page,
'offset' => $offset,
'order_by' => 'entries.date_created',
'order' => 'ASC',
);
$args = wp_parse_args( $this->filters, $args );
$args = wp_parse_args( $this->order, $args );
$count = 0;
$this->entries = Hustle_Entry_Model::query_entries( $args, $count );
$this->filtered_total_entries = $count;
}
}
/**
* Get entries iterator
*
* @return array
*/
public function entries_iterator() {
/**
* Example
*
* @example
* {
* id => 'ENTRY_ID'
* summary = [
* 'num_fields_left' => true/false,
* 'items' => [
* [
* 'colspan' => 2/...,
* 'value' => '----',
* ]
* [
* 'colspan' => 2/...
* value' => '----',
* ]
* ],
* ],
* detail = [
* 'colspan' => '',
* 'items' => [
* [
* 'label' => '----',
* 'value' => '-----'
* 'sub_entries' => [
* [
* 'label' => '----',
* 'value' => '-----'
* ]
* ]
* ]
* [
* 'label' => '----',
* 'value' => '-----'
* ]
* ],
* ]
* }
*/
$entries_iterator = array();
$total_colspan = 5; // Colspan for ID + Date Submitted + Active Integrations + Email + Accordion chevron.
$fields_mappers = $this->get_fields_mappers();
// Start from 4, since first four are ID, Date, Active Integrations, and Email.
$fields_left = count( $fields_mappers ) - 4;
// All headers including ID + Date, start from 0 and max is 4.
$headers = array_slice( $fields_mappers, 0, 4 );
$numerator_id = $this->total_entries;
if ( $this->page_number > 1 ) {
$numerator_id = $this->total_entries - ( ( $this->page_number - 1 ) * $this->per_page );
}
foreach ( $this->entries as $entry ) {
/** Hustle_Entry_Model $entry */
// create placeholder.
$iterator = array(
'id' => $numerator_id,
'entry_id' => $entry->entry_id,
'summary' => array(),
'detail' => array(),
'addons' => array(),
);
$iterator['summary']['num_fields_left'] = $fields_left;
$iterator['summary']['items'] = array();
$iterator['detail']['colspan'] = $total_colspan;
$iterator['detail']['items'] = array();
// Build array for summary row.
$summary_items = array();
foreach ( $headers as $header ) {
$colspan = 2;
$class = '';
if ( isset( $header['type'] ) ) {
if ( 'entry_entry_id' === $header['type'] ) {
$summary_items[] = array(
'colspan' => 1,
'value' => $numerator_id,
);
continue;
} elseif ( 'entry_time_created' === $header['type'] ) {
$colspan = 3;
$class = 'hui-column-date';
} elseif ( 'entry_integrations' === $header['type'] ) {
$class = 'hui-column-apps';
}
}
$value = $this->get_entry_field_value( $entry, $header, '', false, 100 );
$summary_items[] = array(
'colspan' => $colspan,
'value' => $value,
'class' => $class,
);
}
// Build array for -content row.
$detail_items = array();
foreach ( $fields_mappers as $mapper ) {
// Skip entry id and Active integrations.
if ( isset( $mapper['type'] ) && ( 'entry_entry_id' === $mapper['type'] || 'entry_integrations' === $mapper['type'] ) ) {
continue;
}
$label = $mapper['label'];
$value = $this->get_entry_field_value( $entry, $mapper, '', true );
$sub_entries = array();
$detail_items[] = array(
'label' => $label,
'value' => $value,
'sub_entries' => $sub_entries,
);
}
// Additional render for addons.
$addons_detail_items = $this->attach_addon_on_render_entry( $entry );
$addons = array();
foreach ( $addons_detail_items as $provider_meta ) {
foreach ( $provider_meta as $meta ) {
$addons[] = array(
'summary' => array(
'name' => $meta['name'],
'icon' => $meta['icon'],
'data_sent' => $meta['data_sent'],
),
'detail' => $meta['sub_entries'],
);
}
}
$iterator['summary']['items'] = $summary_items;
$iterator['detail']['items'] = $detail_items;
$iterator['addons'] = $addons;
$entries_iterator[] = $iterator;
$numerator_id --;
}
return $entries_iterator;
}
/**
* Get Fields Mappers based on current state of form
*
* @return array
*/
public function get_fields_mappers() {
if ( empty( $this->fields_mappers ) ) {
$this->fields_mappers = $this->build_fields_mappers();
}
return $this->fields_mappers;
}
/**
* Get fields mappers
*
* @return type
*/
private function build_fields_mappers() {
$module = $this->module;
$fields = $module->get_form_fields();
$ignored_field_types = Hustle_Entry_Model::ignored_fields();
$mappers = array(
array(
// read model's property.
'property' => 'entry_id', // must be on entries.
'label' => __( 'ID', 'hustle' ),
'type' => 'entry_entry_id',
),
array(
// read model's property.
'property' => 'time_created', // must be on entries.
'label' => __( 'Date Submitted', 'hustle' ),
'type' => 'entry_time_created',
'class' => 'hui-column-date',
),
array(
// read entry meta.
'meta_key' => 'active_integrations', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'label' => __( 'Active Integrations', 'hustle' ),
'type' => 'entry_integrations',
'class' => 'hui-column-apps',
),
array(
// required meta key. must be on entries.
'meta_key' => 'email', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'label' => __( 'Email', 'hustle' ),
'type' => 'email',
),
);
foreach ( $fields as $field ) {
$field_type = $field['type'];
if ( 'email' === $field['name'] || in_array( $field_type, $ignored_field_types, true ) ) {
continue;
}
// base mapper for every field.
$mapper = array();
$mapper['meta_key'] = $field['name'];// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
$mapper['label'] = $field['label'];
$mapper['type'] = $field_type;
if ( ! empty( $mapper ) ) {
$mappers[] = $mapper;
}
}
return $mappers;
}
/**
* Get entry field value helper
*
* @param Hustle_Entry_Model $entry Entry.
* @param array $mapper Mapper.
* @param string $sub_meta_key Sub meta key.
* @param bool $allow_html Allow HTML.
* @param int $truncate Truncate.
*
* @return string
*/
private function get_entry_field_value( $entry, $mapper, $sub_meta_key = '', $allow_html = false, $truncate = PHP_INT_MAX ) {
/** Hustle_Entry_Model $entry */
if ( isset( $mapper['property'] ) ) {
if ( property_exists( $entry, $mapper['property'] ) ) {
$property = $mapper['property'];
// casting property to string.
if ( is_array( $entry->$property ) ) {
$value = implode( ', ', $entry->$property );
} else {
$value = (string) $entry->$property;
}
} else {
$value = '';
}
} else {
$meta_value = $entry->get_meta( $mapper['meta_key'], '' );
// meta_key based.
$value = Hustle_Entry_Model::meta_value_to_string( $mapper['type'], $meta_value, $allow_html, $truncate );
}
return $value;
}
/**
* Executor for adding additional items on entry page.
*
* @see Hustle_Provider_Form_Hooks_Abstract::on_render_entry()
* @since 4.0
*
* @param Hustle_Entry_Model $entry_model Entry model.
* @return array
*/
private function attach_addon_on_render_entry( Hustle_Entry_Model $entry_model ) {
$additonal_items = array();
// Find all registered addons so history can be shown even for deactivated addons.
$registered_addons = $this->get_registered_addons();
foreach ( $registered_addons as $registered_addon ) {
try {
$form_hooks = $registered_addon->get_addon_form_hooks( $this->module_id );
$meta_data = Hustle_Provider_Utils::find_addon_meta_data_from_entry_model( $registered_addon, $entry_model );
$addon_additional_items = $form_hooks->on_render_entry( $entry_model, $meta_data );
$addon_additional_items = self::format_addon_additional_items( $addon_additional_items );
$additonal_items[] = $addon_additional_items;
} catch ( Exception $e ) {
Opt_In_Utils::maybe_log( $registered_addon->get_slug(), 'failed to on_render_entry', $e->getMessage() );
}
}
return $additonal_items;
}
/**
* Ensuring additional items for addons meet the entries data requirements.
* Format used:
* - label
* - value
* - subentries[]
* - label
* - value
*
* @since 4.0
*
* @param array $addon_additional_items Addon additional items.
* @return mixed
*/
private static function format_addon_additional_items( $addon_additional_items ) {
// to `name` and `value` basis.
$formatted_additional_items = array();
if ( ! is_array( $addon_additional_items ) ) {
return array();
}
foreach ( $addon_additional_items as $additional_item ) {
if ( ! isset( $additional_item['name'] ) || ! isset( $additional_item['data_sent'] ) || ! isset( $additional_item['sub_entries'] ) ) {
continue;
}
$sub_entries = array();
// Check if sub_entries are available.
if ( isset( $additional_item['sub_entries'] ) && is_array( $additional_item['sub_entries'] ) ) {
foreach ( $additional_item['sub_entries'] as $sub_entry ) {
// Make sure label and value exist, without it, it will display empty row.
if ( ! isset( $sub_entry['label'] ) || ! isset( $sub_entry['value'] ) ) {
continue;
}
$sub_entries[] = array(
'label' => $sub_entry['label'],
'value' => $sub_entry['value'],
);
}
}
$formatted_additional_items[] = array(
'name' => $additional_item['name'],
'icon' => $additional_item['icon'],
'data_sent' => $additional_item['data_sent'],
'sub_entries' => $sub_entries,
);
}
return $formatted_additional_items;
}
/**
* Get Globally registered Addons, avoid overhead for checking registered addons many times
*
* @since 4.0
*
* @return array|Hustle_Provider_Abstract[]
*/
public function get_registered_addons() {
if ( empty( self::$registered_addons ) ) {
self::$registered_addons = array();
$registered_addons = Hustle_Provider_Utils::get_registered_addons();
foreach ( $registered_addons as $registered_addon ) {
try {
$form_hooks = $registered_addon->get_addon_form_hooks( $this->module_id );
if ( $form_hooks instanceof Hustle_Provider_Form_Hooks_Abstract ) {
self::$registered_addons[] = $registered_addon;
}
} catch ( Exception $e ) {
Opt_In_Utils::maybe_log( $registered_addon->get_slug(), 'failed to get_addon_form_hooks', $e->getMessage() );
}
}
}
return self::$registered_addons;
}
/**
* Parsing filters from $_REQUEST
*
* @since 4.0
*/
protected function parse_filters() {
$request_data = $_REQUEST;// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$data_range = isset( $request_data['date_range'] ) ? sanitize_text_field( $request_data['date_range'] ) : '';
$search = isset( $request_data['search_email'] ) ? sanitize_text_field( $request_data['search_email'] ) : '';
$filters = array();
if ( ! empty( $data_range ) ) {
$date_ranges = explode( ' - ', $data_range );
if ( is_array( $date_ranges ) && isset( $date_ranges[0] ) && isset( $date_ranges[1] ) ) {
$date_ranges[0] = date( 'Y-m-d', strtotime( $date_ranges[0] ) );// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$date_ranges[1] = date( 'Y-m-d', strtotime( $date_ranges[1] ) );// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$filters['date_created'] = array( $date_ranges[0], $date_ranges[1] );
}
}
if ( ! empty( $search ) ) {
$filters['search_email'] = $search;
}
$this->filters = $filters;
}
/**
* Parsing order from $_REQUEST
*
* @since 4.0
*/
protected function parse_order() {
$valid_order_bys = array(
'entries.date_created',
'entries.entry_id',
);
$valid_orders = array(
'DESC',
'ASC',
);
$request_data = $_REQUEST;// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order_by = isset( $request_data['order_by'] ) ? sanitize_text_field( $request_data['order_by'] ) : 'entries.date_created';
$order = isset( $request_data['order'] ) ? sanitize_text_field( $request_data['order'] ) : 'DESC';
if ( ! empty( $order_by ) ) {
if ( ! in_array( $order_by, $valid_order_bys, true ) ) {
$order_by = 'entries.date_created';
}
$this->order['order_by'] = $order_by;
}
if ( ! empty( $order ) ) {
$order = strtoupper( $order );
if ( ! in_array( $order, $valid_orders, true ) ) {
$order = 'DESC';
}
$this->order['order'] = $order;
}
}
/**
* Flag whether box filter is open or nope
*
* @since 4.0
* @return bool
*/
public function is_filter_box_enabled() {
return ( ! empty( $this->filters ) && ! empty( $this->order ) );
}
/**
* Get module type param
*
* @since 4.0
* @return string
*/
public function get_module_type() {
return $this->screen_params['module_type'];
}
/**
* Get module id param
*
* @since 4.0
* @return string
*/
public function get_module_id() {
return $this->screen_params['module_id'];
}
/**
* Export the entries of the current module.
*
* @since 4.0
*/
private function export() {
$action = filter_input( INPUT_POST, 'hustle_action', FILTER_SANITIZE_SPECIAL_CHARS );
if ( 'export_listing' !== $action ) {
return;
}
$nonce = filter_input( INPUT_POST, '_wpnonce', FILTER_SANITIZE_SPECIAL_CHARS );
if ( ! wp_verify_nonce( $nonce, 'hustle_module_export_listing' ) ) {
return;
}
if ( ! current_user_can( 'hustle_access_emails' ) ) {
return;
}
$id = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT );
if ( ! $id ) {
return;
}
$module = new Hustle_Module_Model( $id );
$filename = sprintf(
'hustle-%s-%s-%s-%s-emails.csv',
$module->module_type,
gmdate( 'Ymd-his' ),
get_bloginfo( 'name' ),
$module->module_name
);
$filename = strtolower( sanitize_file_name( $filename ) );
$entries = $this->get_entries_for_export();
$fp = fopen( 'php://memory', 'w' ); // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen -- disable phpcs because it writes memory
foreach ( $entries as $entry ) {
$fields = self::get_formatted_csv_fields( $entry );
fputcsv( $fp, $fields );
}
fseek( $fp, 0 );
header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
header( 'Content-Description: File Transfer' );
header( 'Content-Type: text/csv; charset=' . get_option( 'blog_charset' ), true );
header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
header( 'Expires: 0' );
header( 'Pragma: public' );
// print BOM Char for Excel Compatible.
echo chr( 239 ) . chr( 187 ) . chr( 191 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// Send the generated csv lines to the browser.
if ( function_exists( 'fpassthru' ) ) {
fpassthru( $fp );
} elseif ( function_exists( 'stream_get_contents' ) ) {
echo stream_get_contents( $fp ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
exit();
}
/**
* Get the entries as a ready to export csv array.
*
* @since 4.0
* @return array
*/
private function get_entries_for_export() {
$headers = $this->get_fields_mappers();
$headers[] = array(
'meta_key' => 'hustle_ip', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'label' => 'IP',
'type' => 'ip',
);
$header_labels = wp_list_pluck( $headers, 'label' );
$entries = array( $header_labels );
$all_entries = Hustle_Entry_Model::get_entries( $this->module_id );
// Get all entries.
foreach ( $all_entries as $entry ) {
$row = array();
// Get the entry's value for each header.
foreach ( $headers as $header ) {
$value = $this->get_entry_field_value( $entry, $header, '', false );
$row[] = $value;
}
$entries[] = $row;
}
return $entries;
}
// ====================
/**
* Format csv fields.
*
* @since 4.0
*
* @param array $fields Fields.
* @return array|string
*/
public static function get_formatted_csv_fields( $fields ) {
if ( empty( $fields ) || ! is_array( $fields ) ) {
return $fields;
}
$formatted_fields = array();
foreach ( $fields as $field ) {
if ( ! is_scalar( $field ) ) {
$formatted_fields[] = '';
continue;
}
if ( is_scalar( $field ) ) {
$formatted_fields[] = self::escape_csv_data( $field );
}
}
return $formatted_fields;
}
/**
* Escape a string to be used in a CSV context
*
* Taken from Forminator, where was taken from WooCommerce CSV Exporter
*
* @see https://github.com/woocommerce/woocommerce/blob/master/includes/export/abstract-wc-csv-exporter.php
*
* @since 1.6
*
* Malicious input can inject formulas into CSV files, opening up the possibility
* for phishing attacks and disclosure of sensitive information.
*
* Additionally, Excel exposes the ability to launch arbitrary commands through
* the DDE protocol.
*
* @see http://www.contextis.com/resources/blog/comma-separated-vulnerabilities/
* @see https://hackerone.com/reports/72785
*
* @since 4.0
*
* @param string $data CSV field to escape.
*
* @return string
*/
public static function escape_csv_data( $data ) {
$active_content_triggers = array( '=', '+', '-', '@' );
if ( in_array( mb_substr( $data, 0, 1 ), $active_content_triggers, true ) ) {
$data = "'" . $data . "'";
}
return $data;
}
}