WooCommerce Cart Abandonment Recovery - Version 1.2.14

Version Description

Download this release

Release Info

Developer cartflowswp
Plugin Icon 128x128 WooCommerce Cart Abandonment Recovery
Version 1.2.14
Comparing to
See all releases

Code changes from version 1.2.13 to 1.2.14

Files changed (48) hide show
  1. .eslintignore +11 -0
  2. .prettierignore +12 -0
  3. .prettierrc.js +14 -0
  4. .stylelintignore +8 -0
  5. .stylelintrc +11 -0
  6. admin/assets/css/admin-cart-abandonment-rtl.css +261 -269
  7. admin/assets/css/admin-cart-abandonment.css +261 -269
  8. {assets → admin/assets}/images/cartflows-icon.svg +0 -0
  9. admin/assets/images/cartflows-logo-small.jpg +0 -0
  10. {assets → admin/assets}/images/cartflows-logo.svg +0 -0
  11. {assets → admin/assets}/images/image-placeholder.png +0 -0
  12. admin/assets/js/admin-email-templates.js +37 -305
  13. admin/assets/js/admin-mce.js +17 -17
  14. admin/assets/js/admin-settings.js +258 -0
  15. admin/bsf-analytics/assets/css/minified/style-rtl.min.css +1 -0
  16. admin/bsf-analytics/assets/css/minified/style.min.css +1 -0
  17. admin/bsf-analytics/assets/css/unminified/style-rtl.css +21 -0
  18. admin/bsf-analytics/assets/css/unminified/style.css +21 -0
  19. admin/bsf-analytics/class-bsf-analytics-loader.php +118 -0
  20. admin/bsf-analytics/class-bsf-analytics-stats.php +256 -0
  21. admin/bsf-analytics/class-bsf-analytics.php +508 -0
  22. admin/bsf-analytics/version.json +4 -0
  23. changelog.txt +9 -1
  24. classes/class-cartflows-ca-admin-notices.php +99 -0
  25. classes/class-cartflows-ca-loader.php +28 -4
  26. classes/class-cartflows-ca-settings.php +65 -2
  27. classes/class-cartflows-ca-tabs.php +505 -0
  28. languages/woo-cart-abandonment-recovery.pot +381 -285
  29. lib/astra-notices/class-astra-notices.php +391 -0
  30. lib/astra-notices/notices.css +39 -0
  31. lib/astra-notices/notices.js +95 -0
  32. modules/cart-abandonment/assets/js/cart-abandonment-tracking.js +93 -91
  33. modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php +0 -2291
  34. modules/cart-abandonment/classes/class-cartflows-ca-cron.php +70 -0
  35. modules/cart-abandonment/{class-cartflows-ca-cart-abandonment-db.php → classes/class-cartflows-ca-database.php} +2 -2
  36. modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php +480 -0
  37. modules/cart-abandonment/{class-cartflows-ca-email-templates-table.php → classes/class-cartflows-ca-email-templates-table.php} +0 -0
  38. modules/cart-abandonment/{class-cartflows-ca-email-templates.php → classes/class-cartflows-ca-email-templates.php} +10 -3
  39. modules/cart-abandonment/classes/class-cartflows-ca-helper.php +210 -0
  40. modules/cart-abandonment/{class-cartflows-ca-module-loader.php → classes/class-cartflows-ca-module-loader.php} +18 -9
  41. modules/cart-abandonment/{class-cartflows-ca-cart-abandonment-table.php → classes/class-cartflows-ca-order-table.php} +1 -1
  42. modules/cart-abandonment/classes/class-cartflows-ca-setting-functions.php +169 -0
  43. modules/cart-abandonment/classes/class-cartflows-ca-tracking.php +1062 -0
  44. modules/cart-abandonment/includes/{admin/cartflows-ca-single-report-details.php → cartflows-ca-single-report-details.php} +1 -1
  45. modules/cart-abandonment/includes/{admin/cartflows-cart-abandonment-reports.php → cartflows-cart-abandonment-reports.php} +0 -0
  46. modules/cart-abandonment/includes/{admin/cartflows-cart-abandonment-tabs.php → cartflows-cart-abandonment-tabs.php} +4 -2
  47. readme.txt +11 -3
  48. woo-cart-abandonment-recovery.php +2 -2
.eslintignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .cache
2
+ node_modules
3
+ vendor
4
+ !.*.js
5
+ Gruntfile.js
6
+ assets/min-js/
7
+ *.json
8
+ lib/
9
+ *-rtl*
10
+ .github
11
+ bsf-analytics
.prettierignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .cache
2
+ vendor
3
+ node_modules
4
+ assets/min-js/
5
+ *.json
6
+ *.yml
7
+ lib/
8
+ *-rtl*
9
+ .github
10
+ Gruntfile.js
11
+ composer.lock
12
+ bsf-analytics
.prettierrc.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Import the default config file and expose it in the project root.
2
+ // Useful for editor integrations.
3
+
4
+ const config = require( '@wordpress/prettier-config' );
5
+ config.overrides = [
6
+ {
7
+ files: [ '*.css' ],
8
+ options: {
9
+ printWidth: 500,
10
+ singleQuote: false,
11
+ },
12
+ },
13
+ ];
14
+ module.exports = config;
.stylelintignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ .cache
2
+ node_modules
3
+ lib/
4
+ vendor
5
+ assets/min-css/
6
+ *-rtl*
7
+ .github
8
+ bsf-analytics
.stylelintrc ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "@wordpress/stylelint-config",
3
+ "rules": {
4
+ "function-parentheses-space-inside": "always",
5
+ "selector-pseudo-class-parentheses-space-inside": "always",
6
+ "rule-empty-line-before": null,
7
+ "comment-empty-line-before": null,
8
+ "selector-class-pattern": null,
9
+ "no-descending-specificity": null
10
+ }
11
+ }
admin/assets/css/admin-cart-abandonment-rtl.css CHANGED
@@ -1,430 +1,422 @@
1
  .wcf-ca-ibox {
2
- clear: both;
3
- margin-bottom: 25px;
4
- margin-top: 0;
5
- padding: 0;
6
  }
7
 
8
  .wcf-ca-ibox-title {
9
- background-color: white;
10
- border-image: none;
11
- border-width: 3px 0px 0;
12
- color: inherit;
13
- margin-bottom: 0;
14
- padding: 14px 15px 7px;
15
- min-height: 48px;
16
  }
17
 
18
  .wcf-ca-ibox-content {
19
- background-color: white;
20
- color: inherit;
21
- padding: 15px 20px 20px 20px;
22
- border-color: #d7dadc;
23
- border-image: none;
24
- border-style: solid solid none;
25
- border-width: 1px 0px;
26
  }
27
 
28
  .wcf-ca-raw {
29
- margin-right: -15px;
30
- margin-left: -15px;
31
  }
32
 
33
  .wcf-ca-grid-container {
34
- display: grid;
35
- grid-template-columns: 1fr 1fr 1fr;
36
- grid-gap: 20px;
37
  }
38
 
39
  .grid-container > div {
40
- background-color: rgba(255, 255, 255, 0.8);
41
- text-align: center;
42
- padding: 20px 0;
43
- font-size: 30px;
44
  }
45
 
46
-
47
  .wcf-ca-center-msg {
48
- margin: auto;
49
- width: 50%;
50
- padding: 10px;
51
- margin-top: 20px;
52
- text-align: center;
53
  }
54
 
55
-
56
  .wcf-ca-switch {
57
- cursor: pointer;
58
- text-indent: -999em;
59
- display: block;
60
- width: 38px;
61
- height: 22px;
62
- border-radius: 30px;
63
- border: none;
64
- position: relative;
65
- box-sizing: border-box;
66
- -webkit-transition: all .3s ease;
67
- transition: all .3s ease;
68
- box-shadow: inset 0 0 0 0 transparent;
69
  }
70
  .wcf-ca-switch:focus {
71
- outline: none;
72
- }
73
- .wcf-ca-switch:before {
74
- border-radius: 50%;
75
- background: #ffffff;
76
- content: '';
77
- position: absolute;
78
- display: block;
79
- width: 18px;
80
- height: 18px;
81
- top: 2px;
82
- right: 2px;
83
- -webkit-transition: all .15s ease;
84
- transition: all .15s ease;
85
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
86
  }
87
  .wcf-ca-switch[wcf-ca-template-switch="on"] {
88
- box-shadow: inset 0 0 0 11px #008000;
89
  }
90
- .wcf-ca-switch[wcf-ca-template-switch="on"]:before {
91
- -webkit-transform: translateX(-16px);
92
- transform: translateX(-16px);
93
  }
94
  .wcf-ca-switch[wcf-ca-template-switch="off"] {
95
- background: #ccc;
96
  }
97
  .wcf-ca-switch.wcap-loading {
98
- cursor: default;
99
- opacity: 0.5;
100
  }
101
 
102
- .wcf-ca-trigger-input{
103
- height: 28px;
104
- width: 40%;
105
- margin-left: 10px;
106
  }
107
 
108
  .wcf-ca-report-btn {
109
- padding: 15px 0px 15px 0px;
110
- display: flex;
111
- width: 100%;
112
- position: relative;
113
  }
114
 
115
  .wcf-ca-email-inputs {
116
- width: 25%;
117
  }
118
 
119
  .wcf-ca-coupon-inputs {
120
- width: 10%;
121
- vertical-align: middle;
122
  }
123
 
124
- .wcf-ca-filter-input{
125
- height: 28px;
126
- width: 7em;
127
- margin: 0;
128
- text-align: center;
129
  }
130
 
131
  .wcf-ca-left-report-field-group {
132
- flex: 1;
133
- margin-top: 0px;
134
  }
135
  .wcf-ca-right-report-field-group {
136
- flex: 1;
137
- text-align: left;
138
-
139
  }
140
- .wcf-search-orders{
141
- display: inline-block;
142
  }
143
- .wcf_export_orders{
144
- display: inline-block;
145
- vertical-align: bottom;
146
- padding-right: 5px;
147
  }
148
 
149
  .wcf-ca-report-table-row {
150
- color: #636363;
151
- border: 1px solid #e5e5e5;
152
  }
153
 
154
  /* Newly Added for the modification of the User Order detail window UI */
155
 
156
  /* Column Classes */
157
 
158
- .wcf-ca-column-one{
159
- width: 100%;
160
  }
161
 
162
  .wcf-ca-column-two,
163
- .wcf-ca-user-address{
164
- width: 50%;
165
  }
166
 
167
- .wcf-pull-left{
168
- float: right;
169
  }
170
  .wcf-ca-email-data,
171
- .wcf-ca-user-detail{
172
- border-radius: 3px;
173
- padding: 20px 25px;
174
- min-height: 330px;
175
- max-height: 420px;
176
- width: 100%;
177
- overflow: auto;
178
  }
179
 
180
- .wcf-ca-user-detail{
181
- max-height: 100%;
182
  }
183
 
184
- .wcf-ca-user-order{
185
- border-radius: 3px;
186
- padding: 25px 25px 30px 25px;
187
- overflow: hidden;
188
- height: auto;
189
- width: 100%;
190
  }
191
 
192
  /* Column Classes */
193
 
194
  /* Section classes */
195
 
196
- .wcf-ca-margin-right{
197
- margin-left: 13px;
198
  }
199
- .wcf-ca-margin-left{
200
- margin-right: 13px;
201
  }
202
 
203
- .wcf-ca-column{
204
- background-color: #fff;
205
- border-radius: 3px;
206
- display: flex;
207
  }
208
 
209
  /* Section Classes */
210
 
211
-
212
- .wcf-ca-right-report-field-group .back-button{
213
- height: auto;
214
- padding: 3px 5px 3px 10px;
215
  }
216
 
217
- .wcf-ca-left-report-field-group .back-button .dashicons{
218
- vertical-align: middle;
219
  }
220
 
221
- .wcf-ca-panel{
222
- cursor: default;
223
- /*background-color: #fff;*/
224
- border-radius: 5px;
225
- display: flex;
226
- padding: 0px;
227
- margin: 10px 0 25px;
228
  }
229
 
230
- .wcf-table{
231
- border: none;
232
- text-align: right;
233
- width: 100%;
234
  }
235
  .wcf-table tr,
236
  .wcf-table tr th,
237
- .wcf-table tr td{
238
- border: none;
239
  }
240
 
241
  .wcf-table tr th a,
242
- .wcf-table tr td a{
243
- text-decoration: none;
244
- cursor: pointer;
245
  }
246
 
247
- .wcf-table tr th{
248
- vertical-align: bottom;
249
- border-bottom: 2px solid #ddd;
250
  }
251
 
252
- .wcf-table tr td{
253
- border-bottom: 1px solid #ddd;
254
- line-height: 1.42857143;
255
- vertical-align: top;
256
- position: relative;
257
  }
258
 
259
- .wcf-ca-panel h2{
260
- margin: 0;
261
- font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
262
- font-size: 21px;
263
- font-weight: 400;
264
- line-height: 1.2;
265
- text-shadow: -1px 1px 1px #fff;
266
- padding: 10px 0 15px;
267
- margin: 0;
268
  }
269
 
270
- .wcf-ca-user-order table{
271
- width: 100%;
272
  }
273
 
274
- .wcf-ca-user-order table thead th{
275
- text-align: center;
276
- padding: 1em 3em;
277
- font-weight: 400;
278
- color: #999;
279
- /*background: #f8f8f8;*/
280
- -webkit-touch-callout: none;
281
- -webkit-user-select: none;
282
- -moz-user-select: none;
283
- -ms-user-select: none;
284
- user-select: none;
285
  }
286
 
287
- .wcf-ca-user-order table thead th:first-of-type{
288
- padding-right: 3.6em;
289
- text-align: right;
290
  }
291
 
292
  .wcf-ca-user-order table tbody tr td {
293
- text-align: center;
294
- padding: 1em 3em;
295
- vertical-align: middle;
296
  }
297
 
298
- .wcf-ca-user-order table tbody tr td:first-of-type{
299
- text-align: right;
300
- /*width: 38px;*/
301
- padding-right: 3em;
302
  }
303
 
304
-
305
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td:first-of-type,
306
  .wcf-ca-user-order table tbody tr#wcf-ca-other td:first-of-type,
307
  .wcf-ca-user-order table tbody tr#wcf-ca-shipping td:first-of-type,
308
  .wcf-ca-user-order table tbody tr#wcf-ca-cart-total td:first-of-type {
309
- text-align: left;
310
- font-weight: 600;
311
- width: 82%;
312
  }
313
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td {
314
- border-top: 1px solid #ccc;
315
  }
316
 
317
- .wcf-ca-tooltip-text.display_tool_tip{
318
- display: block;
319
  }
320
 
321
  .wcf-ca-tooltip-text::before {
322
- border-right: 5px solid transparent;
323
- border-left: 5px solid transparent;
324
- border-top: 5px solid transparent;
325
- border-bottom: 5px solid #444;
326
- bottom: auto;
327
- content: " ";
328
- font-size: 0;
329
- right: 25px;
330
- line-height: 0;
331
- margin-right: -5px;
332
- position: absolute;
333
- top: -10px;
334
- width: 0;
335
  }
336
 
337
  .wcf-ca-tooltip-text {
338
- background: #444;
339
- border-radius: 3px;
340
- color: #fff;
341
- height: auto;
342
- right: 0;
343
- margin-top: 10px;
344
- max-width: 150px;
345
- position: absolute;
346
- padding: 6px 10px;
347
- width: 100%;
348
- display: none;
349
- z-index: 10000;
350
  }
351
  /* Newly Added for the modification of the User Order detail window UI */
352
 
353
-
354
-
355
  .wcf-ca-tags {
356
- list-style: none;
357
- margin: 0;
358
- overflow: hidden;
359
- padding: 0;
360
  }
361
 
362
  .wcf-ca-tags li {
363
- float: right;
364
  }
365
 
366
  .wcf-ca-tag {
367
- background-color: #f16334;
368
- border-radius: 0 3px 3px 0;
369
- color: #fff;
370
- display: inline-block;
371
- height: 26px;
372
- line-height: 26px;
373
- padding: 0 23px 0 20px;
374
- position: relative;
375
- margin: 0 0 0px 5px;
376
- text-decoration: none;
377
- -webkit-transition: color 0.2s;
378
  }
379
 
380
  .wcf-ca-tag::before {
381
- background: #fff;
382
- border-radius: 10px;
383
- box-shadow: inset 0 1px rgba(0, 0, 0, 0.25);
384
- content: '';
385
- height: 6px;
386
- right: 10px;
387
- position: absolute;
388
- width: 6px;
389
- top: 10px;
390
  }
391
 
392
  .wcf-ca-tag::after {
393
- background: #fff;
394
- border-bottom: 13px solid transparent;
395
- border-right: 10px solid #f16334;;
396
- border-top: 13px solid transparent;
397
- content: '';
398
- position: absolute;
399
- left: 0;
400
- top: 0;
401
  }
402
 
403
  .wcf-ca-tag:hover {
404
- background-color: #f16334;
405
- color: white;
406
  }
407
 
408
  .wcf-ca-tag:hover::after {
409
- border-right-color: #f16334;
410
  }
411
 
412
  .wcf-sub-heading {
413
- font-weight: 400;
414
  }
415
 
416
- .wcf-ca-spinner{
417
- float:unset;
418
  }
419
 
420
- .wcf-ca-response-msg{
421
- vertical-align: sub;
422
- line-height: 28px;
423
- margin-right: 10px;
424
- font-weight: bold;
425
  }
426
 
427
- .wcf-ca-export-icon{
428
- line-height: 1.8;
429
- font-size:17px;
430
  }
1
  .wcf-ca-ibox {
2
+ clear: both;
3
+ margin-bottom: 25px;
4
+ margin-top: 0;
5
+ padding: 0;
6
  }
7
 
8
  .wcf-ca-ibox-title {
9
+ background-color: #fff;
10
+ border-image: none;
11
+ border-width: 3px 0 0;
12
+ color: inherit;
13
+ margin-bottom: 0;
14
+ padding: 14px 15px 7px;
15
+ min-height: 48px;
16
  }
17
 
18
  .wcf-ca-ibox-content {
19
+ background-color: #fff;
20
+ color: inherit;
21
+ padding: 15px 20px 20px 20px;
22
+ border-color: #d7dadc;
23
+ border-image: none;
24
+ border-style: solid solid none;
25
+ border-width: 1px 0;
26
  }
27
 
28
  .wcf-ca-raw {
29
+ margin-right: -15px;
30
+ margin-left: -15px;
31
  }
32
 
33
  .wcf-ca-grid-container {
34
+ display: grid;
35
+ grid-template-columns: 1fr 1fr 1fr;
36
+ grid-gap: 20px;
37
  }
38
 
39
  .grid-container > div {
40
+ background-color: rgba( 255, 255, 255, 0.8 );
41
+ text-align: center;
42
+ padding: 20px 0;
43
+ font-size: 30px;
44
  }
45
 
 
46
  .wcf-ca-center-msg {
47
+ margin: auto;
48
+ width: 50%;
49
+ padding: 10px;
50
+ margin-top: 20px;
51
+ text-align: center;
52
  }
53
 
 
54
  .wcf-ca-switch {
55
+ cursor: pointer;
56
+ text-indent: -999em;
57
+ display: block;
58
+ width: 38px;
59
+ height: 22px;
60
+ border-radius: 30px;
61
+ border: none;
62
+ position: relative;
63
+ box-sizing: border-box;
64
+ -webkit-transition: all 0.3s ease;
65
+ transition: all 0.3s ease;
66
+ box-shadow: inset 0 0 0 0 transparent;
67
  }
68
  .wcf-ca-switch:focus {
69
+ outline: none;
70
+ }
71
+ .wcf-ca-switch::before {
72
+ border-radius: 50%;
73
+ background: #fff;
74
+ content: "";
75
+ position: absolute;
76
+ display: block;
77
+ width: 18px;
78
+ height: 18px;
79
+ top: 2px;
80
+ right: 2px;
81
+ -webkit-transition: all 0.15s ease;
82
+ transition: all 0.15s ease;
83
+ box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.3 );
84
  }
85
  .wcf-ca-switch[wcf-ca-template-switch="on"] {
86
+ box-shadow: inset 0 0 0 11px #008000;
87
  }
88
+ .wcf-ca-switch[wcf-ca-template-switch="on"]::before {
89
+ -webkit-transform: translateX( -16px );
90
+ transform: translateX( -16px );
91
  }
92
  .wcf-ca-switch[wcf-ca-template-switch="off"] {
93
+ background: #ccc;
94
  }
95
  .wcf-ca-switch.wcap-loading {
96
+ cursor: default;
97
+ opacity: 0.5;
98
  }
99
 
100
+ .wcf-ca-trigger-input {
101
+ height: 28px;
102
+ width: 40%;
103
+ margin-left: 10px;
104
  }
105
 
106
  .wcf-ca-report-btn {
107
+ padding: 15px 0 15px 0;
108
+ display: flex;
109
+ width: 100%;
110
+ position: relative;
111
  }
112
 
113
  .wcf-ca-email-inputs {
114
+ width: 25%;
115
  }
116
 
117
  .wcf-ca-coupon-inputs {
118
+ width: 10%;
119
+ vertical-align: middle;
120
  }
121
 
122
+ .wcf-ca-filter-input {
123
+ height: 28px;
124
+ width: 7em;
125
+ margin: 0;
126
+ text-align: center;
127
  }
128
 
129
  .wcf-ca-left-report-field-group {
130
+ flex: 1;
131
+ margin-top: 0;
132
  }
133
  .wcf-ca-right-report-field-group {
134
+ flex: 1;
135
+ text-align: left;
 
136
  }
137
+ .wcf-search-orders {
138
+ display: inline-block;
139
  }
140
+ .wcf_export_orders {
141
+ display: inline-block;
142
+ vertical-align: bottom;
143
+ padding-right: 5px;
144
  }
145
 
146
  .wcf-ca-report-table-row {
147
+ color: #636363;
148
+ border: 1px solid #e5e5e5;
149
  }
150
 
151
  /* Newly Added for the modification of the User Order detail window UI */
152
 
153
  /* Column Classes */
154
 
155
+ .wcf-ca-column-one {
156
+ width: 100%;
157
  }
158
 
159
  .wcf-ca-column-two,
160
+ .wcf-ca-user-address {
161
+ width: 50%;
162
  }
163
 
164
+ .wcf-pull-left {
165
+ float: right;
166
  }
167
  .wcf-ca-email-data,
168
+ .wcf-ca-user-detail {
169
+ border-radius: 3px;
170
+ padding: 20px 25px;
171
+ min-height: 330px;
172
+ max-height: 420px;
173
+ width: 100%;
174
+ overflow: auto;
175
  }
176
 
177
+ .wcf-ca-user-detail {
178
+ max-height: 100%;
179
  }
180
 
181
+ .wcf-ca-user-order {
182
+ border-radius: 3px;
183
+ padding: 25px 25px 30px 25px;
184
+ overflow: hidden;
185
+ height: auto;
186
+ width: 100%;
187
  }
188
 
189
  /* Column Classes */
190
 
191
  /* Section classes */
192
 
193
+ .wcf-ca-margin-right {
194
+ margin-left: 13px;
195
  }
196
+ .wcf-ca-margin-left {
197
+ margin-right: 13px;
198
  }
199
 
200
+ .wcf-ca-column {
201
+ background-color: #fff;
202
+ border-radius: 3px;
203
+ display: flex;
204
  }
205
 
206
  /* Section Classes */
207
 
208
+ .wcf-ca-right-report-field-group .back-button {
209
+ height: auto;
210
+ padding: 3px 5px 3px 10px;
 
211
  }
212
 
213
+ .wcf-ca-left-report-field-group .back-button .dashicons {
214
+ vertical-align: middle;
215
  }
216
 
217
+ .wcf-ca-panel {
218
+ cursor: default;
219
+ /*background-color: #fff;*/
220
+ border-radius: 5px;
221
+ display: flex;
222
+ padding: 0;
223
+ margin: 10px 0 25px;
224
  }
225
 
226
+ .wcf-table {
227
+ border: none;
228
+ text-align: right;
229
+ width: 100%;
230
  }
231
  .wcf-table tr,
232
  .wcf-table tr th,
233
+ .wcf-table tr td {
234
+ border: none;
235
  }
236
 
237
  .wcf-table tr th a,
238
+ .wcf-table tr td a {
239
+ text-decoration: none;
240
+ cursor: pointer;
241
  }
242
 
243
+ .wcf-table tr th {
244
+ vertical-align: bottom;
245
+ border-bottom: 2px solid #ddd;
246
  }
247
 
248
+ .wcf-table tr td {
249
+ border-bottom: 1px solid #ddd;
250
+ line-height: 1.42857143;
251
+ vertical-align: top;
252
+ position: relative;
253
  }
254
 
255
+ .wcf-ca-panel h2 {
256
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
257
+ font-size: 21px;
258
+ font-weight: 400;
259
+ line-height: 1.2;
260
+ text-shadow: -1px 1px 1px #fff;
261
+ padding: 10px 0 15px;
262
+ margin: 0;
 
263
  }
264
 
265
+ .wcf-ca-user-order table {
266
+ width: 100%;
267
  }
268
 
269
+ .wcf-ca-user-order table thead th {
270
+ text-align: center;
271
+ padding: 1em 3em;
272
+ font-weight: 400;
273
+ color: #999;
274
+ /*background: #f8f8f8;*/
275
+ -webkit-touch-callout: none;
276
+ -webkit-user-select: none;
277
+ -moz-user-select: none;
278
+ -ms-user-select: none;
279
+ user-select: none;
280
  }
281
 
282
+ .wcf-ca-user-order table thead th:first-of-type {
283
+ padding-right: 3.6em;
284
+ text-align: right;
285
  }
286
 
287
  .wcf-ca-user-order table tbody tr td {
288
+ text-align: center;
289
+ padding: 1em 3em;
290
+ vertical-align: middle;
291
  }
292
 
293
+ .wcf-ca-user-order table tbody tr td:first-of-type {
294
+ text-align: right;
295
+ /*width: 38px;*/
296
+ padding-right: 3em;
297
  }
298
 
 
299
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td:first-of-type,
300
  .wcf-ca-user-order table tbody tr#wcf-ca-other td:first-of-type,
301
  .wcf-ca-user-order table tbody tr#wcf-ca-shipping td:first-of-type,
302
  .wcf-ca-user-order table tbody tr#wcf-ca-cart-total td:first-of-type {
303
+ text-align: left;
304
+ font-weight: 600;
305
+ width: 82%;
306
  }
307
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td {
308
+ border-top: 1px solid #ccc;
309
  }
310
 
311
+ .wcf-ca-tooltip-text.display_tool_tip {
312
+ display: block;
313
  }
314
 
315
  .wcf-ca-tooltip-text::before {
316
+ border-right: 5px solid transparent;
317
+ border-left: 5px solid transparent;
318
+ border-top: 5px solid transparent;
319
+ border-bottom: 5px solid #444;
320
+ bottom: auto;
321
+ content: " ";
322
+ font-size: 0;
323
+ right: 25px;
324
+ line-height: 0;
325
+ margin-right: -5px;
326
+ position: absolute;
327
+ top: -10px;
328
+ width: 0;
329
  }
330
 
331
  .wcf-ca-tooltip-text {
332
+ background: #444;
333
+ border-radius: 3px;
334
+ color: #fff;
335
+ height: auto;
336
+ right: 0;
337
+ margin-top: 10px;
338
+ max-width: 150px;
339
+ position: absolute;
340
+ padding: 6px 10px;
341
+ width: 100%;
342
+ display: none;
343
+ z-index: 10000;
344
  }
345
  /* Newly Added for the modification of the User Order detail window UI */
346
 
 
 
347
  .wcf-ca-tags {
348
+ list-style: none;
349
+ margin: 0;
350
+ overflow: hidden;
351
+ padding: 0;
352
  }
353
 
354
  .wcf-ca-tags li {
355
+ float: right;
356
  }
357
 
358
  .wcf-ca-tag {
359
+ background-color: #f16334;
360
+ border-radius: 0 3px 3px 0;
361
+ color: #fff;
362
+ display: inline-block;
363
+ height: 26px;
364
+ line-height: 26px;
365
+ padding: 0 23px 0 20px;
366
+ position: relative;
367
+ margin: 0 0 0 5px;
368
+ text-decoration: none;
369
+ -webkit-transition: color 0.2s;
370
  }
371
 
372
  .wcf-ca-tag::before {
373
+ background: #fff;
374
+ border-radius: 10px;
375
+ box-shadow: inset 0 1px rgba( 0, 0, 0, 0.25 );
376
+ content: "";
377
+ height: 6px;
378
+ right: 10px;
379
+ position: absolute;
380
+ width: 6px;
381
+ top: 10px;
382
  }
383
 
384
  .wcf-ca-tag::after {
385
+ background: #fff;
386
+ border-bottom: 13px solid transparent;
387
+ border-right: 10px solid #f16334;
388
+ border-top: 13px solid transparent;
389
+ content: "";
390
+ position: absolute;
391
+ left: 0;
392
+ top: 0;
393
  }
394
 
395
  .wcf-ca-tag:hover {
396
+ background-color: #f16334;
397
+ color: #fff;
398
  }
399
 
400
  .wcf-ca-tag:hover::after {
401
+ border-right-color: #f16334;
402
  }
403
 
404
  .wcf-sub-heading {
405
+ font-weight: 400;
406
  }
407
 
408
+ .wcf-ca-spinner {
409
+ float: unset;
410
  }
411
 
412
+ .wcf-ca-response-msg {
413
+ vertical-align: sub;
414
+ line-height: 28px;
415
+ margin-right: 10px;
416
+ font-weight: 700;
417
  }
418
 
419
+ .wcf-ca-export-icon {
420
+ line-height: 1.8;
421
+ font-size: 17px;
422
  }
admin/assets/css/admin-cart-abandonment.css CHANGED
@@ -1,430 +1,422 @@
1
  .wcf-ca-ibox {
2
- clear: both;
3
- margin-bottom: 25px;
4
- margin-top: 0;
5
- padding: 0;
6
  }
7
 
8
  .wcf-ca-ibox-title {
9
- background-color: white;
10
- border-image: none;
11
- border-width: 3px 0px 0;
12
- color: inherit;
13
- margin-bottom: 0;
14
- padding: 14px 15px 7px;
15
- min-height: 48px;
16
  }
17
 
18
  .wcf-ca-ibox-content {
19
- background-color: white;
20
- color: inherit;
21
- padding: 15px 20px 20px 20px;
22
- border-color: #d7dadc;
23
- border-image: none;
24
- border-style: solid solid none;
25
- border-width: 1px 0px;
26
  }
27
 
28
  .wcf-ca-raw {
29
- margin-left: -15px;
30
- margin-right: -15px;
31
  }
32
 
33
  .wcf-ca-grid-container {
34
- display: grid;
35
- grid-template-columns: 1fr 1fr 1fr;
36
- grid-gap: 20px;
37
  }
38
 
39
  .grid-container > div {
40
- background-color: rgba(255, 255, 255, 0.8);
41
- text-align: center;
42
- padding: 20px 0;
43
- font-size: 30px;
44
  }
45
 
46
-
47
  .wcf-ca-center-msg {
48
- margin: auto;
49
- width: 50%;
50
- padding: 10px;
51
- margin-top: 20px;
52
- text-align: center;
53
  }
54
 
55
-
56
  .wcf-ca-switch {
57
- cursor: pointer;
58
- text-indent: -999em;
59
- display: block;
60
- width: 38px;
61
- height: 22px;
62
- border-radius: 30px;
63
- border: none;
64
- position: relative;
65
- box-sizing: border-box;
66
- -webkit-transition: all .3s ease;
67
- transition: all .3s ease;
68
- box-shadow: inset 0 0 0 0 transparent;
69
  }
70
  .wcf-ca-switch:focus {
71
- outline: none;
72
- }
73
- .wcf-ca-switch:before {
74
- border-radius: 50%;
75
- background: #ffffff;
76
- content: '';
77
- position: absolute;
78
- display: block;
79
- width: 18px;
80
- height: 18px;
81
- top: 2px;
82
- left: 2px;
83
- -webkit-transition: all .15s ease;
84
- transition: all .15s ease;
85
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
86
  }
87
  .wcf-ca-switch[wcf-ca-template-switch="on"] {
88
- box-shadow: inset 0 0 0 11px #008000;
89
  }
90
- .wcf-ca-switch[wcf-ca-template-switch="on"]:before {
91
- -webkit-transform: translateX(16px);
92
- transform: translateX(16px);
93
  }
94
  .wcf-ca-switch[wcf-ca-template-switch="off"] {
95
- background: #ccc;
96
  }
97
  .wcf-ca-switch.wcap-loading {
98
- cursor: default;
99
- opacity: 0.5;
100
  }
101
 
102
- .wcf-ca-trigger-input{
103
- height: 28px;
104
- width: 40%;
105
- margin-right: 10px;
106
  }
107
 
108
  .wcf-ca-report-btn {
109
- padding: 15px 0px 15px 0px;
110
- display: flex;
111
- width: 100%;
112
- position: relative;
113
  }
114
 
115
  .wcf-ca-email-inputs {
116
- width: 25%;
117
  }
118
 
119
  .wcf-ca-coupon-inputs {
120
- width: 10%;
121
- vertical-align: middle;
122
  }
123
 
124
- .wcf-ca-filter-input{
125
- height: 28px;
126
- width: 7em;
127
- margin: 0;
128
- text-align: center;
129
  }
130
 
131
  .wcf-ca-left-report-field-group {
132
- flex: 1;
133
- margin-top: 0px;
134
  }
135
  .wcf-ca-right-report-field-group {
136
- flex: 1;
137
- text-align: right;
138
-
139
  }
140
- .wcf-search-orders{
141
- display: inline-block;
142
  }
143
- .wcf_export_orders{
144
- display: inline-block;
145
- vertical-align: bottom;
146
- padding-left: 5px;
147
  }
148
 
149
  .wcf-ca-report-table-row {
150
- color: #636363;
151
- border: 1px solid #e5e5e5;
152
  }
153
 
154
  /* Newly Added for the modification of the User Order detail window UI */
155
 
156
  /* Column Classes */
157
 
158
- .wcf-ca-column-one{
159
- width: 100%;
160
  }
161
 
162
  .wcf-ca-column-two,
163
- .wcf-ca-user-address{
164
- width: 50%;
165
  }
166
 
167
- .wcf-pull-left{
168
- float: left;
169
  }
170
  .wcf-ca-email-data,
171
- .wcf-ca-user-detail{
172
- border-radius: 3px;
173
- padding: 20px 25px;
174
- min-height: 330px;
175
- max-height: 420px;
176
- width: 100%;
177
- overflow: auto;
178
  }
179
 
180
- .wcf-ca-user-detail{
181
- max-height: 100%;
182
  }
183
 
184
- .wcf-ca-user-order{
185
- border-radius: 3px;
186
- padding: 25px 25px 30px 25px;
187
- overflow: hidden;
188
- height: auto;
189
- width: 100%;
190
  }
191
 
192
  /* Column Classes */
193
 
194
  /* Section classes */
195
 
196
- .wcf-ca-margin-right{
197
- margin-right: 13px;
198
  }
199
- .wcf-ca-margin-left{
200
- margin-left: 13px;
201
  }
202
 
203
- .wcf-ca-column{
204
- background-color: #fff;
205
- border-radius: 3px;
206
- display: flex;
207
  }
208
 
209
  /* Section Classes */
210
 
211
-
212
- .wcf-ca-right-report-field-group .back-button{
213
- height: auto;
214
- padding: 3px 10px 3px 5px;
215
  }
216
 
217
- .wcf-ca-left-report-field-group .back-button .dashicons{
218
- vertical-align: middle;
219
  }
220
 
221
- .wcf-ca-panel{
222
- cursor: default;
223
- /*background-color: #fff;*/
224
- border-radius: 5px;
225
- display: flex;
226
- padding: 0px;
227
- margin: 10px 0 25px;
228
  }
229
 
230
- .wcf-table{
231
- border: none;
232
- text-align: left;
233
- width: 100%;
234
  }
235
  .wcf-table tr,
236
  .wcf-table tr th,
237
- .wcf-table tr td{
238
- border: none;
239
  }
240
 
241
  .wcf-table tr th a,
242
- .wcf-table tr td a{
243
- text-decoration: none;
244
- cursor: pointer;
245
  }
246
 
247
- .wcf-table tr th{
248
- vertical-align: bottom;
249
- border-bottom: 2px solid #ddd;
250
  }
251
 
252
- .wcf-table tr td{
253
- border-bottom: 1px solid #ddd;
254
- line-height: 1.42857143;
255
- vertical-align: top;
256
- position: relative;
257
  }
258
 
259
- .wcf-ca-panel h2{
260
- margin: 0;
261
- font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
262
- font-size: 21px;
263
- font-weight: 400;
264
- line-height: 1.2;
265
- text-shadow: 1px 1px 1px #fff;
266
- padding: 10px 0 15px;
267
- margin: 0;
268
  }
269
 
270
- .wcf-ca-user-order table{
271
- width: 100%;
272
  }
273
 
274
- .wcf-ca-user-order table thead th{
275
- text-align: center;
276
- padding: 1em 3em;
277
- font-weight: 400;
278
- color: #999;
279
- /*background: #f8f8f8;*/
280
- -webkit-touch-callout: none;
281
- -webkit-user-select: none;
282
- -moz-user-select: none;
283
- -ms-user-select: none;
284
- user-select: none;
285
  }
286
 
287
- .wcf-ca-user-order table thead th:first-of-type{
288
- padding-left: 3.6em;
289
- text-align: left;
290
  }
291
 
292
  .wcf-ca-user-order table tbody tr td {
293
- text-align: center;
294
- padding: 1em 3em;
295
- vertical-align: middle;
296
  }
297
 
298
- .wcf-ca-user-order table tbody tr td:first-of-type{
299
- text-align: left;
300
- /*width: 38px;*/
301
- padding-left: 3em;
302
  }
303
 
304
-
305
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td:first-of-type,
306
  .wcf-ca-user-order table tbody tr#wcf-ca-other td:first-of-type,
307
  .wcf-ca-user-order table tbody tr#wcf-ca-shipping td:first-of-type,
308
  .wcf-ca-user-order table tbody tr#wcf-ca-cart-total td:first-of-type {
309
- text-align: right;
310
- font-weight: 600;
311
- width: 82%;
312
  }
313
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td {
314
- border-top: 1px solid #ccc;
315
  }
316
 
317
- .wcf-ca-tooltip-text.display_tool_tip{
318
- display: block;
319
  }
320
 
321
  .wcf-ca-tooltip-text::before {
322
- border-left: 5px solid transparent;
323
- border-right: 5px solid transparent;
324
- border-top: 5px solid transparent;
325
- border-bottom: 5px solid #444;
326
- bottom: auto;
327
- content: " ";
328
- font-size: 0;
329
- left: 25px;
330
- line-height: 0;
331
- margin-left: -5px;
332
- position: absolute;
333
- top: -10px;
334
- width: 0;
335
  }
336
 
337
  .wcf-ca-tooltip-text {
338
- background: #444;
339
- border-radius: 3px;
340
- color: #fff;
341
- height: auto;
342
- left: 0;
343
- margin-top: 10px;
344
- max-width: 150px;
345
- position: absolute;
346
- padding: 6px 10px;
347
- width: 100%;
348
- display: none;
349
- z-index: 10000;
350
  }
351
  /* Newly Added for the modification of the User Order detail window UI */
352
 
353
-
354
-
355
  .wcf-ca-tags {
356
- list-style: none;
357
- margin: 0;
358
- overflow: hidden;
359
- padding: 0;
360
  }
361
 
362
  .wcf-ca-tags li {
363
- float: left;
364
  }
365
 
366
  .wcf-ca-tag {
367
- background-color: #f16334;
368
- border-radius: 3px 0 0 3px;
369
- color: #fff;
370
- display: inline-block;
371
- height: 26px;
372
- line-height: 26px;
373
- padding: 0 20px 0 23px;
374
- position: relative;
375
- margin: 0 5px 0px 0;
376
- text-decoration: none;
377
- -webkit-transition: color 0.2s;
378
  }
379
 
380
  .wcf-ca-tag::before {
381
- background: #fff;
382
- border-radius: 10px;
383
- box-shadow: inset 0 1px rgba(0, 0, 0, 0.25);
384
- content: '';
385
- height: 6px;
386
- left: 10px;
387
- position: absolute;
388
- width: 6px;
389
- top: 10px;
390
  }
391
 
392
  .wcf-ca-tag::after {
393
- background: #fff;
394
- border-bottom: 13px solid transparent;
395
- border-left: 10px solid #f16334;;
396
- border-top: 13px solid transparent;
397
- content: '';
398
- position: absolute;
399
- right: 0;
400
- top: 0;
401
  }
402
 
403
  .wcf-ca-tag:hover {
404
- background-color: #f16334;
405
- color: white;
406
  }
407
 
408
  .wcf-ca-tag:hover::after {
409
- border-left-color: #f16334;
410
  }
411
 
412
  .wcf-sub-heading {
413
- font-weight: 400;
414
  }
415
 
416
- .wcf-ca-spinner{
417
- float:unset;
418
  }
419
 
420
- .wcf-ca-response-msg{
421
- vertical-align: sub;
422
- line-height: 28px;
423
- margin-left: 10px;
424
- font-weight: bold;
425
  }
426
 
427
- .wcf-ca-export-icon{
428
- line-height: 1.8;
429
- font-size:17px;
430
  }
1
  .wcf-ca-ibox {
2
+ clear: both;
3
+ margin-bottom: 25px;
4
+ margin-top: 0;
5
+ padding: 0;
6
  }
7
 
8
  .wcf-ca-ibox-title {
9
+ background-color: #fff;
10
+ border-image: none;
11
+ border-width: 3px 0 0;
12
+ color: inherit;
13
+ margin-bottom: 0;
14
+ padding: 14px 15px 7px;
15
+ min-height: 48px;
16
  }
17
 
18
  .wcf-ca-ibox-content {
19
+ background-color: #fff;
20
+ color: inherit;
21
+ padding: 15px 20px 20px 20px;
22
+ border-color: #d7dadc;
23
+ border-image: none;
24
+ border-style: solid solid none;
25
+ border-width: 1px 0;
26
  }
27
 
28
  .wcf-ca-raw {
29
+ margin-left: -15px;
30
+ margin-right: -15px;
31
  }
32
 
33
  .wcf-ca-grid-container {
34
+ display: grid;
35
+ grid-template-columns: 1fr 1fr 1fr;
36
+ grid-gap: 20px;
37
  }
38
 
39
  .grid-container > div {
40
+ background-color: rgba( 255, 255, 255, 0.8 );
41
+ text-align: center;
42
+ padding: 20px 0;
43
+ font-size: 30px;
44
  }
45
 
 
46
  .wcf-ca-center-msg {
47
+ margin: auto;
48
+ width: 50%;
49
+ padding: 10px;
50
+ margin-top: 20px;
51
+ text-align: center;
52
  }
53
 
 
54
  .wcf-ca-switch {
55
+ cursor: pointer;
56
+ text-indent: -999em;
57
+ display: block;
58
+ width: 38px;
59
+ height: 22px;
60
+ border-radius: 30px;
61
+ border: none;
62
+ position: relative;
63
+ box-sizing: border-box;
64
+ -webkit-transition: all 0.3s ease;
65
+ transition: all 0.3s ease;
66
+ box-shadow: inset 0 0 0 0 transparent;
67
  }
68
  .wcf-ca-switch:focus {
69
+ outline: none;
70
+ }
71
+ .wcf-ca-switch::before {
72
+ border-radius: 50%;
73
+ background: #fff;
74
+ content: "";
75
+ position: absolute;
76
+ display: block;
77
+ width: 18px;
78
+ height: 18px;
79
+ top: 2px;
80
+ left: 2px;
81
+ -webkit-transition: all 0.15s ease;
82
+ transition: all 0.15s ease;
83
+ box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.3 );
84
  }
85
  .wcf-ca-switch[wcf-ca-template-switch="on"] {
86
+ box-shadow: inset 0 0 0 11px #008000;
87
  }
88
+ .wcf-ca-switch[wcf-ca-template-switch="on"]::before {
89
+ -webkit-transform: translateX( 16px );
90
+ transform: translateX( 16px );
91
  }
92
  .wcf-ca-switch[wcf-ca-template-switch="off"] {
93
+ background: #ccc;
94
  }
95
  .wcf-ca-switch.wcap-loading {
96
+ cursor: default;
97
+ opacity: 0.5;
98
  }
99
 
100
+ .wcf-ca-trigger-input {
101
+ height: 28px;
102
+ width: 40%;
103
+ margin-right: 10px;
104
  }
105
 
106
  .wcf-ca-report-btn {
107
+ padding: 15px 0 15px 0;
108
+ display: flex;
109
+ width: 100%;
110
+ position: relative;
111
  }
112
 
113
  .wcf-ca-email-inputs {
114
+ width: 25%;
115
  }
116
 
117
  .wcf-ca-coupon-inputs {
118
+ width: 10%;
119
+ vertical-align: middle;
120
  }
121
 
122
+ .wcf-ca-filter-input {
123
+ height: 28px;
124
+ width: 7em;
125
+ margin: 0;
126
+ text-align: center;
127
  }
128
 
129
  .wcf-ca-left-report-field-group {
130
+ flex: 1;
131
+ margin-top: 0;
132
  }
133
  .wcf-ca-right-report-field-group {
134
+ flex: 1;
135
+ text-align: right;
 
136
  }
137
+ .wcf-search-orders {
138
+ display: inline-block;
139
  }
140
+ .wcf_export_orders {
141
+ display: inline-block;
142
+ vertical-align: bottom;
143
+ padding-left: 5px;
144
  }
145
 
146
  .wcf-ca-report-table-row {
147
+ color: #636363;
148
+ border: 1px solid #e5e5e5;
149
  }
150
 
151
  /* Newly Added for the modification of the User Order detail window UI */
152
 
153
  /* Column Classes */
154
 
155
+ .wcf-ca-column-one {
156
+ width: 100%;
157
  }
158
 
159
  .wcf-ca-column-two,
160
+ .wcf-ca-user-address {
161
+ width: 50%;
162
  }
163
 
164
+ .wcf-pull-left {
165
+ float: left;
166
  }
167
  .wcf-ca-email-data,
168
+ .wcf-ca-user-detail {
169
+ border-radius: 3px;
170
+ padding: 20px 25px;
171
+ min-height: 330px;
172
+ max-height: 420px;
173
+ width: 100%;
174
+ overflow: auto;
175
  }
176
 
177
+ .wcf-ca-user-detail {
178
+ max-height: 100%;
179
  }
180
 
181
+ .wcf-ca-user-order {
182
+ border-radius: 3px;
183
+ padding: 25px 25px 30px 25px;
184
+ overflow: hidden;
185
+ height: auto;
186
+ width: 100%;
187
  }
188
 
189
  /* Column Classes */
190
 
191
  /* Section classes */
192
 
193
+ .wcf-ca-margin-right {
194
+ margin-right: 13px;
195
  }
196
+ .wcf-ca-margin-left {
197
+ margin-left: 13px;
198
  }
199
 
200
+ .wcf-ca-column {
201
+ background-color: #fff;
202
+ border-radius: 3px;
203
+ display: flex;
204
  }
205
 
206
  /* Section Classes */
207
 
208
+ .wcf-ca-right-report-field-group .back-button {
209
+ height: auto;
210
+ padding: 3px 10px 3px 5px;
 
211
  }
212
 
213
+ .wcf-ca-left-report-field-group .back-button .dashicons {
214
+ vertical-align: middle;
215
  }
216
 
217
+ .wcf-ca-panel {
218
+ cursor: default;
219
+ /*background-color: #fff;*/
220
+ border-radius: 5px;
221
+ display: flex;
222
+ padding: 0;
223
+ margin: 10px 0 25px;
224
  }
225
 
226
+ .wcf-table {
227
+ border: none;
228
+ text-align: left;
229
+ width: 100%;
230
  }
231
  .wcf-table tr,
232
  .wcf-table tr th,
233
+ .wcf-table tr td {
234
+ border: none;
235
  }
236
 
237
  .wcf-table tr th a,
238
+ .wcf-table tr td a {
239
+ text-decoration: none;
240
+ cursor: pointer;
241
  }
242
 
243
+ .wcf-table tr th {
244
+ vertical-align: bottom;
245
+ border-bottom: 2px solid #ddd;
246
  }
247
 
248
+ .wcf-table tr td {
249
+ border-bottom: 1px solid #ddd;
250
+ line-height: 1.42857143;
251
+ vertical-align: top;
252
+ position: relative;
253
  }
254
 
255
+ .wcf-ca-panel h2 {
256
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
257
+ font-size: 21px;
258
+ font-weight: 400;
259
+ line-height: 1.2;
260
+ text-shadow: 1px 1px 1px #fff;
261
+ padding: 10px 0 15px;
262
+ margin: 0;
 
263
  }
264
 
265
+ .wcf-ca-user-order table {
266
+ width: 100%;
267
  }
268
 
269
+ .wcf-ca-user-order table thead th {
270
+ text-align: center;
271
+ padding: 1em 3em;
272
+ font-weight: 400;
273
+ color: #999;
274
+ /*background: #f8f8f8;*/
275
+ -webkit-touch-callout: none;
276
+ -webkit-user-select: none;
277
+ -moz-user-select: none;
278
+ -ms-user-select: none;
279
+ user-select: none;
280
  }
281
 
282
+ .wcf-ca-user-order table thead th:first-of-type {
283
+ padding-left: 3.6em;
284
+ text-align: left;
285
  }
286
 
287
  .wcf-ca-user-order table tbody tr td {
288
+ text-align: center;
289
+ padding: 1em 3em;
290
+ vertical-align: middle;
291
  }
292
 
293
+ .wcf-ca-user-order table tbody tr td:first-of-type {
294
+ text-align: left;
295
+ /*width: 38px;*/
296
+ padding-left: 3em;
297
  }
298
 
 
299
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td:first-of-type,
300
  .wcf-ca-user-order table tbody tr#wcf-ca-other td:first-of-type,
301
  .wcf-ca-user-order table tbody tr#wcf-ca-shipping td:first-of-type,
302
  .wcf-ca-user-order table tbody tr#wcf-ca-cart-total td:first-of-type {
303
+ text-align: right;
304
+ font-weight: 600;
305
+ width: 82%;
306
  }
307
  .wcf-ca-user-order table tbody tr#wcf-ca-discount td {
308
+ border-top: 1px solid #ccc;
309
  }
310
 
311
+ .wcf-ca-tooltip-text.display_tool_tip {
312
+ display: block;
313
  }
314
 
315
  .wcf-ca-tooltip-text::before {
316
+ border-left: 5px solid transparent;
317
+ border-right: 5px solid transparent;
318
+ border-top: 5px solid transparent;
319
+ border-bottom: 5px solid #444;
320
+ bottom: auto;
321
+ content: " ";
322
+ font-size: 0;
323
+ left: 25px;
324
+ line-height: 0;
325
+ margin-left: -5px;
326
+ position: absolute;
327
+ top: -10px;
328
+ width: 0;
329
  }
330
 
331
  .wcf-ca-tooltip-text {
332
+ background: #444;
333
+ border-radius: 3px;
334
+ color: #fff;
335
+ height: auto;
336
+ left: 0;
337
+ margin-top: 10px;
338
+ max-width: 150px;
339
+ position: absolute;
340
+ padding: 6px 10px;
341
+ width: 100%;
342
+ display: none;
343
+ z-index: 10000;
344
  }
345
  /* Newly Added for the modification of the User Order detail window UI */
346
 
 
 
347
  .wcf-ca-tags {
348
+ list-style: none;
349
+ margin: 0;
350
+ overflow: hidden;
351
+ padding: 0;
352
  }
353
 
354
  .wcf-ca-tags li {
355
+ float: left;
356
  }
357
 
358
  .wcf-ca-tag {
359
+ background-color: #f16334;
360
+ border-radius: 3px 0 0 3px;
361
+ color: #fff;
362
+ display: inline-block;
363
+ height: 26px;
364
+ line-height: 26px;
365
+ padding: 0 20px 0 23px;
366
+ position: relative;
367
+ margin: 0 5px 0 0;
368
+ text-decoration: none;
369
+ -webkit-transition: color 0.2s;
370
  }
371
 
372
  .wcf-ca-tag::before {
373
+ background: #fff;
374
+ border-radius: 10px;
375
+ box-shadow: inset 0 1px rgba( 0, 0, 0, 0.25 );
376
+ content: "";
377
+ height: 6px;
378
+ left: 10px;
379
+ position: absolute;
380
+ width: 6px;
381
+ top: 10px;
382
  }
383
 
384
  .wcf-ca-tag::after {
385
+ background: #fff;
386
+ border-bottom: 13px solid transparent;
387
+ border-left: 10px solid #f16334;
388
+ border-top: 13px solid transparent;
389
+ content: "";
390
+ position: absolute;
391
+ right: 0;
392
+ top: 0;
393
  }
394
 
395
  .wcf-ca-tag:hover {
396
+ background-color: #f16334;
397
+ color: #fff;
398
  }
399
 
400
  .wcf-ca-tag:hover::after {
401
+ border-left-color: #f16334;
402
  }
403
 
404
  .wcf-sub-heading {
405
+ font-weight: 400;
406
  }
407
 
408
+ .wcf-ca-spinner {
409
+ float: unset;
410
  }
411
 
412
+ .wcf-ca-response-msg {
413
+ vertical-align: sub;
414
+ line-height: 28px;
415
+ margin-left: 10px;
416
+ font-weight: 700;
417
  }
418
 
419
+ .wcf-ca-export-icon {
420
+ line-height: 1.8;
421
+ font-size: 17px;
422
  }
{assets → admin/assets}/images/cartflows-icon.svg RENAMED
File without changes
admin/assets/images/cartflows-logo-small.jpg ADDED
Binary file
{assets → admin/assets}/images/cartflows-logo.svg RENAMED
File without changes
{assets → admin/assets}/images/image-placeholder.png RENAMED
File without changes
admin/assets/js/admin-email-templates.js CHANGED
@@ -1,147 +1,6 @@
1
- ( function ( $ ) {
2
- CartAbandonmentSettings = {
3
- init: function () {
4
- $( '#wcf_ca_custom_filter_from' )
5
- .datepicker( {
6
- dateFormat: 'yy-mm-dd',
7
- maxDate: '0',
8
- onClose: function ( selectedDate ) {
9
- jQuery( '#wcf_ca_custom_filter_to' ).datepicker(
10
- 'option',
11
- 'minDate',
12
- selectedDate
13
- );
14
- },
15
- } )
16
- .attr( 'readonly', 'readonly' )
17
- .css( 'background', 'white' );
18
-
19
- $( '#wcf_ca_custom_filter_to' )
20
- .datepicker( {
21
- dateFormat: 'yy-mm-dd',
22
- maxDate: '0',
23
- onClose: function ( selectedDate ) {
24
- jQuery( '#wcf_ca_custom_filter_from' ).datepicker(
25
- 'option',
26
- 'maxDate',
27
- selectedDate
28
- );
29
- },
30
- } )
31
- .attr( 'readonly', 'readonly' )
32
- .css( 'background', 'white' );
33
-
34
- $( '#wcf_ca_custom_filter' ).on( 'click', function () {
35
- var from = $( '#wcf_ca_custom_filter_from' ).val().trim();
36
- var to = $( '#wcf_ca_custom_filter_to' ).val().trim();
37
- var url = window.location.search;
38
- url =
39
- url +
40
- '&from_date=' +
41
- from +
42
- '&to_date=' +
43
- to +
44
- '&filter=custom';
45
- window.location.href = url;
46
- } );
47
-
48
- $( '#wcf_search_id_submit' ).on( 'click', function () {
49
- var search = $( '#wcf_search_id_search_input' ).val().trim();
50
- window.location.href =
51
- window.location.search + '&search_term=' + search;
52
- } );
53
-
54
- // Hide initially.
55
- $(
56
- '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry, #wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status, #wcf_ca_gdpr_message'
57
- )
58
- .closest( 'tr' )
59
- .hide();
60
-
61
- if ( $( '#wcf_ca_gdpr_status:checked' ).length ) {
62
- $( '#wcf_ca_gdpr_message' ).closest( 'tr' ).show();
63
- }
64
-
65
- if ( $( '#wcf_ca_zapier_tracking_status:checked' ).length ) {
66
- $(
67
- '#wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status'
68
- )
69
- .closest( 'tr' )
70
- .show();
71
- }
72
-
73
- if (
74
- $( '#wcf_ca_coupon_code_status:checked' ).length &&
75
- $( '#wcf_ca_zapier_tracking_status:checked' ).length
76
- ) {
77
- $(
78
- '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
79
- )
80
- .closest( 'tr' )
81
- .show();
82
- }
83
-
84
- $( '#wcf_ca_coupon_code_status' ).on( 'click', function () {
85
- if ( ! $( '#wcf_ca_coupon_code_status:checked' ).length ) {
86
- $(
87
- '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
88
- )
89
- .closest( 'tr' )
90
- .fadeOut();
91
- } else {
92
- $(
93
- '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
94
- )
95
- .closest( 'tr' )
96
- .fadeIn();
97
- }
98
- } );
99
-
100
- $( '#wcf_ca_gdpr_status' ).on( 'click', function () {
101
- if ( ! $( '#wcf_ca_gdpr_status:checked' ).length ) {
102
- $( '#wcf_ca_gdpr_message' ).closest( 'tr' ).fadeOut();
103
- } else {
104
- $( '#wcf_ca_gdpr_message' ).closest( 'tr' ).fadeIn();
105
- }
106
- } );
107
-
108
- $( '#wcf_ca_zapier_tracking_status' ).on( 'click', function () {
109
- if ( ! $( '#wcf_ca_zapier_tracking_status:checked' ).length ) {
110
- $(
111
- '#wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status'
112
- )
113
- .closest( 'tr' )
114
- .fadeOut();
115
- } else {
116
- $(
117
- '#wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status'
118
- )
119
- .closest( 'tr' )
120
- .fadeIn();
121
- }
122
-
123
- if (
124
- $( '#wcf_ca_coupon_code_status:checked' ).length &&
125
- $( '#wcf_ca_zapier_tracking_status:checked' ).length
126
- ) {
127
- $(
128
- '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
129
- )
130
- .closest( 'tr' )
131
- .fadeIn();
132
- } else {
133
- $(
134
- '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
135
- )
136
- .closest( 'tr' )
137
- .fadeOut();
138
- }
139
- } );
140
- },
141
- };
142
-
143
  EmailTemplatesAdmin = {
144
- init: function () {
145
  $( document ).on(
146
  'click',
147
  '#wcf_preview_email',
@@ -167,7 +26,7 @@
167
  '.wcar-switch-grid',
168
  EmailTemplatesAdmin.toggle_activate_template_on_grid
169
  );
170
- var coupon_child_fields =
171
  '#wcf_email_discount_type, #wcf_email_discount_amount, #wcf_email_coupon_expiry_date, #wcf_free_shipping_coupon, #wcf_auto_coupon_apply, #wcf_individual_use_only';
172
  $( coupon_child_fields )
173
  .closest( 'tr' )
@@ -175,7 +34,7 @@
175
  $( document ).on(
176
  'click',
177
  '#wcf_override_global_coupon',
178
- function () {
179
  $( coupon_child_fields )
180
  .closest( 'tr' )
181
  .fadeToggle(
@@ -185,8 +44,8 @@
185
  );
186
  },
187
 
188
- send_test_email: function () {
189
- var email_body = '';
190
  if (
191
  jQuery( '#wp-wcf_email_body-wrap' ).hasClass( 'tmce-active' )
192
  ) {
@@ -195,9 +54,9 @@
195
  email_body = jQuery( '#wcf_email_body' ).val();
196
  }
197
 
198
- var email_subject = $( '#wcf_email_subject' ).val();
199
- var email_send_to = $( '#wcf_send_test_email' ).val();
200
- var wp_nonce = $( '#_wpnonce' ).val();
201
 
202
  $( this ).next( 'div.error' ).remove();
203
 
@@ -214,10 +73,10 @@
214
  '<div class="error-message wcf-ca-error-msg"> You must add your email id! </div>'
215
  );
216
  } else {
217
- var data = {
218
- email_subject: email_subject,
219
- email_body: email_body,
220
- email_send_to: email_send_to,
221
  action: 'wcf_ca_preview_email_send',
222
  security: wp_nonce,
223
  };
@@ -226,23 +85,23 @@
226
  .attr( 'disabled', true );
227
 
228
  // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
229
- $.post( ajaxurl, data, function ( response ) {
230
  $( '#mail_response_msg' ).empty().fadeIn();
231
 
232
  if ( response.success ) {
233
- var htmlString =
234
  '<strong> Email has been sent successfully! </strong>';
235
  $( '#mail_response_msg' )
236
  .css( 'color', 'green' )
237
- .html( htmlString )
238
  .delay( 3000 )
239
  .fadeOut();
240
  } else {
241
- var htmlString =
242
  '<strong> Email sending failed! Please check your SMTP settings! </a></strong>';
243
  $( '#mail_response_msg' )
244
  .css( 'color', 'red' )
245
- .html( htmlString )
246
  .delay( 3000 )
247
  .fadeOut();
248
  }
@@ -255,9 +114,9 @@
255
  $( '.wcf-ca-error-msg' ).delay( 2000 ).fadeOut();
256
  },
257
 
258
- delete_coupons: function () {
259
  if ( confirm( wcf_ca_localized_vars._confirm_msg ) ) {
260
- var data = {
261
  action: 'wcf_ca_delete_garbage_coupons',
262
  security: wcf_ca_localized_vars._delete_coupon_nonce,
263
  };
@@ -267,7 +126,7 @@
267
  $( '#wcf_ca_delete_coupons' )
268
  .css( 'cursor', 'wait' )
269
  .attr( 'disabled', true );
270
- $.post( ajaxurl, data, function ( response ) {
271
  $( '.wcf-ca-response-msg' ).empty().fadeIn();
272
  if ( response.success ) {
273
  $( '.wcf-ca-spinner' ).hide();
@@ -284,29 +143,29 @@
284
  } );
285
  }
286
  },
287
- export_orders: function () {
288
  if ( confirm( wcf_ca_localized_vars._confirm_msg_export ) ) {
289
  window.location.href =
290
  window.location.search + '&export_data=true';
291
  }
292
  },
293
- toggle_activate_template_on_grid: function () {
294
- var $switch, state, new_state;
295
- $switch = $( this );
296
- state = $switch.attr( 'wcf-ca-template-switch' );
297
- var css = state === 'on' ? 'green' : 'red';
298
 
299
  $.post(
300
  ajaxurl,
301
  {
302
  action: 'activate_email_templates',
303
  id: $( this ).attr( 'id' ),
304
- state: state,
305
  security: wcf_ca_details.email_toggle_button_nonce,
306
  },
307
- function ( response ) {
308
  $( '#wcf_activate_email_template' ).val(
309
- new_state == 'on' ? 1 : 0
310
  );
311
 
312
  $( '.wcar_tmpl_response_msg' ).remove();
@@ -324,145 +183,18 @@
324
  );
325
  },
326
 
327
- toggle_activate_template: function () {
328
- var $switch, state, new_state;
329
- $switch = $( this );
330
- state = $switch.attr( 'wcf-ca-template-switch' );
331
- new_state = state === 'on' ? 'off' : 'on';
332
  $( '#wcf_activate_email_template' ).val(
333
- new_state == 'on' ? 1 : 0
334
  );
335
  $switch.attr( 'wcf-ca-template-switch', new_state );
336
  },
337
  };
338
 
339
- ZapierSettings = {
340
- init: function () {
341
- $( document ).on(
342
- 'click',
343
- '#wcf_ca_trigger_web_hook_abandoned_btn',
344
- { order_status: 'abandoned' },
345
- ZapierSettings.zapier_trigger_sample
346
- );
347
- },
348
- zapier_trigger_sample: function ( event ) {
349
- var zapier_webhook_url = $(
350
- '#wcf_ca_zapier_cart_' + event.data.order_status + '_webhook'
351
- )
352
- .val()
353
- .trim();
354
-
355
- if ( ! zapier_webhook_url.length ) {
356
- $( '#wcf_ca_' + event.data.order_status + '_btn_message' )
357
- .text( 'Webhook URL is required.' )
358
- .fadeIn()
359
- .css( 'color', '#dc3232' )
360
- .delay( 2000 )
361
- .fadeOut();
362
- return;
363
- }
364
-
365
- $( '#wcf_ca_' + event.data.order_status + '_btn_message' )
366
- .text( 'Triggering...' )
367
- .fadeIn();
368
-
369
- var now = new Date();
370
- var datetime =
371
- now.getFullYear() +
372
- '/' +
373
- ( now.getMonth() + 1 ) +
374
- '/' +
375
- now.getDate();
376
- datetime +=
377
- ' ' +
378
- now.getHours() +
379
- ':' +
380
- now.getMinutes() +
381
- ':' +
382
- now.getSeconds();
383
- if ( $.trim( zapier_webhook_url ) !== '' ) {
384
- var sample_data = {
385
- first_name: wcf_ca_details.name,
386
- last_name: wcf_ca_details.surname,
387
- email: wcf_ca_details.email,
388
- order_status: event.data.order_status,
389
- checkout_url:
390
- window.location.origin +
391
- '/checkout/?wcf_ac_token=something',
392
- coupon_code: 'abcgefgh',
393
- product_names: 'Product1, Product2 & Product3',
394
- cart_total: wcf_ca_details.woo_currency_symbol + '20',
395
- product_table:
396
- '<table align= left; cellpadding="10" cellspacing="0" style="float: none; border: 1px solid #e5e5e5;"> <tr align="center"> <th style="color: #636363; border: 1px solid #e5e5e5;">Item</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Name</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Quantity</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Price</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Line Subtotal</th> </tr> <tr style=color: #636363; border: 1px solid #e5e5e5; align="center"> <td style="color: #636363; border: 1px solid #e5e5e5;"><img class="demo_img" style="height: 42px; width: 42px;" src="http://localhost/wp-local/wp-content/uploads/2019/11/wGdyS_Zgz1A.jpg"></td> <td style="color: #636363; border: 1px solid #e5e5e5;">Product1</td> <td style="color: #636363; border: 1px solid #e5e5e5;"> 1 </td> <td style="color: #636363; border: 1px solid #e5e5e5;">&pound;85.00</td> <td style="color: #636363; border: 1px solid #e5e5e5;" >&pound;85.00</td> </tr> </table>',
397
- };
398
- $.ajax( {
399
- url: zapier_webhook_url,
400
- type: 'POST',
401
- data: sample_data,
402
- success: function ( data ) {
403
- if ( data.status == 'success' ) {
404
- $(
405
- '#wcf_ca_' +
406
- event.data.order_status +
407
- '_btn_message'
408
- )
409
- .text( 'Trigger Success!' )
410
- .css( 'color', '#46b450' );
411
- } else {
412
- $(
413
- '#wcf_ca_' +
414
- event.data.order_status +
415
- '_btn_message'
416
- )
417
- .text( 'Trigger Failed!' )
418
- .css( 'color', '#dc3232' );
419
- }
420
- $(
421
- '#wcf_ca_' +
422
- event.data.order_status +
423
- '_btn_message'
424
- )
425
- .fadeIn()
426
- .delay( 2000 )
427
- .fadeOut();
428
- },
429
- error: function () {
430
- $(
431
- '#wcf_ca_' +
432
- event.data.order_status +
433
- '_btn_message'
434
- )
435
- .text( 'Trigger Failed!' )
436
- .css( 'color', '#dc3232' );
437
- },
438
- } );
439
- } else {
440
- $( 'wcf_ca' + event.data.order_status + '_btn_message' )
441
- .text( 'Please verify webhook URL.' )
442
- .fadeIn()
443
- .delay( 2000 )
444
- .fadeOut();
445
- }
446
- },
447
- };
448
-
449
- ToolTipHover = {
450
- init: function () {
451
- $( '.wcf-ca-report-table-row .wcf-ca-icon-row' ).on(
452
- 'hover',
453
- function () {
454
- $( this )
455
- .find( '.wcf-ca-tooltip-text' )
456
- .toggleClass( 'display_tool_tip' );
457
- }
458
- );
459
- },
460
- };
461
-
462
- $( function () {
463
  EmailTemplatesAdmin.init();
464
- CartAbandonmentSettings.init();
465
- ZapierSettings.init();
466
- ToolTipHover.init();
467
  } );
468
- } )( jQuery );
1
+ ( function( $ ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  EmailTemplatesAdmin = {
3
+ init() {
4
  $( document ).on(
5
  'click',
6
  '#wcf_preview_email',
26
  '.wcar-switch-grid',
27
  EmailTemplatesAdmin.toggle_activate_template_on_grid
28
  );
29
+ const coupon_child_fields =
30
  '#wcf_email_discount_type, #wcf_email_discount_amount, #wcf_email_coupon_expiry_date, #wcf_free_shipping_coupon, #wcf_auto_coupon_apply, #wcf_individual_use_only';
31
  $( coupon_child_fields )
32
  .closest( 'tr' )
34
  $( document ).on(
35
  'click',
36
  '#wcf_override_global_coupon',
37
+ function() {
38
  $( coupon_child_fields )
39
  .closest( 'tr' )
40
  .fadeToggle(
44
  );
45
  },
46
 
47
+ send_test_email() {
48
+ let email_body = '';
49
  if (
50
  jQuery( '#wp-wcf_email_body-wrap' ).hasClass( 'tmce-active' )
51
  ) {
54
  email_body = jQuery( '#wcf_email_body' ).val();
55
  }
56
 
57
+ const email_subject = $( '#wcf_email_subject' ).val();
58
+ const email_send_to = $( '#wcf_send_test_email' ).val();
59
+ const wp_nonce = $( '#_wpnonce' ).val();
60
 
61
  $( this ).next( 'div.error' ).remove();
62
 
73
  '<div class="error-message wcf-ca-error-msg"> You must add your email id! </div>'
74
  );
75
  } else {
76
+ const data = {
77
+ email_subject,
78
+ email_body,
79
+ email_send_to,
80
  action: 'wcf_ca_preview_email_send',
81
  security: wp_nonce,
82
  };
85
  .attr( 'disabled', true );
86
 
87
  // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
88
+ $.post( ajaxurl, data, function( response ) {
89
  $( '#mail_response_msg' ).empty().fadeIn();
90
 
91
  if ( response.success ) {
92
+ const success_string =
93
  '<strong> Email has been sent successfully! </strong>';
94
  $( '#mail_response_msg' )
95
  .css( 'color', 'green' )
96
+ .html( success_string )
97
  .delay( 3000 )
98
  .fadeOut();
99
  } else {
100
+ const error_string =
101
  '<strong> Email sending failed! Please check your SMTP settings! </a></strong>';
102
  $( '#mail_response_msg' )
103
  .css( 'color', 'red' )
104
+ .html( error_string )
105
  .delay( 3000 )
106
  .fadeOut();
107
  }
114
  $( '.wcf-ca-error-msg' ).delay( 2000 ).fadeOut();
115
  },
116
 
117
+ delete_coupons() {
118
  if ( confirm( wcf_ca_localized_vars._confirm_msg ) ) {
119
+ const data = {
120
  action: 'wcf_ca_delete_garbage_coupons',
121
  security: wcf_ca_localized_vars._delete_coupon_nonce,
122
  };
126
  $( '#wcf_ca_delete_coupons' )
127
  .css( 'cursor', 'wait' )
128
  .attr( 'disabled', true );
129
+ $.post( ajaxurl, data, function( response ) {
130
  $( '.wcf-ca-response-msg' ).empty().fadeIn();
131
  if ( response.success ) {
132
  $( '.wcf-ca-spinner' ).hide();
143
  } );
144
  }
145
  },
146
+ export_orders() {
147
  if ( confirm( wcf_ca_localized_vars._confirm_msg_export ) ) {
148
  window.location.href =
149
  window.location.search + '&export_data=true';
150
  }
151
  },
152
+ toggle_activate_template_on_grid() {
153
+ let new_state;
154
+ const $switch = $( this ),
155
+ state = $switch.attr( 'wcf-ca-template-switch' ),
156
+ css = state === 'on' ? 'green' : 'red';
157
 
158
  $.post(
159
  ajaxurl,
160
  {
161
  action: 'activate_email_templates',
162
  id: $( this ).attr( 'id' ),
163
+ state,
164
  security: wcf_ca_details.email_toggle_button_nonce,
165
  },
166
+ function( response ) {
167
  $( '#wcf_activate_email_template' ).val(
168
+ new_state === 'on' ? 1 : 0
169
  );
170
 
171
  $( '.wcar_tmpl_response_msg' ).remove();
183
  );
184
  },
185
 
186
+ toggle_activate_template() {
187
+ const $switch = $( this ),
188
+ state = $switch.attr( 'wcf-ca-template-switch' );
189
+ const new_state = state === 'on' ? 'off' : 'on';
 
190
  $( '#wcf_activate_email_template' ).val(
191
+ new_state === 'on' ? 1 : 0
192
  );
193
  $switch.attr( 'wcf-ca-template-switch', new_state );
194
  },
195
  };
196
 
197
+ $( function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  EmailTemplatesAdmin.init();
 
 
 
199
  } );
200
+ }( jQuery ) );
admin/assets/js/admin-mce.js CHANGED
@@ -1,6 +1,6 @@
1
- ( function ( $ ) {
2
- $( document ).ready( function () {
3
- tinymce.PluginManager.add( 'cartflows_ac', function ( editor, url ) {
4
  editor.addButton( 'cartflows_ac', {
5
  type: 'menubutton',
6
  text: 'WCAR Fields',
@@ -9,91 +9,91 @@
9
  {
10
  text: wcf_ca_details.admin_firstname,
11
  value: '{{admin.firstname}}',
12
- onclick: function () {
13
  editor.insertContent( this.value() );
14
  },
15
  },
16
  {
17
  text: wcf_ca_details.admin_company,
18
  value: '{{admin.company}}',
19
- onclick: function () {
20
  editor.insertContent( this.value() );
21
  },
22
  },
23
  {
24
  text: wcf_ca_details.abandoned_product_details_table,
25
  value: '{{cart.product.table}}',
26
- onclick: function () {
27
  editor.insertContent( this.value() );
28
  },
29
  },
30
  {
31
  text: wcf_ca_details.abandoned_product_names,
32
  value: '{{cart.product.names}}',
33
- onclick: function () {
34
  editor.insertContent( this.value() );
35
  },
36
  },
37
  {
38
  text: wcf_ca_details.cart_checkout_url,
39
  value: '{{cart.checkout_url}}',
40
- onclick: function () {
41
  editor.insertContent( this.value() );
42
  },
43
  },
44
  {
45
  text: wcf_ca_details.coupon_code,
46
  value: '{{cart.coupon_code}}',
47
- onclick: function () {
48
  editor.insertContent( this.value() );
49
  },
50
  },
51
  {
52
  text: wcf_ca_details.customer_firstname,
53
  value: '{{customer.firstname}}',
54
- onclick: function () {
55
  editor.insertContent( this.value() );
56
  },
57
  },
58
  {
59
  text: wcf_ca_details.customer_lastname,
60
  value: '{{customer.lastname}}',
61
- onclick: function () {
62
  editor.insertContent( this.value() );
63
  },
64
  },
65
  {
66
  text: wcf_ca_details.customer_full_name,
67
  value: '{{customer.fullname}}',
68
- onclick: function () {
69
  editor.insertContent( this.value() );
70
  },
71
  },
72
  {
73
  text: wcf_ca_details.cart_abandonment_date,
74
  value: '{{cart.abandoned_date}}',
75
- onclick: function () {
76
  editor.insertContent( this.value() );
77
  },
78
  },
79
  {
80
  text: wcf_ca_details.site_url,
81
  value: '{{site.url}}',
82
- onclick: function () {
83
  editor.insertContent( this.value() );
84
  },
85
  },
86
  {
87
  text: wcf_ca_details.unsubscribe_link,
88
  value: '{{cart.unsubscribe}}',
89
- onclick: function () {
90
  editor.insertContent( this.value() );
91
  },
92
  },
93
- ].sort( function ( a, b ) {
94
  return a.text.localeCompare( b.text );
95
  } ),
96
  } );
97
  } );
98
  } );
99
- } )( jQuery );
1
+ ( function( $ ) {
2
+ $( document ).ready( function() {
3
+ tinymce.PluginManager.add( 'cartflows_ac', function( editor ) {
4
  editor.addButton( 'cartflows_ac', {
5
  type: 'menubutton',
6
  text: 'WCAR Fields',
9
  {
10
  text: wcf_ca_details.admin_firstname,
11
  value: '{{admin.firstname}}',
12
+ onclick() {
13
  editor.insertContent( this.value() );
14
  },
15
  },
16
  {
17
  text: wcf_ca_details.admin_company,
18
  value: '{{admin.company}}',
19
+ onclick() {
20
  editor.insertContent( this.value() );
21
  },
22
  },
23
  {
24
  text: wcf_ca_details.abandoned_product_details_table,
25
  value: '{{cart.product.table}}',
26
+ onclick() {
27
  editor.insertContent( this.value() );
28
  },
29
  },
30
  {
31
  text: wcf_ca_details.abandoned_product_names,
32
  value: '{{cart.product.names}}',
33
+ onclick() {
34
  editor.insertContent( this.value() );
35
  },
36
  },
37
  {
38
  text: wcf_ca_details.cart_checkout_url,
39
  value: '{{cart.checkout_url}}',
40
+ onclick() {
41
  editor.insertContent( this.value() );
42
  },
43
  },
44
  {
45
  text: wcf_ca_details.coupon_code,
46
  value: '{{cart.coupon_code}}',
47
+ onclick() {
48
  editor.insertContent( this.value() );
49
  },
50
  },
51
  {
52
  text: wcf_ca_details.customer_firstname,
53
  value: '{{customer.firstname}}',
54
+ onclick() {
55
  editor.insertContent( this.value() );
56
  },
57
  },
58
  {
59
  text: wcf_ca_details.customer_lastname,
60
  value: '{{customer.lastname}}',
61
+ onclick() {
62
  editor.insertContent( this.value() );
63
  },
64
  },
65
  {
66
  text: wcf_ca_details.customer_full_name,
67
  value: '{{customer.fullname}}',
68
+ onclick() {
69
  editor.insertContent( this.value() );
70
  },
71
  },
72
  {
73
  text: wcf_ca_details.cart_abandonment_date,
74
  value: '{{cart.abandoned_date}}',
75
+ onclick() {
76
  editor.insertContent( this.value() );
77
  },
78
  },
79
  {
80
  text: wcf_ca_details.site_url,
81
  value: '{{site.url}}',
82
+ onclick() {
83
  editor.insertContent( this.value() );
84
  },
85
  },
86
  {
87
  text: wcf_ca_details.unsubscribe_link,
88
  value: '{{cart.unsubscribe}}',
89
+ onclick() {
90
  editor.insertContent( this.value() );
91
  },
92
  },
93
+ ].sort( function( a, b ) {
94
  return a.text.localeCompare( b.text );
95
  } ),
96
  } );
97
  } );
98
  } );
99
+ }( jQuery ) );
admin/assets/js/admin-settings.js ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ( function( $ ) {
2
+ CartAbandonmentSettings = {
3
+ init() {
4
+ $( '#wcf_ca_custom_filter_from' )
5
+ .datepicker( {
6
+ dateFormat: 'yy-mm-dd',
7
+ maxDate: '0',
8
+ onClose( selectedDate ) {
9
+ jQuery( '#wcf_ca_custom_filter_to' ).datepicker(
10
+ 'option',
11
+ 'minDate',
12
+ selectedDate
13
+ );
14
+ },
15
+ } )
16
+ .attr( 'readonly', 'readonly' )
17
+ .css( 'background', 'white' );
18
+
19
+ $( '#wcf_ca_custom_filter_to' )
20
+ .datepicker( {
21
+ dateFormat: 'yy-mm-dd',
22
+ maxDate: '0',
23
+ onClose( selectedDate ) {
24
+ jQuery( '#wcf_ca_custom_filter_from' ).datepicker(
25
+ 'option',
26
+ 'maxDate',
27
+ selectedDate
28
+ );
29
+ },
30
+ } )
31
+ .attr( 'readonly', 'readonly' )
32
+ .css( 'background', 'white' );
33
+
34
+ $( '#wcf_ca_custom_filter' ).on( 'click', function() {
35
+ const from = $( '#wcf_ca_custom_filter_from' ).val().trim();
36
+ const to = $( '#wcf_ca_custom_filter_to' ).val().trim();
37
+ let url = window.location.search;
38
+ url =
39
+ url +
40
+ '&from_date=' +
41
+ from +
42
+ '&to_date=' +
43
+ to +
44
+ '&filter=custom';
45
+ window.location.href = url;
46
+ } );
47
+
48
+ $( '#wcf_search_id_submit' ).on( 'click', function() {
49
+ const search = $( '#wcf_search_id_search_input' ).val().trim();
50
+ window.location.href =
51
+ window.location.search + '&search_term=' + search;
52
+ } );
53
+
54
+ // Hide initially.
55
+ $(
56
+ '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry, #wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status, #wcf_ca_gdpr_message'
57
+ )
58
+ .closest( 'tr' )
59
+ .hide();
60
+
61
+ if ( $( '#wcf_ca_gdpr_status:checked' ).length ) {
62
+ $( '#wcf_ca_gdpr_message' ).closest( 'tr' ).show();
63
+ }
64
+
65
+ if ( $( '#wcf_ca_zapier_tracking_status:checked' ).length ) {
66
+ $(
67
+ '#wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status'
68
+ )
69
+ .closest( 'tr' )
70
+ .show();
71
+ }
72
+
73
+ if (
74
+ $( '#wcf_ca_coupon_code_status:checked' ).length &&
75
+ $( '#wcf_ca_zapier_tracking_status:checked' ).length
76
+ ) {
77
+ $(
78
+ '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
79
+ )
80
+ .closest( 'tr' )
81
+ .show();
82
+ }
83
+
84
+ $( '#wcf_ca_coupon_code_status' ).on( 'click', function() {
85
+ if ( ! $( '#wcf_ca_coupon_code_status:checked' ).length ) {
86
+ $(
87
+ '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
88
+ )
89
+ .closest( 'tr' )
90
+ .fadeOut();
91
+ } else {
92
+ $(
93
+ '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
94
+ )
95
+ .closest( 'tr' )
96
+ .fadeIn();
97
+ }
98
+ } );
99
+
100
+ $( '#wcf_ca_gdpr_status' ).on( 'click', function() {
101
+ if ( ! $( '#wcf_ca_gdpr_status:checked' ).length ) {
102
+ $( '#wcf_ca_gdpr_message' ).closest( 'tr' ).fadeOut();
103
+ } else {
104
+ $( '#wcf_ca_gdpr_message' ).closest( 'tr' ).fadeIn();
105
+ }
106
+ } );
107
+
108
+ $( '#wcf_ca_zapier_tracking_status' ).on( 'click', function() {
109
+ if ( ! $( '#wcf_ca_zapier_tracking_status:checked' ).length ) {
110
+ $(
111
+ '#wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status'
112
+ )
113
+ .closest( 'tr' )
114
+ .fadeOut();
115
+ } else {
116
+ $(
117
+ '#wcf_ca_zapier_cart_abandoned_webhook, #wcf_ca_coupon_code_status'
118
+ )
119
+ .closest( 'tr' )
120
+ .fadeIn();
121
+ }
122
+
123
+ if (
124
+ $( '#wcf_ca_coupon_code_status:checked' ).length &&
125
+ $( '#wcf_ca_zapier_tracking_status:checked' ).length
126
+ ) {
127
+ $(
128
+ '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
129
+ )
130
+ .closest( 'tr' )
131
+ .fadeIn();
132
+ } else {
133
+ $(
134
+ '#wcf_ca_discount_type, #wcf_ca_coupon_amount, #wcf_ca_coupon_expiry'
135
+ )
136
+ .closest( 'tr' )
137
+ .fadeOut();
138
+ }
139
+ } );
140
+ },
141
+ };
142
+
143
+ ZapierSettings = {
144
+ init() {
145
+ $( document ).on(
146
+ 'click',
147
+ '#wcf_ca_trigger_web_hook_abandoned_btn',
148
+ { order_status: 'abandoned' },
149
+ ZapierSettings.zapier_trigger_sample
150
+ );
151
+ },
152
+ zapier_trigger_sample( event ) {
153
+ const zapier_webhook_url = $(
154
+ '#wcf_ca_zapier_cart_' + event.data.order_status + '_webhook'
155
+ )
156
+ .val()
157
+ .trim();
158
+
159
+ if ( ! zapier_webhook_url.length ) {
160
+ $( '#wcf_ca_' + event.data.order_status + '_btn_message' )
161
+ .text( wcf_ca_details.strings.verify_url_error )
162
+ .fadeIn()
163
+ .css( 'color', '#dc3232' )
164
+ .delay( 2000 )
165
+ .fadeOut();
166
+ return;
167
+ }
168
+
169
+ $( '#wcf_ca_' + event.data.order_status + '_btn_message' )
170
+ .text( wcf_ca_details.strings.trigger_process )
171
+ .fadeIn();
172
+
173
+ if ( $.trim( zapier_webhook_url ) !== '' ) {
174
+ const sample_data = {
175
+ first_name: wcf_ca_details.name,
176
+ last_name: wcf_ca_details.surname,
177
+ email: wcf_ca_details.email,
178
+ phone: wcf_ca_details.phone,
179
+ order_status: event.data.order_status,
180
+ checkout_url:
181
+ window.location.origin +
182
+ '/checkout/?wcf_ac_token=something',
183
+ coupon_code: 'abcgefgh',
184
+ product_names: 'Product1, Product2 & Product3',
185
+ cart_total: wcf_ca_details.woo_currency_symbol + '20',
186
+ product_table:
187
+ '<table align= left; cellpadding="10" cellspacing="0" style="float: none; border: 1px solid #e5e5e5;"> <tr align="center"> <th style="color: #636363; border: 1px solid #e5e5e5;">Item</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Name</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Quantity</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Price</th> <th style="color: #636363; border: 1px solid #e5e5e5;">Line Subtotal</th> </tr> <tr style=color: #636363; border: 1px solid #e5e5e5; align="center"> <td style="color: #636363; border: 1px solid #e5e5e5;"><img class="demo_img" style="height: 42px; width: 42px;" src="#"></td> <td style="color: #636363; border: 1px solid #e5e5e5;">Product1</td> <td style="color: #636363; border: 1px solid #e5e5e5;"> 1 </td> <td style="color: #636363; border: 1px solid #e5e5e5;">&pound;85.00</td> <td style="color: #636363; border: 1px solid #e5e5e5;" >&pound;85.00</td> </tr> </table>',
188
+ };
189
+ $.ajax( {
190
+ url: zapier_webhook_url,
191
+ type: 'POST',
192
+ data: sample_data,
193
+ success( data ) {
194
+ if ( data.status === 'success' ) {
195
+ $(
196
+ '#wcf_ca_' +
197
+ event.data.order_status +
198
+ '_btn_message'
199
+ )
200
+ .text( wcf_ca_details.strings.trigger_success )
201
+ .css( 'color', '#46b450' );
202
+ } else {
203
+ $(
204
+ '#wcf_ca_' +
205
+ event.data.order_status +
206
+ '_btn_message'
207
+ )
208
+ .text( wcf_ca_details.strings.trigger_failed )
209
+ .css( 'color', '#dc3232' );
210
+ }
211
+ $(
212
+ '#wcf_ca_' +
213
+ event.data.order_status +
214
+ '_btn_message'
215
+ )
216
+ .fadeIn()
217
+ .delay( 2000 )
218
+ .fadeOut();
219
+ },
220
+ error() {
221
+ $(
222
+ '#wcf_ca_' +
223
+ event.data.order_status +
224
+ '_btn_message'
225
+ )
226
+ .text( wcf_ca_details.strings.trigger_failed )
227
+ .css( 'color', '#dc3232' );
228
+ },
229
+ } );
230
+ } else {
231
+ $( 'wcf_ca' + event.data.order_status + '_btn_message' )
232
+ .text( wcf_ca_details.strings.verify_url )
233
+ .fadeIn()
234
+ .delay( 2000 )
235
+ .fadeOut();
236
+ }
237
+ },
238
+ };
239
+
240
+ ToolTipHover = {
241
+ init() {
242
+ $( '.wcf-ca-report-table-row .wcf-ca-icon-row' ).on(
243
+ 'hover',
244
+ function() {
245
+ $( this )
246
+ .find( '.wcf-ca-tooltip-text' )
247
+ .toggleClass( 'display_tool_tip' );
248
+ }
249
+ );
250
+ },
251
+ };
252
+
253
+ $( function() {
254
+ CartAbandonmentSettings.init();
255
+ ZapierSettings.init();
256
+ ToolTipHover.init();
257
+ } );
258
+ }( jQuery ) );
admin/bsf-analytics/assets/css/minified/style-rtl.min.css ADDED
@@ -0,0 +1 @@
 
1
+ [ID*="-optin-notice"]{padding:1px 12px;border-right-color:#007cba}[ID*="-optin-notice"] .notice-container{padding-top:10px;padding-bottom:12px}[ID*="-optin-notice"] .notice-content{margin:0}[ID*="-optin-notice"] .notice-heading{padding:0 0 12px 20px}[ID*="-optin-notice"] .button-primary{margin-left:5px}
admin/bsf-analytics/assets/css/minified/style.min.css ADDED
@@ -0,0 +1 @@
 
1
+ [ID*="-optin-notice"]{padding:1px 12px;border-left-color:#007cba}[ID*="-optin-notice"] .notice-container{padding-top:10px;padding-bottom:12px}[ID*="-optin-notice"] .notice-content{margin:0}[ID*="-optin-notice"] .notice-heading{padding:0 20px 12px 0}[ID*="-optin-notice"] .button-primary{margin-right:5px}
admin/bsf-analytics/assets/css/unminified/style-rtl.css ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [ID*="-optin-notice"] {
2
+ padding: 1px 12px;
3
+ border-right-color: #007cba;
4
+ }
5
+
6
+ [ID*="-optin-notice"] .notice-container {
7
+ padding-top: 10px;
8
+ padding-bottom: 12px;
9
+ }
10
+
11
+ [ID*="-optin-notice"] .notice-content {
12
+ margin: 0;
13
+ }
14
+
15
+ [ID*="-optin-notice"] .notice-heading {
16
+ padding: 0 0 12px 20px;
17
+ }
18
+
19
+ [ID*="-optin-notice"] .button-primary {
20
+ margin-left: 5px;
21
+ }
admin/bsf-analytics/assets/css/unminified/style.css ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [ID*="-optin-notice"] {
2
+ padding: 1px 12px;
3
+ border-left-color: #007cba;
4
+ }
5
+
6
+ [ID*="-optin-notice"] .notice-container {
7
+ padding-top: 10px;
8
+ padding-bottom: 12px;
9
+ }
10
+
11
+ [ID*="-optin-notice"] .notice-content {
12
+ margin: 0;
13
+ }
14
+
15
+ [ID*="-optin-notice"] .notice-heading {
16
+ padding: 0 20px 12px 0;
17
+ }
18
+
19
+ [ID*="-optin-notice"] .button-primary {
20
+ margin-right: 5px;
21
+ }
admin/bsf-analytics/class-bsf-analytics-loader.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * BSF analytics loader file.
4
+ *
5
+ * @version 1.0.0
6
+ *
7
+ * @package bsf-analytics
8
+ */
9
+
10
+ if ( ! defined( 'ABSPATH' ) ) {
11
+ exit();
12
+ }
13
+
14
+ /**
15
+ * Class BSF_Analytics_Loader.
16
+ */
17
+ class BSF_Analytics_Loader {
18
+
19
+ /**
20
+ * Analytics Entities.
21
+ *
22
+ * @access private
23
+ * @var array Entities array.
24
+ */
25
+ private $entities = array();
26
+
27
+ /**
28
+ * Analytics Version.
29
+ *
30
+ * @access private
31
+ * @var float analytics version.
32
+ */
33
+ private $analytics_version = '';
34
+
35
+ /**
36
+ * Analytics path.
37
+ *
38
+ * @access private
39
+ * @var string path array.
40
+ */
41
+ private $analytics_path = '';
42
+
43
+ /**
44
+ * Instance
45
+ *
46
+ * @access private
47
+ * @var object Class object.
48
+ */
49
+ private static $instance = null;
50
+
51
+ /**
52
+ * Get instace of class.
53
+ *
54
+ * @return object
55
+ */
56
+ public static function get_instance() {
57
+ if ( null === self::$instance ) {
58
+ self::$instance = new self();
59
+ }
60
+
61
+ return self::$instance;
62
+ }
63
+
64
+ /**
65
+ * Constructor
66
+ */
67
+ public function __construct() {
68
+ add_action( 'init', array( $this, 'load_analytics' ) );
69
+ }
70
+
71
+ /**
72
+ * Set entity for analytics.
73
+ *
74
+ * @param string $data Entity attributes data.
75
+ * @return void
76
+ */
77
+ public function set_entity( $data ) {
78
+ array_push( $this->entities, $data );
79
+ }
80
+
81
+ /**
82
+ * Load Analytics library.
83
+ *
84
+ * @return void
85
+ */
86
+ public function load_analytics() {
87
+ $unique_entities = array();
88
+
89
+ if ( ! empty( $this->entities ) ) {
90
+ foreach ( $this->entities as $entity ) {
91
+ foreach ( $entity as $key => $data ) {
92
+
93
+ if ( isset( $data['path'] ) ) {
94
+ if ( file_exists( $data['path'] . '/version.json' ) ) {
95
+ $file_contents = file_get_contents( $data['path'] . '/version.json' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
96
+ $analytics_version = json_decode( $file_contents, 1 );
97
+ $analytics_version = $analytics_version['bsf-analytics-ver'];
98
+
99
+ if ( version_compare( $analytics_version, $this->analytics_version, '>' ) ) {
100
+ $this->analytics_version = $analytics_version;
101
+ $this->analytics_path = $data['path'];
102
+ }
103
+ }
104
+ }
105
+
106
+ if ( ! isset( $unique_entities[ $key ] ) ) {
107
+ $unique_entities[ $key ] = $data;
108
+ }
109
+ }
110
+ }
111
+
112
+ if ( file_exists( $this->analytics_path ) && ! class_exists( 'BSF_Analytics' ) ) {
113
+ require_once $this->analytics_path . '/class-bsf-analytics.php';
114
+ new BSF_Analytics( $unique_entities, $this->analytics_path, $this->analytics_version );
115
+ }
116
+ }
117
+ }
118
+ }
admin/bsf-analytics/class-bsf-analytics-stats.php ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * BSF analytics stat class file.
4
+ *
5
+ * @package bsf-analytics
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ if ( ! class_exists( 'BSF_Analytics_Stats' ) ) {
13
+ /**
14
+ * BSF analytics stat class.
15
+ */
16
+ class BSF_Analytics_Stats {
17
+
18
+ /**
19
+ * Active plugins.
20
+ *
21
+ * Holds the sites active plugins list.
22
+ *
23
+ * @var array
24
+ */
25
+ private $plugins;
26
+
27
+ /**
28
+ * Instance of BSF_Analytics_Stats.
29
+ *
30
+ * Holds only the first object of class.
31
+ *
32
+ * @var object
33
+ */
34
+ private static $instance = null;
35
+
36
+ /**
37
+ * Create only once instance of a class.
38
+ *
39
+ * @return object
40
+ * @since 1.0.0
41
+ */
42
+ public static function instance() {
43
+ if ( null === self::$instance ) {
44
+ self::$instance = new self();
45
+ }
46
+
47
+ return self::$instance;
48
+ }
49
+
50
+ /**
51
+ * Get stats.
52
+ *
53
+ * @return array stats data.
54
+ * @since 1.0.0
55
+ */
56
+ public function get_stats() {
57
+ return apply_filters( 'bsf_core_stats', $this->get_default_stats() );
58
+ }
59
+
60
+ /**
61
+ * Retrieve stats for site.
62
+ *
63
+ * @return array stats data.
64
+ * @since 1.0.0
65
+ */
66
+ private function get_default_stats() {
67
+ return array(
68
+ 'graupi_version' => defined( 'BSF_UPDATER_VERSION' ) ? BSF_UPDATER_VERSION : false,
69
+ 'domain_name' => get_site_url(),
70
+ 'php_os' => PHP_OS,
71
+ 'server_software' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? filter_var( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ), FILTER_SANITIZE_STRING ) : '',
72
+ 'mysql_version' => $this->get_mysql_version(),
73
+ 'php_version' => $this->get_php_version(),
74
+ 'php_max_input_vars' => ini_get( 'max_input_vars' ), // phpcs:ignore:PHPCompatibility.IniDirectives.NewIniDirectives.max_input_varsFound
75
+ 'php_post_max_size' => ini_get( 'post_max_size' ),
76
+ 'php_max_execution_time' => ini_get( 'max_execution_time' ),
77
+ 'php_memory_limit' => ini_get( 'memory_limit' ),
78
+ 'zip_installed' => extension_loaded( 'zip' ),
79
+ 'imagick_availabile' => extension_loaded( 'imagick' ),
80
+ 'xmlreader_exists' => class_exists( 'XMLReader' ),
81
+ 'gd_available' => extension_loaded( 'gd' ),
82
+ 'curl_version' => $this->get_curl_version(),
83
+ 'curl_ssl_version' => $this->get_curl_ssl_version(),
84
+ 'is_writable' => $this->is_content_writable(),
85
+
86
+ 'wp_version' => get_bloginfo( 'version' ),
87
+ 'user_count' => $this->get_user_count(),
88
+ 'site_language' => get_locale(),
89
+ 'timezone' => wp_timezone_string(),
90
+ 'is_ssl' => is_ssl(),
91
+ 'is_multisite' => is_multisite(),
92
+ 'network_url' => network_site_url(),
93
+ 'external_object_cache' => (bool) wp_using_ext_object_cache(),
94
+ 'wp_debug' => WP_DEBUG,
95
+ 'wp_debug_display' => WP_DEBUG_DISPLAY,
96
+ 'script_debug' => SCRIPT_DEBUG,
97
+
98
+ 'active_plugins' => $this->get_active_plugins(),
99
+
100
+ 'active_theme' => get_template(),
101
+ 'active_stylesheet' => get_stylesheet(),
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Get installed PHP version.
107
+ *
108
+ * @return float PHP version.
109
+ * @since 1.0.0
110
+ */
111
+ private function get_php_version() {
112
+ if ( defined( 'PHP_MAJOR_VERSION' ) && defined( 'PHP_MINOR_VERSION' ) && defined( 'PHP_RELEASE_VERSION' ) ) { // phpcs:ignore
113
+ return PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
114
+ }
115
+
116
+ return phpversion();
117
+ }
118
+
119
+ /**
120
+ * User count on site.
121
+ *
122
+ * @return int User count.
123
+ * @since 1.0.0
124
+ */
125
+ private function get_user_count() {
126
+ if ( is_multisite() ) {
127
+ $user_count = get_user_count();
128
+ } else {
129
+ $count = count_users();
130
+ $user_count = $count['total_users'];
131
+ }
132
+
133
+ return $user_count;
134
+ }
135
+
136
+ /**
137
+ * Get active plugin's data.
138
+ *
139
+ * @return array active plugin's list.
140
+ * @since 1.0.0
141
+ */
142
+ private function get_active_plugins() {
143
+ if ( ! $this->plugins ) {
144
+ // Ensure get_plugin_data function is loaded.
145
+ if ( ! function_exists( 'get_plugin_data' ) ) {
146
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
147
+ }
148
+
149
+ $plugins = wp_get_active_and_valid_plugins();
150
+ $plugins = array_map( 'get_plugin_data', $plugins );
151
+ $this->plugins = array_map( array( $this, 'format_plugin' ), $plugins );
152
+ }
153
+
154
+ return $this->plugins;
155
+ }
156
+
157
+ /**
158
+ * Format plugin data.
159
+ *
160
+ * @param string $plugin plugin.
161
+ * @return array formatted plugin data.
162
+ * @since 1.0.0
163
+ */
164
+ public function format_plugin( $plugin ) {
165
+ return array(
166
+ 'name' => html_entity_decode( $plugin['Name'], ENT_COMPAT, 'UTF-8' ),
167
+ 'url' => $plugin['PluginURI'],
168
+ 'version' => $plugin['Version'],
169
+ 'slug' => $plugin['TextDomain'],
170
+ 'author_name' => html_entity_decode( wp_strip_all_tags( $plugin['Author'] ), ENT_COMPAT, 'UTF-8' ),
171
+ 'author_url' => $plugin['AuthorURI'],
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Curl SSL version.
177
+ *
178
+ * @return float SSL version.
179
+ * @since 1.0.0
180
+ */
181
+ private function get_curl_ssl_version() {
182
+ $curl = array();
183
+ if ( function_exists( 'curl_version' ) ) {
184
+ $curl = curl_version(); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_version
185
+ }
186
+
187
+ return isset( $curl['ssl_version'] ) ? $curl['ssl_version'] : false;
188
+ }
189
+
190
+ /**
191
+ * Get cURL version.
192
+ *
193
+ * @return float cURL version.
194
+ * @since 1.0.0
195
+ */
196
+ private function get_curl_version() {
197
+ if ( function_exists( 'curl_version' ) ) {
198
+ $curl = curl_version(); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_version
199
+ }
200
+
201
+ return isset( $curl['version'] ) ? $curl['version'] : false;
202
+ }
203
+
204
+ /**
205
+ * Get MySQL version.
206
+ *
207
+ * @return float MySQL version.
208
+ * @since 1.0.0
209
+ */
210
+ private function get_mysql_version() {
211
+ global $wpdb;
212
+ return $wpdb->db_version();
213
+ }
214
+
215
+ /**
216
+ * Check if content directory is writable.
217
+ *
218
+ * @return bool
219
+ * @since 1.0.0
220
+ */
221
+ private function is_content_writable() {
222
+ $upload_dir = wp_upload_dir();
223
+ return wp_is_writable( $upload_dir['basedir'] );
224
+ }
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Polyfill for sites using WP version less than 5.3
230
+ */
231
+ if ( ! function_exists( 'wp_timezone_string' ) ) {
232
+ /**
233
+ * Get timezone string.
234
+ *
235
+ * @return string timezone string.
236
+ * @since 1.0.0
237
+ */
238
+ function wp_timezone_string() {
239
+ $timezone_string = get_option( 'timezone_string' );
240
+
241
+ if ( $timezone_string ) {
242
+ return $timezone_string;
243
+ }
244
+
245
+ $offset = (float) get_option( 'gmt_offset' );
246
+ $hours = (int) $offset;
247
+ $minutes = ( $offset - $hours );
248
+
249
+ $sign = ( $offset < 0 ) ? '-' : '+';
250
+ $abs_hour = abs( $hours );
251
+ $abs_mins = abs( $minutes * 60 );
252
+ $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
253
+
254
+ return $tz_offset;
255
+ }
256
+ }
admin/bsf-analytics/class-bsf-analytics.php ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * BSF analytics class file.
4
+ *
5
+ * @version 1.0.0
6
+ *
7
+ * @package bsf-analytics
8
+ */
9
+
10
+ if ( ! defined( 'ABSPATH' ) ) {
11
+ exit; // Exit if accessed directly.
12
+ }
13
+
14
+ if ( ! class_exists( 'BSF_Analytics' ) ) {
15
+
16
+ /**
17
+ * BSF analytics
18
+ */
19
+ class BSF_Analytics {
20
+
21
+ /**
22
+ * Member Variable
23
+ *
24
+ * @var array Entities data.
25
+ */
26
+ private $entities;
27
+
28
+ /**
29
+ * Member Variable
30
+ *
31
+ * @var string Usage tracking document URL
32
+ */
33
+ public $usage_doc_link = 'https://store.brainstormforce.com/usage-tracking/?utm_source=wp_dashboard&utm_medium=general_settings&utm_campaign=usage_tracking';
34
+
35
+ /**
36
+ * Setup actions, load files.
37
+ *
38
+ * @param array $args entity data for analytics.
39
+ * @param string $analytics_path directory path to analytics library.
40
+ * @param float $analytics_version analytics library version.
41
+ * @since 1.0.0
42
+ */
43
+ public function __construct( $args, $analytics_path, $analytics_version ) {
44
+
45
+ // Bail when no analytics entities are registered.
46
+ if ( empty( $args ) ) {
47
+ return;
48
+ }
49
+
50
+ $this->entities = $args;
51
+
52
+ define( 'BSF_ANALYTICS_VERSION', $analytics_version );
53
+ define( 'BSF_ANALYTICS_URI', $this->get_analytics_url( $analytics_path ) );
54
+
55
+ add_action( 'admin_init', array( $this, 'handle_optin_optout' ) );
56
+ add_action( 'admin_init', array( $this, 'option_notice' ) );
57
+ add_action( 'init', array( $this, 'maybe_track_analytics' ), 99 );
58
+
59
+ $this->set_actions();
60
+
61
+ add_action( 'admin_init', array( $this, 'register_usage_tracking_setting' ) );
62
+
63
+ $this->includes();
64
+ }
65
+
66
+ /**
67
+ * Setup actions for admin notice style and analytics cron event.
68
+ *
69
+ * @since 1.0.4
70
+ */
71
+ public function set_actions() {
72
+
73
+ foreach ( $this->entities as $key => $data ) {
74
+ add_action( 'astra_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
75
+ add_action( 'update_option_' . $key . '_analytics_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
76
+ add_action( 'add_option_' . $key . '_analytics_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
77
+ }
78
+ }
79
+
80
+ /**
81
+ * BSF Analytics URL
82
+ *
83
+ * @param string $analytics_path directory path to analytics library.
84
+ * @return String URL of bsf-analytics directory.
85
+ * @since 1.0.0
86
+ */
87
+ public function get_analytics_url( $analytics_path ) {
88
+
89
+ $content_dir_path = wp_normalize_path( WP_CONTENT_DIR );
90
+
91
+ $analytics_path = wp_normalize_path( $analytics_path );
92
+
93
+ return str_replace( $content_dir_path, content_url(), $analytics_path );
94
+ }
95
+
96
+ /**
97
+ * Get API URL for sending analytics.
98
+ *
99
+ * @return string API URL.
100
+ * @since 1.0.0
101
+ */
102
+ private function get_api_url() {
103
+ return defined( 'BSF_API_URL' ) ? BSF_API_URL : 'https://support.brainstormforce.com/';
104
+ }
105
+
106
+ /**
107
+ * Enqueue Scripts.
108
+ *
109
+ * @since 1.0.0
110
+ * @return void
111
+ */
112
+ public function enqueue_assets() {
113
+
114
+ /**
115
+ * Load unminified if SCRIPT_DEBUG is true.
116
+ *
117
+ * Directory and Extensions.
118
+ */
119
+ $dir_name = ( SCRIPT_DEBUG ) ? 'unminified' : 'minified';
120
+ $file_rtl = ( is_rtl() ) ? '-rtl' : '';
121
+ $css_ext = ( SCRIPT_DEBUG ) ? '.css' : '.min.css';
122
+
123
+ $css_uri = BSF_ANALYTICS_URI . '/assets/css/' . $dir_name . '/style' . $file_rtl . $css_ext;
124
+
125
+ wp_enqueue_style( 'bsf-analytics-admin-style', $css_uri, false, BSF_ANALYTICS_VERSION, 'all' );
126
+ }
127
+
128
+ /**
129
+ * Send analytics API call.
130
+ *
131
+ * @since 1.0.0
132
+ */
133
+ public function send() {
134
+ wp_remote_post(
135
+ $this->get_api_url() . 'wp-json/bsf-core/v1/analytics/',
136
+ array(
137
+ 'body' => BSF_Analytics_Stats::instance()->get_stats(),
138
+ 'timeout' => 5,
139
+ 'blocking' => false,
140
+ )
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Check if usage tracking is enabled.
146
+ *
147
+ * @return bool
148
+ * @since 1.0.0
149
+ */
150
+ public function is_tracking_enabled() {
151
+
152
+ foreach ( $this->entities as $key => $data ) {
153
+
154
+ $is_enabled = get_site_option( $key . '_analytics_optin' ) === 'yes' ? true : false;
155
+ $is_enabled = $this->is_white_label_enabled( $key ) ? false : $is_enabled;
156
+
157
+ if ( apply_filters( $key . '_tracking_enabled', $is_enabled ) ) {
158
+ return true;
159
+ }
160
+ }
161
+
162
+ return false;
163
+ }
164
+
165
+ /**
166
+ * Check if WHITE label is enabled for BSF products.
167
+ *
168
+ * @param string $source source of analytics.
169
+ * @return bool
170
+ * @since 1.0.0
171
+ */
172
+ public function is_white_label_enabled( $source ) {
173
+
174
+ $options = apply_filters( $source . '_white_label_options', array() );
175
+ $is_enabled = false;
176
+
177
+ if ( is_array( $options ) ) {
178
+ foreach ( $options as $option ) {
179
+ if ( true === $option ) {
180
+ $is_enabled = true;
181
+ break;
182
+ }
183
+ }
184
+ }
185
+
186
+ return $is_enabled;
187
+ }
188
+
189
+ /**
190
+ * Display admin notice for usage tracking.
191
+ *
192
+ * @since 1.0.0
193
+ */
194
+ public function option_notice() {
195
+
196
+ if ( ! current_user_can( 'manage_options' ) ) {
197
+ return;
198
+ }
199
+
200
+ foreach ( $this->entities as $key => $data ) {
201
+
202
+ $time_to_display = isset( $data['time_to_display'] ) ? $data['time_to_display'] : '+24 hours';
203
+ $usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
204
+
205
+ // Don't display the notice if tracking is disabled or White Label is enabled for any of our plugins.
206
+ if ( false !== get_site_option( $key . '_analytics_optin', false ) || $this->is_white_label_enabled( $key ) ) {
207
+ continue;
208
+ }
209
+
210
+ // Show tracker consent notice after 24 hours from installed time.
211
+ if ( strtotime( $time_to_display, $this->get_analytics_install_time( $key ) ) > time() ) {
212
+ continue;
213
+ }
214
+
215
+ /* translators: %s product name */
216
+ $notice_string = __( 'Want to help make <strong>%1s</strong> even more awesome? Allow us to collect non-sensitive diagnostic data and usage information. ', 'woo-cart-abandonment-recovery' );
217
+
218
+ if ( is_multisite() ) {
219
+ $notice_string .= __( 'This will be applicable for all sites from the network.', 'woo-cart-abandonment-recovery' );
220
+ }
221
+
222
+ $language_dir = is_rtl() ? 'rtl' : 'ltr';
223
+
224
+ Astra_Notices::add_notice(
225
+ array(
226
+ 'id' => $key . '-optin-notice',
227
+ 'type' => '',
228
+ 'message' => sprintf(
229
+ '<div class="notice-content">
230
+ <div class="notice-heading">
231
+ %1$s
232
+ </div>
233
+ <div class="astra-notices-container">
234
+ <a href="%2$s" class="astra-notices button-primary">
235
+ %3$s
236
+ </a>
237
+ <a href="%4$s" data-repeat-notice-after="%5$s" class="astra-notices button-secondary">
238
+ %6$s
239
+ </a>
240
+ </div>
241
+ </div>',
242
+ /* translators: %s usage doc link */
243
+ sprintf( $notice_string . '<span dir="%2s"><a href="%3s" target="_blank" rel="noreferrer noopener">%4s</a><span>', esc_html( $data['product_name'] ), $language_dir, esc_url( $usage_doc_link ), __( ' Know More.', 'woo-cart-abandonment-recovery' ) ),
244
+ add_query_arg(
245
+ array(
246
+ $key . '_analytics_optin' => 'yes',
247
+ $key . '_analytics_nonce' => wp_create_nonce( $key . '_analytics_optin' ),
248
+ 'bsf_analytics_source' => $key,
249
+ )
250
+ ),
251
+ __( 'Yes! Allow it', 'woo-cart-abandonment-recovery' ),
252
+ add_query_arg(
253
+ array(
254
+ $key . '_analytics_optin' => 'no',
255
+ $key . '_analytics_nonce' => wp_create_nonce( $key . '_analytics_optin' ),
256
+ 'bsf_analytics_source' => $key,
257
+ )
258
+ ),
259
+ MONTH_IN_SECONDS,
260
+ __( 'No Thanks', 'woo-cart-abandonment-recovery' )
261
+ ),
262
+ 'show_if' => true,
263
+ 'repeat-notice-after' => false,
264
+ 'priority' => 18,
265
+ 'display-with-other-notices' => true,
266
+ )
267
+ );
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Process usage tracking opt out.
273
+ *
274
+ * @since 1.0.0
275
+ */
276
+ public function handle_optin_optout() {
277
+
278
+ if ( ! current_user_can( 'manage_options' ) ) {
279
+ return;
280
+ }
281
+
282
+ $source = isset( $_GET['bsf_analytics_source'] ) ? sanitize_text_field( wp_unslash( $_GET['bsf_analytics_source'] ) ) : '';
283
+
284
+ if ( ! isset( $_GET[ $source . '_analytics_nonce' ] ) ) {
285
+ return;
286
+ }
287
+
288
+ if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ $source . '_analytics_nonce' ] ) ), $source . '_analytics_optin' ) ) {
289
+ return;
290
+ }
291
+
292
+ $optin_status = isset( $_GET[ $source . '_analytics_optin' ] ) ? sanitize_text_field( wp_unslash( $_GET[ $source . '_analytics_optin' ] ) ) : '';
293
+
294
+ if ( 'yes' === $optin_status ) {
295
+ $this->optin( $source );
296
+ } elseif ( 'no' === $optin_status ) {
297
+ $this->optout( $source );
298
+ }
299
+
300
+ wp_safe_redirect(
301
+ remove_query_arg(
302
+ array(
303
+ $source . '_analytics_optin',
304
+ $source . '_analytics_nonce',
305
+ 'bsf_analytics_source',
306
+ )
307
+ )
308
+ );
309
+ }
310
+
311
+ /**
312
+ * Opt in to usage tracking.
313
+ *
314
+ * @param string $source source of analytics.
315
+ * @since 1.0.0
316
+ */
317
+ private function optin( $source ) {
318
+ update_site_option( $source . '_analytics_optin', 'yes' );
319
+ }
320
+
321
+ /**
322
+ * Opt out to usage tracking.
323
+ *
324
+ * @param string $source source of analytics.
325
+ * @since 1.0.0
326
+ */
327
+ private function optout( $source ) {
328
+ update_site_option( $source . '_analytics_optin', 'no' );
329
+ }
330
+
331
+ /**
332
+ * Load analytics stat class.
333
+ *
334
+ * @since 1.0.0
335
+ */
336
+ private function includes() {
337
+ require_once __DIR__ . '/class-bsf-analytics-stats.php';
338
+ }
339
+
340
+ /**
341
+ * Register usage tracking option in General settings page.
342
+ *
343
+ * @since 1.0.0
344
+ */
345
+ public function register_usage_tracking_setting() {
346
+
347
+ foreach ( $this->entities as $key => $data ) {
348
+
349
+ if ( ! apply_filters( $key . '_tracking_enabled', true ) || $this->is_white_label_enabled( $key ) ) {
350
+ return;
351
+ }
352
+
353
+ $usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
354
+ $author = isset( $data['author'] ) ? $data['author'] : 'Brainstorm Force';
355
+
356
+ register_setting(
357
+ 'general', // Options group.
358
+ $key . '_analytics_optin', // Option name/database.
359
+ array( 'sanitize_callback' => array( $this, 'sanitize_option' ) ) // sanitize callback function.
360
+ );
361
+
362
+ add_settings_field(
363
+ $key . '-analytics-optin', // Field ID.
364
+ __( 'Usage Tracking', 'woo-cart-abandonment-recovery' ), // Field title.
365
+ array( $this, 'render_settings_field_html' ), // Field callback function.
366
+ 'general',
367
+ 'default', // Settings page slug.
368
+ array(
369
+ 'type' => 'checkbox',
370
+ 'title' => $author,
371
+ 'name' => $key . '_analytics_optin',
372
+ 'label_for' => $key . '-analytics-optin',
373
+ 'id' => $key . '-analytics-optin',
374
+ 'usage_doc_link' => $usage_doc_link,
375
+ )
376
+ );
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Sanitize Callback Function
382
+ *
383
+ * @param bool $input Option value.
384
+ * @since 1.0.0
385
+ */
386
+ public function sanitize_option( $input ) {
387
+
388
+ if ( ! $input || 'no' === $input ) {
389
+ return 'no';
390
+ }
391
+
392
+ return 'yes';
393
+ }
394
+
395
+ /**
396
+ * Print settings field HTML.
397
+ *
398
+ * @param array $args arguments to field.
399
+ * @since 1.0.0
400
+ */
401
+ public function render_settings_field_html( $args ) {
402
+ ?>
403
+ <fieldset>
404
+ <label for="<?php echo esc_attr( $args['label_for'] ); ?>">
405
+ <input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( get_site_option( $args['name'], 'no' ), 'yes' ); ?>>
406
+ <?php
407
+ /* translators: %s Product title */
408
+ echo esc_html( sprintf( __( 'Allow %s products to track non-sensitive usage tracking data.', 'woo-cart-abandonment-recovery' ), $args['title'] ) );// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
409
+
410
+ if ( is_multisite() ) {
411
+ esc_html_e( ' This will be applicable for all sites from the network.', 'woo-cart-abandonment-recovery' );
412
+ }
413
+ ?>
414
+ </label>
415
+ <?php
416
+ echo wp_kses_post( sprintf( '<a href="%1s" target="_blank" rel="noreferrer noopener">%2s</a>', esc_url( $args['usage_doc_link'] ), __( 'Learn More.', 'woo-cart-abandonment-recovery' ) ) );
417
+ ?>
418
+ </fieldset>
419
+ <?php
420
+ }
421
+
422
+ /**
423
+ * Set analytics installed time in option.
424
+ *
425
+ * @param string $source source of analytics.
426
+ * @return string $time analytics installed time.
427
+ * @since 1.0.0
428
+ */
429
+ private function get_analytics_install_time( $source ) {
430
+
431
+ $time = get_site_option( $source . '_analytics_installed_time' );
432
+
433
+ if ( ! $time ) {
434
+ $time = time();
435
+ update_site_option( $source . '_analytics_installed_time', time() );
436
+ }
437
+
438
+ return $time;
439
+ }
440
+
441
+ /**
442
+ * Schedule/unschedule cron event on updation of option.
443
+ *
444
+ * @param string $old_value old value of option.
445
+ * @param string $value value of option.
446
+ * @param string $option Option name.
447
+ * @since 1.0.0
448
+ */
449
+ public function update_analytics_option_callback( $old_value, $value, $option ) {
450
+ if ( is_multisite() ) {
451
+ $this->add_option_to_network( $option, $value );
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Analytics option add callback.
457
+ *
458
+ * @param string $option Option name.
459
+ * @param string $value value of option.
460
+ * @since 1.0.0
461
+ */
462
+ public function add_analytics_option_callback( $option, $value ) {
463
+ if ( is_multisite() ) {
464
+ $this->add_option_to_network( $option, $value );
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Send analaytics track event if tracking is enabled.
470
+ *
471
+ * @since 1.0.0
472
+ */
473
+ public function maybe_track_analytics() {
474
+
475
+ if ( ! $this->is_tracking_enabled() ) {
476
+ return;
477
+ }
478
+
479
+ $analytics_track = get_site_transient( 'bsf_analytics_track' );
480
+
481
+ // If the last data sent is 2 days old i.e. transient is expired.
482
+ if ( ! $analytics_track ) {
483
+ $this->send();
484
+ set_site_transient( 'bsf_analytics_track', true, 2 * DAY_IN_SECONDS );
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Save analytics option to network.
490
+ *
491
+ * @param string $option name of option.
492
+ * @param string $value value of option.
493
+ * @since 1.0.0
494
+ */
495
+ public function add_option_to_network( $option, $value ) {
496
+
497
+ // If action coming from general settings page.
498
+ if ( isset( $_POST['option_page'] ) && 'general' === $_POST['option_page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
499
+
500
+ if ( get_site_option( $option ) ) {
501
+ update_site_option( $option, $value );
502
+ } else {
503
+ add_site_option( $option, $value );
504
+ }
505
+ }
506
+ }
507
+ }
508
+ }
admin/bsf-analytics/version.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ {
2
+ "bsf-analytics-ver": "1.1.2"
3
+ }
4
+
changelog.txt CHANGED
@@ -1,3 +1,11 @@
 
 
 
 
 
 
 
 
1
  Version 1.2.13 - Tuesday, 13th July 2021
2
  - Fix: Some strings of the plugin were not translatable.
3
 
@@ -124,4 +132,4 @@ Version 1.1.0 - Thursday, 30th May 2019
124
  - Added a view for admin to check email status specific to the particular abandoned user.
125
 
126
  Version 1.0.0 - Monday, 27th May 2019
127
- - Initial Release
1
+ Version 1.2.14 - Tuesday, 5th April 2022
2
+ - New: Added cron cutoff time option in settings.
3
+ Note: If you are using the custom code to update the cron time then please remove it & update same in new option.
4
+ - New: Added an option to append the query parameters to recovery link.
5
+ - Improvement: Showing large product images in outlook in some cases.
6
+ - Improvement: Added filter to update the default first name of customer.
7
+ - Fix: Product price not showing as configured in Woocommerce settings.
8
+
9
  Version 1.2.13 - Tuesday, 13th July 2021
10
  - Fix: Some strings of the plugin were not translatable.
11
 
132
  - Added a view for admin to check email status specific to the particular abandoned user.
133
 
134
  Version 1.0.0 - Monday, 27th May 2019
135
+ - Initial Release
classes/class-cartflows-ca-admin-notices.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * CartFlows Ca Admin Notices.
4
+ *
5
+ * @package CartFlows
6
+ */
7
+
8
+ // Exit if accessed directly.
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+ /**
13
+ * Class Cartflows_Ca_Admin_Notices.
14
+ */
15
+ class Cartflows_Ca_Admin_Notices {
16
+
17
+ /**
18
+ * Instance
19
+ *
20
+ * @access private
21
+ * @var object Class object.
22
+ * @since 1.0.0
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ *
29
+ * @since 1.0.0
30
+ * @return object initialized object of class.
31
+ */
32
+ public static function get_instance() {
33
+ if ( ! isset( self::$instance ) ) {
34
+ self::$instance = new self();
35
+ }
36
+ return self::$instance;
37
+ }
38
+
39
+ /**
40
+ * Constructor
41
+ */
42
+ public function __construct() {
43
+
44
+ add_action( 'admin_init', array( $this, 'show_admin_notices' ) );
45
+ }
46
+
47
+ /**
48
+ * Show admin notices.
49
+ */
50
+ public function show_admin_notices() {
51
+
52
+ $image_path = esc_url( CARTFLOWS_CA_URL . 'admin/assets/images/cartflows-logo-small.jpg' );
53
+
54
+ Astra_Notices::add_notice(
55
+ array(
56
+ 'id' => 'cartflows-ca-5-star-notice',
57
+ 'type' => 'info',
58
+ 'class' => 'cartflows-ca-5-star',
59
+ 'show_if' => true,
60
+ /* translators: %1$s white label plugin name and %2$s deactivation link */
61
+ 'message' => sprintf(
62
+ '<div class="notice-image" style="display: flex;">
63
+ <img src="%1$s" class="custom-logo" alt="CartFlows Icon" itemprop="logo" style="max-width: 90px; border-radius: 50px;"></div>
64
+ <div class="notice-content">
65
+ <div class="notice-heading">
66
+ %2$s
67
+ </div>
68
+ %3$s<br />
69
+ <div class="astra-review-notice-container">
70
+ <a href="%4$s" class="astra-notice-close astra-review-notice button-primary" target="_blank">
71
+ %5$s
72
+ </a>
73
+ <span class="dashicons dashicons-calendar"></span>
74
+ <a href="#" data-repeat-notice-after="%6$s" class="astra-notice-close astra-review-notice">
75
+ %7$s
76
+ </a>
77
+ <span class="dashicons dashicons-smiley"></span>
78
+ <a href="#" class="astra-notice-close astra-review-notice">
79
+ %8$s
80
+ </a>
81
+ </div>
82
+ </div>',
83
+ $image_path,
84
+ __( 'Hello! Seems like you have used WooCommerce Cart Abandonment Recovery plugin to recover abandoned carts. &mdash; Thanks a ton!', 'woo-cart-abandonment-recovery' ),
85
+ __( 'Could you please do us a BIG favor and give it a 5-star rating on WordPress? This would boost our motivation and help other users make a comfortable decision while choosing the CartFlows cart abandonment plugin.', 'woo-cart-abandonment-recovery' ),
86
+ 'https://wordpress.org/support/plugin/woo-cart-abandonment-recovery/reviews/?filter=5#new-post',
87
+ __( 'Ok, you deserve it', 'woo-cart-abandonment-recovery' ),
88
+ MONTH_IN_SECONDS,
89
+ __( 'Nope, maybe later', 'woo-cart-abandonment-recovery' ),
90
+ __( 'I already did', 'woo-cart-abandonment-recovery' )
91
+ ),
92
+ 'repeat-notice-after' => MONTH_IN_SECONDS,
93
+ 'display-notice-after' => ( 3 * WEEK_IN_SECONDS ), // Display notice after 2 weeks.
94
+ )
95
+ );
96
+ }
97
+ }
98
+
99
+ Cartflows_Ca_Admin_Notices::get_instance();
classes/class-cartflows-ca-loader.php CHANGED
@@ -82,7 +82,7 @@ if ( ! class_exists( 'CARTFLOWS_CA_Loader' ) ) {
82
  define( 'CARTFLOWS_CA_BASE', plugin_basename( CARTFLOWS_CA_FILE ) );
83
  define( 'CARTFLOWS_CA_DIR', plugin_dir_path( CARTFLOWS_CA_FILE ) );
84
  define( 'CARTFLOWS_CA_URL', plugins_url( '/', CARTFLOWS_CA_FILE ) );
85
- define( 'CARTFLOWS_CA_VER', '1.2.13' );
86
 
87
  define( 'CARTFLOWS_CA_SLUG', 'cartflows_ca' );
88
 
@@ -190,8 +190,8 @@ if ( ! class_exists( 'CARTFLOWS_CA_Loader' ) ) {
190
  */
191
  public function initialize_cart_abandonment_tables() {
192
 
193
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-cart-abandonment-db.php';
194
- $db = Cartflows_Ca_Cart_Abandonment_Db::get_instance();
195
  $db->create_tables();
196
  $db->template_table_seeder();
197
  }
@@ -219,6 +219,30 @@ if ( ! class_exists( 'CARTFLOWS_CA_Loader' ) ) {
219
 
220
  include_once CARTFLOWS_CA_DIR . 'classes/class-cartflows-ca-settings.php';
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
 
224
  /**
@@ -286,7 +310,7 @@ if ( ! class_exists( 'CARTFLOWS_CA_Loader' ) ) {
286
  public function load_core_components() {
287
 
288
  /* Cart abandonment templates class */
289
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-module-loader.php';
290
 
291
  }
292
 
82
  define( 'CARTFLOWS_CA_BASE', plugin_basename( CARTFLOWS_CA_FILE ) );
83
  define( 'CARTFLOWS_CA_DIR', plugin_dir_path( CARTFLOWS_CA_FILE ) );
84
  define( 'CARTFLOWS_CA_URL', plugins_url( '/', CARTFLOWS_CA_FILE ) );
85
+ define( 'CARTFLOWS_CA_VER', '1.2.14' );
86
 
87
  define( 'CARTFLOWS_CA_SLUG', 'cartflows_ca' );
88
 
190
  */
191
  public function initialize_cart_abandonment_tables() {
192
 
193
+ include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/classes/class-cartflows-ca-database.php';
194
+ $db = Cartflows_Ca_Database::get_instance();
195
  $db->create_tables();
196
  $db->template_table_seeder();
197
  }
219
 
220
  include_once CARTFLOWS_CA_DIR . 'classes/class-cartflows-ca-settings.php';
221
 
222
+ include_once CARTFLOWS_CA_DIR . 'classes/class-cartflows-ca-tabs.php';
223
+
224
+ if ( is_admin() ) {
225
+ require_once CARTFLOWS_CA_DIR . 'lib/astra-notices/class-astra-notices.php';
226
+ }
227
+
228
+ if ( ! class_exists( 'BSF_Analytics_Loader' ) ) {
229
+ require_once CARTFLOWS_CA_DIR . '/admin/bsf-analytics/class-bsf-analytics-loader.php';
230
+ }
231
+
232
+ $bsf_analytics = BSF_Analytics_Loader::get_instance();
233
+
234
+ $bsf_analytics->set_entity(
235
+ array(
236
+ 'cf' => array(
237
+ 'product_name' => 'Woocommerce Cart Abandonment Recovery',
238
+ 'usage_doc_link' => 'https://my.cartflows.com/usage-tracking/',
239
+ 'path' => CARTFLOWS_CA_DIR . 'admin/bsf-analytics',
240
+ 'author' => 'CartFlows Inc',
241
+ ),
242
+ )
243
+ );
244
+
245
+ include_once CARTFLOWS_CA_DIR . 'classes/class-cartflows-ca-admin-notices.php';
246
  }
247
 
248
  /**
310
  public function load_core_components() {
311
 
312
  /* Cart abandonment templates class */
313
+ include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/classes/class-cartflows-ca-module-loader.php';
314
 
315
  }
316
 
classes/class-cartflows-ca-settings.php CHANGED
@@ -66,7 +66,7 @@ class Cartflows_Ca_Settings {
66
  array( $this, 'wcf_ca_status_callback' ),
67
  WCF_CA_PAGE_NAME,
68
  WCF_CA_GENERAL_SETTINGS_SECTION,
69
- array( __( 'Start capturing abandoned carts. <br/><br/> <span class="description"><strong>Note:</strong> Cart will be considered abandoned if order is not completed in <strong>15 minutes</strong>.</span>', 'woo-cart-abandonment-recovery' ) )
70
  );
71
 
72
  register_setting(
@@ -74,6 +74,20 @@ class Cartflows_Ca_Settings {
74
  'wcf_ca_status'
75
  );
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  add_settings_field(
78
  'wcf_ca_ignore_users',
79
  __( 'Disable Tracking For', 'woo-cart-abandonment-recovery' ),
@@ -116,6 +130,20 @@ class Cartflows_Ca_Settings {
116
  'wcar_email_admin_on_recovery'
117
  );
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  // End: General Settings for cart abandonment.
120
  // Start: Delete coupons settings for cart abandonment.
121
 
@@ -372,6 +400,23 @@ class Cartflows_Ca_Settings {
372
 
373
  }
374
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  /**
376
  * Callback for cart abandonment status.
377
  *
@@ -593,6 +638,24 @@ class Cartflows_Ca_Settings {
593
  echo wp_kses_post( $html );
594
  }
595
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  /**
597
  * Callback for ignore users from tracking cart.
598
  *
@@ -605,7 +668,7 @@ class Cartflows_Ca_Settings {
605
  $html = '';
606
  $roles_obj = new WP_Roles();
607
  $roles_names_array = $roles_obj->get_names();
608
- $roles_names_array = array_diff( $roles_names_array, ( is_array( 'Customer' ) ? $value : array( 'Customer' ) ) );
609
  ?>
610
  <p class="wcf_ca_ignore_users" name="wcf_ca_ignore_users" multiple="multiple">
611
  <?php
66
  array( $this, 'wcf_ca_status_callback' ),
67
  WCF_CA_PAGE_NAME,
68
  WCF_CA_GENERAL_SETTINGS_SECTION,
69
+ array( __( 'Start capturing abandoned carts. <br/><br/> <span class="description"><strong>Note:</strong> Cart will be considered abandoned if order is not completed in cart abandoned cut-off time.</span>', 'woo-cart-abandonment-recovery' ) )
70
  );
71
 
72
  register_setting(
74
  'wcf_ca_status'
75
  );
76
 
77
+ add_settings_field(
78
+ 'wcf_ca_cron_run_time',
79
+ __( 'Cart abandoned cut-off time', 'woo-cart-abandonment-recovery' ),
80
+ array( $this, 'wcf_ca_cron_run_time_callback' ),
81
+ WCF_CA_PAGE_NAME,
82
+ WCF_CA_GENERAL_SETTINGS_SECTION,
83
+ array( __( 'Minutes. <br/><br/> <span class="description"><strong>Note:</strong> Consider cart abandoned after above entered minutes of item being added to cart and order not placed.</span><span class="dashicons dashicons-editor-help" title="Please remove the custom code to update cron cut off time, if added."></span>', 'woo-cart-abandonment-recovery' ) )
84
+ );
85
+
86
+ register_setting(
87
+ WCF_CA_SETTINGS_OPTION_GROUP,
88
+ 'wcf_ca_cron_run_time'
89
+ );
90
+
91
  add_settings_field(
92
  'wcf_ca_ignore_users',
93
  __( 'Disable Tracking For', 'woo-cart-abandonment-recovery' ),
130
  'wcar_email_admin_on_recovery'
131
  );
132
 
133
+ add_settings_field(
134
+ 'wcf_ca_global_param',
135
+ __( 'UTM parameters', 'woo-cart-abandonment-recovery' ),
136
+ array( $this, 'wcar_add_param_to_recovery_url' ),
137
+ WCF_CA_PAGE_NAME,
138
+ WCF_CA_GENERAL_SETTINGS_SECTION,
139
+ array( '<br><span>' . __( 'The UTM parameters will be appended to the checkout page links which is available in the recovery emails.', 'woo-cart-abandonment-recovery' ) . '</span>' )
140
+ );
141
+
142
+ register_setting(
143
+ WCF_CA_SETTINGS_OPTION_GROUP,
144
+ 'wcf_ca_global_param'
145
+ );
146
+
147
  // End: General Settings for cart abandonment.
148
  // Start: Delete coupons settings for cart abandonment.
149
 
400
 
401
  }
402
 
403
+ /**
404
+ * Callback for add utm param.
405
+ *
406
+ * @param array $args args.
407
+ * @since 1.2.13
408
+ */
409
+ public function wcar_add_param_to_recovery_url( $args ) {
410
+ $wcf_ca_global_param = get_option( 'wcf_ca_global_param', false );
411
+ $html = '';
412
+ printf(
413
+ '<textarea rows="4" cols="60" id="wcf_ca_global_param" name="wcf_ca_global_param" spellcheck="false" placeholder=" ' . __( 'Add UTM parameter per line.', 'woo-cart-abandonment-recovery' ) . '">%s</textarea>',
414
+ isset( $wcf_ca_global_param ) ? esc_attr( $wcf_ca_global_param ) : ''
415
+ );
416
+ $html .= '<label for="wcf_ca_global_param"> ' . $args[0] . '</label>';
417
+ echo wp_kses_post( $html );
418
+ }
419
+
420
  /**
421
  * Callback for cart abandonment status.
422
  *
638
  echo wp_kses_post( $html );
639
  }
640
 
641
+ /**
642
+ * Callback for cart abandonment status.
643
+ *
644
+ * @param array $args args.
645
+ * @since 1.1.5
646
+ */
647
+ public static function wcf_ca_cron_run_time_callback( $args ) {
648
+ $wcf_ca_cron_run_time = apply_filters( 'woo_ca_update_order_cron_interval', get_option( 'wcf_ca_cron_run_time', 15 ) );
649
+ printf(
650
+ '<input class="wcf-ca-trigger-input wcf-ca-email-inputs" type="number" id="wcf_ca_cron_run_time" name="wcf_ca_cron_run_time" value="%s" />',
651
+ esc_attr( $wcf_ca_cron_run_time )
652
+ );
653
+
654
+ $html = '<label for="wcf_ca_cron_run_time"> ' . $args[0] . '</label>';
655
+ echo wp_kses_post( $html );
656
+ }
657
+
658
+
659
  /**
660
  * Callback for ignore users from tracking cart.
661
  *
668
  $html = '';
669
  $roles_obj = new WP_Roles();
670
  $roles_names_array = $roles_obj->get_names();
671
+ $roles_names_array = array_diff( $roles_names_array, array( 'Customer' ) );
672
  ?>
673
  <p class="wcf_ca_ignore_users" name="wcf_ca_ignore_users" multiple="multiple">
674
  <?php
classes/class-cartflows-ca-tabs.php ADDED
@@ -0,0 +1,505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Settings.
4
+ *
5
+ * @package Woocommerce-Cart-Abandonment-Recovery
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ /**
13
+ * Class Cartflows_Ca_Utils.
14
+ */
15
+ class Cartflows_Ca_Tabs {
16
+
17
+
18
+ /**
19
+ * Class instance.
20
+ *
21
+ * @access private
22
+ * @var $instance Class instance.
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ */
29
+ public static function get_instance() {
30
+ if ( ! isset( self::$instance ) ) {
31
+ self::$instance = new self();
32
+ }
33
+ return self::$instance;
34
+ }
35
+
36
+
37
+ /**
38
+ * Cartflows_Ca_Settings constructor.
39
+ */
40
+ public function __construct() {
41
+
42
+ // Adding menu to view cart abandonment report.
43
+ add_action( 'admin_menu', array( $this, 'abandoned_cart_tracking_menu' ), 999 );
44
+ }
45
+
46
+ /**
47
+ * Add submenu to admin menu.
48
+ *
49
+ * @since 1.1.5
50
+ */
51
+ public function abandoned_cart_tracking_menu() {
52
+
53
+ $capability = current_user_can( 'manage_woocommerce' ) ? 'manage_woocommerce' : 'manage_options';
54
+
55
+ add_submenu_page(
56
+ 'woocommerce',
57
+ __( 'Cart Abandonment', 'woo-cart-abandonment-recovery' ),
58
+ __( 'Cart Abandonment', 'woo-cart-abandonment-recovery' ),
59
+ $capability,
60
+ WCF_CA_PAGE_NAME,
61
+ array( $this, 'render_abandoned_cart_tracking' )
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Render table view for cart abandonment tracking.
67
+ *
68
+ * @since 1.1.5
69
+ */
70
+ public function render_abandoned_cart_tracking() {
71
+
72
+ $wcf_list_table = Cartflows_Ca_Order_Table::get_instance();
73
+
74
+ if ( 'delete' === $wcf_list_table->current_action() ) {
75
+
76
+ $ids = array();
77
+ if ( isset( $_REQUEST['id'] ) && is_array( $_REQUEST['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
78
+ $ids = array_map( 'intval', $_REQUEST['id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
79
+ }
80
+ $deleted_row_count = empty( $ids ) ? 1 : count( $ids );
81
+
82
+ $wcf_list_table->process_bulk_action();
83
+ $message = '<div class="notice notice-success is-dismissible" id="message"><p>' . sprintf( __( 'Items deleted: %d', 'woo-cart-abandonment-recovery' ), $deleted_row_count ) . '</p></div>'; // phpcs:ignore
84
+ set_transient( 'wcf_ca_show_message', $message, 5 );
85
+ if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
86
+ wp_safe_redirect( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) );
87
+ }
88
+ } elseif ( 'unsubscribe' === $wcf_list_table->current_action() ) {
89
+
90
+ global $wpdb;
91
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
92
+ $id = filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
93
+
94
+ $wpdb->update(
95
+ $cart_abandonment_table,
96
+ array( 'unsubscribed' => true ),
97
+ array( 'id' => $id )
98
+ );
99
+ $wcf_list_table->process_bulk_action();
100
+ $message = '<div class="notice notice-success is-dismissible" id="message"><p>' . sprintf( __( 'User(s) unsubscribed successfully!', 'woo-cart-abandonment-recovery' ) ) . '</p></div>'; // phpcs:ignore
101
+ set_transient( 'wcf_ca_show_message', $message, 5 );
102
+ if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
103
+ wp_safe_redirect( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) );
104
+ }
105
+ }
106
+ ?>
107
+
108
+ <?php
109
+ include_once CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR . 'includes/cartflows-cart-abandonment-tabs.php';
110
+ ?>
111
+ <?php
112
+ }
113
+ /**
114
+ * Render Cart abandonment tabs.
115
+ *
116
+ * @since 1.1.5
117
+ */
118
+ public function wcf_display_tabs() {
119
+
120
+ $wcar_action = filter_input( INPUT_GET, 'action', FILTER_SANITIZE_STRING );
121
+ $sub_action = filter_input( INPUT_GET, 'sub_action', FILTER_SANITIZE_STRING );
122
+
123
+ if ( ! $wcar_action ) {
124
+ $wcar_action = WCF_ACTION_REPORTS;
125
+ $active_settings = '';
126
+ $active_reports = '';
127
+ $active_email_templates = '';
128
+ }
129
+
130
+ switch ( $wcar_action ) {
131
+ case WCF_ACTION_SETTINGS:
132
+ $active_settings = 'nav-tab-active';
133
+ break;
134
+ case WCF_ACTION_REPORTS:
135
+ $active_reports = 'nav-tab-active';
136
+ break;
137
+ case WCF_ACTION_EMAIL_TEMPLATES:
138
+ $active_email_templates = 'nav-tab-active';
139
+ break;
140
+ default:
141
+ $active_reports = 'nav-tab-active';
142
+ break;
143
+ }
144
+ // phpcs:disable
145
+ ?>
146
+
147
+
148
+ <div class="nav-tab-wrapper woo-nav-tab-wrapper">
149
+
150
+ <?php
151
+ $url = add_query_arg( array(
152
+ 'page' => WCF_CA_PAGE_NAME,
153
+ 'action' => WCF_ACTION_REPORTS
154
+ ), admin_url( '/admin.php' ) )
155
+ ?>
156
+ <a href="<?php echo $url; ?>"
157
+ class="nav-tab
158
+ <?php
159
+ if ( isset( $active_reports ) ) {
160
+ echo $active_reports;}
161
+ ?>
162
+ ">
163
+ <?php _e( 'Report', 'woo-cart-abandonment-recovery' ); ?>
164
+ </a>
165
+
166
+ <?php
167
+ $url = add_query_arg( array(
168
+ 'page' => WCF_CA_PAGE_NAME,
169
+ 'action' => WCF_ACTION_EMAIL_TEMPLATES
170
+ ), admin_url( '/admin.php' ) )
171
+ ?>
172
+ <a href="<?php echo $url; ?>"
173
+ class="nav-tab
174
+ <?php
175
+ if ( isset( $active_email_templates ) ) {
176
+ echo $active_email_templates;}
177
+ ?>
178
+ ">
179
+ <?php _e( 'Follow-Up Emails', 'woo-cart-abandonment-recovery' ); ?>
180
+ </a>
181
+
182
+ <?php
183
+ $url = add_query_arg( array(
184
+ 'page' => WCF_CA_PAGE_NAME,
185
+ 'action' => WCF_ACTION_SETTINGS
186
+ ), admin_url( '/admin.php' ) )
187
+ ?>
188
+ <a href="<?php echo $url; ?>"
189
+ class="nav-tab
190
+ <?php
191
+ if ( isset( $active_settings ) ) {
192
+ echo $active_settings;}
193
+ ?>
194
+ ">
195
+ <?php _e( 'Settings', 'woo-cart-abandonment-recovery' ); ?>
196
+ </a>
197
+
198
+ </div>
199
+ <?php
200
+ // phpcs:enable
201
+ }
202
+
203
+ /**
204
+ * Render Cart abandonment settings.
205
+ *
206
+ * @since 1.1.5
207
+ */
208
+ public function wcf_display_settings() {
209
+ ?>
210
+
211
+ <form method="post" action="options.php">
212
+ <?php settings_fields( WCF_CA_SETTINGS_OPTION_GROUP ); ?>
213
+ <?php do_settings_sections( WCF_CA_PAGE_NAME ); ?>
214
+ <?php submit_button(); ?>
215
+ </form>
216
+
217
+ <?php
218
+ }
219
+
220
+ /**
221
+ * Render Cart abandonment reports.
222
+ *
223
+ * @since 1.1.5
224
+ */
225
+ public function wcf_display_reports() {
226
+
227
+ $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
228
+ $filter_table = filter_input( INPUT_GET, 'filter_table', FILTER_SANITIZE_STRING );
229
+
230
+ if ( ! $filter ) {
231
+ $filter = 'last_month';
232
+ }
233
+ if ( ! $filter_table ) {
234
+ $filter_table = WCF_CART_ABANDONED_ORDER;
235
+ }
236
+
237
+ $from_date = filter_input( INPUT_GET, 'from_date', FILTER_SANITIZE_STRING );
238
+ $to_date = filter_input( INPUT_GET, 'to_date', FILTER_SANITIZE_STRING );
239
+ $export_data = filter_input( INPUT_GET, 'export_data', FILTER_VALIDATE_BOOLEAN );
240
+
241
+ switch ( $filter ) {
242
+
243
+ case 'yesterday':
244
+ $to_date = gmdate( 'Y-m-d', strtotime( '-1 days' ) );
245
+ $from_date = $to_date;
246
+ break;
247
+ case 'today':
248
+ $to_date = gmdate( 'Y-m-d' );
249
+ $from_date = $to_date;
250
+ break;
251
+ case 'last_week':
252
+ $from_date = gmdate( 'Y-m-d', strtotime( '-7 days' ) );
253
+ $to_date = gmdate( 'Y-m-d' );
254
+ break;
255
+ case 'last_month':
256
+ $from_date = gmdate( 'Y-m-d', strtotime( '-1 months' ) );
257
+ $to_date = gmdate( 'Y-m-d' );
258
+ break;
259
+ case 'custom':
260
+ $to_date = $to_date ? $to_date : gmdate( 'Y-m-d' );
261
+ $from_date = $from_date ? $from_date : $to_date;
262
+ break;
263
+
264
+ }
265
+
266
+ $abandoned_report = $this->get_report_by_type( $from_date, $to_date, WCF_CART_ABANDONED_ORDER );
267
+ $recovered_report = $this->get_report_by_type( $from_date, $to_date, WCF_CART_COMPLETED_ORDER );
268
+ $lost_report = $this->get_report_by_type( $from_date, $to_date, WCF_CART_LOST_ORDER );
269
+
270
+ $wcf_list_table = Cartflows_Ca_Order_Table::get_instance();
271
+ $wcf_list_table->prepare_items( $filter_table, $from_date, $to_date );
272
+
273
+ if ( $export_data ) {
274
+
275
+ $this->download_send_headers();
276
+ echo $this->array2csv( $wcf_list_table->items ); //phpcs:ignore
277
+ die;
278
+
279
+ }
280
+
281
+ $conversion_rate = 0;
282
+ $total_orders = ( $recovered_report['no_of_orders'] + $abandoned_report['no_of_orders'] + $lost_report['no_of_orders'] );
283
+ if ( $total_orders ) {
284
+ $conversion_rate = ( $recovered_report['no_of_orders'] / $total_orders ) * 100;
285
+ }
286
+
287
+ global $woocommerce;
288
+ $conversion_rate = number_format_i18n( $conversion_rate, 2 );
289
+ $currency_symbol = get_woocommerce_currency_symbol();
290
+ require_once CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR . 'includes/cartflows-cart-abandonment-reports.php';
291
+
292
+ }
293
+
294
+ /**
295
+ * Send headers to export orders to csv format.
296
+ */
297
+ public function download_send_headers() {
298
+ $now = gmdate( 'Y-m-d-H-i-s' );
299
+ $filename = 'woo-cart-abandonment-recovery-export-' . $now . '.csv';
300
+
301
+ header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
302
+ header( "Last-Modified: {$now} GMT" );
303
+
304
+ // force download.
305
+ header( 'Content-Type: application/force-download' );
306
+ header( 'Content-Type: application/octet-stream' );
307
+ header( 'Content-Type: application/download' );
308
+
309
+ // disposition / encoding on response body.
310
+ header( "Content-Disposition: attachment;filename={$filename}" );
311
+ header( 'Content-Transfer-Encoding: binary' );
312
+ }
313
+
314
+ /**
315
+ * Convert users data to csv format.
316
+ *
317
+ * @param array $user_data users data.
318
+ */
319
+ public function array2csv( array $user_data ) {
320
+ if ( empty( $user_data ) ) {
321
+ return;
322
+ }
323
+ ob_clean();
324
+ ob_start();
325
+ $data_file = fopen( 'php://output', 'w' );
326
+ fputcsv(
327
+ $data_file,
328
+ array(
329
+ 'First-Name',
330
+ 'Last-Name',
331
+ 'Email',
332
+ 'Phone',
333
+ 'Products',
334
+ 'Cart-Total in ' . get_woocommerce_currency(),
335
+ 'Order-Status',
336
+ 'Unsubscribed',
337
+ 'Coupon-Code',
338
+ )
339
+ );
340
+
341
+ foreach ( $user_data as $data ) {
342
+ $name = unserialize( $data['other_fields'] );
343
+ $checkout_details = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $data['session_id'] );
344
+ $cart_data = Cartflows_Ca_Helper::get_instance()->get_comma_separated_products( $checkout_details->cart_contents );
345
+ fputcsv(
346
+ $data_file,
347
+ array(
348
+ $name['wcf_first_name'],
349
+ $name['wcf_last_name'],
350
+ $data['email'],
351
+ $name['wcf_phone_number'],
352
+ $cart_data,
353
+ $data['cart_total'],
354
+ $data['order_status'],
355
+ $data['unsubscribed'] ? 'Yes' : 'No',
356
+ $data['coupon_code'],
357
+ )
358
+ );
359
+
360
+ }
361
+ fclose( $data_file ); //phpcs:ignore
362
+ return ob_get_clean();
363
+ }
364
+
365
+ /**
366
+ * Get Attributable revenue.
367
+ * Represents the revenue generated by this campaign.
368
+ *
369
+ * @param string $from_date from date.
370
+ * @param string $to_date to date.
371
+ * @param string $type abondened|completed.
372
+ */
373
+ public function get_report_by_type( $from_date, $to_date, $type = WCF_CART_ABANDONED_ORDER ) {
374
+ global $wpdb;
375
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
376
+ $minutes = wcf_ca()->utils->get_cart_abandonment_tracking_cut_off_time();
377
+ $attributable_revenue = $wpdb->get_row(
378
+ $wpdb->prepare( "SELECT SUM(`cart_total`) as revenue, count('*') as no_of_orders FROM {$cart_abandonment_table} WHERE `order_status` = %s AND DATE(`time`) >= %s AND DATE(`time`) <= %s ", $type, $from_date, $to_date ), // phpcs:ignore
379
+ ARRAY_A
380
+ );
381
+ return $attributable_revenue;
382
+ }
383
+
384
+ /**
385
+ * Show report details for specific order.
386
+ */
387
+ public function wcf_display_report_details() {
388
+
389
+ $sesson_id = filter_input( INPUT_GET, 'session_id', FILTER_SANITIZE_STRING );
390
+
391
+ if ( $sesson_id ) {
392
+ $details = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $sesson_id );
393
+ $user_details = (object) unserialize( $details->other_fields );
394
+ $scheduled_emails = Cartflows_Ca_Helper::get_instance()->fetch_scheduled_emails( $sesson_id );
395
+
396
+ require_once CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR . 'includes/cartflows-ca-single-report-details.php';
397
+ }
398
+
399
+ }
400
+
401
+ /**
402
+ * Generate the view for admin product cart block.
403
+ *
404
+ * @param string $cart_contents user cart contents details.
405
+ * @param float $cart_total user cart total.
406
+ * @return string
407
+ */
408
+ public function get_admin_product_block( $cart_contents, $cart_total ) {
409
+
410
+ $cart_items = unserialize( $cart_contents );
411
+
412
+ if ( ! is_array( $cart_items ) || ! count( $cart_items ) ) {
413
+ return;
414
+ }
415
+
416
+ $tr = '';
417
+ $total = 0;
418
+ $discount = 0;
419
+ $tax = 0;
420
+
421
+ foreach ( $cart_items as $cart_item ) {
422
+
423
+ if ( isset( $cart_item['product_id'] ) && isset( $cart_item['quantity'] ) && isset( $cart_item['line_total'] ) && isset( $cart_item['line_subtotal'] ) ) {
424
+ $id = 0 !== $cart_item['variation_id'] ? $cart_item['variation_id'] : $cart_item['product_id'];
425
+ $discount = number_format_i18n( $discount + ( $cart_item['line_subtotal'] - $cart_item['line_total'] ), 2 );
426
+ $total = number_format_i18n( $total + $cart_item['line_subtotal'], 2 );
427
+ $tax = number_format_i18n( $tax + $cart_item['line_tax'], 2 );
428
+ $image_url = get_the_post_thumbnail_url( $id );
429
+ $image_url = ! empty( $image_url ) ? $image_url : get_the_post_thumbnail_url( $cart_item['product_id'] );
430
+
431
+ $product = wc_get_product( $id );
432
+ $product_name = $product ? $product->get_formatted_name() : '';
433
+
434
+ if ( empty( $image_url ) ) {
435
+ $image_url = CARTFLOWS_CA_URL . '/assets/images/image-placeholder.png';
436
+ }
437
+ $tr = $tr . '<tr align="center">
438
+ <td ><img class="demo_img" width="42" height="42" src=" ' . esc_url( $image_url ) . ' "/></td>
439
+ <td >' . $product_name . '</td>
440
+ <td > ' . $cart_item['quantity'] . ' </td>
441
+ <td >' . wc_price( $cart_item['line_total'] ) . '</td>
442
+ <td >' . wc_price( $cart_item['line_total'] ) . '</td>
443
+ </tr> ';
444
+ }
445
+ }
446
+
447
+ return '<table align="left" cellspacing="0" class="widefat fixed striped posts">
448
+ <thead>
449
+ <tr align="center">
450
+ <th >' . __( 'Item', 'woo-cart-abandonment-recovery' ) . '</th>
451
+ <th >' . __( 'Name', 'woo-cart-abandonment-recovery' ) . '</th>
452
+ <th >' . __( 'Quantity', 'woo-cart-abandonment-recovery' ) . '</th>
453
+ <th >' . __( 'Price', 'woo-cart-abandonment-recovery' ) . '</th>
454
+ <th >' . __( 'Line Subtotal', 'woo-cart-abandonment-recovery' ) . '</th>
455
+ </tr>
456
+ </thead>
457
+ <tbody>
458
+ ' . $tr . '
459
+ <tr align="center" id="wcf-ca-discount">
460
+ <td colspan="4" >' . __( 'Discount', 'woo-cart-abandonment-recovery' ) . '</td>
461
+ <td>' . wc_price( $discount ) . '</td>
462
+ </tr>
463
+ <tr align="center" id="wcf-ca-other">
464
+ <td colspan="4" >' . __( 'Other', 'woo-cart-abandonment-recovery' ) . '</td>
465
+ <td>' . wc_price( $tax ) . '</td>
466
+ </tr>
467
+
468
+ <tr align="center" id="wcf-ca-shipping">
469
+ <td colspan="4" >' . __( 'Shipping', 'woo-cart-abandonment-recovery' ) . '</td>
470
+ <td>' . wc_price( $discount + ( $cart_total - $total ) - $tax, 2 ) . '</td>
471
+ </tr>
472
+ <tr align="center" id="wcf-ca-cart-total">
473
+ <td colspan="4" >' . __( 'Cart Total', 'woo-cart-abandonment-recovery' ) . '</td>
474
+ <td>' . wc_price( $cart_total ) . '</td>
475
+ </tr>
476
+ </tbody>
477
+ </table>';
478
+ }
479
+
480
+ /**
481
+ * Check and show warning message if cart abandonment is disabled.
482
+ */
483
+ public function wcf_show_warning_ca() {
484
+ $settings_url = add_query_arg(
485
+ array(
486
+ 'page' => WCF_CA_PAGE_NAME,
487
+ 'action' => WCF_ACTION_SETTINGS,
488
+ ),
489
+ admin_url( '/admin.php' )
490
+ );
491
+
492
+ if ( ! wcf_ca()->utils->is_cart_abandonment_tracking_enabled() ) {
493
+ ?>
494
+ <div class="notice notice-warning is-dismissible">
495
+ <p>
496
+ <?php echo __('Looks like abandonment tracking is disabled! Please enable it from <a href=' . esc_url($settings_url) . '> <strong>settings</strong></a>.', 'woo-cart-abandonment-recovery'); // phpcs:ignore
497
+ ?>
498
+ </p>
499
+ </div>
500
+ <?php
501
+ }
502
+ }
503
+
504
+ }
505
+ Cartflows_Ca_Tabs::get_instance();
languages/woo-cart-abandonment-recovery.pot CHANGED
@@ -1,17 +1,17 @@
1
- # Copyright (C) 2021 CartFlows Inc
2
  # This file is distributed under the same license as the WooCommerce Cart Abandonment Recovery plugin.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WooCommerce Cart Abandonment Recovery 1.2.12\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/woo-cart-abandonment-recovery\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "POT-Creation-Date: 2021-07-13T04:09:13+00:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
- "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: woo-cart-abandonment-recovery\n"
16
 
17
  #. Plugin Name of the plugin
@@ -31,6 +31,66 @@ msgstr ""
31
  msgid "CartFlows Inc"
32
  msgstr ""
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  #. translators: %s: html tags
35
  #: classes/class-cartflows-ca-loader.php:143
36
  msgid "The %1$sWooCommerce Cart Abandonment Recovery%2$s plugin requires %1$sWooCommerce%2$s plugin installed & activated."
@@ -53,762 +113,798 @@ msgid "Enable Tracking"
53
  msgstr ""
54
 
55
  #: classes/class-cartflows-ca-settings.php:69
56
- msgid "Start capturing abandoned carts. <br/><br/> <span class=\"description\"><strong>Note:</strong> Cart will be considered abandoned if order is not completed in <strong>15 minutes</strong>.</span>"
57
  msgstr ""
58
 
59
  #: classes/class-cartflows-ca-settings.php:79
60
- msgid "Disable Tracking For"
61
  msgstr ""
62
 
63
  #: classes/class-cartflows-ca-settings.php:83
64
- msgid " It will ignore selected users from abandonment process when they logged in, and hence they can not receive mail for cart abandoned by themselves."
65
  msgstr ""
66
 
67
  #: classes/class-cartflows-ca-settings.php:93
68
- msgid "Exclude email sending For"
69
  msgstr ""
70
 
71
  #: classes/class-cartflows-ca-settings.php:97
72
- msgid " It will not send future recovery emails to selected order status and will mark as recovered."
73
  msgstr ""
74
 
75
  #: classes/class-cartflows-ca-settings.php:107
76
- msgid "Notify recovery to admin"
77
  msgstr ""
78
 
79
  #: classes/class-cartflows-ca-settings.php:111
 
 
 
 
 
 
 
 
80
  msgid "This option will send an email to admin on new order recovery."
81
  msgstr ""
82
 
83
- #: classes/class-cartflows-ca-settings.php:124
 
 
 
 
 
 
 
 
84
  msgid "Coupons Settings"
85
  msgstr ""
86
 
87
- #: classes/class-cartflows-ca-settings.php:131
88
  msgid "Delete Coupons Automatically"
89
  msgstr ""
90
 
91
- #: classes/class-cartflows-ca-settings.php:135
92
  msgid "Delete coupons automatically on weekly basis.<br><span class=\"description\"><br/><strong>Note:</strong> This option will set a weekly cron to delete all <strong>expired</strong> and <strong>used</strong> coupons automatically in the background.</p>"
93
  msgstr ""
94
 
95
- #: classes/class-cartflows-ca-settings.php:145
96
  msgid "Delete Coupons Manually"
97
  msgstr ""
98
 
99
- #: classes/class-cartflows-ca-settings.php:149
100
  msgid "<br><strong>Note:</strong> This will delete all <strong>expired</strong> and <strong>used</strong> coupons that were created by Woo Cart Abandonment Recovery.</p>"
101
  msgstr ""
102
 
103
- #: classes/class-cartflows-ca-settings.php:161
104
  msgid "Email Settings"
105
  msgstr ""
106
 
107
- #: classes/class-cartflows-ca-settings.php:168
108
  msgid "\"From\" Name"
109
  msgstr ""
110
 
111
- #: classes/class-cartflows-ca-settings.php:172
112
  msgid "Name will appear in email sent."
113
  msgstr ""
114
 
115
- #: classes/class-cartflows-ca-settings.php:177
116
  msgid "\"From\" Address"
117
  msgstr ""
118
 
119
- #: classes/class-cartflows-ca-settings.php:181
120
  msgid "Email which send from."
121
  msgstr ""
122
 
123
- #: classes/class-cartflows-ca-settings.php:186
124
  msgid "\"Reply To\" Address"
125
  msgstr ""
126
 
127
- #: classes/class-cartflows-ca-settings.php:190
128
  msgid "When a user clicks reply, which email address should that reply be sent to?"
129
  msgstr ""
130
 
131
- #: classes/class-cartflows-ca-settings.php:213
132
  msgid "Enable Webhook"
133
  msgstr ""
134
 
135
- #: classes/class-cartflows-ca-settings.php:217
136
  msgid "Allows you to trigger webhook automatically upon cart abandonment and recovery."
137
  msgstr ""
138
 
139
- #: classes/class-cartflows-ca-settings.php:222
140
  msgid "Webhook URL"
141
  msgstr ""
142
 
143
- #: classes/class-cartflows-ca-settings.php:241
144
  msgid "Coupon Code Settings"
145
  msgstr ""
146
 
147
- #: classes/class-cartflows-ca-settings.php:248
148
  msgid "Create Coupon Code"
149
  msgstr ""
150
 
151
- #: classes/class-cartflows-ca-settings.php:252
152
  msgid "Auto-create the special coupon for the abandoned cart to send over the emails."
153
  msgstr ""
154
 
155
- #: classes/class-cartflows-ca-settings.php:257
156
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:538
157
  msgid "Discount Type"
158
  msgstr ""
159
 
160
- #: classes/class-cartflows-ca-settings.php:266
161
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:570
162
  msgid "Coupon Amount"
163
  msgstr ""
164
 
165
- #: classes/class-cartflows-ca-settings.php:275
166
  msgid "Coupon Expires After"
167
  msgstr ""
168
 
169
- #: classes/class-cartflows-ca-settings.php:279
170
  msgid "<br/><br/> <span class=\"description\"><strong>Note: </strong>. Enter zero (0) to restrict coupon from expiring.</span>"
171
  msgstr ""
172
 
173
- #: classes/class-cartflows-ca-settings.php:310
174
  msgid "Webhook Settings"
175
  msgstr ""
176
 
177
- #: classes/class-cartflows-ca-settings.php:319
178
  msgid "GDPR Settings"
179
  msgstr ""
180
 
181
- #: classes/class-cartflows-ca-settings.php:326
182
  msgid "Enable GDPR Integration"
183
  msgstr ""
184
 
185
- #: classes/class-cartflows-ca-settings.php:330
186
  msgid "Ask confirmation from the user before tracking data. <br/><br/> <span class=\"description\"><strong>Note:</strong> By checking this, it will show up confirmation text below the email id on checkout page.</span>"
187
  msgstr ""
188
 
189
- #: classes/class-cartflows-ca-settings.php:335
190
  msgid "GDPR Message"
191
  msgstr ""
192
 
193
- #: classes/class-cartflows-ca-settings.php:356
194
  msgid "Plugin Settings"
195
  msgstr ""
196
 
197
- #: classes/class-cartflows-ca-settings.php:366
198
  msgid "Delete Plugin Data"
199
  msgstr ""
200
 
201
- #: classes/class-cartflows-ca-settings.php:370
202
  msgid "Enabling this option will delete the plugin data while deleting the Plugin."
203
  msgstr ""
204
 
205
- #: classes/class-cartflows-ca-settings.php:494
206
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:608
207
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:704
 
 
 
 
208
  msgid "Hour(s)"
209
  msgstr ""
210
 
211
- #: classes/class-cartflows-ca-settings.php:495
212
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:609
213
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:705
214
  msgid "Day(s)"
215
  msgstr ""
216
 
217
- #: classes/class-cartflows-ca-settings.php:563
218
  msgid "Coupon code should be numeric and has to be greater than or equals to 1."
219
  msgstr ""
220
 
221
- #: classes/class-cartflows-ca-settings.php:695
222
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:86
223
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:138
224
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:88
225
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:122
226
  msgid "Delete"
227
  msgstr ""
228
 
229
- #: classes/class-cartflows-ca-settings.php:781
230
  msgid "Invalid email \"From\" address field"
231
  msgstr ""
232
 
233
- #: classes/class-cartflows-ca-settings.php:799
234
  msgid "Invalid email \"Reply\" address field"
235
  msgstr ""
236
 
237
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:85
238
- msgid "View"
239
- msgstr ""
240
-
241
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:90
242
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:142
243
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1800
244
- msgid "Unsubscribe"
245
- msgstr ""
246
-
247
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:236
248
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1951
249
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2010
250
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:176
251
- msgid "Name"
252
- msgstr ""
253
-
254
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:237
255
- msgid "Email"
256
- msgstr ""
257
-
258
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:238
259
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2032
260
- msgid "Cart Total"
261
- msgstr ""
262
-
263
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:239
264
- msgid "Order Status"
265
- msgstr ""
266
-
267
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php:240
268
- msgid "Time"
269
- msgstr ""
270
-
271
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:125
272
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:127
273
- msgid "New Customer Order - Recovered Order ID: "
274
- msgstr ""
275
-
276
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:211
277
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1043
278
- msgid "This order was abandoned & subsequently recovered."
279
- msgstr ""
280
-
281
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:236
282
- msgid "Mail has been sent successfully!"
283
- msgstr ""
284
-
285
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:238
286
- msgid "Mail sending failed!"
287
- msgstr ""
288
-
289
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:277
290
- msgid "Every Fifteen Minutes"
291
- msgstr ""
292
-
293
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:351
294
- msgid "You have successfully unsubscribed from our email list."
295
  msgstr ""
296
 
297
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:351
298
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:167
299
- msgid "Unsubscribed"
300
  msgstr ""
301
 
302
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:502
303
- msgid "This checkout page is generated by WooCommerce Cart Abandonment Recovery plugin from test mail."
304
  msgstr ""
305
 
306
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:544
307
- msgid "No Thanks"
308
  msgstr ""
309
 
310
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:545
311
- msgid "You won't receive further emails from us, thank you!"
312
  msgstr ""
313
 
314
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1201
315
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1202
316
- msgid "Cart Abandonment"
317
  msgstr ""
318
 
319
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1227
320
- msgid "Items deleted: %d"
 
321
  msgstr ""
322
 
323
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1244
324
- msgid "User(s) unsubscribed successfully!"
 
 
 
325
  msgstr ""
326
 
327
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1305
328
- msgid "Do you really want to delete the used and expired coupons created by Cart Abandonment Plugin?"
 
329
  msgstr ""
330
 
331
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1306
332
- msgid "Do you really want to export orders?"
 
333
  msgstr ""
334
 
335
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1310
336
- msgid "No such order is found."
 
337
  msgstr ""
338
 
339
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1329
340
- msgid "View Report"
341
  msgstr ""
342
 
343
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1467
344
- msgid "Report"
345
  msgstr ""
346
 
347
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1483
348
- msgid "Follow-Up Emails"
349
  msgstr ""
350
 
351
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1499
352
- msgid "Settings"
 
353
  msgstr ""
354
 
355
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1632
356
  msgid "Looks like abandonment tracking is disabled! Please enable it from <a href="
357
  msgstr ""
358
 
359
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1761
360
- msgid "there"
361
- msgstr ""
362
-
363
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1943
364
- msgid "Cart Total ( Cart Total + Shipping + Tax )"
365
- msgstr ""
366
-
367
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1950
368
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2009
369
- msgid "Item"
370
- msgstr ""
371
-
372
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1952
373
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2011
374
- msgid "Quantity"
375
  msgstr ""
376
 
377
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1953
378
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2012
379
- msgid "Price"
380
  msgstr ""
381
 
382
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:1954
383
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2013
384
- msgid "Line Subtotal"
385
  msgstr ""
386
 
387
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2019
388
- msgid "Discount"
389
  msgstr ""
390
 
391
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2023
392
- msgid "Other"
 
 
393
  msgstr ""
394
 
395
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2028
396
- msgid "Shipping"
397
  msgstr ""
398
 
399
- #. translators: %1$s: Coupons Deleted, %2$s: Deleted coupons count'.
400
- #: modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php:2180
401
- msgid "%1$s: %2$d"
402
  msgstr ""
403
 
404
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:76
405
  msgid "Edit"
406
  msgstr ""
407
 
408
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:100
409
  msgid "Clone"
410
  msgstr ""
411
 
412
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:189
413
  msgid "Template Name"
414
  msgstr ""
415
 
416
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:190
417
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:86
418
  msgid "Email Subject"
419
  msgstr ""
420
 
421
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:191
422
  msgid "Trigger After"
423
  msgstr ""
424
 
425
- #: modules/cart-abandonment/class-cartflows-ca-email-templates-table.php:192
426
  msgid "Activate Template"
427
  msgstr ""
428
 
429
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:132
430
  msgid "Admin Firstname"
431
  msgstr ""
432
 
433
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:133
434
  msgid "Admin Company"
435
  msgstr ""
436
 
437
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:134
438
  msgid "Abandoned Product Details Table"
439
  msgstr ""
440
 
441
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:135
442
  msgid "Abandoned Product Names"
443
  msgstr ""
444
 
445
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:136
446
  msgid "Cart Checkout URL"
447
  msgstr ""
448
 
449
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:137
450
  msgid "Coupon Code"
451
  msgstr ""
452
 
453
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:138
454
  msgid "Customer First Name"
455
  msgstr ""
456
 
457
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:139
458
  msgid "Customer Last Name"
459
  msgstr ""
460
 
461
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:140
462
  msgid "Customer Full Name"
463
  msgstr ""
464
 
465
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:141
466
  msgid "Cart Abandonment Date"
467
  msgstr ""
468
 
469
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:142
470
  msgid "Site URL"
471
  msgstr ""
472
 
473
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:143
474
  msgid "Unsubscribe Link"
475
  msgstr ""
476
 
477
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  msgid "Something went wrong"
479
  msgstr ""
480
 
481
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:170
482
  msgid "Activated"
483
  msgstr ""
484
 
485
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:173
486
  msgid "Deactivated"
487
  msgstr ""
488
 
489
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:233
490
  msgid "The Email Template has been successfully added."
491
  msgstr ""
492
 
493
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:243
494
  msgid "The Email Template has been cloned successfully."
495
  msgstr ""
496
 
497
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:253
498
  msgid "The Email Template has been successfully deleted."
499
  msgstr ""
500
 
501
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:262
502
  msgid "The Email Template has been successfully updated."
503
  msgstr ""
504
 
505
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:272
506
  msgid "Default Email Templates has been restored successfully."
507
  msgstr ""
508
 
509
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:439
510
  msgid "Activate Template now?"
511
  msgstr ""
512
 
513
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:459
514
  msgid "Template Name:"
515
  msgstr ""
516
 
517
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:474
518
  msgid "Email Subject:"
519
  msgstr ""
520
 
521
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:489
522
  msgid "Email Body:"
523
  msgstr ""
524
 
525
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:518
526
  msgid "Create Coupon"
527
  msgstr ""
528
 
529
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:531
530
  msgid "Allows you to send new coupon only for this template."
531
  msgstr ""
532
 
533
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:588
534
  msgid "Coupon expiry date"
535
  msgstr ""
536
 
537
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:618
538
  msgid "Enter zero (0) to restrict coupon from expiring"
539
  msgstr ""
540
 
541
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:624
542
  msgid "Free Shipping"
543
  msgstr ""
544
 
545
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:637
546
  msgid "Allows you to grant free shipping. A free shipping method must be enabled in your shipping zone and be set to require \"a valid free shipping coupon\". "
547
  msgstr ""
548
 
549
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:644
550
  msgid "Individual use only"
551
  msgstr ""
552
 
553
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:658
554
  msgid "Check this box if the coupon cannot be used in conjunction with other coupons."
555
  msgstr ""
556
 
557
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:665
558
  msgid "Auto Apply Coupon"
559
  msgstr ""
560
 
561
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:679
562
  msgid " Automatically add the coupon to the cart at the checkout."
563
  msgstr ""
564
 
565
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:685
566
  msgid "Send This Email"
567
  msgstr ""
568
 
569
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:703
570
  msgid "Minute(s)"
571
  msgstr ""
572
 
573
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:718
574
  msgid "after cart is abandoned."
575
  msgstr ""
576
 
577
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:728
578
  msgid "Send Test Email To:"
579
  msgstr ""
580
 
581
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:732
582
  msgid "Send a test email"
583
  msgstr ""
584
 
585
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:745
586
  msgid "Save Changes"
587
  msgstr ""
588
 
589
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:747
590
  msgid "Update Changes"
591
  msgstr ""
592
 
593
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:1053
594
  msgid "Create New Template"
595
  msgstr ""
596
 
597
- #: modules/cart-abandonment/class-cartflows-ca-email-templates.php:1056
598
  msgid " Restore Default Templates"
599
  msgstr ""
600
 
601
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  msgid "Back to Reports"
603
  msgstr ""
604
 
605
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:42
606
  msgid "Email Details:"
607
  msgstr ""
608
 
609
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:52
610
  msgid "All new activated emails will be reschedule for this abandoned order. New emails will be sent to user according to schedule time."
611
  msgstr ""
612
 
613
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:59
614
  msgid "Are your sure?"
615
  msgstr ""
616
 
617
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:63
618
  msgid "Reschedule"
619
  msgstr ""
620
 
621
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:67
622
  msgid "Close"
623
  msgstr ""
624
 
625
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:72
626
  msgid "Do you really want to reschedule emails?"
627
  msgstr ""
628
 
629
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:72
630
  msgid "Reschedule Emails"
631
  msgstr ""
632
 
633
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:78
634
  msgid " No Email Scheduled."
635
  msgstr ""
636
 
637
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:85
638
  msgid "Scheduled Template"
639
  msgstr ""
640
 
641
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:87
642
  msgid "Email Coupon"
643
  msgstr ""
644
 
645
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:88
646
  msgid "Email Sent"
647
  msgstr ""
648
 
649
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:89
650
  msgid "Scheduled At"
651
  msgstr ""
652
 
653
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:118
654
  msgid "The email has been unsubscribed and won't be sent further."
655
  msgstr ""
656
 
657
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:121
658
  msgid "Email is in the queue and will be sent at the scheduled time."
659
  msgstr ""
660
 
661
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:126
662
  msgid "The email has been sent."
663
  msgstr ""
664
 
665
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:130
666
  msgid "The email has been unscheduled due to the complete order and won't be sent further."
667
  msgstr ""
668
 
669
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:163
670
  msgid "User Address Details:"
671
  msgstr ""
672
 
673
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:175
674
  msgid "Billing Address"
675
  msgstr ""
676
 
677
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:179
678
  msgid "Email address"
679
  msgstr ""
680
 
681
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:184
682
  msgid "Phone"
683
  msgstr ""
684
 
685
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:189
686
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:209
687
  msgid "Address 1:"
688
  msgstr ""
689
 
690
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:192
691
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:212
692
  msgid "Address 2:"
693
  msgstr ""
694
 
695
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:195
696
  msgid "Country, City:"
697
  msgstr ""
698
 
699
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:198
700
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:218
701
  msgid "State:"
702
  msgstr ""
703
 
704
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:202
705
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:224
706
  msgid "Postcode:"
707
  msgstr ""
708
 
709
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:207
710
  msgid "Shipping Address"
711
  msgstr ""
712
 
713
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:215
714
  msgid "City:"
715
  msgstr ""
716
 
717
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:221
718
  msgid "Country:"
719
  msgstr ""
720
 
721
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:232
722
  msgid "Checkout Link"
723
  msgstr ""
724
 
725
- #: modules/cart-abandonment/includes/admin/cartflows-ca-single-report-details.php:247
726
  msgid "User Order Details:"
727
  msgstr ""
728
 
729
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:18
730
  msgid "Today"
731
  msgstr ""
732
 
733
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:22
734
  msgid "Yesterday"
735
  msgstr ""
736
 
737
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:26
738
  msgid "Last Week"
739
  msgstr ""
740
 
741
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:30
742
  msgid "Last Month"
743
  msgstr ""
744
 
745
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:39
746
  msgid "Custom Filter"
747
  msgstr ""
748
 
749
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:50
750
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:112
751
  msgid "Recoverable Orders"
752
  msgstr ""
753
 
754
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:54
755
  msgid "Total Recoverable Orders."
756
  msgstr ""
757
 
758
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:59
759
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:115
760
  msgid "Recovered Orders"
761
  msgstr ""
762
 
763
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:61
764
  msgid "Total Recovered Orders."
765
  msgstr ""
766
 
767
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:66
768
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:118
769
  msgid "Lost Orders"
770
  msgstr ""
771
 
772
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:69
773
  msgid "Total Lost Orders."
774
  msgstr ""
775
 
776
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:78
777
  msgid "Recoverable Revenue"
778
  msgstr ""
779
 
780
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:83
781
  msgid "Total Recoverable Revenue."
782
  msgstr ""
783
 
784
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:88
785
  msgid "Recovered Revenue"
786
  msgstr ""
787
 
788
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:94
789
  msgid "Total Recovered Revenue."
790
  msgstr ""
791
 
792
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:99
793
  msgid "Recovery Rate"
794
  msgstr ""
795
 
796
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:101
797
  msgid "Total Percentage Of Recovered Orders After Abandonment."
798
  msgstr ""
799
 
800
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:128
801
  msgid "Search by email"
802
  msgstr ""
803
 
804
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:129
805
  msgid "Search Orders"
806
  msgstr ""
807
 
808
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-reports.php:156
809
  msgid "No Orders Found."
810
  msgstr ""
811
 
812
- #: modules/cart-abandonment/includes/admin/cartflows-cart-abandonment-tabs.php:14
813
  msgid "WooCommerce Cart Abandonment Recovery "
814
  msgstr ""
1
+ # Copyright (C) 2022 CartFlows Inc
2
  # This file is distributed under the same license as the WooCommerce Cart Abandonment Recovery plugin.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WooCommerce Cart Abandonment Recovery 1.2.14\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/woo-cart-abandonment-recovery\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "POT-Creation-Date: 2022-04-05T04:18:35+00:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
+ "X-Generator: WP-CLI 2.5.0\n"
15
  "X-Domain: woo-cart-abandonment-recovery\n"
16
 
17
  #. Plugin Name of the plugin
31
  msgid "CartFlows Inc"
32
  msgstr ""
33
 
34
+ #. translators: %s product name
35
+ #: admin/bsf-analytics/class-bsf-analytics.php:216
36
+ msgid "Want to help make <strong>%1s</strong> even more awesome? Allow us to collect non-sensitive diagnostic data and usage information. "
37
+ msgstr ""
38
+
39
+ #: admin/bsf-analytics/class-bsf-analytics.php:219
40
+ msgid "This will be applicable for all sites from the network."
41
+ msgstr ""
42
+
43
+ #. translators: %s usage doc link
44
+ #: admin/bsf-analytics/class-bsf-analytics.php:243
45
+ msgid " Know More."
46
+ msgstr ""
47
+
48
+ #: admin/bsf-analytics/class-bsf-analytics.php:251
49
+ msgid "Yes! Allow it"
50
+ msgstr ""
51
+
52
+ #: admin/bsf-analytics/class-bsf-analytics.php:260
53
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:389
54
+ msgid "No Thanks"
55
+ msgstr ""
56
+
57
+ #: admin/bsf-analytics/class-bsf-analytics.php:364
58
+ msgid "Usage Tracking"
59
+ msgstr ""
60
+
61
+ #. translators: %s Product title
62
+ #: admin/bsf-analytics/class-bsf-analytics.php:408
63
+ msgid "Allow %s products to track non-sensitive usage tracking data."
64
+ msgstr ""
65
+
66
+ #: admin/bsf-analytics/class-bsf-analytics.php:411
67
+ msgid " This will be applicable for all sites from the network."
68
+ msgstr ""
69
+
70
+ #: admin/bsf-analytics/class-bsf-analytics.php:416
71
+ msgid "Learn More."
72
+ msgstr ""
73
+
74
+ #: classes/class-cartflows-ca-admin-notices.php:84
75
+ msgid "Hello! Seems like you have used WooCommerce Cart Abandonment Recovery plugin to recover abandoned carts. &mdash; Thanks a ton!"
76
+ msgstr ""
77
+
78
+ #: classes/class-cartflows-ca-admin-notices.php:85
79
+ msgid "Could you please do us a BIG favor and give it a 5-star rating on WordPress? This would boost our motivation and help other users make a comfortable decision while choosing the CartFlows cart abandonment plugin."
80
+ msgstr ""
81
+
82
+ #: classes/class-cartflows-ca-admin-notices.php:87
83
+ msgid "Ok, you deserve it"
84
+ msgstr ""
85
+
86
+ #: classes/class-cartflows-ca-admin-notices.php:89
87
+ msgid "Nope, maybe later"
88
+ msgstr ""
89
+
90
+ #: classes/class-cartflows-ca-admin-notices.php:90
91
+ msgid "I already did"
92
+ msgstr ""
93
+
94
  #. translators: %s: html tags
95
  #: classes/class-cartflows-ca-loader.php:143
96
  msgid "The %1$sWooCommerce Cart Abandonment Recovery%2$s plugin requires %1$sWooCommerce%2$s plugin installed & activated."
113
  msgstr ""
114
 
115
  #: classes/class-cartflows-ca-settings.php:69
116
+ msgid "Start capturing abandoned carts. <br/><br/> <span class=\"description\"><strong>Note:</strong> Cart will be considered abandoned if order is not completed in cart abandoned cut-off time.</span>"
117
  msgstr ""
118
 
119
  #: classes/class-cartflows-ca-settings.php:79
120
+ msgid "Cart abandoned cut-off time"
121
  msgstr ""
122
 
123
  #: classes/class-cartflows-ca-settings.php:83
124
+ msgid "Minutes. <br/><br/> <span class=\"description\"><strong>Note:</strong> Consider cart abandoned after above entered minutes of item being added to cart and order not placed.</span><span class=\"dashicons dashicons-editor-help\" title=\"Please remove the custom code to update cron cut off time, if added.\"></span>"
125
  msgstr ""
126
 
127
  #: classes/class-cartflows-ca-settings.php:93
128
+ msgid "Disable Tracking For"
129
  msgstr ""
130
 
131
  #: classes/class-cartflows-ca-settings.php:97
132
+ msgid " It will ignore selected users from abandonment process when they logged in, and hence they can not receive mail for cart abandoned by themselves."
133
  msgstr ""
134
 
135
  #: classes/class-cartflows-ca-settings.php:107
136
+ msgid "Exclude email sending For"
137
  msgstr ""
138
 
139
  #: classes/class-cartflows-ca-settings.php:111
140
+ msgid " It will not send future recovery emails to selected order status and will mark as recovered."
141
+ msgstr ""
142
+
143
+ #: classes/class-cartflows-ca-settings.php:121
144
+ msgid "Notify recovery to admin"
145
+ msgstr ""
146
+
147
+ #: classes/class-cartflows-ca-settings.php:125
148
  msgid "This option will send an email to admin on new order recovery."
149
  msgstr ""
150
 
151
+ #: classes/class-cartflows-ca-settings.php:135
152
+ msgid "UTM parameters"
153
+ msgstr ""
154
+
155
+ #: classes/class-cartflows-ca-settings.php:139
156
+ msgid "The UTM parameters will be appended to the checkout page links which is available in the recovery emails."
157
+ msgstr ""
158
+
159
+ #: classes/class-cartflows-ca-settings.php:152
160
  msgid "Coupons Settings"
161
  msgstr ""
162
 
163
+ #: classes/class-cartflows-ca-settings.php:159
164
  msgid "Delete Coupons Automatically"
165
  msgstr ""
166
 
167
+ #: classes/class-cartflows-ca-settings.php:163
168
  msgid "Delete coupons automatically on weekly basis.<br><span class=\"description\"><br/><strong>Note:</strong> This option will set a weekly cron to delete all <strong>expired</strong> and <strong>used</strong> coupons automatically in the background.</p>"
169
  msgstr ""
170
 
171
+ #: classes/class-cartflows-ca-settings.php:173
172
  msgid "Delete Coupons Manually"
173
  msgstr ""
174
 
175
+ #: classes/class-cartflows-ca-settings.php:177
176
  msgid "<br><strong>Note:</strong> This will delete all <strong>expired</strong> and <strong>used</strong> coupons that were created by Woo Cart Abandonment Recovery.</p>"
177
  msgstr ""
178
 
179
+ #: classes/class-cartflows-ca-settings.php:189
180
  msgid "Email Settings"
181
  msgstr ""
182
 
183
+ #: classes/class-cartflows-ca-settings.php:196
184
  msgid "\"From\" Name"
185
  msgstr ""
186
 
187
+ #: classes/class-cartflows-ca-settings.php:200
188
  msgid "Name will appear in email sent."
189
  msgstr ""
190
 
191
+ #: classes/class-cartflows-ca-settings.php:205
192
  msgid "\"From\" Address"
193
  msgstr ""
194
 
195
+ #: classes/class-cartflows-ca-settings.php:209
196
  msgid "Email which send from."
197
  msgstr ""
198
 
199
+ #: classes/class-cartflows-ca-settings.php:214
200
  msgid "\"Reply To\" Address"
201
  msgstr ""
202
 
203
+ #: classes/class-cartflows-ca-settings.php:218
204
  msgid "When a user clicks reply, which email address should that reply be sent to?"
205
  msgstr ""
206
 
207
+ #: classes/class-cartflows-ca-settings.php:241
208
  msgid "Enable Webhook"
209
  msgstr ""
210
 
211
+ #: classes/class-cartflows-ca-settings.php:245
212
  msgid "Allows you to trigger webhook automatically upon cart abandonment and recovery."
213
  msgstr ""
214
 
215
+ #: classes/class-cartflows-ca-settings.php:250
216
  msgid "Webhook URL"
217
  msgstr ""
218
 
219
+ #: classes/class-cartflows-ca-settings.php:269
220
  msgid "Coupon Code Settings"
221
  msgstr ""
222
 
223
+ #: classes/class-cartflows-ca-settings.php:276
224
  msgid "Create Coupon Code"
225
  msgstr ""
226
 
227
+ #: classes/class-cartflows-ca-settings.php:280
228
  msgid "Auto-create the special coupon for the abandoned cart to send over the emails."
229
  msgstr ""
230
 
231
+ #: classes/class-cartflows-ca-settings.php:285
232
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:544
233
  msgid "Discount Type"
234
  msgstr ""
235
 
236
+ #: classes/class-cartflows-ca-settings.php:294
237
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:576
238
  msgid "Coupon Amount"
239
  msgstr ""
240
 
241
+ #: classes/class-cartflows-ca-settings.php:303
242
  msgid "Coupon Expires After"
243
  msgstr ""
244
 
245
+ #: classes/class-cartflows-ca-settings.php:307
246
  msgid "<br/><br/> <span class=\"description\"><strong>Note: </strong>. Enter zero (0) to restrict coupon from expiring.</span>"
247
  msgstr ""
248
 
249
+ #: classes/class-cartflows-ca-settings.php:338
250
  msgid "Webhook Settings"
251
  msgstr ""
252
 
253
+ #: classes/class-cartflows-ca-settings.php:347
254
  msgid "GDPR Settings"
255
  msgstr ""
256
 
257
+ #: classes/class-cartflows-ca-settings.php:354
258
  msgid "Enable GDPR Integration"
259
  msgstr ""
260
 
261
+ #: classes/class-cartflows-ca-settings.php:358
262
  msgid "Ask confirmation from the user before tracking data. <br/><br/> <span class=\"description\"><strong>Note:</strong> By checking this, it will show up confirmation text below the email id on checkout page.</span>"
263
  msgstr ""
264
 
265
+ #: classes/class-cartflows-ca-settings.php:363
266
  msgid "GDPR Message"
267
  msgstr ""
268
 
269
+ #: classes/class-cartflows-ca-settings.php:384
270
  msgid "Plugin Settings"
271
  msgstr ""
272
 
273
+ #: classes/class-cartflows-ca-settings.php:394
274
  msgid "Delete Plugin Data"
275
  msgstr ""
276
 
277
+ #: classes/class-cartflows-ca-settings.php:398
278
  msgid "Enabling this option will delete the plugin data while deleting the Plugin."
279
  msgstr ""
280
 
281
+ #: classes/class-cartflows-ca-settings.php:413
282
+ msgid "Add UTM parameter per line."
283
+ msgstr ""
284
+
285
+ #: classes/class-cartflows-ca-settings.php:539
286
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:614
287
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:710
288
  msgid "Hour(s)"
289
  msgstr ""
290
 
291
+ #: classes/class-cartflows-ca-settings.php:540
292
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:615
293
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:711
294
  msgid "Day(s)"
295
  msgstr ""
296
 
297
+ #: classes/class-cartflows-ca-settings.php:608
298
  msgid "Coupon code should be numeric and has to be greater than or equals to 1."
299
  msgstr ""
300
 
301
+ #: classes/class-cartflows-ca-settings.php:758
302
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:88
303
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:122
304
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:86
305
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:138
306
  msgid "Delete"
307
  msgstr ""
308
 
309
+ #: classes/class-cartflows-ca-settings.php:844
310
  msgid "Invalid email \"From\" address field"
311
  msgstr ""
312
 
313
+ #: classes/class-cartflows-ca-settings.php:862
314
  msgid "Invalid email \"Reply\" address field"
315
  msgstr ""
316
 
317
+ #: classes/class-cartflows-ca-tabs.php:57
318
+ #: classes/class-cartflows-ca-tabs.php:58
319
+ msgid "Cart Abandonment"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  msgstr ""
321
 
322
+ #: classes/class-cartflows-ca-tabs.php:83
323
+ msgid "Items deleted: %d"
 
324
  msgstr ""
325
 
326
+ #: classes/class-cartflows-ca-tabs.php:100
327
+ msgid "User(s) unsubscribed successfully!"
328
  msgstr ""
329
 
330
+ #: classes/class-cartflows-ca-tabs.php:163
331
+ msgid "Report"
332
  msgstr ""
333
 
334
+ #: classes/class-cartflows-ca-tabs.php:179
335
+ msgid "Follow-Up Emails"
336
  msgstr ""
337
 
338
+ #: classes/class-cartflows-ca-tabs.php:195
339
+ msgid "Settings"
 
340
  msgstr ""
341
 
342
+ #: classes/class-cartflows-ca-tabs.php:450
343
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:297
344
+ msgid "Item"
345
  msgstr ""
346
 
347
+ #: classes/class-cartflows-ca-tabs.php:451
348
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:298
349
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:236
350
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:176
351
+ msgid "Name"
352
  msgstr ""
353
 
354
+ #: classes/class-cartflows-ca-tabs.php:452
355
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:299
356
+ msgid "Quantity"
357
  msgstr ""
358
 
359
+ #: classes/class-cartflows-ca-tabs.php:453
360
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:300
361
+ msgid "Price"
362
  msgstr ""
363
 
364
+ #: classes/class-cartflows-ca-tabs.php:454
365
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:301
366
+ msgid "Line Subtotal"
367
  msgstr ""
368
 
369
+ #: classes/class-cartflows-ca-tabs.php:460
370
+ msgid "Discount"
371
  msgstr ""
372
 
373
+ #: classes/class-cartflows-ca-tabs.php:464
374
+ msgid "Other"
375
  msgstr ""
376
 
377
+ #: classes/class-cartflows-ca-tabs.php:469
378
+ msgid "Shipping"
379
  msgstr ""
380
 
381
+ #: classes/class-cartflows-ca-tabs.php:473
382
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:238
383
+ msgid "Cart Total"
384
  msgstr ""
385
 
386
+ #: classes/class-cartflows-ca-tabs.php:496
387
  msgid "Looks like abandonment tracking is disabled! Please enable it from <a href="
388
  msgstr ""
389
 
390
+ #: modules/cart-abandonment/classes/class-cartflows-ca-cron.php:63
391
+ msgid "Every Fifteen Minutes"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  msgstr ""
393
 
394
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:52
395
+ msgid "Mail has been sent successfully!"
 
396
  msgstr ""
397
 
398
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:54
399
+ msgid "Mail sending failed!"
 
400
  msgstr ""
401
 
402
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:85
403
+ msgid "there"
404
  msgstr ""
405
 
406
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:124
407
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:90
408
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:142
409
+ msgid "Unsubscribe"
410
  msgstr ""
411
 
412
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:143
413
+ msgid "Admin"
414
  msgstr ""
415
 
416
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php:290
417
+ msgid "Cart Total ( Cart Total + Shipping + Tax )"
 
418
  msgstr ""
419
 
420
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:76
421
  msgid "Edit"
422
  msgstr ""
423
 
424
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:100
425
  msgid "Clone"
426
  msgstr ""
427
 
428
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:189
429
  msgid "Template Name"
430
  msgstr ""
431
 
432
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:190
433
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:86
434
  msgid "Email Subject"
435
  msgstr ""
436
 
437
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:191
438
  msgid "Trigger After"
439
  msgstr ""
440
 
441
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates-table.php:192
442
  msgid "Activate Template"
443
  msgstr ""
444
 
445
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:132
446
  msgid "Admin Firstname"
447
  msgstr ""
448
 
449
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:133
450
  msgid "Admin Company"
451
  msgstr ""
452
 
453
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:134
454
  msgid "Abandoned Product Details Table"
455
  msgstr ""
456
 
457
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:135
458
  msgid "Abandoned Product Names"
459
  msgstr ""
460
 
461
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:136
462
  msgid "Cart Checkout URL"
463
  msgstr ""
464
 
465
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:137
466
  msgid "Coupon Code"
467
  msgstr ""
468
 
469
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:138
470
  msgid "Customer First Name"
471
  msgstr ""
472
 
473
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:139
474
  msgid "Customer Last Name"
475
  msgstr ""
476
 
477
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:140
478
  msgid "Customer Full Name"
479
  msgstr ""
480
 
481
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:141
482
  msgid "Cart Abandonment Date"
483
  msgstr ""
484
 
485
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:142
486
  msgid "Site URL"
487
  msgstr ""
488
 
489
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:143
490
  msgid "Unsubscribe Link"
491
  msgstr ""
492
 
493
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:145
494
+ msgid "Triggering..."
495
+ msgstr ""
496
+
497
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:146
498
+ msgid "Trigger Failed."
499
+ msgstr ""
500
+
501
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:147
502
+ msgid "Please verify webhook URL."
503
+ msgstr ""
504
+
505
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:148
506
+ msgid "Webhook URL is required."
507
+ msgstr ""
508
+
509
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:169
510
  msgid "Something went wrong"
511
  msgstr ""
512
 
513
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:176
514
  msgid "Activated"
515
  msgstr ""
516
 
517
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:179
518
  msgid "Deactivated"
519
  msgstr ""
520
 
521
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:239
522
  msgid "The Email Template has been successfully added."
523
  msgstr ""
524
 
525
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:249
526
  msgid "The Email Template has been cloned successfully."
527
  msgstr ""
528
 
529
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:259
530
  msgid "The Email Template has been successfully deleted."
531
  msgstr ""
532
 
533
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:268
534
  msgid "The Email Template has been successfully updated."
535
  msgstr ""
536
 
537
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:278
538
  msgid "Default Email Templates has been restored successfully."
539
  msgstr ""
540
 
541
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:445
542
  msgid "Activate Template now?"
543
  msgstr ""
544
 
545
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:465
546
  msgid "Template Name:"
547
  msgstr ""
548
 
549
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:480
550
  msgid "Email Subject:"
551
  msgstr ""
552
 
553
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:495
554
  msgid "Email Body:"
555
  msgstr ""
556
 
557
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:524
558
  msgid "Create Coupon"
559
  msgstr ""
560
 
561
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:537
562
  msgid "Allows you to send new coupon only for this template."
563
  msgstr ""
564
 
565
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:594
566
  msgid "Coupon expiry date"
567
  msgstr ""
568
 
569
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:624
570
  msgid "Enter zero (0) to restrict coupon from expiring"
571
  msgstr ""
572
 
573
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:630
574
  msgid "Free Shipping"
575
  msgstr ""
576
 
577
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:643
578
  msgid "Allows you to grant free shipping. A free shipping method must be enabled in your shipping zone and be set to require \"a valid free shipping coupon\". "
579
  msgstr ""
580
 
581
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:650
582
  msgid "Individual use only"
583
  msgstr ""
584
 
585
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:664
586
  msgid "Check this box if the coupon cannot be used in conjunction with other coupons."
587
  msgstr ""
588
 
589
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:671
590
  msgid "Auto Apply Coupon"
591
  msgstr ""
592
 
593
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:685
594
  msgid " Automatically add the coupon to the cart at the checkout."
595
  msgstr ""
596
 
597
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:691
598
  msgid "Send This Email"
599
  msgstr ""
600
 
601
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:709
602
  msgid "Minute(s)"
603
  msgstr ""
604
 
605
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:724
606
  msgid "after cart is abandoned."
607
  msgstr ""
608
 
609
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:734
610
  msgid "Send Test Email To:"
611
  msgstr ""
612
 
613
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:738
614
  msgid "Send a test email"
615
  msgstr ""
616
 
617
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:751
618
  msgid "Save Changes"
619
  msgstr ""
620
 
621
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:753
622
  msgid "Update Changes"
623
  msgstr ""
624
 
625
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:1059
626
  msgid "Create New Template"
627
  msgstr ""
628
 
629
+ #: modules/cart-abandonment/classes/class-cartflows-ca-email-templates.php:1062
630
  msgid " Restore Default Templates"
631
  msgstr ""
632
 
633
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:85
634
+ msgid "View"
635
+ msgstr ""
636
+
637
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:237
638
+ msgid "Email"
639
+ msgstr ""
640
+
641
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:239
642
+ msgid "Order Status"
643
+ msgstr ""
644
+
645
+ #: modules/cart-abandonment/classes/class-cartflows-ca-order-table.php:240
646
+ msgid "Time"
647
+ msgstr ""
648
+
649
+ #. translators: %1$s: Coupons Deleted, %2$s: Deleted coupons count'.
650
+ #: modules/cart-abandonment/classes/class-cartflows-ca-setting-functions.php:129
651
+ msgid "%1$s: %2$d"
652
+ msgstr ""
653
+
654
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:100
655
+ msgid "This coupon is for abandoned cart email templates."
656
+ msgstr ""
657
+
658
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:129
659
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:131
660
+ msgid "New Customer Order - Recovered Order ID: "
661
+ msgstr ""
662
+
663
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:215
664
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:831
665
+ msgid "This order was abandoned & subsequently recovered."
666
+ msgstr ""
667
+
668
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:250
669
+ msgid "You have successfully unsubscribed from our email list."
670
+ msgstr ""
671
+
672
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:250
673
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:167
674
+ msgid "Unsubscribed"
675
+ msgstr ""
676
+
677
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:347
678
+ msgid "This checkout page is generated by WooCommerce Cart Abandonment Recovery plugin from test mail."
679
+ msgstr ""
680
+
681
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:390
682
+ msgid "You won't receive further emails from us, thank you!"
683
+ msgstr ""
684
+
685
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:989
686
+ msgid "Do you really want to delete the used and expired coupons created by Cart Abandonment Plugin?"
687
+ msgstr ""
688
+
689
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:990
690
+ msgid "Do you really want to export orders?"
691
+ msgstr ""
692
+
693
+ #: modules/cart-abandonment/classes/class-cartflows-ca-tracking.php:994
694
+ msgid "No such order is found."
695
+ msgstr ""
696
+
697
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:31
698
  msgid "Back to Reports"
699
  msgstr ""
700
 
701
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:42
702
  msgid "Email Details:"
703
  msgstr ""
704
 
705
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:52
706
  msgid "All new activated emails will be reschedule for this abandoned order. New emails will be sent to user according to schedule time."
707
  msgstr ""
708
 
709
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:59
710
  msgid "Are your sure?"
711
  msgstr ""
712
 
713
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:63
714
  msgid "Reschedule"
715
  msgstr ""
716
 
717
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:67
718
  msgid "Close"
719
  msgstr ""
720
 
721
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:72
722
  msgid "Do you really want to reschedule emails?"
723
  msgstr ""
724
 
725
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:72
726
  msgid "Reschedule Emails"
727
  msgstr ""
728
 
729
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:78
730
  msgid " No Email Scheduled."
731
  msgstr ""
732
 
733
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:85
734
  msgid "Scheduled Template"
735
  msgstr ""
736
 
737
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:87
738
  msgid "Email Coupon"
739
  msgstr ""
740
 
741
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:88
742
  msgid "Email Sent"
743
  msgstr ""
744
 
745
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:89
746
  msgid "Scheduled At"
747
  msgstr ""
748
 
749
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:118
750
  msgid "The email has been unsubscribed and won't be sent further."
751
  msgstr ""
752
 
753
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:121
754
  msgid "Email is in the queue and will be sent at the scheduled time."
755
  msgstr ""
756
 
757
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:126
758
  msgid "The email has been sent."
759
  msgstr ""
760
 
761
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:130
762
  msgid "The email has been unscheduled due to the complete order and won't be sent further."
763
  msgstr ""
764
 
765
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:163
766
  msgid "User Address Details:"
767
  msgstr ""
768
 
769
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:175
770
  msgid "Billing Address"
771
  msgstr ""
772
 
773
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:179
774
  msgid "Email address"
775
  msgstr ""
776
 
777
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:184
778
  msgid "Phone"
779
  msgstr ""
780
 
781
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:189
782
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:209
783
  msgid "Address 1:"
784
  msgstr ""
785
 
786
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:192
787
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:212
788
  msgid "Address 2:"
789
  msgstr ""
790
 
791
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:195
792
  msgid "Country, City:"
793
  msgstr ""
794
 
795
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:198
796
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:218
797
  msgid "State:"
798
  msgstr ""
799
 
800
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:202
801
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:224
802
  msgid "Postcode:"
803
  msgstr ""
804
 
805
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:207
806
  msgid "Shipping Address"
807
  msgstr ""
808
 
809
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:215
810
  msgid "City:"
811
  msgstr ""
812
 
813
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:221
814
  msgid "Country:"
815
  msgstr ""
816
 
817
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:232
818
  msgid "Checkout Link"
819
  msgstr ""
820
 
821
+ #: modules/cart-abandonment/includes/cartflows-ca-single-report-details.php:247
822
  msgid "User Order Details:"
823
  msgstr ""
824
 
825
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:18
826
  msgid "Today"
827
  msgstr ""
828
 
829
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:22
830
  msgid "Yesterday"
831
  msgstr ""
832
 
833
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:26
834
  msgid "Last Week"
835
  msgstr ""
836
 
837
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:30
838
  msgid "Last Month"
839
  msgstr ""
840
 
841
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:39
842
  msgid "Custom Filter"
843
  msgstr ""
844
 
845
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:50
846
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:112
847
  msgid "Recoverable Orders"
848
  msgstr ""
849
 
850
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:54
851
  msgid "Total Recoverable Orders."
852
  msgstr ""
853
 
854
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:59
855
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:115
856
  msgid "Recovered Orders"
857
  msgstr ""
858
 
859
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:61
860
  msgid "Total Recovered Orders."
861
  msgstr ""
862
 
863
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:66
864
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:118
865
  msgid "Lost Orders"
866
  msgstr ""
867
 
868
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:69
869
  msgid "Total Lost Orders."
870
  msgstr ""
871
 
872
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:78
873
  msgid "Recoverable Revenue"
874
  msgstr ""
875
 
876
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:83
877
  msgid "Total Recoverable Revenue."
878
  msgstr ""
879
 
880
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:88
881
  msgid "Recovered Revenue"
882
  msgstr ""
883
 
884
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:94
885
  msgid "Total Recovered Revenue."
886
  msgstr ""
887
 
888
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:99
889
  msgid "Recovery Rate"
890
  msgstr ""
891
 
892
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:101
893
  msgid "Total Percentage Of Recovered Orders After Abandonment."
894
  msgstr ""
895
 
896
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:128
897
  msgid "Search by email"
898
  msgstr ""
899
 
900
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:129
901
  msgid "Search Orders"
902
  msgstr ""
903
 
904
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-reports.php:156
905
  msgid "No Orders Found."
906
  msgstr ""
907
 
908
+ #: modules/cart-abandonment/includes/cartflows-cart-abandonment-tabs.php:16
909
  msgid "WooCommerce Cart Abandonment Recovery "
910
  msgstr ""
lib/astra-notices/class-astra-notices.php ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Astra Notices
4
+ *
5
+ * An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
6
+ *
7
+ * @package Astra Notices
8
+ * @since 1.0.0
9
+ */
10
+
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit; // Exit if accessed directly.
13
+ }
14
+
15
+ if ( ! class_exists( 'Astra_Notices' ) ) :
16
+
17
+ /**
18
+ * Astra_Notices
19
+ *
20
+ * @since 1.0.0
21
+ */
22
+ class Astra_Notices {
23
+
24
+ /**
25
+ * Notices
26
+ *
27
+ * @access private
28
+ * @var array Notices.
29
+ * @since 1.0.0
30
+ */
31
+ private static $version = '1.1.11';
32
+
33
+ /**
34
+ * Notices
35
+ *
36
+ * @access private
37
+ * @var array Notices.
38
+ * @since 1.0.0
39
+ */
40
+ private static $notices = array();
41
+
42
+ /**
43
+ * Instance
44
+ *
45
+ * @access private
46
+ * @var object Class object.
47
+ * @since 1.0.0
48
+ */
49
+ private static $instance;
50
+
51
+ /**
52
+ * Initiator
53
+ *
54
+ * @since 1.0.0
55
+ * @return object initialized object of class.
56
+ */
57
+ public static function get_instance() {
58
+ if ( ! isset( self::$instance ) ) {
59
+ self::$instance = new self();
60
+ }
61
+ return self::$instance;
62
+ }
63
+
64
+ /**
65
+ * Constructor
66
+ *
67
+ * @since 1.0.0
68
+ */
69
+ public function __construct() {
70
+ add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
71
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
72
+ add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
73
+ add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
74
+ }
75
+
76
+ /**
77
+ * Filters and Returns a list of allowed tags and attributes for a given context.
78
+ *
79
+ * @param array $allowedposttags array of allowed tags.
80
+ * @param string $context Context type (explicit).
81
+ * @since 1.0.0
82
+ * @return array
83
+ */
84
+ public function add_data_attributes( $allowedposttags, $context ) {
85
+ $allowedposttags['a']['data-repeat-notice-after'] = true;
86
+
87
+ return $allowedposttags;
88
+ }
89
+
90
+ /**
91
+ * Add Notice.
92
+ *
93
+ * @since 1.0.0
94
+ * @param array $args Notice arguments.
95
+ * @return void
96
+ */
97
+ public static function add_notice( $args = array() ) {
98
+ self::$notices[] = $args;
99
+ }
100
+
101
+ /**
102
+ * Dismiss Notice.
103
+ *
104
+ * @since 1.0.0
105
+ * @return void
106
+ */
107
+ public function dismiss_notice() {
108
+ $notice_id = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( $_POST['notice_id'] ) : '';
109
+ $repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( $_POST['repeat_notice_after'] ) : '';
110
+ $nonce = ( isset( $_POST['nonce'] ) ) ? sanitize_key( $_POST['nonce'] ) : '';
111
+ $notice = $this->get_notice_by_id( $notice_id );
112
+ $capability = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
113
+
114
+ if ( ! apply_filters( 'astra_notices_user_cap_check', current_user_can( $capability ) ) ) {
115
+ return;
116
+ }
117
+
118
+ if ( false === wp_verify_nonce( $nonce, 'astra-notices' ) ) {
119
+ wp_send_json_error( esc_html_e( 'WordPress Nonce not validated.' ) );
120
+ }
121
+
122
+ // Valid inputs?
123
+ if ( ! empty( $notice_id ) ) {
124
+
125
+ if ( ! empty( $repeat_notice_after ) ) {
126
+ set_transient( $notice_id, true, $repeat_notice_after );
127
+ } else {
128
+ update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
129
+ }
130
+
131
+ wp_send_json_success();
132
+ }
133
+
134
+ wp_send_json_error();
135
+ }
136
+
137
+ /**
138
+ * Enqueue Scripts.
139
+ *
140
+ * @since 1.0.0
141
+ * @return void
142
+ */
143
+ public function enqueue_scripts() {
144
+ wp_register_style( 'astra-notices', self::get_url() . 'notices.css', array(), self::$version );
145
+ wp_register_script( 'astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
146
+ wp_localize_script(
147
+ 'astra-notices',
148
+ 'astraNotices',
149
+ array(
150
+ '_notice_nonce' => wp_create_nonce( 'astra-notices' ),
151
+ )
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Sort the notices based on the given priority of the notice.
157
+ * This function is called from usort()
158
+ *
159
+ * @since 1.5.2
160
+ * @param array $notice_1 First notice.
161
+ * @param array $notice_2 Second Notice.
162
+ * @return array
163
+ */
164
+ public function sort_notices( $notice_1, $notice_2 ) {
165
+ if ( ! isset( $notice_1['priority'] ) ) {
166
+ $notice_1['priority'] = 10;
167
+ }
168
+ if ( ! isset( $notice_2['priority'] ) ) {
169
+ $notice_2['priority'] = 10;
170
+ }
171
+
172
+ return $notice_1['priority'] - $notice_2['priority'];
173
+ }
174
+
175
+ /**
176
+ * Get all registered notices.
177
+ * Since v1.1.8 it is recommended to register the notices on
178
+ *
179
+ * @return array|null
180
+ */
181
+ private function get_notices() {
182
+ usort( self::$notices, array( $this, 'sort_notices' ) );
183
+
184
+ return self::$notices;
185
+ }
186
+
187
+ /**
188
+ * Get notice by notice_id
189
+ *
190
+ * @param string $notice_id Notice id.
191
+ *
192
+ * @return array notice based on the notice id.
193
+ */
194
+ private function get_notice_by_id( $notice_id ) {
195
+ if ( empty( $notice_id ) ) {
196
+ return array();
197
+ }
198
+
199
+ $notices = $this->get_notices();
200
+ $notice = wp_list_filter(
201
+ $notices,
202
+ array(
203
+ 'id' => $notice_id,
204
+ )
205
+ );
206
+
207
+ return ! empty( $notice ) ? $notice[0] : array();
208
+ }
209
+
210
+ /**
211
+ * Display the notices in the WordPress admin.
212
+ *
213
+ * @since 1.0.0
214
+ * @return void
215
+ */
216
+ public function show_notices() {
217
+ $defaults = array(
218
+ 'id' => '', // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
219
+ 'type' => 'info', // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
220
+ 'message' => '', // Optional, Message.
221
+ 'show_if' => true, // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
222
+ 'repeat-notice-after' => '', // Optional, Dismiss-able notice time. It'll auto show after given time.
223
+ 'display-notice-after' => false, // Optional, Dismiss-able notice time. It'll auto show after given time.
224
+ 'class' => '', // Optional, Additional notice wrapper class.
225
+ 'priority' => 10, // Priority of the notice.
226
+ 'display-with-other-notices' => true, // Should the notice be displayed if other notices are being displayed from Astra_Notices.
227
+ 'is_dismissible' => true,
228
+ 'capability' => 'manage_options', // User capability - This capability is required for the current user to see this notice.
229
+ );
230
+
231
+ // Count for the notices that are rendered.
232
+ $notices_displayed = 0;
233
+ $notices = $this->get_notices();
234
+
235
+ foreach ( $notices as $key => $notice ) {
236
+ $notice = wp_parse_args( $notice, $defaults );
237
+
238
+ // Show notices only for users with `manage_options` cap.
239
+ if ( ! current_user_can( $notice['capability'] ) ) {
240
+ continue;
241
+ }
242
+
243
+ $notice['id'] = self::get_notice_id( $notice, $key );
244
+ $notice['classes'] = self::get_wrap_classes( $notice );
245
+
246
+ // Notices visible after transient expire.
247
+ if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
248
+
249
+ // don't display the notice if it is not supposed to be displayed with other notices.
250
+ if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
251
+ continue;
252
+ }
253
+
254
+ if ( self::is_expired( $notice ) ) {
255
+
256
+ self::markup( $notice );
257
+ ++$notices_displayed;
258
+ }
259
+ }
260
+ }
261
+
262
+ }
263
+
264
+ /**
265
+ * Render a notice.
266
+ *
267
+ * @since 1.0.0
268
+ * @param array $notice Notice markup.
269
+ * @return void
270
+ */
271
+ public static function markup( $notice = array() ) {
272
+ wp_enqueue_script( 'astra-notices' );
273
+ wp_enqueue_style( 'astra-notices' );
274
+
275
+ do_action( 'astra_notice_before_markup' );
276
+
277
+ do_action( "astra_notice_before_markup_{$notice['id']}" );
278
+
279
+ ?>
280
+ <div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo 'astra-notice-wrapper ' . esc_attr( $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
281
+ <div class="astra-notice-container">
282
+ <?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
283
+ <?php echo wp_kses_post( $notice['message'] ); ?>
284
+ </div>
285
+ </div>
286
+ <?php
287
+
288
+ do_action( "astra_notice_after_markup_{$notice['id']}" );
289
+
290
+ do_action( 'astra_notice_after_markup' );
291
+ }
292
+
293
+ /**
294
+ * Get wrapper classes for a notice.
295
+ *
296
+ * @since 1.0.0
297
+ *
298
+ * @param array $notice Notice arguments.
299
+ * @return array Notice wrapper classes.
300
+ */
301
+ private static function get_wrap_classes( $notice ) {
302
+ $classes = array( 'astra-notice', 'notice' );
303
+
304
+ if ( $notice['is_dismissible'] ) {
305
+ $classes[] = 'is-dismissible';
306
+ }
307
+
308
+ $classes[] = $notice['class'];
309
+ if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
310
+ $classes[] = 'notice-' . $notice['type'];
311
+ }
312
+
313
+ return esc_attr( implode( ' ', $classes ) );
314
+ }
315
+
316
+ /**
317
+ * Get HTML ID for a given notice.
318
+ *
319
+ * @since 1.0.0
320
+ *
321
+ * @param array $notice Notice arguments.
322
+ * @param int $key Notice array index.
323
+ * @return string HTML if for the notice.
324
+ */
325
+ private static function get_notice_id( $notice, $key ) {
326
+ if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
327
+ return $notice['id'];
328
+ }
329
+
330
+ return 'astra-notices-id-' . $key;
331
+ }
332
+
333
+ /**
334
+ * Check if the notice is expires.
335
+ *
336
+ * @since 1.0.0
337
+ *
338
+ * @param array $notice Notice arguments.
339
+ * @return boolean
340
+ */
341
+ private static function is_expired( $notice ) {
342
+ $transient_status = get_transient( $notice['id'] );
343
+
344
+ if ( false === $transient_status ) {
345
+
346
+ if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
347
+
348
+ if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
349
+ 'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
350
+ set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
351
+ update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
352
+
353
+ return false;
354
+ }
355
+ }
356
+
357
+ // Check the user meta status if current notice is dismissed or delay completed.
358
+ $meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
359
+
360
+ if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
361
+ return true;
362
+ }
363
+ }
364
+
365
+ return false;
366
+ }
367
+
368
+ /**
369
+ * Get base URL for the astra-notices.
370
+ *
371
+ * @return mixed URL.
372
+ */
373
+ public static function get_url() {
374
+ $path = wp_normalize_path( dirname( __FILE__ ) );
375
+ $theme_dir = wp_normalize_path( get_template_directory() );
376
+
377
+ if ( strpos( $path, $theme_dir ) !== false ) {
378
+ return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
379
+ } else {
380
+ return plugin_dir_url( __FILE__ );
381
+ }
382
+ }
383
+
384
+ }
385
+
386
+ /**
387
+ * Kicking this off by calling 'get_instance()' method
388
+ */
389
+ Astra_Notices::get_instance();
390
+
391
+ endif;
lib/astra-notices/notices.css ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .astra-review-notice-container {
2
+ display: flex;
3
+ align-items: center;
4
+ padding-top: 10px;
5
+ }
6
+
7
+ .astra-review-notice-container .dashicons {
8
+ font-size: 1.4em;
9
+ padding-left: 10px;
10
+ }
11
+
12
+ .astra-review-notice-container a {
13
+ padding-left: 5px;
14
+ text-decoration: none;
15
+ }
16
+
17
+ .astra-review-notice-container .dashicons:first-child {
18
+ padding-left: 0;
19
+ }
20
+
21
+ .astra-notice-container .notice-image img {
22
+ max-width: 90px;
23
+ }
24
+
25
+ .astra-notice-container .notice-content .notice-heading {
26
+ padding-bottom: 5px;
27
+ }
28
+
29
+ .astra-notice-container .notice-content {
30
+ margin-left: 15px;
31
+ }
32
+
33
+ .astra-notice-container {
34
+ padding-top: 10px;
35
+ padding-bottom: 10px;
36
+ display: flex;
37
+ justify-content: left;
38
+ align-items: center;
39
+ }
lib/astra-notices/notices.js ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Customizer controls toggles
3
+ *
4
+ * @package Astra
5
+ */
6
+
7
+ ( function( $ ) {
8
+
9
+ /**
10
+ * Helper class for the main Customizer interface.
11
+ *
12
+ * @since 1.0.0
13
+ * @class ASTCustomizer
14
+ */
15
+ AstraNotices = {
16
+
17
+ /**
18
+ * Initializes our custom logic for the Customizer.
19
+ *
20
+ * @since 1.0.0
21
+ * @method init
22
+ */
23
+ init: function()
24
+ {
25
+ this._bind();
26
+ },
27
+
28
+ /**
29
+ * Binds events for the Astra Portfolio.
30
+ *
31
+ * @since 1.0.0
32
+ * @access private
33
+ * @method _bind
34
+ */
35
+ _bind: function()
36
+ {
37
+ $( document ).on('click', '.astra-notice-close', AstraNotices._dismissNoticeNew );
38
+ $( document ).on('click', '.astra-notice .notice-dismiss', AstraNotices._dismissNotice );
39
+ },
40
+
41
+ _dismissNotice: function( event ) {
42
+ event.preventDefault();
43
+
44
+ var repeat_notice_after = $( this ).parents('.astra-notice').data( 'repeat-notice-after' ) || '';
45
+ var notice_id = $( this ).parents('.astra-notice').attr( 'id' ) || '';
46
+
47
+ AstraNotices._ajax( notice_id, repeat_notice_after );
48
+ },
49
+
50
+ _dismissNoticeNew: function( event ) {
51
+ event.preventDefault();
52
+
53
+ var repeat_notice_after = $( this ).attr( 'data-repeat-notice-after' ) || '';
54
+ var notice_id = $( this ).parents('.astra-notice').attr( 'id' ) || '';
55
+
56
+ var $el = $( this ).parents('.astra-notice');
57
+ $el.fadeTo( 100, 0, function() {
58
+ $el.slideUp( 100, function() {
59
+ $el.remove();
60
+ });
61
+ });
62
+
63
+ AstraNotices._ajax( notice_id, repeat_notice_after );
64
+
65
+ var link = $( this ).attr( 'href' ) || '';
66
+ var target = $( this ).attr( 'target' ) || '';
67
+ if( '' !== link && '_blank' === target ) {
68
+ window.open(link , '_blank');
69
+ }
70
+ },
71
+
72
+ _ajax: function( notice_id, repeat_notice_after ) {
73
+
74
+ if( '' === notice_id ) {
75
+ return;
76
+ }
77
+
78
+ $.ajax({
79
+ url: ajaxurl,
80
+ type: 'POST',
81
+ data: {
82
+ action : 'astra-notice-dismiss',
83
+ nonce : astraNotices._notice_nonce,
84
+ notice_id : notice_id,
85
+ repeat_notice_after : parseInt( repeat_notice_after ),
86
+ },
87
+ });
88
+
89
+ }
90
+ };
91
+
92
+ $( function() {
93
+ AstraNotices.init();
94
+ } );
95
+ } )( jQuery );
modules/cart-abandonment/assets/js/cart-abandonment-tracking.js CHANGED
@@ -1,16 +1,16 @@
1
- ( function ( $ ) {
2
- var timer;
3
- var wcf_cart_abandonment = {
4
- init: function () {
5
  if (
6
- CartFlowsProCAVars._show_gdpr_message &&
7
  ! $( '#wcf_cf_gdpr_message_block' ).length
8
  ) {
9
  $( '#billing_email' ).after(
10
  "<span id='wcf_cf_gdpr_message_block'> <span style='font-size: xx-small'> " +
11
- CartFlowsProCAVars._gdpr_message +
12
  " <a style='cursor: pointer' id='wcf_ca_gdpr_no_thanks'> " +
13
- CartFlowsProCAVars._gdpr_nothanks_msg +
14
  ' </a></span></span>'
15
  );
16
  }
@@ -21,59 +21,55 @@
21
  this._getCheckoutData
22
  );
23
 
24
- $( '#wcf_ca_gdpr_no_thanks' ).on( 'click', function () {
25
  wcf_cart_abandonment._set_cookie();
26
  } );
27
 
28
- $( document.body ).on( 'updated_checkout', function () {
29
  wcf_cart_abandonment._getCheckoutData();
30
  } );
31
 
32
- $( function ( e ) {
33
- setTimeout( function () {
34
  wcf_cart_abandonment._getCheckoutData();
35
  }, 800 );
36
  } );
37
  },
38
 
39
- _set_cookie: function () {
40
- var data = {
41
  wcf_ca_skip_track_data: true,
42
  action: 'cartflows_skip_cart_tracking_gdpr',
43
- security: CartFlowsProCAVars._gdpr_nonce,
44
  };
45
 
46
- jQuery.post(
47
- CartFlowsProCAVars.ajaxurl,
48
- data,
49
- function ( response ) {
50
- if ( response.success ) {
51
- $( '#wcf_cf_gdpr_message_block' )
52
- .empty()
53
- .append(
54
- "<span style='font-size: xx-small'>" +
55
- CartFlowsProCAVars._gdpr_after_no_thanks_msg +
56
- '</span>'
57
- )
58
- .delay( 5000 )
59
- .fadeOut();
60
- }
61
  }
62
- );
63
  },
64
 
65
- _validate_email: function ( value ) {
66
- var valid = true;
67
- if ( value.indexOf( '@' ) == -1 ) {
68
  valid = false;
69
  } else {
70
- var parts = value.split( '@' );
71
- var domain = parts[ 1 ];
72
- if ( domain.indexOf( '.' ) == -1 ) {
73
  valid = false;
74
  } else {
75
- var domainParts = domain.split( '.' );
76
- var ext = domainParts[ 1 ];
77
  if ( ext.length > 14 || ext.length < 2 ) {
78
  valid = false;
79
  }
@@ -82,16 +78,16 @@
82
  return valid;
83
  },
84
 
85
- _getCheckoutData: function () {
86
- var wcf_phone = jQuery( '#billing_phone' ).val();
87
- var wcf_email = jQuery( '#billing_email' ).val();
88
 
89
  if ( typeof wcf_email === 'undefined' ) {
90
  return;
91
  }
92
 
93
- var atposition = wcf_email.indexOf( '@' );
94
- var dotposition = wcf_email.lastIndexOf( '.' );
 
95
 
96
  if ( typeof wcf_phone === 'undefined' || wcf_phone === null ) {
97
  //If phone number field does not exist on the Checkout form
@@ -110,78 +106,84 @@
110
  ) {
111
  //Checking if the email field is valid or phone number is longer than 1 digit
112
  //If Email or Phone valid
113
- var wcf_name = jQuery( '#billing_first_name' ).val();
114
- var wcf_surname = jQuery( '#billing_last_name' ).val();
115
- var wcf_phone = jQuery( '#billing_phone' ).val();
116
- var wcf_country = jQuery( '#billing_country' ).val();
117
- var wcf_city = jQuery( '#billing_city' ).val();
118
 
119
  //Other fields used for "Remember user input" function
120
- var wcf_billing_company = jQuery( '#billing_company' ).val();
121
- var wcf_billing_address_1 = jQuery(
122
  '#billing_address_1'
123
  ).val();
124
- var wcf_billing_address_2 = jQuery(
125
  '#billing_address_2'
126
  ).val();
127
- var wcf_billing_state = jQuery( '#billing_state' ).val();
128
- var wcf_billing_postcode = jQuery( '#billing_postcode' ).val();
129
- var wcf_shipping_first_name = jQuery(
 
 
130
  '#shipping_first_name'
131
  ).val();
132
- var wcf_shipping_last_name = jQuery(
133
  '#shipping_last_name'
134
  ).val();
135
- var wcf_shipping_company = jQuery( '#shipping_company' ).val();
136
- var wcf_shipping_country = jQuery( '#shipping_country' ).val();
137
- var wcf_shipping_address_1 = jQuery(
 
 
 
 
138
  '#shipping_address_1'
139
  ).val();
140
- var wcf_shipping_address_2 = jQuery(
141
  '#shipping_address_2'
142
  ).val();
143
- var wcf_shipping_city = jQuery( '#shipping_city' ).val();
144
- var wcf_shipping_state = jQuery( '#shipping_state' ).val();
145
- var wcf_shipping_postcode = jQuery(
146
  '#shipping_postcode'
147
  ).val();
148
- var wcf_order_comments = jQuery( '#order_comments' ).val();
149
 
150
- var data = {
151
  action: 'cartflows_save_cart_abandonment_data',
152
- wcf_email: wcf_email,
153
- wcf_name: wcf_name,
154
- wcf_surname: wcf_surname,
155
- wcf_phone: wcf_phone,
156
- wcf_country: wcf_country,
157
- wcf_city: wcf_city,
158
- wcf_billing_company: wcf_billing_company,
159
- wcf_billing_address_1: wcf_billing_address_1,
160
- wcf_billing_address_2: wcf_billing_address_2,
161
- wcf_billing_state: wcf_billing_state,
162
- wcf_billing_postcode: wcf_billing_postcode,
163
- wcf_shipping_first_name: wcf_shipping_first_name,
164
- wcf_shipping_last_name: wcf_shipping_last_name,
165
- wcf_shipping_company: wcf_shipping_company,
166
- wcf_shipping_country: wcf_shipping_country,
167
- wcf_shipping_address_1: wcf_shipping_address_1,
168
- wcf_shipping_address_2: wcf_shipping_address_2,
169
- wcf_shipping_city: wcf_shipping_city,
170
- wcf_shipping_state: wcf_shipping_state,
171
- wcf_shipping_postcode: wcf_shipping_postcode,
172
- wcf_order_comments: wcf_order_comments,
173
- security: CartFlowsProCAVars._nonce,
174
- wcf_post_id: CartFlowsProCAVars._post_id,
175
  };
176
 
177
- timer = setTimeout( function () {
178
  if (
179
  wcf_cart_abandonment._validate_email( data.wcf_email )
180
  ) {
181
  jQuery.post(
182
- CartFlowsProCAVars.ajaxurl,
183
  data, //Ajaxurl coming from localized script and contains the link to wp-admin/admin-ajax.php file that handles AJAX requests on Wordpress
184
- function ( response ) {
185
  // success response
186
  }
187
  );
@@ -194,4 +196,4 @@
194
  };
195
 
196
  wcf_cart_abandonment.init();
197
- } )( jQuery );
1
+ ( function( $ ) {
2
+ let timer;
3
+ const wcf_cart_abandonment = {
4
+ init() {
5
  if (
6
+ wcf_ca_vars._show_gdpr_message &&
7
  ! $( '#wcf_cf_gdpr_message_block' ).length
8
  ) {
9
  $( '#billing_email' ).after(
10
  "<span id='wcf_cf_gdpr_message_block'> <span style='font-size: xx-small'> " +
11
+ wcf_ca_vars._gdpr_message +
12
  " <a style='cursor: pointer' id='wcf_ca_gdpr_no_thanks'> " +
13
+ wcf_ca_vars._gdpr_nothanks_msg +
14
  ' </a></span></span>'
15
  );
16
  }
21
  this._getCheckoutData
22
  );
23
 
24
+ $( '#wcf_ca_gdpr_no_thanks' ).on( 'click', function() {
25
  wcf_cart_abandonment._set_cookie();
26
  } );
27
 
28
+ $( document.body ).on( 'updated_checkout', function() {
29
  wcf_cart_abandonment._getCheckoutData();
30
  } );
31
 
32
+ $( function() {
33
+ setTimeout( function() {
34
  wcf_cart_abandonment._getCheckoutData();
35
  }, 800 );
36
  } );
37
  },
38
 
39
+ _set_cookie() {
40
+ const data = {
41
  wcf_ca_skip_track_data: true,
42
  action: 'cartflows_skip_cart_tracking_gdpr',
43
+ security: wcf_ca_vars._gdpr_nonce,
44
  };
45
 
46
+ jQuery.post( wcf_ca_vars.ajaxurl, data, function( response ) {
47
+ if ( response.success ) {
48
+ $( '#wcf_cf_gdpr_message_block' )
49
+ .empty()
50
+ .append(
51
+ "<span style='font-size: xx-small'>" +
52
+ wcf_ca_vars._gdpr_after_no_thanks_msg +
53
+ '</span>'
54
+ )
55
+ .delay( 5000 )
56
+ .fadeOut();
 
 
 
 
57
  }
58
+ } );
59
  },
60
 
61
+ _validate_email( value ) {
62
+ let valid = true;
63
+ if ( value.indexOf( '@' ) === -1 ) {
64
  valid = false;
65
  } else {
66
+ const parts = value.split( '@' );
67
+ const domain = parts[ 1 ];
68
+ if ( domain.indexOf( '.' ) === -1 ) {
69
  valid = false;
70
  } else {
71
+ const domainParts = domain.split( '.' );
72
+ const ext = domainParts[ 1 ];
73
  if ( ext.length > 14 || ext.length < 2 ) {
74
  valid = false;
75
  }
78
  return valid;
79
  },
80
 
81
+ _getCheckoutData() {
82
+ const wcf_email = jQuery( '#billing_email' ).val();
 
83
 
84
  if ( typeof wcf_email === 'undefined' ) {
85
  return;
86
  }
87
 
88
+ let wcf_phone = jQuery( '#billing_phone' ).val();
89
+ const atposition = wcf_email.indexOf( '@' );
90
+ const dotposition = wcf_email.lastIndexOf( '.' );
91
 
92
  if ( typeof wcf_phone === 'undefined' || wcf_phone === null ) {
93
  //If phone number field does not exist on the Checkout form
106
  ) {
107
  //Checking if the email field is valid or phone number is longer than 1 digit
108
  //If Email or Phone valid
109
+ const wcf_name = jQuery( '#billing_first_name' ).val();
110
+ const wcf_surname = jQuery( '#billing_last_name' ).val();
111
+ wcf_phone = jQuery( '#billing_phone' ).val();
112
+ const wcf_country = jQuery( '#billing_country' ).val();
113
+ const wcf_city = jQuery( '#billing_city' ).val();
114
 
115
  //Other fields used for "Remember user input" function
116
+ const wcf_billing_company = jQuery( '#billing_company' ).val();
117
+ const wcf_billing_address_1 = jQuery(
118
  '#billing_address_1'
119
  ).val();
120
+ const wcf_billing_address_2 = jQuery(
121
  '#billing_address_2'
122
  ).val();
123
+ const wcf_billing_state = jQuery( '#billing_state' ).val();
124
+ const wcf_billing_postcode = jQuery(
125
+ '#billing_postcode'
126
+ ).val();
127
+ const wcf_shipping_first_name = jQuery(
128
  '#shipping_first_name'
129
  ).val();
130
+ const wcf_shipping_last_name = jQuery(
131
  '#shipping_last_name'
132
  ).val();
133
+ const wcf_shipping_company = jQuery(
134
+ '#shipping_company'
135
+ ).val();
136
+ const wcf_shipping_country = jQuery(
137
+ '#shipping_country'
138
+ ).val();
139
+ const wcf_shipping_address_1 = jQuery(
140
  '#shipping_address_1'
141
  ).val();
142
+ const wcf_shipping_address_2 = jQuery(
143
  '#shipping_address_2'
144
  ).val();
145
+ const wcf_shipping_city = jQuery( '#shipping_city' ).val();
146
+ const wcf_shipping_state = jQuery( '#shipping_state' ).val();
147
+ const wcf_shipping_postcode = jQuery(
148
  '#shipping_postcode'
149
  ).val();
150
+ const wcf_order_comments = jQuery( '#order_comments' ).val();
151
 
152
+ const data = {
153
  action: 'cartflows_save_cart_abandonment_data',
154
+ wcf_email,
155
+ wcf_name,
156
+ wcf_surname,
157
+ wcf_phone,
158
+ wcf_country,
159
+ wcf_city,
160
+ wcf_billing_company,
161
+ wcf_billing_address_1,
162
+ wcf_billing_address_2,
163
+ wcf_billing_state,
164
+ wcf_billing_postcode,
165
+ wcf_shipping_first_name,
166
+ wcf_shipping_last_name,
167
+ wcf_shipping_company,
168
+ wcf_shipping_country,
169
+ wcf_shipping_address_1,
170
+ wcf_shipping_address_2,
171
+ wcf_shipping_city,
172
+ wcf_shipping_state,
173
+ wcf_shipping_postcode,
174
+ wcf_order_comments,
175
+ security: wcf_ca_vars._nonce,
176
+ wcf_post_id: wcf_ca_vars._post_id,
177
  };
178
 
179
+ timer = setTimeout( function() {
180
  if (
181
  wcf_cart_abandonment._validate_email( data.wcf_email )
182
  ) {
183
  jQuery.post(
184
+ wcf_ca_vars.ajaxurl,
185
  data, //Ajaxurl coming from localized script and contains the link to wp-admin/admin-ajax.php file that handles AJAX requests on Wordpress
186
+ function() {
187
  // success response
188
  }
189
  );
196
  };
197
 
198
  wcf_cart_abandonment.init();
199
+ }( jQuery ) );
modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php DELETED
@@ -1,2291 +0,0 @@
1
- <?php
2
- /**
3
- * Cart Abandonment
4
- *
5
- * @package Woocommerce-Cart-Abandonment-Recovery
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly.
10
- }
11
-
12
- /**
13
- * Cart abandonment tracking class.
14
- */
15
- class Cartflows_Ca_Cart_Abandonment {
16
-
17
-
18
-
19
- /**
20
- * Member Variable
21
- *
22
- * @var object instance
23
- */
24
- private static $instance;
25
-
26
- /**
27
- * Initiator
28
- */
29
- public static function get_instance() {
30
- if ( ! isset( self::$instance ) ) {
31
- self::$instance = new self();
32
- }
33
- return self::$instance;
34
- }
35
-
36
- /**
37
- * Constructor function that initializes required actions and hooks.
38
- */
39
- public function __construct() {
40
-
41
- $this->define_cart_abandonment_constants();
42
-
43
- // Adding menu to view cart abandonment report.
44
- add_action( 'admin_menu', array( $this, 'abandoned_cart_tracking_menu' ), 999 );
45
-
46
- // Adding the styles and scripts for the cart abandonment.
47
- add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_cart_abandonment_script' ), 20 );
48
-
49
- if ( wcf_ca()->utils->is_cart_abandonment_tracking_enabled() && ! isset( $_COOKIE['wcf_ca_skip_track_data'] ) ) {
50
-
51
- // Add script to track the cart abandonment.
52
- add_action( 'woocommerce_after_checkout_form', array( $this, 'cart_abandonment_tracking_script' ) );
53
-
54
- // Store user details from the current checkout page.
55
- add_action( 'wp_ajax_cartflows_save_cart_abandonment_data', array( $this, 'save_cart_abandonment_data' ) );
56
- add_action( 'wp_ajax_nopriv_cartflows_save_cart_abandonment_data', array( $this, 'save_cart_abandonment_data' ) );
57
-
58
- // GDPR actions.
59
- add_action( 'wp_ajax_cartflows_skip_cart_tracking_gdpr', array( $this, 'skip_cart_tracking_by_gdpr' ) );
60
- add_action( 'wp_ajax_nopriv_cartflows_skip_cart_tracking_gdpr', array( $this, 'skip_cart_tracking_by_gdpr' ) );
61
-
62
- // Delete the stored cart abandonment data once order gets created.
63
- add_action( 'woocommerce_new_order', array( $this, 'delete_cart_abandonment_data' ) );
64
- add_action( 'woocommerce_thankyou', array( $this, 'delete_cart_abandonment_data' ) );
65
- add_action( 'woocommerce_order_status_changed', array( $this, 'wcf_ca_update_order_status' ), 999, 3 );
66
-
67
- // Adding filter to restore the data if recreating abandonment order.
68
- add_filter( 'wp', array( $this, 'restore_cart_abandonment_data' ), 10 );
69
- add_filter( 'wp', array( $this, 'unsubscribe_cart_abandonment_emails' ), 10 );
70
-
71
- add_action( 'wp_ajax_wcf_ca_preview_email_send', array( $this, 'send_preview_email' ) );
72
-
73
- // Delete coupons.
74
- add_action( 'wp_ajax_wcf_ca_delete_garbage_coupons', array( $this, 'delete_used_and_expired_coupons' ) );
75
-
76
- $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
77
- $action = filter_input( INPUT_GET, 'action', FILTER_SANITIZE_STRING );
78
- if ( WCF_CA_PAGE_NAME === $page ) {
79
- // Adding filter to add new button to add custom fields.
80
- add_filter( 'mce_buttons', array( $this, 'wcf_filter_mce_button' ) );
81
- add_filter( 'mce_external_plugins', array( $this, 'wcf_filter_mce_plugin' ), 9 );
82
- }
83
-
84
- add_filter( 'cron_schedules', array( $this, 'cartflows_ca_update_order_status_action' ) ); //phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
85
-
86
- // Schedule an action if it's not already scheduled.
87
- if ( ! wp_next_scheduled( 'cartflows_ca_update_order_status_action' ) ) {
88
- wp_schedule_event( time(), 'every_fifteen_minutes', 'cartflows_ca_update_order_status_action' );
89
- }
90
-
91
- // Adding notice to checkout page to inform about test email checkout page.
92
- add_action( 'woocommerce_before_checkout_form', array( $this, 'test_email_checkout_page' ), 9 );
93
-
94
- add_action( 'cartflows_ca_update_order_status_action', array( $this, 'update_order_status' ) );
95
-
96
- }
97
-
98
- }
99
-
100
- /**
101
- * This function will send the email to the store admin when any abandoned cart email recovered.
102
- *
103
- * @param int | string $order_id Order id.
104
- * @param string $wcar_old_status Old status of the order.
105
- * @param string $wcar_new_status New status of the order.
106
- */
107
- public function wcar_send_successful_recovery_email_to_admin( $order_id, $wcar_old_status, $wcar_new_status ) {
108
- global $woocommerce;
109
-
110
- if ( in_array( $wcar_old_status, array( 'pending', 'failed', 'on-hold' ), true ) &&
111
- in_array( $wcar_new_status, array( 'processing', 'completed' ), true )
112
- ) {
113
- $user_id = get_current_user_id();
114
- $order = wc_get_order( $order_id );
115
- if ( version_compare( $woocommerce->version, '3.0.0', '>=' ) ) {
116
- $user_id = $order->get_user_id();
117
- } else {
118
- $user_id = $order->user_id;
119
- }
120
-
121
- $is_recoverd = $this->wcar_check_order_is_recovered( $order_id );
122
-
123
- if ( $is_recoverd ) {
124
- $order = wc_get_order( $order_id );
125
- $email_heading = __( 'New Customer Order - Recovered Order ID: ' . $order_id . '', 'woo-cart-abandonment-recovery' ); //phpcs:ignore
126
- $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
127
- $email_subject = __( 'New Customer Order - Recovered Order ID: ' . $order_id . '', 'woo-cart-abandonment-recovery' ); //phpcs:ignore
128
- $user_email = get_option( 'admin_email' );
129
- $headers[] = 'From: Admin <' . $user_email . '>';
130
- $headers[] = 'Content-Type: text/html';
131
-
132
- ob_start();
133
- wc_get_template(
134
- 'emails/admin-new-order.php',
135
- array(
136
- 'order' => $order,
137
- 'email_heading' => $email_heading,
138
- 'sent_to_admin' => false,
139
- 'plain_text' => false,
140
- 'email' => true,
141
- 'additional_content' => '',
142
- )
143
- );
144
-
145
- $email_body = ob_get_clean();
146
- wc_mail( $user_email, $email_subject, $email_body, $headers );
147
- }
148
- }
149
- }
150
-
151
- /**
152
- * This function will check if cart is recoverd from woocommerce and WCAR.
153
- *
154
- * @param int $order_id order id.
155
- */
156
- public function wcar_check_order_is_recovered( $order_id ) {
157
-
158
- global $wpdb;
159
- $order = wc_get_order( $order_id );
160
- $email = $order->get_billing_email();
161
- $cart_abandonment_table_name = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
162
- $wcar_status = $wpdb->get_var($wpdb->prepare( "SELECT `order_status` FROM {$cart_abandonment_table_name} WHERE `email` = %s", $email )); // phpcs:ignore
163
- $woo_status = $order->get_status();
164
-
165
- if ( 'completed' === $wcar_status && in_array( $woo_status, array( 'completed', 'processing' ), true ) ) {
166
- return true;
167
- }
168
- return false;
169
- }
170
-
171
- /**
172
- * Update the Order status.
173
- *
174
- * @param integer $order_id order id.
175
- * @param string $old_order_status old order status.
176
- * @param string $new_order_status new order status.
177
- */
178
- public function wcf_ca_update_order_status( $order_id, $old_order_status, $new_order_status ) {
179
-
180
- $acceptable_order_statuses = $this->get_acceptable_order_statuses();
181
-
182
- $exclude_on_hold_order = apply_filters_deprecated( 'woo_ca_exclude_on_hold_order_from_tracking', array( false ), '1.2.8', 'New Option is introduced instead of this filter' );
183
-
184
- if ( $exclude_on_hold_order & ! ( in_array( 'on-hold', $acceptable_order_statuses, true ) ) ) {
185
- array_push( $acceptable_order_statuses, 'on-hold' );
186
- }
187
-
188
- if ( ( WCF_CART_FAILED_ORDER === $new_order_status ) ) {
189
- return;
190
- }
191
-
192
- if ( $order_id && in_array( $new_order_status, $acceptable_order_statuses, true ) ) {
193
-
194
- $order = wc_get_order( $order_id );
195
-
196
- $order_email = $order->get_billing_email();
197
- $captured_data = ( WCF_CART_FAILED_ORDER === $new_order_status ) ? $this->get_tracked_data_without_status( $order_email ) : $this->get_captured_data_by_email( $order_email );
198
-
199
- if ( $captured_data && is_object( $captured_data ) ) {
200
- $capture_status = $captured_data->order_status;
201
- global $wpdb;
202
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
203
-
204
- if ( ( WCF_CART_NORMAL_ORDER === $capture_status ) ) {
205
- $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $captured_data->session_id ) ) );
206
- }
207
-
208
- if ( ( WCF_CART_ABANDONED_ORDER === $capture_status || WCF_CART_LOST_ORDER === $capture_status ) ) {
209
- $this->skip_future_emails_when_order_is_completed( sanitize_key( $captured_data->session_id ) );
210
- $this->trigger_zapier_webhook( $captured_data->session_id, WCF_CART_COMPLETED_ORDER );
211
- $note = __( 'This order was abandoned & subsequently recovered.', 'woo-cart-abandonment-recovery' );
212
- $order->add_order_note( $note );
213
- $order->save();
214
- if ( WC()->session ) {
215
- WC()->session->__unset( 'wcf_session_id' );
216
- }
217
- }
218
- }
219
- $wcar_email_admin_recovery = get_option( 'wcar_email_admin_on_recovery' );
220
- if ( 'on' === $wcar_email_admin_recovery ) {
221
- $this->wcar_send_successful_recovery_email_to_admin( $order_id, $old_order_status, $new_order_status );
222
- }
223
- }
224
-
225
- }
226
-
227
-
228
- /**
229
- * Send preview emails.
230
- */
231
- public function send_preview_email() {
232
-
233
- check_ajax_referer( WCF_EMAIL_TEMPLATES_NONCE, 'security' );
234
- $mail_result = $this->send_email_templates( null, true );
235
- if ( $mail_result ) {
236
- wp_send_json_success( __( 'Mail has been sent successfully!', 'woo-cart-abandonment-recovery' ) );
237
- } else {
238
- wp_send_json_error( __( 'Mail sending failed!', 'woo-cart-abandonment-recovery' ) );
239
- }
240
- }
241
-
242
-
243
- /**
244
- * Delete tracked data and set cookie for the user.
245
- */
246
- public function skip_cart_tracking_by_gdpr() {
247
- check_ajax_referer( 'cartflows_skip_cart_tracking_gdpr', 'security' );
248
-
249
- global $wpdb;
250
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
251
-
252
- $session_id = WC()->session->get( 'wcf_session_id' );
253
- if ( $session_id ) {
254
- $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
255
- }
256
-
257
- setcookie( 'wcf_ca_skip_track_data', 'true', 0, '/' );
258
- wp_send_json_success();
259
- }
260
-
261
-
262
- /**
263
- * Create custom schedule.
264
- *
265
- * @param array $schedules schedules.
266
- * @return mixed
267
- */
268
- public function cartflows_ca_update_order_status_action( $schedules ) {
269
-
270
- /**
271
- * Add filter to change the cron interval time to uodate order status.
272
- */
273
- $cron_time = apply_filters( 'woo_ca_update_order_cron_interval', 15 );
274
-
275
- $schedules['every_fifteen_minutes'] = array(
276
- 'interval' => $cron_time * MINUTE_IN_SECONDS,
277
- 'display' => __( 'Every Fifteen Minutes', 'woo-cart-abandonment-recovery' ),
278
- );
279
-
280
- return $schedules;
281
- }
282
-
283
- /**
284
- * Generate new coupon code for abandoned cart.
285
- *
286
- * @param string $discount_type discount type.
287
- * @param float $amount amount.
288
- * @param string $expiry expiry.
289
- * @param string $free_shipping is free shipping.
290
- * @param string $individual_use use coupon individual.
291
- */
292
- public function generate_coupon_code( $discount_type, $amount, $expiry = '', $free_shipping = 'no', $individual_use = 'no' ) {
293
-
294
- $coupon_code = '';
295
-
296
- $coupon_code = wp_generate_password( 8, false, false );
297
-
298
- $new_coupon_id = wp_insert_post(
299
- array(
300
- 'post_title' => $coupon_code,
301
- 'post_content' => '',
302
- 'post_status' => 'publish',
303
- 'post_author' => 1,
304
- 'post_type' => 'shop_coupon',
305
- )
306
- );
307
-
308
- $coupon_post_data = array(
309
- 'discount_type' => $discount_type,
310
- 'description' => WCF_CA_COUPON_DESCRIPTION,
311
- 'coupon_amount' => $amount,
312
- 'individual_use' => $individual_use,
313
- 'product_ids' => '',
314
- 'exclude_product_ids' => '',
315
- 'usage_limit' => '1',
316
- 'usage_count' => '0',
317
- 'date_expires' => $expiry,
318
- 'apply_before_tax' => 'yes',
319
- 'free_shipping' => $free_shipping,
320
- 'coupon_generated_by' => WCF_CA_COUPON_GENERATED_BY,
321
- );
322
-
323
- $coupon_post_data = apply_filters( 'woo_ca_generate_coupon', $coupon_post_data );
324
-
325
- foreach ( $coupon_post_data as $key => $value ) {
326
- update_post_meta( $new_coupon_id, $key, $value );
327
- }
328
-
329
- return $coupon_code;
330
- }
331
-
332
- /**
333
- * Unsubscribe the user from the mailing list.
334
- */
335
- public function unsubscribe_cart_abandonment_emails() {
336
-
337
- $unsubscribe = filter_input( INPUT_GET, 'unsubscribe', FILTER_VALIDATE_BOOLEAN );
338
- $wcf_ac_token = filter_input( INPUT_GET, 'wcf_ac_token', FILTER_SANITIZE_STRING );
339
- if ( $unsubscribe && $this->is_valid_token( $wcf_ac_token ) ) {
340
- $token_data = $this->wcf_decode_token( $wcf_ac_token );
341
- if ( isset( $token_data['wcf_session_id'] ) ) {
342
- $session_id = $token_data['wcf_session_id'];
343
-
344
- global $wpdb;
345
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
346
- $wpdb->update(
347
- $cart_abandonment_table,
348
- array( 'unsubscribed' => true ),
349
- array( 'session_id' => $session_id )
350
- );
351
- wp_die( esc_html__( 'You have successfully unsubscribed from our email list.', 'woo-cart-abandonment-recovery' ), esc_html__( 'Unsubscribed', 'woo-cart-abandonment-recovery' ) );
352
-
353
- }
354
- }
355
-
356
- }
357
-
358
-
359
- /**
360
- * Link JS to mce button.
361
- *
362
- * @param array $plugins mce pluggins.
363
- * @return mixed
364
- */
365
- public function wcf_filter_mce_plugin( $plugins ) {
366
- $plugins['cartflows_ac'] = CARTFLOWS_CA_URL . 'admin/assets/js/admin-mce.js';
367
- return $plugins;
368
- }
369
-
370
- /**
371
- * Register button.
372
- *
373
- * @param array $buttons mce buttons.
374
- * @return mixed
375
- */
376
- public function wcf_filter_mce_button( $buttons ) {
377
- array_push( $buttons, 'cartflows_ac' );
378
- return $buttons;
379
- }
380
-
381
- /**
382
- * Initialise all the constants
383
- */
384
- public function define_cart_abandonment_constants() {
385
- define( 'CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR', CARTFLOWS_CA_DIR . 'modules/cart-abandonment/' );
386
- define( 'CARTFLOWS_CART_ABANDONMENT_TRACKING_URL', CARTFLOWS_CA_URL . 'modules/cart-abandonment/' );
387
- define( 'WCF_CART_ABANDONED_ORDER', 'abandoned' );
388
- define( 'WCF_CART_COMPLETED_ORDER', 'completed' );
389
- define( 'WCF_CART_LOST_ORDER', 'lost' );
390
- define( 'WCF_CART_NORMAL_ORDER', 'normal' );
391
- define( 'WCF_CART_FAILED_ORDER', 'failed' );
392
- define( 'CARTFLOWS_ZAPIER_ACTION_AFTER_TIME', 1800 );
393
-
394
- define( 'WCF_ACTION_ABANDONED_CARTS', 'abandoned_carts' );
395
- define( 'WCF_ACTION_RECOVERED_CARTS', 'recovered_carts' );
396
- define( 'WCF_ACTION_LOST_CARTS', 'lost_carts' );
397
- define( 'WCF_ACTION_SETTINGS', 'settings' );
398
- define( 'WCF_ACTION_REPORTS', 'reports' );
399
-
400
- define( 'WCF_SUB_ACTION_REPORTS_VIEW', 'view' );
401
- define( 'WCF_SUB_ACTION_REPORTS_RESCHEDULE', 'reschedule' );
402
-
403
- define( 'WCF_DEFAULT_CUT_OFF_TIME', 15 );
404
- define( 'WCF_DEFAULT_COUPON_AMOUNT', 10 );
405
-
406
- define( 'WCF_CA_DATETIME_FORMAT', 'Y-m-d H:i:s' );
407
-
408
- define( 'WCF_CA_COUPON_DESCRIPTION', 'This coupon is for abandoned cart email templates.' );
409
- define( 'WCF_CA_COUPON_GENERATED_BY', 'woo-cart-abandonment-recovery' );
410
- }
411
-
412
- /**
413
- * Restore cart abandonemnt data on checkout page.
414
- *
415
- * @param array $fields checkout fields values.
416
- * @return array field values
417
- */
418
- public function restore_cart_abandonment_data( $fields = array() ) {
419
- global $woocommerce;
420
- $result = array();
421
- // Restore only of user is not logged in.
422
- $wcf_ac_token = filter_input( INPUT_GET, 'wcf_ac_token', FILTER_SANITIZE_STRING );
423
- if ( $this->is_valid_token( $wcf_ac_token ) ) {
424
-
425
- // Check if `wcf_restore_token` exists to restore cart data.
426
- $token_data = $this->wcf_decode_token( $wcf_ac_token );
427
- if ( is_array( $token_data ) && isset( $token_data['wcf_session_id'] ) ) {
428
- $result = $this->get_checkout_details( $token_data['wcf_session_id'] );
429
- if ( isset( $result ) && WCF_CART_ABANDONED_ORDER === $result->order_status || WCF_CART_LOST_ORDER === $result->order_status ) {
430
- WC()->session->set( 'wcf_session_id', $token_data['wcf_session_id'] );
431
- }
432
- }
433
-
434
- if ( $result ) {
435
- $cart_content = unserialize( $result->cart_contents );
436
-
437
- if ( $cart_content ) {
438
- $woocommerce->cart->empty_cart();
439
- wc_clear_notices();
440
- foreach ( $cart_content as $cart_item ) {
441
-
442
- $cart_item_data = array();
443
- $variation_data = array();
444
- $id = $cart_item['product_id'];
445
- $qty = $cart_item['quantity'];
446
-
447
- // Skip bundled products when added main product.
448
- if ( isset( $cart_item['bundled_by'] ) ) {
449
- continue;
450
- }
451
-
452
- if ( isset( $cart_item['variation'] ) ) {
453
- foreach ( $cart_item['variation'] as $key => $value ) {
454
- $variation_data[ $key ] = $value;
455
- }
456
- }
457
-
458
- $cart_item_data = $cart_item;
459
-
460
- $woocommerce->cart->add_to_cart( $id, $qty, $cart_item['variation_id'], $variation_data, $cart_item_data );
461
- }
462
-
463
- if ( isset( $token_data['wcf_coupon_code'] ) && ! $woocommerce->cart->applied_coupons ) {
464
- $woocommerce->cart->add_discount( $token_data['wcf_coupon_code'] );
465
- }
466
- }
467
- $other_fields = unserialize( $result->other_fields );
468
-
469
- $parts = explode( ',', $other_fields['wcf_location'] );
470
- if ( count( $parts ) > 1 ) {
471
- $country = $parts[0];
472
- $city = trim( $parts[1] );
473
- } else {
474
- $country = $parts[0];
475
- $city = '';
476
- }
477
-
478
- foreach ( $other_fields as $key => $value ) {
479
- $key = str_replace( 'wcf_', '', $key );
480
- $_POST[ $key ] = sanitize_text_field( $value );
481
- }
482
- $_POST['billing_first_name'] = sanitize_text_field( $other_fields['wcf_first_name'] );
483
- $_POST['billing_last_name'] = sanitize_text_field( $other_fields['wcf_last_name'] );
484
- $_POST['billing_phone'] = sanitize_text_field( $other_fields['wcf_phone_number'] );
485
- $_POST['billing_email'] = sanitize_email( $result->email );
486
- $_POST['billing_city'] = sanitize_text_field( $city );
487
- $_POST['billing_country'] = sanitize_text_field( $country );
488
-
489
- }
490
- }
491
- return $fields;
492
- }
493
-
494
- /**
495
- * Add notice to inform user about test email checkout page.
496
- */
497
- public function test_email_checkout_page() {
498
-
499
- $wcf_ac_token = filter_input( INPUT_GET, 'wcf_ac_token', FILTER_SANITIZE_STRING );
500
- $token_data = $this->wcf_decode_token( $wcf_ac_token );
501
- if ( is_checkout() && ! is_wc_endpoint_url() && isset( $token_data['wcf_preview_email'] ) && $token_data['wcf_preview_email'] ) {
502
- wc_print_notice( __( 'This checkout page is generated by WooCommerce Cart Abandonment Recovery plugin from test mail.', 'woo-cart-abandonment-recovery' ), 'notice' );
503
- }
504
- }
505
-
506
-
507
- /**
508
- * Load cart abandonemnt tracking script.
509
- *
510
- * @return void
511
- */
512
- public function cart_abandonment_tracking_script() {
513
-
514
- $wcf_ca_ignore_users = get_option( 'wcf_ca_ignore_users' );
515
- $current_user = wp_get_current_user();
516
- $roles = $current_user->roles;
517
- $role = array_shift( $roles );
518
- if ( ! empty( $wcf_ca_ignore_users ) ) {
519
- foreach ( $wcf_ca_ignore_users as $user ) {
520
- $user = strtolower( $user );
521
- $role = preg_replace( '/_/', ' ', $role );
522
- if ( $role === $user ) {
523
- return;
524
- }
525
- }
526
- }
527
-
528
- global $post;
529
- wp_enqueue_script(
530
- 'cartflows-cart-abandonment-tracking',
531
- CARTFLOWS_CART_ABANDONMENT_TRACKING_URL . 'assets/js/cart-abandonment-tracking.js',
532
- array( 'jquery' ),
533
- CARTFLOWS_CA_VER,
534
- true
535
- );
536
-
537
- $vars = array(
538
- 'ajaxurl' => admin_url( 'admin-ajax.php' ),
539
- '_nonce' => wp_create_nonce( 'cartflows_save_cart_abandonment_data' ),
540
- '_gdpr_nonce' => wp_create_nonce( 'cartflows_skip_cart_tracking_gdpr' ),
541
- '_post_id' => get_the_ID(),
542
- '_show_gdpr_message' => ( wcf_ca()->utils->is_gdpr_enabled() && ! isset( $_COOKIE['wcf_ca_skip_track_data'] ) ),
543
- '_gdpr_message' => get_option( 'wcf_ca_gdpr_message' ),
544
- '_gdpr_nothanks_msg' => __( 'No Thanks', 'woo-cart-abandonment-recovery' ),
545
- '_gdpr_after_no_thanks_msg' => __( 'You won\'t receive further emails from us, thank you!', 'woo-cart-abandonment-recovery' ),
546
- 'enable_ca_tracking' => true,
547
- );
548
-
549
- wp_localize_script( 'cartflows-cart-abandonment-tracking', 'CartFlowsProCAVars', $vars );
550
-
551
- }
552
-
553
- /**
554
- * Validate the token before use.
555
- *
556
- * @param string $token token form the url.
557
- * @return bool
558
- */
559
- public function is_valid_token( $token ) {
560
- $is_valid = false;
561
- $token_data = $this->wcf_decode_token( $token );
562
- if ( is_array( $token_data ) && array_key_exists( 'wcf_session_id', $token_data ) ) {
563
- $result = $this->get_checkout_details( $token_data['wcf_session_id'] );
564
- if ( isset( $result ) ) {
565
- $is_valid = true;
566
- }
567
- }
568
- return $is_valid;
569
- }
570
-
571
- /**
572
- * Check before emails actually send to user.
573
- *
574
- * @param array $email_data email_data.
575
- * @param array $current_cart_data cart data.
576
- * @return bool
577
- */
578
- public function check_if_already_purchased_by_email_product_ids( $email_data, $current_cart_data ) {
579
-
580
- global $wpdb;
581
- $current_cart_data = unserialize( $current_cart_data );
582
-
583
- // Fetch products & variations.
584
- $products = array_values( wp_list_pluck( $current_cart_data, 'product_id' ) );
585
- $variations = array_values( wp_list_pluck( $current_cart_data, 'variation_id' ) );
586
- $current_products = array_unique( array_merge( $products, $variations ) );
587
-
588
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
589
-
590
- $orders = wc_get_orders(
591
- array(
592
- 'billing_email' => $email_data->email,
593
- 'status' => array( 'processing', 'completed' ),
594
- 'date_after' => gmdate(
595
- 'Y-m-d h:i:s',
596
- strtotime( '-30 days' )
597
- ),
598
- )
599
- );
600
- $need_to_send_email = true;
601
-
602
- foreach ( $orders as $order ) {
603
- $order = wc_get_order( $order->get_id() );
604
- $items = $order->get_items();
605
- foreach ( $items as $item ) {
606
- $product_id = $item->get_product_id();
607
- if ( in_array( $product_id, $current_products, true ) ) {
608
- /**
609
- * Remove duplicate captured order for tracking.
610
- */
611
- $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $email_data->session_id ) ) );
612
- $need_to_send_email = false;
613
- break;
614
- }
615
- }
616
- }
617
- return $need_to_send_email;
618
- }
619
-
620
- /**
621
- * Execute Zapier webhook for further action inside Zapier.
622
- *
623
- * @since 1.0.0
624
- */
625
- public function update_order_status() {
626
-
627
- global $wpdb;
628
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
629
- $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
630
- $minutes = wcf_ca()->utils->get_cart_abandonment_tracking_cut_off_time();
631
-
632
- /**
633
- * Delete abandoned cart orders if empty.
634
- */
635
- $this->delete_empty_abandoned_order();
636
-
637
- $wp_current_datetime = current_time( WCF_CA_DATETIME_FORMAT );
638
- $abandoned_ids = $wpdb->get_results(
639
- $wpdb->prepare('SELECT `session_id` FROM `' . $cart_abandonment_table . '` WHERE `order_status` = %s AND ADDDATE( `time`, INTERVAL %d MINUTE) <= %s', WCF_CART_NORMAL_ORDER, $minutes, $wp_current_datetime ), ARRAY_A // phpcs:ignore
640
- );
641
-
642
- foreach ( $abandoned_ids as $session_id ) {
643
-
644
- if ( isset( $session_id['session_id'] ) ) {
645
-
646
- $current_session_id = $session_id['session_id'];
647
- $this->schedule_emails( $current_session_id );
648
-
649
- $coupon_code = '';
650
- $wcf_ca_coupon_code_status = get_option( 'wcf_ca_coupon_code_status' );
651
-
652
- if ( 'on' === $wcf_ca_coupon_code_status ) {
653
- $discount_type = get_option( 'wcf_ca_discount_type' );
654
- $discount_type = $discount_type ? $discount_type : 'percent';
655
- $amount = get_option( 'wcf_ca_coupon_amount' );
656
- $amount = $amount ? $amount : WCF_DEFAULT_COUPON_AMOUNT;
657
- $coupon_expiry_date = get_option( 'wcf_ca_coupon_expiry' );
658
- $coupon_expiry_unit = get_option( 'wcf_ca_coupon_expiry_unit' );
659
- $coupon_expiry_date = $coupon_expiry_date ? strtotime( $wp_current_datetime . ' +' . $coupon_expiry_date . ' ' . $coupon_expiry_unit ) : '';
660
- $free_shipping_coupon = get_option( 'wcf_ca_free_shipping_coupon' );
661
- $free_shipping = ( isset( $free_shipping_coupon ) && ( $free_shipping_coupon->meta_value ) ) ? 'yes' : 'no';
662
-
663
- $individual_use_only = get_option( 'wcf_ca_individual_use_only' );
664
- $individual_use = ( isset( $individual_use_only ) && ( $individual_use_only->meta_value ) ) ? 'yes' : 'no';
665
-
666
- $coupon_code = $this->generate_coupon_code( $discount_type, $amount, $coupon_expiry_date, $free_shipping, $individual_use );
667
- }
668
-
669
- $wpdb->update(
670
- $cart_abandonment_table,
671
- array(
672
- 'order_status' => WCF_CART_ABANDONED_ORDER,
673
- 'coupon_code' => $coupon_code,
674
- ),
675
- array( 'session_id' => $current_session_id )
676
- );
677
-
678
- $this->trigger_zapier_webhook( $current_session_id, WCF_CART_ABANDONED_ORDER );
679
- }
680
- }
681
-
682
- /**
683
- * Send scheduled emails.
684
- */
685
- $this->send_emails_to_callback();
686
-
687
- // Update order status to lost after campaign complete.
688
- // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
689
- $wpdb->query(
690
- $wpdb->prepare(
691
- "UPDATE $cart_abandonment_table as ca SET order_status = 'lost' WHERE ca.order_status = %s AND DATE(ca.time) <= DATE_SUB( %s , INTERVAL 30 DAY)
692
- AND ( (SELECT count(*) FROM $email_history_table WHERE ca_session_id = ca.session_id ) =
693
- (SELECT count(*) FROM $email_history_table WHERE ca_session_id = ca.session_id AND email_sent = 1) )",
694
- WCF_CART_ABANDONED_ORDER,
695
- $wp_current_datetime
696
- )
697
- );
698
- // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
699
-
700
- /**
701
- * Delete garbage coupons.
702
- */
703
- $wcf_ca_auto_delete_coupons = get_option( 'wcf_ca_auto_delete_coupons' );
704
-
705
- if ( isset( $wcf_ca_auto_delete_coupons ) && 'on' === $wcf_ca_auto_delete_coupons ) {
706
- $this->delete_used_and_expired_coupons();
707
- }
708
-
709
- }
710
-
711
- /**
712
- * Send zapier webhook.
713
- *
714
- * @param string $session_id session id.
715
- * @param string $order_status order status.
716
- */
717
- public function trigger_zapier_webhook( $session_id, $order_status ) {
718
-
719
- $checkout_details = $this->get_checkout_details( $session_id );
720
-
721
- if ( $checkout_details && wcf_ca()->utils->is_zapier_trigger_enabled() ) {
722
- $trigger_details = array();
723
- $url = get_option( 'wcf_ca_zapier_cart_abandoned_webhook' );
724
-
725
- $other_details = unserialize( $checkout_details->other_fields );
726
- $trigger_details['first_name'] = $other_details['wcf_first_name'];
727
- $trigger_details['last_name'] = $other_details['wcf_last_name'];
728
- $trigger_details['phone_number'] = $other_details['wcf_phone_number'];
729
- $trigger_details['billing_address'] = $other_details['wcf_billing_company'] . ' ' . $other_details['wcf_billing_address_1'] . ', ' . $other_details['wcf_billing_state'] . ', ' . $other_details['wcf_location'] . ', ' . $other_details['wcf_billing_postcode'];
730
- $trigger_details['billing_address'] = trim( $trigger_details['billing_address'], ', ' );
731
- $trigger_details['shipping_address'] = $other_details['wcf_shipping_company'] . ' ' . $other_details['wcf_shipping_address_1'] . ', ' . $other_details['wcf_shipping_city'] . ', ' . $other_details['wcf_shipping_state'] . ', ' . $other_details['wcf_shipping_postcode'];
732
- $trigger_details['shipping_address'] = trim( $trigger_details['shipping_address'], ', ' );
733
- $trigger_details['email'] = $checkout_details->email;
734
- $token_data = array( 'wcf_session_id' => $checkout_details->session_id );
735
- $trigger_details['checkout_url'] = $this->get_checkout_url( $checkout_details->checkout_id, $token_data );
736
- $trigger_details['product_names'] = $this->get_comma_separated_products( $checkout_details->cart_contents );
737
- $trigger_details['coupon_code'] = $checkout_details->coupon_code;
738
- $trigger_details['order_status'] = $order_status;
739
- $trigger_details['cart_total'] = $checkout_details->cart_total;
740
- $trigger_details['product_table'] = $this->get_email_product_block( $checkout_details->cart_contents, $checkout_details->cart_total );
741
-
742
- $trigger_details = apply_filters( 'woo_ca_webhook_trigger_details', $trigger_details );
743
-
744
- $parameters = http_build_query( $trigger_details );
745
-
746
- wp_remote_post(
747
- $url,
748
- array(
749
- 'body' => $parameters,
750
- 'timeout' => '5',
751
- 'redirection' => '5',
752
- 'httpversion' => '1.0',
753
- 'blocking' => true,
754
- 'headers' => array(),
755
- 'cookies' => array(),
756
- )
757
- );
758
-
759
- }
760
- }
761
-
762
-
763
- /**
764
- * Sanitize post array.
765
- *
766
- * @return array
767
- */
768
- public function sanitize_post_data() {
769
-
770
- $input_post_values = array(
771
- 'wcf_billing_company' => array(
772
- 'default' => '',
773
- 'sanitize' => FILTER_SANITIZE_STRING,
774
- ),
775
- 'wcf_email' => array(
776
- 'default' => '',
777
- 'sanitize' => FILTER_SANITIZE_EMAIL,
778
- ),
779
- 'wcf_billing_address_1' => array(
780
- 'default' => '',
781
- 'sanitize' => FILTER_SANITIZE_STRING,
782
- ),
783
- 'wcf_billing_address_2' => array(
784
- 'default' => '',
785
- 'sanitize' => FILTER_SANITIZE_STRING,
786
- ),
787
- 'wcf_billing_state' => array(
788
- 'default' => '',
789
- 'sanitize' => FILTER_SANITIZE_STRING,
790
- ),
791
- 'wcf_billing_postcode' => array(
792
- 'default' => '',
793
- 'sanitize' => FILTER_SANITIZE_STRING,
794
- ),
795
- 'wcf_shipping_first_name' => array(
796
- 'default' => '',
797
- 'sanitize' => FILTER_SANITIZE_STRING,
798
- ),
799
- 'wcf_shipping_last_name' => array(
800
- 'default' => '',
801
- 'sanitize' => FILTER_SANITIZE_STRING,
802
- ),
803
- 'wcf_shipping_company' => array(
804
- 'default' => '',
805
- 'sanitize' => FILTER_SANITIZE_STRING,
806
- ),
807
- 'wcf_shipping_country' => array(
808
- 'default' => '',
809
- 'sanitize' => FILTER_SANITIZE_STRING,
810
- ),
811
- 'wcf_shipping_address_1' => array(
812
- 'default' => '',
813
- 'sanitize' => FILTER_SANITIZE_STRING,
814
- ),
815
- 'wcf_shipping_address_2' => array(
816
- 'default' => '',
817
- 'sanitize' => FILTER_SANITIZE_STRING,
818
- ),
819
- 'wcf_shipping_city' => array(
820
- 'default' => '',
821
- 'sanitize' => FILTER_SANITIZE_STRING,
822
- ),
823
- 'wcf_shipping_state' => array(
824
- 'default' => '',
825
- 'sanitize' => FILTER_SANITIZE_STRING,
826
- ),
827
- 'wcf_shipping_postcode' => array(
828
- 'default' => '',
829
- 'sanitize' => FILTER_SANITIZE_STRING,
830
- ),
831
- 'wcf_order_comments' => array(
832
- 'default' => '',
833
- 'sanitize' => FILTER_SANITIZE_STRING,
834
- ),
835
- 'wcf_name' => array(
836
- 'default' => '',
837
- 'sanitize' => FILTER_SANITIZE_STRING,
838
- ),
839
- 'wcf_surname' => array(
840
- 'default' => '',
841
- 'sanitize' => FILTER_SANITIZE_STRING,
842
- ),
843
- 'wcf_phone' => array(
844
- 'default' => '',
845
- 'sanitize' => FILTER_SANITIZE_STRING,
846
- ),
847
- 'wcf_country' => array(
848
- 'default' => '',
849
- 'sanitize' => FILTER_SANITIZE_STRING,
850
- ),
851
- 'wcf_city' => array(
852
- 'default' => '',
853
- 'sanitize' => FILTER_SANITIZE_STRING,
854
- ),
855
- 'wcf_post_id' => array(
856
- 'default' => 0,
857
- 'sanitize' => FILTER_SANITIZE_NUMBER_INT,
858
- ),
859
- );
860
-
861
- $sanitized_post = array();
862
- foreach ( $input_post_values as $key => $input_post_value ) {
863
-
864
- if ( isset( $_POST[ $key ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
865
- $sanitized_post[ $key ] = filter_input( INPUT_POST, $key, $input_post_value['sanitize'] );
866
- } else {
867
- $sanitized_post[ $key ] = $input_post_value['default'];
868
- }
869
- }
870
- return $sanitized_post;
871
-
872
- }
873
-
874
-
875
- /**
876
- * Save cart abandonment tracking and schedule new event.
877
- *
878
- * @since 1.0.0
879
- */
880
- public function save_cart_abandonment_data() {
881
- check_ajax_referer( 'cartflows_save_cart_abandonment_data', 'security' );
882
- $post_data = $this->sanitize_post_data();
883
- if ( isset( $post_data['wcf_email'] ) ) {
884
- $user_email = sanitize_email( $post_data['wcf_email'] );
885
- global $wpdb;
886
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
887
-
888
- // Verify if email is already exists.
889
- $session_id = WC()->session->get( 'wcf_session_id' );
890
- $session_checkout_details = null;
891
- if ( isset( $session_id ) ) {
892
- $session_checkout_details = $this->get_checkout_details( $session_id );
893
- } else {
894
- $session_checkout_details = $this->get_checkout_details_by_email( $user_email );
895
- if ( $session_checkout_details ) {
896
- $session_id = $session_checkout_details->session_id;
897
- WC()->session->set( 'wcf_session_id', $session_id );
898
- } else {
899
- $session_id = md5( uniqid( wp_rand(), true ) );
900
- }
901
- }
902
-
903
- $checkout_details = $this->prepare_abandonment_data( $post_data );
904
-
905
- if ( isset( $session_checkout_details ) && WCF_CART_COMPLETED_ORDER === $session_checkout_details->order_status ) {
906
- WC()->session->__unset( 'wcf_session_id' );
907
- $session_id = md5( uniqid( wp_rand(), true ) );
908
- }
909
-
910
- if ( isset( $checkout_details['cart_total'] ) && $checkout_details['cart_total'] > 0 ) {
911
-
912
- if ( ( ! is_null( $session_id ) ) && ! is_null( $session_checkout_details ) ) {
913
-
914
- // Updating row in the Database where users Session id = same as prevously saved in Session.
915
- $wpdb->update(
916
- $cart_abandonment_table,
917
- $checkout_details,
918
- array( 'session_id' => $session_id )
919
- );
920
-
921
- } else {
922
-
923
- $checkout_details['session_id'] = sanitize_text_field( $session_id );
924
- // Inserting row into Database.
925
- $wpdb->insert(
926
- $cart_abandonment_table,
927
- $checkout_details
928
- );
929
-
930
- // Storing session_id in WooCommerce session.
931
- WC()->session->set( 'wcf_session_id', $session_id );
932
-
933
- }
934
- } else {
935
- $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
936
- }
937
-
938
- wp_send_json_success();
939
- }
940
- }
941
-
942
-
943
- /**
944
- * Prepare cart data to save for abandonment.
945
- *
946
- * @param array $post_data post data.
947
- * @return array
948
- */
949
- public function prepare_abandonment_data( $post_data = array() ) {
950
-
951
- if ( function_exists( 'WC' ) ) {
952
-
953
- // Retrieving cart total value and currency.
954
- $cart_total = WC()->cart->total;
955
-
956
- // Retrieving cart products and their quantities.
957
- $products = WC()->cart->get_cart();
958
- $current_time = current_time( WCF_CA_DATETIME_FORMAT );
959
- $other_fields = array(
960
- 'wcf_billing_company' => $post_data['wcf_billing_company'],
961
- 'wcf_billing_address_1' => $post_data['wcf_billing_address_1'],
962
- 'wcf_billing_address_2' => $post_data['wcf_billing_address_2'],
963
- 'wcf_billing_state' => $post_data['wcf_billing_state'],
964
- 'wcf_billing_postcode' => $post_data['wcf_billing_postcode'],
965
- 'wcf_shipping_first_name' => $post_data['wcf_shipping_first_name'],
966
- 'wcf_shipping_last_name' => $post_data['wcf_shipping_last_name'],
967
- 'wcf_shipping_company' => $post_data['wcf_shipping_company'],
968
- 'wcf_shipping_country' => $post_data['wcf_shipping_country'],
969
- 'wcf_shipping_address_1' => $post_data['wcf_shipping_address_1'],
970
- 'wcf_shipping_address_2' => $post_data['wcf_shipping_address_2'],
971
- 'wcf_shipping_city' => $post_data['wcf_shipping_city'],
972
- 'wcf_shipping_state' => $post_data['wcf_shipping_state'],
973
- 'wcf_shipping_postcode' => $post_data['wcf_shipping_postcode'],
974
- 'wcf_order_comments' => $post_data['wcf_order_comments'],
975
- 'wcf_first_name' => $post_data['wcf_name'],
976
- 'wcf_last_name' => $post_data['wcf_surname'],
977
- 'wcf_phone_number' => $post_data['wcf_phone'],
978
- 'wcf_location' => $post_data['wcf_country'] . ', ' . $post_data['wcf_city'],
979
- );
980
-
981
- $checkout_details = array(
982
- 'email' => $post_data['wcf_email'],
983
- 'cart_contents' => serialize( $products ),
984
- 'cart_total' => sanitize_text_field( $cart_total ),
985
- 'time' => sanitize_text_field( $current_time ),
986
- 'other_fields' => serialize( $other_fields ),
987
- 'checkout_id' => $post_data['wcf_post_id'],
988
- );
989
- }
990
- return $checkout_details;
991
- }
992
-
993
- /**
994
- * Get the acceptable order statuses.
995
- */
996
- public function get_acceptable_order_statuses() {
997
-
998
- $acceptable_order_statuses = get_option( 'wcf_ca_excludes_orders' );
999
- $acceptable_order_statuses = array_map( 'strtolower', $acceptable_order_statuses );
1000
-
1001
- return $acceptable_order_statuses;
1002
- }
1003
-
1004
- /**
1005
- * Deletes cart abandonment tracking and scheduled event.
1006
- *
1007
- * @param int $order_id Order ID.
1008
- * @since 1.0.0
1009
- */
1010
- public function delete_cart_abandonment_data( $order_id ) {
1011
-
1012
- $acceptable_order_statuses = $this->get_acceptable_order_statuses();
1013
-
1014
- $order = wc_get_order( $order_id );
1015
- $order_status = $order->get_status();
1016
- if ( ! in_array( $order_status, $acceptable_order_statuses, true ) ) {
1017
- // Proceed if order status in completed or processing.
1018
- return;
1019
- }
1020
-
1021
- global $wpdb;
1022
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1023
- $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
1024
-
1025
- if ( isset( WC()->session ) ) {
1026
- $session_id = WC()->session->get( 'wcf_session_id' );
1027
-
1028
- if ( isset( $session_id ) ) {
1029
- $checkout_details = $this->get_checkout_details( $session_id );
1030
-
1031
- $has_mail_sent = count( $this->fetch_scheduled_emails( $session_id, true ) );
1032
-
1033
- if ( ! $has_mail_sent ) {
1034
- $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
1035
- } else {
1036
- if ( $checkout_details && ( WCF_CART_ABANDONED_ORDER === $checkout_details->order_status || WCF_CART_LOST_ORDER === $checkout_details->order_status ) ) {
1037
-
1038
- $this->skip_future_emails_when_order_is_completed( $session_id );
1039
-
1040
- $this->trigger_zapier_webhook( $session_id, WCF_CART_COMPLETED_ORDER );
1041
-
1042
- $order = wc_get_order( $order_id );
1043
- $note = __( 'This order was abandoned & subsequently recovered.', 'woo-cart-abandonment-recovery' );
1044
- $order->add_order_note( $note );
1045
- $order->save();
1046
-
1047
- } elseif ( WCF_CART_COMPLETED_ORDER !== $checkout_details->order_status ) {
1048
- // Normal checkout.
1049
-
1050
- $billing_email = filter_input( INPUT_POST, 'billing_email', FILTER_SANITIZE_EMAIL );
1051
-
1052
- if ( $billing_email ) {
1053
- $order_data = $this->get_captured_data_by_email( $billing_email );
1054
-
1055
- if ( ! is_null( $order_data ) ) {
1056
- $existing_cart_contents = unserialize( $order_data->cart_contents );
1057
- $order_cart_contents = unserialize( $checkout_details->cart_contents );
1058
- $existing_cart_products = array_keys( (array) $existing_cart_contents );
1059
- $order_cart_products = array_keys( (array) $order_cart_contents );
1060
- if ( $this->check_if_similar_cart( $existing_cart_products, $order_cart_products ) ) {
1061
- $this->skip_future_emails_when_order_is_completed( $order_data->session_id );
1062
- }
1063
- }
1064
- }
1065
- $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
1066
- }
1067
- }
1068
- }
1069
- if ( WC()->session ) {
1070
- WC()->session->__unset( 'wcf_session_id' );
1071
- }
1072
- }
1073
- }
1074
-
1075
- /**
1076
- * Unschedule future emails for completed orders.
1077
- *
1078
- * @param string $session_id session id.
1079
- * @param bool $skip_complete skip update query.
1080
- */
1081
- public function skip_future_emails_when_order_is_completed( $session_id, $skip_complete = false ) {
1082
-
1083
- global $wpdb;
1084
- $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
1085
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1086
-
1087
- if ( ! $skip_complete ) {
1088
- $wpdb->update(
1089
- $cart_abandonment_table,
1090
- array(
1091
- 'order_status' => WCF_CART_COMPLETED_ORDER,
1092
- ),
1093
- array(
1094
- 'session_id' => sanitize_key( $session_id ),
1095
- )
1096
- );
1097
- }
1098
-
1099
- $wpdb->update(
1100
- $email_history_table,
1101
- array( 'email_sent' => -1 ),
1102
- array(
1103
- 'ca_session_id' => $session_id,
1104
- 'email_sent' => 0,
1105
- )
1106
- );
1107
- }
1108
-
1109
- /**
1110
- * Compare cart if similar products.
1111
- *
1112
- * @param array $cart_a cart_a.
1113
- * @param array $cart_b cart_b.
1114
- * @return bool
1115
- */
1116
- public function check_if_similar_cart( $cart_a, $cart_b ) {
1117
- return (
1118
- is_array( $cart_a )
1119
- && is_array( $cart_b )
1120
- && count( $cart_a ) === count( $cart_b )
1121
- && array_diff( $cart_a, $cart_b ) === array_diff( $cart_b, $cart_a )
1122
- );
1123
- }
1124
-
1125
-
1126
- /**
1127
- * Get the checkout details for the user.
1128
- *
1129
- * @param string $wcf_session_id checkout page session id.
1130
- * @since 1.0.0
1131
- */
1132
- public function get_checkout_details( $wcf_session_id ) {
1133
- global $wpdb;
1134
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1135
- $result = $wpdb->get_row(
1136
- $wpdb->prepare('SELECT * FROM `' . $cart_abandonment_table . '` WHERE session_id = %s', $wcf_session_id ) // phpcs:ignore
1137
- );
1138
- return $result;
1139
- }
1140
-
1141
- /**
1142
- * Get the checkout details for the user.
1143
- *
1144
- * @param string $email user email.
1145
- * @since 1.0.0
1146
- */
1147
- public function get_checkout_details_by_email( $email ) {
1148
- global $wpdb;
1149
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1150
- $result = $wpdb->get_row(
1151
- $wpdb->prepare('SELECT * FROM `' . $cart_abandonment_table . '` WHERE email = %s AND `order_status` IN ( %s, %s )', $email, WCF_CART_ABANDONED_ORDER, WCF_CART_NORMAL_ORDER ) // phpcs:ignore
1152
- );
1153
- return $result;
1154
- }
1155
-
1156
-
1157
- /**
1158
- * Get the checkout details for the user.
1159
- *
1160
- * @param string $value value.
1161
- * @since 1.0.0
1162
- */
1163
- public function get_captured_data_by_email( $value ) {
1164
- global $wpdb;
1165
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1166
- $result = $wpdb->get_row(
1167
- $wpdb->prepare(
1168
- 'SELECT * FROM `' . $cart_abandonment_table . '` WHERE email = %s AND `order_status` IN (%s, %s) ORDER BY `time` DESC LIMIT 1', $value, WCF_CART_ABANDONED_ORDER, WCF_CART_LOST_ORDER ) // phpcs:ignore
1169
- );
1170
- return $result;
1171
- }
1172
-
1173
-
1174
- /**
1175
- * Get the checkout details for the user.
1176
- *
1177
- * @param string $value value.
1178
- * @since 1.0.0
1179
- */
1180
- public function get_tracked_data_without_status( $value ) {
1181
- global $wpdb;
1182
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1183
- $result = $wpdb->get_row(
1184
- $wpdb->prepare(
1185
- 'SELECT * FROM `' . $cart_abandonment_table . '` WHERE email = %s LIMIT 1', $value ) // phpcs:ignore
1186
- );
1187
- return $result;
1188
- }
1189
-
1190
- /**
1191
- * Add submenu to admin menu.
1192
- *
1193
- * @since 1.1.5
1194
- */
1195
- public function abandoned_cart_tracking_menu() {
1196
-
1197
- $capability = current_user_can( 'manage_woocommerce' ) ? 'manage_woocommerce' : 'manage_options';
1198
-
1199
- add_submenu_page(
1200
- 'woocommerce',
1201
- __( 'Cart Abandonment', 'woo-cart-abandonment-recovery' ),
1202
- __( 'Cart Abandonment', 'woo-cart-abandonment-recovery' ),
1203
- $capability,
1204
- WCF_CA_PAGE_NAME,
1205
- array( $this, 'render_abandoned_cart_tracking' )
1206
- );
1207
- }
1208
-
1209
- /**
1210
- * Render table view for cart abandonment tracking.
1211
- *
1212
- * @since 1.1.5
1213
- */
1214
- public function render_abandoned_cart_tracking() {
1215
-
1216
- $wcf_list_table = Cartflows_Ca_Cart_Abandonment_Table::get_instance();
1217
-
1218
- if ( 'delete' === $wcf_list_table->current_action() ) {
1219
-
1220
- $ids = array();
1221
- if ( isset( $_REQUEST['id'] ) && is_array( $_REQUEST['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1222
- $ids = array_map( 'intval', $_REQUEST['id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1223
- }
1224
- $deleted_row_count = empty( $ids ) ? 1 : count( $ids );
1225
-
1226
- $wcf_list_table->process_bulk_action();
1227
- $message = '<div class="notice notice-success is-dismissible" id="message"><p>' . sprintf( __( 'Items deleted: %d', 'woo-cart-abandonment-recovery' ), $deleted_row_count ) . '</p></div>'; // phpcs:ignore
1228
- set_transient( 'wcf_ca_show_message', $message, 5 );
1229
- if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
1230
- wp_safe_redirect( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) );
1231
- }
1232
- } elseif ( 'unsubscribe' === $wcf_list_table->current_action() ) {
1233
-
1234
- global $wpdb;
1235
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1236
- $id = filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
1237
-
1238
- $wpdb->update(
1239
- $cart_abandonment_table,
1240
- array( 'unsubscribed' => true ),
1241
- array( 'id' => $id )
1242
- );
1243
- $wcf_list_table->process_bulk_action();
1244
- $message = '<div class="notice notice-success is-dismissible" id="message"><p>' . sprintf( __( 'User(s) unsubscribed successfully!', 'woo-cart-abandonment-recovery' ) ) . '</p></div>'; // phpcs:ignore
1245
- set_transient( 'wcf_ca_show_message', $message, 5 );
1246
- if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
1247
- wp_safe_redirect( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) );
1248
- }
1249
- }
1250
- ?>
1251
-
1252
- <?php
1253
- include_once CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR . 'includes/admin/cartflows-cart-abandonment-tabs.php';
1254
- ?>
1255
- <?php
1256
- }
1257
-
1258
- /**
1259
- * Count abandoned carts
1260
- *
1261
- * @since 1.1.5
1262
- */
1263
- public function abandoned_cart_count() {
1264
- global $wpdb;
1265
- $cart_abandonment_table_name = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1266
-
1267
- $query = $wpdb->prepare( "SELECT COUNT(`id`) FROM {$cart_abandonment_table_name} WHERE `order_status` = %s", WCF_CART_ABANDONED_ORDER ); // phpcs:ignore
1268
- $total_items = $wpdb->get_var( $query ); // phpcs:ignore
1269
- return $total_items;
1270
- }
1271
-
1272
- /**
1273
- * Load analytics scripts.
1274
- */
1275
- public function load_admin_cart_abandonment_script() {
1276
-
1277
- $wcar_page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
1278
-
1279
- if ( ! ( WCF_CA_PAGE_NAME === $wcar_page ) ) {
1280
- return;
1281
- }
1282
-
1283
- // Styles.
1284
- wp_enqueue_style( 'cartflows-cart-abandonment-admin', CARTFLOWS_CA_URL . 'admin/assets/css/admin-cart-abandonment.css', array(), CARTFLOWS_CA_VER );
1285
-
1286
- wp_enqueue_script(
1287
- 'cartflows-ca-email-tmpl-settings',
1288
- CARTFLOWS_CA_URL . 'admin/assets/js/admin-email-templates.js',
1289
- array( 'jquery' ),
1290
- CARTFLOWS_CA_VER,
1291
- false
1292
- );
1293
-
1294
- if ( WCF_CA_PAGE_NAME === $wcar_page ) {
1295
- $filter_table = filter_input( INPUT_GET, 'filter_table', FILTER_SANITIZE_STRING );
1296
- $from_date = filter_input( INPUT_GET, 'from_date', FILTER_SANITIZE_STRING );
1297
- $to_date = filter_input( INPUT_GET, 'to_date', FILTER_SANITIZE_STRING );
1298
- }
1299
-
1300
- $vars = array(
1301
- 'url' => 'admin-ajax.php',
1302
-
1303
- // For delete coupons.
1304
- '_delete_coupon_nonce' => wp_create_nonce( 'wcf_ca_delete_garbage_coupons' ),
1305
- '_confirm_msg' => __( 'Do you really want to delete the used and expired coupons created by Cart Abandonment Plugin?', 'woo-cart-abandonment-recovery' ),
1306
- '_confirm_msg_export' => __( 'Do you really want to export orders?', 'woo-cart-abandonment-recovery' ),
1307
-
1308
- // For Search orders.
1309
- '_search_button_nonce' => wp_create_nonce( 'wcf_ca_search_orders' ),
1310
- '_result_msg' => __( 'No such order is found.', 'woo-cart-abandonment-recovery' ),
1311
-
1312
- );
1313
- wp_localize_script( 'cartflows-ca-email-tmpl-settings', 'wcf_ca_localized_vars', $vars );
1314
- }
1315
-
1316
-
1317
- /**
1318
- * Render Cart abandonment display button beside title.
1319
- */
1320
- public function setup_cart_abandonment_button() {
1321
-
1322
- if ( ! Cartflows_Admin::is_flow_edit_admin() ) {
1323
- return;
1324
- }
1325
-
1326
- $reports_btn_markup = '<style>.wrap{ position:relative;}</style>';
1327
- $reports_btn_markup .= "<div class='wcf-reports-button-wrap'>";
1328
- $reports_btn_markup .= "<button class='wcf-cart-abandonment-reports-popup button button-secondary'>";
1329
- $reports_btn_markup .= esc_html__( 'View Report', 'woo-cart-abandonment-recovery' );
1330
- $reports_btn_markup .= '</button>';
1331
- $reports_btn_markup .= '</div>';
1332
-
1333
- echo wp_kses_post( $reports_btn_markup );
1334
-
1335
- }
1336
-
1337
- /**
1338
- * Get start and end date for given interval.
1339
- *
1340
- * @param string $interval interval .
1341
- * @return array
1342
- */
1343
- public function get_start_end_by_interval( $interval ) {
1344
-
1345
- if ( 'today' === $interval ) {
1346
- $start_date = gmdate( 'Y-m-d' );
1347
- $end_date = gmdate( 'Y-m-d' );
1348
- } else {
1349
-
1350
- $days = $interval;
1351
-
1352
- $start_date = gmdate( 'Y-m-d', strtotime( '-' . $days . ' days' ) );
1353
- $end_date = gmdate( 'Y-m-d' );
1354
- }
1355
-
1356
- return array(
1357
- 'start' => $start_date,
1358
- 'end' => $end_date,
1359
- );
1360
- }
1361
-
1362
-
1363
- /**
1364
- * Get Attributable revenue.
1365
- * Represents the revenue generated by this campaign.
1366
- *
1367
- * @param string $from_date from date.
1368
- * @param string $to_date to date.
1369
- * @param string $type abondened|completed.
1370
- */
1371
- public function get_report_by_type( $from_date, $to_date, $type = WCF_CART_ABANDONED_ORDER ) {
1372
- global $wpdb;
1373
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1374
- $minutes = wcf_ca()->utils->get_cart_abandonment_tracking_cut_off_time();
1375
- $attributable_revenue = $wpdb->get_row(
1376
- $wpdb->prepare( "SELECT SUM(`cart_total`) as revenue, count('*') as no_of_orders FROM {$cart_abandonment_table} WHERE `order_status` = %s AND DATE(`time`) >= %s AND DATE(`time`) <= %s ", $type, $from_date, $to_date ), // phpcs:ignore
1377
- ARRAY_A
1378
- );
1379
- return $attributable_revenue;
1380
- }
1381
-
1382
-
1383
- /**
1384
- * Get checkout url.
1385
- *
1386
- * @param integer $post_id post id.
1387
- * @param string $token_data token data.
1388
- * @return string
1389
- */
1390
- public function get_checkout_url( $post_id, $token_data ) {
1391
-
1392
- $token = $this->wcf_generate_token( (array) $token_data );
1393
- $checkout_url = get_permalink( $post_id ) . '?wcf_ac_token=' . $token;
1394
- return esc_url( $checkout_url );
1395
- }
1396
-
1397
- /**
1398
- * Geberate the token for the given data.
1399
- *
1400
- * @param array $data data.
1401
- */
1402
- public function wcf_generate_token( $data ) {
1403
- return urlencode( base64_encode( http_build_query( $data ) ) );
1404
- }
1405
-
1406
- /**
1407
- * Decode and get the original contents.
1408
- *
1409
- * @param string $token token.
1410
- */
1411
- public function wcf_decode_token( $token ) {
1412
- $token = sanitize_text_field( $token );
1413
- parse_str( base64_decode( urldecode( $token ) ), $token );
1414
- return $token;
1415
- }
1416
-
1417
- /**
1418
- * Render Cart abandonment tabs.
1419
- *
1420
- * @since 1.1.5
1421
- */
1422
- public function wcf_display_tabs() {
1423
-
1424
- $wcar_action = filter_input( INPUT_GET, 'action', FILTER_SANITIZE_STRING );
1425
- $sub_action = filter_input( INPUT_GET, 'sub_action', FILTER_SANITIZE_STRING );
1426
-
1427
- if ( ! $wcar_action ) {
1428
- $wcar_action = WCF_ACTION_REPORTS;
1429
- $active_settings = '';
1430
- $active_reports = '';
1431
- $active_email_templates = '';
1432
- }
1433
-
1434
- switch ( $wcar_action ) {
1435
- case WCF_ACTION_SETTINGS:
1436
- $active_settings = 'nav-tab-active';
1437
- break;
1438
- case WCF_ACTION_REPORTS:
1439
- $active_reports = 'nav-tab-active';
1440
- break;
1441
- case WCF_ACTION_EMAIL_TEMPLATES:
1442
- $active_email_templates = 'nav-tab-active';
1443
- break;
1444
- default:
1445
- $active_reports = 'nav-tab-active';
1446
- break;
1447
- }
1448
- // phpcs:disable
1449
- ?>
1450
-
1451
-
1452
- <div class="nav-tab-wrapper woo-nav-tab-wrapper">
1453
-
1454
- <?php
1455
- $url = add_query_arg( array(
1456
- 'page' => WCF_CA_PAGE_NAME,
1457
- 'action' => WCF_ACTION_REPORTS
1458
- ), admin_url( '/admin.php' ) )
1459
- ?>
1460
- <a href="<?php echo $url; ?>"
1461
- class="nav-tab
1462
- <?php
1463
- if ( isset( $active_reports ) ) {
1464
- echo $active_reports;}
1465
- ?>
1466
- ">
1467
- <?php _e( 'Report', 'woo-cart-abandonment-recovery' ); ?>
1468
- </a>
1469
-
1470
- <?php
1471
- $url = add_query_arg( array(
1472
- 'page' => WCF_CA_PAGE_NAME,
1473
- 'action' => WCF_ACTION_EMAIL_TEMPLATES
1474
- ), admin_url( '/admin.php' ) )
1475
- ?>
1476
- <a href="<?php echo $url; ?>"
1477
- class="nav-tab
1478
- <?php
1479
- if ( isset( $active_email_templates ) ) {
1480
- echo $active_email_templates;}
1481
- ?>
1482
- ">
1483
- <?php _e( 'Follow-Up Emails', 'woo-cart-abandonment-recovery' ); ?>
1484
- </a>
1485
-
1486
- <?php
1487
- $url = add_query_arg( array(
1488
- 'page' => WCF_CA_PAGE_NAME,
1489
- 'action' => WCF_ACTION_SETTINGS
1490
- ), admin_url( '/admin.php' ) )
1491
- ?>
1492
- <a href="<?php echo $url; ?>"
1493
- class="nav-tab
1494
- <?php
1495
- if ( isset( $active_settings ) ) {
1496
- echo $active_settings;}
1497
- ?>
1498
- ">
1499
- <?php _e( 'Settings', 'woo-cart-abandonment-recovery' ); ?>
1500
- </a>
1501
-
1502
- </div>
1503
- <?php
1504
- // phpcs:enable
1505
- }
1506
-
1507
- /**
1508
- * Render Cart abandonment settings.
1509
- *
1510
- * @since 1.1.5
1511
- */
1512
- public function wcf_display_settings() {
1513
- ?>
1514
-
1515
- <form method="post" action="options.php">
1516
- <?php settings_fields( WCF_CA_SETTINGS_OPTION_GROUP ); ?>
1517
- <?php do_settings_sections( WCF_CA_PAGE_NAME ); ?>
1518
- <?php submit_button(); ?>
1519
- </form>
1520
-
1521
- <?php
1522
- }
1523
-
1524
- /**
1525
- * Render Cart abandonment reports.
1526
- *
1527
- * @since 1.1.5
1528
- */
1529
- public function wcf_display_reports() {
1530
-
1531
- $filter = filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING );
1532
- $filter_table = filter_input( INPUT_GET, 'filter_table', FILTER_SANITIZE_STRING );
1533
-
1534
- if ( ! $filter ) {
1535
- $filter = 'last_month';
1536
- }
1537
- if ( ! $filter_table ) {
1538
- $filter_table = WCF_CART_ABANDONED_ORDER;
1539
- }
1540
-
1541
- $from_date = filter_input( INPUT_GET, 'from_date', FILTER_SANITIZE_STRING );
1542
- $to_date = filter_input( INPUT_GET, 'to_date', FILTER_SANITIZE_STRING );
1543
- $export_data = filter_input( INPUT_GET, 'export_data', FILTER_VALIDATE_BOOLEAN );
1544
-
1545
- switch ( $filter ) {
1546
-
1547
- case 'yesterday':
1548
- $to_date = gmdate( 'Y-m-d', strtotime( '-1 days' ) );
1549
- $from_date = $to_date;
1550
- break;
1551
- case 'today':
1552
- $to_date = gmdate( 'Y-m-d' );
1553
- $from_date = $to_date;
1554
- break;
1555
- case 'last_week':
1556
- $from_date = gmdate( 'Y-m-d', strtotime( '-7 days' ) );
1557
- $to_date = gmdate( 'Y-m-d' );
1558
- break;
1559
- case 'last_month':
1560
- $from_date = gmdate( 'Y-m-d', strtotime( '-1 months' ) );
1561
- $to_date = gmdate( 'Y-m-d' );
1562
- break;
1563
- case 'custom':
1564
- $to_date = $to_date ? $to_date : gmdate( 'Y-m-d' );
1565
- $from_date = $from_date ? $from_date : $to_date;
1566
- break;
1567
-
1568
- }
1569
-
1570
- $abandoned_report = $this->get_report_by_type( $from_date, $to_date, WCF_CART_ABANDONED_ORDER );
1571
- $recovered_report = $this->get_report_by_type( $from_date, $to_date, WCF_CART_COMPLETED_ORDER );
1572
- $lost_report = $this->get_report_by_type( $from_date, $to_date, WCF_CART_LOST_ORDER );
1573
-
1574
- $wcf_list_table = Cartflows_Ca_Cart_Abandonment_Table::get_instance();
1575
- $wcf_list_table->prepare_items( $filter_table, $from_date, $to_date );
1576
-
1577
- if ( $export_data ) {
1578
-
1579
- $this->download_send_headers();
1580
- echo $this->array2csv( $wcf_list_table->items ); //phpcs:ignore
1581
- die;
1582
-
1583
- }
1584
-
1585
- $conversion_rate = 0;
1586
- $total_orders = ( $recovered_report['no_of_orders'] + $abandoned_report['no_of_orders'] + $lost_report['no_of_orders'] );
1587
- if ( $total_orders ) {
1588
- $conversion_rate = ( $recovered_report['no_of_orders'] / $total_orders ) * 100;
1589
- }
1590
-
1591
- global $woocommerce;
1592
- $conversion_rate = number_format_i18n( $conversion_rate, 2 );
1593
- $currency_symbol = get_woocommerce_currency_symbol();
1594
- require_once CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR . 'includes/admin/cartflows-cart-abandonment-reports.php';
1595
-
1596
- }
1597
-
1598
-
1599
- /**
1600
- * Show report details for specific order.
1601
- */
1602
- public function wcf_display_report_details() {
1603
-
1604
- $sesson_id = filter_input( INPUT_GET, 'session_id', FILTER_SANITIZE_STRING );
1605
-
1606
- if ( $sesson_id ) {
1607
- $details = $this->get_checkout_details( $sesson_id );
1608
- $user_details = (object) unserialize( $details->other_fields );
1609
- $scheduled_emails = $this->fetch_scheduled_emails( $sesson_id );
1610
-
1611
- require_once CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR . 'includes/admin/cartflows-ca-single-report-details.php';
1612
- }
1613
-
1614
- }
1615
-
1616
- /**
1617
- * Check and show warning message if cart abandonment is disabled.
1618
- */
1619
- public function wcf_show_warning_ca() {
1620
- $settings_url = add_query_arg(
1621
- array(
1622
- 'page' => WCF_CA_PAGE_NAME,
1623
- 'action' => WCF_ACTION_SETTINGS,
1624
- ),
1625
- admin_url( '/admin.php' )
1626
- );
1627
-
1628
- if ( ! wcf_ca()->utils->is_cart_abandonment_tracking_enabled() ) {
1629
- ?>
1630
- <div class="notice notice-warning is-dismissible">
1631
- <p>
1632
- <?php echo __('Looks like abandonment tracking is disabled! Please enable it from <a href=' . esc_url($settings_url) . '> <strong>settings</strong></a>.', 'woo-cart-abandonment-recovery'); // phpcs:ignore
1633
- ?>
1634
- </p>
1635
- </div>
1636
- <?php
1637
- }
1638
- }
1639
-
1640
- /**
1641
- * Callback trigger event to send the emails.
1642
- */
1643
- public function send_emails_to_callback() {
1644
-
1645
- global $wpdb;
1646
- $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
1647
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1648
- $email_template_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_TEMPLATE_TABLE;
1649
-
1650
- $current_time = current_time( WCF_CA_DATETIME_FORMAT );
1651
- // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
1652
- $emails_send_to = $wpdb->get_results(
1653
- $wpdb->prepare(
1654
- 'SELECT *, EHT.id as email_history_id, ETT.id as email_template_id FROM ' . $email_history_table . ' as EHT
1655
- INNER JOIN ' . $cart_abandonment_table . ' as CAT ON EHT.`ca_session_id` = CAT.`session_id`
1656
- INNER JOIN ' . $email_template_table . ' as ETT ON ETT.`id` = EHT.`template_id`
1657
- WHERE CAT.`order_status` = %s AND CAT.unsubscribed = 0 AND EHT.`email_sent` = 0 AND EHT.`scheduled_time` <= %s',
1658
- WCF_CART_ABANDONED_ORDER,
1659
- $current_time
1660
- )
1661
- );
1662
- // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
1663
- foreach ( $emails_send_to as $email_send_to ) {
1664
- $email_result = $this->send_email_templates( $email_send_to );
1665
- if ( $email_result ) {
1666
- $wpdb->update(
1667
- $email_history_table,
1668
- array( 'email_sent' => true ),
1669
- array( 'id' => $email_send_to->email_history_id )
1670
- );
1671
- }
1672
- }
1673
- }
1674
-
1675
-
1676
- /**
1677
- * Create a dummy object for the preview email.
1678
- *
1679
- * @return stdClass
1680
- */
1681
- public function create_dummy_session_for_preview_email() {
1682
-
1683
- $email_data = new stdClass();
1684
- $current_user = wp_get_current_user();
1685
- $email_data->email_template_id = null;
1686
- $email_data->checkout_id = wc_get_page_id( 'checkout' );
1687
- $email_data->session_id = 'dummy-session-id';
1688
- $email_send_to = filter_input( INPUT_POST, 'email_send_to', FILTER_SANITIZE_EMAIL );
1689
- $email_data->email = $email_send_to ? $email_send_to : $current_user->user_email;
1690
- $email_data->email_body = filter_input( INPUT_POST, 'email_body', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
1691
- $email_data->email_subject = filter_input( INPUT_POST, 'email_subject', FILTER_SANITIZE_STRING );
1692
- $email_data->email_body = html_entity_decode( $email_data->email_body, ENT_COMPAT, 'UTF-8' );
1693
- $email_data->other_fields = serialize(
1694
- array(
1695
- 'wcf_first_name' => $current_user->user_firstname,
1696
- 'wcf_last_name' => $current_user->user_lastname,
1697
- )
1698
- );
1699
- if ( ! WC()->cart->get_cart_contents_count() ) {
1700
- $args = array(
1701
- 'posts_per_page' => 1,
1702
- 'orderby' => 'rand',
1703
- 'post_type' => 'product',
1704
- 'meta_query' => array( //phpcs:ignore
1705
- // Exclude out of stock products.
1706
- array(
1707
- 'key' => '_stock_status',
1708
- 'value' => 'outofstock',
1709
- 'compare' => 'NOT IN',
1710
- ),
1711
- ),
1712
- 'tax_query' => array( //phpcs:ignore
1713
- array(
1714
- 'taxonomy' => 'product_type',
1715
- 'field' => 'slug',
1716
- 'terms' => 'simple',
1717
- ),
1718
- ),
1719
- );
1720
-
1721
- $random_products = get_posts( $args );
1722
- if ( ! empty( $random_products ) ) {
1723
- $random_product = reset( $random_products );
1724
- WC()->cart->add_to_cart( $random_product->ID );
1725
- }
1726
- }
1727
-
1728
- $email_data->cart_total = WC()->cart->total + floatval( WC()->cart->get_cart_shipping_total() );
1729
- $email_data->cart_contents = serialize( WC()->cart->get_cart() );
1730
- $email_data->time = current_time( WCF_CA_DATETIME_FORMAT );
1731
- return $email_data;
1732
- }
1733
-
1734
- /**
1735
- * Callback function to send email templates.
1736
- *
1737
- * @param array $email_data email data .
1738
- * @param boolean $preview_email preview email.
1739
- * @since 1.0.0
1740
- */
1741
- public function send_email_templates( $email_data, $preview_email = false ) {
1742
-
1743
- if ( $preview_email ) {
1744
- $email_data = $this->create_dummy_session_for_preview_email();
1745
- }
1746
-
1747
- if ( filter_var( $email_data->email, FILTER_VALIDATE_EMAIL ) ) {
1748
- if ( ! $preview_email ) {
1749
- if ( ! $this->check_if_already_purchased_by_email_product_ids( $email_data, $email_data->cart_contents ) ) {
1750
- return false;
1751
- }
1752
- }
1753
-
1754
- $other_fields = unserialize( $email_data->other_fields );
1755
-
1756
- $from_email_name = get_option( 'wcf_ca_from_name' );
1757
- $reply_name_preview = get_option( 'wcf_ca_reply_email' );
1758
- $from_email_preview = get_option( 'wcf_ca_from_email' );
1759
-
1760
- $user_first_name = ucfirst( $other_fields['wcf_first_name'] );
1761
- $user_first_name = $user_first_name ? $user_first_name : __( 'there', 'woo-cart-abandonment-recovery' );
1762
- $user_last_name = ucfirst( $other_fields['wcf_last_name'] );
1763
- $user_full_name = trim( $user_first_name . ' ' . $user_last_name );
1764
-
1765
- $subject_email_preview = stripslashes( html_entity_decode( $email_data->email_subject, ENT_QUOTES, 'UTF-8' ) );
1766
- $subject_email_preview = convert_smilies( $subject_email_preview );
1767
- $subject_email_preview = str_replace( '{{customer.firstname}}', $user_first_name, $subject_email_preview );
1768
- $body_email_preview = convert_smilies( $email_data->email_body );
1769
- $body_email_preview = str_replace( '{{customer.firstname}}', $user_first_name, $body_email_preview );
1770
- $body_email_preview = str_replace( '{{customer.lastname}}', $user_last_name, $body_email_preview );
1771
- $body_email_preview = str_replace( '{{customer.fullname}}', $user_full_name, $body_email_preview );
1772
-
1773
- $email_instance = Cartflows_Ca_Email_Templates::get_instance();
1774
- if ( $preview_email ) {
1775
- $coupon_code = 'DUMMY-COUPON';
1776
- } else {
1777
- $override_global_coupon = $email_instance->get_email_template_meta_by_key( $email_data->email_template_id, 'override_global_coupon' );
1778
- if ( $override_global_coupon->meta_value ) {
1779
- $email_history = $email_instance->get_email_history_by_id( $email_data->email_history_id );
1780
- $coupon_code = $email_history->coupon_code;
1781
- } else {
1782
- $coupon_code = $email_data->coupon_code;
1783
- }
1784
- }
1785
-
1786
- $auto_apply_coupon = $email_instance->get_email_template_meta_by_key( $email_data->email_template_id, 'auto_coupon' );
1787
-
1788
- $token_data = array(
1789
- 'wcf_session_id' => $email_data->session_id,
1790
- 'wcf_coupon_code' => isset( $auto_apply_coupon ) && $auto_apply_coupon->meta_value ? $coupon_code : null,
1791
- 'wcf_preview_email' => $preview_email ? true : false,
1792
- );
1793
-
1794
- $checkout_url = $this->get_checkout_url( $email_data->checkout_id, $token_data );
1795
-
1796
- $body_email_preview = str_replace( '{{cart.coupon_code}}', $coupon_code, $body_email_preview );
1797
-
1798
- $current_time_stamp = $email_data->time;
1799
- $body_email_preview = str_replace( '{{cart.abandoned_date}}', $current_time_stamp, $body_email_preview );
1800
- $unsubscribe_element = '<a target="_blank" style="color: lightgray" href="' . $checkout_url . '&unsubscribe=true ">' . __( 'Unsubscribe', 'woo-cart-abandonment-recovery' ) . '</a>';
1801
- $body_email_preview = str_replace( '{{cart.unsubscribe}}', $unsubscribe_element, $body_email_preview );
1802
- $body_email_preview = str_replace( 'http://{{cart.checkout_url}}', '{{cart.checkout_url}}', $body_email_preview );
1803
- $body_email_preview = str_replace( 'https://{{cart.checkout_url}}', '{{cart.checkout_url}}', $body_email_preview );
1804
- $body_email_preview = str_replace( '{{cart.checkout_url}}', $checkout_url, $body_email_preview );
1805
- $host = wp_parse_url( get_site_url() );
1806
- $body_email_preview = str_replace( '{{site.url}}', $host['host'], $body_email_preview );
1807
-
1808
- if ( false !== strpos( $body_email_preview, '{{cart.product.names}}' ) ) {
1809
- $body_email_preview = str_replace( '{{cart.product.names}}', $this->get_comma_separated_products( $email_data->cart_contents ), $body_email_preview );
1810
- }
1811
-
1812
- $admin_user = get_users(
1813
- array(
1814
- 'role' => 'Administrator',
1815
- 'number' => 1,
1816
- )
1817
- );
1818
- $admin_user = reset( $admin_user );
1819
- $admin_first_name = $admin_user->user_firstname ? $admin_user->user_firstname : 'Admin';
1820
- $body_email_preview = str_replace( '{{admin.firstname}}', $admin_first_name, $body_email_preview );
1821
- $body_email_preview = str_replace( '{{admin.company}}', get_bloginfo( 'name' ), $body_email_preview );
1822
-
1823
- $headers = 'From: ' . $from_email_name . ' <' . $from_email_preview . '>' . "\r\n";
1824
- $headers .= 'Content-Type: text/html' . "\r\n";
1825
- $headers .= 'Reply-To: ' . $reply_name_preview . ' ' . "\r\n";
1826
- $var = $this->get_email_product_block( $email_data->cart_contents, $email_data->cart_total );
1827
-
1828
- $body_email_preview = str_replace( '{{cart.product.table}}', $var, $body_email_preview );
1829
- $body_email_preview = wpautop( $body_email_preview );
1830
- $mail_result = wp_mail( $email_data->email, $subject_email_preview, stripslashes( $body_email_preview ), $headers );
1831
- if ( $mail_result ) {
1832
- return true;
1833
- } else {
1834
- // Retry sending mail.
1835
- $mail_result = wp_mail( $email_data->email, $subject_email_preview, stripslashes( $body_email_preview ), $headers );
1836
- if ( ! $preview_email ) {
1837
- return true;
1838
- }
1839
- return false;
1840
- }
1841
- } else {
1842
- return false;
1843
- }
1844
-
1845
- }
1846
-
1847
- /**
1848
- * Generate comma separated products.
1849
- *
1850
- * @param object $cart_contents user cart details.
1851
- */
1852
- public function get_comma_separated_products( $cart_contents ) {
1853
- $cart_comma_string = '';
1854
- if ( ! $cart_contents ) {
1855
- return $cart_comma_string;
1856
- }
1857
- $cart_data = unserialize( $cart_contents );
1858
-
1859
- $cart_length = count( $cart_data );
1860
- $index = 0;
1861
- foreach ( $cart_data as $key => $product ) {
1862
-
1863
- if ( ! isset( $product['product_id'] ) ) {
1864
- continue;
1865
- }
1866
-
1867
- $cart_product = wc_get_product( $product['product_id'] );
1868
-
1869
- if ( $cart_product ) {
1870
- $cart_comma_string = $cart_comma_string . $cart_product->get_title();
1871
- if ( ( $cart_length - 2 ) === $index ) {
1872
- $cart_comma_string = $cart_comma_string . ' & ';
1873
- } elseif ( ( $cart_length - 1 ) !== $index ) {
1874
- $cart_comma_string = $cart_comma_string . ', ';
1875
- }
1876
- $index++;
1877
- }
1878
- }
1879
- return $cart_comma_string;
1880
-
1881
- }
1882
-
1883
- /**
1884
- * Generate the view for email product cart block.
1885
- *
1886
- * @param object $cart_contents user cart contents details.
1887
- * @param float $cart_total user cart total.
1888
- * @return string
1889
- */
1890
- public function get_email_product_block( $cart_contents, $cart_total ) {
1891
-
1892
- $cart_items = unserialize( $cart_contents );
1893
-
1894
- if ( ! is_array( $cart_items ) || ! count( $cart_items ) ) {
1895
- return;
1896
- }
1897
-
1898
- $currency_symbol = get_woocommerce_currency_symbol();
1899
- $tr = '';
1900
- $style = array(
1901
- 'product_image' => array(
1902
- 'style' => 'height: 42px; width: 42px;',
1903
- ),
1904
- 'table' => array(
1905
- 'style' => 'color: #636363; border: 1px solid #e5e5e5;',
1906
- 'attribute' => 'align= left;',
1907
- ),
1908
- );
1909
-
1910
- $style_filter = apply_filters( 'woo_ca_email_template_table_style', $style );
1911
- $product_image_style = isset( $style_filter['product_image']['style'] ) ? $style_filter['product_image']['style'] : '';
1912
- $style = isset( $style_filter['table']['style'] ) ? $style_filter['table']['style'] : '';
1913
-
1914
- foreach ( $cart_items as $cart_item ) {
1915
-
1916
- if ( isset( $cart_item['product_id'] ) && isset( $cart_item['quantity'] ) && isset( $cart_item['line_total'] ) ) {
1917
- $id = 0 !== $cart_item['variation_id'] ? $cart_item['variation_id'] : $cart_item['product_id'];
1918
- $image_url = get_the_post_thumbnail_url( $id );
1919
- $image_url = ! empty( $image_url ) ? $image_url : get_the_post_thumbnail_url( $cart_item['product_id'] );
1920
-
1921
- $product = wc_get_product( $id );
1922
- $product_name = $product ? $product->get_formatted_name() : '';
1923
-
1924
- if ( empty( $image_url ) ) {
1925
- $image_url = CARTFLOWS_CA_URL . '/assets/images/image-placeholder.png';
1926
- }
1927
- $tr = $tr . '<tr style=' . $style . ' align="center">
1928
- <td style="' . $style . '"><img class="demo_img" style="' . $product_image_style . '" src="' . esc_url( $image_url ) . '"></td>
1929
- <td style="' . $style . '">' . $product_name . '</td>
1930
- <td style="' . $style . '"> ' . $cart_item['quantity'] . ' </td>
1931
- <td style="' . $style . '">' . $currency_symbol . number_format_i18n( $cart_item['line_total'], 2 ) . '</td>
1932
- <td style="' . $style . '" >' . $currency_symbol . number_format_i18n( $cart_item['line_total'], 2 ) . '</td>
1933
- </tr> ';
1934
- }
1935
- }
1936
-
1937
- /**
1938
- * Add filter to toggle the Cart Total row.
1939
- */
1940
- $enable_cart_total = apply_filters( 'woo_ca_recovery_enable_cart_total', false );
1941
- if ( $enable_cart_total ) {
1942
- $tr = $tr . '<tr style="' . $style . '" align="center">
1943
- <td colspan="4" style="' . $style . '"> ' . __( 'Cart Total ( Cart Total + Shipping + Tax )', 'woo-cart-abandonment-recovery' ) . ' </td>
1944
- <td style="' . $style . '" >' . $currency_symbol . number_format_i18n( $cart_total, 2 ) . '</td>
1945
- </tr> ';
1946
- }
1947
-
1948
- return '<table ' . $style_filter['table']['attribute'] . ' cellpadding="10" cellspacing="0" style="float: none; border: 1px solid #e5e5e5;">
1949
- <tr align="center">
1950
- <th style="' . $style . '">' . __( 'Item', 'woo-cart-abandonment-recovery' ) . '</th>
1951
- <th style="' . $style . '">' . __( 'Name', 'woo-cart-abandonment-recovery' ) . '</th>
1952
- <th style="' . $style . '">' . __( 'Quantity', 'woo-cart-abandonment-recovery' ) . '</th>
1953
- <th style="' . $style . '">' . __( 'Price', 'woo-cart-abandonment-recovery' ) . '</th>
1954
- <th style="' . $style . '">' . __( 'Line Subtotal', 'woo-cart-abandonment-recovery' ) . '</th>
1955
- </tr> ' . $tr . '
1956
- </table>';
1957
- }
1958
-
1959
- /**
1960
- * Generate the view for admin product cart block.
1961
- *
1962
- * @param object $cart_contents user cart contents details.
1963
- * @param float $cart_total user cart total.
1964
- * @return string
1965
- */
1966
- public function get_admin_product_block( $cart_contents, $cart_total ) {
1967
-
1968
- $cart_items = unserialize( $cart_contents );
1969
-
1970
- if ( ! is_array( $cart_items ) || ! count( $cart_items ) ) {
1971
- return;
1972
- }
1973
-
1974
- $currency_symbol = get_woocommerce_currency_symbol();
1975
- $tr = '';
1976
- $total = 0;
1977
- $discount = 0;
1978
- $tax = 0;
1979
-
1980
- foreach ( $cart_items as $cart_item ) {
1981
-
1982
- if ( isset( $cart_item['product_id'] ) && isset( $cart_item['quantity'] ) && isset( $cart_item['line_total'] ) && isset( $cart_item['line_subtotal'] ) ) {
1983
- $id = 0 !== $cart_item['variation_id'] ? $cart_item['variation_id'] : $cart_item['product_id'];
1984
- $discount = number_format_i18n( $discount + ( $cart_item['line_subtotal'] - $cart_item['line_total'] ), 2 );
1985
- $total = number_format_i18n( $total + $cart_item['line_subtotal'], 2 );
1986
- $tax = number_format_i18n( $tax + $cart_item['line_tax'], 2 );
1987
- $image_url = get_the_post_thumbnail_url( $id );
1988
- $image_url = ! empty( $image_url ) ? $image_url : get_the_post_thumbnail_url( $cart_item['product_id'] );
1989
-
1990
- $product = wc_get_product( $id );
1991
- $product_name = $product ? $product->get_formatted_name() : '';
1992
-
1993
- if ( empty( $image_url ) ) {
1994
- $image_url = CARTFLOWS_CA_URL . '/assets/images/image-placeholder.png';
1995
- }
1996
- $tr = $tr . '<tr align="center">
1997
- <td ><img class="demo_img" width="42" height="42" src=" ' . esc_url( $image_url ) . ' "/></td>
1998
- <td >' . $product_name . '</td>
1999
- <td > ' . $cart_item['quantity'] . ' </td>
2000
- <td >' . $currency_symbol . number_format_i18n( $cart_item['line_total'], 2 ) . '</td>
2001
- <td >' . $currency_symbol . number_format_i18n( $cart_item['line_total'], 2 ) . '</td>
2002
- </tr> ';
2003
- }
2004
- }
2005
-
2006
- return '<table align="left" cellspacing="0" class="widefat fixed striped posts">
2007
- <thead>
2008
- <tr align="center">
2009
- <th >' . __( 'Item', 'woo-cart-abandonment-recovery' ) . '</th>
2010
- <th >' . __( 'Name', 'woo-cart-abandonment-recovery' ) . '</th>
2011
- <th >' . __( 'Quantity', 'woo-cart-abandonment-recovery' ) . '</th>
2012
- <th >' . __( 'Price', 'woo-cart-abandonment-recovery' ) . '</th>
2013
- <th >' . __( 'Line Subtotal', 'woo-cart-abandonment-recovery' ) . '</th>
2014
- </tr>
2015
- </thead>
2016
- <tbody>
2017
- ' . $tr . '
2018
- <tr align="center" id="wcf-ca-discount">
2019
- <td colspan="4" >' . __( 'Discount', 'woo-cart-abandonment-recovery' ) . '</td>
2020
- <td>' . $currency_symbol . ( $discount ) . '</td>
2021
- </tr>
2022
- <tr align="center" id="wcf-ca-other">
2023
- <td colspan="4" >' . __( 'Other', 'woo-cart-abandonment-recovery' ) . '</td>
2024
- <td>' . $currency_symbol . ( $tax ) . '</td>
2025
- </tr>
2026
-
2027
- <tr align="center" id="wcf-ca-shipping">
2028
- <td colspan="4" >' . __( 'Shipping', 'woo-cart-abandonment-recovery' ) . '</td>
2029
- <td>' . $currency_symbol . number_format_i18n( $discount + ( $cart_total - $total ) - $tax, 2 ) . '</td>
2030
- </tr>
2031
- <tr align="center" id="wcf-ca-cart-total">
2032
- <td colspan="4" >' . __( 'Cart Total', 'woo-cart-abandonment-recovery' ) . '</td>
2033
- <td>' . $currency_symbol . $cart_total . '</td>
2034
- </tr>
2035
- </tbody>
2036
- </table>';
2037
- }
2038
-
2039
- /**
2040
- * Schedule events for the abadoned carts to send emails.
2041
- *
2042
- * @param integer $session_id user session id.
2043
- * @param boolean $force_reschedule force reschedule.
2044
- */
2045
- public function schedule_emails( $session_id, $force_reschedule = false ) {
2046
-
2047
- $checkout_details = $this->get_checkout_details( $session_id );
2048
-
2049
- if ( ( $checkout_details->unsubscribed ) || ( WCF_CART_COMPLETED_ORDER === $checkout_details->order_status ) ) {
2050
- return;
2051
- }
2052
-
2053
- $scheduled_emails = $this->fetch_scheduled_emails( $session_id );
2054
- $scheduled_templates = array_column( $scheduled_emails, 'template_id' ); //phpcs:ignore
2055
- $scheduled_time_from = $checkout_details->time;
2056
-
2057
- if ( $force_reschedule ) {
2058
- $scheduled_time_from = current_time( WCF_CA_DATETIME_FORMAT );
2059
- }
2060
-
2061
- $email_tmpl = Cartflows_Ca_Email_Templates::get_instance();
2062
- $templates = $email_tmpl->fetch_all_active_templates();
2063
-
2064
- global $wpdb;
2065
-
2066
- $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
2067
-
2068
- foreach ( $templates as $template ) {
2069
-
2070
- if ( false !== array_search( $template->id, $scheduled_templates, true ) && false === $force_reschedule ) {
2071
- continue;
2072
- }
2073
-
2074
- $timestamp_str = '+' . $template->frequency . ' ' . $template->frequency_unit . 'S';
2075
- $scheduled_time = gmdate( WCF_CA_DATETIME_FORMAT, strtotime( $scheduled_time_from . $timestamp_str ) );
2076
- $discount_type = $email_tmpl->get_email_template_meta_by_key( $template->id, 'discount_type' );
2077
- $discount_type = isset( $discount_type->meta_value ) ? $discount_type->meta_value : '';
2078
- $amount = $email_tmpl->get_email_template_meta_by_key( $template->id, 'coupon_amount' );
2079
- $amount = isset( $amount->meta_value ) ? $amount->meta_value : '';
2080
-
2081
- $coupon_expiry_date = $email_tmpl->get_email_template_meta_by_key( $template->id, 'coupon_expiry_date' );
2082
- $coupon_expiry_unit = $email_tmpl->get_email_template_meta_by_key( $template->id, 'coupon_expiry_unit' );
2083
- $coupon_expiry_date = isset( $coupon_expiry_date->meta_value ) ? $coupon_expiry_date->meta_value : '';
2084
- $coupon_expiry_unit = isset( $coupon_expiry_unit->meta_value ) ? $coupon_expiry_unit->meta_value : 'hours';
2085
-
2086
- $coupon_expiry_date = $coupon_expiry_date ? strtotime( $scheduled_time . ' +' . $coupon_expiry_date . ' ' . $coupon_expiry_unit ) : '';
2087
-
2088
- $free_shipping_coupon = $email_tmpl->get_email_template_meta_by_key( $template->id, 'free_shipping_coupon' );
2089
- $free_shipping = ( isset( $free_shipping_coupon ) && ( $free_shipping_coupon->meta_value ) ) ? 'yes' : 'no';
2090
-
2091
- $individual_use_only = $email_tmpl->get_email_template_meta_by_key( $template->id, 'individual_use_only' );
2092
- $individual_use = ( isset( $individual_use_only ) && ( $individual_use_only->meta_value ) ) ? 'yes' : 'no';
2093
-
2094
- $override_global_coupon = $email_tmpl->get_email_template_meta_by_key( $template->id, 'override_global_coupon' );
2095
-
2096
- $new_coupon_code = '';
2097
- if ( $override_global_coupon->meta_value ) {
2098
- $new_coupon_code = $this->generate_coupon_code( $discount_type, $amount, $coupon_expiry_date, $free_shipping, $individual_use );
2099
- }
2100
-
2101
- $wpdb->replace(
2102
- $email_history_table,
2103
- array(
2104
- 'template_id' => $template->id,
2105
- 'ca_session_id' => $checkout_details->session_id,
2106
- 'coupon_code' => $new_coupon_code,
2107
- 'scheduled_time' => $scheduled_time,
2108
- )
2109
- );
2110
- }
2111
- }
2112
-
2113
- /**
2114
- * Fetch all the scheduled emails with templates for the specific session.
2115
- *
2116
- * @param string $session_id session id.
2117
- * @param boolean $fetch_sent sfetch sent emails.
2118
- * @return array|object|null
2119
- */
2120
- public function fetch_scheduled_emails( $session_id, $fetch_sent = false ) {
2121
- global $wpdb;
2122
- $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
2123
- $email_template_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_TEMPLATE_TABLE;
2124
-
2125
- $query = $wpdb->prepare("SELECT * FROM $email_history_table as eht INNER JOIN $email_template_table as ett ON eht.template_id = ett.id WHERE ca_session_id = %s", sanitize_text_field($session_id)); // phpcs:ignore
2126
-
2127
- if ( $fetch_sent ) {
2128
- $query .= ' AND email_sent = 1';
2129
- }
2130
-
2131
- $result = $wpdb->get_results( $query ); // phpcs:ignore
2132
- return $result;
2133
- }
2134
-
2135
- /**
2136
- * Delete orders from cart abandonment table whose cart total is zero and order status is abandoned.
2137
- */
2138
- public function delete_empty_abandoned_order() {
2139
- global $wpdb;
2140
-
2141
- $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
2142
-
2143
- $where = array(
2144
- 'cart_total' => 0,
2145
- );
2146
-
2147
- $wpdb->delete( $cart_abandonment_table, $where );
2148
- }
2149
-
2150
-
2151
- /**
2152
- * Check if transient is set for delete garbage coupons.
2153
- */
2154
- public function delete_used_and_expired_coupons() {
2155
- $is_ajax_request = wp_doing_ajax();
2156
- $is_transient_set = false;
2157
- global $wpdb;
2158
- if ( $is_ajax_request ) {
2159
- check_ajax_referer( 'wcf_ca_delete_garbage_coupons', 'security' );
2160
- } else {
2161
- $is_transient_set = get_transient( 'woocommerce_ca_delete_garbage_coupons' );
2162
- }
2163
-
2164
- if ( false === $is_transient_set || $is_ajax_request ) {
2165
- $coupons = $this->delete_garbage_coupons();
2166
- $coupon_count = count( $coupons );
2167
-
2168
- if ( $coupon_count ) {
2169
- $coupons_post_ids = implode( ',', wp_list_pluck( $coupons, 'ID' ) );
2170
- $wpdb->query( "DELETE FROM {$wpdb->prefix}postmeta WHERE post_id IN(" . $coupons_post_ids . ')' );//phpcs:ignore
2171
- $wpdb->query( "DELETE FROM {$wpdb->prefix}posts WHERE ID IN(" . $coupons_post_ids . ')' );//phpcs:ignore
2172
- }
2173
-
2174
- if ( ! $is_ajax_request ) {
2175
- set_transient( 'woocommerce_ca_delete_garbage_coupons', $coupons, WEEK_IN_SECONDS );
2176
- return;
2177
- }
2178
-
2179
- // translators: %1$s: Coupons Deleted, %2$s: Deleted coupons count'.
2180
- wp_send_json_success( sprintf( __( '%1$s: %2$d', 'woo-cart-abandonment-recovery' ), 'Coupons Deleted', $coupon_count ) );
2181
-
2182
- }
2183
- }
2184
-
2185
-
2186
- /**
2187
- * Set transient and delete garbage coupons.
2188
- */
2189
- public function delete_garbage_coupons() {
2190
-
2191
- global $wpdb;
2192
-
2193
- $coupon_generated_by = WCF_CA_COUPON_GENERATED_BY;
2194
- $timestamp = time();
2195
- $post_type = 'shop_coupon';
2196
- $coupons = $wpdb->get_results(
2197
- $wpdb->prepare(
2198
- "SELECT ID, coupon_code, usage_limit, total_usaged, expiry_date FROM (
2199
- SELECT p.ID,
2200
- p.post_title AS coupon_code,
2201
- Max(CASE WHEN pm.meta_key = 'date_expires' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS expiry_date,
2202
- Max(CASE WHEN pm.meta_key = 'usage_limit' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS usage_limit,
2203
- Max(CASE WHEN pm.meta_key = 'usage_count' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS total_usaged,
2204
-
2205
- Max(CASE WHEN pm.meta_key = 'coupon_generated_by' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS coupon_generated_by
2206
- FROM {$wpdb->prefix}posts AS p
2207
- INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
2208
- WHERE p.`post_type` = %s
2209
-
2210
- GROUP BY p.ID
2211
- ) AS final_res WHERE coupon_generated_by IS NOT NULL AND coupon_generated_by = %s AND ( ( usage_limit = total_usaged ) OR ( expiry_date <= %d AND expiry_date != '') )",
2212
- $post_type,
2213
- $coupon_generated_by,
2214
- $timestamp
2215
- )
2216
- );
2217
- return $coupons;
2218
- }
2219
-
2220
- /**
2221
- * Send headers to export orders to csv format.
2222
- */
2223
- private function download_send_headers() {
2224
- $now = gmdate( 'Y-m-d-H-i-s' );
2225
- $filename = 'woo-cart-abandonment-recovery-export-' . $now . '.csv';
2226
-
2227
- header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
2228
- header( "Last-Modified: {$now} GMT" );
2229
-
2230
- // force download.
2231
- header( 'Content-Type: application/force-download' );
2232
- header( 'Content-Type: application/octet-stream' );
2233
- header( 'Content-Type: application/download' );
2234
-
2235
- // disposition / encoding on response body.
2236
- header( "Content-Disposition: attachment;filename={$filename}" );
2237
- header( 'Content-Transfer-Encoding: binary' );
2238
- }
2239
-
2240
- /**
2241
- * Convert users data to csv format.
2242
- *
2243
- * @param array $user_data users data.
2244
- */
2245
- private function array2csv( array $user_data ) {
2246
- if ( empty( $user_data ) ) {
2247
- return;
2248
- }
2249
- ob_clean();
2250
- ob_start();
2251
- $data_file = fopen( 'php://output', 'w' );
2252
- fputcsv(
2253
- $data_file,
2254
- array(
2255
- 'First-Name',
2256
- 'Last-Name',
2257
- 'Email',
2258
- 'Phone',
2259
- 'Products',
2260
- 'Cart-Total in ' . get_woocommerce_currency(),
2261
- 'Order-Status',
2262
- 'Unsubscribed',
2263
- 'Coupon-Code',
2264
- )
2265
- );
2266
- foreach ( $user_data as $data ) {
2267
- $name = unserialize( $data['other_fields'] );
2268
- $checkout_details = $this->get_checkout_details( $data['session_id'] );
2269
- $cart_data = $this->get_comma_separated_products( $checkout_details->cart_contents );
2270
- fputcsv(
2271
- $data_file,
2272
- array(
2273
- $name['wcf_first_name'],
2274
- $name['wcf_last_name'],
2275
- $data['email'],
2276
- $name['wcf_phone_number'],
2277
- $cart_data,
2278
- $data['cart_total'],
2279
- $data['order_status'],
2280
- $data['unsubscribed'] ? 'Yes' : 'No',
2281
- $data['coupon_code'],
2282
- )
2283
- );
2284
-
2285
- }
2286
- fclose( $data_file ); //phpcs:ignore
2287
- return ob_get_clean();
2288
- }
2289
- }
2290
-
2291
- Cartflows_Ca_Cart_Abandonment::get_instance();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/cart-abandonment/classes/class-cartflows-ca-cron.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cart Abandonment
4
+ *
5
+ * @package Woocommerce-Cart-Abandonment-Recovery
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ /**
13
+ * Cart abandonment tracking class.
14
+ */
15
+ class Cartflows_Ca_Cron {
16
+
17
+
18
+
19
+ /**
20
+ * Member Variable
21
+ *
22
+ * @var object instance
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ */
29
+ public static function get_instance() {
30
+ if ( ! isset( self::$instance ) ) {
31
+ self::$instance = new self();
32
+ }
33
+ return self::$instance;
34
+ }
35
+
36
+ /**
37
+ * Constructor function that initializes required actions and hooks.
38
+ */
39
+ public function __construct() {
40
+ add_filter( 'cron_schedules', array( $this, 'cartflows_ca_update_order_status_action' ) ); //phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
41
+
42
+ // Schedule an action if it's not already scheduled.
43
+ if ( ! wp_next_scheduled( 'cartflows_ca_update_order_status_action' ) ) {
44
+ wp_schedule_event( time(), 'every_fifteen_minutes', 'cartflows_ca_update_order_status_action' );
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Create custom schedule.
50
+ *
51
+ * @param array $schedules schedules.
52
+ * @return mixed
53
+ */
54
+ public function cartflows_ca_update_order_status_action( $schedules ) {
55
+
56
+ /**
57
+ * Add filter to change the cron interval time to uodate order status.
58
+ */
59
+ $cron_time = apply_filters( 'woo_ca_update_order_cron_interval', get_option( 'wcf_ca_cron_run_time', 15 ) );
60
+
61
+ $schedules['every_fifteen_minutes'] = array(
62
+ 'interval' => intval( $cron_time ) * MINUTE_IN_SECONDS,
63
+ 'display' => __( 'Every Fifteen Minutes', 'woo-cart-abandonment-recovery' ),
64
+ );
65
+
66
+ return $schedules;
67
+ }
68
+ }
69
+
70
+ Cartflows_Ca_Cron::get_instance();
modules/cart-abandonment/{class-cartflows-ca-cart-abandonment-db.php → classes/class-cartflows-ca-database.php} RENAMED
@@ -12,7 +12,7 @@ if ( ! defined( 'ABSPATH' ) ) {
12
  /**
13
  * Cart Abandonment DB class.
14
  */
15
- class Cartflows_Ca_Cart_Abandonment_Db {
16
 
17
 
18
 
@@ -242,4 +242,4 @@ class Cartflows_Ca_Cart_Abandonment_Db {
242
  }
243
  }
244
 
245
- Cartflows_Ca_Cart_Abandonment_Db::get_instance();
12
  /**
13
  * Cart Abandonment DB class.
14
  */
15
+ class Cartflows_Ca_Database {
16
 
17
 
18
 
242
  }
243
  }
244
 
245
+ Cartflows_Ca_Database::get_instance();
modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cart Abandonment
4
+ *
5
+ * @package Woocommerce-Cart-Abandonment-Recovery
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ /**
13
+ * Cart abandonment tracking class.
14
+ */
15
+ class Cartflows_Ca_Email_Schedule {
16
+
17
+
18
+
19
+ /**
20
+ * Member Variable
21
+ *
22
+ * @var object instance
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ */
29
+ public static function get_instance() {
30
+ if ( ! isset( self::$instance ) ) {
31
+ self::$instance = new self();
32
+ }
33
+ return self::$instance;
34
+ }
35
+
36
+ /**
37
+ * Constructor function that initializes required actions and hooks.
38
+ */
39
+ public function __construct() {
40
+
41
+ add_action( 'wp_ajax_wcf_ca_preview_email_send', array( $this, 'send_preview_email' ) );
42
+ }
43
+
44
+ /**
45
+ * Send preview emails.
46
+ */
47
+ public function send_preview_email() {
48
+
49
+ check_ajax_referer( WCF_EMAIL_TEMPLATES_NONCE, 'security' );
50
+ $mail_result = $this->send_email_templates( null, true );
51
+ if ( $mail_result ) {
52
+ wp_send_json_success( __( 'Mail has been sent successfully!', 'woo-cart-abandonment-recovery' ) );
53
+ } else {
54
+ wp_send_json_error( __( 'Mail sending failed!', 'woo-cart-abandonment-recovery' ) );
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Callback function to send email templates.
60
+ *
61
+ * @param array $email_data email data .
62
+ * @param boolean $preview_email preview email.
63
+ * @since 1.0.0
64
+ */
65
+ public function send_email_templates( $email_data, $preview_email = false ) {
66
+
67
+ if ( $preview_email ) {
68
+ $email_data = $this->create_dummy_session_for_preview_email();
69
+ }
70
+
71
+ if ( filter_var( $email_data->email, FILTER_VALIDATE_EMAIL ) ) {
72
+ if ( ! $preview_email ) {
73
+ if ( ! $this->check_if_already_purchased_by_email_product_ids( $email_data, $email_data->cart_contents ) ) {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ $other_fields = unserialize( $email_data->other_fields );
79
+
80
+ $from_email_name = get_option( 'wcf_ca_from_name' );
81
+ $reply_name_preview = get_option( 'wcf_ca_reply_email' );
82
+ $from_email_preview = get_option( 'wcf_ca_from_email' );
83
+
84
+ $user_first_name = ucfirst( $other_fields['wcf_first_name'] );
85
+ $user_first_name = $user_first_name ? $user_first_name : apply_filters( 'woo_ca_default_first_name', __( 'there', 'woo-cart-abandonment-recovery' ) );
86
+ $user_last_name = ucfirst( $other_fields['wcf_last_name'] );
87
+ $user_full_name = trim( $user_first_name . ' ' . $user_last_name );
88
+
89
+ $subject_email_preview = stripslashes( html_entity_decode( $email_data->email_subject, ENT_QUOTES, 'UTF-8' ) );
90
+ $subject_email_preview = convert_smilies( $subject_email_preview );
91
+ $subject_email_preview = str_replace( '{{customer.firstname}}', $user_first_name, $subject_email_preview );
92
+ $body_email_preview = convert_smilies( $email_data->email_body );
93
+ $body_email_preview = str_replace( '{{customer.firstname}}', $user_first_name, $body_email_preview );
94
+ $body_email_preview = str_replace( '{{customer.lastname}}', $user_last_name, $body_email_preview );
95
+ $body_email_preview = str_replace( '{{customer.fullname}}', $user_full_name, $body_email_preview );
96
+
97
+ $email_instance = Cartflows_Ca_Email_Templates::get_instance();
98
+ if ( $preview_email ) {
99
+ $coupon_code = 'DUMMY-COUPON';
100
+ } else {
101
+ $override_global_coupon = $email_instance->get_email_template_meta_by_key( $email_data->email_template_id, 'override_global_coupon' );
102
+ if ( $override_global_coupon->meta_value ) {
103
+ $email_history = $email_instance->get_email_history_by_id( $email_data->email_history_id );
104
+ $coupon_code = $email_history->coupon_code;
105
+ } else {
106
+ $coupon_code = $email_data->coupon_code;
107
+ }
108
+ }
109
+
110
+ $auto_apply_coupon = $email_instance->get_email_template_meta_by_key( $email_data->email_template_id, 'auto_coupon' );
111
+
112
+ $token_data = array(
113
+ 'wcf_session_id' => $email_data->session_id,
114
+ 'wcf_coupon_code' => isset( $auto_apply_coupon ) && $auto_apply_coupon->meta_value ? $coupon_code : null,
115
+ 'wcf_preview_email' => $preview_email ? true : false,
116
+ );
117
+
118
+ $checkout_url = Cartflows_Ca_Helper::get_instance()->get_checkout_url( $email_data->checkout_id, $token_data );
119
+
120
+ $body_email_preview = str_replace( '{{cart.coupon_code}}', $coupon_code, $body_email_preview );
121
+
122
+ $current_time_stamp = $email_data->time;
123
+ $body_email_preview = str_replace( '{{cart.abandoned_date}}', $current_time_stamp, $body_email_preview );
124
+ $unsubscribe_element = '<a target="_blank" style="color: lightgray" href="' . $checkout_url . '&unsubscribe=true ">' . __( 'Unsubscribe', 'woo-cart-abandonment-recovery' ) . '</a>';
125
+ $body_email_preview = str_replace( '{{cart.unsubscribe}}', $unsubscribe_element, $body_email_preview );
126
+ $body_email_preview = str_replace( 'http://{{cart.checkout_url}}', '{{cart.checkout_url}}', $body_email_preview );
127
+ $body_email_preview = str_replace( 'https://{{cart.checkout_url}}', '{{cart.checkout_url}}', $body_email_preview );
128
+ $body_email_preview = str_replace( '{{cart.checkout_url}}', $checkout_url, $body_email_preview );
129
+ $host = wp_parse_url( get_site_url() );
130
+ $body_email_preview = str_replace( '{{site.url}}', $host['host'], $body_email_preview );
131
+
132
+ if ( false !== strpos( $body_email_preview, '{{cart.product.names}}' ) ) {
133
+ $body_email_preview = str_replace( '{{cart.product.names}}', Cartflows_Ca_Helper::get_instance()->get_comma_separated_products( $email_data->cart_contents ), $body_email_preview );
134
+ }
135
+
136
+ $admin_user = get_users(
137
+ array(
138
+ 'role' => 'Administrator',
139
+ 'number' => 1,
140
+ )
141
+ );
142
+ $admin_user = reset( $admin_user );
143
+ $admin_first_name = $admin_user->user_firstname ? $admin_user->user_firstname : __( 'Admin', 'woo-cart-abandonment-recovery' );
144
+ $body_email_preview = str_replace( '{{admin.firstname}}', $admin_first_name, $body_email_preview );
145
+ $body_email_preview = str_replace( '{{admin.company}}', get_bloginfo( 'name' ), $body_email_preview );
146
+
147
+ $headers = 'From: ' . $from_email_name . ' <' . $from_email_preview . '>' . "\r\n";
148
+ $headers .= 'Content-Type: text/html' . "\r\n";
149
+ $headers .= 'Reply-To: ' . $reply_name_preview . ' ' . "\r\n";
150
+ $var = $this->get_email_product_block( $email_data->cart_contents, $email_data->cart_total );
151
+
152
+ $body_email_preview = str_replace( '{{cart.product.table}}', $var, $body_email_preview );
153
+ $body_email_preview = wpautop( $body_email_preview );
154
+ $mail_result = wp_mail( $email_data->email, $subject_email_preview, stripslashes( $body_email_preview ), $headers );
155
+ if ( $mail_result ) {
156
+ return true;
157
+ } else {
158
+ // Retry sending mail.
159
+ $mail_result = wp_mail( $email_data->email, $subject_email_preview, stripslashes( $body_email_preview ), $headers );
160
+ if ( ! $preview_email ) {
161
+ return true;
162
+ }
163
+ return false;
164
+ }
165
+ } else {
166
+ return false;
167
+ }
168
+
169
+ }
170
+
171
+ /**
172
+ * Create a dummy object for the preview email.
173
+ *
174
+ * @return stdClass
175
+ */
176
+ public function create_dummy_session_for_preview_email() {
177
+
178
+ $email_data = new stdClass();
179
+ $current_user = wp_get_current_user();
180
+ $email_data->email_template_id = null;
181
+ $email_data->checkout_id = wc_get_page_id( 'checkout' );
182
+ $email_data->session_id = 'dummy-session-id';
183
+ $email_send_to = filter_input( INPUT_POST, 'email_send_to', FILTER_SANITIZE_EMAIL );
184
+ $email_data->email = $email_send_to ? $email_send_to : $current_user->user_email;
185
+ $email_data->email_body = filter_input( INPUT_POST, 'email_body', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
186
+ $email_data->email_subject = filter_input( INPUT_POST, 'email_subject', FILTER_SANITIZE_STRING );
187
+ $email_data->email_body = html_entity_decode( $email_data->email_body, ENT_COMPAT, 'UTF-8' );
188
+ $email_data->other_fields = serialize(
189
+ array(
190
+ 'wcf_first_name' => $current_user->user_firstname,
191
+ 'wcf_last_name' => $current_user->user_lastname,
192
+ )
193
+ );
194
+ if ( ! WC()->cart->get_cart_contents_count() ) {
195
+ $args = array(
196
+ 'posts_per_page' => 1,
197
+ 'orderby' => 'rand',
198
+ 'post_type' => 'product',
199
+ 'meta_query' => array( //phpcs:ignore
200
+ // Exclude out of stock products.
201
+ array(
202
+ 'key' => '_stock_status',
203
+ 'value' => 'outofstock',
204
+ 'compare' => 'NOT IN',
205
+ ),
206
+ ),
207
+ 'tax_query' => array( //phpcs:ignore
208
+ array(
209
+ 'taxonomy' => 'product_type',
210
+ 'field' => 'slug',
211
+ 'terms' => 'simple',
212
+ ),
213
+ ),
214
+ );
215
+
216
+ $random_products = get_posts( $args );
217
+ if ( ! empty( $random_products ) ) {
218
+ $random_product = reset( $random_products );
219
+ WC()->cart->add_to_cart( $random_product->ID );
220
+ }
221
+ }
222
+
223
+ $email_data->cart_total = WC()->cart->total + floatval( WC()->cart->get_cart_shipping_total() );
224
+ $email_data->cart_contents = serialize( WC()->cart->get_cart() );
225
+ $email_data->time = current_time( WCF_CA_DATETIME_FORMAT );
226
+ return $email_data;
227
+ }
228
+
229
+
230
+ /**
231
+ * Generate the view for email product cart block.
232
+ *
233
+ * @param string $cart_contents user cart contents details.
234
+ * @param float $cart_total user cart total.
235
+ * @return string
236
+ */
237
+ public function get_email_product_block( $cart_contents, $cart_total ) {
238
+
239
+ $cart_items = unserialize( $cart_contents );
240
+
241
+ if ( ! is_array( $cart_items ) || ! count( $cart_items ) ) {
242
+ return;
243
+ }
244
+
245
+ $tr = '';
246
+ $style = array(
247
+ 'product_image' => array(
248
+ 'style' => 'height: 42px; width: 42px;',
249
+ 'attribute' => 'width=42 height=42',
250
+ ),
251
+ 'table' => array(
252
+ 'style' => 'color: #636363; border: 1px solid #e5e5e5;',
253
+ 'attribute' => 'align= left;',
254
+ ),
255
+ );
256
+
257
+ $style_filter = apply_filters( 'woo_ca_email_template_table_style', $style );
258
+ $product_image_style = isset( $style_filter['product_image']['style'] ) ? $style_filter['product_image']['style'] : '';
259
+ $style = isset( $style_filter['table']['style'] ) ? $style_filter['table']['style'] : '';
260
+
261
+ foreach ( $cart_items as $cart_item ) {
262
+
263
+ if ( isset( $cart_item['product_id'] ) && isset( $cart_item['quantity'] ) && isset( $cart_item['line_total'] ) ) {
264
+ $id = 0 !== $cart_item['variation_id'] ? $cart_item['variation_id'] : $cart_item['product_id'];
265
+ $image_url = get_the_post_thumbnail_url( $id );
266
+ $image_url = ! empty( $image_url ) ? $image_url : get_the_post_thumbnail_url( $cart_item['product_id'] );
267
+
268
+ $product = wc_get_product( $id );
269
+ $product_name = $product ? $product->get_formatted_name() : '';
270
+
271
+ if ( empty( $image_url ) ) {
272
+ $image_url = CARTFLOWS_CA_URL . 'admin/assets/images/image-placeholder.png';
273
+ }
274
+ $tr = $tr . '<tr style=' . $style . ' align="center">
275
+ <td style="' . $style . '"><img class="demo_img" style="' . $product_image_style . '" src="' . esc_url( $image_url ) . '" ' . $style_filter['product_image']['attribute'] . '></td>
276
+ <td style="' . $style . '">' . $product_name . '</td>
277
+ <td style="' . $style . '"> ' . $cart_item['quantity'] . ' </td>
278
+ <td style="' . $style . '">' . wc_price( $cart_item['line_total'] ) . '</td>
279
+ <td style="' . $style . '" >' . wc_price( $cart_item['line_total'] ) . '</td>
280
+ </tr> ';
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Add filter to toggle the Cart Total row.
286
+ */
287
+ $enable_cart_total = apply_filters( 'woo_ca_recovery_enable_cart_total', false );
288
+ if ( $enable_cart_total ) {
289
+ $tr = $tr . '<tr style="' . $style . '" align="center">
290
+ <td colspan="4" style="' . $style . '"> ' . __( 'Cart Total ( Cart Total + Shipping + Tax )', 'woo-cart-abandonment-recovery' ) . ' </td>
291
+ <td style="' . $style . '" >' . wc_price( $cart_total ) . '</td>
292
+ </tr> ';
293
+ }
294
+
295
+ return '<table ' . $style_filter['table']['attribute'] . ' cellpadding="10" cellspacing="0" style="float: none; border: 1px solid #e5e5e5;">
296
+ <tr align="center">
297
+ <th style="' . $style . '">' . __( 'Item', 'woo-cart-abandonment-recovery' ) . '</th>
298
+ <th style="' . $style . '">' . __( 'Name', 'woo-cart-abandonment-recovery' ) . '</th>
299
+ <th style="' . $style . '">' . __( 'Quantity', 'woo-cart-abandonment-recovery' ) . '</th>
300
+ <th style="' . $style . '">' . __( 'Price', 'woo-cart-abandonment-recovery' ) . '</th>
301
+ <th style="' . $style . '">' . __( 'Line Subtotal', 'woo-cart-abandonment-recovery' ) . '</th>
302
+ </tr> ' . $tr . '
303
+ </table>';
304
+ }
305
+
306
+ /**
307
+ * Check before emails actually send to user.
308
+ *
309
+ * @param object $email_data email_data.
310
+ * @param string $current_cart_data cart data.
311
+ * @return bool
312
+ */
313
+ public function check_if_already_purchased_by_email_product_ids( $email_data, $current_cart_data ) {
314
+
315
+ global $wpdb;
316
+ $current_cart_data = unserialize( $current_cart_data );
317
+
318
+ // Fetch products & variations.
319
+ $products = array_values( wp_list_pluck( $current_cart_data, 'product_id' ) );
320
+ $variations = array_values( wp_list_pluck( $current_cart_data, 'variation_id' ) );
321
+ $current_products = array_unique( array_merge( $products, $variations ) );
322
+
323
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
324
+
325
+ $orders = wc_get_orders(
326
+ array(
327
+ 'billing_email' => $email_data->email,
328
+ 'status' => array( 'processing', 'completed' ),
329
+ 'date_after' => gmdate(
330
+ 'Y-m-d h:i:s',
331
+ strtotime( '-30 days' )
332
+ ),
333
+ )
334
+ );
335
+ $need_to_send_email = true;
336
+
337
+ foreach ( $orders as $order ) {
338
+ $order = wc_get_order( $order->get_id() );
339
+ $items = $order->get_items();
340
+ foreach ( $items as $item ) {
341
+ $product_id = $item->get_product_id();
342
+ if ( in_array( $product_id, $current_products, true ) ) {
343
+ /**
344
+ * Remove duplicate captured order for tracking.
345
+ */
346
+ $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $email_data->session_id ) ) );
347
+ $need_to_send_email = false;
348
+ break;
349
+ }
350
+ }
351
+ }
352
+ return $need_to_send_email;
353
+ }
354
+
355
+ /**
356
+ * Schedule events for the abadoned carts to send emails.
357
+ *
358
+ * @param integer $session_id user session id.
359
+ * @param boolean $force_reschedule force reschedule.
360
+ */
361
+ public function schedule_emails( $session_id, $force_reschedule = false ) {
362
+
363
+ $checkout_details = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $session_id );
364
+
365
+ if ( ( $checkout_details->unsubscribed ) || ( WCF_CART_COMPLETED_ORDER === $checkout_details->order_status ) ) {
366
+ return;
367
+ }
368
+
369
+ $scheduled_emails = Cartflows_Ca_Helper::get_instance()->fetch_scheduled_emails( $session_id );
370
+ $scheduled_templates = array_column( $scheduled_emails, 'template_id' ); //phpcs:ignore
371
+ $scheduled_time_from = $checkout_details->time;
372
+
373
+ if ( $force_reschedule ) {
374
+ $scheduled_time_from = current_time( WCF_CA_DATETIME_FORMAT );
375
+ }
376
+
377
+ $email_tmpl = Cartflows_Ca_Email_Templates::get_instance();
378
+ $templates = $email_tmpl->fetch_all_active_templates();
379
+
380
+ global $wpdb;
381
+
382
+ $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
383
+
384
+ foreach ( $templates as $template ) {
385
+
386
+ if ( false !== array_search( $template->id, $scheduled_templates, true ) && false === $force_reschedule ) {
387
+ continue;
388
+ }
389
+
390
+ $timestamp_str = '+' . $template->frequency . ' ' . $template->frequency_unit . 'S';
391
+ $scheduled_time = gmdate( WCF_CA_DATETIME_FORMAT, strtotime( $scheduled_time_from . $timestamp_str ) );
392
+ $discount_type = $email_tmpl->get_email_template_meta_by_key( $template->id, 'discount_type' );
393
+ $discount_type = isset( $discount_type->meta_value ) ? $discount_type->meta_value : '';
394
+ $amount = $email_tmpl->get_email_template_meta_by_key( $template->id, 'coupon_amount' );
395
+ $amount = isset( $amount->meta_value ) ? $amount->meta_value : '';
396
+
397
+ $coupon_expiry_date = $email_tmpl->get_email_template_meta_by_key( $template->id, 'coupon_expiry_date' );
398
+ $coupon_expiry_unit = $email_tmpl->get_email_template_meta_by_key( $template->id, 'coupon_expiry_unit' );
399
+ $coupon_expiry_date = isset( $coupon_expiry_date->meta_value ) ? $coupon_expiry_date->meta_value : '';
400
+ $coupon_expiry_unit = isset( $coupon_expiry_unit->meta_value ) ? $coupon_expiry_unit->meta_value : 'hours';
401
+
402
+ $coupon_expiry_date = $coupon_expiry_date ? strtotime( $scheduled_time . ' +' . $coupon_expiry_date . ' ' . $coupon_expiry_unit ) : '';
403
+
404
+ $free_shipping_coupon = $email_tmpl->get_email_template_meta_by_key( $template->id, 'free_shipping_coupon' );
405
+ $free_shipping = ( isset( $free_shipping_coupon ) && ( $free_shipping_coupon->meta_value ) ) ? 'yes' : 'no';
406
+
407
+ $individual_use_only = $email_tmpl->get_email_template_meta_by_key( $template->id, 'individual_use_only' );
408
+ $individual_use = ( isset( $individual_use_only ) && ( $individual_use_only->meta_value ) ) ? 'yes' : 'no';
409
+
410
+ $override_global_coupon = $email_tmpl->get_email_template_meta_by_key( $template->id, 'override_global_coupon' );
411
+
412
+ $new_coupon_code = '';
413
+ if ( $override_global_coupon->meta_value ) {
414
+ $new_coupon_code = $this->generate_coupon_code( $discount_type, $amount, $coupon_expiry_date, $free_shipping, $individual_use );
415
+ }
416
+
417
+ $wpdb->replace(
418
+ $email_history_table,
419
+ array(
420
+ 'template_id' => $template->id,
421
+ 'ca_session_id' => $checkout_details->session_id,
422
+ 'coupon_code' => $new_coupon_code,
423
+ 'scheduled_time' => $scheduled_time,
424
+ )
425
+ );
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Generate new coupon code for abandoned cart.
431
+ *
432
+ * @param string $discount_type discount type.
433
+ * @param float $amount amount.
434
+ * @param string $expiry expiry.
435
+ * @param string $free_shipping is free shipping.
436
+ * @param string $individual_use use coupon individual.
437
+ */
438
+ public function generate_coupon_code( $discount_type, $amount, $expiry = '', $free_shipping = 'no', $individual_use = 'no' ) {
439
+
440
+ $coupon_code = '';
441
+
442
+ $coupon_code = wp_generate_password( 8, false, false );
443
+
444
+ $new_coupon_id = wp_insert_post(
445
+ array(
446
+ 'post_title' => $coupon_code,
447
+ 'post_content' => '',
448
+ 'post_status' => 'publish',
449
+ 'post_author' => 1,
450
+ 'post_type' => 'shop_coupon',
451
+ )
452
+ );
453
+
454
+ $coupon_post_data = array(
455
+ 'discount_type' => $discount_type,
456
+ 'description' => WCF_CA_COUPON_DESCRIPTION,
457
+ 'coupon_amount' => $amount,
458
+ 'individual_use' => $individual_use,
459
+ 'product_ids' => '',
460
+ 'exclude_product_ids' => '',
461
+ 'usage_limit' => '1',
462
+ 'usage_count' => '0',
463
+ 'date_expires' => $expiry,
464
+ 'apply_before_tax' => 'yes',
465
+ 'free_shipping' => $free_shipping,
466
+ 'coupon_generated_by' => WCF_CA_COUPON_GENERATED_BY,
467
+ );
468
+
469
+ $coupon_post_data = apply_filters( 'woo_ca_generate_coupon', $coupon_post_data );
470
+
471
+ foreach ( $coupon_post_data as $key => $value ) {
472
+ update_post_meta( $new_coupon_id, $key, $value );
473
+ }
474
+
475
+ return $coupon_code;
476
+ }
477
+
478
+ }
479
+
480
+ Cartflows_Ca_Email_Schedule::get_instance();
modules/cart-abandonment/{class-cartflows-ca-email-templates-table.php → classes/class-cartflows-ca-email-templates-table.php} RENAMED
File without changes
modules/cart-abandonment/{class-cartflows-ca-email-templates.php → classes/class-cartflows-ca-email-templates.php} RENAMED
@@ -93,7 +93,7 @@ class Cartflows_Ca_Email_Templates {
93
 
94
  $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
95
 
96
- if ( ! ( WCF_CA_PAGE_NAME === $page ) ) {
97
  return;
98
  }
99
 
@@ -141,6 +141,13 @@ class Cartflows_Ca_Email_Templates {
141
  'cart_abandonment_date' => __( 'Cart Abandonment Date', 'woo-cart-abandonment-recovery' ),
142
  'site_url' => __( 'Site URL', 'woo-cart-abandonment-recovery' ),
143
  'unsubscribe_link' => __( 'Unsubscribe Link', 'woo-cart-abandonment-recovery' ),
 
 
 
 
 
 
 
144
  );
145
  wp_localize_script( 'cartflows-ca-email-tmpl-settings', 'wcf_ca_details', $vars );
146
 
@@ -939,8 +946,8 @@ class Cartflows_Ca_Email_Templates {
939
 
940
  if ( $wpnonce && wp_verify_nonce( $wpnonce, WCF_EMAIL_TEMPLATES_NONCE ) ) {
941
 
942
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-cart-abandonment-db.php';
943
- $db = Cartflows_Ca_Cart_Abandonment_Db::get_instance();
944
  $db->template_table_seeder( true );
945
 
946
  $param = array(
93
 
94
  $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
95
 
96
+ if ( WCF_CA_PAGE_NAME !== $page ) {
97
  return;
98
  }
99
 
141
  'cart_abandonment_date' => __( 'Cart Abandonment Date', 'woo-cart-abandonment-recovery' ),
142
  'site_url' => __( 'Site URL', 'woo-cart-abandonment-recovery' ),
143
  'unsubscribe_link' => __( 'Unsubscribe Link', 'woo-cart-abandonment-recovery' ),
144
+ 'strings' => array(
145
+ 'trigger_process' => __( 'Triggering...', 'woo-cart-abandonment-recovery' ),
146
+ 'trigger_failed' => __( 'Trigger Failed.', 'woo-cart-abandonment-recovery' ),
147
+ 'trigger_success' => __( 'Trigger Success.', 'woo-cart-abandonment-recovery' ),
148
+ 'verify_url' => __( 'Please verify webhook URL.', 'woo-cart-abandonment-recovery' ),
149
+ 'verify_url_error' => __( 'Webhook URL is required.', 'woo-cart-abandonment-recovery' ),
150
+ ),
151
  );
152
  wp_localize_script( 'cartflows-ca-email-tmpl-settings', 'wcf_ca_details', $vars );
153
 
946
 
947
  if ( $wpnonce && wp_verify_nonce( $wpnonce, WCF_EMAIL_TEMPLATES_NONCE ) ) {
948
 
949
+ include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/classes/class-cartflows-ca-database.php';
950
+ $db = Cartflows_Ca_Database::get_instance();
951
  $db->template_table_seeder( true );
952
 
953
  $param = array(
modules/cart-abandonment/classes/class-cartflows-ca-helper.php ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cart Abandonment
4
+ *
5
+ * @package Woocommerce-Cart-Abandonment-Recovery
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ /**
13
+ * Cart abandonment tracking class.
14
+ */
15
+ class Cartflows_Ca_Helper {
16
+
17
+
18
+
19
+ /**
20
+ * Member Variable
21
+ *
22
+ * @var object instance
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ */
29
+ public static function get_instance() {
30
+ if ( ! isset( self::$instance ) ) {
31
+ self::$instance = new self();
32
+ }
33
+ return self::$instance;
34
+ }
35
+
36
+ /**
37
+ * Constructor function that initializes required actions and hooks.
38
+ */
39
+ public function __construct() {
40
+ }
41
+
42
+ /**
43
+ * Get checkout url.
44
+ *
45
+ * @param integer $post_id post id.
46
+ * @param string $token_data token data.
47
+ * @return string
48
+ */
49
+ public function get_checkout_url( $post_id, $token_data ) {
50
+
51
+ $token = $this->wcf_generate_token( (array) $token_data );
52
+ $global_param = get_option( 'wcf_ca_global_param', false );
53
+ $checkout_url = get_permalink( $post_id );
54
+
55
+ $token_param = array(
56
+ 'wcf_ac_token' => $token,
57
+ );
58
+ $checkout_url = add_query_arg( $token_param, $checkout_url );
59
+
60
+ if ( ! empty( $global_param ) ) {
61
+
62
+ $query_param = array();
63
+ $global_param = preg_split( "/[\f\r\n]+/", $global_param );
64
+
65
+ foreach ( $global_param as $key => $param ) {
66
+
67
+ $param_parts = explode( '=', $param );
68
+ $query_param[ trim( $param_parts[0] ) ] = trim( $param_parts[1] );
69
+ }
70
+ $checkout_url = add_query_arg( $query_param, $checkout_url );
71
+ }
72
+
73
+ return esc_url( $checkout_url );
74
+ }
75
+
76
+ /**
77
+ * Geberate the token for the given data.
78
+ *
79
+ * @param array $data data.
80
+ */
81
+ public function wcf_generate_token( $data ) {
82
+ return urlencode( base64_encode( http_build_query( $data ) ) );
83
+ }
84
+
85
+ /**
86
+ * Get the acceptable order statuses.
87
+ */
88
+ public function get_acceptable_order_statuses() {
89
+
90
+ $acceptable_order_statuses = get_option( 'wcf_ca_excludes_orders' );
91
+ $acceptable_order_statuses = array_map( 'strtolower', $acceptable_order_statuses );
92
+
93
+ return $acceptable_order_statuses;
94
+ }
95
+
96
+ /**
97
+ * Generate comma separated products.
98
+ *
99
+ * @param string $cart_contents user cart details.
100
+ */
101
+ public function get_comma_separated_products( $cart_contents ) {
102
+ $cart_comma_string = '';
103
+ if ( ! $cart_contents ) {
104
+ return $cart_comma_string;
105
+ }
106
+ $cart_data = unserialize( $cart_contents );
107
+
108
+ $cart_length = count( $cart_data );
109
+ $index = 0;
110
+ foreach ( $cart_data as $key => $product ) {
111
+
112
+ if ( ! isset( $product['product_id'] ) ) {
113
+ continue;
114
+ }
115
+
116
+ $cart_product = wc_get_product( $product['product_id'] );
117
+
118
+ if ( $cart_product ) {
119
+ $cart_comma_string = $cart_comma_string . $cart_product->get_title();
120
+ if ( ( $cart_length - 2 ) === $index ) {
121
+ $cart_comma_string = $cart_comma_string . ' & ';
122
+ } elseif ( ( $cart_length - 1 ) !== $index ) {
123
+ $cart_comma_string = $cart_comma_string . ', ';
124
+ }
125
+ $index++;
126
+ }
127
+ }
128
+ return $cart_comma_string;
129
+
130
+ }
131
+
132
+ /**
133
+ * Count abandoned carts
134
+ *
135
+ * @since 1.1.5
136
+ */
137
+ public function abandoned_cart_count() {
138
+ global $wpdb;
139
+ $cart_abandonment_table_name = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
140
+
141
+ $query = $wpdb->prepare( "SELECT COUNT(`id`) FROM {$cart_abandonment_table_name} WHERE `order_status` = %s", WCF_CART_ABANDONED_ORDER ); // phpcs:ignore
142
+ $total_items = $wpdb->get_var( $query ); // phpcs:ignore
143
+ return $total_items;
144
+ }
145
+
146
+ /**
147
+ * Get start and end date for given interval.
148
+ *
149
+ * @param string $interval interval .
150
+ * @return array
151
+ */
152
+ public function get_start_end_by_interval( $interval ) {
153
+
154
+ if ( 'today' === $interval ) {
155
+ $start_date = gmdate( 'Y-m-d' );
156
+ $end_date = gmdate( 'Y-m-d' );
157
+ } else {
158
+
159
+ $days = $interval;
160
+
161
+ $start_date = gmdate( 'Y-m-d', strtotime( '-' . $days . ' days' ) );
162
+ $end_date = gmdate( 'Y-m-d' );
163
+ }
164
+
165
+ return array(
166
+ 'start' => $start_date,
167
+ 'end' => $end_date,
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Get the checkout details for the user.
173
+ *
174
+ * @param string $wcf_session_id checkout page session id.
175
+ * @since 1.0.0
176
+ */
177
+ public function get_checkout_details( $wcf_session_id ) {
178
+ global $wpdb;
179
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
180
+ $result = $wpdb->get_row(
181
+ $wpdb->prepare('SELECT * FROM `' . $cart_abandonment_table . '` WHERE session_id = %s', $wcf_session_id ) // phpcs:ignore
182
+ );
183
+ return $result;
184
+ }
185
+
186
+ /**
187
+ * Fetch all the scheduled emails with templates for the specific session.
188
+ *
189
+ * @param string $session_id session id.
190
+ * @param boolean $fetch_sent sfetch sent emails.
191
+ * @return array|object|null
192
+ */
193
+ public function fetch_scheduled_emails( $session_id, $fetch_sent = false ) {
194
+ global $wpdb;
195
+ $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
196
+ $email_template_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_TEMPLATE_TABLE;
197
+
198
+ $query = $wpdb->prepare("SELECT * FROM $email_history_table as eht INNER JOIN $email_template_table as ett ON eht.template_id = ett.id WHERE ca_session_id = %s", sanitize_text_field($session_id)); // phpcs:ignore
199
+
200
+ if ( $fetch_sent ) {
201
+ $query .= ' AND email_sent = 1';
202
+ }
203
+
204
+ $result = $wpdb->get_results( $query ); // phpcs:ignore
205
+ return $result;
206
+ }
207
+
208
+ }
209
+
210
+ Cartflows_Ca_Helper::get_instance();
modules/cart-abandonment/{class-cartflows-ca-module-loader.php → classes/class-cartflows-ca-module-loader.php} RENAMED
@@ -42,23 +42,32 @@ class Cartflows_Ca_Module_Loader {
42
  }
43
 
44
 
 
 
45
  /**
46
  * Load required files for module.
47
  */
48
  private function load_module_files() {
49
 
50
- /* Cart abandonment templates class */
51
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-email-templates.php';
 
 
 
 
 
 
 
 
52
 
53
- /* Cart abandonment templates table */
54
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-email-templates-table.php';
55
 
56
- /* Cart abandonment tracking */
57
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-cart-abandonment.php';
58
-
59
- /* Cart abandonment tracking table */
60
- include_once CARTFLOWS_CA_DIR . 'modules/cart-abandonment/class-cartflows-ca-cart-abandonment-table.php';
61
 
 
 
 
 
62
  }
63
 
64
  }
42
  }
43
 
44
 
45
+
46
+
47
  /**
48
  * Load required files for module.
49
  */
50
  private function load_module_files() {
51
 
52
+ $module_files = array(
53
+ 'class-cartflows-ca-tracking.php',
54
+ 'class-cartflows-ca-cron.php',
55
+ 'class-cartflows-ca-email-templates-table.php',
56
+ 'class-cartflows-ca-email-templates.php',
57
+ 'class-cartflows-ca-email-schedule.php',
58
+ 'class-cartflows-ca-helper.php',
59
+ 'class-cartflows-ca-order-table.php',
60
+ 'class-cartflows-ca-setting-functions.php',
61
+ );
62
 
63
+ foreach ( $module_files as $index => $file ) {
 
64
 
65
+ $filename = CARTFLOWS_CA_DIR . '/modules/cart-abandonment/classes/' . $file;
 
 
 
 
66
 
67
+ if ( file_exists( $filename ) ) {
68
+ include_once $filename;
69
+ }
70
+ }
71
  }
72
 
73
  }
modules/cart-abandonment/{class-cartflows-ca-cart-abandonment-table.php → classes/class-cartflows-ca-order-table.php} RENAMED
@@ -15,7 +15,7 @@ if ( ! class_exists( 'WP_List_Table' ) ) {
15
  /**
16
  * Cart abandonment tracking table class.
17
  */
18
- class Cartflows_Ca_Cart_Abandonment_Table extends WP_List_Table {
19
 
20
 
21
  /**
15
  /**
16
  * Cart abandonment tracking table class.
17
  */
18
+ class Cartflows_Ca_Order_Table extends WP_List_Table {
19
 
20
 
21
  /**
modules/cart-abandonment/classes/class-cartflows-ca-setting-functions.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cart Abandonment
4
+ *
5
+ * @package Woocommerce-Cart-Abandonment-Recovery
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ /**
13
+ * Cart abandonment tracking class.
14
+ */
15
+ class Cartflows_Ca_Setting_Functions {
16
+
17
+
18
+
19
+ /**
20
+ * Member Variable
21
+ *
22
+ * @var object instance
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ */
29
+ public static function get_instance() {
30
+ if ( ! isset( self::$instance ) ) {
31
+ self::$instance = new self();
32
+ }
33
+ return self::$instance;
34
+ }
35
+
36
+ /**
37
+ * Constructor function that initializes required actions and hooks.
38
+ */
39
+ public function __construct() {
40
+
41
+ $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
42
+
43
+ if ( WCF_CA_PAGE_NAME === $page ) {
44
+ // Adding filter to add new button to add custom fields.
45
+ add_filter( 'mce_buttons', array( $this, 'wcf_filter_mce_button' ) );
46
+ add_filter( 'mce_external_plugins', array( $this, 'wcf_filter_mce_plugin' ), 9 );
47
+ }
48
+
49
+ // GDPR actions.
50
+ add_action( 'wp_ajax_cartflows_skip_cart_tracking_gdpr', array( $this, 'skip_cart_tracking_by_gdpr' ) );
51
+ add_action( 'wp_ajax_nopriv_cartflows_skip_cart_tracking_gdpr', array( $this, 'skip_cart_tracking_by_gdpr' ) );
52
+
53
+ // Delete coupons.
54
+
55
+ add_action( 'wp_ajax_wcf_ca_delete_garbage_coupons', array( $this, 'delete_used_and_expired_coupons' ) );
56
+ }
57
+
58
+ /**
59
+ * Register button.
60
+ *
61
+ * @param array $buttons mce buttons.
62
+ * @return mixed
63
+ */
64
+ public function wcf_filter_mce_button( $buttons ) {
65
+ array_push( $buttons, 'cartflows_ac' );
66
+ return $buttons;
67
+ }
68
+
69
+
70
+ /**
71
+ * Link JS to mce button.
72
+ *
73
+ * @param array $plugins mce pluggins.
74
+ * @return mixed
75
+ */
76
+ public function wcf_filter_mce_plugin( $plugins ) {
77
+ $plugins['cartflows_ac'] = CARTFLOWS_CA_URL . 'admin/assets/js/admin-mce.js';
78
+ return $plugins;
79
+ }
80
+
81
+
82
+ /**
83
+ * Delete tracked data and set cookie for the user.
84
+ */
85
+ public function skip_cart_tracking_by_gdpr() {
86
+ check_ajax_referer( 'cartflows_skip_cart_tracking_gdpr', 'security' );
87
+
88
+ global $wpdb;
89
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
90
+
91
+ $session_id = WC()->session->get( 'wcf_session_id' );
92
+ if ( $session_id ) {
93
+ $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
94
+ }
95
+
96
+ setcookie( 'wcf_ca_skip_track_data', 'true', 0, '/' );
97
+ wp_send_json_success();
98
+ }
99
+
100
+ /**
101
+ * Check if transient is set for delete garbage coupons.
102
+ */
103
+ public function delete_used_and_expired_coupons() {
104
+ $is_ajax_request = wp_doing_ajax();
105
+ $is_transient_set = false;
106
+ global $wpdb;
107
+ if ( $is_ajax_request ) {
108
+ check_ajax_referer( 'wcf_ca_delete_garbage_coupons', 'security' );
109
+ } else {
110
+ $is_transient_set = get_transient( 'woocommerce_ca_delete_garbage_coupons' );
111
+ }
112
+
113
+ if ( false === $is_transient_set || $is_ajax_request ) {
114
+ $coupons = $this->delete_garbage_coupons();
115
+ $coupon_count = count( $coupons );
116
+
117
+ if ( $coupon_count ) {
118
+ $coupons_post_ids = implode( ',', wp_list_pluck( $coupons, 'ID' ) );
119
+ $wpdb->query( "DELETE FROM {$wpdb->prefix}postmeta WHERE post_id IN(" . $coupons_post_ids . ')' );//phpcs:ignore
120
+ $wpdb->query( "DELETE FROM {$wpdb->prefix}posts WHERE ID IN(" . $coupons_post_ids . ')' );//phpcs:ignore
121
+ }
122
+
123
+ if ( ! $is_ajax_request ) {
124
+ set_transient( 'woocommerce_ca_delete_garbage_coupons', $coupons, WEEK_IN_SECONDS );
125
+ return;
126
+ }
127
+
128
+ // translators: %1$s: Coupons Deleted, %2$s: Deleted coupons count'.
129
+ wp_send_json_success( sprintf( __( '%1$s: %2$d', 'woo-cart-abandonment-recovery' ), 'Coupons Deleted', $coupon_count ) );
130
+
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Set transient and delete garbage coupons.
136
+ */
137
+ public function delete_garbage_coupons() {
138
+
139
+ global $wpdb;
140
+
141
+ $coupon_generated_by = WCF_CA_COUPON_GENERATED_BY;
142
+ $timestamp = time();
143
+ $post_type = 'shop_coupon';
144
+ $coupons = $wpdb->get_results(
145
+ $wpdb->prepare(
146
+ "SELECT ID, coupon_code, usage_limit, total_usaged, expiry_date FROM (
147
+ SELECT p.ID,
148
+ p.post_title AS coupon_code,
149
+ Max(CASE WHEN pm.meta_key = 'date_expires' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS expiry_date,
150
+ Max(CASE WHEN pm.meta_key = 'usage_limit' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS usage_limit,
151
+ Max(CASE WHEN pm.meta_key = 'usage_count' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS total_usaged,
152
+
153
+ Max(CASE WHEN pm.meta_key = 'coupon_generated_by' AND p.`ID` = pm.`post_id` THEN pm.meta_value END) AS coupon_generated_by
154
+ FROM {$wpdb->prefix}posts AS p
155
+ INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
156
+ WHERE p.`post_type` = %s
157
+
158
+ GROUP BY p.ID
159
+ ) AS final_res WHERE coupon_generated_by IS NOT NULL AND coupon_generated_by = %s AND ( ( usage_limit = total_usaged ) OR ( expiry_date <= %d AND expiry_date != '') )",
160
+ $post_type,
161
+ $coupon_generated_by,
162
+ $timestamp
163
+ )
164
+ );
165
+ return $coupons;
166
+ }
167
+ }
168
+
169
+ Cartflows_Ca_Setting_Functions::get_instance();
modules/cart-abandonment/classes/class-cartflows-ca-tracking.php ADDED
@@ -0,0 +1,1062 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cart Abandonment
4
+ *
5
+ * @package Woocommerce-Cart-Abandonment-Recovery
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ /**
13
+ * Cart abandonment tracking class.
14
+ */
15
+ class Cartflows_Ca_Tracking {
16
+
17
+
18
+
19
+ /**
20
+ * Member Variable
21
+ *
22
+ * @var object instance
23
+ */
24
+ private static $instance;
25
+
26
+ /**
27
+ * Initiator
28
+ */
29
+ public static function get_instance() {
30
+ if ( ! isset( self::$instance ) ) {
31
+ self::$instance = new self();
32
+ }
33
+ return self::$instance;
34
+ }
35
+
36
+ /**
37
+ * Constructor function that initializes required actions and hooks.
38
+ */
39
+ public function __construct() {
40
+
41
+ $this->define_cart_abandonment_constants();
42
+
43
+ // Adding the styles and scripts for the cart abandonment.
44
+ add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_cart_abandonment_script' ), 20 );
45
+
46
+ if ( wcf_ca()->utils->is_cart_abandonment_tracking_enabled() && ! isset( $_COOKIE['wcf_ca_skip_track_data'] ) ) {
47
+
48
+ // Add script to track the cart abandonment.
49
+ add_action( 'woocommerce_after_checkout_form', array( $this, 'cart_abandonment_tracking_script' ) );
50
+
51
+ // Store user details from the current checkout page.
52
+ add_action( 'wp_ajax_cartflows_save_cart_abandonment_data', array( $this, 'save_cart_abandonment_data' ) );
53
+ add_action( 'wp_ajax_nopriv_cartflows_save_cart_abandonment_data', array( $this, 'save_cart_abandonment_data' ) );
54
+
55
+ // Delete the stored cart abandonment data once order gets created.
56
+ add_action( 'woocommerce_new_order', array( $this, 'delete_cart_abandonment_data' ) );
57
+ add_action( 'woocommerce_thankyou', array( $this, 'delete_cart_abandonment_data' ) );
58
+ add_action( 'woocommerce_order_status_changed', array( $this, 'wcf_ca_update_order_status' ), 999, 3 );
59
+
60
+ // Adding filter to restore the data if recreating abandonment order.
61
+ add_filter( 'wp', array( $this, 'restore_cart_abandonment_data' ), 10 );
62
+ add_filter( 'wp', array( $this, 'unsubscribe_cart_abandonment_emails' ), 10 );
63
+
64
+ // Adding notice to checkout page to inform about test email checkout page.
65
+ add_action( 'woocommerce_before_checkout_form', array( $this, 'test_email_checkout_page' ), 9 );
66
+
67
+ add_action( 'cartflows_ca_update_order_status_action', array( $this, 'update_order_status' ) );
68
+
69
+ }
70
+
71
+ }
72
+
73
+ /**
74
+ * Initialise all the constants
75
+ */
76
+ public function define_cart_abandonment_constants() {
77
+ define( 'CARTFLOWS_CART_ABANDONMENT_TRACKING_DIR', CARTFLOWS_CA_DIR . 'modules/cart-abandonment/' );
78
+ define( 'CARTFLOWS_CART_ABANDONMENT_TRACKING_URL', CARTFLOWS_CA_URL . 'modules/cart-abandonment/' );
79
+ define( 'WCF_CART_ABANDONED_ORDER', 'abandoned' );
80
+ define( 'WCF_CART_COMPLETED_ORDER', 'completed' );
81
+ define( 'WCF_CART_LOST_ORDER', 'lost' );
82
+ define( 'WCF_CART_NORMAL_ORDER', 'normal' );
83
+ define( 'WCF_CART_FAILED_ORDER', 'failed' );
84
+ define( 'CARTFLOWS_ZAPIER_ACTION_AFTER_TIME', 1800 );
85
+
86
+ define( 'WCF_ACTION_ABANDONED_CARTS', 'abandoned_carts' );
87
+ define( 'WCF_ACTION_RECOVERED_CARTS', 'recovered_carts' );
88
+ define( 'WCF_ACTION_LOST_CARTS', 'lost_carts' );
89
+ define( 'WCF_ACTION_SETTINGS', 'settings' );
90
+ define( 'WCF_ACTION_REPORTS', 'reports' );
91
+
92
+ define( 'WCF_SUB_ACTION_REPORTS_VIEW', 'view' );
93
+ define( 'WCF_SUB_ACTION_REPORTS_RESCHEDULE', 'reschedule' );
94
+
95
+ define( 'WCF_DEFAULT_CUT_OFF_TIME', 15 );
96
+ define( 'WCF_DEFAULT_COUPON_AMOUNT', 10 );
97
+
98
+ define( 'WCF_CA_DATETIME_FORMAT', 'Y-m-d H:i:s' );
99
+
100
+ define( 'WCF_CA_COUPON_DESCRIPTION', __( 'This coupon is for abandoned cart email templates.', 'woo-cart-abandonment-recovery' ) );
101
+ define( 'WCF_CA_COUPON_GENERATED_BY', 'woo-cart-abandonment-recovery' );
102
+ }
103
+
104
+ /**
105
+ * This function will send the email to the store admin when any abandoned cart email recovered.
106
+ *
107
+ * @param int | string $order_id Order id.
108
+ * @param string $wcar_old_status Old status of the order.
109
+ * @param string $wcar_new_status New status of the order.
110
+ */
111
+ public function wcar_send_successful_recovery_email_to_admin( $order_id, $wcar_old_status, $wcar_new_status ) {
112
+ global $woocommerce;
113
+
114
+ if ( in_array( $wcar_old_status, array( 'pending', 'failed', 'on-hold' ), true ) &&
115
+ in_array( $wcar_new_status, array( 'processing', 'completed' ), true )
116
+ ) {
117
+ $user_id = get_current_user_id();
118
+ $order = wc_get_order( $order_id );
119
+ if ( version_compare( $woocommerce->version, '3.0.0', '>=' ) ) {
120
+ $user_id = $order->get_user_id();
121
+ } else {
122
+ $user_id = $order->user_id;
123
+ }
124
+
125
+ $is_recoverd = $this->wcar_check_order_is_recovered( $order_id );
126
+
127
+ if ( $is_recoverd ) {
128
+ $order = wc_get_order( $order_id );
129
+ $email_heading = __( 'New Customer Order - Recovered Order ID: ' . $order_id . '', 'woo-cart-abandonment-recovery' ); //phpcs:ignore
130
+ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
131
+ $email_subject = __( 'New Customer Order - Recovered Order ID: ' . $order_id . '', 'woo-cart-abandonment-recovery' ); //phpcs:ignore
132
+ $user_email = get_option( 'admin_email' );
133
+ $headers[] = 'From: Admin <' . $user_email . '>';
134
+ $headers[] = 'Content-Type: text/html';
135
+
136
+ ob_start();
137
+ wc_get_template(
138
+ 'emails/admin-new-order.php',
139
+ array(
140
+ 'order' => $order,
141
+ 'email_heading' => $email_heading,
142
+ 'sent_to_admin' => false,
143
+ 'plain_text' => false,
144
+ 'email' => true,
145
+ 'additional_content' => '',
146
+ )
147
+ );
148
+
149
+ $email_body = ob_get_clean();
150
+ wc_mail( $user_email, $email_subject, $email_body, $headers );
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * This function will check if cart is recoverd from woocommerce and WCAR.
157
+ *
158
+ * @param int $order_id order id.
159
+ */
160
+ public function wcar_check_order_is_recovered( $order_id ) {
161
+
162
+ global $wpdb;
163
+ $order = wc_get_order( $order_id );
164
+ $email = $order->get_billing_email();
165
+ $cart_abandonment_table_name = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
166
+ $wcar_status = $wpdb->get_var($wpdb->prepare( "SELECT `order_status` FROM {$cart_abandonment_table_name} WHERE `email` = %s", $email )); // phpcs:ignore
167
+ $woo_status = $order->get_status();
168
+
169
+ if ( 'completed' === $wcar_status && in_array( $woo_status, array( 'completed', 'processing' ), true ) ) {
170
+ return true;
171
+ }
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * Update the Order status.
177
+ *
178
+ * @param integer $order_id order id.
179
+ * @param string $old_order_status old order status.
180
+ * @param string $new_order_status new order status.
181
+ */
182
+ public function wcf_ca_update_order_status( $order_id, $old_order_status, $new_order_status ) {
183
+
184
+ $acceptable_order_statuses = Cartflows_Ca_Helper::get_instance()->get_acceptable_order_statuses();
185
+
186
+ $exclude_on_hold_order = apply_filters_deprecated( 'woo_ca_exclude_on_hold_order_from_tracking', array( false ), '1.2.8', 'New Option is introduced instead of this filter' );
187
+
188
+ if ( $exclude_on_hold_order & ! ( in_array( 'on-hold', $acceptable_order_statuses, true ) ) ) {
189
+ array_push( $acceptable_order_statuses, 'on-hold' );
190
+ }
191
+
192
+ if ( ( WCF_CART_FAILED_ORDER === $new_order_status ) ) {
193
+ return;
194
+ }
195
+
196
+ if ( $order_id && in_array( $new_order_status, $acceptable_order_statuses, true ) ) {
197
+
198
+ $order = wc_get_order( $order_id );
199
+
200
+ $order_email = $order->get_billing_email();
201
+ $captured_data = ( WCF_CART_FAILED_ORDER === $new_order_status ) ? $this->get_tracked_data_without_status( $order_email ) : $this->get_captured_data_by_email( $order_email );
202
+
203
+ if ( $captured_data && is_object( $captured_data ) ) {
204
+ $capture_status = $captured_data->order_status;
205
+ global $wpdb;
206
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
207
+
208
+ if ( ( WCF_CART_NORMAL_ORDER === $capture_status ) ) {
209
+ $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $captured_data->session_id ) ) );
210
+ }
211
+
212
+ if ( ( WCF_CART_ABANDONED_ORDER === $capture_status || WCF_CART_LOST_ORDER === $capture_status ) ) {
213
+ $this->skip_future_emails_when_order_is_completed( sanitize_key( $captured_data->session_id ) );
214
+ $this->trigger_zapier_webhook( $captured_data->session_id, WCF_CART_COMPLETED_ORDER );
215
+ $note = __( 'This order was abandoned & subsequently recovered.', 'woo-cart-abandonment-recovery' );
216
+ $order->add_order_note( $note );
217
+ $order->save();
218
+ if ( WC()->session ) {
219
+ WC()->session->__unset( 'wcf_session_id' );
220
+ }
221
+ }
222
+ }
223
+ $wcar_email_admin_recovery = get_option( 'wcar_email_admin_on_recovery' );
224
+ if ( 'on' === $wcar_email_admin_recovery ) {
225
+ $this->wcar_send_successful_recovery_email_to_admin( $order_id, $old_order_status, $new_order_status );
226
+ }
227
+ }
228
+
229
+ }
230
+
231
+ /**
232
+ * Unsubscribe the user from the mailing list.
233
+ */
234
+ public function unsubscribe_cart_abandonment_emails() {
235
+
236
+ $unsubscribe = filter_input( INPUT_GET, 'unsubscribe', FILTER_VALIDATE_BOOLEAN );
237
+ $wcf_ac_token = filter_input( INPUT_GET, 'wcf_ac_token', FILTER_SANITIZE_STRING );
238
+ if ( $unsubscribe && $this->is_valid_token( $wcf_ac_token ) ) {
239
+ $token_data = $this->wcf_decode_token( $wcf_ac_token );
240
+ if ( isset( $token_data['wcf_session_id'] ) ) {
241
+ $session_id = $token_data['wcf_session_id'];
242
+
243
+ global $wpdb;
244
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
245
+ $wpdb->update(
246
+ $cart_abandonment_table,
247
+ array( 'unsubscribed' => true ),
248
+ array( 'session_id' => $session_id )
249
+ );
250
+ wp_die( esc_html__( 'You have successfully unsubscribed from our email list.', 'woo-cart-abandonment-recovery' ), esc_html__( 'Unsubscribed', 'woo-cart-abandonment-recovery' ) );
251
+
252
+ }
253
+ }
254
+
255
+ }
256
+
257
+ /**
258
+ * Restore cart abandonemnt data on checkout page.
259
+ *
260
+ * @param array $fields checkout fields values.
261
+ * @return array field values
262
+ */
263
+ public function restore_cart_abandonment_data( $fields = array() ) {
264
+ global $woocommerce;
265
+ $result = array();
266
+ // Restore only of user is not logged in.
267
+ $wcf_ac_token = filter_input( INPUT_GET, 'wcf_ac_token', FILTER_SANITIZE_STRING );
268
+ if ( $this->is_valid_token( $wcf_ac_token ) ) {
269
+
270
+ // Check if `wcf_restore_token` exists to restore cart data.
271
+ $token_data = $this->wcf_decode_token( $wcf_ac_token );
272
+ if ( is_array( $token_data ) && isset( $token_data['wcf_session_id'] ) ) {
273
+ $result = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $token_data['wcf_session_id'] );
274
+ if ( isset( $result ) && WCF_CART_ABANDONED_ORDER === $result->order_status || WCF_CART_LOST_ORDER === $result->order_status ) {
275
+ WC()->session->set( 'wcf_session_id', $token_data['wcf_session_id'] );
276
+ }
277
+ }
278
+
279
+ if ( $result ) {
280
+ $cart_content = unserialize( $result->cart_contents );
281
+
282
+ if ( $cart_content ) {
283
+ $woocommerce->cart->empty_cart();
284
+ wc_clear_notices();
285
+ foreach ( $cart_content as $cart_item ) {
286
+
287
+ $cart_item_data = array();
288
+ $variation_data = array();
289
+ $id = $cart_item['product_id'];
290
+ $qty = $cart_item['quantity'];
291
+
292
+ // Skip bundled products when added main product.
293
+ if ( isset( $cart_item['bundled_by'] ) ) {
294
+ continue;
295
+ }
296
+
297
+ if ( isset( $cart_item['variation'] ) ) {
298
+ foreach ( $cart_item['variation'] as $key => $value ) {
299
+ $variation_data[ $key ] = $value;
300
+ }
301
+ }
302
+
303
+ $cart_item_data = $cart_item;
304
+
305
+ $woocommerce->cart->add_to_cart( $id, $qty, $cart_item['variation_id'], $variation_data, $cart_item_data );
306
+ }
307
+
308
+ if ( isset( $token_data['wcf_coupon_code'] ) && ! $woocommerce->cart->applied_coupons ) {
309
+ $woocommerce->cart->add_discount( $token_data['wcf_coupon_code'] );
310
+ }
311
+ }
312
+ $other_fields = unserialize( $result->other_fields );
313
+
314
+ $parts = explode( ',', $other_fields['wcf_location'] );
315
+ if ( count( $parts ) > 1 ) {
316
+ $country = $parts[0];
317
+ $city = trim( $parts[1] );
318
+ } else {
319
+ $country = $parts[0];
320
+ $city = '';
321
+ }
322
+
323
+ foreach ( $other_fields as $key => $value ) {
324
+ $key = str_replace( 'wcf_', '', $key );
325
+ $_POST[ $key ] = sanitize_text_field( $value );
326
+ }
327
+ $_POST['billing_first_name'] = sanitize_text_field( $other_fields['wcf_first_name'] );
328
+ $_POST['billing_last_name'] = sanitize_text_field( $other_fields['wcf_last_name'] );
329
+ $_POST['billing_phone'] = sanitize_text_field( $other_fields['wcf_phone_number'] );
330
+ $_POST['billing_email'] = sanitize_email( $result->email );
331
+ $_POST['billing_city'] = sanitize_text_field( $city );
332
+ $_POST['billing_country'] = sanitize_text_field( $country );
333
+
334
+ }
335
+ }
336
+ return $fields;
337
+ }
338
+
339
+ /**
340
+ * Add notice to inform user about test email checkout page.
341
+ */
342
+ public function test_email_checkout_page() {
343
+
344
+ $wcf_ac_token = filter_input( INPUT_GET, 'wcf_ac_token', FILTER_SANITIZE_STRING );
345
+ $token_data = $this->wcf_decode_token( $wcf_ac_token );
346
+ if ( is_checkout() && ! is_wc_endpoint_url() && isset( $token_data['wcf_preview_email'] ) && $token_data['wcf_preview_email'] ) {
347
+ wc_print_notice( __( 'This checkout page is generated by WooCommerce Cart Abandonment Recovery plugin from test mail.', 'woo-cart-abandonment-recovery' ), 'notice' );
348
+ }
349
+ }
350
+
351
+
352
+ /**
353
+ * Load cart abandonemnt tracking script.
354
+ *
355
+ * @return void
356
+ */
357
+ public function cart_abandonment_tracking_script() {
358
+
359
+ $wcf_ca_ignore_users = get_option( 'wcf_ca_ignore_users' );
360
+ $current_user = wp_get_current_user();
361
+ $roles = $current_user->roles;
362
+ $role = array_shift( $roles );
363
+ if ( ! empty( $wcf_ca_ignore_users ) ) {
364
+ foreach ( $wcf_ca_ignore_users as $user ) {
365
+ $user = strtolower( $user );
366
+ $role = preg_replace( '/_/', ' ', $role );
367
+ if ( $role === $user ) {
368
+ return;
369
+ }
370
+ }
371
+ }
372
+
373
+ global $post;
374
+ wp_enqueue_script(
375
+ 'cartflows-cart-abandonment-tracking',
376
+ CARTFLOWS_CART_ABANDONMENT_TRACKING_URL . 'assets/js/cart-abandonment-tracking.js',
377
+ array( 'jquery' ),
378
+ CARTFLOWS_CA_VER,
379
+ true
380
+ );
381
+
382
+ $vars = array(
383
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
384
+ '_nonce' => wp_create_nonce( 'cartflows_save_cart_abandonment_data' ),
385
+ '_gdpr_nonce' => wp_create_nonce( 'cartflows_skip_cart_tracking_gdpr' ),
386
+ '_post_id' => get_the_ID(),
387
+ '_show_gdpr_message' => ( wcf_ca()->utils->is_gdpr_enabled() && ! isset( $_COOKIE['wcf_ca_skip_track_data'] ) ),
388
+ '_gdpr_message' => get_option( 'wcf_ca_gdpr_message' ),
389
+ '_gdpr_nothanks_msg' => __( 'No Thanks', 'woo-cart-abandonment-recovery' ),
390
+ '_gdpr_after_no_thanks_msg' => __( 'You won\'t receive further emails from us, thank you!', 'woo-cart-abandonment-recovery' ),
391
+ 'enable_ca_tracking' => true,
392
+ );
393
+
394
+ wp_localize_script( 'cartflows-cart-abandonment-tracking', 'wcf_ca_vars', $vars );
395
+
396
+ }
397
+
398
+ /**
399
+ * Validate the token before use.
400
+ *
401
+ * @param string $token token form the url.
402
+ * @return bool
403
+ */
404
+ public function is_valid_token( $token ) {
405
+ $is_valid = false;
406
+ $token_data = $this->wcf_decode_token( $token );
407
+ if ( is_array( $token_data ) && array_key_exists( 'wcf_session_id', $token_data ) ) {
408
+ $result = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $token_data['wcf_session_id'] );
409
+ if ( isset( $result ) ) {
410
+ $is_valid = true;
411
+ }
412
+ }
413
+ return $is_valid;
414
+ }
415
+
416
+
417
+ /**
418
+ * Execute Zapier webhook for further action inside Zapier.
419
+ *
420
+ * @since 1.0.0
421
+ */
422
+ public function update_order_status() {
423
+
424
+ global $wpdb;
425
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
426
+ $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
427
+ $minutes = wcf_ca()->utils->get_cart_abandonment_tracking_cut_off_time();
428
+ $email_instance = Cartflows_Ca_Email_Schedule::get_instance();
429
+
430
+ /**
431
+ * Delete abandoned cart orders if empty.
432
+ */
433
+ $this->delete_empty_abandoned_order();
434
+
435
+ $wp_current_datetime = current_time( WCF_CA_DATETIME_FORMAT );
436
+ $abandoned_ids = $wpdb->get_results(
437
+ $wpdb->prepare('SELECT `session_id` FROM `' . $cart_abandonment_table . '` WHERE `order_status` = %s AND ADDDATE( `time`, INTERVAL %d MINUTE) <= %s', WCF_CART_NORMAL_ORDER, $minutes, $wp_current_datetime ), ARRAY_A // phpcs:ignore
438
+ );
439
+
440
+ foreach ( $abandoned_ids as $session_id ) {
441
+
442
+ if ( isset( $session_id['session_id'] ) ) {
443
+
444
+ $current_session_id = $session_id['session_id'];
445
+ $email_instance->schedule_emails( $current_session_id );
446
+
447
+ $coupon_code = '';
448
+ $wcf_ca_coupon_code_status = get_option( 'wcf_ca_coupon_code_status' );
449
+
450
+ if ( 'on' === $wcf_ca_coupon_code_status ) {
451
+ $discount_type = get_option( 'wcf_ca_discount_type' );
452
+ $discount_type = $discount_type ? $discount_type : 'percent';
453
+ $amount = get_option( 'wcf_ca_coupon_amount' );
454
+ $amount = $amount ? $amount : WCF_DEFAULT_COUPON_AMOUNT;
455
+ $coupon_expiry_date = get_option( 'wcf_ca_coupon_expiry' );
456
+ $coupon_expiry_unit = get_option( 'wcf_ca_coupon_expiry_unit' );
457
+ $coupon_expiry_date = $coupon_expiry_date ? strtotime( $wp_current_datetime . ' +' . $coupon_expiry_date . ' ' . $coupon_expiry_unit ) : '';
458
+ $free_shipping_coupon = get_option( 'wcf_ca_free_shipping_coupon' );
459
+ $free_shipping = ( isset( $free_shipping_coupon ) ) && ( $free_shipping_coupon->meta_value ) ? 'yes' : 'no';
460
+
461
+ $individual_use_only = get_option( 'wcf_ca_individual_use_only' );
462
+ $individual_use = ( isset( $individual_use_only ) ) && ( $individual_use_only->meta_value ) ? 'yes' : 'no';
463
+
464
+ $coupon_code = $email_instance->generate_coupon_code( $discount_type, $amount, $coupon_expiry_date, $free_shipping, $individual_use );
465
+ }
466
+
467
+ $wpdb->update(
468
+ $cart_abandonment_table,
469
+ array(
470
+ 'order_status' => WCF_CART_ABANDONED_ORDER,
471
+ 'coupon_code' => $coupon_code,
472
+ ),
473
+ array( 'session_id' => $current_session_id )
474
+ );
475
+
476
+ $this->trigger_zapier_webhook( $current_session_id, WCF_CART_ABANDONED_ORDER );
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Send scheduled emails.
482
+ */
483
+ $this->send_emails_to_callback();
484
+
485
+ // Update order status to lost after campaign complete.
486
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
487
+ $wpdb->query(
488
+ $wpdb->prepare(
489
+ "UPDATE $cart_abandonment_table as ca SET order_status = 'lost' WHERE ca.order_status = %s AND DATE(ca.time) <= DATE_SUB( %s , INTERVAL 30 DAY)
490
+ AND ( (SELECT count(*) FROM $email_history_table WHERE ca_session_id = ca.session_id ) =
491
+ (SELECT count(*) FROM $email_history_table WHERE ca_session_id = ca.session_id AND email_sent = 1) )",
492
+ WCF_CART_ABANDONED_ORDER,
493
+ $wp_current_datetime
494
+ )
495
+ );
496
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
497
+
498
+ /**
499
+ * Delete garbage coupons.
500
+ */
501
+ $wcf_ca_auto_delete_coupons = get_option( 'wcf_ca_auto_delete_coupons' );
502
+
503
+ if ( isset( $wcf_ca_auto_delete_coupons ) && 'on' === $wcf_ca_auto_delete_coupons ) {
504
+ Cartflows_Ca_Setting_Functions::get_instance()->delete_used_and_expired_coupons();
505
+ }
506
+
507
+ }
508
+
509
+ /**
510
+ * Send zapier webhook.
511
+ *
512
+ * @param string $session_id session id.
513
+ * @param string $order_status order status.
514
+ */
515
+ public function trigger_zapier_webhook( $session_id, $order_status ) {
516
+
517
+ $checkout_details = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $session_id );
518
+
519
+ if ( $checkout_details && wcf_ca()->utils->is_zapier_trigger_enabled() ) {
520
+ $trigger_details = array();
521
+ $url = get_option( 'wcf_ca_zapier_cart_abandoned_webhook' );
522
+
523
+ $other_details = unserialize( $checkout_details->other_fields );
524
+ $trigger_details['first_name'] = $other_details['wcf_first_name'];
525
+ $trigger_details['last_name'] = $other_details['wcf_last_name'];
526
+ $trigger_details['phone_number'] = $other_details['wcf_phone_number'];
527
+ $trigger_details['billing_address'] = $other_details['wcf_billing_company'] . ' ' . $other_details['wcf_billing_address_1'] . ', ' . $other_details['wcf_billing_state'] . ', ' . $other_details['wcf_location'] . ', ' . $other_details['wcf_billing_postcode'];
528
+ $trigger_details['billing_address'] = trim( $trigger_details['billing_address'], ', ' );
529
+ $trigger_details['shipping_address'] = $other_details['wcf_shipping_company'] . ' ' . $other_details['wcf_shipping_address_1'] . ', ' . $other_details['wcf_shipping_city'] . ', ' . $other_details['wcf_shipping_state'] . ', ' . $other_details['wcf_shipping_postcode'];
530
+ $trigger_details['shipping_address'] = trim( $trigger_details['shipping_address'], ', ' );
531
+ $trigger_details['email'] = $checkout_details->email;
532
+ $token_data = array( 'wcf_session_id' => $checkout_details->session_id );
533
+ $trigger_details['checkout_url'] = Cartflows_Ca_Helper::get_instance()->get_checkout_url( $checkout_details->checkout_id, $token_data );
534
+ $trigger_details['product_names'] = Cartflows_Ca_Helper::get_instance()->get_comma_separated_products( $checkout_details->cart_contents );
535
+ $trigger_details['coupon_code'] = $checkout_details->coupon_code;
536
+ $trigger_details['order_status'] = $order_status;
537
+ $trigger_details['cart_total'] = $checkout_details->cart_total;
538
+ $trigger_details['product_table'] = Cartflows_Ca_Email_Schedule::get_instance()->get_email_product_block( $checkout_details->cart_contents, $checkout_details->cart_total );
539
+
540
+ $trigger_details = apply_filters( 'woo_ca_webhook_trigger_details', $trigger_details );
541
+
542
+ $parameters = http_build_query( $trigger_details );
543
+
544
+ wp_remote_post(
545
+ $url,
546
+ array(
547
+ 'body' => $parameters,
548
+ 'timeout' => '5',
549
+ 'redirection' => '5',
550
+ 'httpversion' => '1.0',
551
+ 'blocking' => true,
552
+ 'headers' => array(),
553
+ 'cookies' => array(),
554
+ )
555
+ );
556
+
557
+ }
558
+ }
559
+
560
+
561
+ /**
562
+ * Sanitize post array.
563
+ *
564
+ * @return array
565
+ */
566
+ public function sanitize_post_data() {
567
+
568
+ $input_post_values = array(
569
+ 'wcf_billing_company' => array(
570
+ 'default' => '',
571
+ 'sanitize' => FILTER_SANITIZE_STRING,
572
+ ),
573
+ 'wcf_email' => array(
574
+ 'default' => '',
575
+ 'sanitize' => FILTER_SANITIZE_EMAIL,
576
+ ),
577
+ 'wcf_billing_address_1' => array(
578
+ 'default' => '',
579
+ 'sanitize' => FILTER_SANITIZE_STRING,
580
+ ),
581
+ 'wcf_billing_address_2' => array(
582
+ 'default' => '',
583
+ 'sanitize' => FILTER_SANITIZE_STRING,
584
+ ),
585
+ 'wcf_billing_state' => array(
586
+ 'default' => '',
587
+ 'sanitize' => FILTER_SANITIZE_STRING,
588
+ ),
589
+ 'wcf_billing_postcode' => array(
590
+ 'default' => '',
591
+ 'sanitize' => FILTER_SANITIZE_STRING,
592
+ ),
593
+ 'wcf_shipping_first_name' => array(
594
+ 'default' => '',
595
+ 'sanitize' => FILTER_SANITIZE_STRING,
596
+ ),
597
+ 'wcf_shipping_last_name' => array(
598
+ 'default' => '',
599
+ 'sanitize' => FILTER_SANITIZE_STRING,
600
+ ),
601
+ 'wcf_shipping_company' => array(
602
+ 'default' => '',
603
+ 'sanitize' => FILTER_SANITIZE_STRING,
604
+ ),
605
+ 'wcf_shipping_country' => array(
606
+ 'default' => '',
607
+ 'sanitize' => FILTER_SANITIZE_STRING,
608
+ ),
609
+ 'wcf_shipping_address_1' => array(
610
+ 'default' => '',
611
+ 'sanitize' => FILTER_SANITIZE_STRING,
612
+ ),
613
+ 'wcf_shipping_address_2' => array(
614
+ 'default' => '',
615
+ 'sanitize' => FILTER_SANITIZE_STRING,
616
+ ),
617
+ 'wcf_shipping_city' => array(
618
+ 'default' => '',
619
+ 'sanitize' => FILTER_SANITIZE_STRING,
620
+ ),
621
+ 'wcf_shipping_state' => array(
622
+ 'default' => '',
623
+ 'sanitize' => FILTER_SANITIZE_STRING,
624
+ ),
625
+ 'wcf_shipping_postcode' => array(
626
+ 'default' => '',
627
+ 'sanitize' => FILTER_SANITIZE_STRING,
628
+ ),
629
+ 'wcf_order_comments' => array(
630
+ 'default' => '',
631
+ 'sanitize' => FILTER_SANITIZE_STRING,
632
+ ),
633
+ 'wcf_name' => array(
634
+ 'default' => '',
635
+ 'sanitize' => FILTER_SANITIZE_STRING,
636
+ ),
637
+ 'wcf_surname' => array(
638
+ 'default' => '',
639
+ 'sanitize' => FILTER_SANITIZE_STRING,
640
+ ),
641
+ 'wcf_phone' => array(
642
+ 'default' => '',
643
+ 'sanitize' => FILTER_SANITIZE_STRING,
644
+ ),
645
+ 'wcf_country' => array(
646
+ 'default' => '',
647
+ 'sanitize' => FILTER_SANITIZE_STRING,
648
+ ),
649
+ 'wcf_city' => array(
650
+ 'default' => '',
651
+ 'sanitize' => FILTER_SANITIZE_STRING,
652
+ ),
653
+ 'wcf_post_id' => array(
654
+ 'default' => 0,
655
+ 'sanitize' => FILTER_SANITIZE_NUMBER_INT,
656
+ ),
657
+
658
+ );
659
+
660
+ $sanitized_post = array();
661
+ foreach ( $input_post_values as $key => $input_post_value ) {
662
+
663
+ if ( isset( $_POST[ $key ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
664
+ $sanitized_post[ $key ] = filter_input( INPUT_POST, $key, $input_post_value['sanitize'] );
665
+ } else {
666
+ $sanitized_post[ $key ] = $input_post_value['default'];
667
+ }
668
+ }
669
+ return $sanitized_post;
670
+
671
+ }
672
+
673
+ /**
674
+ * Save cart abandonment tracking and schedule new event.
675
+ *
676
+ * @since 1.0.0
677
+ */
678
+ public function save_cart_abandonment_data() {
679
+ check_ajax_referer( 'cartflows_save_cart_abandonment_data', 'security' );
680
+ $post_data = $this->sanitize_post_data();
681
+ if ( isset( $post_data['wcf_email'] ) ) {
682
+ $user_email = sanitize_email( $post_data['wcf_email'] );
683
+ global $wpdb;
684
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
685
+
686
+ // Verify if email is already exists.
687
+ $session_id = WC()->session->get( 'wcf_session_id' );
688
+ $session_checkout_details = null;
689
+ if ( isset( $session_id ) ) {
690
+ $session_checkout_details = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $session_id );
691
+ } else {
692
+ $session_checkout_details = $this->get_checkout_details_by_email( $user_email );
693
+ if ( $session_checkout_details ) {
694
+ $session_id = $session_checkout_details->session_id;
695
+ WC()->session->set( 'wcf_session_id', $session_id );
696
+ } else {
697
+ $session_id = md5( uniqid( wp_rand(), true ) );
698
+ }
699
+ }
700
+
701
+ $checkout_details = $this->prepare_abandonment_data( $post_data );
702
+
703
+ if ( isset( $session_checkout_details ) && WCF_CART_COMPLETED_ORDER === $session_checkout_details->order_status ) {
704
+ WC()->session->__unset( 'wcf_session_id' );
705
+ $session_id = md5( uniqid( wp_rand(), true ) );
706
+ }
707
+
708
+ if ( isset( $checkout_details['cart_total'] ) && $checkout_details['cart_total'] > 0 ) {
709
+
710
+ if ( ( ! is_null( $session_id ) ) && ! is_null( $session_checkout_details ) ) {
711
+
712
+ // Updating row in the Database where users Session id = same as prevously saved in Session.
713
+ $wpdb->update(
714
+ $cart_abandonment_table,
715
+ $checkout_details,
716
+ array( 'session_id' => $session_id )
717
+ );
718
+
719
+ } else {
720
+
721
+ $checkout_details['session_id'] = sanitize_text_field( $session_id );
722
+ // Inserting row into Database.
723
+ $wpdb->insert(
724
+ $cart_abandonment_table,
725
+ $checkout_details
726
+ );
727
+
728
+ // Storing session_id in WooCommerce session.
729
+ WC()->session->set( 'wcf_session_id', $session_id );
730
+
731
+ }
732
+ } else {
733
+ $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
734
+ }
735
+
736
+ wp_send_json_success();
737
+ }
738
+ }
739
+
740
+ /**
741
+ * Prepare cart data to save for abandonment.
742
+ *
743
+ * @param array $post_data post data.
744
+ * @return array
745
+ */
746
+ public function prepare_abandonment_data( $post_data = array() ) {
747
+
748
+ if ( function_exists( 'WC' ) ) {
749
+
750
+ // Retrieving cart total value and currency.
751
+ $cart_total = WC()->cart->total;
752
+
753
+ $payment_gateway = WC()->session->chosen_payment_method;
754
+
755
+ // Retrieving cart products and their quantities.
756
+ $products = WC()->cart->get_cart();
757
+ $current_time = current_time( WCF_CA_DATETIME_FORMAT );
758
+ $other_fields = array(
759
+ 'wcf_billing_company' => $post_data['wcf_billing_company'],
760
+ 'wcf_billing_address_1' => $post_data['wcf_billing_address_1'],
761
+ 'wcf_billing_address_2' => $post_data['wcf_billing_address_2'],
762
+ 'wcf_billing_state' => $post_data['wcf_billing_state'],
763
+ 'wcf_billing_postcode' => $post_data['wcf_billing_postcode'],
764
+ 'wcf_shipping_first_name' => $post_data['wcf_shipping_first_name'],
765
+ 'wcf_shipping_last_name' => $post_data['wcf_shipping_last_name'],
766
+ 'wcf_shipping_company' => $post_data['wcf_shipping_company'],
767
+ 'wcf_shipping_country' => $post_data['wcf_shipping_country'],
768
+ 'wcf_shipping_address_1' => $post_data['wcf_shipping_address_1'],
769
+ 'wcf_shipping_address_2' => $post_data['wcf_shipping_address_2'],
770
+ 'wcf_shipping_city' => $post_data['wcf_shipping_city'],
771
+ 'wcf_shipping_state' => $post_data['wcf_shipping_state'],
772
+ 'wcf_shipping_postcode' => $post_data['wcf_shipping_postcode'],
773
+ 'wcf_order_comments' => $post_data['wcf_order_comments'],
774
+ 'wcf_first_name' => $post_data['wcf_name'],
775
+ 'wcf_last_name' => $post_data['wcf_surname'],
776
+ 'wcf_phone_number' => $post_data['wcf_phone'],
777
+ 'wcf_location' => $post_data['wcf_country'] . ', ' . $post_data['wcf_city'],
778
+ );
779
+
780
+ $checkout_details = array(
781
+ 'email' => $post_data['wcf_email'],
782
+ 'cart_contents' => serialize( $products ),
783
+ 'cart_total' => sanitize_text_field( $cart_total ),
784
+ 'time' => sanitize_text_field( $current_time ),
785
+ 'other_fields' => serialize( $other_fields ),
786
+ 'checkout_id' => $post_data['wcf_post_id'],
787
+ );
788
+ }
789
+ return $checkout_details;
790
+ }
791
+
792
+ /**
793
+ * Deletes cart abandonment tracking and scheduled event.
794
+ *
795
+ * @param int $order_id Order ID.
796
+ * @since 1.0.0
797
+ */
798
+ public function delete_cart_abandonment_data( $order_id ) {
799
+
800
+ $acceptable_order_statuses = Cartflows_Ca_Helper::get_instance()->get_acceptable_order_statuses();
801
+
802
+ $order = wc_get_order( $order_id );
803
+ $order_status = $order->get_status();
804
+ if ( ! in_array( $order_status, $acceptable_order_statuses, true ) ) {
805
+ // Proceed if order status in completed or processing.
806
+ return;
807
+ }
808
+
809
+ global $wpdb;
810
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
811
+ $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
812
+
813
+ if ( isset( WC()->session ) ) {
814
+ $session_id = WC()->session->get( 'wcf_session_id' );
815
+
816
+ if ( isset( $session_id ) ) {
817
+ $checkout_details = Cartflows_Ca_Helper::get_instance()->get_checkout_details( $session_id );
818
+
819
+ $has_mail_sent = count( Cartflows_Ca_Helper::get_instance()->fetch_scheduled_emails( $session_id, true ) );
820
+
821
+ if ( ! $has_mail_sent ) {
822
+ $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
823
+ } else {
824
+ if ( $checkout_details && ( WCF_CART_ABANDONED_ORDER === $checkout_details->order_status || WCF_CART_LOST_ORDER === $checkout_details->order_status ) ) {
825
+
826
+ $this->skip_future_emails_when_order_is_completed( $session_id );
827
+
828
+ $this->trigger_zapier_webhook( $session_id, WCF_CART_COMPLETED_ORDER );
829
+
830
+ $order = wc_get_order( $order_id );
831
+ $note = __( 'This order was abandoned & subsequently recovered.', 'woo-cart-abandonment-recovery' );
832
+ $order->add_order_note( $note );
833
+ $order->save();
834
+
835
+ } elseif ( WCF_CART_COMPLETED_ORDER !== $checkout_details->order_status ) {
836
+ // Normal checkout.
837
+
838
+ $billing_email = filter_input( INPUT_POST, 'billing_email', FILTER_SANITIZE_EMAIL );
839
+
840
+ if ( $billing_email ) {
841
+ $order_data = $this->get_captured_data_by_email( $billing_email );
842
+
843
+ if ( ! is_null( $order_data ) ) {
844
+ $existing_cart_contents = unserialize( $order_data->cart_contents );
845
+ $order_cart_contents = unserialize( $checkout_details->cart_contents );
846
+ $existing_cart_products = array_keys( (array) $existing_cart_contents );
847
+ $order_cart_products = array_keys( (array) $order_cart_contents );
848
+ if ( $this->check_if_similar_cart( $existing_cart_products, $order_cart_products ) ) {
849
+ $this->skip_future_emails_when_order_is_completed( $order_data->session_id );
850
+ }
851
+ }
852
+ }
853
+ $wpdb->delete( $cart_abandonment_table, array( 'session_id' => sanitize_key( $session_id ) ) );
854
+ }
855
+ }
856
+ }
857
+ if ( WC()->session ) {
858
+ WC()->session->__unset( 'wcf_session_id' );
859
+ }
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Unschedule future emails for completed orders.
865
+ *
866
+ * @param string $session_id session id.
867
+ * @param bool $skip_complete skip update query.
868
+ */
869
+ public function skip_future_emails_when_order_is_completed( $session_id, $skip_complete = false ) {
870
+
871
+ global $wpdb;
872
+ $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
873
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
874
+
875
+ if ( ! $skip_complete ) {
876
+ $wpdb->update(
877
+ $cart_abandonment_table,
878
+ array(
879
+ 'order_status' => WCF_CART_COMPLETED_ORDER,
880
+ ),
881
+ array(
882
+ 'session_id' => sanitize_key( $session_id ),
883
+ )
884
+ );
885
+ }
886
+
887
+ $wpdb->update(
888
+ $email_history_table,
889
+ array( 'email_sent' => -1 ),
890
+ array(
891
+ 'ca_session_id' => $session_id,
892
+ 'email_sent' => 0,
893
+ )
894
+ );
895
+ }
896
+
897
+ /**
898
+ * Compare cart if similar products.
899
+ *
900
+ * @param array $cart_a cart_a.
901
+ * @param array $cart_b cart_b.
902
+ * @return bool
903
+ */
904
+ public function check_if_similar_cart( $cart_a, $cart_b ) {
905
+ return (
906
+ is_array( $cart_a )
907
+ && is_array( $cart_b )
908
+ && count( $cart_a ) === count( $cart_b )
909
+ && array_diff( $cart_a, $cart_b ) === array_diff( $cart_b, $cart_a )
910
+ );
911
+ }
912
+
913
+ /**
914
+ * Get the checkout details for the user.
915
+ *
916
+ * @param string $email user email.
917
+ * @since 1.0.0
918
+ */
919
+ public function get_checkout_details_by_email( $email ) {
920
+ global $wpdb;
921
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
922
+ $result = $wpdb->get_row(
923
+ $wpdb->prepare('SELECT * FROM `' . $cart_abandonment_table . '` WHERE email = %s AND `order_status` IN ( %s, %s )', $email, WCF_CART_ABANDONED_ORDER, WCF_CART_NORMAL_ORDER ) // phpcs:ignore
924
+ );
925
+ return $result;
926
+ }
927
+
928
+
929
+ /**
930
+ * Get the checkout details for the user.
931
+ *
932
+ * @param string $value value.
933
+ * @since 1.0.0
934
+ */
935
+ public function get_captured_data_by_email( $value ) {
936
+ global $wpdb;
937
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
938
+ $result = $wpdb->get_row(
939
+ $wpdb->prepare(
940
+ 'SELECT * FROM `' . $cart_abandonment_table . '` WHERE email = %s AND `order_status` IN (%s, %s) ORDER BY `time` DESC LIMIT 1', $value, WCF_CART_ABANDONED_ORDER, WCF_CART_LOST_ORDER ) // phpcs:ignore
941
+ );
942
+ return $result;
943
+ }
944
+
945
+
946
+ /**
947
+ * Get the checkout details for the user.
948
+ *
949
+ * @param string $value value.
950
+ * @since 1.0.0
951
+ */
952
+ public function get_tracked_data_without_status( $value ) {
953
+ global $wpdb;
954
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
955
+ $result = $wpdb->get_row(
956
+ $wpdb->prepare(
957
+ 'SELECT * FROM `' . $cart_abandonment_table . '` WHERE email = %s LIMIT 1', $value ) // phpcs:ignore
958
+ );
959
+ return $result;
960
+ }
961
+
962
+ /**
963
+ * Load analytics scripts.
964
+ */
965
+ public function load_admin_cart_abandonment_script() {
966
+
967
+ $wcar_page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
968
+
969
+ if ( WCF_CA_PAGE_NAME !== $wcar_page ) {
970
+ return;
971
+ }
972
+
973
+ // Styles.
974
+ wp_enqueue_style( 'cartflows-cart-abandonment-admin', CARTFLOWS_CA_URL . 'admin/assets/css/admin-cart-abandonment.css', array(), CARTFLOWS_CA_VER );
975
+
976
+ wp_enqueue_script(
977
+ 'cartflows-cart-abandonment-admin',
978
+ CARTFLOWS_CA_URL . 'admin/assets/js/admin-settings.js',
979
+ array( 'jquery' ),
980
+ CARTFLOWS_CA_VER,
981
+ false
982
+ );
983
+
984
+ $vars = array(
985
+ 'url' => 'admin-ajax.php',
986
+
987
+ // For delete coupons.
988
+ '_delete_coupon_nonce' => wp_create_nonce( 'wcf_ca_delete_garbage_coupons' ),
989
+ '_confirm_msg' => __( 'Do you really want to delete the used and expired coupons created by Cart Abandonment Plugin?', 'woo-cart-abandonment-recovery' ),
990
+ '_confirm_msg_export' => __( 'Do you really want to export orders?', 'woo-cart-abandonment-recovery' ),
991
+
992
+ // For Search orders.
993
+ '_search_button_nonce' => wp_create_nonce( 'wcf_ca_search_orders' ),
994
+ '_result_msg' => __( 'No such order is found.', 'woo-cart-abandonment-recovery' ),
995
+
996
+ );
997
+ wp_localize_script( 'cartflows-ca-email-tmpl-settings', 'wcf_ca_localized_vars', $vars );
998
+ }
999
+
1000
+ /**
1001
+ * Decode and get the original contents.
1002
+ *
1003
+ * @param string $token token.
1004
+ */
1005
+ public function wcf_decode_token( $token ) {
1006
+ $token = sanitize_text_field( $token );
1007
+ parse_str( base64_decode( urldecode( $token ) ), $token );
1008
+ return $token;
1009
+ }
1010
+
1011
+ /**
1012
+ * Callback trigger event to send the emails.
1013
+ */
1014
+ public function send_emails_to_callback() {
1015
+
1016
+ global $wpdb;
1017
+ $email_history_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_HISTORY_TABLE;
1018
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1019
+ $email_template_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_TEMPLATE_TABLE;
1020
+
1021
+ $current_time = current_time( WCF_CA_DATETIME_FORMAT );
1022
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
1023
+ $emails_send_to = $wpdb->get_results(
1024
+ $wpdb->prepare(
1025
+ 'SELECT *, EHT.id as email_history_id, ETT.id as email_template_id FROM ' . $email_history_table . ' as EHT
1026
+ INNER JOIN ' . $cart_abandonment_table . ' as CAT ON EHT.`ca_session_id` = CAT.`session_id`
1027
+ INNER JOIN ' . $email_template_table . ' as ETT ON ETT.`id` = EHT.`template_id`
1028
+ WHERE CAT.`order_status` = %s AND CAT.unsubscribed = 0 AND EHT.`email_sent` = 0 AND EHT.`scheduled_time` <= %s',
1029
+ WCF_CART_ABANDONED_ORDER,
1030
+ $current_time
1031
+ )
1032
+ );
1033
+ // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
1034
+ foreach ( $emails_send_to as $email_send_to ) {
1035
+ $email_result = Cartflows_Ca_Email_Schedule::get_instance()->send_email_templates( $email_send_to );
1036
+ if ( $email_result ) {
1037
+ $wpdb->update(
1038
+ $email_history_table,
1039
+ array( 'email_sent' => true ),
1040
+ array( 'id' => $email_send_to->email_history_id )
1041
+ );
1042
+ }
1043
+ }
1044
+ }
1045
+
1046
+ /**
1047
+ * Delete orders from cart abandonment table whose cart total is zero and order status is abandoned.
1048
+ */
1049
+ public function delete_empty_abandoned_order() {
1050
+ global $wpdb;
1051
+
1052
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
1053
+
1054
+ $where = array(
1055
+ 'cart_total' => 0,
1056
+ );
1057
+
1058
+ $wpdb->delete( $cart_abandonment_table, $where );
1059
+ }
1060
+ }
1061
+
1062
+ Cartflows_Ca_Tracking::get_instance();
modules/cart-abandonment/includes/{admin/cartflows-ca-single-report-details.php → cartflows-ca-single-report-details.php} RENAMED
@@ -225,7 +225,7 @@ if ( ! defined( 'ABSPATH' ) ) {
225
  </p>
226
  <p>
227
  <?php
228
- $cart_abandonment = Cartflows_Ca_Cart_Abandonment::get_instance();
229
  $token_data = array( 'wcf_session_id' => $details->session_id );
230
  ?>
231
  <strong> <a target="_blank" href=" <?php echo $cart_abandonment->get_checkout_url( $details->checkout_id, $token_data ); //phpcs:ignore?> ">
225
  </p>
226
  <p>
227
  <?php
228
+ $cart_abandonment = Cartflows_Ca_Helper::get_instance();
229
  $token_data = array( 'wcf_session_id' => $details->session_id );
230
  ?>
231
  <strong> <a target="_blank" href=" <?php echo $cart_abandonment->get_checkout_url( $details->checkout_id, $token_data ); //phpcs:ignore?> ">
modules/cart-abandonment/includes/{admin/cartflows-cart-abandonment-reports.php → cartflows-cart-abandonment-reports.php} RENAMED
File without changes
modules/cart-abandonment/includes/{admin/cartflows-cart-abandonment-tabs.php → cartflows-cart-abandonment-tabs.php} RENAMED
@@ -9,6 +9,8 @@ if ( ! defined( 'ABSPATH' ) ) {
9
  exit; // Exit if accessed directly.
10
  }
11
 
 
 
12
  ?>
13
  <div class="wrap">
14
  <h1 id="wcf_cart_abandonment_tracking_table"><?php echo esc_html__( 'WooCommerce Cart Abandonment Recovery ', 'woo-cart-abandonment-recovery' ); ?></h1>
@@ -45,11 +47,11 @@ if ( ! defined( 'ABSPATH' ) ) {
45
  $this->wcf_display_report_details();
46
  break;
47
  case WCF_SUB_ACTION_REPORTS_RESCHEDULE:
48
- $ca_obj = Cartflows_Ca_Cart_Abandonment::get_instance();
49
 
50
  $session_id = filter_input( INPUT_GET, 'session_id', FILTER_SANITIZE_STRING );
51
  if ( $session_id ) {
52
- $ca_obj->schedule_emails( $session_id, true );
53
  }
54
 
55
  $param = array(
9
  exit; // Exit if accessed directly.
10
  }
11
 
12
+
13
+
14
  ?>
15
  <div class="wrap">
16
  <h1 id="wcf_cart_abandonment_tracking_table"><?php echo esc_html__( 'WooCommerce Cart Abandonment Recovery ', 'woo-cart-abandonment-recovery' ); ?></h1>
47
  $this->wcf_display_report_details();
48
  break;
49
  case WCF_SUB_ACTION_REPORTS_RESCHEDULE:
50
+ $email_schedule = Cartflows_Ca_Email_Schedule::get_instance();
51
 
52
  $session_id = filter_input( INPUT_GET, 'session_id', FILTER_SANITIZE_STRING );
53
  if ( $session_id ) {
54
+ $email_schedule->schedule_emails( $session_id, true );
55
  }
56
 
57
  $param = array(
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: sujaypawar, wpcrafter
3
  Tags: woocommerce, cart abandonment, cart recovery
4
  Requires at least: 5.4
5
- Tested up to: 5.9.0
6
- Stable tag: 1.2.13
7
  Requires PHP: 5.6
8
  License: GPLv2 or later
9
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -128,6 +128,14 @@ Here are few thoughts behind making it available for free:
128
 
129
  == Changelog ==
130
 
 
 
 
 
 
 
 
 
131
  = Version 1.2.13 - Tuesday, 13th July 2021 =
132
  * Fix: Some strings of the plugin were not translatable.
133
 
@@ -255,4 +263,4 @@ Here are few thoughts behind making it available for free:
255
  = Version 1.0.0 - Monday, 27th May 2019 =
256
  * Initial Release
257
 
258
- == Upgrade Notice ==
2
  Contributors: sujaypawar, wpcrafter
3
  Tags: woocommerce, cart abandonment, cart recovery
4
  Requires at least: 5.4
5
+ Tested up to: 5.9.2
6
+ Stable tag: 1.2.14
7
  Requires PHP: 5.6
8
  License: GPLv2 or later
9
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
128
 
129
  == Changelog ==
130
 
131
+ = Version 1.2.14 - Tuesday, 5th April 2022 =
132
+ * New: Added cron cutoff time option in settings.
133
+ Note: If you are using the custom code to update the cron time then please remove it & update same in new option.
134
+ * New: Added an option to append the query parameters to recovery link.
135
+ * Improvement: Showing large product images in outlook in some cases.
136
+ * Improvement: Added filter to update the default first name of customer.
137
+ * Fix: Product price not showing as configured in Woocommerce settings.
138
+
139
  = Version 1.2.13 - Tuesday, 13th July 2021 =
140
  * Fix: Some strings of the plugin were not translatable.
141
 
263
  = Version 1.0.0 - Monday, 27th May 2019 =
264
  * Initial Release
265
 
266
+ == Upgrade Notice ==
woo-cart-abandonment-recovery.php CHANGED
@@ -3,12 +3,12 @@
3
  * Plugin Name: WooCommerce Cart Abandonment Recovery
4
  * Plugin URI: https://cartflows.com/
5
  * Description: Recover your lost revenue. Capture email address of users on the checkout page and send follow up emails if they don't complete the purchase.
6
- * Version: 1.2.13
7
  * Author: CartFlows Inc
8
  * Author URI: https://cartflows.com/
9
  * Text Domain: woo-cart-abandonment-recovery
10
  * WC requires at least: 3.0
11
- * WC tested up to: 5.5.0
12
  *
13
  * @package Woocommerce-Cart-Abandonment-Recovery
14
  */
3
  * Plugin Name: WooCommerce Cart Abandonment Recovery
4
  * Plugin URI: https://cartflows.com/
5
  * Description: Recover your lost revenue. Capture email address of users on the checkout page and send follow up emails if they don't complete the purchase.
6
+ * Version: 1.2.14
7
  * Author: CartFlows Inc
8
  * Author URI: https://cartflows.com/
9
  * Text Domain: woo-cart-abandonment-recovery
10
  * WC requires at least: 3.0
11
+ * WC tested up to: 6.3.1
12
  *
13
  * @package Woocommerce-Cart-Abandonment-Recovery
14
  */