Paid Memberships Pro - Version 1.9.5.4

Version Description

  • 2018-09-10 =
  • SECURITY: Some values used in SQL queries in our reporting code were sanitized but not later escaped via esc_sql(). All variables added to SQL queries in the reports are now wrapped in esc_sql(). The previous code was not vulnerable to any known attack, but this change hardens the code against vulnerabilities in the case other parts of the code change in the future.
  • BUG FIX: Fixed issue with lost passwords when Theme My Login 7 is active. (Thanks, Jeff Farthing)
  • BUG FIX: No longer sending an "error canceling the subscription" email when subscriptions are cancelled from Stripe.
  • BUG FIX: Fixed issue where TwoCheckout orders were not correctly updating the TOS consent data. (Thanks, Charl P. Botha)
  • BUG FIX: Fixed issue where privacy function weren't defaulting to $current_user correctly. In practice, we were always passing a user_id anyway.
  • BUG FIX/ENHANCEMENT: Changed the confirmation message to use wpautop instead of apply_filters('the_content'). If you were relying on shortcodes or other content that required that filter, you use add_filter('pmpro_level_description', 'the_content') to revert this for your site.
  • BUG FIX/ENHANCEMENT: Using the strict parameter of sanitize_user when getting usernames. This will prevent some special characters from being used in usernames at checkout. This is inline with WP core and other plugins. (Thanks, David Cervantes Caballero)
  • ENHANCEMENT: Added a breakdown of orders at each price point to the Sales Report widget
  • ENHANCEMENT: Showing the Stripe version we use on the Payment Settings page.
  • ENHANCEMENT: Updated Copyright date and GPLv2 link in license.txt.
Download this release

Release Info

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

Code changes from version 1.9.5.3 to 1.9.5.4

CHANGELOG.txt CHANGED
@@ -1,4 +1,18 @@
1
== Changelog ==
2
= 1.9.5.3 - 2018-06-26 =
3
* BUG FIX: The pmpro_ipnhandler_extend_memberships function actually needed use $user_id instead of $current_user.
4
1
== Changelog ==
2
+
3
+ = 1.9.5.4 - 2018-09-10 =
4
+ * SECURITY: Some values used in SQL queries in our reporting code were sanitized but not later escaped via esc_sql(). All variables added to SQL queries in the reports are now wrapped in esc_sql(). The previous code was not vulnerable to any known attack, but this change hardens the code against vulnerabilities in the case other parts of the code change in the future.
5
+ * BUG FIX: Fixed issue with lost passwords when Theme My Login 7 is active. (Thanks, Jeff Farthing)
6
+ * BUG FIX: No longer sending an "error canceling the subscription" email when subscriptions are cancelled from Stripe.
7
+ * BUG FIX: Fixed issue where TwoCheckout orders were not correctly updating the TOS consent data. (Thanks, Charl P. Botha)
8
+ * BUG FIX: Fixed issue where privacy function weren't defaulting to $current_user correctly. In practice, we were always passing a user_id anyway.
9
+ * BUG FIX/ENHANCEMENT: Changed the confirmation message to use wpautop instead of apply_filters('the_content'). If you were relying on shortcodes or other content that required that filter, you use add_filter('pmpro_level_description', 'the_content') to revert this for your site.
10
+ * BUG FIX/ENHANCEMENT: Using the strict parameter of sanitize_user when getting usernames. This will prevent some special characters from being used in usernames at checkout. This is inline with WP core and other plugins. (Thanks, David Cervantes Caballero)
11
+ * ENHANCEMENT: Added a breakdown of orders at each price point to the Sales Report widget
12
+ * ENHANCEMENT: Showing the Stripe version we use on the Payment Settings page.
13
+ * ENHANCEMENT: Updated Copyright date and GPLv2 link in license.txt.
14
+
15
+
16
= 1.9.5.3 - 2018-06-26 =
17
* BUG FIX: The pmpro_ipnhandler_extend_memberships function actually needed use $user_id instead of $current_user.
18
CONTRIBUTE.md → CONTRIBUTING.md RENAMED
@@ -1,4 +1,4 @@
1
- #Contribute to Paid Memberships Pro
2
3
Paid Memberships Pro is the "community solution" for membership sites on WordPress, and so contributions of all kinds are appreciated.
4
@@ -21,8 +21,9 @@ __Please Note:__ GitHub is for bug reports and contributions only. If you have a
21
* For new features and enhancements, checkout the branch for the version the feature is milestoned for.
22
* Make sure to pull in any "upstream" changes first.
23
* Use `git remote add upstream https://github.com/strangerstudios/paid-memberships-pro.git` to set the upstream repo
24
- * Use `git checkout upstream/dev` then `git pull` to pull in the latest updates on dev.
25
- * Use `git checkout dev` then `git merge upstream/dev` to merge those updates into your dev.
26
* Create a new local branch for each separate bug fix or feature. This will ensure that each pull request is for one issue only and easier to process.
27
* Use `git checkout -b nameofmybugfixorfeature` to create the new branch
28
* Make the changes to your local repository.
1
+ # Contribute to Paid Memberships Pro
2
3
Paid Memberships Pro is the "community solution" for membership sites on WordPress, and so contributions of all kinds are appreciated.
4
21
* For new features and enhancements, checkout the branch for the version the feature is milestoned for.
22
* Make sure to pull in any "upstream" changes first.
23
* Use `git remote add upstream https://github.com/strangerstudios/paid-memberships-pro.git` to set the upstream repo
24
+ * Use `git checkout dev` to get on the development branch.
25
+ * Use `git pull upstream dev` to get the latest updates.
26
+ * Use `git push` to push those updates to your fork.
27
* Create a new local branch for each separate bug fix or feature. This will ensure that each pull request is for one issue only and easier to process.
28
* Use `git checkout -b nameofmybugfixorfeature` to create the new branch
29
* Make the changes to your local repository.
adminpages/reports/login.php CHANGED
@@ -3,11 +3,11 @@
3
PMPro Report
4
Title: Logins
5
Slug: login
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.
@@ -53,7 +53,7 @@ function pmpro_report_login_widget()
53
<td><?php echo number_format_i18n($logins['alltime']); ?></td>
54
</tr>
55
</tbody>
56
- </table>
57
</span>
58
<?php
59
}
@@ -62,13 +62,13 @@ function pmpro_report_login_page()
62
{
63
global $wpdb;
64
$now = current_time('timestamp');
65
-
66
//vars
67
if(!empty($_REQUEST['s']))
68
$s = sanitize_text_field($_REQUEST['s']);
69
else
70
$s = "";
71
-
72
if(!empty($_REQUEST['l'])) {
73
if($_REQUEST['l'] == 'all')
74
$l = 'all';
@@ -78,12 +78,12 @@ function pmpro_report_login_page()
78
$l = "";
79
}
80
?>
81
- <form id="posts-filter" method="get" action="">
82
<h1>
83
<?php _e('Visits, Views, and Logins Report', 'paid-memberships-pro' );?>
84
- </h1>
85
<ul class="subsubsub">
86
- <li>
87
<?php _e('Show', 'paid-memberships-pro' )?> <select name="l" onchange="jQuery('#posts-filter').submit();">
88
<option value="" <?php if(!$l) { ?>selected="selected"<?php } ?>><?php _e('All Users', 'paid-memberships-pro' )?></option>
89
<option value="all" <?php if($l == "all") { ?>selected="selected"<?php } ?>><?php _e('All Levels', 'paid-memberships-pro' )?></option>
@@ -96,47 +96,47 @@ function pmpro_report_login_page()
96
<?php
97
}
98
?>
99
- </select>
100
</li>
101
</ul>
102
<p class="search-box">
103
<label class="hidden" for="post-search-input"><?php _e('Search', 'paid-memberships-pro' )?> <?php if(empty($l)) echo "Users"; else echo "Members";?>:</label>
104
- <input type="hidden" name="page" value="pmpro-reports" />
105
- <input type="hidden" name="report" value="login" />
106
<input id="post-search-input" type="text" value="<?php echo esc_attr($s)?>" name="s"/>
107
<input class="button" type="submit" value="Search Members"/>
108
</p>
109
- <?php
110
- //some vars for the search
111
if(isset($_REQUEST['pn']))
112
$pn = intval($_REQUEST['pn']);
113
else
114
$pn = 1;
115
-
116
if(isset($_REQUEST['limit']))
117
$limit = intval($_REQUEST['limit']);
118
else
119
$limit = 15;
120
-
121
$end = $pn * $limit;
122
- $start = $end - $limit;
123
-
124
if($s)
125
{
126
$sqlQuery = "SELECT SQL_CALC_FOUND_ROWS u.ID, u.user_login, u.user_email, UNIX_TIMESTAMP(u.user_registered) as joindate, mu.membership_id, mu.initial_payment, mu.billing_amount, mu.cycle_period, mu.cycle_number, mu.billing_limit, mu.trial_amount, mu.trial_limit, UNIX_TIMESTAMP(mu.startdate) as startdate, UNIX_TIMESTAMP(mu.enddate) as enddate, m.name as membership FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id LEFT JOIN $wpdb->pmpro_memberships_users mu ON u.ID = mu.user_id AND mu.status = 'active' LEFT JOIN $wpdb->pmpro_membership_levels m ON mu.membership_id = m.id WHERE (u.user_login LIKE '%" . esc_sql($s) . "%' OR u.user_email LIKE '%" . esc_sql($s) . "%' OR um.meta_value LIKE '%" . esc_sql($s) . "%') ";
127
-
128
if($l == "all")
129
$sqlQuery .= " AND mu.status = 'active' AND mu.membership_id > 0 ";
130
elseif($l)
131
- $sqlQuery .= " AND mu.membership_id = '" . esc_sql($l) . "' ";
132
-
133
$sqlQuery .= "GROUP BY u.ID ORDER BY user_registered DESC LIMIT $start, $limit";
134
}
135
else
136
{
137
$sqlQuery = "SELECT SQL_CALC_FOUND_ROWS u.ID, u.user_login, u.user_email, UNIX_TIMESTAMP(u.user_registered) as joindate, mu.membership_id, mu.initial_payment, mu.billing_amount, mu.cycle_period, mu.cycle_number, mu.billing_limit, mu.trial_amount, mu.trial_limit, UNIX_TIMESTAMP(mu.startdate) as startdate, UNIX_TIMESTAMP(mu.enddate) as enddate, m.name as membership FROM $wpdb->users u LEFT JOIN $wpdb->pmpro_memberships_users mu ON u.ID = mu.user_id AND mu.status = 'active' LEFT JOIN $wpdb->pmpro_membership_levels m ON mu.membership_id = m.id";
138
$sqlQuery .= " WHERE 1=1 ";
139
-
140
if($l == "all")
141
$sqlQuery .= " AND mu.membership_id > 0 AND mu.status = 'active' ";
142
elseif($l)
@@ -145,24 +145,24 @@ function pmpro_report_login_page()
145
}
146
147
$sqlQuery = apply_filters("pmpro_members_list_sql", $sqlQuery);
148
-
149
$theusers = $wpdb->get_results($sqlQuery);
150
$totalrows = $wpdb->get_var("SELECT FOUND_ROWS() as found_rows");
151
-
152
if($theusers)
153
{
154
?>
155
- <p class="clear"><?php echo strval($totalrows)?> <?php if(empty($l)) echo "users"; else echo "members";?> found.
156
- <?php
157
- }
158
?>
159
<table class="widefat">
160
<thead>
161
<tr class="thead">
162
<th><?php _e('ID', 'paid-memberships-pro' )?></th>
163
- <th><?php _e('User', 'paid-memberships-pro' )?></th>
164
<th><?php _e('Name', 'paid-memberships-pro' )?></th>
165
- <th><?php _e('Membership', 'paid-memberships-pro' )?></th>
166
<th><?php _e('Joined', 'paid-memberships-pro' )?></th>
167
<th><?php _e('Expires', 'paid-memberships-pro' )?></th>
168
<th><?php _e('Last Visit', 'paid-memberships-pro' )?></th>
@@ -172,15 +172,15 @@ function pmpro_report_login_page()
172
<th><?php _e('Total Views', 'paid-memberships-pro' )?></th>
173
<th><?php _e('Last Login', 'paid-memberships-pro' )?></th>
174
<th><?php _e('Logins This Month', 'paid-memberships-pro' )?></th>
175
- <th><?php _e('Total Logins', 'paid-memberships-pro' )?></th>
176
</tr>
177
</thead>
178
- <tbody id="users" class="list:user user-list">
179
- <?php
180
- $count = 0;
181
foreach($theusers as $auser)
182
{
183
- //get meta
184
$theuser = get_userdata($auser->ID);
185
$visits = get_user_meta($auser->ID, "pmpro_visits", true);
186
$views = get_user_meta($auser->ID, "pmpro_views", true);
@@ -197,17 +197,17 @@ function pmpro_report_login_page()
197
$userlink = '<a href="user-edit.php?user_id=' . $theuser->ID . '">' . $theuser->user_login . '</a>';
198
$userlink = apply_filters("pmpro_members_list_user_link", $userlink, $theuser);
199
echo $userlink;
200
- ?>
201
</strong>
202
- </td>
203
<td>
204
<?php echo $theuser->display_name;?>
205
</td>
206
- <td><?php echo $auser->membership?></td>
207
<td><?php echo date_i18n("m/d/Y", strtotime($theuser->user_registered, current_time("timestamp")))?></td>
208
<td>
209
- <?php
210
- if($auser->enddate)
211
echo date_i18n(get_option('date_format'), $auser->enddate);
212
else
213
echo "Never";
@@ -215,7 +215,7 @@ function pmpro_report_login_page()
215
</td>
216
<td><?php if(!empty($visits['last'])) echo $visits['last'];?></td>
217
<td><?php if(!empty($visits['month']) && pmpro_isDateThisMonth($visits['last'])) echo $visits['month'];?></td>
218
- <td><?php if(!empty($visits['alltime'])) echo $visits['alltime'];?></td>
219
<td><?php if(!empty($views['month']) && pmpro_isDateThisMonth($views['last'])) echo $views['month'];?></td>
220
<td><?php if(!empty($views['alltime'])) echo $views['alltime'];?></td>
221
<td><?php if(!empty($logins['last'])) echo $logins['last'];?></td>
@@ -224,7 +224,7 @@ function pmpro_report_login_page()
224
</tr>
225
<?php
226
}
227
-
228
if(!$theusers)
229
{
230
?>
@@ -233,7 +233,7 @@ function pmpro_report_login_page()
233
</tr>
234
<?php
235
}
236
- ?>
237
</tbody>
238
</table>
239
</form>
@@ -254,28 +254,28 @@ function pmpro_report_login_wp_visits()
254
//don't track admin
255
if(is_admin())
256
return;
257
-
258
//only track logged in users
259
if(!is_user_logged_in())
260
return;
261
-
262
//check for cookie
263
if(!empty($_COOKIE['pmpro_visit']))
264
return;
265
-
266
$now = current_time('timestamp');
267
-
268
//set cookie, then track
269
- setcookie("pmpro_visit", "1", NULL, COOKIEPATH, COOKIE_DOMAIN, false);
270
-
271
global $current_user;
272
//track for user
273
if(!empty($current_user->ID))
274
- {
275
- $visits = $current_user->pmpro_visits;
276
if(empty($visits))
277
$visits = array("last"=>"N/A", "thisdate"=>NULL, "month"=>0, "thismonth"=>NULL, "alltime"=>0);
278
-
279
//track logins for user
280
$visits['last'] = date_i18n(get_option("date_format"), $now);
281
$visits['alltime'] = $visits['alltime'] + 1; // BUG FIX: Caused fatal error in certain PHP versions
@@ -287,16 +287,16 @@ function pmpro_report_login_wp_visits()
287
$visits['month'] = 1;
288
$visits['thismonth'] = $thismonth;
289
}
290
-
291
//update user data
292
update_user_meta($current_user->ID, "pmpro_visits", $visits);
293
}
294
-
295
//track for all
296
- $visits = get_option("pmpro_visits");
297
if(empty($visits))
298
$visits = array("today"=>0, "thisdate"=>NULL, "month"=>0, "thismonth"=> NULL, "alltime"=>0);
299
-
300
$visits['alltime'] = $visits['alltime'] + 1; // BUG FIX: Caused fatal error in certain PHP versions
301
$thisdate = date_i18n("Y-d-m", $now);
302
if($thisdate == $visits['thisdate'])
@@ -313,8 +313,8 @@ function pmpro_report_login_wp_visits()
313
$visits['month'] = 1;
314
$visits['thismonth'] = $thismonth;
315
}
316
-
317
- update_option("pmpro_visits", $visits);
318
}
319
add_action("wp", "pmpro_report_login_wp_visits");
320
@@ -333,17 +333,17 @@ function pmpro_report_login_wp_views()
333
//don't track admin
334
if(is_admin())
335
return;
336
-
337
global $current_user;
338
$now = current_time('timestamp');
339
-
340
//track for user
341
if(!empty($current_user->ID))
342
- {
343
- $views = $current_user->pmpro_views;
344
if(empty($views))
345
$views = array("last"=>"N/A", "month"=>0, "alltime"=>0);
346
-
347
//track logins for user
348
$views['last'] = date_i18n(get_option("date_format"), $now);
349
$views['alltime'] = $views['alltime'] + 1;
@@ -355,16 +355,16 @@ function pmpro_report_login_wp_views()
355
$views['month'] = 1;
356
$views['thismonth'] = $thismonth;
357
}
358
-
359
//update user data
360
update_user_meta($current_user->ID, "pmpro_views", $views);
361
}
362
-
363
//track for all
364
- $views = get_option("pmpro_views");
365
if(empty($views))
366
$views = array("today"=>0, "thisdate"=> NULL, "month"=>0, "thismonth"=> NULL, "alltime"=>0);
367
-
368
$views['alltime'] = $views['alltime'] + 1;
369
$thisdate = date_i18n("Y-d-m", $now);
370
if($thisdate == $views['thisdate'])
@@ -382,8 +382,8 @@ function pmpro_report_login_wp_views()
382
$views['month'] = 1;
383
$views['thismonth'] = $thismonth;
384
}
385
-
386
- update_option("pmpro_views", $views);
387
}
388
add_action("wp_head", "pmpro_report_login_wp_views");
389
@@ -391,13 +391,13 @@ add_action("wp_head", "pmpro_report_login_wp_views");
391
function pmpro_report_login_wp_login($user_login)
392
{
393
$now = current_time('timestamp');
394
-
395
//get user data
396
- $user = get_user_by("login", $user_login);
397
$logins = $user->pmpro_logins;
398
if(empty($logins))
399
$logins = array("last"=>"N/A", "thisdate"=>NULL, "month"=>0, "thismonth"=> NULL, "alltime"=>0);
400
-
401
//track logins for user
402
$logins['last'] = date_i18n(get_option("date_format"), $now);
403
$logins['alltime'] = $logins['alltime'] + 1;
@@ -405,19 +405,19 @@ function pmpro_report_login_wp_login($user_login)
405
if($thismonth == $logins['thismonth'])
406
$logins['month'] = $logins['month'] + 1;
407
else
408
- {
409
$logins['month'] = 1;
410
$logins['thismonth'] = $thismonth;
411
}
412
-
413
//update user data
414
update_user_meta($user->ID, "pmpro_logins", $logins);
415
-
416
//track logins overall
417
$logins = get_option("pmpro_logins");
418
if(empty($logins))
419
$logins = array("today"=>0, "thisdate"=>NULL, "month"=>0, "thismonth"=>NULL, "alltime"=>0);
420
-
421
$logins['alltime'] = $logins['alltime'] + 1;
422
$thisdate = date_i18n("Y-d-m", $now);
423
if($thisdate == $logins['thisdate'])
@@ -434,7 +434,7 @@ function pmpro_report_login_wp_login($user_login)
434
$logins['month'] = 1;
435
$logins['thismonth'] = $thismonth;
436
}
437
-
438
- update_option("pmpro_logins", $logins);
439
}
440
add_action("wp_login", "pmpro_report_login_wp_login");
3
PMPro Report
4
Title: Logins
5
Slug: login
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.
53
<td><?php echo number_format_i18n($logins['alltime']); ?></td>
54
</tr>
55
</tbody>
56
+ </table>
57
</span>
58
<?php
59
}
62
{
63
global $wpdb;
64
$now = current_time('timestamp');
65
+
66
//vars
67
if(!empty($_REQUEST['s']))
68
$s = sanitize_text_field($_REQUEST['s']);
69
else
70
$s = "";
71
+
72
if(!empty($_REQUEST['l'])) {
73
if($_REQUEST['l'] == 'all')
74
$l = 'all';
78
$l = "";
79
}
80
?>
81
+ <form id="posts-filter" method="get" action="">
82
<h1>
83
<?php _e('Visits, Views, and Logins Report', 'paid-memberships-pro' );?>
84
+ </h1>
85
<ul class="subsubsub">
86
+ <li>
87
<?php _e('Show', 'paid-memberships-pro' )?> <select name="l" onchange="jQuery('#posts-filter').submit();">
88
<option value="" <?php if(!$l) { ?>selected="selected"<?php } ?>><?php _e('All Users', 'paid-memberships-pro' )?></option>
89
<option value="all" <?php if($l == "all") { ?>selected="selected"<?php } ?>><?php _e('All Levels', 'paid-memberships-pro' )?></option>
96
<?php
97
}
98
?>
99
+ </select>
100
</li>
101
</ul>
102
<p class="search-box">
103
<label class="hidden" for="post-search-input"><?php _e('Search', 'paid-memberships-pro' )?> <?php if(empty($l)) echo "Users"; else echo "Members";?>:</label>
104
+ <input type="hidden" name="page" value="pmpro-reports" />
105
+ <input type="hidden" name="report" value="login" />
106
<input id="post-search-input" type="text" value="<?php echo esc_attr($s)?>" name="s"/>
107
<input class="button" type="submit" value="Search Members"/>
108
</p>
109
+ <?php
110
+ //some vars for the search
111
if(isset($_REQUEST['pn']))
112
$pn = intval($_REQUEST['pn']);
113
else
114
$pn = 1;
115
+
116
if(isset($_REQUEST['limit']))
117
$limit = intval($_REQUEST['limit']);
118
else
119
$limit = 15;
120
+
121
$end = $pn * $limit;
122
+ $start = $end - $limit;
123
+
124
if($s)
125
{
126
$sqlQuery = "SELECT SQL_CALC_FOUND_ROWS u.ID, u.user_login, u.user_email, UNIX_TIMESTAMP(u.user_registered) as joindate, mu.membership_id, mu.initial_payment, mu.billing_amount, mu.cycle_period, mu.cycle_number, mu.billing_limit, mu.trial_amount, mu.trial_limit, UNIX_TIMESTAMP(mu.startdate) as startdate, UNIX_TIMESTAMP(mu.enddate) as enddate, m.name as membership FROM $wpdb->users u LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id LEFT JOIN $wpdb->pmpro_memberships_users mu ON u.ID = mu.user_id AND mu.status = 'active' LEFT JOIN $wpdb->pmpro_membership_levels m ON mu.membership_id = m.id WHERE (u.user_login LIKE '%" . esc_sql($s) . "%' OR u.user_email LIKE '%" . esc_sql($s) . "%' OR um.meta_value LIKE '%" . esc_sql($s) . "%') ";
127
+
128
if($l == "all")
129
$sqlQuery .= " AND mu.status = 'active' AND mu.membership_id > 0 ";
130
elseif($l)
131
+ $sqlQuery .= " AND mu.membership_id = '" . esc_sql($l) . "' ";
132
+
133
$sqlQuery .= "GROUP BY u.ID ORDER BY user_registered DESC LIMIT $start, $limit";
134
}
135
else
136
{
137
$sqlQuery = "SELECT SQL_CALC_FOUND_ROWS u.ID, u.user_login, u.user_email, UNIX_TIMESTAMP(u.user_registered) as joindate, mu.membership_id, mu.initial_payment, mu.billing_amount, mu.cycle_period, mu.cycle_number, mu.billing_limit, mu.trial_amount, mu.trial_limit, UNIX_TIMESTAMP(mu.startdate) as startdate, UNIX_TIMESTAMP(mu.enddate) as enddate, m.name as membership FROM $wpdb->users u LEFT JOIN $wpdb->pmpro_memberships_users mu ON u.ID = mu.user_id AND mu.status = 'active' LEFT JOIN $wpdb->pmpro_membership_levels m ON mu.membership_id = m.id";
138
$sqlQuery .= " WHERE 1=1 ";
139
+
140
if($l == "all")
141
$sqlQuery .= " AND mu.membership_id > 0 AND mu.status = 'active' ";
142
elseif($l)
145
}
146
147
$sqlQuery = apply_filters("pmpro_members_list_sql", $sqlQuery);
148
+
149
$theusers = $wpdb->get_results($sqlQuery);
150
$totalrows = $wpdb->get_var("SELECT FOUND_ROWS() as found_rows");
151
+
152
if($theusers)
153
{
154
?>
155
+ <p class="clear"><?php echo strval($totalrows)?> <?php if(empty($l)) echo "users"; else echo "members";?> found.
156
+ <?php
157
+ }
158
?>
159
<table class="widefat">
160
<thead>
161
<tr class="thead">
162
<th><?php _e('ID', 'paid-memberships-pro' )?></th>
163
+ <th><?php _e('User', 'paid-memberships-pro' )?></th>
164
<th><?php _e('Name', 'paid-memberships-pro' )?></th>
165
+ <th><?php _e('Membership', 'paid-memberships-pro' )?></th>
166
<th><?php _e('Joined', 'paid-memberships-pro' )?></th>
167
<th><?php _e('Expires', 'paid-memberships-pro' )?></th>
168
<th><?php _e('Last Visit', 'paid-memberships-pro' )?></th>
172
<th><?php _e('Total Views', 'paid-memberships-pro' )?></th>
173
<th><?php _e('Last Login', 'paid-memberships-pro' )?></th>
174
<th><?php _e('Logins This Month', 'paid-memberships-pro' )?></th>
175
+ <th><?php _e('Total Logins', 'paid-memberships-pro' )?></th>
176
</tr>
177
</thead>
178
+ <tbody id="users" class="list:user user-list">
179
+ <?php
180
+ $count = 0;
181
foreach($theusers as $auser)
182
{
183
+ //get meta
184
$theuser = get_userdata($auser->ID);
185
$visits = get_user_meta($auser->ID, "pmpro_visits", true);
186
$views = get_user_meta($auser->ID, "pmpro_views", true);
197
$userlink = '<a href="user-edit.php?user_id=' . $theuser->ID . '">' . $theuser->user_login . '</a>';
198
$userlink = apply_filters("pmpro_members_list_user_link", $userlink, $theuser);
199
echo $userlink;
200
+ ?>
201
</strong>
202
+ </td>
203
<td>
204
<?php echo $theuser->display_name;?>
205
</td>
206
+ <td><?php echo $auser->membership?></td>
207
<td><?php echo date_i18n("m/d/Y", strtotime($theuser->user_registered, current_time("timestamp")))?></td>
208
<td>
209
+ <?php
210
+ if($auser->enddate)
211
echo date_i18n(get_option('date_format'), $auser->enddate);
212
else
213
echo "Never";
215
</td>
216
<td><?php if(!empty($visits['last'])) echo $visits['last'];?></td>
217
<td><?php if(!empty($visits['month']) && pmpro_isDateThisMonth($visits['last'])) echo $visits['month'];?></td>
218
+ <td><?php if(!empty($visits['alltime'])) echo $visits['alltime'];?></td>
219
<td><?php if(!empty($views['month']) && pmpro_isDateThisMonth($views['last'])) echo $views['month'];?></td>
220
<td><?php if(!empty($views['alltime'])) echo $views['alltime'];?></td>
221
<td><?php if(!empty($logins['last'])) echo $logins['last'];?></td>
224
</tr>
225
<?php
226
}
227
+
228
if(!$theusers)
229
{
230
?>
233
</tr>
234
<?php
235
}
236
+ ?>
237
</tbody>
238
</table>
239
</form>
254
//don't track admin
255
if(is_admin())
256
return;
257
+
258
//only track logged in users
259
if(!is_user_logged_in())
260
return;
261
+
262
//check for cookie
263
if(!empty($_COOKIE['pmpro_visit']))
264
return;
265
+
266
$now = current_time('timestamp');
267
+
268
//set cookie, then track
269
+ setcookie("pmpro_visit", "1", NULL, COOKIEPATH, COOKIE_DOMAIN, false);
270
+
271
global $current_user;
272
//track for user
273
if(!empty($current_user->ID))
274
+ {
275
+ $visits = $current_user->pmpro_visits;
276
if(empty($visits))
277
$visits = array("last"=>"N/A", "thisdate"=>NULL, "month"=>0, "thismonth"=>NULL, "alltime"=>0);
278
+
279
//track logins for user
280
$visits['last'] = date_i18n(get_option("date_format"), $now);
281
$visits['alltime'] = $visits['alltime'] + 1; // BUG FIX: Caused fatal error in certain PHP versions
287
$visits['month'] = 1;
288
$visits['thismonth'] = $thismonth;
289
}
290
+
291
//update user data
292
update_user_meta($current_user->ID, "pmpro_visits", $visits);
293
}
294
+
295
//track for all
296
+ $visits = get_option("pmpro_visits");
297
if(empty($visits))
298
$visits = array("today"=>0, "thisdate"=>NULL, "month"=>0, "thismonth"=> NULL, "alltime"=>0);
299
+
300
$visits['alltime'] = $visits['alltime'] + 1; // BUG FIX: Caused fatal error in certain PHP versions
301
$thisdate = date_i18n("Y-d-m", $now);
302
if($thisdate == $visits['thisdate'])
313
$visits['month'] = 1;
314
$visits['thismonth'] = $thismonth;
315
}
316
+
317
+ update_option("pmpro_visits", $visits);
318
}
319
add_action("wp", "pmpro_report_login_wp_visits");
320
333
//don't track admin
334
if(is_admin())
335
return;
336
+
337
global $current_user;
338
$now = current_time('timestamp');
339
+
340
//track for user
341
if(!empty($current_user->ID))
342
+ {
343
+ $views = $current_user->pmpro_views;
344
if(empty($views))
345
$views = array("last"=>"N/A", "month"=>0, "alltime"=>0);
346
+
347
//track logins for user
348
$views['last'] = date_i18n(get_option("date_format"), $now);
349
$views['alltime'] = $views['alltime'] + 1;
355
$views['month'] = 1;
356
$views['thismonth'] = $thismonth;
357
}
358
+
359
//update user data
360
update_user_meta($current_user->ID, "pmpro_views", $views);
361
}
362
+
363
//track for all
364
+ $views = get_option("pmpro_views");
365
if(empty($views))
366
$views = array("today"=>0, "thisdate"=> NULL, "month"=>0, "thismonth"=> NULL, "alltime"=>0);
367
+
368
$views['alltime'] = $views['alltime'] + 1;
369
$thisdate = date_i18n("Y-d-m", $now);
370
if($thisdate == $views['thisdate'])
382
$views['month'] = 1;
383
$views['thismonth'] = $thismonth;
384
}
385
+
386
+ update_option("pmpro_views", $views);
387
}
388
add_action("wp_head", "pmpro_report_login_wp_views");
389
391
function pmpro_report_login_wp_login($user_login)
392
{
393
$now = current_time('timestamp');
394
+
395
//get user data
396
+ $user = get_user_by("login", $user_login);
397
$logins = $user->pmpro_logins;
398
if(empty($logins))
399
$logins = array("last"=>"N/A", "thisdate"=>NULL, "month"=>0, "thismonth"=> NULL, "alltime"=>0);
400
+
401
//track logins for user
402
$logins['last'] = date_i18n(get_option("date_format"), $now);
403
$logins['alltime'] = $logins['alltime'] + 1;
405
if($thismonth == $logins['thismonth'])
406
$logins['month'] = $logins['month'] + 1;
407
else
408
+ {
409
$logins['month'] = 1;
410
$logins['thismonth'] = $thismonth;
411
}
412
+
413
//update user data
414
update_user_meta($user->ID, "pmpro_logins", $logins);
415
+
416
//track logins overall
417
$logins = get_option("pmpro_logins");
418
if(empty($logins))
419
$logins = array("today"=>0, "thisdate"=>NULL, "month"=>0, "thismonth"=>NULL, "alltime"=>0);
420
+
421
$logins['alltime'] = $logins['alltime'] + 1;
422
$thisdate = date_i18n("Y-d-m", $now);
423
if($thisdate == $logins['thisdate'])
434
$logins['month'] = 1;
435
$logins['thismonth'] = $thismonth;
436
}
437
+
438
+ update_option("pmpro_logins", $logins);
439
}
440
add_action("wp_login", "pmpro_report_login_wp_login");
adminpages/reports/memberships.php CHANGED
@@ -3,11 +3,11 @@
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.
@@ -21,7 +21,7 @@ $pmpro_reports['memberships'] = __('Membership Stats', 'paid-memberships-pro' );
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', plugins_url( 'js/jsapi.js', plugin_dir_path( __DIR__ ) ) );
24
-
25
}
26
}
27
add_action( 'init', 'pmpro_report_memberships_init' );
@@ -30,10 +30,10 @@ add_action( 'init', 'pmpro_report_memberships_init' );
30
//widget
31
function pmpro_report_memberships_widget() {
32
global $wpdb;
33
-
34
//get levels to show stats on first 3
35
$pmpro_levels = pmpro_getAllLevels(true, true);
36
-
37
$pmpro_level_order = pmpro_getOption('level_order');
38
39
if(!empty($pmpro_level_order))
@@ -51,10 +51,10 @@ function pmpro_report_memberships_widget() {
51
52
$pmpro_levels = $reordered_levels;
53
}
54
-
55
$pmpro_levels = apply_filters( 'pmpro_report_levels', $pmpro_levels );
56
?>
57
- <span id="pmpro_report_memberships">
58
<table class="wp-list-table widefat fixed striped">
59
<thead>
60
<tr>
@@ -70,7 +70,7 @@ function pmpro_report_memberships_widget() {
70
'this year'=> __('This Year', 'paid-memberships-pro' ),
71
'all time'=> __('All Time', 'paid-memberships-pro' ),
72
);
73
-
74
foreach($reports as $report_type => $report_name) {
75
?>
76
<tbody>
@@ -83,8 +83,8 @@ function pmpro_report_memberships_widget() {
83
//level stats
84
$count = 0;
85
$max_level_count = apply_filters( 'pmpro_admin_reports_included_levels', 3 );
86
-
87
- foreach($pmpro_levels as $level) {
88
if($count++ >= $max_level_count) break;
89
?>
90
<tr class="pmpro_report_tr_sub" style="display: none;">
@@ -92,8 +92,8 @@ function pmpro_report_memberships_widget() {
92
<td><?php echo number_format_i18n(pmpro_getSignups($report_type, $level->id)); ?></td>
93
<td><?php echo number_format_i18n(pmpro_getCancellations($report_type, $level->id)); ?></td>
94
</tr>
95
- <?php
96
- }
97
?>
98
</tbody>
99
<?php
@@ -106,7 +106,7 @@ function pmpro_report_memberships_widget() {
106
jQuery('.pmpro_report_th ').click(function() {
107
//toggle sub rows
108
jQuery(this).closest('tbody').find('.pmpro_report_tr_sub').toggle();
109
-
110
//change arrow
111
if(jQuery(this).hasClass('pmpro_report_th_closed')) {
112
jQuery(this).removeClass('pmpro_report_th_closed');
@@ -124,18 +124,18 @@ function pmpro_report_memberships_widget() {
124
function pmpro_report_memberships_page()
125
{
126
global $wpdb, $pmpro_currency_symbol;
127
-
128
//get values from form
129
if(isset($_REQUEST['type']))
130
$type = sanitize_text_field($_REQUEST['type']);
131
else
132
$type = "signup_v_all";
133
-
134
if(isset($_REQUEST['period']))
135
$period = sanitize_text_field($_REQUEST['period']);
136
else
137
$period = "monthly";
138
-
139
if(isset($_REQUEST['month']))
140
$month = intval($_REQUEST['month']);
141
else
@@ -146,17 +146,17 @@ function pmpro_report_memberships_page()
146
$year = intval($_REQUEST['year']);
147
else
148
$year = date_i18n("Y");
149
-
150
if(isset($_REQUEST['level']))
151
$l = intval($_REQUEST['level']);
152
else
153
$l = "";
154
-
155
//calculate start date and how to group dates returned from DB
156
if($period == "daily")
157
{
158
- $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
159
- $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
160
$date_function = 'DAY';
161
}
162
elseif($period == "monthly")
@@ -171,47 +171,47 @@ function pmpro_report_memberships_page()
171
$enddate = strval(intval($year)+1) . '-01-01';
172
$date_function = 'YEAR';
173
}
174
-
175
//testing or live data
176
$gateway_environment = pmpro_getOption("gateway_environment");
177
-
178
//get data
179
if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all") {
180
$sqlQuery = "SELECT $date_function(startdate) as date, COUNT(DISTINCT user_id) as signups
181
- FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . $startdate . "' ";
182
183
if(!empty($enddate))
184
- $sqlQuery .= "AND startdate < '" . $enddate . "' ";
185
}
186
if ( $type === "mrr_ltv" ) {
187
// Get total revenue, number of months in system, and date
188
if ( $period == 'annual' )
189
$sqlQuery = "SELECT SUM(total) as total, COUNT(DISTINCT MONTH(timestamp)) as months, $date_function(timestamp) as date
190
FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
191
- AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
192
193
if ( $period == 'monthly' )
194
$sqlQuery = "SELECT SUM(total) as total, $date_function(timestamp) as date
195
FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
196
- AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
197
198
if(!empty($enddate))
199
- $sqlQuery .= "AND timestamp < '" . $enddate . "' ";
200
}
201
-
202
if(!empty($l))
203
- $sqlQuery .= "AND membership_id IN(" . $l . ") ";
204
205
$sqlQuery .= " GROUP BY date ORDER BY date ";
206
207
$dates = $wpdb->get_results($sqlQuery);
208
-
209
//fill in blanks in dates
210
- $cols = array();
211
if($period == "daily")
212
{
213
$lastday = date_i18n("t", strtotime($startdate, current_time("timestamp")));
214
-
215
for($i = 1; $i <= $lastday; $i++)
216
{
217
// Signups vs. Cancellations, Expirations, or All
@@ -262,7 +262,7 @@ function pmpro_report_memberships_page()
262
elseif($period == "annual") //annual
263
{
264
}
265
-
266
$dates = ( ! empty( $cols ) ) ? $cols : $dates;
267
268
// Signups vs. all
@@ -276,14 +276,14 @@ function pmpro_report_memberships_page()
276
$sqlQuery .= "WHERE mu1.status IN('expired') ";
277
else
278
$sqlQuery .= "WHERE mu1.status IN('inactive','expired','cancelled','admin_cancelled') ";
279
-
280
- $sqlQuery .= "AND mu1.startdate >= '" . $startdate . "'
281
- AND mu1.startdate < '" . $enddate . "' ";
282
-
283
//restrict by level
284
if(!empty($l))
285
- $sqlQuery .= "AND mu1.membership_id IN(" . $l . ") ";
286
-
287
$sqlQuery .= " GROUP BY date ORDER BY date ";
288
289
/**
@@ -298,9 +298,9 @@ function pmpro_report_memberships_page()
298
* @param int $l Level ID
299
*/
300
$sqlQuery = apply_filters('pmpro_reports_signups_sql', $sqlQuery, $type, $startdate, $enddate, $l);
301
-
302
- $cdates = $wpdb->get_results($sqlQuery, OBJECT_K);
303
-
304
foreach( $dates as $day => &$date )
305
{
306
if(!empty($cdates) && !empty($cdates[$day]))
@@ -315,11 +315,11 @@ function pmpro_report_memberships_page()
315
$dummy_date = new stdClass();
316
$dummy_date->total = 0;
317
$dummy_date->months = 0;
318
- $dummy_date->date = $dates[0]->date - 1;
319
array_unshift( $dates, $dummy_date ); // Add to beginning
320
}
321
?>
322
- <form id="posts-filter" method="get" action="">
323
<h1>
324
<?php _e('Membership Stats', 'paid-memberships-pro' );?>
325
</h1>
@@ -363,15 +363,15 @@ function pmpro_report_memberships_page()
363
}
364
?>
365
</select>
366
-
367
- <input type="hidden" name="page" value="pmpro-reports" />
368
- <input type="hidden" name="report" value="memberships" />
369
<input type="submit" class="button" value="<?php _e('Generate Report', 'paid-memberships-pro' );?>" />
370
</li>
371
</ul>
372
-
373
- <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
374
-
375
<script>
376
//update month/year when period dropdown is changed
377
jQuery(document).ready(function() {
@@ -379,7 +379,7 @@ function pmpro_report_memberships_page()
379
pmpro_ShowMonthOrYear();
380
});
381
});
382
-
383
function pmpro_ShowMonthOrYear()
384
{
385
var period = jQuery('#period').val();
@@ -402,14 +402,14 @@ function pmpro_report_memberships_page()
402
jQuery('#year').hide();
403
}
404
}
405
-
406
pmpro_ShowMonthOrYear();
407
-
408
//draw the chart
409
google.load("visualization", "1", {packages:["corechart"]});
410
google.setOnLoadCallback(drawChart);
411
- function drawChart() {
412
-
413
var data = google.visualization.arrayToDataTable([
414
<?php if ( $type === "signup_v_all" ) : // Signups vs. all cancellations ?>
415
['<?php echo $date_function;?>', 'Signups', 'All Cancellations'],
@@ -417,14 +417,14 @@ function pmpro_report_memberships_page()
417
['<?php if($period == "monthly") echo date_i18n("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; ?>],
418
<?php } ?>
419
<?php endif; ?>
420
-
421
<?php if ( $type === "signup_v_cancel" ) : // Signups vs. cancellations ?>
422
['<?php echo $date_function;?>', 'Signups', 'Cancellations'],
423
<?php foreach($dates as $key => $value) { ?>
424
['<?php if($period == "monthly") echo date_i18n("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; ?>],
425
<?php } ?>
426
<?php endif; ?>
427
-
428
<?php if ( $type === "signup_v_expiration" ) : // Signups vs. expirations ?>
429
['<?php echo $date_function;?>', 'Signups', 'Expirations'],
430
<?php foreach($dates as $key => $value) { ?>
@@ -440,17 +440,17 @@ function pmpro_report_memberships_page()
440
<?php endif; ?>
441
]);
442
443
- var options = {
444
colors: ['#0099c6', '#dc3912'],
445
hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
446
- vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
447
};
448
449
<?php if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all" ) : // Signups vs. cancellations ?>
450
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
451
-
452
<?php elseif ( $type === "mrr_ltv" ) : // MRR & LTV ?>
453
-
454
<?php
455
//prefix or suffix?
456
if(pmpro_getCurrencyPosition() == "right")
@@ -458,7 +458,7 @@ function pmpro_report_memberships_page()
458
else
459
$position = "prefix";
460
?>
461
-
462
var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
463
formatter.format(data, 2);
464
var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
@@ -469,7 +469,7 @@ function pmpro_report_memberships_page()
469
chart.draw(data, options);
470
}
471
</script>
472
-
473
</form>
474
<?php
475
}
@@ -487,7 +487,7 @@ function pmpro_getSignups($period = false, $levels = 'all')
487
$cache = get_transient( 'pmpro_report_memberships_signups' );
488
if( ! empty( $cache ) && ! empty( $cache[$period] ) && ! empty( $cache[$period][$levels] ) )
489
return $cache[$period][$levels];
490
-
491
//a sale is an order with status = success
492
if( $period == 'today' )
493
$startdate = date_i18n(' Y-m-d' );
@@ -498,18 +498,18 @@ function pmpro_getSignups($period = false, $levels = 'all')
498
else
499
$startdate = '';
500
501
-
502
//build query
503
global $wpdb;
504
505
- $sqlQuery = "SELECT COUNT(DISTINCT user_id) FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . $startdate . "' ";
506
507
//restrict by level
508
if(!empty($levels) && $levels != 'all')
509
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
510
-
511
$signups = $wpdb->get_var($sqlQuery);
512
-
513
//save in cache
514
if(!empty($cache) && !empty($cache[$period]))
515
$cache[$period][$levels] = $signups;
@@ -517,9 +517,9 @@ function pmpro_getSignups($period = false, $levels = 'all')
517
$cache[$period] = array($levels => $signups);
518
else
519
$cache = array($period => array($levels => $signups));
520
-
521
set_transient("pmpro_report_memberships_signups", $cache, 3600*24);
522
-
523
return $signups;
524
}
525
@@ -569,7 +569,7 @@ function pmpro_getCancellations($period = null, $levels = 'all', $status = array
569
$startdate = '1970-01-01'; //all time (no point in using a value prior to the start of the UNIX epoch)
570
$enddate = "'".strval(intval($year)+1) . "-01-01'";
571
}
572
-
573
/*
574
build query.
575
cancellations are marked in the memberships users table with status 'inactive', 'expired', 'cancelled', 'admin_cancelled'
@@ -579,12 +579,12 @@ function pmpro_getCancellations($period = null, $levels = 'all', $status = array
579
580
$sqlQuery = "
581
SELECT COUNT( DISTINCT mu1.user_id )
582
- FROM {$wpdb->pmpro_memberships_users} AS mu1
583
- WHERE mu1.status IN('" . implode("','", $status) . "')
584
- AND mu1.enddate >= '" . $startdate . "'
585
- AND mu1.enddate <= " . $enddate . "
586
";
587
-
588
//restrict by level
589
if(!empty($levels) && $levels != 'all') {
590
@@ -594,9 +594,9 @@ function pmpro_getCancellations($period = null, $levels = 'all', $status = array
594
$levels = array($levels);
595
}
596
597
- $sqlQuery .= "AND mu1.membership_id IN(" . implode(", ", $levels) . ") ";
598
}
599
-
600
/**
601
* Filter query to get cancellation numbers in signups vs cancellations detailed report.
602
*
@@ -608,9 +608,9 @@ function pmpro_getCancellations($period = null, $levels = 'all', $status = array
608
* @param array(string) $status Statuses to include as cancelled.
609
*/
610
$sqlQuery = apply_filters('pmpro_reports_get_cancellations_sql', $sqlQuery, $period, $levels, $status);
611
-
612
$cancellations = $wpdb->get_var($sqlQuery);
613
-
614
//save in cache
615
if(!empty($cache) && !empty($cache[$hash]))
616
$cache[$hash] = $cancellations;
@@ -618,9 +618,9 @@ function pmpro_getCancellations($period = null, $levels = 'all', $status = array
618
$cache[$hash] = $cancellations;
619
else
620
$cache = array($hash => $cancellations);
621
-
622
set_transient("pmpro_report_memberships_cancellations", $cache, 3600*24);
623
-
624
return $cancellations;
625
}
626
@@ -630,8 +630,8 @@ function pmpro_getMRR($period, $levels = 'all')
630
//check for a transient
631
//$cache = get_transient("pmpro_report_mrr");
632
if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
633
- return $cache[$period][$levels];
634
-
635
//a sale is an order with status NOT IN refunded, review, token, error
636
if($period == "this month")
637
$startdate = date_i18n("Y-m") . "-01";
@@ -639,31 +639,31 @@ function pmpro_getMRR($period, $levels = 'all')
639
$startdate = date_i18n("Y") . "-01-01";
640
else
641
$startdate = "";
642
-
643
$gateway_environment = pmpro_getOption("gateway_environment");
644
-
645
//build query
646
global $wpdb;
647
// Get total revenue
648
- $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) . "' ";
649
650
//restrict by level
651
if(!empty($levels) && $levels != 'all') {
652
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
653
}
654
-
655
$revenue = $wpdb->get_var($sqlQuery);
656
-
657
//when was the first order
658
$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");
659
-
660
//if we don't have a timestamp, we can't do this
661
if(empty($first_order_timestamp))
662
return false;
663
-
664
//how many months ago was the first order
665
- $months = $wpdb->get_var("SELECT PERIOD_DIFF('" . date_i18n("Ym") . "', '" . date_i18n("Ym", $first_order_timestamp) . "')");
666
-
667
/* this works in PHP 5.3+ without using MySQL to get the diff
668
$date1 = new DateTime(date_i18n("Y-m-d", $first_order_timestamp));
669
$date2 = new DateTime(date_i18n("Y-m-d"));
@@ -671,12 +671,12 @@ function pmpro_getMRR($period, $levels = 'all')
671
$years = intval($interval->format('%y'));
672
$months = $years*12 + intval($interval->format('%m'));
673
*/
674
-
675
if($months > 0)
676
$mrr = $revenue / $months;
677
else
678
$mrr = 0;
679
-
680
//save in cache
681
if(!empty($cache) && !empty($cache[$period]))
682
$cache[$period][$levels] = $mrr;
@@ -684,15 +684,15 @@ function pmpro_getMRR($period, $levels = 'all')
684
$cache[$period] = array($levels => $mrr);
685
else
686
$cache = array($period => array($levels => $mrr));
687
-
688
set_transient("pmpro_report_mrr", $cache, 3600*24);
689
-
690
return $mrr;
691
}
692
693
//get Cancellation Rate
694
function pmpro_getCancellationRate($period, $levels = 'all', $status = NULL)
695
- {
696
//make sure status is an array
697
if(!is_array($status))
698
$status = array($status);
@@ -702,15 +702,15 @@ function pmpro_getCancellationRate($period, $levels = 'all', $status = NULL)
702
$hash = md5($period . $levels . implode('',$status));
703
if(!empty($cache) && !empty($cache[$hash]))
704
return $cache[$hash];
705
-
706
$signups = pmpro_getSignups($period, $levels);
707
$cancellations = pmpro_getCancellations($period, $levels, $status);
708
-
709
if(empty($signups))
710
return false;
711
-
712
$rate = number_format(($cancellations / $signups)*100, 2);
713
-
714
//save in cache
715
if(!empty($cache) && !empty($cache[$period]))
716
$cache[$period][$levels] = $rate;
@@ -718,7 +718,7 @@ function pmpro_getCancellationRate($period, $levels = 'all', $status = NULL)
718
$cache[$period] = array($levels => $rate);
719
else
720
$cache = array($period => array($levels => $rate));
721
-
722
set_transient("pmpro_report_cancellation_rate", $cache, 3600*24);
723
724
return $rate;
@@ -726,23 +726,23 @@ function pmpro_getCancellationRate($period, $levels = 'all', $status = NULL)
726
727
//get LTV
728
function pmpro_getLTV($period, $levels = 'all', $mrr = NULL, $signups = NULL, $cancellation_rate = NULL)
729
- {
730
if(empty($mrr))
731
$mrr = pmpro_getMRR($period, $levels);
732
if(empty($signups))
733
$signups = pmpro_getSignups($period, $levels);
734
if(empty($cancellation_rate))
735
$cancellation_rate = pmpro_getCancellationRate($period, $levels);
736
-
737
//average monthly spend
738
if(empty($signups))
739
return false;
740
-
741
if($signups > 0)
742
$ams = $mrr / $signups;
743
else
744
$ams = 0;
745
-
746
if($cancellation_rate > 0)
747
$ltv = $ams * (1/$cancellation_rate);
748
else
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.
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', plugins_url( 'js/jsapi.js', plugin_dir_path( __DIR__ ) ) );
24
+
25
}
26
}
27
add_action( 'init', 'pmpro_report_memberships_init' );
30
//widget
31
function pmpro_report_memberships_widget() {
32
global $wpdb;
33
+
34
//get levels to show stats on first 3
35
$pmpro_levels = pmpro_getAllLevels(true, true);
36
+
37
$pmpro_level_order = pmpro_getOption('level_order');
38
39
if(!empty($pmpro_level_order))
51
52
$pmpro_levels = $reordered_levels;
53
}
54
+
55
$pmpro_levels = apply_filters( 'pmpro_report_levels', $pmpro_levels );
56
?>
57
+ <span id="pmpro_report_memberships">
58
<table class="wp-list-table widefat fixed striped">
59
<thead>
60
<tr>
70
'this year'=> __('This Year', 'paid-memberships-pro' ),
71
'all time'=> __('All Time', 'paid-memberships-pro' ),
72
);
73
+
74
foreach($reports as $report_type => $report_name) {
75
?>
76
<tbody>
83
//level stats
84
$count = 0;
85
$max_level_count = apply_filters( 'pmpro_admin_reports_included_levels', 3 );
86
+
87
+ foreach($pmpro_levels as $level) {
88
if($count++ >= $max_level_count) break;
89
?>
90
<tr class="pmpro_report_tr_sub" style="display: none;">
92
<td><?php echo number_format_i18n(pmpro_getSignups($report_type, $level->id)); ?></td>
93
<td><?php echo number_format_i18n(pmpro_getCancellations($report_type, $level->id)); ?></td>
94
</tr>
95
+ <?php
96
+ }
97
?>
98
</tbody>
99
<?php
106
jQuery('.pmpro_report_th ').click(function() {
107
//toggle sub rows
108
jQuery(this).closest('tbody').find('.pmpro_report_tr_sub').toggle();
109
+
110
//change arrow
111
if(jQuery(this).hasClass('pmpro_report_th_closed')) {
112
jQuery(this).removeClass('pmpro_report_th_closed');
124
function pmpro_report_memberships_page()
125
{
126
global $wpdb, $pmpro_currency_symbol;
127
+
128
//get values from form
129
if(isset($_REQUEST['type']))
130
$type = sanitize_text_field($_REQUEST['type']);
131
else
132
$type = "signup_v_all";
133
+
134
if(isset($_REQUEST['period']))
135
$period = sanitize_text_field($_REQUEST['period']);
136
else
137
$period = "monthly";
138
+
139
if(isset($_REQUEST['month']))
140
$month = intval($_REQUEST['month']);
141
else
146
$year = intval($_REQUEST['year']);
147
else
148
$year = date_i18n("Y");
149
+
150
if(isset($_REQUEST['level']))
151
$l = intval($_REQUEST['level']);
152
else
153
$l = "";
154
+
155
//calculate start date and how to group dates returned from DB
156
if($period == "daily")
157
{
158
+ $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
159
+ $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
160
$date_function = 'DAY';
161
}
162
elseif($period == "monthly")
171
$enddate = strval(intval($year)+1) . '-01-01';
172
$date_function = 'YEAR';
173
}
174
+
175
//testing or live data
176
$gateway_environment = pmpro_getOption("gateway_environment");
177
+
178
//get data
179
if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all") {
180
$sqlQuery = "SELECT $date_function(startdate) as date, COUNT(DISTINCT user_id) as signups
181
+ FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . esc_sql( $startdate ) . "' ";
182
183
if(!empty($enddate))
184
+ $sqlQuery .= "AND startdate < '" . esc_sql( $enddate ) . "' ";
185
}
186
if ( $type === "mrr_ltv" ) {
187
// Get total revenue, number of months in system, and date
188
if ( $period == 'annual' )
189
$sqlQuery = "SELECT SUM(total) as total, COUNT(DISTINCT MONTH(timestamp)) as months, $date_function(timestamp) as date
190
FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
191
+ AND timestamp >= '" . esc_sql( $startdate ) . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
192
193
if ( $period == 'monthly' )
194
$sqlQuery = "SELECT SUM(total) as total, $date_function(timestamp) as date
195
FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token')
196
+ AND timestamp >= '" . esc_sql( $startdate ) . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
197
198
if(!empty($enddate))
199
+ $sqlQuery .= "AND timestamp < '" . esc_sql( $enddate ) . "' ";
200
}
201
+
202
if(!empty($l))
203
+ $sqlQuery .= "AND membership_id IN(" . esc_sql( $l ) . ") ";
204
205
$sqlQuery .= " GROUP BY date ORDER BY date ";
206
207
$dates = $wpdb->get_results($sqlQuery);
208
+
209
//fill in blanks in dates
210
+ $cols = array();
211
if($period == "daily")
212
{
213
$lastday = date_i18n("t", strtotime($startdate, current_time("timestamp")));
214
+
215
for($i = 1; $i <= $lastday; $i++)
216
{
217
// Signups vs. Cancellations, Expirations, or All
262
elseif($period == "annual") //annual
263
{
264
}
265
+
266
$dates = ( ! empty( $cols ) ) ? $cols : $dates;
267
268
// Signups vs. all
276
$sqlQuery .= "WHERE mu1.status IN('expired') ";
277
else
278
$sqlQuery .= "WHERE mu1.status IN('inactive','expired','cancelled','admin_cancelled') ";
279
+
280
+ $sqlQuery .= "AND mu1.startdate >= '" . esc_sql( $startdate ) . "'
281
+ AND mu1.startdate < '" . esc_sql( $enddate ) . "' ";
282
+
283
//restrict by level
284
if(!empty($l))
285
+ $sqlQuery .= "AND mu1.membership_id IN(" . esc_sql( $l ) . ") ";
286
+
287
$sqlQuery .= " GROUP BY date ORDER BY date ";
288
289
/**
298
* @param int $l Level ID
299
*/
300
$sqlQuery = apply_filters('pmpro_reports_signups_sql', $sqlQuery, $type, $startdate, $enddate, $l);
301
+
302
+ $cdates = $wpdb->get_results($sqlQuery, OBJECT_K);
303
+
304
foreach( $dates as $day => &$date )
305
{
306
if(!empty($cdates) && !empty($cdates[$day]))
315
$dummy_date = new stdClass();
316
$dummy_date->total = 0;
317
$dummy_date->months = 0;
318
+ $dummy_date->date = $dates[0]->date - 1;
319
array_unshift( $dates, $dummy_date ); // Add to beginning
320
}
321
?>
322
+ <form id="posts-filter" method="get" action="">
323
<h1>
324
<?php _e('Membership Stats', 'paid-memberships-pro' );?>
325
</h1>
363
}
364
?>
365
</select>
366
+
367
+ <input type="hidden" name="page" value="pmpro-reports" />
368
+ <input type="hidden" name="report" value="memberships" />
369
<input type="submit" class="button" value="<?php _e('Generate Report', 'paid-memberships-pro' );?>" />
370
</li>
371
</ul>
372
+
373
+ <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
374
+
375
<script>
376
//update month/year when period dropdown is changed
377
jQuery(document).ready(function() {
379
pmpro_ShowMonthOrYear();
380
});
381
});
382
+
383
function pmpro_ShowMonthOrYear()
384
{
385
var period = jQuery('#period').val();
402
jQuery('#year').hide();
403
}
404
}
405
+
406
pmpro_ShowMonthOrYear();
407
+
408
//draw the chart
409
google.load("visualization", "1", {packages:["corechart"]});
410
google.setOnLoadCallback(drawChart);
411
+ function drawChart() {
412
+
413
var data = google.visualization.arrayToDataTable([
414
<?php if ( $type === "signup_v_all" ) : // Signups vs. all cancellations ?>
415
['<?php echo $date_function;?>', 'Signups', 'All Cancellations'],
417
['<?php if($period == "monthly") echo date_i18n("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; ?>],
418
<?php } ?>
419
<?php endif; ?>
420
+
421
<?php if ( $type === "signup_v_cancel" ) : // Signups vs. cancellations ?>
422
['<?php echo $date_function;?>', 'Signups', 'Cancellations'],
423
<?php foreach($dates as $key => $value) { ?>
424
['<?php if($period == "monthly") echo date_i18n("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; ?>],
425
<?php } ?>
426
<?php endif; ?>
427
+
428
<?php if ( $type === "signup_v_expiration" ) : // Signups vs. expirations ?>
429
['<?php echo $date_function;?>', 'Signups', 'Expirations'],
430
<?php foreach($dates as $key => $value) { ?>
440
<?php endif; ?>
441
]);
442
443
+ var options = {
444
colors: ['#0099c6', '#dc3912'],
445
hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
446
+ vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
447
};
448
449
<?php if ( $type === "signup_v_cancel" || $type === "signup_v_expiration" || $type === "signup_v_all" ) : // Signups vs. cancellations ?>
450
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
451
+
452
<?php elseif ( $type === "mrr_ltv" ) : // MRR & LTV ?>
453
+
454
<?php
455
//prefix or suffix?
456
if(pmpro_getCurrencyPosition() == "right")
458
else
459
$position = "prefix";
460
?>
461
+
462
var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
463
formatter.format(data, 2);
464
var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
469
chart.draw(data, options);
470
}
471
</script>
472
+
473
</form>
474
<?php
475
}
487
$cache = get_transient( 'pmpro_report_memberships_signups' );
488
if( ! empty( $cache ) && ! empty( $cache[$period] ) && ! empty( $cache[$period][$levels] ) )
489
return $cache[$period][$levels];
490
+
491
//a sale is an order with status = success
492
if( $period == 'today' )
493
$startdate = date_i18n(' Y-m-d' );
498
else
499
$startdate = '';
500
501
+
502
//build query
503
global $wpdb;
504
505
+ $sqlQuery = "SELECT COUNT(DISTINCT user_id) FROM $wpdb->pmpro_memberships_users WHERE startdate >= '" . esc_sql( $startdate ) . "' ";
506
507
//restrict by level
508
if(!empty($levels) && $levels != 'all')
509
+ $sqlQuery .= "AND membership_id IN(" . esc_sql( $levels ) . ") ";
510
+
511
$signups = $wpdb->get_var($sqlQuery);
512
+
513
//save in cache
514
if(!empty($cache) && !empty($cache[$period]))
515
$cache[$period][$levels] = $signups;
517
$cache[$period] = array($levels => $signups);
518
else
519
$cache = array($period => array($levels => $signups));
520
+
521
set_transient("pmpro_report_memberships_signups", $cache, 3600*24);
522
+
523
return $signups;
524
}
525
569
$startdate = '1970-01-01'; //all time (no point in using a value prior to the start of the UNIX epoch)
570
$enddate = "'".strval(intval($year)+1) . "-01-01'";
571
}
572
+
573
/*
574
build query.
575
cancellations are marked in the memberships users table with status 'inactive', 'expired', 'cancelled', 'admin_cancelled'
579
580
$sqlQuery = "
581
SELECT COUNT( DISTINCT mu1.user_id )
582
+ FROM {$wpdb->pmpro_memberships_users} AS mu1
583
+ WHERE mu1.status IN('" . esc_sql( implode( "','", $status ) ) . "')
584
+ AND mu1.enddate >= '" . esc_sql( $startdate ) . "'
585
+ AND mu1.enddate <= " . esc_sql( $enddate ) . "
586
";
587
+
588
//restrict by level
589
if(!empty($levels) && $levels != 'all') {
590
594
$levels = array($levels);
595
}
596
597
+ $sqlQuery .= "AND mu1.membership_id IN(" . esc_sql( implode( ", ", $levels ) ) . ") ";
598
}
599
+
600
/**
601
* Filter query to get cancellation numbers in signups vs cancellations detailed report.
602
*
608
* @param array(string) $status Statuses to include as cancelled.
609
*/
610
$sqlQuery = apply_filters('pmpro_reports_get_cancellations_sql', $sqlQuery, $period, $levels, $status);
611
+
612
$cancellations = $wpdb->get_var($sqlQuery);
613
+
614
//save in cache
615
if(!empty($cache) && !empty($cache[$hash]))
616
$cache[$hash] = $cancellations;
618
$cache[$hash] = $cancellations;
619
else
620
$cache = array($hash => $cancellations);
621
+
622
set_transient("pmpro_report_memberships_cancellations", $cache, 3600*24);
623
+
624
return $cancellations;
625
}
626
630
//check for a transient
631
//$cache = get_transient("pmpro_report_mrr");
632
if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
633
+ return $cache[$period][$levels];
634
+
635
//a sale is an order with status NOT IN refunded, review, token, error
636
if($period == "this month")
637
$startdate = date_i18n("Y-m") . "-01";
639
$startdate = date_i18n("Y") . "-01-01";
640
else
641
$startdate = "";
642
+
643
$gateway_environment = pmpro_getOption("gateway_environment");
644
+
645
//build query
646
global $wpdb;
647
// Get total revenue
648
+ $sqlQuery = "SELECT SUM(total) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . esc_sql( $startdate ) . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
649
650
//restrict by level
651
if(!empty($levels) && $levels != 'all') {
652
+ $sqlQuery .= "AND membership_id IN(" . esc_sql( $levels ) . ") ";
653
}
654
+
655
$revenue = $wpdb->get_var($sqlQuery);
656
+
657
//when was the first order
658
$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");
659
+
660
//if we don't have a timestamp, we can't do this
661
if(empty($first_order_timestamp))
662
return false;
663
+
664
//how many months ago was the first order
665
+ $months = $wpdb->get_var("SELECT PERIOD_DIFF('" . esc_sql( date_i18n("Ym") ) . "', '" . esc_sql( date_i18n("Ym", $first_order_timestamp ) ) . "')");
666
+
667
/* this works in PHP 5.3+ without using MySQL to get the diff
668
$date1 = new DateTime(date_i18n("Y-m-d", $first_order_timestamp));
669
$date2 = new DateTime(date_i18n("Y-m-d"));
671
$years = intval($interval->format('%y'));
672
$months = $years*12 + intval($interval->format('%m'));
673
*/
674
+
675
if($months > 0)
676
$mrr = $revenue / $months;
677
else
678
$mrr = 0;
679
+
680
//save in cache
681
if(!empty($cache) && !empty($cache[$period]))
682
$cache[$period][$levels] = $mrr;
684
$cache[$period] = array($levels => $mrr);
685
else
686
$cache = array($period => array($levels => $mrr));
687
+
688
set_transient("pmpro_report_mrr", $cache, 3600*24);
689
+
690
return $mrr;
691
}
692
693
//get Cancellation Rate
694
function pmpro_getCancellationRate($period, $levels = 'all', $status = NULL)
695
+ {
696
//make sure status is an array
697
if(!is_array($status))
698
$status = array($status);
702
$hash = md5($period . $levels . implode('',$status));
703
if(!empty($cache) && !empty($cache[$hash]))
704
return $cache[$hash];
705
+
706
$signups = pmpro_getSignups($period, $levels);
707
$cancellations = pmpro_getCancellations($period, $levels, $status);
708
+
709
if(empty($signups))
710
return false;
711
+
712
$rate = number_format(($cancellations / $signups)*100, 2);
713
+
714
//save in cache
715
if(!empty($cache) && !empty($cache[$period]))
716
$cache[$period][$levels] = $rate;
718
$cache[$period] = array($levels => $rate);
719
else
720
$cache = array($period => array($levels => $rate));
721
+
722
set_transient("pmpro_report_cancellation_rate", $cache, 3600*24);
723
724
return $rate;
726
727
//get LTV
728
function pmpro_getLTV($period, $levels = 'all', $mrr = NULL, $signups = NULL, $cancellation_rate = NULL)
729
+ {
730
if(empty($mrr))
731
$mrr = pmpro_getMRR($period, $levels);
732
if(empty($signups))
733
$signups = pmpro_getSignups($period, $levels);
734
if(empty($cancellation_rate))
735
$cancellation_rate = pmpro_getCancellationRate($period, $levels);
736
+
737
//average monthly spend
738
if(empty($signups))
739
return false;
740
+
741
if($signups > 0)
742
$ams = $mrr / $signups;
743
else
744
$ams = 0;
745
+
746
if($cancellation_rate > 0)
747
$ltv = $ams * (1/$cancellation_rate);
748
else
adminpages/reports/sales.php CHANGED
@@ -3,11 +3,11 @@
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.
@@ -29,10 +29,9 @@ function pmpro_report_sales_init()
29
30
}
31
add_action("init", "pmpro_report_sales_init");
32
-
33
//widget
34
- function pmpro_report_sales_widget()
35
- {
36
global $wpdb;
37
?>
38
<style>
@@ -47,74 +46,91 @@ function pmpro_report_sales_widget()
47
<th scope="col"><?php _e('Revenue', 'paid-memberships-pro' ); ?></th>
48
</tr>
49
</thead>
50
- <tbody>
51
- <tr>
52
- <th scope="row"><?php _e('Today', 'paid-memberships-pro' ); ?></th>
53
- <td><?php echo number_format_i18n(pmpro_getSales("today")); ?></td>
54
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("today"));?></td>
55
- </tr>
56
- <tr>
57
- <th scope="row"><?php _e('This Month', 'paid-memberships-pro' ); ?></th>
58
- <td><?php echo number_format_i18n(pmpro_getSales("this month")); ?></td>
59
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("this month"));?></td>
60
- </tr>
61
- <tr>
62
- <th scope="row"><?php _e('This Year', 'paid-memberships-pro' ); ?></th>
63
- <td><?php echo number_format_i18n(pmpro_getSales("this year")); ?></td>
64
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("this year"));?></td>
65
- </tr>
66
- <tr>
67
- <th scope="row"><?php _e('All Time', 'paid-memberships-pro' ); ?></th>
68
- <td><?php echo number_format_i18n(pmpro_getSales("all time")); ?></td>
69
- <td><?php echo pmpro_formatPrice(pmpro_getRevenue("all time"));?></td>
70
- </tr>
71
- </tbody>
72
- </table>
73
</span>
74
<?php
75
}
76
77
function pmpro_report_sales_page()
78
{
79
global $wpdb, $pmpro_currency_symbol, $pmpro_currency, $pmpro_currencies;
80
-
81
//get values from form
82
if(isset($_REQUEST['type']))
83
$type = sanitize_text_field($_REQUEST['type']);
84
else
85
$type = "revenue";
86
-
87
if($type == "sales")
88
$type_function = "COUNT";
89
else
90
$type_function = "SUM";
91
-
92
if(isset($_REQUEST['period']))
93
$period = sanitize_text_field($_REQUEST['period']);
94
else
95
$period = "daily";
96
-
97
if(isset($_REQUEST['month']))
98
$month = intval($_REQUEST['month']);
99
else
100
$month = date_i18n("n", current_time('timestamp'));
101
-
102
$thisyear = date_i18n("Y", current_time('timestamp'));
103
if(isset($_REQUEST['year']))
104
$year = intval($_REQUEST['year']);
105
else
106
$year = $thisyear;
107
-
108
if(isset($_REQUEST['level']))
109
$l = intval($_REQUEST['level']);
110
else
111
$l = "";
112
-
113
//calculate start date and how to group dates returned from DB
114
if($period == "daily")
115
{
116
- $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
117
- $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
118
$date_function = 'DAY';
119
}
120
elseif($period == "monthly")
@@ -128,29 +144,29 @@ function pmpro_report_sales_page()
128
$startdate = '1960-01-01'; //all time
129
$date_function = 'YEAR';
130
}
131
-
132
//testing or live data
133
$gateway_environment = pmpro_getOption("gateway_environment");
134
-
135
//get data
136
- $sqlQuery = "SELECT $date_function(timestamp) as date, $type_function(total) as value FROM $wpdb->pmpro_membership_orders WHERE total > 0 AND timestamp >= '" . $startdate . "' AND status NOT IN('refunded', 'review', 'token', 'error') AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
137
-
138
if(!empty($enddate))
139
- $sqlQuery .= "AND timestamp < '" . $enddate . "' ";
140
-
141
if(!empty($l))
142
- $sqlQuery .= "AND membership_id IN(" . $l . ") ";
143
-
144
$sqlQuery .= " GROUP BY date ORDER BY date ";
145
-
146
- $dates = $wpdb->get_results($sqlQuery);
147
-
148
//fill in blanks in dates
149
- $cols = array();
150
if($period == "daily")
151
{
152
$lastday = date_i18n("t", strtotime($startdate, current_time("timestamp")));
153
-
154
for($i = 1; $i <= $lastday; $i++)
155
{
156
$cols[$i] = 0;
@@ -162,7 +178,7 @@ function pmpro_report_sales_page()
162
}
163
}
164
elseif($period == "monthly")
165
- {
166
for($i = 1; $i < 13; $i++)
167
{
168
$cols[$i] = 0;
@@ -183,7 +199,7 @@ function pmpro_report_sales_page()
183
$min = min($min, $date->date);
184
$max = max($max, $date->date);
185
}
186
-
187
for($i = $min; $i <= $max; $i++)
188
{
189
foreach($dates as $date)
@@ -192,13 +208,13 @@ function pmpro_report_sales_page()
192
$cols[$i] = $date->value;
193
}
194
}
195
- }
196
?>
197
- <form id="posts-filter" method="get" action="">
198
<h1>
199
<?php _e('Sales and Revenue', 'paid-memberships-pro' );?>
200
</h1>
201
-
202
<div class="tablenav top">
203
<?php _e('Show', 'paid-memberships-pro' )?>
204
<select id="period" name="period">
@@ -234,14 +250,14 @@ function pmpro_report_sales_page()
234
}
235
?>
236
</select>
237
-
238
- <input type="hidden" name="page" value="pmpro-reports" />
239
- <input type="hidden" name="report" value="sales" />
240
<input type="submit" class="button action" value="<?php _e('Generate Report', 'paid-memberships-pro' );?>" />
241
</div>
242
-
243
- <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
244
-
245
<script>
246
//update month/year when period dropdown is changed
247
jQuery(document).ready(function() {
@@ -249,7 +265,7 @@ function pmpro_report_sales_page()
249
pmpro_ShowMonthOrYear();
250
});
251
});
252
-
253
function pmpro_ShowMonthOrYear()
254
{
255
var period = jQuery('#period').val();
@@ -272,14 +288,14 @@ function pmpro_report_sales_page()
272
jQuery('#year').hide();
273
}
274
}
275
-
276
pmpro_ShowMonthOrYear();
277
-
278
//draw the chart
279
google.load("visualization", "1", {packages:["corechart"]});
280
google.setOnLoadCallback(drawChart);
281
- function drawChart() {
282
-
283
var data = google.visualization.arrayToDataTable([
284
['<?php echo $date_function;?>', '<?php echo ucwords($type);?>'],
285
<?php foreach($cols as $date => $value) { ?>
@@ -287,19 +303,19 @@ function pmpro_report_sales_page()
287
<?php } ?>
288
]);
289
290
- var options = {
291
colors: ['#51a351', '#387038'],
292
hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
293
- vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
294
};
295
-
296
- <?php
297
- if($type != "sales")
298
- {
299
if(pmpro_getCurrencyPosition() == "right")
300
$position = "suffix";
301
else
302
- $position = "prefix";
303
?>
304
var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
305
formatter.format(data, 1);
@@ -311,7 +327,7 @@ function pmpro_report_sales_page()
311
chart.draw(data, options);
312
}
313
</script>
314
-
315
</form>
316
<?php
317
}
@@ -327,7 +343,7 @@ function pmpro_getSales($period, $levels = NULL)
327
$cache = get_transient("pmpro_report_sales");
328
if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
329
return $cache[$period][$levels];
330
-
331
//a sale is an order with status NOT IN('refunded', 'review', 'token', 'error') with a total > 0
332
if($period == "today")
333
$startdate = date_i18n("Y-m-d", current_time('timestamp'));
@@ -337,19 +353,19 @@ function pmpro_getSales($period, $levels = NULL)
337
$startdate = date_i18n("Y", current_time('timestamp')) . "-01-01";
338
else
339
$startdate = "";
340
-
341
$gateway_environment = pmpro_getOption("gateway_environment");
342
-
343
//build query
344
global $wpdb;
345
- $sqlQuery = "SELECT COUNT(*) FROM $wpdb->pmpro_membership_orders WHERE total > 0 AND status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql($gateway_environment) . "' ";
346
-
347
//restrict by level
348
if(!empty($levels))
349
- $sqlQuery .= "AND membership_id IN(" . $levels . ") ";
350
-
351
$sales = $wpdb->get_var($sqlQuery);
352
-
353
//save in cache
354
if(!empty($cache) && !empty($cache[$period]))
355
$cache[$period][$levels] = $sales;
@@ -357,20 +373,85 @@ function pmpro_getSales($period, $levels = NULL)
357
$cache[$period] = array($levels => $sales);
358
else
359
$cache = array($period => array($levels => $sales));
360
-
361
set_transient("pmpro_report_sales", $cache, 3600*24);
362
-
363
return $sales;
364
}
365
366
//get revenue
367
function pmpro_getRevenue($period, $levels = NULL)
368
{
369
//check for a transient
370
$cache = get_transient("pmpro_report_revenue");
371
if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
372
- return $cache[$period][$levels];
373
-
374
//a sale is an order with status NOT IN('refunded', 'review', 'token', 'error')
375
if($period == "today")
376
$startdate = date_i18n("Y-m-d", current_time('timestamp'));
@@ -380,19 +461,19 @@ function pmpro_getRevenue($period, $levels = NULL)
380
$startdate = date_i18n("Y", current_time('timestamp')) . "-01-01";
381
else
382
$startdate = "";
383
-
384
$gateway_environment = pmpro_getOption("gateway_environment");
385
-
386
//build query
387
global $wpdb;
388
- $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) . "' ";
389
-
390
//restrict by level
391
if(!empty($levels))
392
$sqlQuery .= "AND membership_id IN(" . $levels . ") ";
393
-
394
$revenue = $wpdb->get_var($sqlQuery);
395
-
396
//save in cache
397
if(!empty($cache) && !empty($cache[$period]))
398
$cache[$period][$levels] = $revenue;
@@ -400,9 +481,9 @@ function pmpro_getRevenue($period, $levels = NULL)
400
$cache[$period] = array($levels => $revenue);
401
else
402
$cache = array($period => array($levels => $revenue));
403
-
404
set_transient("pmpro_report_revenue", $cache, 3600*24);
405
-
406
return $revenue;
407
}
408
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.
29
30
}
31
add_action("init", "pmpro_report_sales_init");
32
+
33
//widget
34
+ function pmpro_report_sales_widget() {
35
global $wpdb;
36
?>
37
<style>
46
<th scope="col"><?php _e('Revenue', 'paid-memberships-pro' ); ?></th>
47
</tr>
48
</thead>
49
+ <?php
50
+ $reports = array(
51
+ 'today' => __('Today', 'paid-memberships-pro' ),
52
+ 'this month' => __('This Month', 'paid-memberships-pro' ),
53
+ 'this year' => __('This Year', 'paid-memberships-pro' ),
54
+ 'all time' => __('All Time', 'paid-memberships-pro' ),
55
+ );
56
+
57
+ foreach ( $reports as $report_type => $report_name ) {
58
+ ?>
59
+ <tbody>
60
+ <tr class="pmpro_report_tr">
61
+ <th scope="row"><button class="pmpro_report_th pmpro_report_th_closed"><?php echo $report_name; ?></button></th>
62
+ <td><?php echo number_format_i18n( pmpro_getSales( $report_type ) ); ?></td>
63
+ <td><?php echo pmpro_formatPrice( pmpro_getRevenue( $report_type ) ); ?></td>
64
+ </tr>
65
+ <?php
66
+ //sale prices stats
67
+ $count = 0;
68
+ $max_prices_count = apply_filters( 'pmpro_admin_reports_max_sale_prices', 5 );
69
+ $prices = pmpro_get_prices_paid( $report_type, $max_prices_count );
70
+ foreach ( $prices as $price => $quantitiy ) {
71
+ if ( $count++ >= $max_prices_count ) {
72
+ break;
73
+ }
74
+ ?>
75
+ <tr class="pmpro_report_tr_sub" style="display: none;">
76
+ <th scope="row">- <?php echo pmpro_formatPrice( $price );?></th>
77
+ <td><?php echo number_format_i18n( $quantitiy ); ?></td>
78
+ <td><?php echo pmpro_formatPrice( $price * $quantitiy ); ?></td>
79
+ </tr>
80
+ <?php
81
+ }
82
+ ?>
83
+ </tbody>
84
+ <?php
85
+ }
86
+ ?>
87
+ </table>
88
</span>
89
+
90
<?php
91
}
92
93
function pmpro_report_sales_page()
94
{
95
global $wpdb, $pmpro_currency_symbol, $pmpro_currency, $pmpro_currencies;
96
+
97
//get values from form
98
if(isset($_REQUEST['type']))
99
$type = sanitize_text_field($_REQUEST['type']);
100
else
101
$type = "revenue";
102
+
103
if($type == "sales")
104
$type_function = "COUNT";
105
else
106
$type_function = "SUM";
107
+
108
if(isset($_REQUEST['period']))
109
$period = sanitize_text_field($_REQUEST['period']);
110
else
111
$period = "daily";
112
+
113
if(isset($_REQUEST['month']))
114
$month = intval($_REQUEST['month']);
115
else
116
$month = date_i18n("n", current_time('timestamp'));
117
+
118
$thisyear = date_i18n("Y", current_time('timestamp'));
119
if(isset($_REQUEST['year']))
120
$year = intval($_REQUEST['year']);
121
else
122
$year = $thisyear;
123
+
124
if(isset($_REQUEST['level']))
125
$l = intval($_REQUEST['level']);
126
else
127
$l = "";
128
+
129
//calculate start date and how to group dates returned from DB
130
if($period == "daily")
131
{
132
+ $startdate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-01';
133
+ $enddate = $year . '-' . substr("0" . $month, strlen($month) - 1, 2) . '-32';
134
$date_function = 'DAY';
135
}
136
elseif($period == "monthly")
144
$startdate = '1960-01-01'; //all time
145
$date_function = 'YEAR';
146
}
147
+
148
//testing or live data
149
$gateway_environment = pmpro_getOption("gateway_environment");
150
+
151
//get data
152
+ $sqlQuery = "SELECT $date_function(timestamp) as date, $type_function(total) as value FROM $wpdb->pmpro_membership_orders WHERE total > 0 AND timestamp >= '" . esc_sql( $startdate ) . "' AND status NOT IN('refunded', 'review', 'token', 'error') AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
153
+
154
if(!empty($enddate))
155
+ $sqlQuery .= "AND timestamp < '" . esc_sql( $enddate ) . "' ";
156
+
157
if(!empty($l))
158
+ $sqlQuery .= "AND membership_id IN(" . esc_sql( $l ) . ") ";
159
+
160
$sqlQuery .= " GROUP BY date ORDER BY date ";
161
+
162
+ $dates = $wpdb->get_results($sqlQuery);
163
+
164
//fill in blanks in dates
165
+ $cols = array();
166
if($period == "daily")
167
{
168
$lastday = date_i18n("t", strtotime($startdate, current_time("timestamp")));
169
+
170
for($i = 1; $i <= $lastday; $i++)
171
{
172
$cols[$i] = 0;
178
}
179
}
180
elseif($period == "monthly")
181
+ {
182
for($i = 1; $i < 13; $i++)
183
{
184
$cols[$i] = 0;
199
$min = min($min, $date->date);
200
$max = max($max, $date->date);
201
}
202
+
203
for($i = $min; $i <= $max; $i++)
204
{
205
foreach($dates as $date)
208
$cols[$i] = $date->value;
209
}
210
}
211
+ }
212
?>
213
+ <form id="posts-filter" method="get" action="">
214
<h1>
215
<?php _e('Sales and Revenue', 'paid-memberships-pro' );?>
216
</h1>
217
+
218
<div class="tablenav top">
219
<?php _e('Show', 'paid-memberships-pro' )?>
220
<select id="period" name="period">
250
}
251
?>
252
</select>
253
+
254
+ <input type="hidden" name="page" value="pmpro-reports" />
255
+ <input type="hidden" name="report" value="sales" />
256
<input type="submit" class="button action" value="<?php _e('Generate Report', 'paid-memberships-pro' );?>" />
257
</div>
258
+
259
+ <div id="chart_div" style="clear: both; width: 100%; height: 500px;"></div>
260
+
261
<script>
262
//update month/year when period dropdown is changed
263
jQuery(document).ready(function() {
265
pmpro_ShowMonthOrYear();
266
});
267
});
268
+
269
function pmpro_ShowMonthOrYear()
270
{
271
var period = jQuery('#period').val();
288
jQuery('#year').hide();
289
}
290
}
291
+
292
pmpro_ShowMonthOrYear();
293
+
294
//draw the chart
295
google.load("visualization", "1", {packages:["corechart"]});
296
google.setOnLoadCallback(drawChart);
297
+ function drawChart() {
298
+
299
var data = google.visualization.arrayToDataTable([
300
['<?php echo $date_function;?>', '<?php echo ucwords($type);?>'],
301
<?php foreach($cols as $date => $value) { ?>
303
<?php } ?>
304
]);
305
306
+ var options = {
307
colors: ['#51a351', '#387038'],
308
hAxis: {title: '<?php echo $date_function;?>', titleTextStyle: {color: 'black'}, maxAlternation: 1},
309
+ vAxis: {color: 'green', titleTextStyle: {color: '#51a351'}},
310
};
311
+
312
+ <?php
313
+ if($type != "sales")
314
+ {
315
if(pmpro_getCurrencyPosition() == "right")
316
$position = "suffix";
317
else
318
+ $position = "prefix";
319
?>
320
var formatter = new google.visualization.NumberFormat({<?php echo $position;?>: '<?php echo html_entity_decode($pmpro_currency_symbol);?>'});
321
formatter.format(data, 1);
327
chart.draw(data, options);
328
}
329
</script>
330
+
331
</form>
332
<?php
333
}
343
$cache = get_transient("pmpro_report_sales");
344
if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
345
return $cache[$period][$levels];
346
+
347
//a sale is an order with status NOT IN('refunded', 'review', 'token', 'error') with a total > 0
348
if($period == "today")
349
$startdate = date_i18n("Y-m-d", current_time('timestamp'));
353
$startdate = date_i18n("Y", current_time('timestamp')) . "-01-01";
354
else
355
$startdate = "";
356
+
357
$gateway_environment = pmpro_getOption("gateway_environment");
358
+
359
//build query
360
global $wpdb;
361
+ $sqlQuery = "SELECT COUNT(*) FROM $wpdb->pmpro_membership_orders WHERE total > 0 AND status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . esc_sql( $startdate ) . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
362
+
363
//restrict by level
364
if(!empty($levels))
365
+ $sqlQuery .= "AND membership_id IN(" . esc_sql( $levels ) . ") ";
366
+
367
$sales = $wpdb->get_var($sqlQuery);
368
+
369
//save in cache
370
if(!empty($cache) && !empty($cache[$period]))
371
$cache[$period][$levels] = $sales;
373
$cache[$period] = array($levels => $sales);
374
else
375
$cache = array($period => array($levels => $sales));
376
+
377
set_transient("pmpro_report_sales", $cache, 3600*24);
378
+
379
return $sales;
380
}
381
382
+ /**
383
+ * Gets an array of all prices paid in a time period
384
+ *
385
+ * @param string $period time period to query.
386
+ */
387
+ function pmpro_get_prices_paid( $period, $count = NULL ) {
388
+ // Check for a transient.
389
+ $cache = get_transient( 'pmpro_report_prices_paid' );
390
+ if ( ! empty( $cache ) && ! empty( $cache[ $period . $count ] ) ) {
391
+ return $cache[ $period . $count ];
392
+ }
393
+
394
+ // A sale is an order with status NOT IN('refunded', 'review', 'token', 'error') with a total > 0.
395
+ if ( 'today' === $period ) {
396
+ $startdate = date_i18n( 'Y-m-d', current_time( 'timestamp' ) );
397
+ } elseif ( 'this month' === $period ) {
398
+ $startdate = date_i18n( 'Y-m', current_time( 'timestamp' ) ) . '-01';
399
+ } elseif ( 'this year' === $period ) {
400
+ $startdate = date_i18n( 'Y', current_time( 'timestamp' ) ) . '-01-01';
401
+ } else {
402
+ $startdate = '';
403
+ }
404
+
405
+ $gateway_environment = pmpro_getOption( 'gateway_environment' );
406
+
407
+ // Build query.
408
+ global $wpdb;
409
+ $sql_query = "SELECT total, COUNT(*) as num FROM $wpdb->pmpro_membership_orders WHERE total > 0 AND status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . $startdate . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
410
+
411
+ // Restrict by level.
412
+ if ( ! empty( $levels ) ) {
413
+ $sql_query .= 'AND membership_id IN(' . $levels . ') ';
414
+ }
415
+
416
+ $sql_query .= ' GROUP BY total ORDER BY num DESC ';
417
+
418
+ $prices = $wpdb->get_results( $sql_query );
419
+
420
+ if( !empty( $count) ) {
421
+ $prices = array_slice( $prices, 0, $count, true );
422
+ }
423
+
424
+ $prices_formatted = array();
425
+ foreach ( $prices as $price ) {
426
+ if ( isset( $price->total ) ) {
427
+ $sql_query = "SELECT COUNT(*) FROM $wpdb->pmpro_membership_orders WHERE total = '" . esc_sql( $price->total ) . "' AND status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . esc_sql( $startdate ) . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
428
+ $sales = $wpdb->get_var( $sql_query );
429
+ $prices_formatted[ $price->total ] = $sales;
430
+ }
431
+ }
432
+
433
+ krsort( $prices_formatted );
434
+
435
+ // Save in cache.
436
+ if ( ! empty( $cache ) ) {
437
+ $cache[ $period . $count ] = $prices_formatted;
438
+ } else {
439
+ $cache = array( $period . $count => $prices_formatted );
440
+ }
441
+
442
+ set_transient( 'pmpro_report_sales', $cache, 3600 * 24 );
443
+
444
+ return $prices_formatted;
445
+ }
446
+
447
//get revenue
448
function pmpro_getRevenue($period, $levels = NULL)
449
{
450
//check for a transient
451
$cache = get_transient("pmpro_report_revenue");
452
if(!empty($cache) && !empty($cache[$period]) && !empty($cache[$period][$levels]))
453
+ return $cache[$period][$levels];
454
+
455
//a sale is an order with status NOT IN('refunded', 'review', 'token', 'error')
456
if($period == "today")
457
$startdate = date_i18n("Y-m-d", current_time('timestamp'));
461
$startdate = date_i18n("Y", current_time('timestamp')) . "-01-01";
462
else
463
$startdate = "";
464
+
465
$gateway_environment = pmpro_getOption("gateway_environment");
466
+
467
//build query
468
global $wpdb;
469
+ $sqlQuery = "SELECT SUM(total) FROM $wpdb->pmpro_membership_orders WHERE status NOT IN('refunded', 'review', 'token', 'error') AND timestamp >= '" . esc_sql( $startdate ) . "' AND gateway_environment = '" . esc_sql( $gateway_environment ) . "' ";
470
+
471
//restrict by level
472
if(!empty($levels))
473
$sqlQuery .= "AND membership_id IN(" . $levels . ") ";
474
+
475
$revenue = $wpdb->get_var($sqlQuery);
476
+
477
//save in cache
478
if(!empty($cache) && !empty($cache[$period]))
479
$cache[$period][$levels] = $revenue;
481
$cache[$period] = array($levels => $revenue);
482
else
483
$cache = array($period => array($levels => $revenue));
484
+
485
set_transient("pmpro_report_revenue", $cache, 3600*24);
486
+
487
return $revenue;
488
}
489
classes/gateways/class.pmprogateway_stripe.php CHANGED
@@ -5,6 +5,8 @@
5
use Stripe\Plan as Stripe_Plan;
6
use Stripe\Charge as Stripe_Charge;
7
8
//include pmprogateway
9
require_once(dirname(__FILE__) . "/class.pmprogateway.php");
10
@@ -41,7 +43,7 @@
41
if( true === $this->dependencies() ) {
42
$this->loadStripeLibrary();
43
Stripe\Stripe::setApiKey(pmpro_getOption("stripe_secretkey"));
44
- Stripe\Stripe::setAPIVersion("2017-08-15");
45
self::$is_loaded = true;
46
}
47
@@ -300,6 +302,11 @@
300
<p><?php _e('To fully integrate with Stripe, be sure to set your Web Hook URL to', 'paid-memberships-pro' );?> <pre><?php echo admin_url("admin-ajax.php") . "?action=stripe_webhook";?></pre></p>
301
</td>
302
</tr>
303
<?php
304
}
305
@@ -1794,6 +1801,8 @@
1794
*/
1795
function cancel(&$order, $update_status = true)
1796
{
1797
//no matter what happens below, we're going to cancel the order in our system
1798
if($update_status)
1799
$order->updateStatus("cancelled");
@@ -1810,7 +1819,8 @@
1810
//find subscription with this order code
1811
$subscription = $this->getSubscription($order);
1812
1813
- if(!empty($subscription))
1814
{
1815
if($this->cancelSubscriptionAtGateway($subscription))
1816
{
5
use Stripe\Plan as Stripe_Plan;
6
use Stripe\Charge as Stripe_Charge;
7
8
+ define( "PMPRO_STRIPE_API_VERSION", "2017-08-15" );
9
+
10
//include pmprogateway
11
require_once(dirname(__FILE__) . "/class.pmprogateway.php");
12
43
if( true === $this->dependencies() ) {
44
$this->loadStripeLibrary();
45
Stripe\Stripe::setApiKey(pmpro_getOption("stripe_secretkey"));
46
+ Stripe\Stripe::setAPIVersion( PMPRO_STRIPE_API_VERSION );
47
self::$is_loaded = true;
48
}
49
302
<p><?php _e('To fully integrate with Stripe, be sure to set your Web Hook URL to', 'paid-memberships-pro' );?> <pre><?php echo admin_url("admin-ajax.php") . "?action=stripe_webhook";?></pre></p>
303
</td>
304
</tr>
305
+
306
+ <tr>
307
+ <th><?php _e( 'Stripe API Version', 'paid-memberships-pro' ); ?>:</th>
308
+ <td><?php echo PMPRO_STRIPE_API_VERSION; ?></td>
309
+ </tr>
310
<?php
311
}
312
1801
*/
1802
function cancel(&$order, $update_status = true)
1803
{
1804
+ global $pmpro_stripe_event;
1805
+
1806
//no matter what happens below, we're going to cancel the order in our system
1807
if($update_status)
1808
$order->updateStatus("cancelled");
1819
//find subscription with this order code
1820
$subscription = $this->getSubscription($order);
1821
1822
+ if(!empty($subscription)
1823
+ && ( empty( $pmpro_stripe_event ) || empty( $pmpro_stripe_event->type ) || $pmpro_stripe_event->type != 'customer.subscription.deleted' ) )
1824
{
1825
if($this->cancelSubscriptionAtGateway($subscription))
1826
{
includes/filters.php CHANGED
@@ -165,13 +165,13 @@ if ( empty( $_REQUEST['discount_code'] ) && ! empty( $_REQUEST['other_discount_c
165
166
// apply all the_content filters to confirmation messages for levels
167
function pmpro_pmpro_confirmation_message( $message ) {
168
- return apply_filters( 'the_content', $message );
169
}
170
add_filter( 'pmpro_confirmation_message', 'pmpro_pmpro_confirmation_message' );
171
172
// apply all the_content filters to level descriptions
173
function pmpro_pmpro_level_description( $description ) {
174
- return apply_filters( 'the_content', $description );
175
}
176
add_filter( 'pmpro_level_description', 'pmpro_pmpro_level_description' );
177
165
166
// apply all the_content filters to confirmation messages for levels
167
function pmpro_pmpro_confirmation_message( $message ) {
168
+ return wpautop( $message );
169
}
170
add_filter( 'pmpro_confirmation_message', 'pmpro_pmpro_confirmation_message' );
171
172
// apply all the_content filters to level descriptions
173
function pmpro_pmpro_level_description( $description ) {
174
+ return wpautop( $description );
175
}
176
add_filter( 'pmpro_level_description', 'pmpro_pmpro_level_description' );
177
includes/login.php CHANGED
@@ -50,7 +50,9 @@ add_filter('wp_signup_location', 'pmpro_wp_signup_location');
50
51
//redirect from default login pages to PMPro
52
function pmpro_login_head()
53
- {
54
$login_redirect = apply_filters("pmpro_login_redirect", true);
55
56
if((pmpro_is_login_page() || is_page("login") ||
@@ -124,10 +126,12 @@ function pmpro_login_head()
124
}
125
}
126
}
127
- elseif ( function_exists( 'tml_is_action' ) && function_exists( 'tml_get_action_url' ) )
128
{
129
- if ( $link = tml_get_action_url( 'login' ) ) {
130
- if ( ! tml_is_action( 'login' ) ) {
131
wp_redirect( $link );
132
exit;
133
}
50
51
//redirect from default login pages to PMPro
52
function pmpro_login_head()
53
+ {
54
+ global $pagenow;
55
+
56
$login_redirect = apply_filters("pmpro_login_redirect", true);
57
58
if((pmpro_is_login_page() || is_page("login") ||
126
}
127
}
128
}
129
+ elseif ( function_exists( 'tml_is_action' ) && function_exists( 'tml_get_action_url' ) && function_exists( 'tml_action_exists' ) )
130
{
131
+ $action = ! empty( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
132
+ if ( tml_action_exists( $action ) ) {
133
+ if ( 'wp-login.php' == $pagenow ) {
134
+ $link = tml_get_action_url( $action );
135
wp_redirect( $link );
136
exit;
137
}
includes/privacy.php CHANGED
@@ -411,7 +411,7 @@ function pmpro_save_consent( $user_id = NULL, $post_id = NULL, $post_modified =
411
// Default to current user.
412
if( empty( $user_id ) ) {
413
global $current_user;
414
- $user_id = $user->ID;
415
}
416
417
if( empty( $user_id ) ) {
@@ -455,7 +455,7 @@ function pmpro_get_consent_log( $user_id = NULL, $reversed = true ) {
455
// Default to current user.
456
if( empty( $user_id ) ) {
457
global $current_user;
458
- $user_id = $user->ID;
459
}
460
461
if( empty( $user_id ) ) {
@@ -494,7 +494,7 @@ function pmpro_after_checkout_update_consent( $user_id, $order ) {
494
}
495
add_action( 'pmpro_after_checkout', 'pmpro_after_checkout_update_consent', 10, 2 );
496
add_action( 'pmpro_before_send_to_paypal_standard', 'pmpro_after_checkout_update_consent', 10, 2);
497
- add_action( 'pmpro_before_send_to_twocheckout', 'pmpro_after_checkout_update_consent' );
498
499
/**
500
* Convert a consent entry into a English sentence.
@@ -528,4 +528,4 @@ function pmpro_is_consent_current( $entry ) {
528
return true;
529
}
530
return false;
531
- }
411
// Default to current user.
412
if( empty( $user_id ) ) {
413
global $current_user;
414
+ $user_id = $current_user->ID;
415
}
416
417
if( empty( $user_id ) ) {
455
// Default to current user.
456
if( empty( $user_id ) ) {
457
global $current_user;
458
+ $user_id = $current_user->ID;
459
}
460
461
if( empty( $user_id ) ) {
494
}
495
add_action( 'pmpro_after_checkout', 'pmpro_after_checkout_update_consent', 10, 2 );
496
add_action( 'pmpro_before_send_to_paypal_standard', 'pmpro_after_checkout_update_consent', 10, 2);
497
+ add_action( 'pmpro_before_send_to_twocheckout', 'pmpro_after_checkout_update_consent', 10, 2);
498
499
/**
500
* Convert a consent entry into a English sentence.
528
return true;
529
}
530
return false;
531
+ }
languages/paid-memberships-pro.mo CHANGED
Binary file
languages/paid-memberships-pro.po CHANGED
@@ -5,11 +5,11 @@
5
msgid ""
6
msgstr ""
7
"Project-Id-Version: paid-memberships-pro\n"
8
- "Report-Msgid-Bugs-To: jason@strangerstudios.com\n"
9
- "POT-Creation-Date: 2018-05-24 14:48-0400\n"
10
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11
"Last-Translator: \n"
12
- "Language-Team: Stranger Studios <jason@strangerstudios.com>\n"
13
"MIME-Version: 1.0\n"
14
"Content-Type: text/plain; charset=UTF-8\n"
15
"Content-Transfer-Encoding: 8bit\n"
@@ -61,7 +61,7 @@ msgstr ""
61
#: adminpages/addons.php:79 adminpages/addons.php:92 adminpages/orders.php:605
62
#: adminpages/orders.php:712 adminpages/orders.php:741
63
#: adminpages/orders.php:850 adminpages/orders.php:881
64
- #: adminpages/orders.php:892
65
msgid "All"
66
msgstr ""
67
@@ -192,7 +192,9 @@ msgstr ""
192
#: adminpages/orders.php:1340 adminpages/orders.php:1350
193
#: includes/profile.php:186 shortcodes/pmpro_account.php:148
194
#: adminpages/addons.php:231 adminpages/addons.php:234
195
- #: adminpages/addons.php:247 shortcodes/pmpro_account.php:145
196
#: shortcodes/pmpro_account.php:146 shortcodes/pmpro_account.php:148
197
msgid "N/A"
198
msgstr ""
@@ -314,7 +316,7 @@ msgid ""
314
msgstr ""
315
316
#: adminpages/admin_header.php:135
317
- #: classes/gateways/class.pmprogateway_stripe.php:66
318
#: adminpages/admin_header.php:125 adminpages/admin_header.php:135
319
#: classes/gateways/class.pmprogateway_stripe.php:66
320
#, php-format
@@ -541,7 +543,7 @@ msgstr ""
541
#: adminpages/advancedsettings.php:167 adminpages/advancedsettings.php:226
542
#: adminpages/advancedsettings.php:238 adminpages/membershiplevels.php:694
543
#: adminpages/paymentsettings.php:236
544
- #: classes/gateways/class.pmprogateway_stripe.php:289 includes/profile.php:125
545
#: adminpages/advancedsettings.php:128 adminpages/advancedsettings.php:135
546
#: adminpages/advancedsettings.php:148 adminpages/advancedsettings.php:151
547
#: adminpages/advancedsettings.php:167 adminpages/advancedsettings.php:187
@@ -571,6 +573,7 @@ msgstr ""
571
#: classes/gateways/class.pmprogateway_stripe.php:222
572
#: classes/gateways/class.pmprogateway_stripe.php:249
573
#: classes/gateways/class.pmprogateway_stripe.php:281
574
#: classes/gateways/class.pmprogateway_stripe.php:297
575
#: classes/gateways/class.pmprogateway_stripe.php:298 includes/profile.php:101
576
#: includes/profile.php:105 includes/profile.php:110 includes/profile.php:117
@@ -622,7 +625,7 @@ msgstr ""
622
623
#: adminpages/advancedsettings.php:227 adminpages/membershiplevels.php:694
624
#: adminpages/paymentsettings.php:237
625
- #: classes/gateways/class.pmprogateway_stripe.php:290 includes/profile.php:126
626
#: adminpages/advancedsettings.php:188 adminpages/advancedsettings.php:195
627
#: adminpages/advancedsettings.php:208 adminpages/advancedsettings.php:211
628
#: adminpages/advancedsettings.php:227 adminpages/membershiplevels.php:563
@@ -648,6 +651,7 @@ msgstr ""
648
#: classes/gateways/class.pmprogateway_stripe.php:223
649
#: classes/gateways/class.pmprogateway_stripe.php:250
650
#: classes/gateways/class.pmprogateway_stripe.php:282
651
#: classes/gateways/class.pmprogateway_stripe.php:298
652
#: classes/gateways/class.pmprogateway_stripe.php:299 includes/profile.php:102
653
#: includes/profile.php:106 includes/profile.php:111 includes/profile.php:118
@@ -838,9 +842,9 @@ msgstr ""
838
#: adminpages/orders.php:910 adminpages/orders.php:937
839
#: adminpages/orders.php:966 adminpages/orders.php:1103
840
#: adminpages/orders.php:1134 adminpages/orders.php:1140
841
- #: adminpages/reports/login.php:140 adminpages/reports/login.php:142
842
- #: adminpages/reports/login.php:158 adminpages/reports/login.php:162
843
- #: adminpages/templates/orders-email.php:46
844
#: adminpages/templates/orders-print.php:75
845
msgid "ID"
846
msgstr ""
@@ -853,6 +857,7 @@ msgstr ""
853
#: adminpages/orders.php:261 adminpages/orders.php:333
854
#: adminpages/orders.php:344 adminpages/orders.php:362
855
#: adminpages/orders.php:375 adminpages/orders.php:386
856
msgid "This will be generated when you save."
857
msgstr ""
858
@@ -869,10 +874,11 @@ msgstr ""
869
#: adminpages/orders.php:265 adminpages/orders.php:337
870
#: adminpages/orders.php:349 adminpages/orders.php:366
871
#: adminpages/orders.php:380 adminpages/orders.php:391
872
- #: adminpages/orders.php:598 adminpages/orders.php:901
873
- #: adminpages/orders.php:911 adminpages/orders.php:938
874
- #: adminpages/orders.php:967 adminpages/orders.php:1104
875
- #: adminpages/orders.php:1135 adminpages/orders.php:1141
876
msgid "Code"
877
msgstr ""
878
@@ -880,13 +886,13 @@ msgstr ""
880
#: adminpages/discountcodes.php:349 adminpages/discountcodes.php:352
881
#: adminpages/discountcodes.php:353 adminpages/discountcodes.php:354
882
#: adminpages/discountcodes.php:359 adminpages/discountcodes.php:424
883
- #: adminpages/discountcodes.php:465
884
msgid "Start Date"
885
msgstr ""
886
887
#: adminpages/discountcodes.php:483
888
#: classes/gateways/class.pmprogateway_braintree.php:459
889
- #: classes/gateways/class.pmprogateway_stripe.php:621 pages/billing.php:313
890
#: pages/checkout.php:463 adminpages/discountcodes.php:367
891
#: adminpages/discountcodes.php:370 adminpages/discountcodes.php:371
892
#: adminpages/discountcodes.php:372 adminpages/discountcodes.php:377
@@ -913,15 +919,16 @@ msgstr ""
913
#: classes/gateways/class.pmprogateway_stripe.php:570
914
#: classes/gateways/class.pmprogateway_stripe.php:597
915
#: classes/gateways/class.pmprogateway_stripe.php:613
916
#: classes/gateways/class.pmprogateway_stripe.php:629
917
#: classes/gateways/class.pmprogateway_stripe.php:630 pages/billing.php:249
918
#: pages/billing.php:253 pages/billing.php:262 pages/billing.php:265
919
- #: pages/billing.php:268 pages/billing.php:310 pages/billing.php:316
920
- #: pages/billing.php:317 pages/billing.php:319 pages/billing.php:342
921
- #: pages/checkout.php:463 pages/checkout.php:508 pages/checkout.php:524
922
- #: pages/checkout.php:525 pages/checkout.php:532 pages/checkout.php:553
923
- #: pages/checkout.php:562 pages/checkout.php:571 pages/checkout.php:575
924
- #: pages/checkout.php:582 pages/checkout.php:585
925
msgid "Expiration Date"
926
msgstr ""
927
@@ -1013,7 +1020,7 @@ msgstr ""
1013
1014
#: adminpages/discountcodes.php:587 adminpages/discountcodes.php:641
1015
#: adminpages/membershiplevels.php:410 adminpages/membershiplevels.php:511
1016
- #: classes/gateways/class.pmprogateway_stripe.php:679
1017
#: adminpages/discountcodes.php:446 adminpages/discountcodes.php:466
1018
#: adminpages/discountcodes.php:467 adminpages/discountcodes.php:468
1019
#: adminpages/discountcodes.php:473 adminpages/discountcodes.php:492
@@ -1041,6 +1048,7 @@ msgstr ""
1041
#: classes/gateways/class.pmprogateway_stripe.php:637
1042
#: classes/gateways/class.pmprogateway_stripe.php:664
1043
#: classes/gateways/class.pmprogateway_stripe.php:671
1044
#: classes/gateways/class.pmprogateway_stripe.php:687
1045
#: classes/gateways/class.pmprogateway_stripe.php:688
1046
msgid "Day(s)"
@@ -1048,7 +1056,7 @@ msgstr ""
1048
1049
#: adminpages/discountcodes.php:587 adminpages/discountcodes.php:641
1050
#: adminpages/membershiplevels.php:410 adminpages/membershiplevels.php:511
1051
- #: classes/gateways/class.pmprogateway_stripe.php:679
1052
#: adminpages/discountcodes.php:446 adminpages/discountcodes.php:466
1053
#: adminpages/discountcodes.php:467 adminpages/discountcodes.php:468
1054
#: adminpages/discountcodes.php:473 adminpages/discountcodes.php:492
@@ -1076,6 +1084,7 @@ msgstr ""
1076
#: classes/gateways/class.pmprogateway_stripe.php:637
1077
#: classes/gateways/class.pmprogateway_stripe.php:664
1078
#: classes/gateways/class.pmprogateway_stripe.php:671
1079
#: classes/gateways/class.pmprogateway_stripe.php:687
1080
#: classes/gateways/class.pmprogateway_stripe.php:688
1081
msgid "Month(s)"
@@ -1083,7 +1092,7 @@ msgstr ""
1083
1084
#: adminpages/discountcodes.php:587 adminpages/discountcodes.php:641
1085
#: adminpages/membershiplevels.php:410 adminpages/membershiplevels.php:511
1086
- #: classes/gateways/class.pmprogateway_stripe.php:679
1087
#: adminpages/discountcodes.php:446 adminpages/discountcodes.php:466
1088
#: adminpages/discountcodes.php:467 adminpages/discountcodes.php:468
1089
#: adminpages/discountcodes.php:473 adminpages/discountcodes.php:492
@@ -1111,6 +1120,7 @@ msgstr ""
1111
#: classes/gateways/class.pmprogateway_stripe.php:637
1112
#: classes/gateways/class.pmprogateway_stripe.php:664
1113
#: classes/gateways/class.pmprogateway_stripe.php:671
1114
#: classes/gateways/class.pmprogateway_stripe.php:687
1115
#: classes/gateways/class.pmprogateway_stripe.php:688
1116
msgid "Week(s)"
@@ -1118,7 +1128,7 @@ msgstr ""
1118
1119
#: adminpages/discountcodes.php:587 adminpages/discountcodes.php:641
1120
#: adminpages/membershiplevels.php:410 adminpages/membershiplevels.php:511
1121
- #: classes/gateways/class.pmprogateway_stripe.php:679
1122
#: adminpages/discountcodes.php:446 adminpages/discountcodes.php:466
1123
#: adminpages/discountcodes.php:467 adminpages/discountcodes.php:468
1124
#: adminpages/discountcodes.php:473 adminpages/discountcodes.php:492
@@ -1146,6 +1156,7 @@ msgstr ""
1146
#: classes/gateways/class.pmprogateway_stripe.php:637
1147
#: classes/gateways/class.pmprogateway_stripe.php:664
1148
#: classes/gateways/class.pmprogateway_stripe.php:671
1149
#: classes/gateways/class.pmprogateway_stripe.php:687
1150
#: classes/gateways/class.pmprogateway_stripe.php:688
1151
msgid "Year(s)"
@@ -1401,7 +1412,7 @@ msgstr ""
1401
#: adminpages/orders.php:989 adminpages/orders.php:992
1402
#: adminpages/orders.php:1021 adminpages/orders.php:1050
1403
#: adminpages/orders.php:1205 adminpages/orders.php:1239
1404
- #: adminpages/orders.php:1245
1405
msgid "edit"
1406
msgstr ""
1407
@@ -1434,7 +1445,7 @@ msgstr ""
1434
#: adminpages/orders.php:995 adminpages/orders.php:998
1435
#: adminpages/orders.php:1027 adminpages/orders.php:1056
1436
#: adminpages/orders.php:1211 adminpages/orders.php:1245
1437
- #: adminpages/orders.php:1251
1438
msgid "delete"
1439
msgstr ""
1440
@@ -1667,7 +1678,7 @@ msgid "Billing Details"
1667
msgstr ""
1668
1669
#: adminpages/membershiplevels.php:406
1670
- #: classes/gateways/class.pmprogateway_stripe.php:777
1671
#: adminpages/membershiplevels.php:349 adminpages/membershiplevels.php:351
1672
#: adminpages/membershiplevels.php:370 adminpages/membershiplevels.php:372
1673
#: adminpages/membershiplevels.php:373 adminpages/membershiplevels.php:396
@@ -1685,6 +1696,7 @@ msgstr ""
1685
#: classes/gateways/class.pmprogateway_stripe.php:735
1686
#: classes/gateways/class.pmprogateway_stripe.php:762
1687
#: classes/gateways/class.pmprogateway_stripe.php:769
1688
#: classes/gateways/class.pmprogateway_stripe.php:785
1689
#: classes/gateways/class.pmprogateway_stripe.php:786
1690
msgid "per"
@@ -1854,14 +1866,15 @@ msgstr ""
1854
#: adminpages/orders.php:511 adminpages/orders.php:561
1855
#: adminpages/orders.php:633 adminpages/orders.php:662
1856
#: adminpages/orders.php:765 adminpages/orders.php:796
1857
- #: adminpages/orders.php:807 pages/account.php:44 pages/billing.php:295
1858
- #: pages/billing.php:299 pages/billing.php:330 pages/billing.php:339
1859
- #: pages/billing.php:342 pages/billing.php:344 pages/billing.php:348
1860
- #: pages/billing.php:364 pages/billing.php:365 pages/billing.php:371
1861
- #: pages/billing.php:392 pages/billing.php:397 pages/billing.php:401
1862
- #: pages/billing.php:406 pages/cancel.php:71 pages/cancel.php:83
1863
- #: pages/cancel.php:84 shortcodes/pmpro_account.php:70
1864
- #: shortcodes/pmpro_account.php:72 shortcodes/pmpro_account.php:73
1865
msgid "Cancel"
1866
msgstr ""
1867
@@ -1970,7 +1983,7 @@ msgstr ""
1970
#: adminpages/orders.php:992 adminpages/orders.php:995
1971
#: adminpages/orders.php:1024 adminpages/orders.php:1053
1972
#: adminpages/orders.php:1208 adminpages/orders.php:1242
1973
- #: adminpages/orders.php:1248
1974
msgid "copy"
1975
msgstr ""
1976
@@ -1988,18 +2001,19 @@ msgstr ""
1988
#: adminpages/orders.php:591 adminpages/orders.php:698
1989
#: adminpages/orders.php:727 adminpages/orders.php:833
1990
#: adminpages/orders.php:864 adminpages/orders.php:875
1991
msgid "Export to CSV"
1992
msgstr ""
1993
1994
#: adminpages/memberslist.php:30 adminpages/orders.php:981
1995
#: adminpages/reports/login.php:87 adminpages/reports/memberships.php:328
1996
- #: adminpages/reports/sales.php:203 adminpages/memberslist.php:30
1997
#: adminpages/orders.php:603 adminpages/orders.php:710
1998
#: adminpages/orders.php:739 adminpages/orders.php:848
1999
#: adminpages/orders.php:879 adminpages/orders.php:890
2000
- #: adminpages/reports/login.php:65 adminpages/reports/login.php:67
2001
- #: adminpages/reports/login.php:83 adminpages/reports/login.php:87
2002
- #: adminpages/reports/memberships.php:256
2003
#: adminpages/reports/memberships.php:263
2004
#: adminpages/reports/memberships.php:276
2005
#: adminpages/reports/memberships.php:292
@@ -2011,7 +2025,7 @@ msgid "Show"
2011
msgstr ""
2012
2013
#: adminpages/memberslist.php:32 adminpages/reports/login.php:89
2014
- #: adminpages/reports/memberships.php:355 adminpages/reports/sales.php:226
2015
#: classes/class.pmproemail.php:154 classes/class.pmproemail.php:199
2016
#: adminpages/memberslist.php:32 adminpages/reports/login.php:67
2017
#: adminpages/reports/login.php:69 adminpages/reports/login.php:85
@@ -2088,13 +2102,14 @@ msgstr ""
2088
#: pages/account.php:90 pages/account.php:94 pages/billing.php:58
2089
#: pages/billing.php:62 pages/billing.php:71 pages/billing.php:74
2090
#: pages/billing.php:76 pages/billing.php:77 pages/billing.php:80
2091
- #: pages/billing.php:103 pages/billing.php:104 pages/checkout.php:275
2092
- #: pages/checkout.php:298 pages/checkout.php:300 pages/checkout.php:302
2093
- #: pages/checkout.php:311 pages/checkout.php:314 pages/checkout.php:317
2094
- #: pages/checkout.php:319 pages/checkout.php:321 pages/checkout.php:326
2095
- #: pages/checkout.php:329 pages/confirmation.php:59 pages/confirmation.php:61
2096
- #: pages/confirmation.php:66 pages/confirmation.php:67
2097
- #: pages/confirmation.php:69 pages/invoice.php:46 pages/invoice.php:48
2098
msgid "Billing Address"
2099
msgstr ""
2100
@@ -2178,21 +2193,21 @@ msgstr ""
2178
#: adminpages/orders.php:297 adminpages/orders.php:119
2179
#: adminpages/orders.php:169 adminpages/orders.php:270
2180
#: adminpages/orders.php:284 adminpages/orders.php:285
2181
- #: adminpages/orders.php:295
2182
msgid "Order saved successfully."
2183
msgstr ""
2184
2185
#: adminpages/orders.php:300 adminpages/orders.php:124
2186
#: adminpages/orders.php:174 adminpages/orders.php:275
2187
#: adminpages/orders.php:287 adminpages/orders.php:288
2188
- #: adminpages/orders.php:298
2189
msgid "Error updating order timestamp."
2190
msgstr ""
2191
2192
#: adminpages/orders.php:304 adminpages/orders.php:130
2193
#: adminpages/orders.php:180 adminpages/orders.php:281
2194
#: adminpages/orders.php:291 adminpages/orders.php:292
2195
- #: adminpages/orders.php:302
2196
msgid "Error saving order."
2197
msgstr ""
2198
@@ -2200,8 +2215,9 @@ msgstr ""
2200
#: adminpages/orders.php:195 adminpages/orders.php:245
2201
#: adminpages/orders.php:317 adminpages/orders.php:321
2202
#: adminpages/orders.php:346 adminpages/orders.php:352
2203
- #: adminpages/orders.php:362 classes/class.memberorder.php:743
2204
- #: classes/class.memberorder.php:746
2205
msgid "Order"
2206
msgstr ""
2207
@@ -2209,6 +2225,7 @@ msgstr ""
2209
#: adminpages/orders.php:247 adminpages/orders.php:319
2210
#: adminpages/orders.php:323 adminpages/orders.php:348
2211
#: adminpages/orders.php:354 adminpages/orders.php:364
2212
msgid "New Order"
2213
msgstr ""
2214
@@ -2216,6 +2233,7 @@ msgstr ""
2216
#: adminpages/orders.php:270 adminpages/orders.php:342
2217
#: adminpages/orders.php:359 adminpages/orders.php:371
2218
#: adminpages/orders.php:390 adminpages/orders.php:401
2219
msgid "Randomly generated for you."
2220
msgstr ""
2221
@@ -2223,6 +2241,7 @@ msgstr ""
2223
#: adminpages/orders.php:275 adminpages/orders.php:347
2224
#: adminpages/orders.php:364 adminpages/orders.php:376
2225
#: adminpages/orders.php:395 adminpages/orders.php:406
2226
msgid "User ID"
2227
msgstr ""
2228
@@ -2230,6 +2249,7 @@ msgstr ""
2230
#: adminpages/orders.php:284 adminpages/orders.php:356
2231
#: adminpages/orders.php:376 adminpages/orders.php:385
2232
#: adminpages/orders.php:407 adminpages/orders.php:418
2233
msgid "Membership Level ID"
2234
msgstr ""
2235
@@ -2237,6 +2257,7 @@ msgstr ""
2237
#: adminpages/orders.php:293 adminpages/orders.php:365
2238
#: adminpages/orders.php:389 adminpages/orders.php:394
2239
#: adminpages/orders.php:420 adminpages/orders.php:431
2240
msgid "Billing Name"
2241
msgstr ""
2242
@@ -2244,6 +2265,7 @@ msgstr ""
2244
#: adminpages/orders.php:301 adminpages/orders.php:373
2245
#: adminpages/orders.php:401 adminpages/orders.php:402
2246
#: adminpages/orders.php:432 adminpages/orders.php:443
2247
msgid "Billing Street"
2248
msgstr ""
2249
@@ -2251,7 +2273,8 @@ msgstr ""
2251
#: adminpages/orders.php:258 adminpages/orders.php:308
2252
#: adminpages/orders.php:380 adminpages/orders.php:409
2253
#: adminpages/orders.php:412 adminpages/orders.php:443
2254
- #: adminpages/orders.php:454
2255
msgid "Billing City"
2256
msgstr ""
2257
@@ -2259,6 +2282,7 @@ msgstr ""
2259
#: adminpages/orders.php:315 adminpages/orders.php:387
2260
#: adminpages/orders.php:416 adminpages/orders.php:423
2261
#: adminpages/orders.php:454 adminpages/orders.php:465
2262
msgid "Billing State"
2263
msgstr ""
2264
@@ -2266,7 +2290,8 @@ msgstr ""
2266
#: adminpages/orders.php:272 adminpages/orders.php:322
2267
#: adminpages/orders.php:394 adminpages/orders.php:423
2268
#: adminpages/orders.php:434 adminpages/orders.php:465
2269
- #: adminpages/orders.php:476
2270
msgid "Billing Postal Code"
2271
msgstr ""
2272
@@ -2274,7 +2299,8 @@ msgstr ""
2274
#: adminpages/orders.php:279 adminpages/orders.php:329
2275
#: adminpages/orders.php:401 adminpages/orders.php:430
2276