Paid Memberships Pro - Version 1.8.6.1

Version Description

  • SECURITY: Removed debug code from the PayPal IPN Handler script that was causing invalid IPN requests to process as if they were valid. (Thanks, Francois Harvey)
Download this release

Release Info

Developer strangerstudios
Plugin Icon 128x128 Paid Memberships Pro
Version 1.8.6.1
Comparing to
See all releases

Code changes from version 1.8.6.2 to 1.8.6.1

adminpages/functions.php CHANGED
@@ -55,7 +55,8 @@ function pmpro_checkLevelForStripeCompatibility($level = NULL)
55
$level = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_membership_levels WHERE id = '" . esc_sql($level) . "' LIMIT 1");
56
57
//check this level
58
- if($level->billing_limit > 0)
59
{
60
return false;
61
}
55
$level = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_membership_levels WHERE id = '" . esc_sql($level) . "' LIMIT 1");
56
57
//check this level
58
+ if(($level->cycle_number > 0 && $level->cycle_period == "Day") ||
59
+ $level->billing_limit > 0)
60
{
61
return false;
62
}
adminpages/reports.php CHANGED
@@ -1,66 +1,62 @@
1
- <?php
2
- global $pmpro_reports;
3
-
4
- require_once(dirname(__FILE__) . "/admin_header.php");
5
-
6
- //default view, report widgets
7
- if(empty($_REQUEST['report']))
8
- {
9
- //wrapper
10
- ?>
11
- <div id="dashboard-widgets-wrap">
12
- <div id="dashboard-widgets" class="metabox-holder pmpro_reports-holder">
13
- <div id="postbox-container-1" class="postbox-container">
14
- <div id="normal-sortables" class="meta-box-sortables ui-sortable">
15
- <?php
16
-
17
- //report widgets
18
- $count = 0;
19
- $nreports = count($pmpro_reports);
20
- $split = false;
21
- foreach($pmpro_reports as $report => $title)
22
- {
23
- //make sure title is translated (since these are set before translations happen)
24
- $title = __($title, "pmpro");
25
-
26
- //put half of the report widgets in postbox-container-2
27
- if(!$split && $count++ > $nreports/2)
28
- {
29
- $split = true;
30
- ?>
31
- </div></div><div id="postbox-container-2" class="postbox-container"><div id="side-sortables" class="meta-box-sortables ui-sortable">
32
- <?php
33
- }
34
- ?>
35
- <div id="pmpro_report_<?php echo $report; ?>" class="postbox pmpro_clickable" onclick="location.href='<?php echo admin_url("admin.php?page=pmpro-reports&report=" . $report);?>';">
36
- <h3 class="hndle"><span><?php echo $title; ?></span></h3>
37
- <div class="inside">
38
- <?php call_user_func("pmpro_report_" . $report . "_widget"); ?>
39
- <p style="text-align:center;">
40
- <a class="button button-primary" href="<?php echo admin_url("admin.php?page=pmpro-reports&report=" . $report);?>"><?php _e('Details', 'pmpro');?></a>
41
- </p>
42
- </div>
43
- </div>
44
- <?php
45
- }
46
-
47
- //end wrapper
48
- ?>
49
- </div>
50
- </div>
51
- </div>
52
- <?php
53
- }
54
- else
55
- {
56
- //view a single report
57
- $report = sanitize_text_field($_REQUEST['report']);
58
- call_user_func("pmpro_report_" . $report . "_page");
59
- ?>
60
- <hr />
61
- <a class="button button-primary" href="<?php echo admin_url("admin.php?page=pmpro-reports");?>"><?php _e('Back to Reports Dashboard', 'pmpro');?></a>
62
- <?php
63
- }
64
-
65
- require_once(dirname(__FILE__) . "/admin_footer.php");
66
?>
1
+ <?php
2
+ global $pmpro_reports;
3
+
4
+ require_once(dirname(__FILE__) . "/admin_header.php");
5
+
6
+ //default view, report widgets
7
+ if(empty($_REQUEST['report']))
8
+ {
9
+ //wrapper
10
+ ?>
11
+ <div id="dashboard-widgets-wrap">
12
+ <div id="dashboard-widgets" class="metabox-holder pmpro_reports-holder columns-2">
13
+ <div id="postbox-container-1" class="postbox-container">
14
+ <div id="normal-sortables" class="meta-box-sortables ui-sortable">
15
+ <?php
16
+
17
+ //report widgets
18
+ $count = 0;
19
+ $nreports = count($pmpro_reports);
20
+ $split = false;
21
+ foreach($pmpro_reports as $report => $title)
22
+ {
23
+ //make sure title is translated (since these are set before translations happen)
24
+ $title = __($title, "pmpro");
25
+
26
+ //put half of the report widgets in postbox-container-2
27
+ if(!$split && $count++ > $nreports/2)
28
+ {
29
+ $split = true;
30
+ ?>
31
+ </div></div><div id="postbox-container-2" class="postbox-container"><div id="side-sortables" class="meta-box-sortables ui-sortable">
32
+ <?php
33
+ }
34
+ ?>
35
+ <div id="pmpro_report_<?php echo $report; ?>" class="postbox pmpro_clickable" onclick="location.href='<?php echo admin_url("admin.php?page=pmpro-reports&report=" . $report);?>';">
36
+ <h3 class="hndle"><span><?php echo $title; ?></span></h3>
37
+ <div class="inside">
38
+ <?php call_user_func("pmpro_report_" . $report . "_widget"); ?>
39
+ <div style="margin-top:10px;border-top: 1px solid #ddd; padding-top: 10px; text-align:center;">
40
+ <a class="button button-primary" href="<?php echo admin_url("admin.php?page=pmpro-reports&report=" . $report);?>"><?php _e('Details', 'pmpro');?></a>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ <?php
45
+ }
46
+
47
+ //end wrapper
48
+ ?>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <?php
53
+ }
54
+ else
55
+ {
56
+ //view a single report
57
+ $report = sanitize_text_field($_REQUEST['report']);
58
+ call_user_func("pmpro_report_" . $report . "_page");
59
+ }
60
+
61
+ require_once(dirname(__FILE__) . "/admin_footer.php");
62
?>
adminpages/reports/login.php CHANGED
@@ -23,38 +23,22 @@ function pmpro_report_login_widget()
23
$views = get_option("pmpro_views", array("today"=>0, "thisday"=>date("Y-m-d", $now), "alltime"=>0, "month"=>0, "thismonth"=>date("n", $now)));
24
$logins = get_option("pmpro_logins", array("today"=>0, "thisday"=>date("Y-m-d", $now), "alltime"=>0, "month"=>0, "thismonth"=>date("n", $now)));
25
?>
26
- <span id="pmpro_report_login">
27
- <table class="wp-list-table widefat fixed striped">
28
- <thead>
29
- <tr>
30
- <th scope="col">&nbsp;</th>
31
- <th scope="col"><?php _e('Visits','pmpro'); ?></th>
32
- <th scope="col"><?php _e('Views','pmpro'); ?></th>
33
- <th scope="col"><?php _e('Logins','pmpro'); ?></th>
34
- </tr>
35
- </thead>
36
- <tbody>
37
- <tr>
38
- <th scope="row"><?php _e('Today','pmpro'); ?></th>
39
- <td><?php echo number_format_i18n($visits['today']); ?></td>
40
- <td><?php echo number_format_i18n($views['today']); ?></td>
41
- <td><?php echo number_format_i18n($logins['today']);?></td>
42
- </tr>
43
- <tr>
44
- <th scope="row"><?php _e('This Month','pmpro'); ?></th>
45
- <td><?php echo number_format_i18n($visits['month']); ?></td>
46
- <td><?php echo number_format_i18n($views['month']); ?></td>
47
- <td><?php echo number_format_i18n($logins['month']); ?></td>
48
- </tr>
49
- <tr>
50
- <th scope="row"><?php _e('All Time','pmpro'); ?></th>
51
- <td><?php echo number_format_i18n($visits['alltime']); ?></td>
52
- <td><?php echo number_format_i18n($views['alltime']);?></td>
53
- <td><?php echo number_format_i18n($logins['alltime']); ?></td>
54
- </tr>
55
- </tbody>
56
- </table>
57
- </span>
58
<?php
59
}
60
23
$views = get_option("pmpro_views", array("today"=>0, "thisday"=>date("Y-m-d", $now), "alltime"=>0, "month"=>0, "thismonth"=>date("n", $now)));
24
$logins = get_option("pmpro_logins", array("today"=>0, "thisday"=>date("Y-m-d", $now), "alltime"=>0, "month"=>0, "thismonth"=>date("n", $now)));
25
?>
26
+ <div style="width: 33%; float: left;">
27
+ <p><?php _e('Visits Today', 'pmpro')?>: <?php echo $visits['today'];?></p>
28
+ <p><?php _e('Visits This Month', 'pmpro')?>: <?php echo $visits['month'];?></p>
29
+ <p><?php _e('Visits All Time', 'pmpro')?>: <?php echo $visits['alltime'];?></p>
30
+ </div>
31
+ <div style="width: 33%; float: left;">
32
+ <p><?php _e('Views Today', 'pmpro')?>: <?php echo $views['today'];?></p>
33
+ <p><?php _e('Views This Month', 'pmpro')?>: <?php echo $views['month'];?></p>
34
+ <p><?php _e('Views All Time', 'pmpro')?>: <?php echo $views['alltime'];?></p>
35
+ </div>
36
+ <div style="width: 33%; float: left;">
37
+ <p><?php _e('Logins Today', 'pmpro')?>: <?php echo $logins['today'];?></p>
38
+ <p><?php _e('Logins This Month', 'pmpro')?>: <?php echo $logins['month'];?></p>
39
+ <p><?php _e('Logins All Time', 'pmpro')?>: <?php echo $logins['alltime'];?></p>
40
+ </div>
41
+ <div class="clear"></div>
42
<?php
43
}
44
adminpages/reports/memberships.php CHANGED
@@ -1,624 +1,660 @@
1
- <?php
2
- /*
3
- PMPro Report
4
- Title: Membership Stats
5
- Slug: memberships
6
-
7
- For each report, add a line like:
8
- global $pmpro_reports;
9
- $pmpro_reports['slug'] = 'Title';
10
-
11
- For each report, also write two functions:
12
- * pmpro_report_{slug}_widget() to show up on the report homepage.
13
- * pmpro_report_{slug}_page() to show up when users click on the report page widget.
14
- */
15
-
16
- global $pmpro_reports;
17
-
18
- $pmpro_reports['memberships'] = __('Membership Stats', 'pmpro');
19
-
20
- //queue Google Visualization JS on report page
21
- function pmpro_report_memberships_init() {
22
- if(is_admin() && isset($_REQUEST['report']) && $_REQUEST['report'] == "memberships" && isset($_REQUEST['page']) && $_REQUEST['page'] == "pmpro-reports")
23
- wp_enqueue_script("jsapi", "https://www.google.com/jsapi");
24
- }
25
- add_action( 'init', 'pmpro_report_memberships_init' );
26
-
27
-
28
- //widget
29
- function pmpro_report_memberships_widget() {
30
- global $wpdb;
31
- ?>
32
- <span id="pmpro_report_memberships">
33
- <table class="wp-list-table widefat fixed striped">
34
- <thead>
35
- <tr>
36
- <th scope="col">&nbsp;</th>
37
- <th scope="col"><?php _e('Signups','pmpro'); ?></th>
38
- <th scope="col"><?php _e('Cancellations','pmpro'); ?></th>
39
- </tr>
40
- </thead>
41
- <tbody>
42
- <tr>
43
- <th scope="row"><?php _e('Today','pmpro'); ?></th>
44
- <td><?php echo number_format_i18n(pmpro_getSignups('today')); ?></td>
45
- <td><?php echo number_format_i18n(pmpro_getCancellations('today')); ?></td>
46
- </tr>
47
- <tr>
48
- <th scope="row"><?php _e('This Month','pmpro'); ?></th>
49
- <td><?php echo number_format_i18n(pmpro_getSignups('this month')); ?></td>
50
- <td><?php echo number_format_i18n(pmpro_getCancellations('this month')); ?></td>
51
- </tr>
52
- <tr>
53
- <th scope="row"><?php _e('This Year','pmpro'); ?></th>
54
- <td><?php echo number_format_i18n(pmpro_getSignups('this year')); ?></td>
55
- <td><?php echo number_format_i18n(pmpro_getCancellations('this year')); ?></td>
56
- </tr>
57
- <tr>
58
- <th scope="row"><?php _e('All Time','pmpro'); ?></th>
59
- <td><?php echo number_format_i18n(pmpro_getSignups('all time')); ?></td>
60
- <td><?php echo number_format_i18n(pmpro_getCancellations('all time')); ?></td>
61
- </tr>
62
- </tbody>
63
- </table>
64
- </span>
65
- <?php
66
- }
67
-
68
- function pmpro_report_memberships_page()
69
- {
70
- global $wpdb, $pmpro_currency_symbol;
71
-
72
- //get values from form
73
- if(isset($_REQUEST['type']))
74
- $type = sanitize_text_field($_REQUEST['type']);
75
- else
76
- $type = "signup_v_cancel";
77
-
78
- if(isset($_REQUEST['period']))
79
- $period = sanitize_text_field($_REQUEST['period']);
80
- else
81
- $period = "monthly";
82
-
83
- if(isset($_REQUEST['month']))
84
- $month = intval($_REQUEST['month']);
85
- else
86
- $month = date("n");
87
-
88
- $thisyear = date("Y");
89
- if(isset($_REQUEST['year']))
90
- $year = intval($_REQUEST['year']);
91
- else
92
- $year = date("Y");
93
-
94
- if(isset($_REQUEST['level']))
95
- $l = intval($_REQUEST['level']);
96
- else
97
- $l = "";
98
-
99
- //calculate start date and how to group dates returned from DB
100
- if($period == "daily")
101
- {
102
- $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
103
- $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
104
- $date_function = 'DAY';
105
- }
106
- elseif($period == "monthly")
107
- {
108
- $startdate = $year . '-01-01';
109
- $enddate = strval(intval($year)+1) . '-01-01';
110
- $date_function = 'MONTH';
111
- }
112
- elseif($period == "annual")
113
- {
114
- $startdate = '1960-01-01'; //all time
115
- $date_function = 'YEAR';
116
- }
117
-
118
- //testing or live data
119
- $gateway_environment = pmpro_getOption("gateway_environment");
120
-
121
- //get data
122
- if ( $type === "signup_v_cancel" ) {
123
- $sqlQuery = "SELECT $date_function(startdate) as date, COUNT(DISTINCT user_id) as signups
124
- FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . $startdate . "' ";
125
-
126
- if(!empty($enddate))
127
- $sqlQuery .= "AND startdate < '" . $enddate . "' ";
128
- }
129
- if ( $type === "mrr_ltv" ) {
130
- // Get total revenue, number of months in system, and date
131
- if ( $period == 'annual' )
132
- $sqlQuery = "SELECT SUM(total) as total, COUNT(DISTINCT MONTH(timestamp)) as months, $date_function(timestamp) as date
133
- FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
134
- AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
135
-
136
- if ( $period == 'monthly' )
137
- $sqlQuery = "SELECT SUM(total) as total, $date_function(timestamp) as date
138
- FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
139
- AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
140
-
141
- if(!empty($enddate))
142
- $sqlQuery .= "AND timestamp < '" . $enddate . "' ";
143
- }
144
-
145
- if(!empty($l))
146
- $sqlQuery .= "AND membership_id IN(" . $l . ") ";
147
-
148
- $sqlQuery .= " GROUP BY date ORDER BY date ";
149
-
150
- $dates = $wpdb->get_results($sqlQuery);
151
-
152
- //fill in blanks in dates
153
- $cols = array();
154
- if($period == "daily")
155
- {
156
- $lastday = date("t", strtotime($startdate, current_time("timestamp")));
157
-
158
- for($i = 1; $i <= $lastday; $i++)
159
- {
160
- // Signups vs. Cancellations
161
- if ( $type === "signup_v_cancel" ) {
162
- $cols[$i] = new stdClass();
163
- $cols[$i]->signups = 0;
164
- foreach($dates as $date)
165
- {
166
- if( $date->date == $i ) {
167
- $cols[$i]->signups = $date->signups;
168
- }
169
- }
170
- }
171
- }
172
- }
173
- elseif($period == "monthly")
174
- {
175
- for($i = 1; $i < 13; $i++)
176
- {
177
- // Signups vs. Cancellations
178
- if ( $type === "signup_v_cancel" ) {
179
- $cols[$i] = new stdClass();
180
- $cols[$i]->date = $i;
181
- $cols[$i]->signups = 0;
182
- foreach($dates as $date)
183
- {
184
- if( $date->date == $i ) {
185
- $cols[$i]->date = $date->date;
186
- $cols[$i]->signups = $date->signups;
187
- }
188
- }
189
- }
190
-
191
- // MRR & LTV
192
- if ( $type === "mrr_ltv" ) {
193
- $cols[$i] = new stdClass();
194
- $cols[$i]->date = $i;
195
- $cols[$i]->months = 1;
196
- foreach($dates as $date)
197
- {
198
- if( $date->date == $i ) {
199
- $cols[$i]->total = $date->total;
200
- }
201
- }
202
- }
203
- }
204
- }
205
- elseif($period == "annual") //annual
206
- {
207
- }
208
-
209
- $dates = ( ! empty( $cols ) ) ? $cols : $dates;
210
-
211
- // Signups vs. cancellations
212
- if ( $type === "signup_v_cancel" )
213
- {
214
- $sqlQuery = "SELECT $date_function(mu1.modified) as date, COUNT(DISTINCT mu1.user_id) as cancellations
215
- FROM $wpdb->pmpro_memberships_users mu1
216
- LEFT JOIN $wpdb->pmpro_memberships_users mu2 ON mu1.user_id = mu2.user_id AND
217
- mu2.modified > mu1.enddate AND
218
- DATE_ADD(mu1.modified, INTERVAL 1 DAY) > mu2.startdate
219
- WHERE mu1.status = 'inactive'
220
- AND mu2.id IS NULL
221
- AND mu1.startdate >= '" . $startdate . "'
222
- AND mu1.startdate < '" . $enddate . "' ";
223
-
224
- //restrict by level
225
- if(!empty($l))
226
- $sqlQuery .= "AND membership_id IN(" . $l . ") ";
227
-
228
- $sqlQuery .= " GROUP BY date ORDER BY date ";
229
-
230
- $cdates = $wpdb->get_results($sqlQuery, OBJECT_K);
231
-
232
- foreach( $dates as &$date )
233
- {
234
- if(!empty($cdates) && !empty($cdates[$date->date]))
235
- $date->cancellations = $cdates[$date->date]->cancellations;
236
- else
237
- $date->cancellations = 0;
238
- }
239
- }
240
-
241
- // MRR & LTV
242
- if ( $type === "mrr_ltv" && count( $dates ) === 1 ) {
243
- $dummy_date = new stdClass();
244
- $dummy_date->total = 0;
245
- $dummy_date->months = 0;
246
- $dummy_date->date = $dates[0]->date - 1;
247
- array_unshift( $dates, $dummy_date ); // Add to beginning
248
- }
249
- ?>
250
- <form id="posts-filter" method="get" action="">
251
- <h2>
252
- <?php _e('Membership Stats', 'pmpro');?>
253
- </h2>
254
- <ul class="subsubsub">
255
- <li>
256
- <?php _ex('Show', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?>
257
- <select id="period" name="period">
258
- <option value="daily" <?php selected($period, "daily");?>><?php _e('Daily', 'pmpro');?></option>
259
- <option value="monthly" <?php selected($period, "monthly");?>><?php _e('Monthly', 'pmpro');?></option>
260
- <option value="annual" <?php selected($period, "annual");?>><?php _e('Annual', 'pmpro');?></option>
261
- </select>
262
- <select id="type" name="type">
263
- <option value="signup_v_cancel" <?php selected($type, "signup_v_cancel");?>><?php _e('Signups vs. Cancellations', 'pmpro');?></option>
264
- <?php /*
265
- <option value="mrr_ltv" <?php selected($type, "mrr_ltv");?>><?php _e('MRR & LTV', 'pmpro');?></option>
266
- */ ?>
267
- </select>
268
- <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
269
- <select id="month" name="month">
270
- <?php for($i = 1; $i < 13; $i++) { ?>
271
- <option value="<?php echo $i;?>" <?php selected($month, $i);?>><?php echo date("F", mktime(0, 0, 0, $i, 2));?></option>
272
- <?php } ?>
273
- </select>
274
- <select id="year" name="year">
275
- <?php for($i = $thisyear; $i > 2007; $i--) { ?>
276
- <option value="<?php echo $i;?>" <?php selected($year, $i);?>><?php echo $i;?></option>
277
- <?php } ?>
278
- </select>
279
- <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
280
- <select name="level">
281
- <option value="" <?php if(!$l) { ?>selected="selected"<?php } ?>><?php _e('All Levels', 'pmpro');?></option>
282
- <?php
283
- $levels = $wpdb->get_results("SELECT id, name FROM $wpdb->pmpro_membership_levels ORDER BY name");
284
- foreach($levels as $level)
285
- {
286
- ?>
287
- <option value="<?php echo $level->id?>" <?php if($l == $level->id) { ?>selected="selected"<?php } ?>><?php echo $level->name?></option>
288
- <?php
289
- }
290
- ?>
291
- </select>
292
-
293
- <input type="hidden" name="page" value="pmpro-reports" />
294
- <input type="hidden" name="report" value="memberships" />
295
- <input type="submit" value="<?php _ex('Generate Report', 'Submit button value.', 'pmpro');?>" />
296
- </li>
297
- </ul>
298
-
299
- <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
300
-
301
- <script>
302
- //update month/year when period dropdown is changed
303
- jQuery(document).ready(function() {
304
- jQuery('#period').change(function() {
305
- pmpro_ShowMonthOrYear();
306
- });
307
- });
308
-
309
- function pmpro_ShowMonthOrYear()
310
- {
311
- var period = jQuery('#period').val();
312
- if(period == 'daily')
313
- {
314
- jQuery('#for').show();
315
- jQuery('#month').show();
316
- jQuery('#year').show();
317
- }
318
- else if(period == 'monthly')
319
- {
320
- jQuery('#for').show();
321
- jQuery('#month').hide();
322
- jQuery('#year').show();
323
- }
324
- else
325
- {
326
- jQuery('#for').hide();
327
- jQuery('#month').hide();
328
- jQuery('#year').hide();
329
- }
330
- }
331
-
332
- pmpro_ShowMonthOrYear();
333
-
334
- //draw the chart
335
- google.load("visualization", "1", {packages:["corechart"]});
336
- google.setOnLoadCallback(drawChart);
337
- function drawChart() {
338
-
339
- var data = google.visualization.arrayToDataTable([
340
- <?php if ( $type === "signup_v_cancel" ) : // Signups vs. cancellations ?>
341
- ['<?php echo $date_function;?>', 'Signups', 'Cancellations'],
342
- <?php foreach($dates as $key => $value) { ?>
343
- ['<?php if($period == "monthly") echo date("M", mktime(0,0,0,$value->date,2)); else if($period == "daily") echo $key; else echo $value->date;?>', <?php echo $value->signups; ?>, <?php echo $value->cancellations; ?>],
344
- <?php } ?>
345
- <?php endif; ?>
346
-
347
- <?php if ( $type === "mrr_ltv" ) : // Signups vs. cancellations ?>
348
- ['<?php echo $date_function;?>', 'MRR', 'LTV'],
349
- <?php foreach($dates as $key => $value) { ?>
350
- ['<?php if($period == "monthly") echo date("M", mktime(0,0,0,$value->date,2)); else if($period == "daily") echo $key; else echo $value->date;?>', <?php echo (($mrr = $value->total / $value->months) && $mrr != 0) ? $mrr : 0; ?>, <?php echo pmpro_getLTV($period, NULL, $mrr ); ?>],
351
- <?php } ?>
352
- <?php endif; ?>
353
- ]);
354
-
355
- var options = {
356
- colors: ['#0099c6', '#dc3912'],
357
- hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
358
- vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
359
- };
360
-
361
- <?php if ( $type === "signup_v_cancel" ) : // Signups vs. cancellations ?>
362
- var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
363
- <?php elseif ( $type === "mrr_ltv" ) : // MRR & LTV ?>
364
-
365
- <?php
366
- //prefix or suffix?
367
- if(pmpro_getCurrencyPosition() == "right")
368
- $position = "suffix";
369
- else
370
- $position = "prefix";
371
- ?>
372
-
373
- var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
374
- formatter.format(data, 2);
375
- var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
376
- formatter.format(data, 1);
377
-
378
- var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
379
- <?php endif; ?>
380
- chart.draw(data, options);
381
- }
382
- </script>
383
-
384
- </form>
385
- <?php
386
- }
387
-
388
-
389
-
390
- /*
391
- Other code required for your reports. This file is loaded every time WP loads with PMPro enabled.
392
- */
393
-
394
- //get signups
395
- function pmpro_getSignups($period = false, $levels = 'all')
396
- {
397
- //check for a transient
398
- $cache = get_transient( 'pmpro_report_memberships_signups' );
399
- if( ! empty( $cache ) && ! empty( $cache[$period] ) && ! empty( $cache[$period][$levels] ) )
400
- return $cache[$period][$levels];
401
-
402
- //a sale is an order with status = success
403
- if( $period == 'today' )
404
- $startdate = date(' Y-m-d' );
405
- elseif( $period == 'this month')
406
- $startdate = date( 'Y-m' ) . '-01';
407
- elseif( $period == 'this year')
408
- $startdate = date( 'Y' ) . '-01-01';
409
- else
410
- $startdate = '';
411
-
412
-
413
- //build query
414
- global $wpdb;
415
-
416
- $sqlQuery = "SELECT COUNT(DISTINCT user_id) FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . $startdate . "' ";
417
-
418
- //restrict by level
419
- if(!empty($levels) && $levels != 'all')
420
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
421
-
422
- $signups = $wpdb->get_var($sqlQuery);
423
-
424
- //save in cache
425
- if(!empty($cache) && !empty($cache[$period]))
426
- $cache[$period][$levels] = $signups;
427
- elseif(!empty($cache))
428
- $cache[$period] = array($levels => $signups);
429
- else
430
- $cache = array($period => array($levels => $signups));
431
-
432
- set_transient("pmpro_report_memberships_signups", $cache, 3600*24);
433
-
434
- return $signups;
435
- }
436
-
437
- //get cancellations
438
- function pmpro_getCancellations($period = false, $levels = 'all')
439
- {
440
- //check for a transient
441
- $cache = get_transient( 'pmpro_report_memberships_cancellations' );
442
- if( ! empty( $cache ) && ! empty( $cache[$period] ) && ! empty( $cache[$period][$levels] ) )
443
- return $cache[$period][$levels];
444
-
445
- //figure out start date
446
- if( $period == 'today' )
447
- $startdate = date(' Y-m-d' );
448
- elseif( $period == 'this month')
449
- $startdate = date( 'Y-m' ) . '-01';
450
- elseif( $period == 'this year')
451
- $startdate = date( 'Y' ) . '-01-01';
452
- else
453
- $startdate = '';
454
-
455
- $startdate_plus_one = strtotime( $startdate . + ' + 1 day', current_time("timestamp") );
456
-
457
- /*
458
- build query.
459
- cancellations are marked in the memberships users table with status = 'inactive'
460
- we try to ignore cancellations when the user gets a new level with 24 hours (probably an upgrade or downgrade)
461
- */
462
- global $wpdb;
463
-
464
- //$sqlQuery = "SELECT mu1.user_id, mu2.user_id FROM $wpdb->pmpro_memberships_users mu1 LEFT JOIN $wpdb->pmpro_memberships_users mu2 ON mu1.user_id = mu2.user_id AND mu2.status = 'inactive' AND mu2.startdate > mu1.startdate";
465
- $sqlQuery = "SELECT COUNT(mu1.id)
466
- FROM $wpdb->pmpro_memberships_users mu1
467
- LEFT JOIN $wpdb->pmpro_memberships_users mu2 ON mu1.user_id = mu2.user_id AND
468
- mu2.modified > mu1.enddate AND
469
- DATE_ADD(mu1.modified, INTERVAL 1 DAY) > mu2.startdate
470
- WHERE mu1.status = 'inactive'
471
- AND mu2.id IS NULL
472
- AND mu1.startdate >= '" . $startdate . "' ";
473
-
474
- //restrict by level
475
- if(!empty($levels) && $levels != 'all')
476
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
477
-
478
- $cancellations = $wpdb->get_var($sqlQuery);
479
-
480
- //save in cache
481
- if(!empty($cache) && !empty($cache[$period]) && is_array($cache[$period]))
482
- $cache[$period][$levels] = $cancellations;
483
- elseif(!empty($cache))
484
- $cache[$period] = array($levels => $cancellations);
485
- else
486
- $cache = array($period => array($levels => $cancellations));
487
-
488
- set_transient("pmpro_report_memberships_cancellations", $cache, 3600*24);
489
-
490
- return $cancellations;
491
- }
492
-
493
- //get MRR
494
- function pmpro_getMRR($period, $levels = 'all')
495
- {
496
- //check for a transient
497
- //$cache = get_transient("pmpro_report_mrr");
498
- if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
499
- return $cache[$period][$levels];
500
-
501
- //a sale is an order with status NOT IN refunded, review, token, error
502
- if($period == "this month")
503
- $startdate = date("Y-m") . "-01";
504
- elseif($period == "this year")
505
- $startdate = date("Y") . "-01-01";
506
- else
507
- $startdate = "";
508
-
509
- $gateway_environment = pmpro_getOption("gateway_environment");
510
-
511
- //build query
512
- global $wpdb;
513
- // Get total revenue
514
- $sqlQuery = "SELECT SUM(total) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
515
-
516
- //restrict by level
517
- if(!empty($levels) && $levels != 'all') {
518
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
519
- }
520
-
521
- $revenue = $wpdb->get_var($sqlQuery);
522
-
523
- //when was the first order
524
- $first_order_timestamp = $wpdb->get_var("SELECT UNIX_TIMESTAMP(`timestamp`) FROM $wpdb->pmpro_membership_orders WHERE `timestamp` IS NOT NULL AND `timestamp` > '0000-00-00 00:00:00' ORDER BY `timestamp` LIMIT 1");
525
-
526
- //if we don't have a timestamp, we can't do this
527
- if(empty($first_order_timestamp))
528
- return false;
529
-
530
- //how many months ago was the first order
531
- $months = $wpdb->get_var("SELECT PERIOD_DIFF('" . date("Ym") . "', '" . date("Ym", $first_order_timestamp) . "')");
532
-
533
- /* this works in PHP 5.3+ without using MySQL to get the diff
534
- $date1 = new DateTime(date("Y-m-d", $first_order_timestamp));
535
- $date2 = new DateTime(date("Y-m-d"));
536
- $interval = $date1->diff($date2);
537
- $years = intval($interval->format('%y'));
538
- $months = $years*12 + intval($interval->format('%m'));
539
- */
540
-
541
- if($months > 0)
542
- $mrr = $revenue / $months;
543
- else
544
- $mrr = 0;
545
-
546
- //save in cache
547
- if(!empty($cache) && !empty($cache[$period]))
548
- $cache[$period][$levels] = $mrr;
549
- elseif(!empty($cache))
550
- $cache[$period] = array($levels => $mrr);
551
- else
552
- $cache = array($period => array($levels => $mrr));
553
-
554
- set_transient("pmpro_report_mrr", $cache, 3600*24);
555
-
556
- return $mrr;
557
- }
558
-
559
- //get Cancellation Rate
560
- function pmpro_getCancellationRate($period, $levels = 'all')
561
- {
562
- //check for a transient
563
- $cache = get_transient("pmpro_report_cancellation_rate");
564
- if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
565
- return $cache[$period][$levels];
566
-
567
- $signups = pmpro_getSignups($period, $levels);
568
- $cancellations = pmpro_getCancellations($period, $levels);
569
-
570
- if(empty($signups))
571
- return false;
572
-
573
- $rate = number_format(($cancellations / $signups)*100, 2);
574
-
575
- //save in cache
576
- if(!empty($cache) && !empty($cache[$period]))
577
- $cache[$period][$levels] = $rate;
578
- elseif(!empty($cache))
579
- $cache[$period] = array($levels => $rate);
580
- else
581
- $cache = array($period => array($levels => $rate));
582
-
583
- set_transient("pmpro_report_cancellation_rate", $cache, 3600*24);
584
-
585
- return $rate;
586
- }
587
-
588
- //get LTV
589
- function pmpro_getLTV($period, $levels = 'all', $mrr = NULL, $signups = NULL, $cancellation_rate = NULL)
590
- {
591
- if(empty($mrr))
592
- $mrr = pmpro_getMRR($period, $levels);
593
- if(empty($signups))
594
- $signups = pmpro_getSignups($period, $levels);
595
- if(empty($cancellation_rate))
596
- $cancellation_rate = pmpro_getCancellationRate($period, $levels);
597
-
598
- //average monthly spend
599
- if(empty($signups))
600
- return false;
601
-
602
- if($signups > 0)
603
- $ams = $mrr / $signups;
604
- else
605
- $ams = 0;
606
-
607
- if($cancellation_rate > 0)
608
- $ltv = $ams * (1/$cancellation_rate);
609
- else
610
- $ltv = $ams;
611
-
612
- return $ltv;
613
- }
614
-
615
- //delete transients when an order goes through
616
- function pmpro_report_memberships_delete_transients()
617
- {
618
- delete_transient("pmpro_report_mrr");
619
- delete_transient("pmpro_report_cancellation_rate");
620
- delete_transient("pmpro_report_memberships_cancellations");
621
- delete_transient("pmpro_report_memberships_signups");
622
- }
623
- add_action("pmpro_after_checkout", "pmpro_report_memberships_delete_transients");
624
- add_action("pmpro_updated_order", "pmpro_report_memberships_delete_transients");
1
+ <?php
2
+ /*
3
+ PMPro Report
4
+ Title: Membership Stats
5
+ Slug: memberships
6
+
7
+ For each report, add a line like:
8
+ global $pmpro_reports;
9
+ $pmpro_reports['slug'] = 'Title';
10
+
11
+ For each report, also write two functions:
12
+ * pmpro_report_{slug}_widget() to show up on the report homepage.
13
+ * pmpro_report_{slug}_page() to show up when users click on the report page widget.
14
+ */
15
+
16
+ global $pmpro_reports;
17
+
18
+ $pmpro_reports['memberships'] = __('Membership Stats', 'pmpro');
19
+
20
+ //queue Google Visualization JS on report page
21
+ function pmpro_report_memberships_init() {
22
+ if(is_admin() && isset($_REQUEST['report']) && $_REQUEST['report'] == "memberships" && isset($_REQUEST['page']) && $_REQUEST['page'] == "pmpro-reports")
23
+ wp_enqueue_script("jsapi", "https://www.google.com/jsapi");
24
+ }
25
+ add_action( 'init', 'pmpro_report_memberships_init' );
26
+
27
+
28
+ //widget
29
+ function pmpro_report_memberships_widget() {
30
+ global $wpdb, $pmpro_currency_symbol;
31
+ ?>
32
+ <style type="text/css">
33
+ #pmpro_report_memberships .section-label {
34
+ margin: 15px 0;
35
+ font-size: 18px;
36
+ text-align: left;
37
+ display: block;
38
+ }
39
+
40
+ #pmpro_report_memberships .section-label:first-child {
41
+ margin-top: 0;
42
+ }
43
+
44
+ #pmpro_report_memberships div {text-align: center;}
45
+ #pmpro_report_memberships em {display: block; font-style: normal; font-size: 2em; margin: 5px; line-height: 26px;}
46
+ </style>
47
+ <span id="pmpro_report_memberships">
48
+ <label class="section-label"><?php _e('Signups', 'pmpro');?>:</label>
49
+ <div style="width: 25%; float: left;">
50
+ <label><?php _e('All Time', 'pmpro');?></label>
51
+ <em><?php echo pmpro_getSignups( 'all time' ); ?></em>
52
+ </div>
53
+ <div style="width: 25%; float: left;">
54
+ <label><?php _e('This Year', 'pmpro');?></label>
55
+ <em><?php echo pmpro_getSignups( 'this year' ); ?></em>
56
+ </div>
57
+ <div style="width: 25%; float: left;">
58
+ <label><?php _e('This Month', 'pmpro');?></label>
59
+ <em><?php echo pmpro_getSignups( 'this month' ); ?></em>
60
+ </div>
61
+ <div style="width: 25%; float: left;">
62
+ <label><?php _e('Today', 'pmpro');?></label>
63
+ <em><?php echo pmpro_getSignups( 'today' ); ?></em>
64
+ </div>
65
+ <div class="clear"></div>
66
+
67
+ <label class="section-label"><?php _e('Cancellations', 'pmpro');?>:</label>
68
+ <div style="width: 25%; float: left;">
69
+ <label><?php _e('All Time', 'pmpro');?></label>
70
+ <em><?php echo pmpro_getCancellations( 'all time' ); ?></em>
71
+ </div>
72
+ <div style="width: 25%; float: left;">
73
+ <label><?php _e('This Year', 'pmpro');?></label>
74
+ <em><?php echo pmpro_getCancellations( 'this year' ); ?></em>
75
+ </div>
76
+ <div style="width: 25%; float: left;">
77
+ <label><?php _e('This Month', 'pmpro');?></label>
78
+ <em><?php echo pmpro_getCancellations( 'this month' ); ?></em>
79
+ </div>
80
+ <div style="width: 25%; float: left;">
81
+ <label><?php _e('Today', 'pmpro');?></label>
82
+ <em><?php echo pmpro_getCancellations( 'today' ); ?></em>
83
+ </div>
84
+ <div class="clear"></div>
85
+
86
+ <label class="section-label"><?php _e('Other Stats', 'pmpro');?>:</label>
87
+ <div style="width: 33%; float: left;">
88
+ <label><?php _e('Monthly Recurring Revenue (MRR)', 'pmpro');?></label>
89
+ <em><?php echo pmpro_formatPrice(pmpro_getMRR( 'all time' )); ?></em>
90
+ </div>
91
+ <div style="width: 33%; float: left;">
92
+ <label><?php _e('Cancellation Rate', 'pmpro');?></label>
93
+ <em><?php echo pmpro_getCancellationRate('all time' ); ?>%</em>
94
+ </div>
95
+ <div style="width: 33%; float: left;">
96
+ <label><?php _e('Lifetime Value (LTV)', 'pmpro');?></label>
97
+ <em><?php echo pmpro_formatPrice(pmpro_getLTV('all time')); ?></em>
98
+ </div>
99
+ <div class="clear"></div>
100
+ </span>
101
+ <?php
102
+ }
103
+
104
+ function pmpro_report_memberships_page()
105
+ {
106
+ global $wpdb, $pmpro_currency_symbol;
107
+
108
+ //get values from form
109
+ if(isset($_REQUEST['type']))
110
+ $type = sanitize_text_field($_REQUEST['type']);
111
+ else
112
+ $type = "signup_v_cancel";
113
+
114
+ if(isset($_REQUEST['period']))
115
+ $period = sanitize_text_field($_REQUEST['period']);
116
+ else
117
+ $period = "monthly";
118
+
119
+ if(isset($_REQUEST['month']))
120
+ $month = intval($_REQUEST['month']);
121
+ else
122
+ $month = date("n");
123
+
124
+ $thisyear = date("Y");
125
+ if(isset($_REQUEST['year']))
126
+ $year = intval($_REQUEST['year']);
127
+ else
128
+ $year = date("Y");
129
+
130
+ if(isset($_REQUEST['level']))
131
+ $l = intval($_REQUEST['level']);
132
+ else
133
+ $l = "";
134
+
135
+ //calculate start date and how to group dates returned from DB
136
+ if($period == "daily")
137
+ {
138
+ $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
139
+ $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
140
+ $date_function = 'DAY';
141
+ }
142
+ elseif($period == "monthly")
143
+ {
144
+ $startdate = $year . '-01-01';
145
+ $enddate = strval(intval($year)+1) . '-01-01';
146
+ $date_function = 'MONTH';
147
+ }
148
+ elseif($period == "annual")
149
+ {
150
+ $startdate = '1960-01-01'; //all time
151
+ $date_function = 'YEAR';
152
+ }
153
+
154
+ //testing or live data
155
+ $gateway_environment = pmpro_getOption("gateway_environment");
156
+
157
+ //get data
158
+ if ( $type === "signup_v_cancel" ) {
159
+ $sqlQuery = "SELECT $date_function(startdate) as date, COUNT(DISTINCT user_id) as signups
160
+ FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . $startdate . "' ";
161
+
162
+ if(!empty($enddate))
163
+ $sqlQuery .= "AND startdate < '" . $enddate . "' ";
164
+ }
165
+ if ( $type === "mrr_ltv" ) {
166
+ // Get total revenue, number of months in system, and date
167
+ if ( $period == 'annual' )
168
+ $sqlQuery = "SELECT SUM(total) as total, COUNT(DISTINCT MONTH(timestamp)) as months, $date_function(timestamp) as date
169
+ FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
170
+ AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
171
+
172
+ if ( $period == 'monthly' )
173
+ $sqlQuery = "SELECT SUM(total) as total, $date_function(timestamp) as date
174
+ FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
175
+ AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
176
+
177
+ if(!empty($enddate))
178
+ $sqlQuery .= "AND timestamp < '" . $enddate . "' ";
179
+ }
180
+
181
+ if(!empty($l))
182
+ $sqlQuery .= "AND membership_id IN(" . $l . ") ";
183
+
184
+ $sqlQuery .= " GROUP BY date ORDER BY date ";
185
+
186
+ $dates = $wpdb->get_results($sqlQuery);
187
+
188
+ //fill in blanks in dates
189
+ $cols = array();
190
+ if($period == "daily")
191
+ {
192
+ $lastday = date("t", strtotime($startdate, current_time("timestamp")));
193
+
194
+ for($i = 1; $i <= $lastday; $i++)
195
+ {
196
+ // Signups vs. Cancellations
197
+ if ( $type === "signup_v_cancel" ) {
198
+ $cols[$i] = new stdClass();
199
+ $cols[$i]->signups = 0;
200
+ foreach($dates as $date)
201
+ {
202
+ if( $date->date == $i ) {
203
+ $cols[$i]->signups = $date->signups;
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ elseif($period == "monthly")
210
+ {
211
+ for($i = 1; $i < 13; $i++)
212
+ {
213
+ // Signups vs. Cancellations
214
+ if ( $type === "signup_v_cancel" ) {
215
+ $cols[$i] = new stdClass();
216
+ $cols[$i]->date = $i;
217
+ $cols[$i]->signups = 0;
218
+ foreach($dates as $date)
219
+ {
220
+ if( $date->date == $i ) {
221
+ $cols[$i]->date = $date->date;
222
+ $cols[$i]->signups = $date->signups;
223
+ }
224
+ }
225
+ }
226
+
227
+ // MRR & LTV
228
+ if ( $type === "mrr_ltv" ) {
229
+ $cols[$i] = new stdClass();
230
+ $cols[$i]->date = $i;
231
+ $cols[$i]->months = 1;
232
+ foreach($dates as $date)
233
+ {
234
+ if( $date->date == $i ) {
235
+ $cols[$i]->total = $date->total;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+ elseif($period == "annual") //annual
242
+ {
243
+ }
244
+
245
+ $dates = ( ! empty( $cols ) ) ? $cols : $dates;
246
+
247
+ // Signups vs. cancellations
248
+ if ( $type === "signup_v_cancel" )
249
+ {
250
+ $sqlQuery = "SELECT $date_function(mu1.modified) as date, COUNT(DISTINCT mu1.user_id) as cancellations
251
+ FROM $wpdb->pmpro_memberships_users mu1
252
+ LEFT JOIN $wpdb->pmpro_memberships_users mu2 ON mu1.user_id = mu2.user_id AND
253
+ mu2.modified > mu1.enddate AND
254
+ DATE_ADD(mu1.modified, INTERVAL 1 DAY) > mu2.startdate
255
+ WHERE mu1.status = 'inactive'
256
+ AND mu2.id IS NULL
257
+ AND mu1.startdate >= '" . $startdate . "'
258
+ AND mu1.startdate < '" . $enddate . "' ";
259
+
260
+ //restrict by level
261
+ if(!empty($l))
262
+ $sqlQuery .= "AND membership_id IN(" . $l . ") ";
263
+
264
+ $sqlQuery .= " GROUP BY date ORDER BY date ";
265
+
266
+ $cdates = $wpdb->get_results($sqlQuery, OBJECT_K);
267
+
268
+ foreach( $dates as &$date )
269
+ {
270
+ if(!empty($cdates[$date->date]))
271
+ $date->cancellations = $cdates[$date->date]->cancellations;
272
+ else
273
+ $date->cancellations = 0;
274
+ }
275
+ }
276
+
277
+ // MRR & LTV
278
+ if ( $type === "mrr_ltv" && count( $dates ) === 1 ) {
279
+ $dummy_date = new stdClass();
280
+ $dummy_date->total = 0;
281
+ $dummy_date->months = 0;
282
+ $dummy_date->date = $dates[0]->date - 1;
283
+ array_unshift( $dates, $dummy_date ); // Add to beginning
284
+ }
285
+ ?>
286
+ <form id="posts-filter" method="get" action="">
287
+ <h2>
288
+ <?php _e('Membership Stats', 'pmpro');?>
289
+ </h2>
290
+ <ul class="subsubsub">
291
+ <li>
292
+ <?php _ex('Show', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?>
293
+ <select id="period" name="period">
294
+ <option value="daily" <?php selected($period, "daily");?>><?php _e('Daily', 'pmpro');?></option>
295
+ <option value="monthly" <?php selected($period, "monthly");?>><?php _e('Monthly', 'pmpro');?></option>
296
+ <option value="annual" <?php selected($period, "annual");?>><?php _e('Annual', 'pmpro');?></option>
297
+ </select>
298
+ <select id="type" name="type">
299
+ <option value="signup_v_cancel" <?php selected($type, "signup_v_cancel");?>><?php _e('Signups vs. Cancellations', 'pmpro');?></option>
300
+ <?php /*
301
+ <option value="mrr_ltv" <?php selected($type, "mrr_ltv");?>><?php _e('MRR & LTV', 'pmpro');?></option>
302
+ */ ?>
303
+ </select>
304
+ <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
305
+ <select id="month" name="month">
306
+ <?php for($i = 1; $i < 13; $i++) { ?>
307
+ <option value="<?php echo $i;?>" <?php selected($month, $i);?>><?php echo date("F", mktime(0, 0, 0, $i, 2));?></option>
308
+ <?php } ?>
309
+ </select>
310
+ <select id="year" name="year">
311
+ <?php for($i = $thisyear; $i > 2007; $i--) { ?>
312
+ <option value="<?php echo $i;?>" <?php selected($year, $i);?>><?php echo $i;?></option>
313
+ <?php } ?>
314
+ </select>
315
+ <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
316
+ <select name="level">
317
+ <option value="" <?php if(!$l) { ?>selected="selected"<?php } ?>><?php _e('All Levels', 'pmpro');?></option>
318
+ <?php
319
+ $levels = $wpdb->get_results("SELECT id, name FROM $wpdb->pmpro_membership_levels ORDER BY name");
320
+ foreach($levels as $level)
321
+ {
322
+ ?>
323
+ <option value="<?php echo $level->id?>" <?php if($l == $level->id) { ?>selected="selected"<?php } ?>><?php echo $level->name?></option>
324
+ <?php
325
+ }
326
+ ?>
327
+ </select>
328
+
329
+ <input type="hidden" name="page" value="pmpro-reports" />
330
+ <input type="hidden" name="report" value="memberships" />
331
+ <input type="submit" value="<?php _ex('Generate Report', 'Submit button value.', 'pmpro');?>" />
332
+ </li>
333
+ </ul>
334
+
335
+ <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
336
+
337
+ <script>
338
+ //update month/year when period dropdown is changed
339
+ jQuery(document).ready(function() {
340
+ jQuery('#period').change(function() {
341
+ pmpro_ShowMonthOrYear();
342
+ });
343
+ });
344
+
345
+ function pmpro_ShowMonthOrYear()
346
+ {
347
+ var period = jQuery('#period').val();
348
+ if(period == 'daily')
349
+ {
350
+ jQuery('#for').show();
351
+ jQuery('#month').show();
352
+ jQuery('#year').show();
353
+ }
354
+ else if(period == 'monthly')
355
+ {
356
+ jQuery('#for').show();
357
+ jQuery('#month').hide();
358
+ jQuery('#year').show();
359
+ }
360
+ else
361
+ {
362
+ jQuery('#for').hide();
363
+ jQuery('#month').hide();
364
+ jQuery('#year').hide();
365
+ }
366
+ }
367
+
368
+ pmpro_ShowMonthOrYear();
369
+
370
+ //draw the chart
371
+ google.load("visualization", "1", {packages:["corechart"]});
372
+ google.setOnLoadCallback(drawChart);
373
+ function drawChart() {
374
+
375
+ var data = google.visualization.arrayToDataTable([
376
+ <?php if ( $type === "signup_v_cancel" ) : // Signups vs. cancellations ?>
377
+ ['<?php echo $date_function;?>', 'Signups', 'Cancellations'],
378
+ <?php foreach($dates as $key => $value) { ?>
379
+ ['<?php if($period == "monthly") echo date("M", mktime(0,0,0,$value->date,2)); else if($period == "daily") echo $key; else echo $value->date;?>', <?php echo $value->signups; ?>, <?php echo $value->cancellations; ?>],
380
+ <?php } ?>
381
+ <?php endif; ?>
382
+
383
+ <?php if ( $type === "mrr_ltv" ) : // Signups vs. cancellations ?>
384
+ ['<?php echo $date_function;?>', 'MRR', 'LTV'],
385
+ <?php foreach($dates as $key => $value) { ?>
386
+ ['<?php if($period == "monthly") echo date("M", mktime(0,0,0,$value->date,2)); else if($period == "daily") echo $key; else echo $value->date;?>', <?php echo (($mrr = $value->total / $value->months) && $mrr != 0) ? $mrr : 0; ?>, <?php echo pmpro_getLTV($period, NULL, $mrr ); ?>],
387
+ <?php } ?>
388
+ <?php endif; ?>
389
+ ]);
390
+
391
+ var options = {
392
+ colors: ['#0099c6', '#dc3912'],
393
+ hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
394
+ vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
395
+ };
396
+
397
+ <?php if ( $type === "signup_v_cancel" ) : // Signups vs. cancellations ?>
398
+ var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
399
+ <?php elseif ( $type === "mrr_ltv" ) : // MRR & LTV ?>
400
+
401
+ <?php
402
+ //prefix or suffix?
403
+ if(pmpro_getCurrencyPosition() == "right")
404
+ $position = "suffix";
405
+ else
406
+ $position = "prefix";
407
+ ?>
408
+
409
+ var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
410
+ formatter.format(data, 2);
411
+ var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
412
+ formatter.format(data, 1);
413
+
414
+ var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
415
+ <?php endif; ?>
416
+ chart.draw(data, options);
417
+ }
418
+ </script>
419
+
420
+ </form>
421
+ <?php
422
+ }
423
+
424
+
425
+
426
+ /*
427
+ Other code required for your reports. This file is loaded every time WP loads with PMPro enabled.
428
+ */
429
+
430
+ //get signups
431
+ function pmpro_getSignups($period = false, $levels = 'all')
432
+ {
433
+ //check for a transient
434
+ $cache = get_transient( 'pmpro_report_memberships_signups' );
435
+ if( ! empty( $cache ) && ! empty( $cache[$period] ) && ! empty( $cache[$period][$levels] ) )
436
+ return $cache[$period][$levels];
437
+
438
+ //a sale is an order with status = success
439
+ if( $period == 'today' )
440
+ $startdate = date(' Y-m-d' );
441
+ elseif( $period == 'this month')
442
+ $startdate = date( 'Y-m' ) . '-01';
443
+ elseif( $period == 'this year')
444
+ $startdate = date( 'Y' ) . '-01-01';
445
+ else
446
+ $startdate = '';
447
+
448
+
449
+ //build query
450
+ global $wpdb;
451
+
452
+ $sqlQuery = "SELECT COUNT(DISTINCT user_id) FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . $startdate . "' ";
453
+
454
+ //restrict by level
455
+ if(!empty($levels) && $levels != 'all')
456
+ $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
457
+
458
+ $signups = $wpdb->get_var($sqlQuery);
459
+
460
+ //save in cache
461
+ if(!empty($cache) && !empty($cache[$period]))
462
+ $cache[$period][$levels] = $signups;
463
+ elseif(!empty($cache))
464
+ $cache[$period] = array($levels => $signups);
465
+ else
466
+ $cache = array($period => array($levels => $signups));
467
+
468
+ set_transient("pmpro_report_memberships_signups", $cache, 3600*24);
469
+
470
+ return $signups;
471
+ }
472
+
473
+ //get cancellations
474
+ function pmpro_getCancellations($period = false, $levels = 'all')
475
+ {
476
+ //check for a transient
477
+ $cache = get_transient( 'pmpro_report_memberships_cancellations' );
478
+ if( ! empty( $cache ) && ! empty( $cache[$period] ) && ! empty( $cache[$period][$levels] ) )
479
+ return $cache[$period][$levels];
480
+
481
+ //figure out start date
482
+ if( $period == 'today' )
483
+ $startdate = date(' Y-m-d' );
484
+ elseif( $period == 'this month')
485
+ $startdate = date( 'Y-m' ) . '-01';
486
+ elseif( $period == 'this year')
487
+ $startdate = date( 'Y' ) . '-01-01';
488
+ else
489
+ $startdate = '';
490
+
491
+ $startdate_plus_one = strtotime( $startdate . + ' + 1 day', current_time("timestamp") );
492
+
493
+ /*
494
+ build query.
495
+ cancellations are marked in the memberships users table with status = 'inactive'
496
+ we try to ignore cancellations when the user gets a new level with 24 hours (probably an upgrade or downgrade)
497
+ */
498
+ global $wpdb;
499
+
500
+ //$sqlQuery = "SELECT mu1.user_id, mu2.user_id FROM $wpdb->pmpro_memberships_users mu1 LEFT JOIN $wpdb->pmpro_memberships_users mu2 ON mu1.user_id = mu2.user_id AND mu2.status = 'inactive' AND mu2.startdate > mu1.startdate";
501
+ $sqlQuery = "SELECT COUNT(mu1.id)
502
+ FROM $wpdb->pmpro_memberships_users mu1
503
+ LEFT JOIN $wpdb->pmpro_memberships_users mu2 ON mu1.user_id = mu2.user_id AND
504
+ mu2.modified > mu1.enddate AND
505
+ DATE_ADD(mu1.modified, INTERVAL 1 DAY) > mu2.startdate
506
+ WHERE mu1.status = 'inactive'
507
+ AND mu2.id IS NULL
508
+ AND mu1.startdate >= '" . $startdate . "' ";
509
+
510
+ //restrict by level
511
+ if(!empty($levels) && $levels != 'all')
512
+ $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
513
+
514
+ $cancellations = $wpdb->get_var($sqlQuery);
515
+
516
+ //save in cache
517
+ if(!empty($cache) && !empty($cache[$period]) && is_array($cache[$period]))
518
+ $cache[$period][$levels] = $cancellations;
519
+ elseif(!empty($cache))
520
+ $cache[$period] = array($levels => $cancellations);
521
+ else
522
+ $cache = array($period => array($levels => $cancellations));
523
+
524
+ set_transient("pmpro_report_memberships_cancellations", $cache, 3600*24);
525
+
526
+ return $cancellations;
527
+ }
528
+
529
+ //get MRR
530
+ function pmpro_getMRR($period, $levels = 'all')
531
+ {
532
+ //check for a transient
533
+ //$cache = get_transient("pmpro_report_mrr");
534
+ if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
535
+ return $cache[$period][$levels];
536
+
537
+ //a sale is an order with status NOT IN refunded, review, token, error
538
+ if($period == "this month")
539
+ $startdate = date("Y-m") . "-01";
540
+ elseif($period == "this year")
541
+ $startdate = date("Y") . "-01-01";
542
+ else
543
+ $startdate = "";
544
+
545
+ $gateway_environment = pmpro_getOption("gateway_environment");
546
+
547
+ //build query
548
+ global $wpdb;
549
+ // Get total revenue
550
+ $sqlQuery = "SELECT SUM(total) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
551
+
552
+ //restrict by level
553
+ if(!empty($levels) && $levels != 'all') {
554
+ $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
555
+ }
556
+
557
+ $revenue = $wpdb->get_var($sqlQuery);
558
+
559
+ //when was the first order
560
+ $first_order_timestamp = $wpdb->get_var("SELECT UNIX_TIMESTAMP(`timestamp`) FROM $wpdb->pmpro_membership_orders WHERE `timestamp` IS NOT NULL AND `timestamp` > '0000-00-00 00:00:00' ORDER BY `timestamp` LIMIT 1");
561
+
562
+ //if we don't have a timestamp, we can't do this
563
+ if(empty($first_order_timestamp))
564
+ return false;
565
+
566
+ //how many months ago was the first order
567
+ $months = $wpdb->get_var("SELECT PERIOD_DIFF('" . date("Ym") . "', '" . date("Ym", $first_order_timestamp) . "')");
568
+
569
+ /* this works in PHP 5.3+ without using MySQL to get the diff
570
+ $date1 = new DateTime(date("Y-m-d", $first_order_timestamp));
571
+ $date2 = new DateTime(date("Y-m-d"));
572
+ $interval = $date1->diff($date2);
573
+ $years = intval($interval->format('%y'));
574
+ $months = $years*12 + intval($interval->format('%m'));
575
+ */
576
+
577
+ if($months > 0)
578
+ $mrr = $revenue / $months;
579
+ else
580
+ $mrr = 0;
581
+
582
+ //save in cache
583
+ if(!empty($cache) && !empty($cache[$period]))
584
+ $cache[$period][$levels] = $mrr;
585
+ elseif(!empty($cache))
586
+ $cache[$period] = array($levels => $mrr);
587
+ else
588
+ $cache = array($period => array($levels => $mrr));
589
+
590
+ set_transient("pmpro_report_mrr", $cache, 3600*24);
591
+
592
+ return $mrr;
593
+ }
594
+
595
+ //get Cancellation Rate
596
+ function pmpro_getCancellationRate($period, $levels = 'all')
597
+ {
598
+ //check for a transient
599
+ $cache = get_transient("pmpro_report_cancellation_rate");
600
+ if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
601
+ return $cache[$period][$levels];
602
+
603
+ $signups = pmpro_getSignups($period, $levels);
604
+ $cancellations = pmpro_getCancellations($period, $levels);
605
+
606
+ if(empty($signups))
607
+ return false;
608
+
609
+ $rate = number_format(($cancellations / $signups)*100, 2);
610
+
611
+ //save in cache
612
+ if(!empty($cache) && !empty($cache[$period]))
613
+ $cache[$period][$levels] = $rate;
614
+ elseif(!empty($cache))
615
+ $cache[$period] = array($levels => $rate);
616
+ else
617
+ $cache = array($period => array($levels => $rate));
618
+
619
+ set_transient("pmpro_report_cancellation_rate", $cache, 3600*24);
620
+
621
+ return $rate;
622
+ }
623
+
624
+ //get LTV
625
+ function pmpro_getLTV($period, $levels = 'all', $mrr = NULL, $signups = NULL, $cancellation_rate = NULL)
626
+ {
627
+ if(empty($mrr))
628
+ $mrr = pmpro_getMRR($period, $levels);
629
+ if(empty($signups))
630
+ $signups = pmpro_getSignups($period, $levels);
631
+ if(empty($cancellation_rate))
632
+ $cancellation_rate = pmpro_getCancellationRate($period, $levels);
633
+
634
+ //average monthly spend
635
+ if(empty($signups))
636
+ return false;
637
+
638
+ if($signups > 0)
639
+ $ams = $mrr / $signups;
640
+ else
641
+ $ams = 0;
642
+
643
+ if($cancellation_rate > 0)
644
+ $ltv = $ams * (1/$cancellation_rate);
645
+ else
646
+ $ltv = $ams;
647
+
648
+ return $ltv;
649
+ }
650
+
651
+ //delete transients when an order goes through
652
+ function pmpro_report_memberships_delete_transients()
653
+ {
654
+ delete_transient("pmpro_report_mrr");
655
+ delete_transient("pmpro_report_cancellation_rate");
656
+ delete_transient("pmpro_report_memberships_cancellations");
657
+ delete_transient("pmpro_report_memberships_signups");
658
+ }
659
+ add_action("pmpro_after_checkout", "pmpro_report_memberships_delete_transients");
660
+ add_action("pmpro_updated_order", "pmpro_report_memberships_delete_transients");
adminpages/reports/sales.php CHANGED
@@ -1,415 +1,406 @@
1
- <?php
2
- /*
3
- PMPro Report
4
- Title: Sales
5
- Slug: sales
6
-
7
- For each report, add a line like:
8
- global $pmpro_reports;
9
- $pmpro_reports['slug'] = 'Title';
10
-
11
- For each report, also write two functions:
12
- * pmpro_report_{slug}_widget() to show up on the report homepage.
13
- * pmpro_report_{slug}_page() to show up when users click on the report page widget.
14
- */
15
- global $pmpro_reports;
16
- $gateway_environment = pmpro_getOption("gateway_environment");
17
- if($gateway_environment == "sandbox")
18
- $pmpro_reports['sales'] = __('Sales and Revenue (Testing/Sandbox)', 'pmpro');
19
- else
20
- $pmpro_reports['sales'] = __('Sales and Revenue', 'pmpro');
21
-
22
- //queue Google Visualization JS on report page
23
- function pmpro_report_sales_init()
24
- {
25
- if(is_admin() && isset($_REQUEST['report']) && $_REQUEST['report'] == "sales" && isset($_REQUEST['page']) && $_REQUEST['page'] == "pmpro-reports")
26
- {
27
- wp_enqueue_script("jsapi", "https://www.google.com/jsapi");
28
- }
29
- }
30
- add_action("init", "pmpro_report_sales_init");
31
-
32
- //widget
33
- function pmpro_report_sales_widget()
34
- {
35
- global $wpdb;
36
- ?>
37
- <style>
38
- #pmpro_report_sales tbody td:last-child {text-align: right; }
39
- </style>
40
- <span id="pmpro_report_sales">
41
- <table class="wp-list-table widefat fixed striped">
42
- <thead>
43
- <tr>
44
- <th scope="col">&nbsp;</th>
45
- <th scope="col"><?php _e('Sales','pmpro'); ?></th>
46
- <th scope="col"><?php _e('Revenue','pmpro'); ?></th>
47
- </tr>
48
- </thead>
49
- <tbody>
50
- <tr>
51
- <th scope="row"><?php _e('Today','pmpro'); ?></th>
52
- <td><?php echo number_format_i18n(pmpro_getSales("today")); ?></td>
53
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("today"));?></td>
54
- </tr>
55
- <tr>
56
- <th scope="row"><?php _e('This Month','pmpro'); ?></th>
57
- <td><?php echo number_format_i18n(pmpro_getSales("this month")); ?></td>
58
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("this month"));?></td>
59
- </tr>
60
- <tr>
61
- <th scope="row"><?php _e('This Year','pmpro'); ?></th>
62
- <td><?php echo number_format_i18n(pmpro_getSales("this year")); ?></td>
63
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("this year"));?></td>
64
- </tr>
65
- <tr>
66
- <th scope="row"><?php _e('All Time','pmpro'); ?></th>
67
- <td><?php echo number_format_i18n(pmpro_getSales("all time")); ?></td>
68
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("all time"));?></td>
69
- </tr>
70
- </tbody>
71
- </table>
72
- </span>
73
- <?php
74
- }
75
-
76
- function pmpro_report_sales_page()
77
- {
78
- global $wpdb, $pmpro_currency_symbol, $pmpro_currency, $pmpro_currencies;
79
-
80
- //get values from form
81
- if(isset($_REQUEST['type']))
82
- $type = sanitize_text_field($_REQUEST['type']);
83
- else
84
- $type = "revenue";
85
-
86
- if($type == "sales")
87
- $type_function = "COUNT";
88
- else
89
- $type_function = "SUM";
90
-
91
- if(isset($_REQUEST['period']))
92
- $period = sanitize_text_field($_REQUEST['period']);
93
- else
94
- $period = "daily";
95
-
96
- if(isset($_REQUEST['month']))
97
- $month = intval($_REQUEST['month']);
98
- else
99
- $month = date("n", current_time('timestamp'));
100
-
101
- $thisyear = date("Y", current_time('timestamp'));
102
- if(isset($_REQUEST['year']))
103
- $year = intval($_REQUEST['year']);
104
- else
105
- $year = $thisyear;
106
-
107
- if(isset($_REQUEST['level']))
108
- $l = intval($_REQUEST['level']);
109
- else
110
- $l = "";
111
-
112
- //calculate start date and how to group dates returned from DB
113
- if($period == "daily")
114
- {
115
- $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
116
- $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
117
- $date_function = 'DAY';
118
- }
119
- elseif($period == "monthly")
120
- {
121
- $startdate = $year . '-01-01';
122
- $enddate = strval(intval($year)+1) . '-01-01';
123
- $date_function = 'MONTH';
124
- }
125
- else
126
- {
127
- $startdate = '1960-01-01'; //all time
128
- $date_function = 'YEAR';
129
- }
130
-
131
- //testing or live data
132
- $gateway_environment = pmpro_getOption("gateway_environment");
133
-
134
- //get data
135
- $sqlQuery = "SELECT $date_function(timestamp) as date, $type_function(total) as value FROM $wpdb->pmpro_membership_orders WHERE timestamp >= '" . $startdate . "' AND status NOT IN('refunded', 'review', 'token', 'error') AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
136
-
137
- if(!empty($enddate))
138
- $sqlQuery .= "AND timestamp < '" . $enddate . "' ";
139
-
140
- if(!empty($l))
141
- $sqlQuery .= "AND membership_id IN(" . $l . ") ";
142
-
143
- $sqlQuery .= " GROUP BY date ORDER BY date ";
144
-
145
- $dates = $wpdb->get_results($sqlQuery);
146
-
147
- //fill in blanks in dates
148
- $cols = array();
149
- if($period == "daily")
150
- {
151
- $lastday = date("t", strtotime($startdate, current_time("timestamp")));
152
-
153
- for($i = 1; $i <= $lastday; $i++)
154
- {
155
- $cols[$i] = 0;
156
- foreach($dates as $date)
157
- {
158
- if($date->date == $i)
159
- $cols[$i] = $date->value;
160
- }
161
- }
162
- }
163
- elseif($period == "monthly")
164
- {
165
- for($i = 1; $i < 13; $i++)
166
- {
167
- $cols[$i] = 0;
168
- foreach($dates as $date)
169
- {
170
- if($date->date == $i)
171
- $cols[$i] = $date->value;
172
- }
173
- }
174
- }
175
- else //annual
176
- {
177
- //get min and max years
178
- $min = 9999;
179
- $max = 0;
180
- foreach($dates as $date)
181
- {
182
- $min = min($min, $date->date);
183
- $max = max($max, $date->date);
184
- }
185
-
186
- for($i = $min; $i <= $max; $i++)
187
- {
188
- foreach($dates as $date)
189
- {
190
- if($date->date == $i)
191
- $cols[$i] = $date->value;
192
- }
193
- }
194
- }
195
- ?>
196
- <form id="posts-filter" method="get" action="">
197
- <h2>
198
- <?php _e('Sales and Revenue', 'pmpro');?>
199
- </h2>
200
-
201
- <div class="tablenav top">
202
- <?php _ex('Show', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?>
203
- <select id="period" name="period">
204
- <option value="daily" <?php selected($period, "daily");?>><?php _e('Daily', 'pmpro');?></option>
205
- <option value="monthly" <?php selected($period, "monthly");?>><?php _e('Monthly', 'pmpro');?></option>
206
- <option value="annual" <?php selected($period, "annual");?>><?php _e('Annual', 'pmpro');?></option>
207
- </select>
208
- <select name="type">
209
- <option value="revenue" <?php selected($type, "revenue");?>><?php _e('Revenue', 'pmpro');?></option>
210
- <option value="sales" <?php selected($type, "sales");?>><?php _e('Sales', 'pmpro');?></option>
211
- </select>
212
- <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
213
- <select id="month" name="month">
214
- <?php for($i = 1; $i < 13; $i++) { ?>
215
- <option value="<?php echo $i;?>" <?php selected($month, $i);?>><?php echo date("F", mktime(0, 0, 0, $i, 2));?></option>
216
- <?php } ?>
217
- </select>
218
- <select id="year" name="year">
219
- <?php for($i = $thisyear; $i > 2007; $i--) { ?>
220
- <option value="<?php echo $i;?>" <?php selected($year, $i);?>><?php echo $i;?></option>
221
- <?php } ?>
222
- </select>
223
- <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
224
- <select name="level">
225
- <option value="" <?php if(!$l) { ?>selected="selected"<?php } ?>><?php _e('All Levels', 'pmpro');?></option>
226
- <?php
227
- $levels = $wpdb->get_results("SELECT id, name FROM $wpdb->pmpro_membership_levels ORDER BY name");
228
- foreach($levels as $level)
229
- {
230
- ?>
231
- <option value="<?php echo $level->id?>" <?php if($l == $level->id) { ?>selected="selected"<?php } ?>><?php echo $level->name?></option>
232
- <?php
233
- }
234
- ?>
235
- </select>
236
-
237
- <input type="hidden" name="page" value="pmpro-reports" />
238
- <input type="hidden" name="report" value="sales" />
239
- <input type="submit" class="button action" value="<?php _ex('Generate Report', 'Submit button value.', 'pmpro');?>" />
240
- </div>
241
-
242
- <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
243
-
244
- <script>
245
- //update month/year when period dropdown is changed
246
- jQuery(document).ready(function() {
247
- jQuery('#period').change(function() {
248
- pmpro_ShowMonthOrYear();
249
- });
250
- });
251
-
252
- function pmpro_ShowMonthOrYear()
253
- {
254
- var period = jQuery('#period').val();
255
- if(period == 'daily')
256
- {
257
- jQuery('#for').show();
258
- jQuery('#month').show();
259
- jQuery('#year').show();
260
- }
261
- else if(period == 'monthly')
262
- {
263
- jQuery('#for').show();
264
- jQuery('#month').hide();
265
- jQuery('#year').show();
266
- }
267
- else
268
- {
269
- jQuery('#for').hide();
270
- jQuery('#month').hide();
271
- jQuery('#year').hide();
272
- }
273
- }
274
-
275
- pmpro_ShowMonthOrYear();
276
-
277
- //draw the chart
278
- google.load("visualization", "1", {packages:["corechart"]});
279
- google.setOnLoadCallback(drawChart);
280
- function drawChart() {
281
-
282
- var data = google.visualization.arrayToDataTable([
283
- ['<?php echo $date_function;?>', '<?php echo ucwords($type);?>'],
284
- <?php foreach($cols as $date => $value) { ?>
285
- ['<?php if($period == "monthly") echo date("M", mktime(0,0,0,$date,2)); else echo $date;?>', <?php echo $value;?>],
286
- <?php } ?>
287
- ]);
288
-
289
- var options = {
290
- colors: ['#51a351', '#387038'],
291
- hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
292
- vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
293
- };
294
-
295
- <?php
296
- if($type != "sales")
297
- {
298
- if(pmpro_getCurrencyPosition() == "right")
299
- $position = "suffix";
300
- else
301
- $position = "prefix";
302
- ?>
303
- var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
304
- formatter.format(data, 1);
305
- <?php
306
- }
307
- ?>
308
-
309
- var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
310
- chart.draw(data, options);
311
- }
312
- </script>
313
-
314
- </form>
315
- <?php
316
- }
317
-
318
- /*
319
- Other code required for your reports. This file is loaded every time WP loads with PMPro enabled.
320
- */
321
-
322
- //get sales
323
- function pmpro_getSales($period, $levels = NULL)
324
- {
325
- //check for a transient
326
- $cache = get_transient("pmpro_report_sales");
327
- if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
328
- return $cache[$period][$levels];
329
-
330
- //a sale is an order with status NOT IN('refunded', 'review', 'token', 'error')
331
- if($period == "today")
332
- $startdate = date("Y-m-d", current_time('timestamp'));
333
- elseif($period == "this month")
334
- $startdate = date("Y-m", current_time('timestamp')) . "-01";
335
- elseif($period == "this year")
336
- $startdate = date("Y", current_time('timestamp')) . "-01-01";
337
- else
338
- $startdate = "";
339
-
340
- $gateway_environment = pmpro_getOption("gateway_environment");
341
-
342
- //build query
343
- global $wpdb;
344
- $sqlQuery = "SELECT COUNT(*) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
345
-
346
- //restrict by level
347
- if(!empty($levels))
348
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
349
-
350
- $sales = $wpdb->get_var($sqlQuery);
351
-
352
- //save in cache
353
- if(!empty($cache) && !empty($cache[$period]))
354
- $cache[$period][$levels] = $sales;
355
- elseif(!empty($cache))
356
- $cache[$period] = array($levels => $sales);
357
- else
358
- $cache = array($period => array($levels => $sales));
359
-
360
- set_transient("pmpro_report_sales", $cache, 3600*24);
361
-
362
- return $sales;
363
- }
364
-
365
- //get revenue
366
- function pmpro_getRevenue($period, $levels = NULL)
367
- {
368
- //check for a transient
369
- $cache = get_transient("pmpro_report_revenue");
370
- if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
371
- return $cache[$period][$levels];
372
-
373
- //a sale is an order with status NOT IN('refunded', 'review', 'token', 'error')
374
- if($period == "today")
375
- $startdate = date("Y-m-d", current_time('timestamp'));
376
- elseif($period == "this month")
377
- $startdate = date("Y-m", current_time('timestamp')) . "-01";
378
- elseif($period == "this year")
379
- $startdate = date("Y", current_time('timestamp')) . "-01-01";
380
- else
381
- $startdate = "";
382
-
383
- $gateway_environment = pmpro_getOption("gateway_environment");
384
-
385
- //build query
386
- global $wpdb;
387
- $sqlQuery = "SELECT SUM(total) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
388
-
389
- //restrict by level
390
- if(!empty($levels))
391
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
392
-
393
- $revenue = $wpdb->get_var($sqlQuery);
394
-
395
- //save in cache
396
- if(!empty($cache) && !empty($cache[$period]))
397
- $cache[$period][$levels] = $revenue;
398
- elseif(!empty($cache))
399
- $cache[$period] = array($levels => $revenue);
400
- else
401
- $cache = array($period => array($levels => $revenue));
402
-
403
- set_transient("pmpro_report_revenue", $cache, 3600*24);
404
-
405
- return $revenue;
406
- }
407
-
408
- //delete transients when an order goes through
409
- function pmpro_report_sales_delete_transients()
410
- {
411
- delete_transient("pmpro_report_sales");
412
- delete_transient("pmpro_report_revenue");
413
- }
414
- add_action("pmpro_after_checkout", "pmpro_report_sales_delete_transients");
415
- add_action("pmpro_updated_order", "pmpro_report_sales_delete_transients");
1
+ <?php
2
+ /*
3
+ PMPro Report
4
+ Title: Sales
5
+ Slug: sales
6
+
7
+ For each report, add a line like:
8
+ global $pmpro_reports;
9
+ $pmpro_reports['slug'] = 'Title';
10
+
11
+ For each report, also write two functions:
12
+ * pmpro_report_{slug}_widget() to show up on the report homepage.
13
+ * pmpro_report_{slug}_page() to show up when users click on the report page widget.
14
+ */
15
+ global $pmpro_reports;
16
+ $gateway_environment = pmpro_getOption("gateway_environment");
17
+ if($gateway_environment == "sandbox")
18
+ $pmpro_reports['sales'] = __('Sales and Revenue (Testing/Sandbox)', 'pmpro');
19
+ else
20
+ $pmpro_reports['sales'] = __('Sales and Revenue', 'pmpro');
21
+
22
+ //queue Google Visualization JS on report page
23
+ function pmpro_report_sales_init()
24
+ {
25
+ if(is_admin() && isset($_REQUEST['report']) && $_REQUEST['report'] == "sales" && isset($_REQUEST['page']) && $_REQUEST['page'] == "pmpro-reports")
26
+ {
27
+ wp_enqueue_script("jsapi", "https://www.google.com/jsapi");
28
+ }
29
+ }
30
+ add_action("init", "pmpro_report_sales_init");
31
+
32
+ //widget
33
+ function pmpro_report_sales_widget()
34
+ {
35
+ global $wpdb;
36
+ ?>
37
+ <style>
38
+ #pmpro_report_sales div {text-align: center;}
39
+ #pmpro_report_sales em {display: block; font-style: normal; font-size: 2em; margin: 5px;}
40
+ </style>
41
+ <span id="#pmpro_report_sales">
42
+ <div style="width: 25%; float: left;">
43
+ <em><?php echo pmpro_getSales("all time");?></em>
44
+ <label>All Time</label>
45
+ <em><?php echo pmpro_formatPrice(pmpro_getRevenue("all time"));?></em>
46
+ </div>
47
+ <div style="width: 25%; float: left;">
48
+ <em><?php echo pmpro_getSales("this year");?></em>
49
+ <label>This Year</label>
50
+ <em><?php echo pmpro_formatPrice(pmpro_getRevenue("this year"));?></em>
51
+ </div>
52
+ <div style="width: 25%; float: left;">
53
+ <em><?php echo pmpro_getSales("this month");?></em>
54
+ <label>This Month</label>
55
+ <em><?php echo pmpro_formatPrice(pmpro_getRevenue("this month"));?></em>
56
+ </div>
57
+ <div style="width: 25%; float: left;">
58
+ <em><?php echo pmpro_getSales("today");?></em>
59
+ <label>Today</label>
60
+ <em><?php echo pmpro_formatPrice(pmpro_getRevenue("today"));?></em>
61
+ </div>
62
+ <div class="clear"></div>
63
+ </span>
64
+ <?php
65
+ }
66
+
67
+ function pmpro_report_sales_page()
68
+ {
69
+ global $wpdb, $pmpro_currency_symbol, $pmpro_currency, $pmpro_currencies;
70
+
71
+ //get values from form
72
+ if(isset($_REQUEST['type']))
73
+ $type = sanitize_text_field($_REQUEST['type']);
74
+ else
75
+ $type = "revenue";
76
+
77
+ if($type == "sales")
78
+ $type_function = "COUNT";
79
+ else
80
+ $type_function = "SUM";
81
+
82
+ if(isset($_REQUEST['period']))
83
+ $period = sanitize_text_field($_REQUEST['period']);
84
+ else
85
+ $period = "daily";
86
+
87
+ if(isset($_REQUEST['month']))
88
+ $month = intval($_REQUEST['month']);
89
+ else
90
+ $month = date("n", current_time('timestamp'));
91
+
92
+ $thisyear = date("Y", current_time('timestamp'));
93
+ if(isset($_REQUEST['year']))
94
+ $year = intval($_REQUEST['year']);
95
+ else
96
+ $year = $thisyear;
97
+
98
+ if(isset($_REQUEST['level']))
99
+ $l = intval($_REQUEST['level']);
100
+ else
101
+ $l = "";
102
+
103
+ //calculate start date and how to group dates returned from DB
104
+ if($period == "daily")
105
+ {
106
+ $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
107
+ $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
108
+ $date_function = 'DAY';
109
+ }
110
+ elseif($period == "monthly")
111
+ {
112
+ $startdate = $year . '-01-01';
113
+ $enddate = strval(intval($year)+1) . '-01-01';
114
+ $date_function = 'MONTH';
115
+ }
116
+ else
117
+ {
118
+ $startdate = '1960-01-01'; //all time
119
+ $date_function = 'YEAR';
120
+ }
121
+
122
+ //testing or live data
123
+ $gateway_environment = pmpro_getOption("gateway_environment");
124
+
125
+ //get data
126
+ $sqlQuery = "SELECT $date_function(timestamp) as date, $type_function(total) as value FROM $wpdb->pmpro_membership_orders WHERE timestamp >= '" . $startdate . "' AND status NOT IN('refunded', 'review', 'token', 'error') AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
127
+
128
+ if(!empty($enddate))
129
+ $sqlQuery .= "AND timestamp < '" . $enddate . "' ";
130
+
131
+ if(!empty($l))
132
+ $sqlQuery .= "AND membership_id IN(" . $l . ") ";
133
+
134
+ $sqlQuery .= " GROUP BY date ORDER BY date ";
135
+
136
+ $dates = $wpdb->get_results($sqlQuery);
137
+
138
+ //fill in blanks in dates
139
+ $cols = array();
140
+ if($period == "daily")
141
+ {
142
+ $lastday = date("t", strtotime($startdate, current_time("timestamp")));
143
+
144
+ for($i = 1; $i <= $lastday; $i++)
145
+ {
146
+ $cols[$i] = 0;
147
+ foreach($dates as $date)
148
+ {
149
+ if($date->date == $i)
150
+ $cols[$i] = $date->value;
151
+ }
152
+ }
153
+ }
154
+ elseif($period == "monthly")
155
+ {
156
+ for($i = 1; $i < 13; $i++)
157
+ {
158
+ $cols[$i] = 0;
159
+ foreach($dates as $date)
160
+ {
161
+ if($date->date == $i)
162
+ $cols[$i] = $date->value;
163
+ }
164
+ }
165
+ }
166
+ else //annual
167
+ {
168
+ //get min and max years
169
+ $min = 9999;
170
+ $max = 0;
171
+ foreach($dates as $date)
172
+ {
173
+ $min = min($min, $date->date);
174
+ $max = max($max, $date->date);
175
+ }
176
+
177
+ for($i = $min; $i <= $max; $i++)
178
+ {
179
+ foreach($dates as $date)
180
+ {
181
+ if($date->date == $i)
182
+ $cols[$i] = $date->value;
183
+ }
184
+ }
185
+ }
186
+ ?>
187
+ <form id="posts-filter" method="get" action="">
188
+ <h2>
189
+ <?php _e('Sales and Revenue', 'pmpro');?>
190
+ </h2>
191
+
192
+ <div class="tablenav top">
193
+ <?php _ex('Show', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?>
194
+ <select id="period" name="period">
195
+ <option value="daily" <?php selected($period, "daily");?>><?php _e('Daily', 'pmpro');?></option>
196
+ <option value="monthly" <?php selected($period, "monthly");?>><?php _e('Monthly', 'pmpro');?></option>
197
+ <option value="annual" <?php selected($period, "annual");?>><?php _e('Annual', 'pmpro');?></option>
198
+ </select>
199
+ <select name="type">
200
+ <option value="revenue" <?php selected($type, "revenue");?>><?php _e('Revenue', 'pmpro');?></option>
201
+ <option value="sales" <?php selected($type, "sales");?>><?php _e('Sales', 'pmpro');?></option>
202
+ </select>
203
+ <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
204
+ <select id="month" name="month">
205
+ <?php for($i = 1; $i < 13; $i++) { ?>
206
+ <option value="<?php echo $i;?>" <?php selected($month, $i);?>><?php echo date("F", mktime(0, 0, 0, $i, 2));?></option>
207
+ <?php } ?>
208
+ </select>
209
+ <select id="year" name="year">
210
+ <?php for($i = $thisyear; $i > 2007; $i--) { ?>
211
+ <option value="<?php echo $i;?>" <?php selected($year, $i);?>><?php echo $i;?></option>
212
+ <?php } ?>
213
+ </select>
214
+ <span id="for"><?php _ex('for', 'Dropdown label, e.g. Show Daily Revenue for January', 'pmpro')?></span>
215
+ <select name="level">
216
+ <option value="" <?php if(!$l) { ?>selected="selected"<?php } ?>><?php _e('All Levels', 'pmpro');?></option>
217
+ <?php
218
+ $levels = $wpdb->get_results("SELECT id, name FROM $wpdb->pmpro_membership_levels ORDER BY name");
219
+ foreach($levels as $level)
220
+ {
221
+ ?>
222
+ <option value="<?php echo $level->id?>" <?php if($l == $level->id) { ?>selected="selected"<?php } ?>><?php echo $level->name?></option>
223
+ <?php
224
+ }
225
+ ?>
226
+ </select>
227
+
228
+ <input type="hidden" name="page" value="pmpro-reports" />
229
+ <input type="hidden" name="report" value="sales" />
230
+ <input type="submit" class="button action" value="<?php _ex('Generate Report', 'Submit button value.', 'pmpro');?>" />
231
+ </div>
232
+
233
+ <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
234
+
235
+ <script>
236
+ //update month/year when period dropdown is changed
237
+ jQuery(document).ready(function() {
238
+ jQuery('#period').change(function() {
239
+ pmpro_ShowMonthOrYear();
240
+ });
241
+ });
242
+
243
+ function pmpro_ShowMonthOrYear()
244
+ {
245
+ var period = jQuery('#period').val();
246
+ if(period == 'daily')
247
+ {
248
+ jQuery('#for').show();
249
+ jQuery('#month').show();
250
+ jQuery('#year').show();
251
+ }
252
+ else if(period == 'monthly')
253
+ {
254
+ jQuery('#for').show();
255
+ jQuery('#month').hide();
256
+ jQuery('#year').show();
257
+ }
258
+ else
259
+ {
260
+ jQuery('#for').hide();
261
+ jQuery('#month').hide();
262
+ jQuery('#year').hide();
263
+ }
264
+ }
265
+
266
+ pmpro_ShowMonthOrYear();
267
+
268
+ //draw the chart
269
+ google.load("visualization", "1", {packages:["corechart"]});
270
+ google.setOnLoadCallback(drawChart);
271
+ function drawChart() {
272
+
273
+ var data = google.visualization.arrayToDataTable([
274
+ ['<?php echo $date_function;?>', '<?php echo ucwords($type);?>'],
275
+ <?php foreach($cols as $date => $value) { ?>
276
+ ['<?php if($period == "monthly") echo date("M", mktime(0,0,0,$date,2)); else echo $date;?>', <?php echo $value;?>],
277
+ <?php } ?>
278
+ ]);
279
+
280
+ var options = {
281
+ colors: ['#51a351', '#387038'],
282
+ hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
283
+ vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
284
+ };
285
+
286
+ <?php
287
+ if($type != "sales")
288
+ {
289
+ if(pmpro_getCurrencyPosition() == "right")
290
+ $position = "suffix";
291
+ else
292
+ $position = "prefix";
293
+ ?>
294
+ var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
295
+ formatter.format(data, 1);
296
+ <?php
297
+ }
298
+ ?>
299
+
300
+ var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
301
+ chart.draw(data, options);
302
+ }
303
+ </script>
304
+
305
+ </form>
306
+ <?php
307
+ }
308
+
309
+ /*
310
+ Other code required for your reports. This file is loaded every time WP loads with PMPro enabled.
311
+ */
312
+
313
+ //get sales
314
+ function pmpro_getSales($period, $levels = NULL)
315
+ {
316
+ //check for a transient
317
+ $cache = get_transient("pmpro_report_sales");
318
+ if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
319
+ return $cache[$period][$levels];
320
+
321
+ //a sale is an order with status NOT IN('refunded', 'review', 'token', 'error')
322
+ if($period == "today")
323
+ $startdate = date("Y-m-d", current_time('timestamp'));
324
+ elseif($period == "this month")
325
+ $startdate = date("Y-m", current_time('timestamp')) . "-01";
326
+ elseif($period == "this year")
327
+ $startdate = date("Y", current_time('timestamp')) . "-01-01";
328
+ else
329
+ $startdate = "";
330
+
331
+ $gateway_environment = pmpro_getOption("gateway_environment");
332
+
333
+ //build query
334
+ global $wpdb;
335
+ $sqlQuery = "SELECT COUNT(*) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
336
+
337
+ //restrict by level
338
+ if(!empty($levels))
339
+ $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
340
+
341
+ $sales = $wpdb->get_var($sqlQuery);
342
+
343
+ //save in cache
344
+ if(!empty($cache) && !empty($cache[$period]))
345
+ $cache[$period][$levels] = $sales;
346
+ elseif(!empty($cache))
347
+ $cache[$period] = array($levels => $sales);
348
+ else
349
+ $cache = array($period => array($levels => $sales));
350
+
351
+ set_transient("pmpro_report_sales", $cache, 3600*24);
352
+
353
+ return $sales;
354
+ }
355
+
356
+ //get revenue
357
+ function pmpro_getRevenue($period, $levels = NULL)
358
+ {
359
+ //check for a transient
360
+ $cache = get_transient("pmpro_report_revenue");
361
+ if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
362
+ return $cache[$period][$levels];
363
+
364
+ //a sale is an order with status NOT IN('refunded', 'review', 'token', 'error')
365
+ if($period == "today")
366
+ $startdate = date("Y-m-d", current_time('timestamp'));
367
+ elseif($period == "this month")
368
+ $startdate = date("Y-m", current_time('timestamp')) . "-01";
369
+ elseif($period == "this year")
370
+ $startdate = date("Y", current_time('timestamp')) . "-01-01";
371
+ else
372
+ $startdate = "";
373
+
374
+ $gateway_environment = pmpro_getOption("gateway_environment");
375
+
376
+ //build query
377
+ global $wpdb;
378
+ $sqlQuery = "SELECT SUM(total) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
379
+
380
+ //restrict by level
381
+ if(!empty($levels))
382
+ $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
383
+
384
+ $revenue = $wpdb->get_var($sqlQuery);
385
+
386
+ //save in cache
387
+ if(!empty($cache) && !empty($cache[$period]))
388
+ $cache[$period][$levels] = $revenue;
389
+ elseif(!empty($cache))
390
+ $cache[$period] = array($levels => $revenue);
391
+ else
392
+ $cache = array($period => array($levels => $revenue));
393
+
394
+ set_transient("pmpro_report_revenue", $cache, 3600*24);
395
+
396
+ return $revenue;
397
+ }
398
+
399
+ //delete transients when an order goes through
400
+ function pmpro_report_sales_delete_transients()
401
+ {
402
+ delete_transient("pmpro_report_sales");
403
+ delete_transient("pmpro_report_revenue");
404
+ }
405
+ add_action("pmpro_after_checkout", "pmpro_report_sales_delete_transients");
406
+ add_action("pmpro_updated_order", "pmpro_report_sales_delete_transients");
adminpages/templates/orders-email.php DELETED
@@ -1,71 +0,0 @@
1
- <?php
2
- /**
3
- * Template for Email Invoices
4
- *
5
- * @since 1.8.6
6
- */
7
- ?>
8
- <table style="width:600px;margin-left:auto;margin-right:auto;">
9
- <thead>
10
- <tr>
11
- <td rowspan="2" style="width:80%;">
12
- <h2><?php bloginfo( 'sitename' ); ?></h2>
13
- </td>
14
- <td><?php echo __('Invoice #: ', 'pmpro') . '&nbsp;' . $order->code; ?></td>
15
- </tr>
16
- <tr>
17
- <td>
18
- <?php echo __( 'Date:', 'pmpro' ) . '&nbsp;' . date( 'Y-m-d', $order->timestamp ) ?>
19
- </td>
20
- </tr>
21
- <?php if(!empty($order->billing->name)): ?>
22
- <tr>
23
- <td>
24
- <strong><?php _e( 'Bill to:', 'pmpro' ); ?></strong><br>
25
- <?php
26
- echo pmpro_formatAddress(
27
- $order->billing->name,
28
- $order->billing->street,
29
- "",
30
- $order->billing->city,
31
- $order->billing->state,
32
- $order->billing->zip,
33
- $order->billing->country,
34
- $order->billing->phone
35
- );
36
- ?>
37
- <?php endif; ?>
38
- </td>
39
- </tr>
40
- </thead>
41
- <tbody>
42
- <tr>
43
- <td colspan="2">
44
- <table style="width:100%;border-width:1px;border-style:solid;border-collapse:collapse;">
45
- <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
46
- <th style="text-align:center;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('ID', 'pmpro'); ?></th>
47
- <th style="border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Item', 'pmpro'); ?></th>
48
- <th style="border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Price', 'pmpro'); ?></th>
49
- </tr>
50
- <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
51
- <td style="text-align:center;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $level->id; ?></td>
52
- <td style="border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $level->name; ?></td>
53
- <td style="text-align:right;"><?php echo $order->subtotal; ?></td>
54
- </tr>
55
- <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
56
- <th colspan="2" style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Subtotal', 'pmpro'); ?></th>
57
- <td style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $order->subtotal; ?></td>
58
- </tr>
59
- <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
60
- <th colspan="2" style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Tax', 'pmpro'); ?></th>
61
- <td style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $order->tax; ?></td>
62
- </tr>
63
- <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
64
- <th colspan="2" style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Total', 'pmpro'); ?></th>
65
- <th style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo pmpro_formatPrice($order->total); ?></th>
66
- </tr>
67
- </table>
68
- </td>
69
- </tr>
70
- </tbody>
71
- </table>
adminpages/templates/orders-print.php CHANGED
@@ -1,99 +1,71 @@
1
<?php
2
/**
3
- * Template for Print Invoices
4
*
5
* @since 1.8.6
6
*/
7
?>
8
- <!doctype html>
9
- <html lang="en">
10
- <head>
11
- <meta charset="UTF-8">
12
- <style>
13
- .main, .header {
14
- display: block;
15
- }
16
- .right {
17
- display: inline-block;
18
- float: right;
19
- }
20
- .alignright {
21
- text-align: right;
22
- }
23
- .aligncenter {
24
- text-align: center;
25
- }
26
- .invoice, .invoice tr, .invoice th, .invoice td {
27
- border: 1px solid;
28
- border-collapse: collapse;
29
- padding: 4px;
30
- }
31
- .invoice {
32
- width: 100%;
33
- }
34
- @media screen {
35
- body {
36
- max-width: 50%;
37
- margin: 0 auto;
38
- }
39
- }
40
- </style>
41
- </head>
42
- <body>
43
- <header class="header">
44
- <div>
45
<h2><?php bloginfo( 'sitename' ); ?></h2>
46
- </div>
47
- <div class="right">
48
- <table>
49
- <tr>
50
- <td><?php echo __('Invoice #: ', 'pmpro') . '&nbsp;' . $order->code; ?></td>
51
</tr>
52
- <tr>
53
- <td>
54
- <?php echo __( 'Date:', 'pmpro' ) . '&nbsp;' . date( 'Y-m-d', $order->timestamp ) ?>
55
- </td>
56
</tr>
57
</table>
58
- </div>
59
- </header>
60
- <main class="main">
61
- <p>
62
- <?php echo pmpro_formatAddress(
63
- $order->billing->name,
64
- $order->billing->address1,
65
- $order->billing->address2,
66
- $order->billing->city,
67
- $order->billing->state,
68
- $order->billing->zip,
69
- $order->billing->country,
70
- $order->billing->phone
71
- ); ?>
72
- </p>
73
- <table class="invoice">
74
- <tr>
75
- <th><?php _e('ID', 'pmpro'); ?></th>
76
- <th><?php _e('Item', 'pmpro'); ?></th>
77
- <th><?php _e('Price', 'pmpro'); ?></th>
78
- </tr>
79
- <tr>
80
- <td class="aligncenter"><?php echo $level->id; ?></td>
81
- <td><?php echo $level->name; ?></td>
82
- <td class="alignright"><?php echo $order->subtotal; ?></td>
83
- </tr>
84
- <tr>
85
- <th colspan="2" class="alignright"><?php _e('Subtotal', 'pmpro'); ?></th>
86
- <td class="alignright"><?php echo $order->subtotal; ?></td>
87
- </tr>
88
- <tr>
89
- <th colspan="2" class="alignright"><?php _e('Tax', 'pmpro'); ?></th>
90
- <td class="alignright"><?php echo $order->tax; ?></td>
91
- </tr>
92
- <tr>
93
- <th colspan="2" class="alignright"><?php _e('Total', 'pmpro'); ?></th>
94
- <th class="alignright"><?php echo pmpro_formatPrice( $order->total ); ?></th>
95
- </tr>
96
- </table>
97
- </main>
98
- </body>
99
- </html>
1
<?php
2
/**
3
+ * Template for Order Print Views
4
*
5
* @since 1.8.6
6
*/
7
?>
8
+ <table style="width:600px;margin-left:auto;margin-right:auto;">
9
+ <thead>
10
+ <tr>
11
+ <td rowspan="2" style="width:80%;">
12
<h2><?php bloginfo( 'sitename' ); ?></h2>
13
+ </td>
14
+ <td><?php echo __('Invoice #: ', 'pmpro') . '&nbsp;' . $order->code; ?></td>
15
+ </tr>
16
+ <tr>
17
+ <td>
18
+ <?php echo __( 'Date:', 'pmpro' ) . '&nbsp;' . date( 'Y-m-d', $order->timestamp ) ?>
19
+ </td>
20
+ </tr>
21
+ <?php if(!empty($order->billing->name)): ?>
22
+ <tr>
23
+ <td>
24
+ <strong><?php _e( 'Bill to:', 'pmpro' ); ?></strong><br>
25
+ <?php
26
+ echo pmpro_formatAddress(
27
+ $order->billing->name,
28
+ $order->billing->street,
29
+ "",
30
+ $order->billing->city,
31
+ $order->billing->state,
32
+ $order->billing->zip,
33
+ $order->billing->country,
34
+ $order->billing->phone
35
+ );
36
+ ?>
37
+ <?php endif; ?>
38
+ </td>
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ <tr>
43
+ <td colspan="2">
44
+ <table style="width:100%;border-width:1px;border-style:solid;border-collapse:collapse;">
45
+ <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
46
+ <th style="text-align:center;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('ID', 'pmpro'); ?></th>
47
+ <th style="border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Item', 'pmpro'); ?></th>
48
+ <th style="border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Price', 'pmpro'); ?></th>
49
</tr>
50
+ <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
51
+ <td style="text-align:center;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $level->id; ?></td>
52
+ <td style="border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $level->name; ?></td>
53
+ <td style="text-align:right;"><?php echo $order->subtotal; ?></td>
54
+ </tr>
55
+ <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
56
+ <th colspan="2" style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Subtotal', 'pmpro'); ?></th>
57
+ <td style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $order->subtotal; ?></td>
58
+ </tr>
59
+ <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
60
+ <th colspan="2" style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Tax', 'pmpro'); ?></th>
61
+ <td style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo $order->tax; ?></td>
62
+ </tr>
63
+ <tr style="border-width:1px;border-style:solid;border-collapse:collapse;">
64
+ <th colspan="2" style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php _e('Total', 'pmpro'); ?></th>
65
+ <th style="text-align:right;border-width:1px;border-style:solid;border-collapse:collapse;"><?php echo pmpro_formatPrice($order->total); ?></th>
66
</tr>
67
</table>
68
+ </td>
69
+ </tr>
70
+ </tbody>
71
+ </table>
classes/class.pmproemail.php CHANGED
@@ -849,12 +849,12 @@
849
$this->template = "billable_invoice";
850
851
// Load invoice template
852
- if ( file_exists( get_stylesheet_directory() . '/paid-memberships-pro/pages/orders-email.php' ) ) {
853
- $template = get_stylesheet_directory() . '/paid-memberships-pro/pages/orders-email.php';
854
- } elseif ( file_exists( get_template_directory() . '/paid-memberships-pro/pages/orders-email.php' ) ) {
855
- $template = get_template_directory() . '/paid-memberships-pro/pages/orders-email.php';
856
} else {
857
- $template = PMPRO_DIR . '/adminpages/templates/orders-email.php';
858
}
859
860
ob_start();
849
$this->template = "billable_invoice";
850
851
// Load invoice template
852
+ if ( file_exists( get_stylesheet_directory() . '/paid-memberships-pro/pages/orders-print.php' ) ) {
853
+ $template = get_stylesheet_directory() . '/paid-memberships-pro/pages/orders-print.php';
854
+ } elseif ( file_exists( get_template_directory() . '/paid-memberships-pro/pages/orders-print.php' ) ) {
855
+ $template = get_template_directory() . '/paid-memberships-pro/pages/orders-print.php';
856
} else {
857
+ $template = PMPRO_DIR . '/adminpages/templates/orders-print.php';
858
}
859
860
ob_start();
css/admin.css CHANGED
@@ -1,122 +1,118 @@
1
- /* icon */
2
- #wp-admin-bar-paid-memberships-pro .ab-item .ab-icon:before {
3
- font-family: "dashicons";
4
- content: "\f307";
5
- }
6
- .pmpro_admin tr td .dashicons {padding-top: 5px; }
7
-
8
- /* header/etc */
9
- .pmpro_admin {background: url(../images/Paid-Memberships-Pro_watermark.png) bottom right no-repeat !important; padding: 1em 0 70px 0; }
10
-
11
- .pmpro_admin .pmpro_banner h2 {float: left; }
12
- .pmpro_admin .pmpro_banner .pmpro_meta {float: left; margin: 26px 0 0 0; font-size: 12px; }
13
- .pmpro_admin .pmpro_banner .pmpro_meta .pmpro_tag-blue {margin: 0 0 0 5px; }
14
- .pmpro_admin .pmpro_banner .pmpro_logo {float: left; margin: 0 1em 0 0; width: 350px; height: 75px; }
15
- .pmpro_admin .pmpro_banner ul.pmpro_menu {clear: both; border: 1px solid #CCC; border-radius: 5px; -moz-border-radius: 5px; background: #FFF; }
16
- .pmpro_admin .pmpro_banner ul.pmpro_menu li {display: inline-block; margin: 10px 0; padding: 0px 10px; border-right: 1px solid #CCC; }
17
- .pmpro_admin .pmpro_banner ul.pmpro_menu li a, .pmpro_admin .pmpro_banner ul.pmpro_menu li a:link {color: #1e0741; text-decoration: none; }
18
- .pmpro_admin .pmpro_banner ul.pmpro_menu li a:hover {text-decoration: underline; color: #412f5b; }
19
-
20
- .pmpro_admin .pmpro_tag-grey {display: inline-block; font-size: 11px; font-weight: bold; position: relative; padding: 2px 5px; border: 1px solid #CCC; background: whiteSmoke; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; }
21
- .pmpro_admin .pmpro_tag-blue {display: inline-block; font-size: 11px; font-weight: bold; position: relative; padding: 2px 5px; border: 1px solid #2997c8; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; background: #2997c8; color: #FFF; text-decoration: none; }
22
- .pmpro_admin a.pmpro_tag-blue:hover {background: #77a02e; border: 1px solid #77a02e; }
23
-
24
- .pmpro_admin .topborder {border-top: 1px solid #CCC; margin-top: 1em; padding-top: 1em; }
25
- .pmpro_admin #editorcontainer #description {width: 100%; height: 180px; }
26
- .pmpro_admin .widefat {margin-top: 1em; }
27
-
28
- /* checkboxes */
29
- .checkbox_box {width: 300px; background: #FFFFFF; border: 1px solid #CCC;}
30
- .checkbox_box div {border-bottom: 1px solid #CCC; padding: 3px;}
31
- .checkbox_box .clickable {cursor: pointer;}
32
- .checkbox_box .clickable:hover {background: #FFC;}
33
-
34
- /* levels */
35
- tr.pmpro_gray td {color: #AAA;}
36
- tr td.level_name a {font-size: 115%; font-weight: bold; }
37
- .membership-levels tr {background: #fff;}
38
- .membership-levels tr.alternate {background: #f9f9f9;}
39
- .membership-levels tr.ui-sortable-helper {border: 1px solid #2997C8;}
40
- tr.testclass {border: 3px solid #2997C8; background: #2997C8;}
41
-
42
- /* settings */
43
- tr.pmpro_settings_divider td {padding: 5px; margin: 0; color: #555; border-top: 1px solid #CCC; border-bottom: 1px solid #CCC;}
44
- tr.pmpro_settings_divider td:before {content: "- ";}
45
- tr.pmpro_settings_divider td:after {content: " -";}
46
-
47
- /* messages */
48
- .pmpro_message {background-color: #D5E4F7; background-image: url(../images/icon_information.gif); background-position: 3px 5px; background-repeat: no-repeat; margin: .5em 0; padding: 6px 6px 6px 25px; color: #345395; font-size: 11px; font-weight: bold; line-height: 1.3em; }
49
-
50
- .pmpro_success {background-color: #CFEECA; background-image: url(../images/icon_success.gif); color: #208A1B; }
51
- .pmpro_error {background-color: #F9D6CB; background-image: url(../images/icon_error.gif); color: #E36154; }
52
- .pmpro_alert {background-color: #FFF6CC; background-image: url(../images/icon_alert.gif); color: #CF8516; }
53
-
54
- .pmpro_message a {color: #345395; }
55
- .pmpro_success a {color: #208A1B; }
56
- .pmpro_error a {color: #E36154; }
57
- .pmpro_alert a {color: #CF8516; }
58
-
59
- /* highlighted trs */
60
- tr.pmpro_message {background-image: none;}
61
- tr.pmpro_success {background-image: none;}
62
- tr.pmpro_error {background-image: none;}
63
- tr.pmpro_alert {background-image: none;}
64
-
65
- /* discount levels */
66
- .pmpro_discount_levels {border: 1px solid #CCC;}
67
- .pmpro_discount_levels div {padding: 5px; border: 1px solid #CCC;}
68
- .pmpro_discount_levels div div {margin-top: 5px; background: #F5F5F5;}
69
-
70
- /* pagination */
71
- div.pmpro_pagination {padding: 3px; margin: 5px 0px 5px 0px; font-size: 10px; float: right; }
72
- div.pmpro_pagination a {padding: 2px 5px 2px 5px; margin: 1px; border: 1px solid #666; text-decoration: none; /* no underline */ color: #666; background: #EEE; }
73
- div.pmpro_pagination a:hover, div.pmpro_pagination a:active {background: #FFF; }
74
- div.pmpro_pagination span.current {border: 1px solid #FFF; color: #FFF; background: #666; padding: 2px 5px 2px 5px; margin: 1px; font-weight: bold; }
75
- div.pmpro_pagination span.disabled {padding: 2px 5px 2px 5px; margin: 2px; border: 1px solid #BBB; color: #BBB; background: #EFEFEF;}
76
-
77
- p.pmpro_meta_notice {font-size: .8em; padding-top: 5px; border-top: 1px solid #CCC;}
78
-
79
- /* add ons */
80
- .pmpro_admin .widgets-holder-wrap {clear: both; margin-top: 20px; padding: 0 8px; }
81
- .pmpro_admin .widgets-holder-wrap .widget {float: left; width: 32%; margin: 0 1% 1% 0; position: relative; }
82
- .pmpro_admin .widgets-holder-wrap p.description {padding: 0; }
83
- .pmpro_admin .widgets-holder-wrap .widget-top {height: auto; cursor: default; }
84
- .pmpro_admin .widgets-holder-wrap .widget-inside {display: block; height: 130px; overflow: hidden; }
85
- .pmpro_admin .widgets-holder-wrap .widget-inside p {height: 80px; overflow: hidden; }
86
- .pmpro_admin #pmpro-gists.widgets-holder-wrap .widget-inside, .pmpro_admin #pmpro-gists.widgets-holder-wrap .widget-inside p {height: auto; }
87
- .pmpro_admin .widgets-holder-wrap .widget-title { }
88
- .pmpro_admin .widgets-holder-wrap .widget-title h4 { }
89
- .pmpro_admin .widgets-holder-wrap .widget-title .status-label {display: block; float: left; margin: 0 5px 0 0; width: 10px;
90
- height: 10px; overflow: hidden; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; border: 1px solid #DFDFDF; text-indent: -9999em; }
91
- .pmpro_admin .widgets-holder-wrap .disabled .widget-title .status-label {background: #F00; }
92
- .pmpro_admin .widgets-holder-wrap .enabled .widget-title .status-label {background: #0C0; }
93
-
94
- .pmpro_admin .widgets-holder-wrap .widget-title .version {position: absolute; top: 13px; right: 10px; }
95
- .pmpro_admin .widgets-holder-wrap .widget-inside .addon-thumb {width: 100px; height: 100px; float: right; margin: 10px 0 0 10px; border: 1px solid #DFDFDF; background: #FFF; padding: 2px;}
96
-
97
- /*@media (min-width: 1200px) {
98
- .auto-fold .pmpro_admin .widgets-holder-wrap .widget-inside, .auto-fold .pmpro_admin .widgets-holder-wrap .widget-inside p {height: auto; }
99
- }
100
- */
101
- @media (max-width:900px) {
102
- .auto-fold .pmpro_admin .widgets-holder-wrap .widget {float: none; width: 100%; }
103
- .auto-fold .pmpro_admin .widgets-holder-wrap .widget-inside, .auto-fold .pmpro_admin .widgets-holder-wrap .widget-inside p {height: auto; }
104
- }
105
-
106
- /* misc */
107
- .pmpro_lite {color: #AAA;}
108
- .pmpro_pad20 {padding: 20px !important;}
109
- .pmpro_red {color: #CC0000;}
110
- .pmpro_green {color: #00AA00;}
111
- .ssp_description #description {width: 100%;}
112
- .top0em {margin-top: 0;}
113
- h2.nav-tab-wrapper {margin-bottom: 1em; }
114
-
115
- /* reports */
116
- .pmpro_reports-holder { }
117
- .pmpro_clickable {cursor: pointer;}
118
- .js .postbox.pmpro_clickable h3 {cursor: pointer;}
119
- .pmpro_reports-holder .wp-list-table tbody td {font-size: 1.2rem; font-weight: bold; }
120
- @media screen and (max-width: 782px) {
121
- .pmpro_reports-holder tr:not(.inline-edit-row):not(.no-items) td:not(.check-column) {display: table-cell; }
122
- }
1
+ /* icon */
2
+ #wp-admin-bar-paid-memberships-pro .ab-item .ab-icon:before {
3
+ font-family: "dashicons";
4
+ content: "\f307";
5
+ }
6
+ .pmpro_admin tr td .dashicons {padding-top: 5px; }
7
+
8
+ /* header/etc */
9
+ .pmpro_admin {background: url(../images/Paid-Memberships-Pro_watermark.png) bottom right no-repeat !important; padding: 1em 0 70px 0; }