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' ), ); } }