hook = (string) add_submenu_page( 'woocommerce-marketing', $title, $title, self::CAPABILITY, self::SLUG, array( $this, 'render' ) );
}
public function render(): void {
// wp-header-end tells WordPress where to put other plugins' admin notices: above the app.
echo '
';
}
/**
* @param string $hook Current admin page hook.
*/
public function assets( string $hook ): void {
if ( '' === $this->hook || $hook !== $this->hook ) {
return;
}
wp_enqueue_style( 'wp-components' );
wp_enqueue_style( 'cnwscoupons-admin', CNWSCOUPONS_URL . 'assets/css/admin.css', array( 'wp-components' ), self::asset_version( 'assets/css/admin.css' ) );
wp_enqueue_script(
'cnwscoupons-admin',
CNWSCOUPONS_URL . 'assets/js/admin.js',
array( 'wp-element', 'wp-components', 'wp-api-fetch', 'wp-i18n', 'wp-hooks', 'wp-dom-ready', 'wp-date' ),
self::asset_version( 'assets/js/admin.js' ),
true
);
wp_set_script_translations( 'cnwscoupons-admin', 'webstyle-coupons' );
wp_add_inline_script( 'cnwscoupons-admin', 'window.cnwscouponsSettings = ' . wp_json_encode( $this->settings() ) . ';', 'before' );
/**
* Lets add-ons load their scripts on the campaign screen, e.g. to add fields to the guide through
* the JavaScript filters cnwscoupons.guide.shipping and cnwscoupons.summary.shipping.
*/
do_action( 'cnwscoupons_admin_enqueue_scripts' );
}
/**
* Download every code of a campaign as CSV (e-mail, code, uses, expires).
*/
public function export(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_die( esc_html__( 'You are not allowed to export codes.', 'webstyle-coupons' ) );
}
check_admin_referer( self::EXPORT_ACTION );
$campaign = $this->campaigns->find( isset( $_GET['campaign'] ) ? absint( $_GET['campaign'] ) : 0 );
if ( ! $campaign ) {
wp_die( esc_html__( 'The campaign does not exist.', 'webstyle-coupons' ) );
}
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $campaign->slug . '-codes.csv' ) . '"' );
$out = fopen( 'php://output', 'w' );
// Excel reads UTF-8 correctly only with a byte order mark.
fwrite( $out, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Streaming to php://output.
fputcsv( $out, array( 'email', 'code', 'uses', 'expires' ), ';' );
foreach ( $this->codes->for_campaign( $campaign->id, PHP_INT_MAX ) as $row ) {
fputcsv( $out, array( $row->email, $row->code, (int) $row->uses, $row->expires_at ? get_date_from_gmt( (string) $row->expires_at, 'Y-m-d' ) : '' ), ';' );
}
fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Streaming to php://output.
exit;
}
/**
* Download the overview table (per category and campaign) for the chosen filters as CSV.
*/
public function export_overview(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_die( esc_html__( 'You are not allowed to export codes.', 'webstyle-coupons' ) );
}
check_admin_referer( self::OVERVIEW_EXPORT );
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Checked above.
$report = $this->dashboard->filtered(
isset( $_GET['period'] ) ? sanitize_key( wp_unslash( $_GET['period'] ) ) : '',
isset( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : '',
isset( $_GET['to'] ) ? sanitize_text_field( wp_unslash( $_GET['to'] ) ) : '',
isset( $_GET['category'] ) ? (int) $_GET['category'] : 0,
isset( $_GET['campaign'] ) ? absint( $_GET['campaign'] ) : 0
);
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( null === $report ) {
wp_die( esc_html__( 'Choose a valid period of at most two years.', 'webstyle-coupons' ) );
}
$names = array();
foreach ( $report['categories'] as $group ) {
$names[ $group['id'] ] = $group['name'];
}
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( 'coupon-campaigns-' . $report['from'] . '-' . $report['to'] . '.csv' ) . '"' );
$out = fopen( 'php://output', 'w' );
fwrite( $out, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Streaming to php://output.
fputcsv( $out, array( 'category', 'campaign', 'type', 'issued', 'codes_used', 'used_percent', 'redemptions', 'saved', 'revenue' ), ';' );
foreach ( $report['campaigns'] as $row ) {
fputcsv(
$out,
array(
$names[ $row['category_id'] ] ?? '',
$row['name'],
$row['type'],
$row['issued'],
$row['used'],
null === $row['rate'] ? '' : $row['rate'],
$row['redemptions'],
$row['saved'],
$row['revenue'],
),
';'
);
}
fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Streaming to php://output.
exit;
}
/**
* @param string[] $links Plugin row links.
* @return string[]
*/
public function action_links( $links ) {
array_unshift( $links, sprintf( '%s', esc_url( self::url() ), esc_html__( 'Campaigns', 'webstyle-coupons' ) ) );
return $links;
}
/**
* Plugin version plus the file's modification time, so browsers fetch a changed file at once.
*
* @param string $file Path inside the plugin.
*/
private static function asset_version( string $file ): string {
$time = @filemtime( CNWSCOUPONS_PATH . $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- A missing file just falls back to the plugin version.
return CNWSCOUPONS_VERSION . ( $time ? '.' . $time : '' );
}
public static function url(): string {
return admin_url( 'admin.php?page=' . self::SLUG );
}
private function settings(): array {
$countries = function_exists( 'WC' ) && WC()->countries ? WC()->countries->get_countries() : array();
return array(
'restPath' => '/' . RestController::NAMESPACE,
// Not wp_nonce_url(): it HTML-escapes the ampersand, which breaks the URL once JavaScript uses it.
'exportUrl' => add_query_arg(
array(
'action' => self::EXPORT_ACTION,
'_wpnonce' => wp_create_nonce( self::EXPORT_ACTION ),
),
admin_url( 'admin-post.php' )
),
'overviewExportUrl' => add_query_arg(
array(
'action' => self::OVERVIEW_EXPORT,
'_wpnonce' => wp_create_nonce( self::OVERVIEW_EXPORT ),
),
admin_url( 'admin-post.php' )
),
'currency' => html_entity_decode( (string) get_woocommerce_currency_symbol(), ENT_QUOTES ),
'countries' => array_map( static fn( $name ) => html_entity_decode( (string) $name, ENT_QUOTES ), $countries ),
'promotions' => $this->promotions->visible(),
'issueBatch' => RestController::ISSUE_BATCH,
'price' => array(
'symbol' => html_entity_decode( (string) get_woocommerce_currency_symbol(), ENT_QUOTES ),
'position' => (string) get_option( 'woocommerce_currency_pos', 'left' ),
'decimal' => wc_get_price_decimal_separator(),
'thousand' => wc_get_price_thousand_separator(),
'decimals' => wc_get_price_decimals(),
),
'dateFormat' => (string) get_option( 'date_format', 'Y-m-d' ),
'shipping' => ShippingInfo::zones(),
'shippingUrl' => admin_url( 'admin.php?page=wc-settings&tab=shipping' ),
'today' => wp_date( 'Y-m-d' ),
);
}
}