/var/www/html_us/wp-content/plugins/woocommerce/src/Admin/ReportCSVExporter.php


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
<?php
/**
 * Handles reports CSV export batches.
 */

namespace Automattic\WooCommerce\Admin;

if ( ! 
defined'ABSPATH' ) ) {
    exit;
}

use 
Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;

/**
 * Include dependencies.
 */
if ( ! class_exists'WC_CSV_Batch_Exporter'false ) ) {
    include_once 
WC_ABSPATH 'includes/export/abstract-wc-csv-batch-exporter.php';
}

/**
 * ReportCSVExporter Class.
 */
class ReportCSVExporter extends \WC_CSV_Batch_Exporter {
    
/**
     * Type of report being exported.
     *
     * @var string
     */
    
protected $report_type;

    
/**
     * Parameters for the report query.
     *
     * @var array
     */
    
protected $report_args;

    
/**
     * REST controller for the report.
     *
     * @var WC_REST_Reports_Controller
     */
    
protected $controller;

    
/**
     * Constructor.
     *
     * @param string $type Report type. E.g. 'customers'.
     * @param array  $args Report parameters.
     */
    
public function __construct$type false$args = array() ) {
        
parent::__construct();

        
self::maybe_create_directory();

        if ( ! empty( 
$type ) ) {
            
$this->set_report_type$type );
            
$this->set_column_names$this->get_report_columns() );
        }

        if ( ! empty( 
$args ) ) {
            
$this->set_report_args$args );
        }
    }

    
/**
     * Create the directory for reports if it does not yet exist.
     */
    
public static function maybe_create_directory() {
        
$reports_dir self::get_reports_directory();

        
$files = array(
            array(
                
'base'    => $reports_dir,
                
'file'    => '.htaccess',
                
'content' => 'DirectoryIndex index.php index.html' PHP_EOL 'deny from all',
            ),
            array(
                
'base'    => $reports_dir,
                
'file'    => 'index.html',
                
'content' => '',
            ),
        );

        foreach ( 
$files as $file ) {
            if ( ! 
file_existstrailingslashit$file['base'] ) ) ) {
                
wp_mkdir_p$file['base'] );
            }
            if ( ! 
file_existstrailingslashit$file['base'] ) . $file['file'] ) ) {
                
$file_handle = @fopentrailingslashit$file['base'] ) . $file['file'], 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
                
if ( $file_handle ) {
                    
fwrite$file_handle$file['content'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
                    
fclose$file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
                
}
            }
        }
    }

    
/**
     * Get report uploads directory.
     *
     * @return string
     */
    
public static function get_reports_directory() {
        
$upload_dir wp_upload_dir();
        return 
trailingslashit$upload_dir['basedir'] ) . 'woocommerce_uploads/reports/';
    }

    
/**
     * Get file path to export to.
     *
     * @return string
     */
    
protected function get_file_path() {
        return 
self::get_reports_directory() . $this->get_filename();
    }


    
/**
     * Setter for report type.
     *
     * @param string $type The report type. E.g. customers.
     */
    
public function set_report_type$type ) {
        
$this->report_type $type;
        
$this->export_type "admin_{$type}_report";
        
$this->filename    "wc-{$type}-report-export";
        
$this->controller  $this->map_report_controller();
    }

    
/**
     * Setter for report args.
     *
     * @param array $args The report args.
     */
    
public function set_report_args$args ) {
        
// Use our own internal limit and include all extended info.
        
$report_args array_merge(
            
$args,
            array(
                
'per_page'      => $this->get_limit(),
                
'extended_info' => true,
            )
        );

        
// Should this happen externally?
        
if ( isset( $report_args['page'] ) ) {
            
$this->set_page$report_args['page'] );
        }

        
$this->report_args $report_args;
    }

    
/**
     * Get a REST controller instance for the report type.
     *
     * @return bool|WC_REST_Reports_Controller Report controller instance or boolean false on error.
     */
    
protected function map_report_controller() {
        
// @todo - Add filter to this list.
        
$controller_map = array(
            
'products'   => 'Automattic\WooCommerce\Admin\API\Reports\Products\Controller',
            
'variations' => 'Automattic\WooCommerce\Admin\API\Reports\Variations\Controller',
            
'orders'     => 'Automattic\WooCommerce\Admin\API\Reports\Orders\Controller',
            
'categories' => 'Automattic\WooCommerce\Admin\API\Reports\Categories\Controller',
            
'taxes'      => 'Automattic\WooCommerce\Admin\API\Reports\Taxes\Controller',
            
'coupons'    => 'Automattic\WooCommerce\Admin\API\Reports\Coupons\Controller',
            
'stock'      => 'Automattic\WooCommerce\Admin\API\Reports\Stock\Controller',
            
'downloads'  => 'Automattic\WooCommerce\Admin\API\Reports\Downloads\Controller',
            
'customers'  => 'Automattic\WooCommerce\Admin\API\Reports\Customers\Controller',
            
'revenue'    => 'Automattic\WooCommerce\Admin\API\Reports\Revenue\Stats\Controller',
        );

        if ( isset( 
$controller_map$this->report_type ] ) ) {
            
// Load the controllers if accessing outside the REST API.
            
return new $controller_map$this->report_type ]();
        }

        
// Should this do something else?
        
return false;
    }

    
/**
     * Get the report columns from the controller.
     *
     * @return array Array of report column names.
     */
    
protected function get_report_columns() {
        
// Default to the report's defined export columns.
        
if ( $this->controller instanceof ExportableInterface ) {
            return 
$this->controller->get_export_columns();
        }

        
// Fallback to generating columns from the report schema.
        
$report_columns = array();
        
$report_schema  $this->controller->get_item_schema();

        if ( isset( 
$report_schema['properties'] ) ) {
            foreach ( 
$report_schema['properties'] as $column_name => $column_info ) {
                
// Expand extended info columns into export.
                
if ( 'extended_info' === $column_name ) {
                    
// Remove columns with questionable CSV values, like markup.
                    
$extended_info  array_diffarray_keys$column_info ), array( 'image' ) );
                    
$report_columns array_merge$report_columns$extended_info );
                } else {
                    
$report_columns[] = $column_name;
                }
            }
        }

        return 
$report_columns;
    }

    
/**
     * Get total % complete.
     *
     * Forces an int from parent::get_percent_complete(), which can return a float.
     *
     * @return int Percent complete.
     */
    
public function get_percent_complete() {
        return 
intvalparent::get_percent_complete() );
    }

    
/**
     * Get total number of rows in export.
     *
     * @return int Number of rows to export.
     */
    
public function get_total_rows() {
        return 
$this->total_rows;
    }

    
/**
     * Prepare data for export.
     */
    
public function prepare_data_to_export() {
        
$request  = new \WP_REST_Request'GET'"/wc-analytics/reports/{$this->report_type});
        
$params   $this->controller->get_collection_params();
        
$defaults = array();

        foreach ( 
$params as $arg => $options ) {
            if ( isset( 
$options['default'] ) ) {
                
$defaults$arg ] = $options['default'];
            }
        }

        
$request->set_attributes( array( 'args' => $params ) );
        
$request->set_default_params$defaults );
        
$request->set_query_params$this->report_args );
        
$request->sanitize_params();

        
// Does the controller have an export-specific item retrieval method?
        // @todo - Potentially revisit. This is only for /revenue/stats/.
        
if ( is_callable( array( $this->controller'get_export_items' ) ) ) {
            
$response $this->controller->get_export_items$request );
        } else {
            
$response $this->controller->get_items$request );
        }

        
// Use WP_REST_Server::response_to_data() to embed links in data.
        
add_filter'woocommerce_rest_check_permissions''__return_true' );
        
$rest_server rest_get_server();
        
$report_data $rest_server->response_to_data$responsetrue );
        
remove_filter'woocommerce_rest_check_permissions''__return_true' );

        
$report_meta      $response->get_headers();
        
$this->total_rows $report_meta['X-WP-Total'];
        
$this->row_data   array_map( array( $this'generate_row_data' ), $report_data );
    }

    
/**
     * Generate row data from a raw report item.
     *
     * @param object $item Report item data.
     * @return array CSV row data.
     */
    
protected function get_raw_row_data$item ) {
        
$columns $this->get_column_names();
        
$row     = array();

        
// Expand extended info.
        
if ( isset( $item['extended_info'] ) ) {
            
// Pull extended info property from report item object.
            
$extended_info = (array) $item['extended_info'];
            unset( 
$item['extended_info'] );

            
// Merge extended info columns into report item object.
            
$item array_merge$item$extended_info );
        }

        foreach ( 
$columns as $column_id => $column_name ) {
            
$value = isset( $item$column_name ] ) ? $item$column_name ] : null;

            if ( 
has_filter"woocommerce_export_{$this->export_type}_column_{$column_name}) ) {
                
// Filter for 3rd parties.
                
$value apply_filters"woocommerce_export_{$this->export_type}_column_{$column_name}"''$item );

            } elseif ( 
is_callable( array( $this"get_column_value_{$column_name}) ) ) {
                
// Handle special columns which don't map 1:1 to item data.
                
$value $this->{"get_column_value_{$column_name}"}( $item$this->export_type );

            } elseif ( ! 
is_scalar$value ) ) {
                
// Ensure that the value is somewhat readable in CSV.
                
$value wp_json_encode$value );
            }

            
$row$column_id ] = $value;
        }

        return 
$row;
    }

    
/**
     * Get the export row for a given report item.
     *
     * @param object $item Report item data.
     * @return array CSV row data.
     */
    
protected function generate_row_data$item ) {
        
// Default to the report's export method.
        
if ( $this->controller instanceof ExportableInterface ) {
            
$row $this->controller->prepare_item_for_export$item );
        } else {
            
// Fallback to raw report data.
            
$row $this->get_raw_row_data$item );
        }

        return 
apply_filters"woocommerce_export_{$this->export_type}_row_data"$row$item );
    }
}