Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/paid-memberships-pro/adminpages/memberships-csv.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 +
3 + // only admins can get this
4 + if ( ! function_exists( 'current_user_can' ) || ( ! current_user_can( 'manage_options' ) && ! current_user_can( 'pmpro_reportscsv' ) ) ) {
5 + die( esc_html__( 'You do not have permissions to perform this action.', 'paid-memberships-pro' ) );
6 + }
7 +
8 + if ( ! defined( 'PMPRO_BENCHMARK' ) ) {
9 + define( 'PMPRO_BENCHMARK', false );
10 + }
11 +
12 + $start_memory = memory_get_usage( true );
13 + $start_time = microtime( true );
14 +
15 + if ( true === PMPRO_BENCHMARK ) {
16 + error_log( str_repeat( '-', 10 ) . date_i18n( 'Y-m-d H:i:s' ) . str_repeat( '-', 10 ) );
17 + }
18 +
19 + /**
20 + * Filter to set max number of records to process at a time
21 + * for the export (helps manage memory footprint)
22 + *
23 + * NOTE: Use the pmpro_before_membership_stats_csv_export hook to increase memory "on-the-fly"
24 + * Can reset with the pmpro_after_membership_stats_csv_export hook
25 + *
26 + * @since 2.9
27 + */
28 + // set the number of records we'll load to try and protect ourselves from OOM errors
29 + $max_record_loops = apply_filters( 'pmpro_set_max_membership_stats_records_per_export_loop', 2000 );
30 +
31 + global $wpdb, $pmpro_currency_symbol;
32 +
33 + //get values from form
34 + if(isset($_REQUEST['type']))
35 + $type = sanitize_text_field($_REQUEST['type']);
36 + else
37 + $type = "signup_v_all";
38 +
39 + if(isset($_REQUEST['period']))
40 + $period = sanitize_text_field($_REQUEST['period']);
41 + else
42 + $period = "monthly";
43 +
44 + if(isset($_REQUEST['month']))
45 + $month = intval($_REQUEST['month']);
46 + else
47 + $month = date_i18n("n");
48 +
49 + $thisyear = date_i18n("Y");
50 + if(isset($_REQUEST['year']))
51 + $year = intval($_REQUEST['year']);
52 + else
53 + $year = date_i18n("Y");
54 +
55 + if(isset($_REQUEST['level'])) {
56 + if( $_REQUEST['level'] == 'paid-levels' ) {
57 + $l = pmpro_report_get_levels( 'paid' ); // String of ints and commas. Already escaped for SQL.
58 + }elseif( $_REQUEST['level'] == 'free-levels' ) {
59 + $l = pmpro_report_get_levels( 'free' ); // String of ints and commas. Already escaped for SQL.
60 + }else{
61 + $l = intval($_REQUEST['level']); // Escaping for SQL.
62 + }
63 + } else {
64 + $l = "";
65 + }
66 +
67 + if ( isset( $_REQUEST[ 'discount_code' ] ) ) {
68 + $discount_code = intval( $_REQUEST[ 'discount_code' ] );
69 + } else {
70 + $discount_code = '';
71 + }
72 +
73 + //calculate start date and how to group dates returned from DB
74 + if($period == "daily")
75 + {
76 + $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
77 + $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-' . date_i18n( 't', strtotime( $startdate ) );
78 + $date_function = 'DAY';
79 + }
80 + elseif($period == "monthly")
81 + {
82 + $startdate = $year . '-01-01';
83 + $enddate = strval(intval($year)+1) . '-01-01';
84 + $date_function = 'MONTH';
85 + }
86 + elseif($period == "annual")
87 + {
88 + $startdate = '1970-01-01'; //all time
89 + $enddate = strval(intval($thisyear)+1) . '-01-01';
90 + $date_function = 'YEAR';
91 + }
92 +
93 + //testing or live data
94 + $gateway_environment = get_option( "pmpro_gateway_environment");
95 +
96 + //get data
97 + if (
98 + $type === "signup_v_cancel" ||
99 + $type === "signup_v_expiration" ||
100 + $type === "signup_v_all"
101 + ) {
102 + $sqlQuery = "SELECT $date_function(mu.startdate) as date, COUNT(DISTINCT mu.user_id) as signups
103 + FROM $wpdb->pmpro_memberships_users mu ";
104 +
105 + if ( ! empty( $discount_code ) ) {
106 + $sqlQuery .= "LEFT JOIN $wpdb->pmpro_discount_codes_uses dc ON mu.user_id = dc.user_id ";
107 + }
108 +
109 + $sqlQuery .= "WHERE mu.startdate >= '" . esc_sql( $startdate ) . "' ";
110 +
111 + if ( ! empty( $enddate ) ) {
112 + $sqlQuery .= "AND mu.startdate <= '" . esc_sql( $enddate ) . "' ";
113 + }
114 + }
115 +
116 + if ( ! empty( $l ) ) {
117 + $sqlQuery .= "AND mu.membership_id IN(" . $l . ") "; // $l is already escaped. See declaration.
118 + }
119 +
120 + if ( ! empty( $discount_code ) ) {
121 + $sqlQuery .= "AND dc.code_id = '" . esc_sql( $discount_code ) . "' ";
122 + }
123 +
124 + $sqlQuery .= " GROUP BY date ORDER BY date ";
125 +
126 + $dates = $wpdb->get_results($sqlQuery);
127 +
128 + //fill in blanks in dates
129 + $cols = array();
130 + if($period == "daily")
131 + {
132 + $lastday = date_i18n("t", strtotime($startdate, current_time("timestamp")));
133 +
134 + for($i = 1; $i <= $lastday; $i++)
135 + {
136 + // Signups vs. Cancellations, Expirations, or All
137 + if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all" ) {
138 + $cols[$i] = new stdClass();
139 + $cols[$i]->signups = 0;
140 + foreach($dates as $day => $date)
141 + {
142 + if( $date->date == $i ) {
143 + $cols[$i]->signups = $date->signups;
144 + }
145 + }
146 + }
147 + }
148 + }
149 + elseif($period == "monthly")
150 + {
151 + for($i = 1; $i < 13; $i++)
152 + {
153 + // Signups vs. Cancellations, Expirations, or All
154 + if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all" ) {
155 + $cols[$i] = new stdClass();
156 + $cols[$i]->date = $i;
157 + $cols[$i]->signups = 0;
158 + foreach($dates as $date)
159 + {
160 + if( $date->date == $i ) {
161 + $cols[$i]->date = $date->date;
162 + $cols[$i]->signups = $date->signups;
163 + }
164 + }
165 + }
166 + }
167 + }
168 + elseif($period == "annual") //annual
169 + {
170 + // Get the first year we have signups for.
171 + $first_year = $thisyear;
172 + foreach ( $dates as $date ) {
173 + if ( $date->date < $first_year ) {
174 + $first_year = $date->date;
175 + }
176 + }
177 +
178 + for ( $i = $first_year; $i <= $thisyear; $i++ ) {
179 + // Signups vs. Cancellations, Expirations, or All
180 + if ( $type === 'signup_v_cancel' || $type === 'signup_v_expiration' || $type === 'signup_v_all' ) {
181 + $cols[ $i ] = new stdClass();
182 + $cols[ $i ]->date = $i;
183 + $cols[ $i ]->signups = 0;
184 + foreach ( $dates as $date ) {
185 + if ( $date->date == $i ) {
186 + $cols[ $i ]->date = $date->date;
187 + $cols[ $i ]->signups = $date->signups;
188 + }
189 + }
190 + }
191 + }
192 + }
193 +
194 + $dates = ( ! empty( $cols ) ) ? $cols : $dates;
195 +
196 + // Signups vs. all
197 + if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all" )
198 + {
199 + $sqlQuery = "SELECT $date_function(mu1.modified) as date, COUNT(DISTINCT mu1.user_id) as cancellations
200 + FROM $wpdb->pmpro_memberships_users mu1 ";
201 +
202 + //restrict by discount code
203 + if ( ! empty( $discount_code ) ) {
204 + $sqlQuery .= "LEFT JOIN $wpdb->pmpro_discount_codes_uses dc ON mu1.user_id = dc.user_id ";
205 + }
206 +
207 + if ( $type === "signup_v_cancel")
208 + $sqlQuery .= "WHERE mu1.status IN('inactive','cancelled','admin_cancelled') ";
209 + elseif($type === "signup_v_expiration")
210 + $sqlQuery .= "WHERE mu1.status IN('expired') ";
211 + else
212 + $sqlQuery .= "WHERE mu1.status IN('inactive','expired','cancelled','admin_cancelled') ";
213 +
214 + $sqlQuery .= "AND mu1.enddate >= '" . esc_sql( $startdate ) . "'
215 + AND mu1.enddate < '" . esc_sql( $enddate ) . "' ";
216 +
217 + //restrict by level
218 + if ( ! empty( $l ) ) {
219 + $sqlQuery .= "AND mu1.membership_id IN(" . $l . ") "; // $l is already escaped. See declaration.
220 + }
221 +
222 + if ( ! empty( $discount_code ) ) {
223 + $sqlQuery .= "AND dc.code_id = '" . esc_sql( $discount_code ) . "' ";
224 + }
225 +
226 + $sqlQuery .= " GROUP BY date ORDER BY date ";
227 +
228 + /**
229 + * Filter query to get cancellation numbers in signups vs cancellations detailed report.
230 + *
231 + * @since 1.8.8
232 + *
233 + * @param string $sqlQuery The current SQL
234 + * @param string $type report type
235 + * @param string $startdate Start Date in YYYY-MM-DD format
236 + * @param string $enddate End Date in YYYY-MM-DD format
237 + * @param int $l Level ID
238 + */
239 + $sqlQuery = apply_filters('pmpro_reports_signups_sql', $sqlQuery, $type, $startdate, $enddate, $l);
240 +
241 + $cdates = $wpdb->get_results($sqlQuery, OBJECT_K);
242 +
243 + foreach ( $dates as $day => &$date ) {
244 + if ( ! empty( $cdates ) && ! empty( $cdates[$day] ) ) {
245 + $date->cancellations = $cdates[$day]->cancellations;
246 + } else {
247 + $date->cancellations = 0;
248 + }
249 + }
250 +
251 + }
252 +
253 + $headers = array();
254 + $headers[] = 'Content-Type: text/csv';
255 + $headers[] = 'Cache-Control: max-age=0, no-cache, no-store';
256 + $headers[] = 'Pragma: no-cache';
257 + $headers[] = 'Connection: close';
258 +
259 + $filename = 'membership-statistics.csv';
260 + /*
261 + Insert logic here for building filename from $filter and other values.
262 + */
263 + $filename = apply_filters( 'pmpro_membership_stats_csv_export_filename', $filename );
264 + $headers[] = "Content-Disposition: attachment; filename={$filename};";
265 +
266 + // Default CSV file headers.
267 + $csv_file_header_array = array(
268 + 'date',
269 + 'signups',
270 + 'cancellations'
271 + );
272 +
273 + // These are the meta_keys for the fields (arrays are object, property. so e.g. $theuser->ID) - Date items are manually handled further down.
274 + $default_columns = array(
275 + array( 'each_date', 'date' ),
276 + array( 'each_date', 'signups' ),
277 + array( 'each_date', 'cancellations' )
278 + );
279 +
280 + $dateformat = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
281 +
282 + $csv_file_header = implode( ',', $csv_file_header_array ) . "\n";
283 +
284 + // Generate a temporary file to store the data in.
285 + $tmp_dir = apply_filters( 'pmpro_membership_stats_csv_export_tmp_dir', sys_get_temp_dir() );
286 + $filename = tempnam( $tmp_dir, 'pmpro_olcsv_' );
287 +
288 + // open in append mode
289 + $csv_fh = fopen( $filename, 'a' );
290 +
291 + // write the CSV header to the file
292 + fprintf( $csv_fh, '%s', $csv_file_header );
293 +
294 + $user_ids = $wpdb->get_col( $sqlQuery );
295 + $users_found = count( $user_ids );
296 +
297 + if ( empty( $user_ids ) && empty( $_REQUEST['pmpro_no_download'] ) ) {
298 + // send data to remote browser
299 + pmpro_transmit_report_data( $csv_fh, $filename, $headers );
300 + }
301 +
302 + if ( PMPRO_BENCHMARK ) {
303 + $pre_action_time = microtime( true );
304 + $pre_action_memory = memory_get_usage( true );
305 + }
306 +
307 + do_action( 'pmpro_before_membership_stats_csv_export', $user_ids );
308 +
309 + $i_start = 0;
310 + $i_limit = 0;
311 + $iterations = 1;
312 +
313 + if ( $users_found >= $max_record_loops ) {
314 + $iterations = ceil( $users_found / $max_record_loops );
315 + $i_limit = $max_record_loops;
316 + }
317 +
318 + $end = 0;
319 + $time_limit = ini_get( 'max_execution_time' );
320 +
321 + if ( PMPRO_BENCHMARK ) {
322 + error_log( "PMPRO_BENCHMARK - Total records to process: {$users_found}" );
323 + error_log( "PMPRO_BENCHMARK - Will process {$iterations} iterations of max {$max_record_loops} records per iteration." );
324 + $pre_iteration_time = microtime( true );
325 + $pre_iteration_memory = memory_get_usage( true );
326 + }
327 +
328 + for ( $ic = 1; $ic <= $iterations; $ic ++ ) {
329 +
330 + if ( PMPRO_BENCHMARK ) {
331 + $start_iteration_time = microtime( true );
332 + $start_iteration_memory = memory_get_usage( true );
333 + }
334 +
335 + // avoiding timeouts (modify max run-time for export)
336 + if ( $end != 0 ) {
337 +
338 + $iteration_diff = $end - $start;
339 + $new_time_limit = ceil( $iteration_diff * $iterations * 1.2 );
340 +
341 + if ( $time_limit < $new_time_limit ) {
342 + $time_limit = $new_time_limit;
343 + set_time_limit( $time_limit );
344 + }
345 + }
346 +
347 +
348 + // get the user list we should process
349 + $user_list = array_slice( $user_ids, $i_start, $max_record_loops );
350 +
351 + if ( PMPRO_BENCHMARK ) {
352 + $pre_data_time = microtime( true );
353 + $pre_data_memory = memory_get_usage( true );
354 + }
355 +
356 + foreach ( $dates as $each_date ) {
357 +
358 + $csvoutput = array();
359 +
360 + if ( ! empty( $default_columns ) ) {
361 + $count = 0;
362 +
363 + foreach ( $default_columns as $col ) {
364 +
365 + // checking $object->property. note the double $$
366 + switch ( count( $col ) ) {
367 + case 3:
368 + $val = isset( ${$col[0]}->{$col[1]}->{$col[2]} ) ? ${$col[0]}->{$col[1]}->{$col[2]} : null;
369 + break;
370 +
371 + case 2:
372 + $val = isset( ${$col[0]}->{$col[1]} ) ? ${$col[0]}->{$col[1]} : null;
373 + break;
374 +
375 + default:
376 + $val = null;
377 + }
378 +
379 + array_push( $csvoutput, pmpro_enclose( $val ) );
380 + }
381 + }
382 +
383 + $line = implode( ',', $csvoutput ) . "\n";
384 +
385 + // output
386 + fprintf( $csv_fh, '%s', $line );
387 +
388 + $line = null;
389 + $csvoutput = null;
390 +
391 + $end = current_time( 'timestamp' );
392 +
393 +
394 +
395 + } // end of foreach users.
396 +
397 + if ( PMPRO_BENCHMARK ) {
398 + $after_data_time = microtime( true );
399 + $after_data_memory = memory_get_peak_usage( true );
400 +
401 + $time_processing_data = $after_data_time - $start_time;
402 + $memory_processing_data = $after_data_memory - $start_memory;
403 +
404 + list($sec, $usec) = explode( '.', $time_processing_data );
405 +
406 + error_log( "PMPRO_BENCHMARK - Time processing data: {$sec}.{$usec} seconds" );
407 + error_log( 'PMPRO_BENCHMARK - Peak memory usage: ' . number_format( $memory_processing_data, false, '.', ',' ) . ' bytes' );
408 + }
409 + $user_list = null;
410 + wp_cache_flush();
411 + }
412 +
413 + // If this was run via Toolkit API, we don't have to output the CSV file.
414 + if ( empty( $_REQUEST['pmpro_no_download'] ) ) {
415 + pmpro_transmit_report_data( $csv_fh, $filename, $headers );
416 + }
417 +
418 + /**
419 + * Enclose items passed through to ensure data structure is valid for export CSV.
420 + *
421 + * @param mixed $string|$date Enclose and return a given string, required for CSV files.
422 + * @return string
423 + */
424 + function pmpro_enclose( $s ) {
425 + return '"' . str_replace( '"', '\\"', $s ) . '"';
426 + }
427 +
428 + /**
429 + * Write the data to the CSV and create the CSV file.
430 + *
431 + * @param mixed $csv_fh The temp file we opened and write to.
432 + * @param string $filename The name of the CSV file.
433 + * @param array $headers The headers for the CSV file.
434 + * @return void
435 + */
436 + function pmpro_transmit_report_data( $csv_fh, $filename, $headers = array() ) {
437 +
438 + // close the temp file
439 + fclose( $csv_fh );
440 +
441 + if ( version_compare( phpversion(), '5.3.0', '>' ) ) {
442 +
443 + // make sure we get the right file size
444 + clearstatcache( true, $filename );
445 + } else {
446 + // for any PHP version prior to v5.3.0
447 + clearstatcache();
448 + }
449 +
450 + // did we accidentally send errors/warnings to browser?
451 + if ( headers_sent() ) {
452 + echo esc_html( str_repeat( '-', 75 ) ) . "<br/>\n";
453 + echo 'Please open a support case and paste in the warnings/errors you see above this text to\n ';
454 + echo 'the <a href="http://paidmembershipspro.com/support/?utm_source=plugin&utm_medium=pmpro-membership-stats-csv&utm_campaign=support" target="_blank">Paid Memberships Pro support forum</a><br/>\n';
455 + echo esc_html( str_repeat( '-', 75 ) ) . "<br/>\n";
456 + echo wp_kses_post( file_get_contents( $filename ) );
457 + echo esc_html( str_repeat( '-', 75 ) ) . "<br/>\n";
458 + }
459 +
460 + // transmission
461 + if ( ! empty( $headers ) ) {
462 + // set the download size
463 + $headers[] = 'Content-Length: ' . filesize( $filename );
464 +
465 + // set headers
466 + foreach ( $headers as $header ) {
467 + header( $header . "\r\n" );
468 + }
469 +
470 + // disable compression for the duration of file download
471 + if ( ini_get( 'zlib.output_compression' ) ) {
472 + ini_set( 'zlib.output_compression', 'Off' );
473 + }
474 +
475 + if ( function_exists( 'fpassthru' ) ) {
476 + // use fpassthru to output the csv
477 + $csv_fh = fopen( $filename, 'rb' );
478 + fpassthru( $csv_fh );
479 + fclose( $csv_fh );
480 + } else {
481 + // use readfile() if fpassthru() is disabled (like on Flywheel Hosted)
482 + readfile( $filename );
483 + }
484 +
485 + // remove the temp file
486 + unlink( $filename );
487 + }
488 +
489 + // allow user to clean up after themselves
490 + do_action( 'pmpro_after_membership_stats_csv_export' );
491 + exit;
492 + }
493 +