Printful Integration for WooCommerce - Version 2.0

Version Description

New major plugin version * All new Printful dashboard * Connect to Printful with a single click * View your Printful profits and latest Printful product orders in WordPress admin * Edit your shipping carriers from Printful dashboard * Improved sales tax compatibility with existing tax rates * New status page - see if your integration is running smoothly * New support page - all info about finding help in one place * Size chart tab - when pushing products from Printful, the size chart will be placed in a separate tab * Improved logging of API requests coming to and from Printful

Download this release

Release Info

Developer printful
Plugin Icon 128x128 Printful Integration for WooCommerce
Version 2.0
Comparing to
See all releases

Code changes from version 1.2.8 to 2.0

Files changed (42) hide show
  1. assets/css/dashboard.css +181 -0
  2. assets/css/global.css +19 -0
  3. assets/css/settings.css +40 -0
  4. assets/css/status.css +18 -0
  5. assets/css/support.css +62 -0
  6. assets/images/connect.svg +74 -0
  7. assets/images/printful-logo-footer.png +0 -0
  8. assets/images/printful-menu-icon.png +0 -0
  9. assets/js/block-loader.js +22 -0
  10. assets/js/connect.js +51 -0
  11. assets/js/intercom.min.js +12 -0
  12. assets/js/settings.js +42 -0
  13. includes/class-printful-admin-dashboard.php +267 -0
  14. includes/class-printful-admin-settings.php +250 -0
  15. includes/class-printful-admin-status.php +541 -0
  16. includes/class-printful-admin-support.php +235 -0
  17. includes/class-printful-admin.php +167 -0
  18. includes/class-printful-api-resource.php +109 -22
  19. includes/class-printful-carriers.php +88 -0
  20. includes/class-printful-client.php +110 -91
  21. includes/class-printful-integration.php +147 -238
  22. includes/class-printful-request-log.php +142 -0
  23. includes/class-printful-shipping.php +308 -266
  24. includes/class-printful-size-chart-tab.php +132 -0
  25. includes/class-printful-taxes.php +188 -0
  26. includes/templates/ajax-loader.php +11 -0
  27. includes/templates/connect.php +40 -0
  28. includes/templates/error.php +1 -0
  29. includes/templates/footer.php +1 -0
  30. includes/templates/header.php +20 -0
  31. includes/templates/inline-script.php +3 -0
  32. includes/templates/order-table.php +69 -0
  33. includes/templates/quick-links.php +32 -0
  34. includes/templates/setting-group.php +59 -0
  35. includes/templates/setting-submit.php +21 -0
  36. includes/templates/shipping-notification.php +4 -0
  37. includes/templates/stats.php +44 -0
  38. includes/templates/status-report.php +19 -0
  39. includes/templates/status-table.php +54 -0
  40. includes/templates/support-info.php +27 -0
  41. printful-shipping.php +114 -56
  42. readme.txt +159 -132
assets/css/dashboard.css ADDED
@@ -0,0 +1,181 @@
1
+ .printful-connect {
2
+ position: relative;
3
+ height: 100%;
4
+ }
5
+
6
+ .printful-connect-inner {
7
+ max-width: 600px;
8
+ margin: 100px auto 0 auto;
9
+ text-align: center;
10
+ position: relative;
11
+ }
12
+ .printful-connect .connect-image {
13
+ max-width: 100%;
14
+ }
15
+ .printful-connect h1 {
16
+ margin-bottom: 30px;
17
+ font-size: 34px;
18
+ }
19
+ .printful-connect .connect-description {
20
+ margin: 20px 0 30px 0;
21
+ }
22
+
23
+ /*noinspection CssUnusedSymbol*/
24
+ .printful-connect .printful-connect-button {
25
+ height: 53px;
26
+ line-height: 53px;
27
+ padding: 0 40px;
28
+ font-size: 18px;
29
+ }
30
+
31
+ .printful-stats {
32
+ position: relative;
33
+ margin: 20px 0 50px 0;
34
+ background-color: #fff;
35
+ padding: 30px 20px;
36
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
37
+ border-left: #0073aa 4px solid;
38
+ }
39
+ .printful-stats .printful-stats-item {
40
+ width: 49%;
41
+ display: inline-block;
42
+ margin-bottom: 25px;
43
+ box-sizing: border-box;
44
+ vertical-align: top;
45
+ padding: 0 8px;
46
+ }
47
+
48
+ .printful-stats .printful-stats-item h4 {
49
+ margin: 0 0 10px 0;
50
+ font-size: 24px;
51
+ line-height: 26px;
52
+ }
53
+ .printful-stats .printful-stats-item h4 .dashicons {
54
+ font-size: 26px;
55
+ width: 26px;
56
+ height: 26px;
57
+ }
58
+
59
+ /*noinspection ALL*/
60
+ .printful-stats .printful-stats-item h4 .dashicons-arrow-up-alt {
61
+ color: green;
62
+ }
63
+ .printful-stats .printful-stats-item h4 .dashicons-arrow-down-alt {
64
+ color: #d54e21;
65
+ }
66
+
67
+ @media (min-width: 768px) {
68
+ .printful-stats .printful-stats-item {
69
+ width: 24%;
70
+ margin-bottom: 0;
71
+ padding: 0 25px;
72
+ }
73
+ .printful-stats .printful-stats-item h4 {
74
+ font-size: 32px;
75
+ line-height: 36px;
76
+ }
77
+ .printful-stats .printful-stats-item h4 .dashicons {
78
+ padding-top: 4px;
79
+ font-size: 30px;
80
+ width: 30px;
81
+ height: 30px;
82
+ }
83
+ }
84
+
85
+ .printful-latest-orders {
86
+ display: table;
87
+ width: 100%;
88
+ margin-bottom: 50px;
89
+ }
90
+ .printful-latest-orders .row {
91
+ display: table-row;
92
+ }
93
+ .printful-latest-orders .row .cell {
94
+ display: table-cell;
95
+ }
96
+
97
+ .wrap h2.nav-tab-wrapper.printful-tabs {
98
+ margin-bottom: 15px;
99
+ }
100
+
101
+
102
+ .printful-quick-links {
103
+ position: relative;
104
+ margin-bottom: 30px;
105
+ }
106
+ .printful-quick-links .printful-quick-links-item {
107
+ width: 120px;
108
+ background: #fff;
109
+ border: solid 1px #e1e1e1;
110
+ float: left;
111
+ padding: 20px;
112
+ margin-right: 25px;
113
+ margin-bottom: 25px;
114
+ text-align: center;
115
+ text-decoration: none;
116
+ }
117
+ .printful-quick-links .printful-quick-links-item .dashicons {
118
+ font-size: 40px;
119
+ width: 40px;
120
+ height: 40px;
121
+ text-decoration: none;
122
+ color: #444;
123
+ }
124
+ .printful-quick-links .printful-quick-links-item h4 {
125
+ text-decoration: none;
126
+ color: #444;
127
+ margin: 5px 0 0 0;
128
+ }
129
+ .printful-quick-links .printful-quick-links-item:hover {
130
+ cursor: pointer;
131
+ }
132
+ .printful-quick-links .printful-quick-links-item:hover .dashicons,
133
+ .printful-quick-links .printful-quick-links-item:hover h4 {
134
+ color: #0D72B2;
135
+ }
136
+
137
+ .printful-error {
138
+ margin-top: 20px;
139
+ }
140
+
141
+ .printful-notice {
142
+ background: #fff;
143
+ border-left: 4px solid #dc3232;
144
+ padding: 1px 12px;
145
+ margin: 10px 0 20px 0;
146
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
147
+ text-align: left;
148
+ }
149
+ .printful-notice li {
150
+ font-weight: bold;
151
+ margin-left: 20px;
152
+ list-style: disc;
153
+ }
154
+
155
+ .block-loader {
156
+ text-align: center;
157
+ padding: 20px 0;
158
+ line-height: 20px;
159
+ }
160
+ .block-loader .message {
161
+ padding-left: 10px;
162
+ line-height: 20px;
163
+ display: inline-block;
164
+ font-size: 16px;
165
+ }
166
+ .block-loader .loader {
167
+ vertical-align: top;
168
+ }
169
+
170
+ #wpfooter::after {
171
+ box-sizing: border-box;
172
+ content: "";
173
+ width: 64px;
174
+ left: 50%;
175
+ margin-left: -32px;
176
+ height: 28px;
177
+ position: absolute;
178
+ bottom: 0;
179
+ background: url("../images/printful-logo-footer.png") no-repeat bottom center;
180
+ background-size: 64px;
181
+ }
assets/css/global.css ADDED
@@ -0,0 +1,19 @@
1
+ .printful-toolbar .printful-toolbar-issues {
2
+ background-color: #d54e21;
3
+ display: inline-block;
4
+ padding: 0 5px !important;
5
+ border-radius: 10px !important;
6
+ line-height: 16px !important;
7
+ font-size: 11px !important;
8
+ color: #fff !important;
9
+ }
10
+ .printful-toolbar:hover .printful-toolbar-issues,
11
+ .printful-toolbar:active .printful-toolbar-issues,
12
+ .printful-toolbar.hover .printful-toolbar-issues
13
+ {
14
+ color: #fff !important;
15
+ }
16
+
17
+ #adminmenu .toplevel_page_printful-dashboard img {
18
+ width: 28px;
19
+ }
assets/css/settings.css ADDED
@@ -0,0 +1,40 @@
1
+ .printful-setting-group {
2
+ overflow: hidden;
3
+ margin-bottom: 20px;
4
+ }
5
+
6
+ .printful-setting-group .input-text {
7
+ width: 400px;
8
+ }
9
+
10
+ .printful-setting-group .checkbox-group-item i {
11
+ color: #777;
12
+ }
13
+
14
+ .printful-submit .woocommerce-save-button {
15
+ float: left;
16
+ }
17
+
18
+ .printful-submit .loader-wrap {
19
+ display: inline-block;
20
+ padding: 4px 10px;
21
+ float: left;
22
+ }
23
+
24
+ .printful-submit .loader-wrap .loader {
25
+ display: none;
26
+ float: left;
27
+ }
28
+
29
+ .printful-submit .loader-wrap .dashicons {
30
+ font-size: 24px;
31
+ width: 24px;
32
+ height: 24px;
33
+ }
34
+
35
+ .printful-submit .loader-wrap .pass,
36
+ .printful-submit .loader-wrap .fail {
37
+ display: none;
38
+ float: left;
39
+ line-height: 24px;
40
+ }
assets/css/status.css ADDED
@@ -0,0 +1,18 @@
1
+ .pass {
2
+ font-weight: bold;
3
+ color: green;
4
+ }
5
+ .fail {
6
+ font-weight: bold;
7
+ color: red;
8
+ }
9
+ .warning {
10
+ font-weight: bold;
11
+ color: #cab827;
12
+ }
13
+ .printful-status .col-status {
14
+ width: 10%;
15
+ }
16
+ .printful-status .col-desc {
17
+ width: 57%;
18
+ }
assets/css/support.css ADDED
@@ -0,0 +1,62 @@
1
+ .support-report-wrap {
2
+ background-color: #fff;
3
+ padding: 20px;
4
+ margin: 20px 0;
5
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
6
+ border-left: #0073aa 4px solid;
7
+ }
8
+
9
+ .support-report-wrap p {
10
+ margin-top: 0;
11
+ }
12
+
13
+ .support-report-wrap .support-report {
14
+ width: 100%;
15
+ height: 100px;
16
+ font-family: monospace;
17
+ margin-bottom: 10px;
18
+ background: #efefef;
19
+ }
20
+
21
+ .support-info-wrap {
22
+ margin-top: 40px;
23
+ box-sizing: border-box;
24
+
25
+ }
26
+
27
+ .support-info-wrap .support-info-block {
28
+ box-sizing: border-box;
29
+ padding: 20px;
30
+ background: #fff;
31
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
32
+ margin-bottom: 20px;
33
+ }
34
+
35
+ .support-info-wrap .support-info-block h3 {
36
+ text-align: center;
37
+ margin-top: 10px;
38
+ }
39
+
40
+ .support-info-wrap .support-info-block .btn-wrap {
41
+ margin-top: 20px;
42
+ text-align: center;
43
+ }
44
+
45
+ @media (min-width: 768px) {
46
+
47
+ .support-info-wrap {
48
+ justify-content: space-between;
49
+ display: flex;
50
+ }
51
+
52
+ .support-info-wrap .support-info-block {
53
+ width: 32%;
54
+ }
55
+
56
+ .support-info-wrap .support-info-block:first-child {
57
+
58
+ }
59
+ .support-info-wrap .support-info-block:last-child {
60
+
61
+ }
62
+ }
assets/images/connect.svg ADDED
@@ -0,0 +1,74 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="408" height="177" viewBox="0 0 408 177">
2
+ <g fill="none" fill-rule="evenodd" transform="translate(1 1)">
3
+ <path fill="#FFF" fill-rule="nonzero" stroke="#231F20" stroke-width="2" d="M299 120h107v8H299zM0 120h107v8H0z"/>
4
+ <path stroke="#D1D3D4" d="M197.5 21V10.2c0-5.5-4.1-9.8-9.6-9.8h-124c-5.5 0-10.4 4.2-10.4 9.8V87M208.5 21V10.2c0-5.5 4.9-9.8 10.4-9.8h124c5.5 0 9.6 4.2 9.6 9.8V87"/>
5
+ <path fill="#FFF" fill-rule="nonzero" stroke="#231F20" stroke-width="2" d="M5 48h97v72H5z"/>
6
+ <g fill-rule="nonzero">
7
+ <path fill="#9B5C8F" d="M27.104 70h44.77C74.706 70 77 72.312 77 75.168v17.225c0 2.856-2.293 5.168-5.127 5.168H55.82L58.022 103l-9.691-5.44H27.127C24.294 97.56 22 95.25 22 92.394V75.168C21.978 72.334 24.27 70 27.104 70z"/>
8
+ <path fill="#FFF" d="M25.389 74.664c.316-.431.79-.658 1.422-.703 1.152-.091 1.807.453 1.965 1.633.7 4.743 1.468 8.76 2.28 12.05l4.946-9.463c.451-.862 1.016-1.316 1.693-1.361.994-.068 1.603.567 1.852 1.906.565 3.017 1.287 5.582 2.145 7.76.587-5.763 1.58-9.916 2.98-12.48.34-.635.836-.953 1.491-.998.52-.046.994.113 1.423.453.429.34.655.772.7 1.294.022.408-.045.749-.226 1.09-.88 1.633-1.603 4.378-2.19 8.19-.565 3.699-.768 6.58-.633 8.646.046.567-.045 1.066-.27 1.497-.272.5-.678.772-1.197.817-.588.046-1.197-.227-1.784-.84-2.1-2.155-3.771-5.377-4.99-9.666a425.409 425.409 0 0 0-3.252 6.535c-1.333 2.564-2.462 3.88-3.41 3.948-.61.046-1.13-.476-1.58-1.565-1.152-2.973-2.394-8.714-3.726-17.223-.09-.59.044-1.112.36-1.52zm48.526 3.563c-.813-1.43-2.01-2.292-3.613-2.633a5.904 5.904 0 0 0-1.22-.136c-2.167 0-3.928 1.135-5.306 3.404-1.174 1.929-1.76 4.062-1.76 6.399 0 1.747.36 3.244 1.083 4.493.813 1.43 2.01 2.291 3.613 2.632.429.09.835.136 1.22.136 2.19 0 3.95-1.135 5.306-3.404 1.174-1.951 1.761-4.084 1.761-6.421.023-1.77-.361-3.245-1.084-4.47zm-2.845 6.285c-.316 1.498-.88 2.61-1.716 3.358-.655.59-1.265.84-1.83.726-.541-.113-.993-.59-1.331-1.475a5.778 5.778 0 0 1-.407-2.065c0-.567.045-1.134.158-1.656a7.357 7.357 0 0 1 1.197-2.7c.745-1.112 1.535-1.566 2.348-1.407.542.113.994.59 1.333 1.475.27.703.406 1.407.406 2.065 0 .59-.045 1.157-.158 1.679zm-11.29-6.285c-.813-1.43-2.033-2.292-3.613-2.633a5.902 5.902 0 0 0-1.22-.136c-2.168 0-3.929 1.135-5.306 3.404-1.174 1.929-1.762 4.062-1.762 6.399 0 1.747.362 3.244 1.084 4.493.813 1.43 2.01 2.291 3.613 2.632.43.09.835.136 1.22.136 2.19 0 3.951-1.135 5.306-3.404 1.174-1.951 1.761-4.084 1.761-6.421 0-1.77-.361-3.245-1.084-4.47zm-2.868 6.285c-.316 1.498-.881 2.61-1.716 3.358-.655.59-1.265.84-1.83.726-.541-.113-.993-.59-1.332-1.475a5.787 5.787 0 0 1-.406-2.065c0-.567.045-1.134.158-1.656a7.354 7.354 0 0 1 1.197-2.7c.745-1.112 1.535-1.566 2.348-1.407.542.113.994.59 1.332 1.475.272.703.407 1.407.407 2.065a6.726 6.726 0 0 1-.158 1.679z"/>
9
+ </g>
10
+ <path fill="#FFF" fill-rule="nonzero" stroke="#231F20" stroke-width="2" d="M304 48h97v72h-97z"/>
11
+ <g fill-rule="nonzero">
12
+ <path fill="#F2C994" d="M320 98l17.5-30L355 98z"/>
13
+ <path fill="#ED4642" d="M336 98l17.5-30L371 98z"/>
14
+ <path fill="#17BCB5" d="M352 98l17.5-30L387 98z"/>
15
+ <path fill="#16342F" d="M352 98h19l-9.5-16z"/>
16
+ <path fill="#DF392F" d="M336 98h19l-9.5-16z"/>
17
+ <path fill="#15291B" d="M352 98h3l-1.5-3z"/>
18
+ </g>
19
+ <path fill="#FFF" fill-rule="nonzero" stroke="#231F20" stroke-width="2" d="M155 21h97v124h-97z"/>
20
+ <path fill="#FFF" fill-rule="nonzero" stroke="#231F20" stroke-width="2" d="M150 142h107v33H150z"/>
21
+ <path stroke="#D1D3D4" d="M162.5 161.8l1.3-3.4 2 3.6 5.3-9.2 4 9.2 2.7-4.6 3.3 2.6"/>
22
+ <path stroke="#231F20" d="M161.5 148.5h20v20h-20zM187.5 148.5h61v20h-61z"/>
23
+ <circle cx="191.2" cy="33.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
24
+ <path stroke="#FFF" d="M191.2 33.8l.7-3.8"/>
25
+ <circle cx="191.2" cy="44.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
26
+ <path stroke="#FFF" d="M191.2 44.8l2.3 3"/>
27
+ <circle cx="191.2" cy="55.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
28
+ <path stroke="#FFF" d="M191.2 55.8l-3 2.3"/>
29
+ <circle cx="191.2" cy="66.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
30
+ <path stroke="#FFF" d="M191.2 66.8l2.3 3"/>
31
+ <circle cx="191.2" cy="77.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
32
+ <path stroke="#FFF" d="M191.2 77.8l3.6 1.2"/>
33
+ <path fill="#F1F2F2" fill-rule="nonzero" stroke="#231F20" d="M183.5 87.5h7v3h-7zM193.5 87.5h7v3h-7zM203.5 87.5h7v3h-7zM213.5 87.5h7v3h-7zM223.5 87.5h7v3h-7z"/>
34
+ <path fill="#F2C994" fill-rule="nonzero" stroke="#231F20" d="M233.5 87.5h7v3h-7z"/>
35
+ <path stroke="#231F20" stroke-width="2" d="M184 30v52"/>
36
+ <circle cx="214.2" cy="33.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
37
+ <path stroke="#FFF" d="M214.2 33.8l2.3 3"/>
38
+ <circle cx="214.2" cy="44.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
39
+ <path stroke="#FFF" d="M214.2 44.8l3-2.4"/>
40
+ <circle cx="214.2" cy="55.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
41
+ <path stroke="#FFF" d="M214.2 55.8l2.3 3"/>
42
+ <circle cx="214.2" cy="66.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
43
+ <path stroke="#FFF" d="M214.2 66.8l-3 2.3"/>
44
+ <circle cx="214.2" cy="77.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
45
+ <path stroke="#FFF" d="M214.2 77.8l2.3 3"/>
46
+ <path stroke="#231F20" stroke-width="2" d="M207 30v52"/>
47
+ <circle cx="237.2" cy="33.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
48
+ <path stroke="#FFF" d="M237.2 33.8l3.7.8"/>
49
+ <circle cx="237.2" cy="44.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
50
+ <path stroke="#FFF" d="M237.2 44.8l2.3 3"/>
51
+ <circle cx="237.2" cy="55.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
52
+ <path stroke="#FFF" d="M237.2 55.8l.3 3.8"/>
53
+ <circle cx="237.2" cy="66.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
54
+ <path stroke="#FFF" d="M237.2 66.8l3.4-1.7"/>
55
+ <circle cx="237.2" cy="77.8" r="4" fill="#D1D3D4" fill-rule="nonzero"/>
56
+ <path stroke="#FFF" d="M237.2 77.8l2.3 3"/>
57
+ <path stroke="#231F20" stroke-width="2" d="M230 30v52M184 96v34M207 96v34"/>
58
+ <path fill="#F1F2F2" fill-rule="nonzero" stroke="#231F20" d="M179.5 109.5h9v3h-9zM202.5 125.5h9v4h-9z"/>
59
+ <path stroke="#231F20" stroke-width="2" d="M230 96v34"/>
60
+ <path fill="#F1F2F2" fill-rule="nonzero" stroke="#231F20" d="M225.5 114.5h9v4h-9z"/>
61
+ <circle cx="169.5" cy="35.8" r="5" fill="#F2C994" fill-rule="nonzero" stroke="#231F20" stroke-width="2"/>
62
+ <circle cx="169.5" cy="48.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
63
+ <circle cx="169.5" cy="59.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
64
+ <circle cx="169.5" cy="70.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
65
+ <circle cx="169.5" cy="81.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
66
+ <circle cx="169.5" cy="99.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
67
+ <circle cx="169.5" cy="113.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
68
+ <circle cx="169.5" cy="127.8" r="3" fill="#D1D3D4" fill-rule="nonzero"/>
69
+ <circle cx="194.2" cy="152.8" r="2" fill="#F2C994" fill-rule="nonzero"/>
70
+ <circle cx="210.2" cy="162.8" r="2" fill="#F2C994" fill-rule="nonzero"/>
71
+ <path stroke="#231F20" d="M187 156.5h61"/>
72
+ <circle cx="239.2" cy="159.8" r="2" fill="#F2C994" fill-rule="nonzero"/>
73
+ </g>
74
+ </svg>
assets/images/printful-logo-footer.png ADDED
Binary file
assets/images/printful-menu-icon.png ADDED
Binary file
assets/js/block-loader.js ADDED
@@ -0,0 +1,22 @@
1
+ var Printful_Block_Loader;
2
+
3
+ (function () {
4
+ 'use strict';
5
+
6
+ Printful_Block_Loader = {
7
+ load: function (ajax_url, block) {
8
+
9
+ block = jQuery('#' + block);
10
+ if (block.length > 0) {
11
+
12
+ jQuery.ajax({
13
+ type: "GET",
14
+ url: ajax_url,
15
+ success: function (response) {
16
+ block.html(response);
17
+ }
18
+ });
19
+ }
20
+ }
21
+ };
22
+ })();
assets/js/connect.js ADDED
@@ -0,0 +1,51 @@
1
+ var Printful_Connect;
2
+
3
+ (function () {
4
+ 'use strict';
5
+
6
+ Printful_Connect = {
7
+ interval: 0,
8
+ ajax_url: '',
9
+ init: function (ajax_url) {
10
+ this.ajax_url = ajax_url;
11
+ this.loader();
12
+ this.listen_status();
13
+ this.listen_auth_return();
14
+ },
15
+ loader: function () {
16
+ jQuery('.printful-connect-button').click(function () {
17
+ jQuery(this).hide();
18
+ jQuery(this).siblings('.loader').removeClass('hidden');
19
+ });
20
+ },
21
+ listen_status: function () {
22
+ this.interval = setInterval(this.get_status.bind(this), 10000); //check status every 10 secs
23
+ },
24
+ get_status: function () {
25
+ var interval = this.interval;
26
+ jQuery.ajax( {
27
+ type: "GET",
28
+ url: this.ajax_url,
29
+ success: function( response ) {
30
+ if (response === 'OK') {
31
+ clearInterval(interval);
32
+ Printful_Connect.send_return_message();
33
+ }
34
+ }
35
+ });
36
+ },
37
+ listen_auth_return: function () {
38
+ var intercom = Intercom.getInstance();
39
+ intercom.on('printful-auth', function (data) {
40
+ if (data.success === true) {
41
+ location.reload();
42
+ }
43
+ });
44
+ },
45
+ send_return_message: function () {
46
+ var intercom = Intercom.getInstance();
47
+ intercom.emit('printful-auth', {success: true});
48
+ window.top.close();
49
+ }
50
+ };
51
+ })();
assets/js/intercom.min.js ADDED
@@ -0,0 +1,12 @@
1
+ /*! intercom.js | https://github.com/diy/intercom.js | Apache License (v2) */
2
+ var Intercom=function(){var g=function(){};g.createInterface=function(b){return{on:function(a,c){"undefined"===typeof this[b]&&(this[b]={});this[b].hasOwnProperty(a)||(this[b][a]=[]);this[b][a].push(c)},off:function(a,c){"undefined"!==typeof this[b]&&this[b].hasOwnProperty(a)&&i.removeItem(c,this[b][a])},trigger:function(a){if("undefined"!==typeof this[b]&&this[b].hasOwnProperty(a))for(var c=Array.prototype.slice.call(arguments,1),e=0;e<this[b][a].length;e++)this[b][a][e].apply(this[b][a][e],c)}}};
3
+ var m=g.createInterface("_handlers");g.prototype._on=m.on;g.prototype._off=m.off;g.prototype._trigger=m.trigger;var n=g.createInterface("handlers");g.prototype.on=function(){n.on.apply(this,arguments);Array.prototype.unshift.call(arguments,"on");this._trigger.apply(this,arguments)};g.prototype.off=n.off;g.prototype.trigger=n.trigger;var f=window.localStorage;"undefined"===typeof f&&(f={getItem:function(){},setItem:function(){},removeItem:function(){}});var i={},h=function(){return(65536*(1+Math.random())|
4
+ 0).toString(16).substring(1)};i.guid=function(){return h()+h()+"-"+h()+"-"+h()+"-"+h()+"-"+h()+h()+h()};i.throttle=function(b,a){var c=0;return function(){var e=(new Date).getTime();e-c>b&&(c=e,a.apply(this,arguments))}};i.extend=function(b,a){if("undefined"===typeof b||!b)b={};if("object"===typeof a)for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b};i.removeItem=function(b,a){for(var c=a.length-1;0<=c;c--)a[c]===b&&a.splice(c,1);return a};var d=function(){var b=this,a=(new Date).getTime();
5
+ this.origin=i.guid();this.lastMessage=a;this.bindings=[];this.receivedIDs={};this.previousValues={};a=function(){b._onStorageEvent.apply(b,arguments)};window.attachEvent?document.attachEvent("onstorage",a):window.addEventListener("storage",a,!1)};d.prototype._transaction=function(b){var a=this,c=!1,e=!1,p=null,d=function(){if(!c){var g=(new Date).getTime(),s=parseInt(f.getItem(l)||0);s&&1E3>g-s?(e||(a._on("storage",d),e=!0),p=window.setTimeout(d,20)):(c=!0,f.setItem(l,g),b(),e&&a._off("storage",d),
6
+ p&&window.clearTimeout(p),f.removeItem(l))}};d()};d.prototype._cleanup_emit=i.throttle(100,function(){this._transaction(function(){for(var b=(new Date).getTime()-t,a=0,c=JSON.parse(f.getItem(j)||"[]"),e=c.length-1;0<=e;e--)c[e].timestamp<b&&(c.splice(e,1),a++);0<a&&f.setItem(j,JSON.stringify(c))})});d.prototype._cleanup_once=i.throttle(100,function(){var b=this;this._transaction(function(){var a,c=JSON.parse(f.getItem(k)||"{}");(new Date).getTime();var e=0;for(a in c)b._once_expired(a,c)&&(delete c[a],
7
+ e++);0<e&&f.setItem(k,JSON.stringify(c))})});d.prototype._once_expired=function(b,a){if(!a||!a.hasOwnProperty(b)||"object"!==typeof a[b])return!0;var c=a[b].ttl||u,e=(new Date).getTime();return a[b].timestamp<e-c};d.prototype._localStorageChanged=function(b,a){if(b&&b.key)return b.key===a;var c=f.getItem(a);if(c===this.previousValues[a])return!1;this.previousValues[a]=c;return!0};d.prototype._onStorageEvent=function(b){var b=b||window.event,a=this;this._localStorageChanged(b,j)&&this._transaction(function(){for(var b=
8
+ (new Date).getTime(),e=f.getItem(j),e=JSON.parse(e||"[]"),d=0;d<e.length;d++)if(e[d].origin!==a.origin&&!(e[d].timestamp<a.lastMessage)){if(e[d].id){if(a.receivedIDs.hasOwnProperty(e[d].id))continue;a.receivedIDs[e[d].id]=!0}a.trigger(e[d].name,e[d].payload)}a.lastMessage=b});this._trigger("storage",b)};d.prototype._emit=function(b,a,c){if((c="string"===typeof c||"number"===typeof c?String(c):null)&&c.length){if(this.receivedIDs.hasOwnProperty(c))return;this.receivedIDs[c]=!0}var e={id:c,name:b,origin:this.origin,
9
+ timestamp:(new Date).getTime(),payload:a},d=this;this._transaction(function(){var c=f.getItem(j)||"[]",c=[c.substring(0,c.length-1),"[]"===c?"":",",JSON.stringify(e),"]"].join("");f.setItem(j,c);d.trigger(b,a);window.setTimeout(function(){d._cleanup_emit()},50)})};d.prototype.bind=function(b,a){for(var c=0;c<d.bindings.length;c++){var e=d.bindings[c].factory(b,a||null,this);e&&this.bindings.push(e)}};d.prototype.emit=function(b,a){this._emit.apply(this,arguments);this._trigger("emit",b,a)};d.prototype.once=
10
+ function(b,a,c){if(d.supported){var e=this;this._transaction(function(){var d=JSON.parse(f.getItem(k)||"{}");e._once_expired(b,d)&&(d[b]={},d[b].timestamp=(new Date).getTime(),"number"===typeof c&&(d[b].ttl=1E3*c),f.setItem(k,JSON.stringify(d)),a(),window.setTimeout(function(){e._cleanup_once()},50))})}};i.extend(d.prototype,g.prototype);d.bindings=[];d.supported="undefined"!==typeof f;var j="intercom",k="intercom_once",l="intercom_lock",t=5E4,u=36E5;d.destroy=function(){f.removeItem(l);f.removeItem(j);
11
+ f.removeItem(k)};var q=null;d.getInstance=function(){q||(q=new d);return q};var r=function(b,a,c){a=i.extend({id:null,send:!0,receive:!0},a);if(a.receive){var d=[],f=function(f){-1===d.indexOf(f)&&(d.push(f),b.on(f,function(b){var d="function"===typeof a.id?a.id(f,b):null,e="function"===typeof a.receive?a.receive(f,b):!0;(e||"boolean"!==typeof e)&&c._emit(f,b,d)}))},g;for(g in c.handlers)for(var h=0;h<c.handlers[g].length;h++)f(g,c.handlers[g][h]);c._on("on",f)}a.send&&c._on("emit",function(c,d){var e=
12
+ "function"===typeof a.send?a.send(c,d):!0;(e||"boolean"!==typeof e)&&b.emit(c,d)})};r.factory=function(b,a,c){return"undefined"===typeof b.socket?!1:new r(b,a,c)};d.bindings.push(r);return d}();
assets/js/settings.js ADDED
@@ -0,0 +1,42 @@
1
+ var Printful_Settings;
2
+
3
+ (function () {
4
+ 'use strict';
5
+
6
+ Printful_Settings = {
7
+ init_submit: function () {
8
+
9
+ var form = jQuery('form[name=printful_settings]');
10
+ var submit_button = form.find('.woocommerce-save-button');
11
+ var loader = form.find('.loader');
12
+ var pass = form.find('.loader-wrap .pass');
13
+ var fail = form.find('.loader-wrap .fail');
14
+
15
+ submit_button.click(function (e) {
16
+
17
+ e.preventDefault();
18
+ submit_button.attr('disabled', 'disabled');
19
+ loader.show();
20
+
21
+ jQuery.ajax({
22
+ type: "POST",
23
+ url: form.attr('action'),
24
+ data: form.serialize(),
25
+ success: function (response) {
26
+ submit_button.removeAttr('disabled');
27
+ loader.hide();
28
+
29
+ if (response === 'OK') {
30
+ pass.show(0).delay(3000).hide(0);
31
+ } else {
32
+ fail.show(0).delay(3000).hide(0);
33
+ }
34
+ }
35
+ });
36
+ });
37
+ },
38
+ enable_submit_btn: function () {
39
+ jQuery('.printful-submit input[type=submit]').removeClass('disabled').prop('disabled', false);
40
+ }
41
+ };
42
+ })();
includes/class-printful-admin-dashboard.php ADDED
@@ -0,0 +1,267 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Admin_Dashboard {
5
+
6
+ const API_KEY_SEARCH_STRING = 'Printful';
7
+
8
+ public static $_instance;
9
+
10
+ /**
11
+ * @return Printful_Admin_Dashboard
12
+ */
13
+ public static function instance() {
14
+
15
+ if ( is_null( self::$_instance ) ) {
16
+ self::$_instance = new self();
17
+ }
18
+
19
+ return self::$_instance;
20
+ }
21
+
22
+ /**
23
+ * Printful_Admin_Dashboard constructor.
24
+ */
25
+ function __construct() {
26
+
27
+ }
28
+
29
+ /**
30
+ * Show the view
31
+ */
32
+ public static function view() {
33
+
34
+ $dashboard = self::instance();
35
+ $api_key = Printful_Integration::instance()->get_option('printful_key');
36
+ $connect_status = Printful_Integration::instance()->is_connected();
37
+
38
+ if ( $connect_status ) {
39
+ $dashboard->render_dashboard();
40
+ } else if(!$connect_status && strlen($api_key) > 0) {
41
+ $dashboard->render_connect_error();
42
+ } else {
43
+ $dashboard->render_connect();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Display the Printful connect page
49
+ */
50
+ public function render_connect() {
51
+
52
+ $status = Printful_Admin_Status::instance();
53
+ $issues = array();
54
+
55
+ $api_enabled = $status->run_single_test('check_WC_API_enabled');
56
+ $permalinks_set = $status->run_single_test('check_permalinks');
57
+
58
+ if ( $permalinks_set == Printful_Admin_Status::PF_STATUS_FAIL ) {
59
+ $message = 'WooCommerce API will not work unless your permalinks are set up correctly. Go to <a href="%s">Permalinks settings</a> and make sure that they are NOT set to "plain".';
60
+ $settings_url = admin_url( 'options-permalink.php' );
61
+ $issues[] = sprintf( $message, $settings_url );
62
+ }
63
+
64
+ if ( $api_enabled == Printful_Admin_Status::PF_STATUS_FAIL ) {
65
+ $message = 'Your site needs to enable WooCommerce API so that Printful can connect to it. Go to <a href="%s">API settings</a> and enable it.';
66
+ $settings_url = admin_url( 'admin.php?page=wc-settings&tab=api' );
67
+ $issues[] = sprintf( $message, $settings_url );
68
+ }
69
+
70
+ Printful_Admin::load_template( 'header', array( 'tabs' => Printful_Admin::get_tabs() ) );
71
+
72
+ Printful_Admin::load_template( 'connect', array(
73
+ 'consumer_key' => $this->_get_consumer_key(),
74
+ 'waiting_sync' => isset( $_GET['sync-in-progress'] ),
75
+ 'consumer_key_error' => isset( $_GET['consumer-key-error'] ),
76
+ 'issues' => $issues,
77
+ )
78
+ );
79
+
80
+ if ( isset( $_GET['sync-in-progress'] ) ) {
81
+ $emit_auth_response = 'Printful_Connect.send_return_message();';
82
+ Printful_Admin::load_template( 'inline-script', array( 'script' => $emit_auth_response ) );
83
+ }
84
+
85
+ Printful_Admin::load_template('footer');
86
+ }
87
+
88
+ /**
89
+ * Display the Printful connect error page
90
+ */
91
+ public function render_connect_error() {
92
+
93
+ Printful_Admin::load_template( 'header', array( 'tabs' => Printful_Admin::get_tabs() ) );
94
+
95
+ $connect_error = Printful_Integration::instance()->get_connect_error();
96
+ if ( $connect_error ) {
97
+ Printful_Admin::load_template('error', array('error' => $connect_error));
98
+ }
99
+
100
+ Printful_Admin::load_template('footer');
101
+ }
102
+
103
+ /**
104
+ * Display the dashboard
105
+ */
106
+ public function render_dashboard() {
107
+
108
+ Printful_Admin::load_template( 'header', array( 'tabs' => Printful_Admin::get_tabs() ) );
109
+
110
+ $stats = $this->_get_stats(true);
111
+ $orders = $this->_get_orders(true);
112
+ $error = false;
113
+
114
+ if ( is_wp_error( $stats ) ) {
115
+ $error = $stats;
116
+ }
117
+ if ( is_wp_error( $orders ) ) {
118
+ $error = $orders;
119
+ }
120
+
121
+ if ( ! $error ) {
122
+
123
+ if ( $stats ) {
124
+ Printful_Admin::load_template( 'stats', array( 'stats' => $stats ) );
125
+ } else {
126
+ Printful_Admin::load_template( 'ajax-loader', array( 'action' => 'get_printful_stats', 'message' => 'Loading your stats...' ) );
127
+ }
128
+
129
+ if ($orders) {
130
+ Printful_Admin::load_template( 'order-table', array( 'orders' => $orders ) );
131
+ } else {
132
+ Printful_Admin::load_template( 'ajax-loader', array( 'action' => 'get_printful_orders', 'message' => 'Loading your orders...' ) );
133
+ }
134
+
135
+ } else {
136
+ Printful_Admin::load_template( 'error', array( 'error' => $error->get_error_message('printful') ) );
137
+ }
138
+
139
+ Printful_Admin::load_template( 'quick-links' );
140
+
141
+ if ( isset( $_GET['sync-in-progress'] ) ) {
142
+ $emit_auth_response = 'Printful_Connect.send_return_message();';
143
+ Printful_Admin::load_template( 'inline-script', array( 'script' => $emit_auth_response ) );
144
+ }
145
+
146
+ Printful_Admin::load_template( 'footer' );
147
+ }
148
+
149
+ /**
150
+ * Ajax response for stats block
151
+ */
152
+ public static function render_stats_ajax() {
153
+
154
+ $stats = self::instance()->_get_stats();
155
+
156
+ if ( ! empty( $stats ) && ! is_wp_error( $stats ) ) {
157
+ Printful_Admin::load_template( 'stats', array( 'stats' => $stats ) );
158
+ } else {
159
+ Printful_Admin::load_template( 'error', array( 'error' => $stats->get_error_message( 'printful' ) ) );
160
+ }
161
+
162
+ exit;
163
+ }
164
+
165
+ /**
166
+ * Ajax response for stats block
167
+ */
168
+ public static function render_orders_ajax() {
169
+
170
+ $orders = self::instance()->_get_orders();
171
+
172
+ if ( ! empty( $orders ) && is_wp_error( $orders ) ) {
173
+ Printful_Admin::load_template( 'error', array( 'error' => $orders->get_error_message('printful') ) );
174
+ } else {
175
+ Printful_Admin::load_template( 'order-table', array( 'orders' => $orders ) );
176
+ }
177
+
178
+ exit;
179
+ }
180
+
181
+ /**
182
+ * Get store statistics from API
183
+ * @param bool $only_cached_results
184
+ * @return mixed
185
+ */
186
+ private function _get_stats($only_cached_results = false) {
187
+
188
+ $stats = get_transient( 'printful_stats' );
189
+ if ( $only_cached_results || $stats ) {
190
+ return $stats;
191
+ }
192
+
193
+ try {
194
+ $stats = Printful_Integration::instance()->get_client()->get( 'store/statistics' );
195
+ if ( ! empty( $stats['store_statistics'] ) ) {
196
+ $stats = $stats['store_statistics'];
197
+ }
198
+ set_transient( 'printful_stats', $stats, MINUTE_IN_SECONDS * 5 ); //cache for 5 minute
199
+ } catch (PrintfulApiException $e) {
200
+ return new WP_Error('printful', 'Could not connect to Printful API. Please try again later!');
201
+ } catch (PrintfulException $e) {
202
+ return new WP_Error('printful', 'Could not connect to Printful API. Please try again later!');
203
+ }
204
+
205
+ return $stats;
206
+ }
207
+
208
+ /**
209
+ * Get Printful orders from the API
210
+ * @param bool $only_cached_results
211
+ * @return mixed
212
+ */
213
+ private function _get_orders($only_cached_results = false) {
214
+
215
+ $orders = get_transient( 'printful_orders' );
216
+
217
+ if ( $only_cached_results || $orders ) {
218
+ return $orders;
219
+ }
220
+
221
+ try {
222
+ $order_data = Printful_Integration::instance()->get_client()->get( 'orders' );
223
+
224
+ if ( ! empty( $order_data ) ) {
225
+
226
+ foreach ( $order_data as $key => $order ) {
227
+
228
+ if($order['status'] == 'pending') {
229
+ $order_data[$key]['status'] = 'Waiting for fulfillment';
230
+ }
231
+ }
232
+ }
233
+
234
+ $orders = array( 'count' => count( $order_data ), 'results' => $order_data );
235
+ set_transient( 'printful_orders', $orders, MINUTE_IN_SECONDS * 5 ); //cache for 5 minute
236
+ } catch (PrintfulApiException $e) {
237
+ return new WP_Error('printful', 'Could not connect to Printful API. Please try again later!');
238
+ } catch (PrintfulException $e) {
239
+ return new WP_Error('printful', 'Could not connect to Printful API. Please try again later!');
240
+ }
241
+
242
+ return $orders;
243
+ }
244
+
245
+ /**
246
+ * Get the last used consumer key fragment and use it for validating the address
247
+ * @return null|string
248
+ */
249
+ private function _get_consumer_key() {
250
+
251
+ global $wpdb;
252
+
253
+ // Get the API key
254
+ $search = "AND description LIKE '%" . esc_sql( $wpdb->esc_like( wc_clean( self::API_KEY_SEARCH_STRING ) ) ) . "%' ";
255
+ $query = "SELECT truncated_key FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1 {$search} ORDER BY key_id DESC LIMIT 1";
256
+ $consumer_key = $wpdb->get_var( $query );
257
+
258
+ //if not found by description, it was probably manually created. try the last used key instead
259
+ if ( ! $consumer_key ) {
260
+ $query = "SELECT truncated_key FROM {$wpdb->prefix}woocommerce_api_keys ORDER BY key_id DESC LIMIT 1";
261
+ $consumer_key = $wpdb->get_var( $query );
262
+ }
263
+
264
+ return $consumer_key;
265
+ }
266
+
267
+ }
includes/class-printful-admin-settings.php ADDED
@@ -0,0 +1,250 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Admin_Settings {
5
+
6
+ public static $_instance;
7
+ const CARRIER_TYPE_STANDARD = 'standard';
8
+ const CARRIER_TYPE_EXPEDITED = 'expedited';
9
+
10
+ public static $integration_fields = array(
11
+ 'printful_key' => array(
12
+ 'title' => 'Printful store API key',
13
+ 'type' => 'text',
14
+ 'desc_tip' => true,
15
+ 'description' => 'Your store\'s Printful API key. Create it in the Prinful dashboard',
16
+ 'default' => false,
17
+ ),
18
+ 'calculate_tax' => array(
19
+ 'title' => 'Calculate sales tax',
20
+ 'type' => 'checkbox',
21
+ 'label' => 'Calculated for all products shipped to North Carolina and California',
22
+ 'default' => 'no',
23
+ ),
24
+ 'disable_ssl' => array(
25
+ 'title' => 'Disable SSL',
26
+ 'type' => 'checkbox',
27
+ 'label' => 'Use HTTP instead of HTTPS to connect to the Printful API (may be required if the plugin does not work for some hosting configurations)',
28
+ 'default' => 'no',
29
+ ),
30
+ );
31
+
32
+ /**
33
+ * @return Printful_Admin_Settings
34
+ */
35
+ public static function instance() {
36
+
37
+ if ( is_null( self::$_instance ) ) {
38
+ self::$_instance = new self();
39
+ }
40
+
41
+ return self::$_instance;
42
+ }
43
+
44
+ /**
45
+ * Setup the view
46
+ */
47
+ public static function view() {
48
+
49
+ $settings = self::instance();
50
+ $settings->render();
51
+ }
52
+
53
+ /**
54
+ * Display the view
55
+ */
56
+ public function render() {
57
+
58
+ Printful_Admin::load_template( 'header', array( 'tabs' => Printful_Admin::get_tabs() ) );
59
+
60
+ echo '<form method="post" name="printful_settings" action="' . admin_url( 'admin-ajax.php?action=save_printful_settings' ) . '">';
61
+
62
+ //integration settings
63
+ $integration_settings = $this->setup_integration_fields();
64
+ Printful_Admin::load_template( 'setting-group', $integration_settings );
65
+
66
+ Printful_Admin::load_template( 'shipping-notification' );
67
+
68
+ //carriers settings
69
+ Printful_Admin::load_template( 'ajax-loader', array( 'action' => 'get_printful_carriers', 'message' => 'Loading your carriers...' ) );
70
+
71
+ Printful_Admin::load_template( 'setting-submit', array( 'nonce' => wp_create_nonce( 'printful_settings' ), 'disabled' => true ) );
72
+
73
+ echo '</from>';
74
+
75
+ Printful_Admin::load_template( 'footer' );
76
+ }
77
+
78
+ /**
79
+ * Display the ajax content for carrier settings
80
+ */
81
+ public static function render_carriers_ajax() {
82
+
83
+ $carrier_settings = self::instance()->setup_carrier_fields();
84
+ Printful_Admin::load_template( 'setting-group', $carrier_settings );
85
+ $enable_submit = 'Printful_Settings.enable_submit_btn();';
86
+ Printful_Admin::load_template( 'inline-script', array('script' => $enable_submit) );
87
+ exit;
88
+ }
89
+
90
+ /**
91
+ * @return mixed
92
+ * @internal param $integration_settings
93
+ */
94
+ public function setup_integration_fields() {
95
+
96
+ $integration_settings = array(
97
+ 'title' => 'Integration settings',
98
+ 'description' => '',
99
+ 'settings' => self::$integration_fields,
100
+ );
101
+
102
+ foreach ( $integration_settings['settings'] as $key => $setting ) {
103
+ if ( $setting['type'] !== 'title' ) {
104
+ $integration_settings['settings'][ $key ]['value'] = Printful_Integration::instance()->get_option( $key, $setting['default'] );
105
+ }
106
+ }
107
+
108
+ return $integration_settings;
109
+ }
110
+
111
+ /**
112
+ * @internal param $carrier_settings
113
+ */
114
+ public function setup_carrier_fields() {
115
+
116
+ $carrier_settings = array(
117
+ 'title' => 'Carriers & Services',
118
+ 'description' => "You can specify here which shipping carriers are available for this store.\n
119
+ Uncheck the ones you want to disable. If you leave “Flat rate” checked it means that any order where flat rate was selected can still ship with any carrier and service.",
120
+ 'settings' => array(),
121
+ );
122
+
123
+ if ( ! Printful_Integration::instance()->is_connected() ) {
124
+ $carrier_settings['description'] = 'You need to be connected to Printful API to edit carrier settings!';
125
+ return $carrier_settings;
126
+ }
127
+
128
+ $carriers = Printful_Carriers::instance()->carriers;
129
+ if ( empty( $carriers ) ) {
130
+ return false;
131
+ }
132
+
133
+ $standard = array();
134
+ $expedited = array();
135
+
136
+ foreach ( $carriers as $carrier ) {
137
+
138
+ $item = array(
139
+ 'title' => false,
140
+ 'type' => 'checkbox',
141
+ 'label' => $carrier['title'] . ' <i>' . $carrier['subtitle'] . '</i>',
142
+ 'default' => 'yes',
143
+ 'value' => ( $carrier['status'] == 'on' ? 'yes' : 'no' ),
144
+ );
145
+
146
+ if ( $carrier['type'] == self::CARRIER_TYPE_STANDARD ) {
147
+ $standard[ $carrier['carrier_id'] ] = $item;
148
+ } else {
149
+ $expedited[ $carrier['carrier_id'] ] = $item;
150
+ }
151
+ }
152
+
153
+ $carrier_settings['settings'][ self::CARRIER_TYPE_STANDARD ] = array(
154
+ 'title' => 'Standard shipping',
155
+ 'type' => 'checkbox-group',
156
+ 'items' => $standard,
157
+ );
158
+
159
+ $carrier_settings['settings'][ self::CARRIER_TYPE_EXPEDITED ] = array(
160
+ 'title' => 'Expedited shipping',
161
+ 'type' => 'checkbox-group',
162
+ 'items' => $expedited,
163
+ );
164
+
165
+ return $carrier_settings;
166
+ }
167
+
168
+ /**
169
+ * Prepare carrier data for posting to Printful API
170
+ * @return array
171
+ */
172
+ public function prepare_carriers() {
173
+
174
+ $carriers = Printful_Carriers::instance()->carriers;
175
+
176
+ if ( empty( $carriers ) ) {
177
+ return false;
178
+ }
179
+
180
+ $standard = ( ! empty( $_POST[ self::CARRIER_TYPE_STANDARD ] ) ? $_POST[ self::CARRIER_TYPE_STANDARD ] : array() );
181
+ $expedited = ( ! empty( $_POST[ self::CARRIER_TYPE_EXPEDITED ] ) ? $_POST[ self::CARRIER_TYPE_EXPEDITED ] : array() );
182
+
183
+ $saved_carriers = array_merge( $standard, $expedited );
184
+
185
+ if ( empty( $saved_carriers ) ) {
186
+ return false;
187
+ }
188
+
189
+ $request_body = array();
190
+
191
+ foreach ( $carriers as $carrier ) {
192
+ $status = 'off';
193
+ if ( in_array( $carrier['carrier_id'], $saved_carriers ) ) {
194
+ $status = 'on';
195
+ }
196
+ $request_body[] = array(
197
+ 'carrier_id' => $carrier['carrier_id'],
198
+ 'status' => $status,
199
+ );
200
+ }
201
+
202
+ return $request_body;
203
+ }
204
+
205
+ /**
206
+ * Ajax endpoint for saving the settings
207
+ */
208
+ public static function save_printful_settings() {
209
+
210
+ if ( ! empty( $_POST ) ) {
211
+
212
+ check_admin_referer( 'printful_settings' );
213
+
214
+ //save carriers first, so API key change does not affect this
215
+ if ( Printful_Integration::instance()->is_connected(true) ) {
216
+
217
+ //save remote carrier settings
218
+ $request_body = Printful_Admin_Settings::instance()->prepare_carriers();
219
+ $result = Printful_Carriers::instance()->post_carriers( $request_body );
220
+
221
+ if ( ! $result ) {
222
+ die( 'Error: failed to save carriers' );
223
+ }
224
+ }
225
+
226
+ $options = array();
227
+
228
+ //build save options list
229
+ foreach ( self::$integration_fields as $key => $field ) {
230
+
231
+ if ( $field['type'] == 'checkbox' ) {
232
+ if ( isset( $_POST[ $key ] ) ) {
233
+ $options[ $key ] = 'yes';
234
+ } else {
235
+ $options[ $key ] = 'no';
236
+ }
237
+ } else {
238
+ if ( isset( $_POST[ $key ] ) ) {
239
+ $options[ $key ] = $_POST[ $key ];
240
+ }
241
+ }
242
+ }
243
+
244
+ //save integration settings
245
+ Printful_Integration::instance()->update_settings( $options );
246
+
247
+ die('OK');
248
+ }
249
+ }
250
+ }
includes/class-printful-admin-status.php ADDED
@@ -0,0 +1,541 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Admin_Status {
5
+
6
+ const PF_STATUS_OK = 1;
7
+ const PF_STATUS_WARNING = 0;
8
+ const PF_STATUS_FAIL = -1;
9
+
10
+ const API_KEY_SEARCH_STRING = 'Printful';
11
+ const PF_WEBHOOK_NAME = 'Printful Integration';
12
+ const PF_REMOTE_REQUEST_URL = 'webhook/woocommerce?store=1';
13
+ const PF_REMOTE_REQUEST_TOPIC = 'woo.plugin.test';
14
+ const PF_STATUS_ISSUE_COUNT = 'printful_status_issue_count';
15
+ const PF_CACHED_CHECKLIST = 'printful_cached_checklist';
16
+
17
+ public static $_instance;
18
+ public static $checklist_items = array(
19
+ array(
20
+ 'name' => 'WordPress Permalinks',
21
+ 'description' => 'WooCommerce API will not work unless your permalinks in Settings > Permalinks are set up correctly. Make sure you that they are NOT set to "plain".',
22
+ 'method' => 'check_permalinks',
23
+ ),
24
+ array(
25
+ 'name' => 'WordPress version',
26
+ 'description' => 'WordPress should always be updated to the latest version. Updates can be installed from your WordPress admin dashboard.',
27
+ 'method' => 'check_WP_version',
28
+ ),
29
+ array(
30
+ 'name' => 'WooCommerce API enabled',
31
+ 'description' => 'Your site needs to enbable WooCommerce API so that Printful can connect to it',
32
+ 'method' => 'check_WC_API_enabled',
33
+ ),
34
+ array(
35
+ 'name' => 'WooCommerce Webhooks',
36
+ 'description' => 'Printful requires WooCommerce webhooks to quickly capture you incoming orders, products updates etc.',
37
+ 'method' => 'check_PF_webhooks',
38
+ ),
39
+ array(
40
+ 'name' => 'WooCommerce API keys are set',
41
+ 'description' => 'Printful needs access to your WooCommerce API for the integration to work - otherwise we can\'t sync your store, push or pull your products etc.',
42
+ 'method' => 'check_WC_API_access',
43
+ ),
44
+ array(
45
+ 'name' => 'WooCommerce authentication URL access',
46
+ 'description' => 'Some hosts have unnecessarily intrusive security checks in place that prevent WooCommerce API authentication from working (mod_security rule #1234234). If this check fails, you will not be able authorize Printful app.',
47
+ 'method' => 'check_WC_auth_url_access',
48
+ ),
49
+ array(
50
+ 'name' => 'WordPress remote requests',
51
+ 'description' => 'WordPress needs to be able to connect to Printful server to call webhooks. If this check fails, contact your hosting support.',
52
+ 'method' => 'check_remote_requests',
53
+ ),
54
+ array(
55
+ 'name' => 'Printful API key is set',
56
+ 'description' => 'Your store needs access to Printful API to use most of it\'s features like shipping rates, tax rates and other settings.',
57
+ 'method' => 'check_PF_API_key',
58
+ ),
59
+ array(
60
+ 'name' => 'Connection to Printful API',
61
+ 'description' => 'Is your store successfully connected to Printful API',
62
+ 'method' => 'check_PF_API_connect',
63
+ ),
64
+ array(
65
+ 'name' => 'Recent store sync errors',
66
+ 'description' => 'Printful will connect to your store\'s API regularly and sync your latest products, orders etc. If there have been any recent issues with sync, this check will fail.',
67
+ 'method' => 'check_PF_sync_errors',
68
+ ),
69
+ array(
70
+ 'name' => 'Write permissions',
71
+ 'description' => 'Make the uploads directory writable. This is required for mockup generator product push to work correctly. Contact your hosting provider if you need help with this.',
72
+ 'method' => 'check_uploads_write',
73
+ ),
74
+ array(
75
+ 'name' => 'PHP memory limit',
76
+ 'description' => 'Set PHP allocated memory limit to at least 128mb. Contact your hosting provider if you need help with this.',
77
+ 'method' => 'check_PHP_memory_limit',
78
+ ),
79
+ array(
80
+ 'name' => 'PHP script time limit',
81
+ 'description' => 'Set PHP script execution time limit to at least 30 seconds. This is required to successfully push products with many variants. Contact your hosting provider if you need help with this.',
82
+ 'method' => 'check_PHP_time_limit',
83
+ ),
84
+ // array(
85
+ // 'name' => 'Error logs',
86
+ // 'description' => 'Your WordPress site needs to have it\'s error logging set up correctly so in case of issues you can figure out what\'s wrong. Note: your hosting might already have an independent log from WordPress.',
87
+ // 'method' => 'check_WP_error_logs',
88
+ // ),
89
+ array(
90
+ 'name' => 'W3 Total Cache DB Cache',
91
+ 'description' => 'If you are using W3 Total Cache, the database caching feature needs to be disabled since it can cause issues with product push to store.',
92
+ 'method' => 'check_W3_db_cache',
93
+ 'silent' => true,
94
+ ),
95
+ array(
96
+ 'name' => 'Remove Print Aura plugin',
97
+ 'description' => 'Print Aura plugin is known to cause issues so it needs to be removed',
98
+ 'method' => 'check_printaura_plugin',
99
+ 'silent' => true,
100
+ ),
101
+ );
102
+
103
+ /**
104
+ * @return Printful_Admin_Status
105
+ */
106
+ public static function instance() {
107
+
108
+ if ( is_null( self::$_instance ) ) {
109
+ self::$_instance = new self();
110
+ }
111
+
112
+ return self::$_instance;
113
+ }
114
+
115
+ /**
116
+ * Setup the view variables
117
+ */
118
+ public static function view() {
119
+
120
+ $status = self::instance();
121
+ $status->render();
122
+ }
123
+
124
+ /**
125
+ * Render the view
126
+ */
127
+ public function render() {
128
+
129
+ Printful_Admin::load_template( 'header', array( 'tabs' => Printful_Admin::get_tabs() ) );
130
+
131
+ $checklist = self::get_checklist( true );
132
+ if ( $checklist ) {
133
+ Printful_Admin::load_template( 'status-table', array( 'checklist' => $checklist ) );
134
+ } else {
135
+ Printful_Admin::load_template( 'ajax-loader', array( 'action' => 'get_printful_status_checklist', 'message' => 'Testing your store (this may take up to 30 seconds)...' ) );
136
+ }
137
+
138
+ Printful_Admin::load_template( 'footer' );
139
+ }
140
+
141
+ /**
142
+ * Build the content for status page
143
+ */
144
+ public static function render_status_table_ajax() {
145
+
146
+ $checklist = self::get_checklist();
147
+ Printful_Admin::load_template( 'status-table', array( 'checklist' => $checklist ) );
148
+
149
+ exit;
150
+ }
151
+
152
+ /**
153
+ * Run the tests
154
+ * @param bool $only_cached_results
155
+ * @return array
156
+ */
157
+ public static function get_checklist($only_cached_results = false) {
158
+
159
+ $status = self::instance();
160
+
161
+ $list = get_transient(Printful_Admin_Status::PF_CACHED_CHECKLIST);
162
+
163
+ if ( $only_cached_results || $list ) {
164
+ return $list;
165
+ }
166
+
167
+ $list = array();
168
+ $list['overall_status'] = true;
169
+ $issueCount = 0;
170
+
171
+ foreach ( self::$checklist_items as $item ) {
172
+ $list_item = array();
173
+ $list_item['name'] = $item['name'];
174
+ $list_item['description'] = $item['description'];
175
+
176
+ if ( method_exists( $status, $item['method'] ) ) {
177
+ $list_item['status'] = $status->{$item['method']}();
178
+
179
+ if ( $status->should_result_be_visible( $list_item['status'], $item ) ) {
180
+ $list['items'][] = $list_item;
181
+ }
182
+
183
+ if ( $list_item['status'] == self::PF_STATUS_FAIL) {
184
+ $list['overall_status'] = false;
185
+ $issueCount ++;
186
+ }
187
+ }
188
+ }
189
+
190
+ set_transient( Printful_Admin_Status::PF_CACHED_CHECKLIST, $list, MINUTE_IN_SECONDS );
191
+ set_transient( Printful_Admin_Status::PF_STATUS_ISSUE_COUNT, $issueCount, HOUR_IN_SECONDS );
192
+
193
+ return $list;
194
+ }
195
+
196
+ /**
197
+ * Execute only one test
198
+ * @param $method
199
+ * @return mixed
200
+ */
201
+ public function run_single_test( $method ) {
202
+ if ( method_exists( $this, $method ) ) {
203
+ return $this->{$method}();
204
+ }
205
+ return false;
206
+ }
207
+
208
+ /**
209
+ * @param $status
210
+ * @param bool $item
211
+ *
212
+ * @return int
213
+ */
214
+ private function should_result_be_visible( $status, $item = false ) {
215
+
216
+ if ( ! isset( $item['silent'] ) || ( $item['silent'] === true && $status === self::PF_STATUS_FAIL ) ) { //silent items are only shown on FAIL
217
+ return true;
218
+ }
219
+
220
+ return false;
221
+ }
222
+
223
+ /**
224
+ * Function for checking if thumbnails are resized
225
+ */
226
+ private function check_uploads_write() {
227
+
228
+ $upload_dir = wp_upload_dir();
229
+ if ( is_writable( $upload_dir['basedir'] ) ) {
230
+ return self::PF_STATUS_OK;
231
+ }
232
+
233
+ return self::PF_STATUS_FAIL;
234
+ }
235
+
236
+ /**
237
+ * @return int
238
+ */
239
+ private function check_PHP_memory_limit() {
240
+
241
+ $memory_limit = ini_get( 'memory_limit' );
242
+
243
+ if ( preg_match( '/^(\d+)(.)#x2F;', $memory_limit, $matches ) ) {
244
+ if ( $matches[2] == 'M' ) {
245
+ $memory_limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB
246
+ } else if ( $matches[2] == 'K' ) {
247
+ $memory_limit = $matches[1] * 1024; // nnnK -> nnn KB
248
+ }
249
+ }
250
+
251
+ $ok = ( $memory_limit >= 128 * 1024 * 1024 ); // at least 128M?
252
+
253
+ if ( $ok ) {
254
+ return self::PF_STATUS_OK;
255
+ }
256
+
257
+ return self::PF_STATUS_FAIL;
258
+ }
259
+
260
+ /**
261
+ * @return int
262
+ */
263
+ private function check_WP_version() {
264
+
265
+ $current = get_bloginfo( 'version' );
266
+
267
+ try {
268
+ $url = 'https://api.wordpress.org/core/version-check/1.7/';
269
+ $response = wp_remote_get( $url );
270
+
271
+ if ( ! is_wp_error( $response ) ) {
272
+ $json = $response['body'];
273
+ $obj = json_decode( $json );
274
+ }
275
+
276
+ if ( empty( $obj ) ) {
277
+ return self::PF_STATUS_FAIL;
278
+ }
279
+
280
+ $version = $obj->offers[0];
281
+ $latest = $version->version;
282
+
283
+ } catch ( Exception $e ) {
284
+ return self::PF_STATUS_FAIL;
285
+ }
286
+
287
+ if ( ! $latest ) {
288
+ return self::PF_STATUS_FAIL;
289
+ }
290
+
291
+ if ( version_compare( $current, $latest, '==' ) ) {
292
+ return self::PF_STATUS_OK;
293
+ }
294
+
295
+ return self::PF_STATUS_FAIL;
296
+ }
297
+
298
+ /**
299
+ * @return int
300
+ */
301
+ private function check_PF_webhooks() {
302
+
303
+ // Query args
304
+ $args = array(
305
+ 'post_type' => 'shop_webhook',
306
+ 'nopaging' => true,
307
+ 'ignore_sticky_posts' => true,
308
+ 's' => self::PF_WEBHOOK_NAME,
309
+ 'post_status' => 'published',
310
+ );
311
+
312
+ // Get the webhooks
313
+ $webhook_results = new WP_Query( $args );
314
+ $webhooks = $webhook_results->posts;
315
+
316
+ if ( count( $webhooks ) > 0 ) {
317
+ return self::PF_STATUS_OK;
318
+ }
319
+
320
+ return self::PF_STATUS_FAIL;
321
+ }
322
+
323
+ /**
324
+ * @return int
325
+ */
326
+ private function check_WC_API_access() {
327
+
328
+ global $wpdb;
329
+
330
+ //if any keys are set
331
+ $query = "SELECT COUNT(*) as key_count FROM {$wpdb->prefix}woocommerce_api_keys";
332
+ $count = $wpdb->get_var( $query );
333
+
334
+ if ( $count == 0 ) {
335
+ return self::PF_STATUS_FAIL;
336
+ }
337
+
338
+ // Get the API key with matching description
339
+ $search = "AND description LIKE '%" . esc_sql( $wpdb->esc_like( wc_clean( self::API_KEY_SEARCH_STRING ) ) ) . "%' ";
340
+ $query = "SELECT * FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1 {$search} ORDER BY last_access DESC LIMIT 1";
341
+ $key = $wpdb->get_row( $query );
342
+
343
+ if ( ! empty( $key ) && $key->permissions == 'read_write' ) {
344
+ return self::PF_STATUS_OK;
345
+ }
346
+
347
+ return self::PF_STATUS_WARNING;
348
+ }
349
+
350
+ /**
351
+ * @return int
352
+ */
353
+ private function check_PF_API_key() {
354
+
355
+ $option = get_option( 'woocommerce_printful_settings', array() );
356
+ if ( ! empty( $option['printful_key'] ) && strlen( $option['printful_key'] ) == 36 ) {
357
+ return self::PF_STATUS_OK;
358
+ }
359
+
360
+ return self::PF_STATUS_FAIL;
361
+ }
362
+
363
+ /**
364
+ * @return int
365
+ */
366
+ private function check_PF_API_connect() {
367
+
368
+ if ( Printful_Integration::instance()->is_connected(true) ) {
369
+ return self::PF_STATUS_OK;
370
+ }
371
+
372
+ return self::PF_STATUS_FAIL;
373
+ }
374
+
375
+ /**
376
+ * @return int
377
+ */
378
+ private function check_PHP_time_limit() {
379
+ $time_limit = ini_get( 'max_execution_time' );
380
+
381
+ if ( $time_limit >= 30 ) {
382
+ return self::PF_STATUS_OK;
383
+ }
384
+
385
+ return self::PF_STATUS_FAIL;
386
+ }
387
+
388
+ /**
389
+ * @return int
390
+ */
391
+ private function check_PF_sync_errors() {
392
+
393
+ $sync_log = get_option( Printful_Request_log::PF_OPTION_INCOMING_API_REQUEST_LOG, array() );
394
+ if ( empty( $sync_log ) ) {
395
+ return self::PF_STATUS_OK; //no results means no errors
396
+ }
397
+
398
+ $sync_log = array_reverse( $sync_log );
399
+ $sync_log = array_slice( $sync_log, 0, 6 ); //we only care about last to syncs
400
+
401
+ foreach ( $sync_log as $sl ) {
402
+ if ( ! empty( $sl['result'] ) && $sl['result'] == 'ERROR' ) {
403
+ return self::PF_STATUS_FAIL;
404
+ }
405
+ }
406
+
407
+ return self::PF_STATUS_OK;
408
+ }
409
+
410
+ /**
411
+ * @return int
412
+ */
413
+ private function check_W3_db_cache() {
414
+
415
+ if ( ! is_plugin_active( 'w3-total-cache/w3-total-cache.php' ) ) {
416
+ return self::PF_STATUS_OK;
417
+ }
418
+
419
+ $w3tc_config_file = get_home_path() . 'wp-content/w3tc-config/master.php';
420
+ if ( file_exists( $w3tc_config_file ) && is_readable( $w3tc_config_file ) ) {
421
+ $content = @file_get_contents( $w3tc_config_file );
422
+ $config = @json_decode( substr( $content, 14 ), true );
423
+
424
+ if ( is_array( $config ) && ! empty( $config['dbcache.enabled'] ) ) {
425
+ return ! $config['dbcache.enabled'];
426
+ }
427
+ }
428
+
429
+ return self::PF_STATUS_OK;
430
+ }
431
+
432
+ /**
433
+ * @return int
434
+ */
435
+ private function check_WC_API_enabled() {
436
+
437
+ $enabled = get_option('woocommerce_api_enabled', false);
438
+
439
+ if($enabled == 'yes') {
440
+ return self::PF_STATUS_OK;
441
+ }
442
+
443
+ return self::PF_STATUS_FAIL;
444
+ }
445
+
446
+ /**
447
+ * @return int
448
+ */
449
+ private function check_permalinks() {
450
+
451
+ $permalinks = get_option( 'permalink_structure', false );
452
+
453
+ if ( $permalinks && strlen( $permalinks ) > 0 ) {
454
+ return self::PF_STATUS_OK;
455
+ }
456
+
457
+ return self::PF_STATUS_FAIL;
458
+ }
459
+
460
+ /**
461
+ * @return int
462
+ */
463
+ private function check_printaura_plugin() {
464
+
465
+ if ( ! is_plugin_active( 'printaura-woocommerce-api/printaura-woocommerce-api.php' ) ) {
466
+ return self::PF_STATUS_OK;
467
+ }
468
+
469
+ return self::PF_STATUS_FAIL;
470
+ }
471
+
472
+ /**
473
+ * @return int
474
+ */
475
+ private function check_remote_requests() {
476
+
477
+ // Setup request args.
478
+ $http_args = array(
479
+ 'method' => 'POST',
480
+ 'timeout' => MINUTE_IN_SECONDS,
481
+ 'redirection' => 0,
482
+ 'httpversion' => '1.0',
483
+ 'blocking' => true,
484
+ 'user-agent' => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', WC_VERSION, $GLOBALS['wp_version'] ),
485
+ 'body' => trim( json_encode( array( 'test' => true ) ) ),
486
+ 'headers' => array( 'Content-Type' => 'application/json' ),
487
+ 'cookies' => array(),
488
+ );
489
+
490
+ // Add custom headers.
491
+ $http_args['headers']['X-WC-Webhook-Source'] = home_url( '/' ); // Since 2.6.0.
492
+ $http_args['headers']['X-WC-Webhook-Topic'] = self::PF_REMOTE_REQUEST_TOPIC;
493
+
494
+ // Webhook away!
495
+ $response = wp_safe_remote_request( Printful_Base::get_printful_host() . self::PF_REMOTE_REQUEST_URL, $http_args );
496
+
497
+ if ( is_wp_error( $response ) ) {
498
+ return self::PF_STATUS_FAIL;
499
+ }
500
+
501
+ return self::PF_STATUS_OK;
502
+ }
503
+
504
+ /**
505
+ * @return int
506
+ */
507
+ private function check_WC_auth_url_access() {
508
+
509
+ $url = home_url( '/' ) . 'wc-auth/v1/authorize?app_name=Printful&scope=read_write&user_id=1&return_url=https%3A%2F%2Fwww.printful.com%2Fdashboard%2Fwoocommerce%2Freturn&callback_url=https%3A%2F%2Fwww.printful.com%2Fwebhook%2Fwoocommerce-auth-callback';
510
+ $http_args = array(
511
+ 'timeout' => 60,
512
+ 'method' => 'GET',
513
+ 'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
514
+ );
515
+
516
+ $response = wp_safe_remote_get( $url, $http_args );
517
+ $code = $response['response']['code'];
518
+
519
+ if ( $code == 200 ) {
520
+ return self::PF_STATUS_OK;
521
+ }
522
+
523
+ return self::PF_STATUS_FAIL;
524
+ }
525
+
526
+ /**
527
+ * @return int
528
+ */
529
+ private function check_WP_error_logs() {
530
+
531
+ if (
532
+ ( defined( 'WP_DEBUG' ) && WP_DEBUG == true )
533
+ &&
534
+ ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG == true )
535
+ ) {
536
+ return self::PF_STATUS_OK;
537
+ }
538
+
539
+ return self::PF_STATUS_FAIL;
540
+ }
541
+ }
includes/class-printful-admin-support.php ADDED
@@ -0,0 +1,235 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Admin_Support {
5
+
6
+ public static $_instance;
7
+
8
+ /**
9
+ * @return Printful_Admin_Support
10
+ */
11
+ public static function instance() {
12
+
13
+ if ( is_null( self::$_instance ) ) {
14
+ self::$_instance = new self();
15
+ }
16
+
17
+ return self::$_instance;
18
+ }
19
+
20
+ /**
21
+ * Setup the view
22
+ */
23
+ public static function view() {
24
+
25
+ $support = self::instance();
26
+ $support->render();
27
+ }
28
+
29
+ /**
30
+ * Display support report
31
+ */
32
+ public function render() {
33
+
34
+ Printful_Admin::load_template( 'header', array( 'tabs' => Printful_Admin::get_tabs() ) );
35
+
36
+ Printful_Admin::load_template( 'ajax-loader', array( 'action' => 'get_printful_status_report', 'message' => 'Building support report (this may take up to 30 seconds)...' ) );
37
+
38
+ Printful_Admin::load_template( 'support-info' );
39
+
40
+ Printful_Admin::load_template( 'footer' );
41
+ }
42
+
43
+ /**
44
+ * Build the content for status page
45
+ */
46
+ public static function render_status_report_ajax() {
47
+
48
+ $status_report = self::instance()->generate_report();
49
+ Printful_Admin::load_template( 'status-report', array( 'status_report' => $status_report ) );
50
+
51
+ exit;
52
+ }
53
+
54
+ /**
55
+ * Create system status report
56
+ * @return string
57
+ */
58
+ public function generate_report() {
59
+
60
+ if ( ! class_exists( 'WC_REST_System_Status_Controller' ) ) {
61
+ return false;
62
+ }
63
+
64
+ $system_status = new WC_REST_System_Status_Controller; //make use of the woocommerce system status report
65
+
66
+ ob_start();
67
+
68
+ echo "##### Printful Checklist #####\n";
69
+ $checklist = Printful_Admin_Status::get_checklist();
70
+ foreach ( $checklist['items'] as $item ) {
71
+
72
+ $status = 'OK';
73
+ if($item['status'] == Printful_Admin_Status::PF_STATUS_WARNING) {
74
+ $status = 'WARNING';
75
+ } else if($item['status'] == Printful_Admin_Status::PF_STATUS_FAIL) {
76
+ $status = 'FAIL';
77
+ }
78
+ echo str_pad( esc_html($item['name']), 30 ) . "=> " . esc_html($status) . "\n";
79
+ }
80
+
81
+ echo "\n\n##### Printful Last Sync's #####\n";
82
+ $syncReport = $this->get_sync_report();
83
+ if ( ! empty( $syncReport ) ) {
84
+
85
+ echo str_pad( 'Date', 30 );
86
+ echo str_pad( 'Request', 30 );
87
+ echo str_pad( 'Message', 30 );
88
+ echo "\n";
89
+
90
+ foreach ( $syncReport as $sr ) {
91
+
92
+ echo str_pad( esc_html($sr['date']) . ';', 30 );
93
+ echo str_pad( esc_html($sr['path']) . ';', 30 );
94
+ echo str_pad( esc_html($sr['message']) . ';', 30 );
95
+
96
+ echo "\n";
97
+ }
98
+ }
99
+
100
+ echo "\n\n##### Environment #####\n";
101
+ $this->output_report_block( $system_status->get_environment_info() );
102
+
103
+ echo "\n\n##### Database #####\n";
104
+ $this->output_report_block( $system_status->get_database_info() );
105
+
106
+ echo "\n\n##### Active Plugins #####\n";
107
+ foreach ( $system_status->get_active_plugins() as $plugin ) {
108
+ if ( ! empty( $plugin['name'] ) ) {
109
+ echo esc_html( $plugin['name'] ) . " (" . $plugin['version'] . ")\n";
110
+ }
111
+ }
112
+
113
+ echo "\n\n##### Theme #####\n";
114
+ $this->output_report_block( $system_status->get_theme_info() );
115
+
116
+ echo "\n\n##### WooCommerce settings #####\n";
117
+ $this->output_report_block( $system_status->get_settings() );
118
+
119
+ if (
120
+ ( defined( 'WP_DEBUG' ) && WP_DEBUG == true )
121
+ &&
122
+ ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG == true )
123
+ ) {
124
+ echo "\n\n##### Wordpress Error log (last 50 entries) #####\n";
125
+ $contents = $this->get_error_log_contents();
126
+ if ( $contents ) {
127
+ print_r( $contents );
128
+ }
129
+ }
130
+
131
+ $report = ob_get_contents();
132
+ ob_end_clean();
133
+
134
+ return $report;
135
+ }
136
+
137
+ /**
138
+ * Get last 50 lines of error log
139
+ * @return bool|string
140
+ */
141
+ public function get_error_log_contents() {
142
+
143
+ if ( ! function_exists( 'fopen' ) ) {
144
+ return false;
145
+ }
146
+
147
+ return $this->file_tail( WP_CONTENT_DIR . '/debug.log', 50 );
148
+ }
149
+
150
+ /**
151
+ * source: https://gist.github.com/lorenzos/1711e81a9162320fde20
152
+ * @param $filepath
153
+ * @param int $lines
154
+ * @param bool $adaptive
155
+ *
156
+ * @return bool|string
157
+ */
158
+ function file_tail( $filepath, $lines = 1, $adaptive = true ) {
159
+
160
+ $f = @fopen( $filepath, "rb" );
161
+ if ( $f === false ) {
162
+ return false;
163
+ }
164
+
165
+ // Sets buffer size, according to the number of lines to retrieve.
166
+ if ( ! $adaptive ) {
167
+ $buffer = 4096;
168
+ } else {
169
+ $buffer = ( $lines < 2 ? 64 : ( $lines < 10 ? 512 : 4096 ) );
170
+ }
171
+
172
+ // Jump to last character
173
+ fseek( $f, - 1, SEEK_END );
174
+ if ( fread( $f, 1 ) != "\n" ) {
175
+ $lines -= 1;
176
+ }
177
+
178
+ $output = '';
179
+ $chunk = '';
180
+ while ( ftell( $f ) > 0 && $lines >= 0 ) {
181
+ // Figure out how far back we should jump
182
+ $seek = min( ftell( $f ), $buffer );
183
+ // Do the jump (backwards, relative to where we are)
184
+ fseek( $f, - $seek, SEEK_CUR );
185
+ $output = ( $chunk = fread( $f, $seek ) ) . $output;
186
+ fseek( $f, - mb_strlen( $chunk, '8bit' ), SEEK_CUR );
187
+ $lines -= substr_count( $chunk, "\n" );
188
+ }
189
+ while ( $lines ++ < 0 ) {
190
+ $output = substr( $output, strpos( $output, "\n" ) + 1 );
191
+ }
192
+ fclose( $f );
193
+
194
+ return trim( $output );
195
+ }
196
+
197
+
198
+ /**
199
+ * Displays the data
200
+ * @param $data
201
+ */
202
+ public function output_report_block( $data ) {
203
+
204
+ foreach ( $data as $key => $item ) {
205
+ if ( is_string( $item ) ) {
206
+ echo str_pad( $key, 30 ) . "=> " . $item . "\n";
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Returns log of last incoming API requests from Printful
213
+ * @return array
214
+ */
215
+ public function get_sync_report() {
216
+
217
+ $report = array();
218
+ $request_log = get_option( Printful_Request_log::PF_OPTION_INCOMING_API_REQUEST_LOG, array() );
219
+ $request_log = array_reverse( $request_log );
220
+
221
+ if ( empty( $request_log ) ) {
222
+ return $report;
223
+ }
224
+
225
+ foreach ( $request_log as $log ) {
226
+ $report[] = array(
227
+ 'date' => $log['date'],
228
+ 'path' => $log['request'],
229
+ 'message' => $log['result'],
230
+ );
231
+ }
232
+
233
+ return $report;
234
+ }
235
+ }
includes/class-printful-admin.php ADDED
@@ -0,0 +1,167 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit;
3
+
4
+ class Printful_Admin {
5
+
6
+ const MENU_TITLE_TOP = 'Printful';
7
+ const PAGE_TITLE_DASHBOARD = 'Dashboard';
8
+ const MENU_TITLE_DASHBOARD = 'Dashboard';
9
+ const MENU_SLUG_DASHBOARD = 'printful-dashboard';
10
+ const CAPABILITY = 'manage_options';
11
+
12
+ public static function init() {
13
+ $admin = new self;
14
+ $admin->register_admin();
15
+ }
16
+
17
+ /**
18
+ * Register admin scripts
19
+ */
20
+ public function register_admin() {
21
+
22
+ add_action( 'admin_menu', array($this, 'register_admin_menu_page'));
23
+ add_action( 'admin_enqueue_scripts', array($this, 'add_admin_styles'));
24
+ add_action( 'admin_enqueue_scripts', array($this, 'add_admin_scripts'));
25
+ add_action( 'wp_enqueue_scripts', array($this, 'add_global_style'));
26
+ add_action( 'admin_bar_menu', array($this, 'add_printful_status_toolbar'), 999 );
27
+ }
28
+
29
+ /**
30
+ * Loads stylesheets used in printful admin pages
31
+ * @param $hook
32
+ */
33
+ public function add_admin_styles($hook) {
34
+
35
+ wp_enqueue_style( 'printful-global', plugins_url( '../assets/css/global.css', __FILE__ ) );
36
+
37
+ if ( strpos( $hook, 'printful-dashboard' ) !== false ) {
38
+ wp_enqueue_style( 'printful-dashboard', plugins_url( '../assets/css/dashboard.css', __FILE__ ) );
39
+ wp_enqueue_style( 'printful-status', plugins_url( '../assets/css/status.css', __FILE__ ) );
40
+ wp_enqueue_style( 'printful-support', plugins_url( '../assets/css/support.css', __FILE__ ) );
41
+ wp_enqueue_style( 'printful-settings', plugins_url( '../assets/css/settings.css', __FILE__ ) );
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Loads stylesheet for printful toolbar element
47
+ */
48
+ public function add_global_style() {
49
+ wp_enqueue_style( 'printful-global', plugins_url( '../assets/css/global.css', __FILE__ ) );
50
+ }
51
+
52
+ /**
53
+ * Loads scripts used in printful admin pages
54
+ * @param $hook
55
+ */
56
+ public function add_admin_scripts($hook) {
57
+ if ( strpos( $hook, 'printful-dashboard' ) !== false ) {
58
+ wp_enqueue_script( 'printful-settings', plugins_url( '../assets/js/settings.js', __FILE__ ) );
59
+ wp_enqueue_script( 'printful-connect', plugins_url( '../assets/js/connect.js', __FILE__ ) );
60
+ wp_enqueue_script( 'printful-block-loader', plugins_url( '../assets/js/block-loader.js', __FILE__ ) );
61
+ wp_enqueue_script( 'printful-intercom', plugins_url( '../assets/js/intercom.min.js', __FILE__ ) );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Register admin menu pages
67
+ */
68
+ public function register_admin_menu_page() {
69
+
70
+ add_menu_page(
71
+ self::PAGE_TITLE_DASHBOARD,
72
+ self::MENU_TITLE_TOP,
73
+ self::CAPABILITY,
74