WooCommerce PDF Invoices & Packing Slips - Version 2.1.2

Version Description

  • Feature: New action wpo_wcpdf_init_document
  • Fix: Use title getters for my-account and backend buttons
  • Fix: Legacy Premium Templates reference
  • Tweak: Skip documents overview in settings, default to invoice
Download this release

Release Info

Developer pomegranate
Plugin Icon 128x128 WooCommerce PDF Invoices & Packing Slips
Version 2.1.2
Comparing to
See all releases

Code changes from version 2.1.1 to 2.1.2

Files changed (47) hide show
  1. assets/css/setup-wizard.css +399 -399
  2. assets/js/confetti.js +347 -347
  3. assets/js/order-script.js +42 -42
  4. assets/js/setup-wizard.js +20 -20
  5. composer.json +5 -5
  6. composer.lock +203 -203
  7. includes/class-wcpdf-admin.php +488 -488
  8. includes/class-wcpdf-frontend.php +7 -1
  9. includes/class-wcpdf-install.php +358 -358
  10. includes/class-wcpdf-main.php +502 -502
  11. includes/class-wcpdf-pdf-maker.php +60 -60
  12. includes/class-wcpdf-settings-callbacks.php +502 -502
  13. includes/class-wcpdf-settings-debug.php +195 -195
  14. includes/class-wcpdf-settings-documents.php +1 -0
  15. includes/class-wcpdf-settings-general.php +298 -298
  16. includes/class-wcpdf-settings.php +254 -254
  17. includes/class-wcpdf-setup-wizard.php +246 -246
  18. includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php +162 -162
  19. includes/documents/abstract-wcpdf-order-document-methods.php +1029 -1029
  20. includes/documents/abstract-wcpdf-order-document.php +677 -676
  21. includes/documents/class-wcpdf-invoice.php +352 -352
  22. includes/documents/class-wcpdf-packing-slip.php +149 -149
  23. includes/documents/class-wcpdf-sequential-number-store.php +167 -167
  24. includes/views/attachment-settings-hint.php +23 -23
  25. includes/views/dompdf-status.php +202 -202
  26. includes/views/setup-wizard/attach-to.php +23 -23
  27. includes/views/setup-wizard/display-options.php +18 -18
  28. includes/views/setup-wizard/good-to-go.php +5 -5
  29. includes/views/setup-wizard/logo.php +8 -8
  30. includes/views/setup-wizard/paper-format.php +13 -13
  31. includes/views/setup-wizard/shop-name.php +10 -10
  32. includes/views/wcpdf-extensions.php +130 -130
  33. includes/views/wcpdf-settings-page.php +51 -51
  34. readme.txt +226 -220
  35. templates/Simple/invoice.php +144 -144
  36. templates/Simple/packing-slip.php +112 -112
  37. vendor/autoload.php +7 -7
  38. vendor/composer/ClassLoader.php +445 -445
  39. vendor/composer/LICENSE +21 -21
  40. vendor/composer/autoload_real.php +52 -52
  41. vendor/composer/installed.json +193 -193
  42. vendor/dompdf/dompdf/CONTRIBUTING.md +65 -65
  43. vendor/dompdf/dompdf/README.md +211 -211
  44. vendor/dompdf/dompdf/composer.json +44 -44
  45. vendor/dompdf/dompdf/lib/Cpdf.php +5640 -5640
  46. vendor/dompdf/dompdf/lib/html5lib/Data.php +123 -123
  47. vendor/dompdf/dompdf/lib/html5lib/Tokenizer.php +0 -26
assets/css/setup-wizard.css CHANGED
@@ -1,399 +1,399 @@
1
- .wpo-wizzard {
2
- background:#f9f9f9;
3
- }
4
-
5
- .wpo-wcpdf-setup {
6
- padding:0;
7
- font-family: 'Ubuntu', sans-serif;
8
- font-size: 14px;
9
- line-height: 22px;
10
- color:#444;
11
- background:none;
12
- -webkit-box-shadow: none;
13
- box-shadow: none;
14
- }
15
-
16
- .wpo-wcpdf-setup h1, .wpo-wcpdf-setup h2 {
17
- border:none;
18
- color:#444;
19
- margin:0;
20
- }
21
-
22
- #confetti {
23
- width:100%;
24
- height:100%;
25
- position:absolute;
26
- left:0;
27
- top:0;
28
- z-index:-999;
29
- }
30
-
31
- .wpo-setup {
32
- background-color:#f9f9f9;
33
- font-family: 'Ubuntu', sans-serif;
34
- font-size: 14px;
35
- line-height: 22px;
36
- color:#444;
37
- width:100%;
38
- height:100%;
39
- margin:0;
40
- padding:0;
41
- }
42
-
43
- .confetti {
44
- width:100%;
45
- height:100%;
46
- z-index:-999;
47
- position:relative;
48
- }
49
-
50
- .wpo-setup-card {
51
- display:block;
52
- background-color:#f2fafa;
53
- max-width:900px;
54
- margin:100px auto 100px auto;
55
- padding:0;
56
- border-radius:10px;
57
- box-shadow:10px 10px 80px rgba(0,0,0,0.05);
58
- overflow:hidden;
59
- }
60
-
61
- .wpo-plugin-title {
62
- width:100%;
63
- padding:30px 0 50px 0;
64
- margin:0;
65
- background-color:#fdfdfd;
66
- text-align:center;
67
- display:block;
68
- color:#61707d;
69
- }
70
-
71
- .wpo-progress-bar {
72
- width:100%;
73
- height:36px;
74
- overflow:hidden;
75
- list-style: none;
76
- margin:-18px 0 0 0;
77
- padding:0;
78
- }
79
-
80
- .wpo-progress-bar li {
81
- width:16.66%;
82
- float:left;
83
- box-sizing: border-box;
84
- }
85
-
86
- .wpo-progress-marker {
87
- height:24px;
88
- width:24px;
89
- background-color:#fdfdfd;
90
- border:6px solid #3e9b9a;
91
- border-radius:50%;
92
- margin:0 auto;
93
- }
94
-
95
- .wpo-progress-bar .completed {
96
- background-color:#3e9b9a;
97
- }
98
-
99
- .wpo-progress-bar .active {
100
- background-color:#2d7170;
101
- }
102
-
103
- .wpo-setup-content {
104
- float:left;
105
- width:100%;
106
- overflow:hidden;
107
- }
108
-
109
- .wpo-step-description {
110
- width:50%;
111
- float:left;
112
- padding:50px;
113
- box-sizing: border-box;
114
- }
115
-
116
- .wpo-step-description ul {
117
- padding-left:16px;
118
- }
119
-
120
- .wpo-setup-input {
121
- width:50%;
122
- float:left;
123
- padding:50px 50px 50px 0;
124
- box-sizing: border-box;
125
- position:relative;
126
- }
127
-
128
- .wpo-setup-input select {
129
- width:100%;
130
- border:none;
131
- font-size:1.4em;
132
- background-color:#fdfdfd;
133
- color:#444;
134
- border:1px solid #3e9b9a;
135
- height:40px;
136
- }
137
-
138
- .wpo-setup-buttons {
139
- width:100%;
140
- float:left;
141
- padding:0px 50px 50px 50px;
142
- box-sizing: border-box;
143
- }
144
-
145
- .wpo-button-next, .wpo-button-previous, .wpo-skip-step {
146
- background-color:#3e9b9a;
147
- border-radius:5px;
148
- padding:10px 20px;
149
- border:none;
150
- box-sizing: border-box;
151
- font-size:1em;
152
- text-decoration: none;
153
- cursor:pointer;
154
- color:#fdfdfd;
155
- font-size:1.4em;
156
- box-sizing: border-box;
157
- }
158
-
159
- .wpo-button-next:hover, .wpo-button-previous:hover {
160
- background-color:#2d7170;
161
- }
162
-
163
- .wpo-button-next, .wpo-skip-step {
164
- float:right;
165
- margin-left:10px;
166
- }
167
-
168
- .wpo-button-previous {
169
- float:left;
170
- }
171
-
172
- .wpo-skip-step {
173
- background-color:#fdfdfd;
174
- color:#61707d;
175
- }
176
-
177
- .wpo-skip-step:hover {
178
- color:#333;
179
- }
180
-
181
- /* Shop address */
182
-
183
- .wpo-setup-input .shop-name {
184
- width:100%;
185
- height:40px;
186
- padding:10px 15px;
187
- box-sizing: border-box;
188
- font-size:1.2em;
189
- margin-bottom:20px;
190
- border:1px solid #3e9b9a;
191
- font-family: 'Ubuntu', sans-serif;
192
- }
193
-
194
- .wpo-setup-input .shop-address {
195
- width:100%;
196
- height:200px;
197
- padding:15px 15px;
198
- font-size:1.2em;
199
- line-height:1.4em;
200
- box-sizing: border-box;
201
- border:1px solid #3e9b9a;
202
- resize: none;
203
- float:left;
204
- margin:0;
205
- font-family: 'Ubuntu', sans-serif;
206
- text-align:left!important;
207
- }
208
-
209
- /* Your logo */
210
-
211
- .wpo-setup-input #img-header_logo {
212
- width:100%;
213
- height:auto;
214
- background:#fdfdfd;
215
- margin-bottom:20px;
216
- padding:20px;
217
- box-sizing: border-box;
218
- position:relative;
219
- }
220
-
221
- .wpo-setup-input #logo-preview img {
222
- height:80%;
223
- position:absolute;
224
- left:50%;
225
- -ms-transform: translate(-50%, 0%); /* IE 9 */
226
- -webkit-transform: translate(-50%, 0%); /* Safari */
227
- transform: translate(-50%, 0%);
228
- }
229
-
230
- /* Attach to */
231
-
232
- .wpo-setup-input input[type=checkbox] {
233
- -webkit-appearance: none;
234
- background-color: #fdfdfd;
235
- border: 1px solid #cacece;
236
- box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05);
237
- padding: 14px;
238
- border-radius: 3px;
239
- display: inline-block;
240
- position: relative;
241
- }
242
-
243
- .wpo-setup-input input[type=checkbox]:active, .wpo-setup-input input[type=checkbox]:checked:active {
244
- box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px 1px 3px rgba(0,0,0,0.1);
245
- }
246
-
247
- .wpo-setup-input input[type=checkbox]:checked {
248
- background-color: #fdfdfd;
249
- border: 1px solid #adb8c0;
250
- box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05), inset 15px 10px -12px rgba(255,255,255,0.1);
251
- color: #99a1a7;
252
- }
253
-
254
- .wpo-setup-input input[type=checkbox]:checked:after {
255
- content: '\2714';
256
- font-size: 24px;
257
- position: absolute;
258
- top: 0px;
259
- left: 3px;
260
- color: #3e9b9a;
261
- }
262
-
263
- .wpo-setup-input .checkbox {
264
- font-size:1.2em;
265
- margin-left:50px;
266
- margin-top:-35px;
267
- margin-bottom:-8px;
268
- display:block;
269
- }
270
-
271
- /* Customizer */
272
-
273
- .wpo-setup-input .how-to-box {
274
- float:right;
275
- width:auto;
276
- max-width:100%;
277
- height:auto;
278
- border:1px solid #3e9b9a;
279
- overflow:hidden;
280
- background:#fff;
281
- }
282
-
283
- .wpo-setup-input .how-to-box img {
284
- width:100%;
285
- }
286
-
287
- .wpo-step-description a:link, .wpo-step-description a:visited {
288
- color:#3e9b9a;
289
- text-decoration:none;
290
- }
291
-
292
- /* Good to go */
293
-
294
- .wpo-final {
295
- text-align:center;
296
- width:100%;
297
- padding:50px 200px;
298
- }
299
-
300
- .wpo-final h1 {
301
- font-size:3em;
302
- line-height:1em;
303
- }
304
-
305
- /* Portrait tablets and small desktops */
306
- @media (min-width: 768px) and (max-width: 991px) {
307
- .wpo-setup-card {
308
- width:80%;
309
- }
310
- }
311
-
312
- /* Landscape phones and portrait tablets */
313
- @media (max-width: 767px) {
314
- .wpo-setup-card {
315
- width:100%;
316
- min-height:100%;
317
- border-radius:0px;
318
- margin:0;
319
- }
320
-
321
- .wpo-step-description, .wpo-setup-input {
322
- width:100%;
323
- padding-left:50px;
324
- }
325
-
326
- .wpo-final {
327
- padding:50px;
328
- }
329
-
330
- .wpo-progress-bar {
331
- margin-top:-12px;
332
- }
333
-
334
- .wpo-progress-marker {
335
- height:16px;
336
- width:16px;
337
- border-width:4px;
338
- }
339
-
340
- .wpo-button-next, .wpo-button-previous, .wpo-skip-step {
341
- font-size:1em;
342
- padding:8px 12px;
343
- }
344
- }
345
-
346
- /* Landscape phones and smaller */
347
- @media (max-width: 480px) {
348
-
349
- .wpo-wizzard, .wpo-wcpdf-setup {
350
- margin:0;
351
- height:100%;
352
- }
353
-
354
- .wpo-wcpdf-setup form {
355
- height:100%;
356
- }
357
-
358
- .wpo-setup-card {
359
- width:100%;
360
- min-height:100%;
361
- border-radius:0px;
362
- margin:0;
363
- }
364
-
365
- .wpo-plugin-title {
366
- padding:25px 0 30px 0;
367
- font-size:1.4em;
368
- }
369
-
370
- .wpo-step-description, .wpo-setup-input, .wpo-setup-buttons {
371
- width:100%;
372
- padding:20px;
373
- }
374
-
375
- .wpo-step-description {
376
- padding-bottom:0;
377
- }
378
-
379
- .wpo-progress-bar {
380
- margin-top:-12px;
381
- }
382
-
383
- .wpo-progress-marker {
384
- height:16px;
385
- width:16px;
386
- border-width:4px;
387
- }
388
-
389
- .wpo-button-next, .wpo-button-previous, .wpo-skip-step {
390
- font-size:1em;
391
- padding:8px 12px;
392
- }
393
-
394
- .wpo-setup-input .checkbox {
395
- font-size:1em;
396
- }
397
- }
398
-
399
-
1
+ .wpo-wizzard {
2
+ background:#f9f9f9;
3
+ }
4
+
5
+ .wpo-wcpdf-setup {
6
+ padding:0;
7
+ font-family: 'Ubuntu', sans-serif;
8
+ font-size: 14px;
9
+ line-height: 22px;
10
+ color:#444;
11
+ background:none;
12
+ -webkit-box-shadow: none;
13
+ box-shadow: none;
14
+ }
15
+
16
+ .wpo-wcpdf-setup h1, .wpo-wcpdf-setup h2 {
17
+ border:none;
18
+ color:#444;
19
+ margin:0;
20
+ }
21
+
22
+ #confetti {
23
+ width:100%;
24
+ height:100%;
25
+ position:absolute;
26
+ left:0;
27
+ top:0;
28
+ z-index:-999;
29
+ }
30
+
31
+ .wpo-setup {
32
+ background-color:#f9f9f9;
33
+ font-family: 'Ubuntu', sans-serif;
34
+ font-size: 14px;
35
+ line-height: 22px;
36
+ color:#444;
37
+ width:100%;
38
+ height:100%;
39
+ margin:0;
40
+ padding:0;
41
+ }
42
+
43
+ .confetti {
44
+ width:100%;
45
+ height:100%;
46
+ z-index:-999;
47
+ position:relative;
48
+ }
49
+
50
+ .wpo-setup-card {
51
+ display:block;
52
+ background-color:#f2fafa;
53
+ max-width:900px;
54
+ margin:100px auto 100px auto;
55
+ padding:0;
56
+ border-radius:10px;
57
+ box-shadow:10px 10px 80px rgba(0,0,0,0.05);
58
+ overflow:hidden;
59
+ }
60
+
61
+ .wpo-plugin-title {
62
+ width:100%;
63
+ padding:30px 0 50px 0;
64
+ margin:0;
65
+ background-color:#fdfdfd;
66
+ text-align:center;
67
+ display:block;
68
+ color:#61707d;
69
+ }
70
+
71
+ .wpo-progress-bar {
72
+ width:100%;
73
+ height:36px;
74
+ overflow:hidden;
75
+ list-style: none;
76
+ margin:-18px 0 0 0;
77
+ padding:0;
78
+ }
79
+
80
+ .wpo-progress-bar li {
81
+ width:16.66%;
82
+ float:left;
83
+ box-sizing: border-box;
84
+ }
85
+
86
+ .wpo-progress-marker {
87
+ height:24px;
88
+ width:24px;
89
+ background-color:#fdfdfd;
90
+ border:6px solid #3e9b9a;
91
+ border-radius:50%;
92
+ margin:0 auto;
93
+ }
94
+
95
+ .wpo-progress-bar .completed {
96
+ background-color:#3e9b9a;
97
+ }
98
+
99
+ .wpo-progress-bar .active {
100
+ background-color:#2d7170;
101
+ }
102
+
103
+ .wpo-setup-content {
104
+ float:left;
105
+ width:100%;
106
+ overflow:hidden;
107
+ }
108
+
109
+ .wpo-step-description {
110
+ width:50%;
111
+ float:left;
112
+ padding:50px;
113
+ box-sizing: border-box;
114
+ }
115
+
116
+ .wpo-step-description ul {
117
+ padding-left:16px;
118
+ }
119
+
120
+ .wpo-setup-input {
121
+ width:50%;
122
+ float:left;
123
+ padding:50px 50px 50px 0;
124
+ box-sizing: border-box;
125
+ position:relative;
126
+ }
127
+
128
+ .wpo-setup-input select {
129
+ width:100%;
130
+ border:none;
131
+ font-size:1.4em;
132
+ background-color:#fdfdfd;
133
+ color:#444;
134
+ border:1px solid #3e9b9a;
135
+ height:40px;
136
+ }
137
+
138
+ .wpo-setup-buttons {
139
+ width:100%;
140
+ float:left;
141
+ padding:0px 50px 50px 50px;
142
+ box-sizing: border-box;
143
+ }
144
+
145
+ .wpo-button-next, .wpo-button-previous, .wpo-skip-step {
146
+ background-color:#3e9b9a;
147
+ border-radius:5px;
148
+ padding:10px 20px;
149
+ border:none;
150
+ box-sizing: border-box;
151
+ font-size:1em;
152
+ text-decoration: none;
153
+ cursor:pointer;
154
+ color:#fdfdfd;
155
+ font-size:1.4em;
156
+ box-sizing: border-box;
157
+ }
158
+
159
+ .wpo-button-next:hover, .wpo-button-previous:hover {
160
+ background-color:#2d7170;
161
+ }
162
+
163
+ .wpo-button-next, .wpo-skip-step {
164
+ float:right;
165
+ margin-left:10px;
166
+ }
167
+
168
+ .wpo-button-previous {
169
+ float:left;
170
+ }
171
+
172
+ .wpo-skip-step {
173
+ background-color:#fdfdfd;
174
+ color:#61707d;
175
+ }
176
+
177
+ .wpo-skip-step:hover {
178
+ color:#333;
179
+ }
180
+
181
+ /* Shop address */
182
+
183
+ .wpo-setup-input .shop-name {
184
+ width:100%;
185
+ height:40px;
186
+ padding:10px 15px;
187
+ box-sizing: border-box;
188
+ font-size:1.2em;
189
+ margin-bottom:20px;
190
+ border:1px solid #3e9b9a;
191
+ font-family: 'Ubuntu', sans-serif;
192
+ }
193
+
194
+ .wpo-setup-input .shop-address {
195
+ width:100%;
196
+ height:200px;
197
+ padding:15px 15px;
198
+ font-size:1.2em;
199
+ line-height:1.4em;
200
+ box-sizing: border-box;
201
+ border:1px solid #3e9b9a;
202
+ resize: none;
203
+ float:left;
204
+ margin:0;
205
+ font-family: 'Ubuntu', sans-serif;
206
+ text-align:left!important;
207
+ }
208
+
209
+ /* Your logo */
210
+
211
+ .wpo-setup-input #img-header_logo {
212
+ width:100%;
213
+ height:auto;
214
+ background:#fdfdfd;
215
+ margin-bottom:20px;
216
+ padding:20px;
217
+ box-sizing: border-box;
218
+ position:relative;
219
+ }
220
+
221
+ .wpo-setup-input #logo-preview img {
222
+ height:80%;
223
+ position:absolute;
224
+ left:50%;
225
+ -ms-transform: translate(-50%, 0%); /* IE 9 */
226
+ -webkit-transform: translate(-50%, 0%); /* Safari */
227
+ transform: translate(-50%, 0%);
228
+ }
229
+
230
+ /* Attach to */
231
+
232
+ .wpo-setup-input input[type=checkbox] {
233
+ -webkit-appearance: none;
234
+ background-color: #fdfdfd;
235
+ border: 1px solid #cacece;
236
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05);
237
+ padding: 14px;
238
+ border-radius: 3px;
239
+ display: inline-block;
240
+ position: relative;
241
+ }
242
+
243
+ .wpo-setup-input input[type=checkbox]:active, .wpo-setup-input input[type=checkbox]:checked:active {
244
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px 1px 3px rgba(0,0,0,0.1);
245
+ }
246
+
247
+ .wpo-setup-input input[type=checkbox]:checked {
248
+ background-color: #fdfdfd;
249
+ border: 1px solid #adb8c0;
250
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05), inset 15px 10px -12px rgba(255,255,255,0.1);
251
+ color: #99a1a7;
252
+ }
253
+
254
+ .wpo-setup-input input[type=checkbox]:checked:after {
255
+ content: '\2714';
256
+ font-size: 24px;
257
+ position: absolute;
258
+ top: 0px;
259
+ left: 3px;
260
+ color: #3e9b9a;
261
+ }
262
+
263
+ .wpo-setup-input .checkbox {
264
+ font-size:1.2em;
265
+ margin-left:50px;
266
+ margin-top:-35px;
267
+ margin-bottom:-8px;
268
+ display:block;
269
+ }
270
+
271
+ /* Customizer */
272
+
273
+ .wpo-setup-input .how-to-box {
274
+ float:right;
275
+ width:auto;
276
+ max-width:100%;
277
+ height:auto;
278
+ border:1px solid #3e9b9a;
279
+ overflow:hidden;
280
+ background:#fff;
281
+ }
282
+
283
+ .wpo-setup-input .how-to-box img {
284
+ width:100%;
285
+ }
286
+
287
+ .wpo-step-description a:link, .wpo-step-description a:visited {
288
+ color:#3e9b9a;
289
+ text-decoration:none;
290
+ }
291
+
292
+ /* Good to go */
293
+
294
+ .wpo-final {
295
+ text-align:center;
296
+ width:100%;
297
+ padding:50px 200px;
298
+ }
299
+
300
+ .wpo-final h1 {
301
+ font-size:3em;
302
+ line-height:1em;
303
+ }
304
+
305
+ /* Portrait tablets and small desktops */
306
+ @media (min-width: 768px) and (max-width: 991px) {
307
+ .wpo-setup-card {
308
+ width:80%;
309
+ }
310
+ }
311
+
312
+ /* Landscape phones and portrait tablets */
313
+ @media (max-width: 767px) {
314
+ .wpo-setup-card {
315
+ width:100%;
316
+ min-height:100%;
317
+ border-radius:0px;
318
+ margin:0;
319
+ }
320
+
321
+ .wpo-step-description, .wpo-setup-input {
322
+ width:100%;
323
+ padding-left:50px;
324
+ }
325
+
326
+ .wpo-final {
327
+ padding:50px;
328
+ }
329
+
330
+ .wpo-progress-bar {
331
+ margin-top:-12px;
332
+ }
333
+
334
+ .wpo-progress-marker {
335
+ height:16px;
336
+ width:16px;
337
+ border-width:4px;
338
+ }
339
+
340
+ .wpo-button-next, .wpo-button-previous, .wpo-skip-step {
341
+ font-size:1em;
342
+ padding:8px 12px;
343
+ }
344
+ }
345
+
346
+ /* Landscape phones and smaller */
347
+ @media (max-width: 480px) {
348
+
349
+ .wpo-wizzard, .wpo-wcpdf-setup {
350
+ margin:0;
351
+ height:100%;
352
+ }
353
+
354
+ .wpo-wcpdf-setup form {
355
+ height:100%;
356
+ }
357
+
358
+ .wpo-setup-card {
359
+ width:100%;
360
+ min-height:100%;
361
+ border-radius:0px;
362
+ margin:0;
363
+ }
364
+
365
+ .wpo-plugin-title {
366
+ padding:25px 0 30px 0;
367
+ font-size:1.4em;
368
+ }
369
+
370
+ .wpo-step-description, .wpo-setup-input, .wpo-setup-buttons {
371
+ width:100%;
372
+ padding:20px;
373
+ }
374
+
375
+ .wpo-step-description {
376
+ padding-bottom:0;
377
+ }
378
+
379
+ .wpo-progress-bar {
380
+ margin-top:-12px;
381
+ }
382
+
383
+ .wpo-progress-marker {
384
+ height:16px;
385
+ width:16px;
386
+ border-width:4px;
387
+ }
388
+
389
+ .wpo-button-next, .wpo-button-previous, .wpo-skip-step {
390
+ font-size:1em;
391
+ padding:8px 12px;
392
+ }
393
+
394
+ .wpo-setup-input .checkbox {
395
+ font-size:1em;
396
+ }
397
+ }
398
+
399
+
assets/js/confetti.js CHANGED
@@ -1,348 +1,348 @@
1
- jQuery(document).ready(function($) {
2
- var frameRate = 30;
3
- var dt = 1.0 / frameRate;
4
- var DEG_TO_RAD = Math.PI / 180;
5
- var RAD_TO_DEG = 180 / Math.PI;
6
- var colors = [
7
- ["#df0049", "#660671"],
8
- ["#00e857", "#005291"],
9
- ["#2bebbc", "#05798a"],
10
- ["#ffd200", "#b06c00"]
11
- ];
12
-
13
- function Vector2(_x, _y) {
14
- this.x = _x, this.y = _y;
15
- this.Length = function() {
16
- return Math.sqrt(this.SqrLength());
17
- }
18
- this.SqrLength = function() {
19
- return this.x * this.x + this.y * this.y;
20
- }
21
- this.Equals = function(_vec0, _vec1) {
22
- return _vec0.x == _vec1.x && _vec0.y == _vec1.y;
23
- }
24
- this.Add = function(_vec) {
25
- this.x += _vec.x;
26
- this.y += _vec.y;
27
- }
28
- this.Sub = function(_vec) {
29
- this.x -= _vec.x;
30
- this.y -= _vec.y;
31
- }
32
- this.Div = function(_f) {
33
- this.x /= _f;
34
- this.y /= _f;
35
- }
36
- this.Mul = function(_f) {
37
- this.x *= _f;
38
- this.y *= _f;
39
- }
40
- this.Normalize = function() {
41
- var sqrLen = this.SqrLength();
42
- if (sqrLen != 0) {
43
- var factor = 1.0 / Math.sqrt(sqrLen);
44
- this.x *= factor;
45
- this.y *= factor;
46
- }
47
- }
48
- this.Normalized = function() {
49
- var sqrLen = this.SqrLength();
50
- if (sqrLen != 0) {
51
- var factor = 1.0 / Math.sqrt(sqrLen);
52
- return new Vector2(this.x * factor, this.y * factor);
53
- }
54
- return new Vector2(0, 0);
55
- }
56
- }
57
- Vector2.Lerp = function(_vec0, _vec1, _t) {
58
- return new Vector2((_vec1.x - _vec0.x) * _t + _vec0.x, (_vec1.y - _vec0.y) * _t + _vec0.y);
59
- }
60
- Vector2.Distance = function(_vec0, _vec1) {
61
- return Math.sqrt(Vector2.SqrDistance(_vec0, _vec1));
62
- }
63
- Vector2.SqrDistance = function(_vec0, _vec1) {
64
- var x = _vec0.x - _vec1.x;
65
- var y = _vec0.y - _vec1.y;
66
- return (x * x + y * y + z * z);
67
- }
68
- Vector2.Scale = function(_vec0, _vec1) {
69
- return new Vector2(_vec0.x * _vec1.x, _vec0.y * _vec1.y);
70
- }
71
- Vector2.Min = function(_vec0, _vec1) {
72
- return new Vector2(Math.min(_vec0.x, _vec1.x), Math.min(_vec0.y, _vec1.y));
73
- }
74
- Vector2.Max = function(_vec0, _vec1) {
75
- return new Vector2(Math.max(_vec0.x, _vec1.x), Math.max(_vec0.y, _vec1.y));
76
- }
77
- Vector2.ClampMagnitude = function(_vec0, _len) {
78
- var vecNorm = _vec0.Normalized;
79
- return new Vector2(vecNorm.x * _len, vecNorm.y * _len);
80
- }
81
- Vector2.Sub = function(_vec0, _vec1) {
82
- return new Vector2(_vec0.x - _vec1.x, _vec0.y - _vec1.y, _vec0.z - _vec1.z);
83
- }
84
-
85
- function EulerMass(_x, _y, _mass, _drag) {
86
- this.position = new Vector2(_x, _y);
87
- this.mass = _mass;
88
- this.drag = _drag;
89
- this.force = new Vector2(0, 0);
90
- this.velocity = new Vector2(0, 0);
91
- this.AddForce = function(_f) {
92
- this.force.Add(_f);
93
- }
94
- this.Integrate = function(_dt) {
95
- var acc = this.CurrentForce(this.position);
96
- acc.Div(this.mass);
97
- var posDelta = new Vector2(this.velocity.x, this.velocity.y);
98
- posDelta.Mul(_dt);
99
- this.position.Add(posDelta);
100
- acc.Mul(_dt);
101
- this.velocity.Add(acc);
102
- this.force = new Vector2(0, 0);
103
- }
104
- this.CurrentForce = function(_pos, _vel) {
105
- var totalForce = new Vector2(this.force.x, this.force.y);
106
- var speed = this.velocity.Length();
107
- var dragVel = new Vector2(this.velocity.x, this.velocity.y);
108
- dragVel.Mul(this.drag * this.mass * speed);
109
- totalForce.Sub(dragVel);
110
- return totalForce;
111
- }
112
- }
113
-
114
- function ConfettiPaper(_x, _y) {
115
- this.pos = new Vector2(_x, _y);
116
- this.rotationSpeed = Math.random() * 600 + 800;
117
- this.angle = DEG_TO_RAD * Math.random() * 360;
118
- this.rotation = DEG_TO_RAD * Math.random() * 360;
119
- this.cosA = 1.0;
120
- this.size = 5.0;
121
- this.oscillationSpeed = Math.random() * 1.5 + 0.5;
122
- this.xSpeed = 40.0;
123
- this.ySpeed = Math.random() * 60 + 50.0;
124
- this.corners = new Array();
125
- this.time = Math.random();
126
- var ci = Math.round(Math.random() * (colors.length - 1));
127
- this.frontColor = colors[ci][0];
128
- this.backColor = colors[ci][1];
129
- for (var i = 0; i < 4; i++) {
130
- var dx = Math.cos(this.angle + DEG_TO_RAD * (i * 90 + 45));
131
- var dy = Math.sin(this.angle + DEG_TO_RAD * (i * 90 + 45));
132
- this.corners[i] = new Vector2(dx, dy);
133
- }
134
- this.Update = function(_dt) {
135
- this.time += _dt;
136
- this.rotation += this.rotationSpeed * _dt;
137
- this.cosA = Math.cos(DEG_TO_RAD * this.rotation);
138
- this.pos.x += Math.cos(this.time * this.oscillationSpeed) * this.xSpeed * _dt
139
- this.pos.y += this.ySpeed * _dt;
140
- if (this.pos.y > ConfettiPaper.bounds.y) {
141
- this.pos.x = Math.random() * ConfettiPaper.bounds.x;
142
- this.pos.y = 0;
143
- }
144
- }
145
- this.Draw = function(_g) {
146
- if (this.cosA > 0) {
147
- _g.fillStyle = this.frontColor;
148
- } else {
149
- _g.fillStyle = this.backColor;
150
- }
151
- _g.beginPath();
152
- _g.moveTo(this.pos.x + this.corners[0].x * this.size, this.pos.y + this.corners[0].y * this.size * this.cosA);
153
- for (var i = 1; i < 4; i++) {
154
- _g.lineTo(this.pos.x + this.corners[i].x * this.size, this.pos.y + this.corners[i].y * this.size * this.cosA);
155
- }
156
- _g.closePath();
157
- _g.fill();
158
- }
159
- }
160
- ConfettiPaper.bounds = new Vector2(0, 0);
161
-
162
- function ConfettiRibbon(_x, _y, _count, _dist, _thickness, _angle, _mass, _drag) {
163
- this.particleDist = _dist;
164
- this.particleCount = _count;
165
- this.particleMass = _mass;
166
- this.particleDrag = _drag;
167
- this.particles = new Array();
168
- var ci = Math.round(Math.random() * (colors.length - 1));
169
- this.frontColor = colors[ci][0];
170
- this.backColor = colors[ci][1];
171
- this.xOff = Math.cos(DEG_TO_RAD * _angle) * _thickness;
172
- this.yOff = Math.sin(DEG_TO_RAD * _angle) * _thickness;
173
- this.position = new Vector2(_x, _y);
174
- this.prevPosition = new Vector2(_x, _y);
175
- this.velocityInherit = Math.random() * 2 + 4;
176
- this.time = Math.random() * 100;
177
- this.oscillationSpeed = Math.random() * 2 + 2;
178
- this.oscillationDistance = Math.random() * 40 + 40;
179
- this.ySpeed = Math.random() * 40 + 80;
180
- for (var i = 0; i < this.particleCount; i++) {
181
- this.particles[i] = new EulerMass(_x, _y - i * this.particleDist, this.particleMass, this.particleDrag);
182
- }
183
- this.Update = function(_dt) {
184
- var i = 0;
185
- this.time += _dt * this.oscillationSpeed;
186
- this.position.y += this.ySpeed * _dt;
187
- this.position.x += Math.cos(this.time) * this.oscillationDistance * _dt;
188
- this.particles[0].position = this.position;
189
- var dX = this.prevPosition.x - this.position.x;
190
- var dY = this.prevPosition.y - this.position.y;
191
- var delta = Math.sqrt(dX * dX + dY * dY);
192
- this.prevPosition = new Vector2(this.position.x, this.position.y);
193
- for (i = 1; i < this.particleCount; i++) {
194
- var dirP = Vector2.Sub(this.particles[i - 1].position, this.particles[i].position);
195
- dirP.Normalize();
196
- dirP.Mul((delta / _dt) * this.velocityInherit);
197
- this.particles[i].AddForce(dirP);
198
- }
199
- for (i = 1; i < this.particleCount; i++) {
200
- this.particles[i].Integrate(_dt);
201
- }
202
- for (i = 1; i < this.particleCount; i++) {
203
- var rp2 = new Vector2(this.particles[i].position.x, this.particles[i].position.y);
204
- rp2.Sub(this.particles[i - 1].position);
205
- rp2.Normalize();
206
- rp2.Mul(this.particleDist);
207
- rp2.Add(this.particles[i - 1].position);
208
- this.particles[i].position = rp2;
209
- }
210
- if (this.position.y > ConfettiRibbon.bounds.y + this.particleDist * this.particleCount) {
211
- this.Reset();
212
- }
213
- }
214
- this.Reset = function() {
215
- this.position.y = -Math.random() * ConfettiRibbon.bounds.y;
216
- this.position.x = Math.random() * ConfettiRibbon.bounds.x;
217
- this.prevPosition = new Vector2(this.position.x, this.position.y);
218
- this.velocityInherit = Math.random() * 2 + 4;
219
- this.time = Math.random() * 100;
220
- this.oscillationSpeed = Math.random() * 2.0 + 1.5;
221
- this.oscillationDistance = Math.random() * 40 + 40;
222
- this.ySpeed = Math.random() * 40 + 80;
223
- var ci = Math.round(Math.random() * (colors.length - 1));
224
- this.frontColor = colors[ci][0];
225
- this.backColor = colors[ci][1];
226
- this.particles = new Array();
227
- for (var i = 0; i < this.particleCount; i++) {
228
- this.particles[i] = new EulerMass(this.position.x, this.position.y - i * this.particleDist, this.particleMass, this.particleDrag);
229
- }
230
- }
231
- this.Draw = function(_g) {
232
- for (var i = 0; i < this.particleCount - 1; i++) {
233
- var p0 = new Vector2(this.particles[i].position.x + this.xOff, this.particles[i].position.y + this.yOff);
234
- var p1 = new Vector2(this.particles[i + 1].position.x + this.xOff, this.particles[i + 1].position.y + this.yOff);
235
- if (this.Side(this.particles[i].position.x, this.particles[i].position.y, this.particles[i + 1].position.x, this.particles[i + 1].position.y, p1.x, p1.y) < 0) {
236
- _g.fillStyle = this.frontColor;
237
- _g.strokeStyle = this.frontColor;
238
- } else {
239
- _g.fillStyle = this.backColor;
240
- _g.strokeStyle = this.backColor;
241
- }
242
- if (i == 0) {
243
- _g.beginPath();
244
- _g.moveTo(this.particles[i].position.x, this.particles[i].position.y);
245
- _g.lineTo(this.particles[i + 1].position.x, this.particles[i + 1].position.y);
246
- _g.lineTo((this.particles[i + 1].position.x + p1.x) * 0.5, (this.particles[i + 1].position.y + p1.y) * 0.5);
247
- _g.closePath();
248
- _g.stroke();
249
- _g.fill();
250
- _g.beginPath();
251
- _g.moveTo(p1.x, p1.y);
252
- _g.lineTo(p0.x, p0.y);
253
- _g.lineTo((this.particles[i + 1].position.x + p1.x) * 0.5, (this.particles[i + 1].position.y + p1.y) * 0.5);
254
- _g.closePath();
255
- _g.stroke();
256
- _g.fill();
257
- } else if (i == this.particleCount - 2) {
258
- _g.beginPath();
259
- _g.moveTo(this.particles[i].position.x, this.particles[i].position.y);
260
- _g.lineTo(this.particles[i + 1].position.x, this.particles[i + 1].position.y);
261
- _g.lineTo((this.particles[i].position.x + p0.x) * 0.5, (this.particles[i].position.y + p0.y) * 0.5);
262
- _g.closePath();
263
- _g.stroke();
264
- _g.fill();
265
- _g.beginPath();
266
- _g.moveTo(p1.x, p1.y);
267
- _g.lineTo(p0.x, p0.y);
268
- _g.lineTo((this.particles[i].position.x + p0.x) * 0.5, (this.particles[i].position.y + p0.y) * 0.5);
269
- _g.closePath();
270
- _g.stroke();
271
- _g.fill();
272
- } else {
273
- _g.beginPath();
274
- _g.moveTo(this.particles[i].position.x, this.particles[i].position.y);
275
- _g.lineTo(this.particles[i + 1].position.x, this.particles[i + 1].position.y);
276
- _g.lineTo(p1.x, p1.y);
277
- _g.lineTo(p0.x, p0.y);
278
- _g.closePath();
279
- _g.stroke();
280
- _g.fill();
281
- }
282
- }
283
- }
284
- this.Side = function(x1, y1, x2, y2, x3, y3) {
285
- return ((x1 - x2) * (y3 - y2) - (y1 - y2) * (x3 - x2));
286
- }
287
- }
288
- ConfettiRibbon.bounds = new Vector2(0, 0);
289
- confetti = {};
290
- confetti.Context = function(parent) {
291
- var i = 0;
292
- var canvasParent = document.getElementById(parent);
293
- var canvas = document.createElement('canvas');
294
- canvas.width = canvasParent.offsetWidth;
295
- canvas.height = canvasParent.offsetHeight;
296
- canvasParent.appendChild(canvas);
297
- var context = canvas.getContext('2d');
298
- var interval = null;
299
- var confettiRibbonCount = 7;
300
- var rpCount = 30;
301
- var rpDist = 8.0;
302
- var rpThick = 8.0;
303
- var confettiRibbons = new Array();
304
- ConfettiRibbon.bounds = new Vector2(canvas.width, canvas.height);
305
- for (i = 0; i < confettiRibbonCount; i++) {
306
- confettiRibbons[i] = new ConfettiRibbon(Math.random() * canvas.width, -Math.random() * canvas.height * 2, rpCount, rpDist, rpThick, 45, 1, 0.05);
307
- }
308
- var confettiPaperCount = 25;
309
- var confettiPapers = new Array();
310
- ConfettiPaper.bounds = new Vector2(canvas.width, canvas.height);
311
- for (i = 0; i < confettiPaperCount; i++) {
312
- confettiPapers[i] = new ConfettiPaper(Math.random() * canvas.width, Math.random() * canvas.height);
313
- }
314
- this.resize = function() {
315
- canvas.width = canvasParent.offsetWidth;
316
- canvas.height = canvasParent.offsetHeight;
317
- ConfettiPaper.bounds = new Vector2(canvas.width, canvas.height);
318
- ConfettiRibbon.bounds = new Vector2(canvas.width, canvas.height);
319
- }
320
- this.start = function() {
321
- this.stop()
322
- var context = this
323
- this.interval = setInterval(function() {
324
- confetti.update();
325
- }, 1000.0 / frameRate)
326
- }
327
- this.stop = function() {
328
- clearInterval(this.interval);
329
- }
330
- this.update = function() {
331
- var i = 0;
332
- context.clearRect(0, 0, canvas.width, canvas.height);
333
- for (i = 0; i < confettiPaperCount; i++) {
334
- confettiPapers[i].Update(dt);
335
- confettiPapers[i].Draw(context);
336
- }
337
- for (i = 0; i < confettiRibbonCount; i++) {
338
- confettiRibbons[i].Update(dt);
339
- confettiRibbons[i].Draw(context);
340
- }
341
- }
342
- }
343
- var confetti = new confetti.Context('confetti');
344
- confetti.start();
345
- $(window).resize(function() {
346
- confetti.resize();
347
- });
348
  });
1
+ jQuery(document).ready(function($) {
2
+ var frameRate = 30;
3
+ var dt = 1.0 / frameRate;
4
+ var DEG_TO_RAD = Math.PI / 180;
5
+ var RAD_TO_DEG = 180 / Math.PI;
6
+ var colors = [
7
+ ["#df0049", "#660671"],
8
+ ["#00e857", "#005291"],
9
+ ["#2bebbc", "#05798a"],
10
+ ["#ffd200", "#b06c00"]
11
+ ];
12
+
13
+ function Vector2(_x, _y) {
14
+ this.x = _x, this.y = _y;
15
+ this.Length = function() {
16
+ return Math.sqrt(this.SqrLength());
17
+ }
18
+ this.SqrLength = function() {
19
+ return this.x * this.x + this.y * this.y;
20
+ }
21
+ this.Equals = function(_vec0, _vec1) {
22
+ return _vec0.x == _vec1.x && _vec0.y == _vec1.y;
23
+ }
24
+ this.Add = function(_vec) {
25
+ this.x += _vec.x;
26
+ this.y += _vec.y;
27
+ }
28
+ this.Sub = function(_vec) {
29
+ this.x -= _vec.x;
30
+ this.y -= _vec.y;
31
+ }
32
+ this.Div = function(_f) {
33
+ this.x /= _f;
34
+ this.y /= _f;
35
+ }
36
+ this.Mul = function(_f) {
37
+ this.x *= _f;
38
+ this.y *= _f;
39
+ }
40
+ this.Normalize = function() {
41
+ var sqrLen = this.SqrLength();
42
+ if (sqrLen != 0) {
43
+ var factor = 1.0 / Math.sqrt(sqrLen);
44
+ this.x *= factor;
45
+ this.y *= factor;
46
+ }
47
+ }
48
+ this.Normalized = function() {
49
+ var sqrLen = this.SqrLength();
50
+ if (sqrLen != 0) {
51
+ var factor = 1.0 / Math.sqrt(sqrLen);
52
+ return new Vector2(this.x * factor, this.y * factor);
53
+ }
54
+ return new Vector2(0, 0);
55
+ }
56
+ }
57
+ Vector2.Lerp = function(_vec0, _vec1, _t) {
58
+ return new Vector2((_vec1.x - _vec0.x) * _t + _vec0.x, (_vec1.y - _vec0.y) * _t + _vec0.y);
59
+ }
60
+ Vector2.Distance = function(_vec0, _vec1) {
61
+ return Math.sqrt(Vector2.SqrDistance(_vec0, _vec1));
62
+ }
63
+ Vector2.SqrDistance = function(_vec0, _vec1) {
64
+ var x = _vec0.x - _vec1.x;
65
+ var y = _vec0.y - _vec1.y;
66
+ return (x * x + y * y + z * z);
67
+ }
68
+ Vector2.Scale = function(_vec0, _vec1) {
69
+ return new Vector2(_vec0.x * _vec1.x, _vec0.y * _vec1.y);
70
+ }
71
+ Vector2.Min = function(_vec0, _vec1) {
72
+ return new Vector2(Math.min(_vec0.x, _vec1.x), Math.min(_vec0.y, _vec1.y));
73
+ }
74
+ Vector2.Max = function(_vec0, _vec1) {
75
+ return new Vector2(Math.max(_vec0.x, _vec1.x), Math.max(_vec0.y, _vec1.y));
76
+ }
77
+ Vector2.ClampMagnitude = function(_vec0, _len) {
78
+ var vecNorm = _vec0.Normalized;
79
+ return new Vector2(vecNorm.x * _len, vecNorm.y * _len);
80
+ }
81
+ Vector2.Sub = function(_vec0, _vec1) {
82
+ return new Vector2(_vec0.x - _vec1.x, _vec0.y - _vec1.y, _vec0.z - _vec1.z);
83
+ }
84
+
85
+ function EulerMass(_x, _y, _mass, _drag) {
86
+ this.position = new Vector2(_x, _y);
87
+ this.mass = _mass;
88
+ this.drag = _drag;
89
+ this.force = new Vector2(0, 0);
90
+ this.velocity = new Vector2(0, 0);
91
+ this.AddForce = function(_f) {
92
+ this.force.Add(_f);
93
+ }
94
+ this.Integrate = function(_dt) {
95
+ var acc = this.CurrentForce(this.position);
96
+ acc.Div(this.mass);
97
+ var posDelta = new Vector2(this.velocity.x, this.velocity.y);
98
+ posDelta.Mul(_dt);
99
+ this.position.Add(posDelta);
100
+ acc.Mul(_dt);
101
+ this.velocity.Add(acc);
102
+ this.force = new Vector2(0, 0);
103
+ }
104
+ this.CurrentForce = function(_pos, _vel) {
105
+ var totalForce = new Vector2(this.force.x, this.force.y);
106
+ var speed = this.velocity.Length();
107
+ var dragVel = new Vector2(this.velocity.x, this.velocity.y);
108
+ dragVel.Mul(this.drag * this.mass * speed);
109
+ totalForce.Sub(dragVel);
110
+ return totalForce;
111
+ }
112
+ }
113
+
114
+ function ConfettiPaper(_x, _y) {
115
+ this.pos = new Vector2(_x, _y);
116
+ this.rotationSpeed = Math.random() * 600 + 800;
117
+ this.angle = DEG_TO_RAD * Math.random() * 360;
118
+ this.rotation = DEG_TO_RAD * Math.random() * 360;
119
+ this.cosA = 1.0;
120
+ this.size = 5.0;
121
+ this.oscillationSpeed = Math.random() * 1.5 + 0.5;
122
+ this.xSpeed = 40.0;
123
+ this.ySpeed = Math.random() * 60 + 50.0;
124
+ this.corners = new Array();
125
+ this.time = Math.random();
126
+ var ci = Math.round(Math.random() * (colors.length - 1));
127
+ this.frontColor = colors[ci][0];
128
+ this.backColor = colors[ci][1];
129
+ for (var i = 0; i < 4; i++) {
130
+ var dx = Math.cos(this.angle + DEG_TO_RAD * (i * 90 + 45));
131
+ var dy = Math.sin(this.angle + DEG_TO_RAD * (i * 90 + 45));
132
+ this.corners[i] = new Vector2(dx, dy);
133
+ }
134
+ this.Update = function(_dt) {
135
+ this.time += _dt;
136
+ this.rotation += this.rotationSpeed * _dt;
137
+ this.cosA = Math.cos(DEG_TO_RAD * this.rotation);
138
+ this.pos.x += Math.cos(this.time * this.oscillationSpeed) * this.xSpeed * _dt
139
+ this.pos.y += this.ySpeed * _dt;
140
+ if (this.pos.y > ConfettiPaper.bounds.y) {
141
+ this.pos.x = Math.random() * ConfettiPaper.bounds.x;
142
+ this.pos.y = 0;
143
+ }
144
+ }
145
+ this.Draw = function(_g) {
146
+ if (this.cosA > 0) {
147
+ _g.fillStyle = this.frontColor;
148
+ } else {
149
+ _g.fillStyle = this.backColor;
150
+ }
151
+ _g.beginPath();
152
+ _g.moveTo(this.pos.x + this.corners[0].x * this.size, this.pos.y + this.corners[0].y * this.size * this.cosA);
153
+ for (var i = 1; i < 4; i++) {
154
+ _g.lineTo(this.pos.x + this.corners[i].x * this.size, this.pos.y + this.corners[i].y * this.size * this.cosA);
155
+ }
156
+ _g.closePath();
157
+ _g.fill();
158
+ }
159
+ }
160
+ ConfettiPaper.bounds = new Vector2(0, 0);
161
+
162
+ function ConfettiRibbon(_x, _y, _count, _dist, _thickness, _angle, _mass, _drag) {
163
+ this.particleDist = _dist;
164
+ this.particleCount = _count;
165
+ this.particleMass = _mass;
166
+ this.particleDrag = _drag;
167
+ this.particles = new Array();
168
+ var ci = Math.round(Math.random() * (colors.length - 1));
169
+ this.frontColor = colors[ci][0];
170
+ this.backColor = colors[ci][1];
171
+ this.xOff = Math.cos(DEG_TO_RAD * _angle) * _thickness;
172
+ this.yOff = Math.sin(DEG_TO_RAD * _angle) * _thickness;
173
+ this.position = new Vector2(_x, _y);
174
+ this.prevPosition = new Vector2(_x, _y);
175
+ this.velocityInherit = Math.random() * 2 + 4;
176
+ this.time = Math.random() * 100;
177
+ this.oscillationSpeed = Math.random() * 2 + 2;
178
+ this.oscillationDistance = Math.random() * 40 + 40;
179
+ this.ySpeed = Math.random() * 40 + 80;
180
+ for (var i = 0; i < this.particleCount; i++) {
181
+ this.particles[i] = new EulerMass(_x, _y - i * this.particleDist, this.particleMass, this.particleDrag);
182
+ }
183
+ this.Update = function(_dt) {
184
+ var i = 0;
185
+ this.time += _dt * this.oscillationSpeed;
186
+ this.position.y += this.ySpeed * _dt;
187
+ this.position.x += Math.cos(this.time) * this.oscillationDistance * _dt;
188
+ this.particles[0].position = this.position;
189
+ var dX = this.prevPosition.x - this.position.x;
190
+ var dY = this.prevPosition.y - this.position.y;
191
+ var delta = Math.sqrt(dX * dX + dY * dY);
192
+ this.prevPosition = new Vector2(this.position.x, this.position.y);
193
+ for (i = 1; i < this.particleCount; i++) {
194
+ var dirP = Vector2.Sub(this.particles[i - 1].position, this.particles[i].position);
195
+ dirP.Normalize();
196
+ dirP.Mul((delta / _dt) * this.velocityInherit);
197
+ this.particles[i].AddForce(dirP);
198
+ }
199
+ for (i = 1; i < this.particleCount; i++) {
200
+ this.particles[i].Integrate(_dt);
201
+ }
202
+ for (i = 1; i < this.particleCount; i++) {
203
+ var rp2 = new Vector2(this.particles[i].position.x, this.particles[i].position.y);
204
+ rp2.Sub(this.particles[i - 1].position);
205
+ rp2.Normalize();
206
+ rp2.Mul(this.particleDist);
207
+ rp2.Add(this.particles[i - 1].position);
208
+ this.particles[i].position = rp2;
209
+ }
210
+ if (this.position.y > ConfettiRibbon.bounds.y + this.particleDist * this.particleCount) {
211
+ this.Reset();
212
+ }
213
+ }
214
+ this.Reset = function() {
215
+ this.position.y = -Math.random() * ConfettiRibbon.bounds.y;
216
+ this.position.x = Math.random() * ConfettiRibbon.bounds.x;
217
+ this.prevPosition = new Vector2(this.position.x, this.position.y);
218
+ this.velocityInherit = Math.random() * 2 + 4;
219
+ this.time = Math.random() * 100;
220
+ this.oscillationSpeed = Math.random() * 2.0 + 1.5;
221
+ this.oscillationDistance = Math.random() * 40 + 40;
222
+ this.ySpeed = Math.random() * 40 + 80;
223
+ var ci = Math.round(Math.random() * (colors.length - 1));
224
+ this.frontColor = colors[ci][0];
225
+ this.backColor = colors[ci][1];
226
+ this.particles = new Array();
227
+ for (var i = 0; i < this.particleCount; i++) {
228
+ this.particles[i] = new EulerMass(this.position.x, this.position.y - i * this.particleDist, this.particleMass, this.particleDrag);
229
+ }
230
+ }
231
+ this.Draw = function(_g) {
232
+ for (var i = 0; i < this.particleCount - 1; i++) {
233
+ var p0 = new Vector2(this.particles[i].position.x + this.xOff, this.particles[i].position.y + this.yOff);
234
+ var p1 = new Vector2(this.particles[i + 1].position.x + this.xOff, this.particles[i + 1].position.y + this.yOff);
235
+ if (this.Side(this.particles[i].position.x, this.particles[i].position.y, this.particles[i + 1].position.x, this.particles[i + 1].position.y, p1.x, p1.y) < 0) {
236
+ _g.fillStyle = this.frontColor;
237
+ _g.strokeStyle = this.frontColor;
238
+ } else {
239
+ _g.fillStyle = this.backColor;
240
+ _g.strokeStyle = this.backColor;
241
+ }
242
+ if (i == 0) {
243
+ _g.beginPath();
244
+ _g.moveTo(this.particles[i].position.x, this.particles[i].position.y);
245
+ _g.lineTo(this.particles[i + 1].position.x, this.particles[i + 1].position.y);
246
+ _g.lineTo((this.particles[i + 1].position.x + p1.x) * 0.5, (this.particles[i + 1].position.y + p1.y) * 0.5);
247
+ _g.closePath();
248
+ _g.stroke();
249
+ _g.fill();
250
+ _g.beginPath();
251
+ _g.moveTo(p1.x, p1.y);
252
+ _g.lineTo(p0.x, p0.y);
253
+ _g.lineTo((this.particles[i + 1].position.x + p1.x) * 0.5, (this.particles[i + 1].position.y + p1.y) * 0.5);
254
+ _g.closePath();
255
+ _g.stroke();
256
+ _g.fill();
257
+ } else if (i == this.particleCount - 2) {
258
+ _g.beginPath();
259
+ _g.moveTo(this.particles[i].position.x, this.particles[i].position.y);
260
+ _g.lineTo(this.particles[i + 1].position.x, this.particles[i + 1].position.y);
261
+ _g.lineTo((this.particles[i].position.x + p0.x) * 0.5, (this.particles[i].position.y + p0.y) * 0.5);
262
+ _g.closePath();
263
+ _g.stroke();
264
+ _g.fill();
265
+ _g.beginPath();
266
+ _g.moveTo(p1.x, p1.y);
267
+ _g.lineTo(p0.x, p0.y);
268
+ _g.lineTo((this.particles[i].position.x + p0.x) * 0.5, (this.particles[i].position.y + p0.y) * 0.5);
269
+ _g.closePath();
270
+ _g.stroke();
271
+ _g.fill();
272
+ } else {
273
+ _g.beginPath();
274
+ _g.moveTo(this.particles[i].position.x, this.particles[i].position.y);
275
+ _g.lineTo(this.particles[i + 1].position.x, this.particles[i + 1].position.y);
276
+ _g.lineTo(p1.x, p1.y);
277
+ _g.lineTo(p0.x, p0.y);
278
+ _g.closePath();
279
+ _g.stroke();
280
+ _g.fill();
281
+ }
282
+ }
283
+ }
284
+ this.Side = function(x1, y1, x2, y2, x3, y3) {
285
+ return ((x1 - x2) * (y3 - y2) - (y1 - y2) * (x3 - x2));
286
+ }
287
+ }
288
+ ConfettiRibbon.bounds = new Vector2(0, 0);
289
+ confetti = {};
290
+ confetti.Context = function(parent) {
291
+ var i = 0;
292
+ var canvasParent = document.getElementById(parent);
293
+ var canvas = document.createElement('canvas');
294
+ canvas.width = canvasParent.offsetWidth;
295
+ canvas.height = canvasParent.offsetHeight;
296
+ canvasParent.appendChild(canvas);
297
+ var context = canvas.getContext('2d');
298
+ var interval = null;
299
+ var confettiRibbonCount = 7;
300
+ var rpCount = 30;
301
+ var rpDist = 8.0;
302
+ var rpThick = 8.0;
303
+ var confettiRibbons = new Array();
304
+ ConfettiRibbon.bounds = new Vector2(canvas.width, canvas.height);
305
+ for (i = 0; i < confettiRibbonCount; i++) {
306
+ confettiRibbons[i] = new ConfettiRibbon(Math.random() * canvas.width, -Math.random() * canvas.height * 2, rpCount, rpDist, rpThick, 45, 1, 0.05);
307
+ }
308
+ var confettiPaperCount = 25;
309
+ var confettiPapers = new Array();
310
+ ConfettiPaper.bounds = new Vector2(canvas.width, canvas.height);
311
+ for (i = 0; i < confettiPaperCount; i++) {
312
+ confettiPapers[i] = new ConfettiPaper(Math.random() * canvas.width, Math.random() * canvas.height);
313
+ }
314
+ this.resize = function() {
315
+ canvas.width = canvasParent.offsetWidth;
316
+ canvas.height = canvasParent.offsetHeight;
317
+ ConfettiPaper.bounds = new Vector2(canvas.width, canvas.height);
318
+ ConfettiRibbon.bounds = new Vector2(canvas.width, canvas.height);
319
+ }
320
+ this.start = function() {
321
+ this.stop()
322
+ var context = this
323
+ this.interval = setInterval(function() {
324
+ confetti.update();
325
+ }, 1000.0 / frameRate)
326
+ }
327
+ this.stop = function() {
328
+ clearInterval(this.interval);
329
+ }
330
+ this.update = function() {
331
+ var i = 0;
332
+ context.clearRect(0, 0, canvas.width, canvas.height);
333
+ for (i = 0; i < confettiPaperCount; i++) {
334
+ confettiPapers[i].Update(dt);
335
+ confettiPapers[i].Draw(context);
336
+ }
337
+ for (i = 0; i < confettiRibbonCount; i++) {
338
+ confettiRibbons[i].Update(dt);
339
+ confettiRibbons[i].Draw(context);
340
+ }
341
+ }
342
+ }
343
+ var confetti = new confetti.Context('confetti');
344
+ confetti.start();
345
+ $(window).resize(function() {
346
+ confetti.resize();
347
+ });
348
  });
assets/js/order-script.js CHANGED
@@ -1,42 +1,42 @@
1
- jQuery(document).ready(function($) {
2
- $("#doaction, #doaction2").click(function (event) {
3
- var actionselected = $(this).attr("id").substr(2);
4
- var action = $('select[name="' + actionselected + '"]').val();
5
- if ( $.inArray(action, wpo_wcpdf_ajax.bulk_actions) !== -1 ) {
6
- event.preventDefault();
7
- var template = action;
8
- var checked = [];
9
- $('tbody th.check-column input[type="checkbox"]:checked').each(
10
- function() {
11
- checked.push($(this).val());
12
- }
13
- );
14
-
15
- if (!checked.length) {
16
- alert('You have to select order(s) first!');
17
- return;
18
- }
19
-
20
- var order_ids=checked.join('x');
21
-
22
- if (wpo_wcpdf_ajax.ajaxurl.indexOf("?") != -1) {
23
- url = wpo_wcpdf_ajax.ajaxurl+'&action=generate_wpo_wcpdf&document_type='+template+'&order_ids='+order_ids+'&_wpnonce='+wpo_wcpdf_ajax.nonce;
24
- } else {
25
- url = wpo_wcpdf_ajax.ajaxurl+'?action=generate_wpo_wcpdf&document_type='+template+'&order_ids='+order_ids+'&_wpnonce='+wpo_wcpdf_ajax.nonce;
26
- }
27
-
28
- window.open(url,'_blank');
29
- }
30
- });
31
-
32
- $('#wpo_wcpdf-data-input-box').insertAfter('#woocommerce-order-data');
33
-
34
- // enable invoice number edit if user initiated
35
- $( ".wpo-wcpdf-set-date-number, .wpo-wcpdf-edit-date-number" ).click(function() {
36
- $form = $(this).closest('.wcpdf-data-fields');
37
- $form.find(".read-only").hide();
38
- $form.find(".editable").show();
39
- $form.find(':input').prop('disabled', false);
40
- });
41
- });
42
-
1
+ jQuery(document).ready(function($) {
2
+ $("#doaction, #doaction2").click(function (event) {
3
+ var actionselected = $(this).attr("id").substr(2);
4
+ var action = $('select[name="' + actionselected + '"]').val();
5
+ if ( $.inArray(action, wpo_wcpdf_ajax.bulk_actions) !== -1 ) {
6
+ event.preventDefault();
7
+ var template = action;
8
+ var checked = [];
9
+ $('tbody th.check-column input[type="checkbox"]:checked').each(
10
+ function() {
11
+ checked.push($(this).val());
12
+ }
13
+ );
14
+
15
+ if (!checked.length) {
16
+ alert('You have to select order(s) first!');
17
+ return;
18
+ }
19
+
20
+ var order_ids=checked.join('x');
21
+
22
+ if (wpo_wcpdf_ajax.ajaxurl.indexOf("?") != -1) {
23
+ url = wpo_wcpdf_ajax.ajaxurl+'&action=generate_wpo_wcpdf&document_type='+template+'&order_ids='+order_ids+'&_wpnonce='+wpo_wcpdf_ajax.nonce;
24
+ } else {
25
+ url = wpo_wcpdf_ajax.ajaxurl+'?action=generate_wpo_wcpdf&document_type='+template+'&order_ids='+order_ids+'&_wpnonce='+wpo_wcpdf_ajax.nonce;
26
+ }
27
+
28
+ window.open(url,'_blank');
29
+ }
30
+ });
31
+
32
+ $('#wpo_wcpdf-data-input-box').insertAfter('#woocommerce-order-data');
33
+
34
+ // enable invoice number edit if user initiated
35
+ $( ".wpo-wcpdf-set-date-number, .wpo-wcpdf-edit-date-number" ).click(function() {
36
+ $form = $(this).closest('.wcpdf-data-fields');
37
+ $form.find(".read-only").hide();
38
+ $form.find(".editable").show();
39
+ $form.find(':input').prop('disabled', false);
40
+ });
41
+ });
42
+
assets/js/setup-wizard.js CHANGED
@@ -1,20 +1,20 @@
1
- jQuery( function( $ ) {
2
-
3
- $( '.tab' ).click(function() {
4
- $( this ).closest('.extra-field').find('.tab').removeClass( 'active' );
5
- $( this ).addClass( 'active' );
6
- var $language = $( this ).attr('id');
7
- console.log($language);
8
- $( this ).siblings('.extra-field-input').hide();
9
- $('.' + $language ).show();
10
- });
11
-
12
- // Show Preview of logo
13
- $('#file-upload').change( function(event) {
14
- if ( event.target.files[0] ) {
15
- var tmppath = URL.createObjectURL(event.target.files[0]);
16
- $( '#logo-preview' ).find( "img" ).attr( 'src',tmppath );
17
- }
18
- });
19
-
20
- });
1
+ jQuery( function( $ ) {
2
+
3
+ $( '.tab' ).click(function() {
4
+ $( this ).closest('.extra-field').find('.tab').removeClass( 'active' );
5
+ $( this ).addClass( 'active' );
6
+ var $language = $( this ).attr('id');
7
+ console.log($language);
8
+ $( this ).siblings('.extra-field-input').hide();
9
+ $('.' + $language ).show();
10
+ });
11
+
12
+ // Show Preview of logo
13
+ $('#file-upload').change( function(event) {
14
+ if ( event.target.files[0] ) {
15
+ var tmppath = URL.createObjectURL(event.target.files[0]);
16
+ $( '#logo-preview' ).find( "img" ).attr( 'src',tmppath );
17
+ }
18
+ });
19
+
20
+ });
composer.json CHANGED
@@ -1,6 +1,6 @@
1
- {
2
- "require": {
3
- "php": ">=5.3.0",
4
- "dompdf/dompdf": "*"
5
- }
6
  }
1
+ {
2
+ "require": {
3
+ "php": ">=5.3.0",
4
+ "dompdf/dompdf": "*"
5
+ }
6
  }
composer.lock CHANGED
@@ -1,203 +1,203 @@
1
- {
2
- "_readme": [
3
- "This file locks the dependencies of your project to a known state",
4
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
- "This file is @generated automatically"
6
- ],
7
- "content-hash": "7e8e737c6fffa1c43aefc1018cab381f",
8
- "packages": [
9
- {
10
- "name": "dompdf/dompdf",
11
- "version": "v0.8.2",
12
- "source": {
13
- "type": "git",
14
- "url": "https://github.com/dompdf/dompdf.git",
15
- "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6"
16
- },
17
- "dist": {
18
- "type": "zip",
19
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5113accd9ae5d466077cce5208dcf3fb871bf8f6",
20
- "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6",
21
- "shasum": ""
22
- },
23
- "require": {
24
- "ext-dom": "*",
25
- "ext-gd": "*",
26
- "ext-mbstring": "*",
27
- "phenx/php-font-lib": "0.5.*",
28
- "phenx/php-svg-lib": "0.3.*",
29
- "php": ">=5.4.0"
30
- },
31
- "require-dev": {
32
- "phpunit/phpunit": "4.8.*",
33
- "squizlabs/php_codesniffer": "2.*"
34
- },
35
- "type": "library",
36
- "extra": {
37
- "branch-alias": {
38
- "dev-develop": "0.7-dev"
39
- }
40
- },
41
- "autoload": {
42
- "psr-4": {
43
- "Dompdf\\": "src/"
44
- },
45
- "classmap": [
46
- "lib/"
47
- ]
48
- },
49
- "notification-url": "https://packagist.org/downloads/",
50
- "license": [
51
- "LGPL-2.1"
52
- ],
53
- "authors": [
54
- {
55
- "name": "Fabien Ménager",
56
- "email": "fabien.menager@gmail.com"
57
- },
58
- {
59
- "name": "Brian Sweeney",
60
- "email": "eclecticgeek@gmail.com"
61
- },
62
- {
63
- "name": "Gabriel Bull",
64
- "email": "me@gabrielbull.com"
65
- }
66
- ],
67
- "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
68
- "homepage": "https://github.com/dompdf/dompdf",
69
- "time": "2017-11-26T14:49:08+00:00"
70
- },
71
- {
72
- "name": "phenx/php-font-lib",
73
- "version": "0.5.1",
74
- "source": {
75
- "type": "git",
76
- "url": "https://github.com/PhenX/php-font-lib.git",
77
- "reference": "760148820110a1ae0936e5cc35851e25a938bc97"
78
- },
79
- "dist": {
80
- "type": "zip",
81
- "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97",
82
- "reference": "760148820110a1ae0936e5cc35851e25a938bc97",
83
- "shasum": ""
84
- },
85
- "require-dev": {
86
- "phpunit/phpunit": "^4.8"
87
- },
88
- "type": "library",
89
- "autoload": {
90
- "psr-4": {
91
- "FontLib\\": "src/FontLib"
92
- }
93
- },
94
- "notification-url": "https://packagist.org/downloads/",
95
- "license": [
96
- "LGPL-3.0"
97
- ],
98
- "authors": [
99
- {
100
- "name": "Fabien Ménager",
101
- "email": "fabien.menager@gmail.com"
102
- }
103
- ],
104
- "description": "A library to read, parse, export and make subsets of different types of font files.",
105
- "homepage": "https://github.com/PhenX/php-font-lib",
106
- "time": "2017-09-13T16:14:37+00:00"
107
- },
108
- {
109
- "name": "phenx/php-svg-lib",
110
- "version": "v0.3",
111
- "source": {
112
- "type": "git",
113
- "url": "https://github.com/PhenX/php-svg-lib.git",
114
- "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa"
115
- },
116
- "dist": {
117
- "type": "zip",
118
- "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
119
- "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
120
- "shasum": ""
121
- },
122
- "require": {
123
- "sabberworm/php-css-parser": "8.1.*"
124
- },
125
- "require-dev": {
126
- "phpunit/phpunit": "~5.0"
127
- },
128
- "type": "library",
129
- "autoload": {
130
- "psr-0": {
131
- "Svg\\": "src/"
132
- }
133
- },
134
- "notification-url": "https://packagist.org/downloads/",
135
- "license": [
136
- "LGPL-3.0"
137
- ],
138
- "authors": [
139
- {
140
- "name": "Fabien Ménager",
141
- "email": "fabien.menager@gmail.com"
142
- }
143
- ],
144
- "description": "A library to read, parse and export to PDF SVG files.",
145
- "homepage": "https://github.com/PhenX/php-svg-lib",
146
- "time": "2017-05-24T10:07:27+00:00"
147
- },
148
- {
149
- "name": "sabberworm/php-css-parser",
150
- "version": "8.1.0",
151
- "source": {
152
- "type": "git",
153
- "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
154
- "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef"
155
- },
156
- "dist": {
157
- "type": "zip",
158
- "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/850cbbcbe7fbb155387a151ea562897a67e242ef",
159
- "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef",
160
- "shasum": ""
161
- },
162
- "require": {
163
- "php": ">=5.3.2"
164
- },
165
- "require-dev": {
166
- "phpunit/phpunit": "*"
167
- },
168
- "type": "library",
169
- "autoload": {
170
- "psr-0": {
171
- "Sabberworm\\CSS": "lib/"
172
- }
173
- },
174
- "notification-url": "https://packagist.org/downloads/",
175
- "license": [
176
- "MIT"
177
- ],
178
- "authors": [
179
- {
180
- "name": "Raphael Schweikert"
181
- }
182
- ],
183
- "description": "Parser for CSS Files written in PHP",
184
- "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
185
- "keywords": [
186
- "css",
187
- "parser",
188
- "stylesheet"
189
- ],
190
- "time": "2016-07-19T19:14:21+00:00"
191
- }
192
- ],
193
- "packages-dev": [],
194
- "aliases": [],
195
- "minimum-stability": "stable",
196
- "stability-flags": [],
197
- "prefer-stable": false,
198
- "prefer-lowest": false,
199
- "platform": {
200
- "php": ">=5.3.0"
201
- },
202
- "platform-dev": []
203
- }
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "7e8e737c6fffa1c43aefc1018cab381f",
8
+ "packages": [
9
+ {
10
+ "name": "dompdf/dompdf",
11
+ "version": "v0.8.2",
12
+ "source": {
13
+ "type": "git",
14
+ "url": "https://github.com/dompdf/dompdf.git",
15
+ "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6"
16
+ },
17
+ "dist": {
18
+ "type": "zip",
19
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5113accd9ae5d466077cce5208dcf3fb871bf8f6",
20
+ "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6",
21
+ "shasum": ""
22
+ },
23
+ "require": {
24
+ "ext-dom": "*",
25
+ "ext-gd": "*",
26
+ "ext-mbstring": "*",
27
+ "phenx/php-font-lib": "0.5.*",
28
+ "phenx/php-svg-lib": "0.3.*",
29
+ "php": ">=5.4.0"
30
+ },
31
+ "require-dev": {
32
+ "phpunit/phpunit": "4.8.*",
33
+ "squizlabs/php_codesniffer": "2.*"
34
+ },
35
+ "type": "library",
36
+ "extra": {
37
+ "branch-alias": {
38
+ "dev-develop": "0.7-dev"
39
+ }
40
+ },
41
+ "autoload": {
42
+ "psr-4": {
43
+ "Dompdf\\": "src/"
44
+ },
45
+ "classmap": [
46
+ "lib/"
47
+ ]
48
+ },
49
+ "notification-url": "https://packagist.org/downloads/",
50
+ "license": [
51
+ "LGPL-2.1"
52
+ ],
53
+ "authors": [
54
+ {
55
+ "name": "Fabien Ménager",
56
+ "email": "fabien.menager@gmail.com"
57
+ },
58
+ {
59
+ "name": "Brian Sweeney",
60
+ "email": "eclecticgeek@gmail.com"
61
+ },
62
+ {
63
+ "name": "Gabriel Bull",
64
+ "email": "me@gabrielbull.com"
65
+ }
66
+ ],
67
+ "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
68
+ "homepage": "https://github.com/dompdf/dompdf",
69
+ "time": "2017-11-26T14:49:08+00:00"
70
+ },
71
+ {
72
+ "name": "phenx/php-font-lib",
73
+ "version": "0.5.1",
74
+ "source": {
75
+ "type": "git",
76
+ "url": "https://github.com/PhenX/php-font-lib.git",
77
+ "reference": "760148820110a1ae0936e5cc35851e25a938bc97"
78
+ },
79
+ "dist": {
80
+ "type": "zip",
81
+ "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97",
82
+ "reference": "760148820110a1ae0936e5cc35851e25a938bc97",
83
+ "shasum": ""
84
+ },
85
+ "require-dev": {
86
+ "phpunit/phpunit": "^4.8"
87
+ },
88
+ "type": "library",
89
+ "autoload": {
90
+ "psr-4": {
91
+ "FontLib\\": "src/FontLib"
92
+ }
93
+ },
94
+ "notification-url": "https://packagist.org/downloads/",
95
+ "license": [
96
+ "LGPL-3.0"
97
+ ],
98
+ "authors": [
99
+ {
100
+ "name": "Fabien Ménager",
101
+ "email": "fabien.menager@gmail.com"
102
+ }
103
+ ],
104
+ "description": "A library to read, parse, export and make subsets of different types of font files.",
105
+ "homepage": "https://github.com/PhenX/php-font-lib",
106
+ "time": "2017-09-13T16:14:37+00:00"
107
+ },
108
+ {
109
+ "name": "phenx/php-svg-lib",
110
+ "version": "v0.3",
111
+ "source": {
112
+ "type": "git",
113
+ "url": "https://github.com/PhenX/php-svg-lib.git",
114
+ "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa"
115
+ },
116
+ "dist": {
117
+ "type": "zip",
118
+ "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
119
+ "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
120
+ "shasum": ""
121
+ },
122
+ "require": {
123
+ "sabberworm/php-css-parser": "8.1.*"
124
+ },
125
+ "require-dev": {
126
+ "phpunit/phpunit": "~5.0"
127
+ },
128
+ "type": "library",
129
+ "autoload": {
130
+ "psr-0": {
131
+ "Svg\\": "src/"
132
+ }
133
+ },
134
+ "notification-url": "https://packagist.org/downloads/",
135
+ "license": [
136
+ "LGPL-3.0"
137
+ ],
138
+ "authors": [
139
+ {
140
+ "name": "Fabien Ménager",
141
+ "email": "fabien.menager@gmail.com"
142
+ }
143
+ ],
144
+ "description": "A library to read, parse and export to PDF SVG files.",
145
+ "homepage": "https://github.com/PhenX/php-svg-lib",
146
+ "time": "2017-05-24T10:07:27+00:00"
147
+ },
148
+ {
149
+ "name": "sabberworm/php-css-parser",
150
+ "version": "8.1.0",
151
+ "source": {
152
+ "type": "git",
153
+ "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
154
+ "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef"
155
+ },
156
+ "dist": {
157
+ "type": "zip",
158
+ "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/850cbbcbe7fbb155387a151ea562897a67e242ef",
159
+ "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef",
160
+ "shasum": ""
161
+ },
162
+ "require": {
163
+ "php": ">=5.3.2"
164
+ },
165
+ "require-dev": {
166
+ "phpunit/phpunit": "*"
167
+ },
168
+ "type": "library",
169
+ "autoload": {
170
+ "psr-0": {
171
+ "Sabberworm\\CSS": "lib/"
172
+ }
173
+ },
174
+ "notification-url": "https://packagist.org/downloads/",
175
+ "license": [
176
+ "MIT"
177
+ ],
178
+ "authors": [
179
+ {
180
+ "name": "Raphael Schweikert"
181
+ }
182
+ ],
183
+ "description": "Parser for CSS Files written in PHP",
184
+ "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
185
+ "keywords": [
186
+ "css",
187
+ "parser",
188
+ "stylesheet"
189
+ ],
190
+ "time": "2016-07-19T19:14:21+00:00"
191
+ }
192
+ ],
193
+ "packages-dev": [],
194
+ "aliases": [],
195
+ "minimum-stability": "stable",
196
+ "stability-flags": [],
197
+ "prefer-stable": false,
198
+ "prefer-lowest": false,
199
+ "platform": {
200
+ "php": ">=5.3.0"
201
+ },
202
+ "platform-dev": []
203
+ }
includes/class-wcpdf-admin.php CHANGED
@@ -1,489 +1,489 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Admin' ) ) :
13
-
14
- class Admin {
15
- function __construct() {
16
- add_action( 'woocommerce_admin_order_actions_end', array( $this, 'add_listing_actions' ) );
17
- add_filter( 'manage_edit-shop_order_columns', array( $this, 'add_invoice_number_column' ), 999 );
18
- add_action( 'manage_shop_order_posts_custom_column', array( $this, 'invoice_number_column_data' ), 2 );
19
- add_action( 'add_meta_boxes_shop_order', array( $this, 'add_meta_boxes' ) );
20
- add_action( 'admin_footer', array( $this, 'bulk_actions' ) );
21
- add_filter( 'woocommerce_shop_order_search_fields', array( $this, 'search_fields' ) );
22
-
23
- add_action( 'save_post', array( $this,'save_invoice_number_date' ) );
24
-
25
- // manually send emails
26
- // WooCommerce core processes order actions at priority 50
27
- add_action( 'woocommerce_process_shop_order_meta', array( $this, 'send_emails' ), 60, 2 );
28
-
29
- add_action( 'admin_notices', array( $this, 'review_plugin_notice' ) );
30
-
31
- add_action( 'init', array( $this, 'setup_wizard') );
32
- // add_action( 'wpo_wcpdf_after_pdf', array( $this,'update_pdf_counter' ), 10, 2 );
33
-
34
- add_filter( 'manage_edit-shop_order_sortable_columns', array( $this, 'invoice_number_column_sortable' ) );
35
- add_filter( 'pre_get_posts', array( $this, 'sort_by_invoice_number' ) );
36
- }
37
-
38
- // display review admin notice after 100 pdf downloads
39
- public function review_plugin_notice() {
40
- if ( $this->is_order_page() === false && !( isset( $_GET['page'] ) && $_GET['page'] == 'wpo_wcpdf_options_page' ) ) {
41
- return;
42
- }
43
-
44
- if ( get_option( 'wpo_wcpdf_review_notice_dismissed' ) !== false ) {
45
- return;
46
- } else {
47
- if ( isset( $_GET['wpo_wcpdf_dismis_review'] ) ) {
48
- update_option( 'wpo_wcpdf_review_notice_dismissed', true );
49
- return;
50
- }
51
-
52
- // keep track of how many days this notice is show so we can remove it after 7 days
53
- $notice_shown_on = get_option( 'wpo_wcpdf_review_notice_shown', array() );
54
- $today = date('Y-m-d');
55
- if ( !in_array($today, $notice_shown_on) ) {
56
- $notice_shown_on[] = $today;
57
- update_option( 'wpo_wcpdf_review_notice_shown', $notice_shown_on );
58
- }
59
- // count number of days review is shown, dismiss forever if shown more than 7
60
- if (count($notice_shown_on) > 7) {
61
- update_option( 'wpo_wcpdf_review_notice_dismissed', true );
62
- return;
63
- }
64
-
65
- // get invoice count to determine whether notice should be shown
66
- $invoice_count = $this->get_invoice_count();
67
- if ( $invoice_count > 100 ) {
68
- $rounded_count = (int) substr( (string) $invoice_count, 0, 1 ) * pow( 10, strlen( (string) $invoice_count ) - 1);
69
- ?>
70
- <div class="notice notice-info is-dismissible wpo-wcpdf-review-notice">
71
- <h3><?php printf( __( 'Wow, you have created more than %d invoices with our plugin!', 'woocommerce-pdf-invoices-packing-slips' ), $rounded_count ); ?></h3>
72
- <p><?php _e( 'It would mean a lot to us if you would quickly give our plugin a 5-star rating. Help us spread the word and boost our motivation!', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
73
- <ul>
74
- <li><a href="https://wordpress.org/support/plugin/woocommerce-pdf-invoices-packing-slips/reviews/?rate=5#new-post" class="button"><?php _e( 'Yes you deserve it!', 'woocommerce-pdf-invoices-packing-slips' ); ?></span></a></li>
75
- <li><a href="<?php echo esc_url( add_query_arg( 'wpo_wcpdf_dismis_review', true ) ); ?>" class="wpo-wcpdf-dismiss"><?php _e( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ); ?> / <?php _e( 'Already did!', 'woocommerce-pdf-invoices-packing-slips' ); ?></a></li>
76
- <li><a href="mailto:support@wpovernight.com?Subject=Here%20is%20how%20I%20think%20you%20can%20do%20better"><?php _e( 'Actually, I have a complaint...', 'woocommerce-pdf-invoices-packing-slips' ); ?></a></li>
77
- </ul>
78
- </div>
79
- <!-- Hide extensions ad if this is shown -->
80
- <style>.wcpdf-extensions-ad { display: none; }</style>
81
- <?php
82
- }
83
- }
84
- }
85
-
86
- public function setup_wizard() {
87
- // Setup/welcome
88
- if ( ! empty( $_GET['page'] ) ) {
89
- switch ( $_GET['page'] ) {
90
- case 'wpo-wcpdf-setup' :
91
- include_once( WPO_WCPDF()->plugin_path() . '/includes/class-wcpdf-setup-wizard.php' );
92
- break;
93
- }
94
- }
95
- }
96
-
97
- public function get_invoice_count() {
98
- global $wpdb;
99
- $invoice_count = $wpdb->get_var( $wpdb->prepare( "SELECT count(*) FROM {$wpdb->postmeta} WHERE meta_key = %s", '_wcpdf_invoice_number' ) );
100
- return (int) $invoice_count;
101
- }
102
-
103
- public function update_pdf_counter( $document_type, $document ) {
104
- if ( in_array( $document_type, array('invoice','packing-slip') ) ) {
105
- $pdf_count = (int) get_option( 'wpo_wcpdf_count_'.$document_type, 0 );
106
- update_option( 'wpo_wcpdf_count_'.$document_type, $pdf_count + 1 );
107
- }
108
- }
109
-
110
- /**
111
- * Add PDF actions to the orders listing
112
- */
113
- public function add_listing_actions( $order ) {
114
- // do not show buttons for trashed orders
115
- if ( $order->get_status() == 'trash' ) {
116
- return;
117
- }
118
-
119
- $listing_actions = array();
120
- $documents = WPO_WCPDF()->documents->get_documents();
121
- foreach ($documents as $document) {
122
- $listing_actions[$document->get_type()] = array(
123
- 'url' => wp_nonce_url( admin_url( "admin-ajax.php?action=generate_wpo_wcpdf&document_type={$document->get_type()}&order_ids=" . WCX_Order::get_id( $order ) ), 'generate_wpo_wcpdf' ),
124
- 'img' => !empty($document->icon) ? $document->icon : WPO_WCPDF()->plugin_url() . "/assets/images/generic_document.png",
125
- 'alt' => "PDF " . $document->get_title(),
126
- );
127
- }
128
-
129
- $listing_actions = apply_filters( 'wpo_wcpdf_listing_actions', $listing_actions, $order );
130
-
131
- foreach ($listing_actions as $action => $data) {
132
- ?>
133
- <a href="<?php echo $data['url']; ?>" class="button tips wpo_wcpdf <?php echo $action; ?>" target="_blank" alt="<?php echo $data['alt']; ?>" data-tip="<?php echo $data['alt']; ?>">
134
- <img src="<?php echo $data['img']; ?>" alt="<?php echo $data['alt']; ?>" width="16">
135
- </a>
136
- <?php
137
- }
138
- }
139
-
140
- /**
141
- * Create additional Shop Order column for Invoice Numbers
142
- * @param array $columns shop order columns
143
- */
144
- public function add_invoice_number_column( $columns ) {
145
- // get invoice settings
146
- $invoice = wcpdf_get_invoice( null );
147
- $invoice_settings = $invoice->get_settings();
148
- if ( !isset( $invoice_settings['invoice_number_column'] ) ) {
149
- return $columns;
150
- }
151
-
152
- // put the column after the Status column
153
- $new_columns = array_slice($columns, 0, 2, true) +
154
- array( 'pdf_invoice_number' => __( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ) ) +
155
- array_slice($columns, 2, count($columns) - 1, true) ;
156
- return $new_columns;
157
- }
158
-
159
- /**
160
- * Display Invoice Number in Shop Order column (if available)
161
- * @param string $column column slug
162
- */
163
- public function invoice_number_column_data( $column ) {
164
- global $post, $the_order;
165
-
166
- if ( $column == 'pdf_invoice_number' ) {
167
- if ( empty( $the_order ) || WCX_Order::get_id( $the_order ) != $post->ID ) {
168
- $order = WCX::get_order( $post->ID );
169
- if ( $invoice = wcpdf_get_invoice( $order ) ) {
170
- echo $invoice->get_number();
171
- }
172
- do_action( 'wcpdf_invoice_number_column_end', $order );
173
- } else {
174
- if ( $invoice = wcpdf_get_invoice( $the_order ) ) {
175
- echo $invoice->get_number();
176
- }
177
- do_action( 'wcpdf_invoice_number_column_end', $the_order );
178
- }
179
- }
180
- }
181
-
182
- /**
183
- * Add the meta box on the single order page
184
- */
185
- public function add_meta_boxes() {
186
- // resend order emails
187
- if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '3.2', '>=' ) ) {
188
- add_meta_box(
189
- 'wpo_wcpdf_send_emails',
190
- __( 'Send order email', 'woocommerce-pdf-invoices-packing-slips' ),
191
- array( $this, 'send_order_email_meta_box' ),
192
- 'shop_order',
193
- 'side',
194
- 'high'
195
- );
196
- }
197
-
198
- // create PDF buttons
199
- add_meta_box(
200
- 'wpo_wcpdf-box',
201
- __( 'Create PDF', 'woocommerce-pdf-invoices-packing-slips' ),
202
- array( $this, 'pdf_actions_meta_box' ),
203
- 'shop_order',
204
- 'side',
205
- 'default'
206
- );
207
-
208
- // Invoice number & date
209
- add_meta_box(
210
- 'wpo_wcpdf-data-input-box',
211
- __( 'PDF Invoice data', 'woocommerce-pdf-invoices-packing-slips' ),
212
- array( $this, 'data_input_box_content' ),
213
- 'shop_order',
214
- 'normal',
215
- 'default'
216
- );
217
- }
218
-
219
- /**
220
- * Resend order emails
221
- */
222
- public function send_order_email_meta_box( $post ) {
223
- ?>
224
- <ul class="wpo_wcpdf_send_emails submitbox">
225
- <li class="wide" id="actions">
226
- <select name="wpo_wcpdf_send_emails">
227
- <option value=""></option>
228
- <?php
229
- $mailer = WC()->mailer();
230
- $available_emails = apply_filters( 'woocommerce_resend_order_emails_available', array( 'new_order', 'cancelled_order', 'customer_processing_order', 'customer_completed_order', 'customer_invoice' ) );
231
- $mails = $mailer->get_emails();
232
- if ( ! empty( $mails ) && ! empty( $available_emails ) ) { ?>
233
- <?php
234
- foreach ( $mails as $mail ) {
235
- if ( in_array( $mail->id, $available_emails ) && 'no' !== $mail->enabled ) {
236
- echo '<option value="send_email_' . esc_attr( $mail->id ) . '">' . esc_html( $mail->title ) . '</option>';
237
- }
238
- } ?>
239
- <?php
240
- }
241
- ?>
242
- </select>
243
- <input type="submit" class="button save_order button-primary" name="save" value="<?php esc_attr_e( 'Save order & send email', 'woocommerce-pdf-invoices-packing-slips' ); ?>" />
244
- <?php
245
- $title = __( 'Send email', 'woocommerce-pdf-invoices-packing-slips' );
246
- $url = wp_nonce_url( add_query_arg('wpo_wcpdf_action','resend_email'), 'generate_wpo_wcpdf' );
247
- // printf('<a href="%s" class="button wpo_wcpdf_send_email"><span>%s</span></a>')
248
- ?>
249
- </li>
250
- </ul>
251
- <?php
252
- }
253
-
254
- /**
255
- * Create the meta box content on the single order page
256
- */
257
- public function pdf_actions_meta_box( $post ) {
258
- global $post_id;
259
-
260
- $meta_box_actions = array();
261
- $documents = WPO_WCPDF()->documents->get_documents();
262
- foreach ($documents as $document) {
263
- $meta_box_actions[$document->get_type()] = array(
264
- 'url' => wp_nonce_url( admin_url( "admin-ajax.php?action=generate_wpo_wcpdf&document_type={$document->get_type()}&order_ids=" . $post_id ), 'generate_wpo_wcpdf' ),
265
- 'alt' => esc_attr( "PDF " . $document->get_title() ),
266
- 'title' => "PDF " . $document->get_title(),
267
- );
268
- }
269
-
270
- $meta_box_actions = apply_filters( 'wpo_wcpdf_meta_box_actions', $meta_box_actions, $post_id );
271
-
272
- ?>
273
- <ul class="wpo_wcpdf-actions">
274
- <?php
275
- foreach ($meta_box_actions as $document_type => $data) {
276
- printf('<li><a href="%1$s" class="button" target="_blank" alt="%2$s">%3$s</a></li>', $data['url'], $data['alt'],$data['title']);
277
- }
278
- ?>
279
- </ul>
280
- <?php
281
- }
282
-
283
- /**
284
- * Add metabox for invoice number & date
285
- */
286
- public function data_input_box_content ( $post ) {
287
- $order = WCX::get_order( $post->ID );
288
-
289
- do_action( 'wpo_wcpdf_meta_box_start', $post->ID );
290
-
291
- if ( $invoice = wcpdf_get_invoice( $order ) ) {
292
- $invoice_number = $invoice->get_number();
293
- $invoice_date = $invoice->get_date();
294
- ?>
295
- <div class="wcpdf-data-fields">
296
- <h4><?php _e( 'Invoice', 'woocommerce-pdf-invoices-packing-slips' ) ?><?php if ($invoice->exists()) : ?><span id="" class="wpo-wcpdf-edit-date-number dashicons dashicons-edit"></span><?php endif; ?></h4>
297
-
298
- <!-- Read only -->
299
- <div class="read-only">
300
- <?php if ($invoice->exists()) : ?>
301
- <div class="invoice-number">
302
- <p class="form-field _wcpdf_invoice_number_field ">
303
- <p>
304
- <span><strong><?php _e( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ); ?>:</strong></span>
305
- <span><?php if (!empty($invoice_number)) echo $invoice_number->get_formatted(); ?></span>
306
- </p>
307
- </p>
308
- </div>
309
-
310
- <div class="invoice-date">
311
- <p class="form-field form-field-wide">
312
- <p>
313
- <span><strong><?php _e( 'Invoice Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></strong></span>
314
- <span><?php if (!empty($invoice_date)) echo $invoice_date->date_i18n( wc_date_format().' @ '.wc_time_format() ); ?></span>
315
- </p>
316
- </p>
317
- </div>
318
- <?php else : ?>
319
- <span class="wpo-wcpdf-set-date-number button"><?php _e( 'Set invoice number & date', 'woocommerce-pdf-invoices-packing-slips' ) ?></span>
320
- <?php endif; ?>
321
- </div>
322
-
323
- <!-- Editable -->
324
- <div class="editable">
325
- <p class="form-field _wcpdf_invoice_number_field ">
326
- <label for="_wcpdf_invoice_number"><?php _e( 'Invoice Number (unformatted!)', 'woocommerce-pdf-invoices-packing-slips' ); ?>:</label>
327
- <?php if ( $invoice->exists() && !empty($invoice_number) ) : ?>
328
- <input type="text" class="short" style="" name="_wcpdf_invoice_number" id="_wcpdf_invoice_number" value="<?php echo $invoice_number->get_plain(); ?>" disabled="disabled">
329
- <?php else : ?>
330
- <input type="text" class="short" style="" name="_wcpdf_invoice_number" id="_wcpdf_invoice_number" value="" disabled="disabled">
331
- <?php endif; ?>
332
- </p>
333
- <p class="form-field form-field-wide">
334
- <label for="wcpdf_invoice_date"><?php _e( 'Invoice Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></label>
335
- <?php if ( $invoice->exists() && !empty($invoice_date) ) : ?>
336
- <input type="text" class="date-picker-field" name="wcpdf_invoice_date" id="wcpdf_invoice_date" maxlength="10" value="<?php echo $invoice_date->date_i18n( 'Y-m-d' ); ?>" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" disabled="disabled"/>@<input type="number" class="hour" placeholder="<?php _e( 'h', 'woocommerce' ) ?>" name="wcpdf_invoice_date_hour" id="wcpdf_invoice_date_hour" min="0" max="23" size="2" value="<?php echo $invoice_date->date_i18n( 'H' ) ?>" pattern="([01]?[0-9]{1}|2[0-3]{1})" />:<input type="number" class="minute" placeholder="<?php _e( 'm', 'woocommerce' ) ?>" name="wcpdf_invoice_date_minute" id="wcpdf_invoice_date_minute" min="0" max="59" size="2" value="<?php echo $invoice_date->date_i18n( 'i' ); ?>" pattern="[0-5]{1}[0-9]{1}" />
337
- <?php else : ?>
338
- <input type="text" class="date-picker-field" name="wcpdf_invoice_date" id="wcpdf_invoice_date" maxlength="10" disabled="disabled" value="" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />@<input type="number" class="hour" disabled="disabled" placeholder="<?php _e( 'h', 'woocommerce' ) ?>" name="wcpdf_invoice_date_hour" id="wcpdf_invoice_date_hour" min="0" max="23" size="2" value="" pattern="([01]?[0-9]{1}|2[0-3]{1})" />:<input type="number" class="minute" placeholder="<?php _e( 'm', 'woocommerce' ) ?>" name="wcpdf_invoice_date_minute" id="wcpdf_invoice_date_minute" min="0" max="59" size="2" value="" pattern="[0-5]{1}[0-9]{1}" disabled="disabled" />
339
- <?php endif; ?>
340
- </p>
341
- </div>
342
- </div>
343
- <?php
344
- }
345
-
346
- do_action( 'wpo_wcpdf_meta_box_end', $post->ID );
347
- }
348
-
349
- /**
350
- * Add actions to menu
351
- */
352
- public function bulk_actions() {
353
- if ( $this->is_order_page() ) {
354
- $bulk_actions = array();
355
- $documents = WPO_WCPDF()->documents->get_documents();
356
- foreach ($documents as $document) {
357
- $bulk_actions[$document->get_type()] = "PDF " . $document->get_title();
358
- }
359
-
360
- $bulk_actions = apply_filters( 'wpo_wcpdf_bulk_actions', $bulk_actions );
361
-
362
- ?>
363
- <script type="text/javascript">
364
- jQuery(document).ready(function() {
365
- <?php foreach ($bulk_actions as $action => $title) { ?>
366
- jQuery('<option>').val('<?php echo $action; ?>').html('<?php echo esc_attr( $title ); ?>').appendTo("select[name='action'], select[name='action2']");
367
- <?php } ?>
368
- });
369
- </script>
370
- <?php
371
- }
372
- }
373
-
374
- /**
375
- * Save invoice number
376
- */
377
- public function save_invoice_number_date($post_id) {
378
- $post_type = get_post_type( $post_id );
379
- if( $post_type == 'shop_order' ) {
380
- // bail if this is not an actual 'Save order' action
381
- if (!isset($_POST['action']) || $_POST['action'] != 'editpost') {
382
- return;
383
- }
384
-
385
- $order = WCX::get_order( $post_id );
386
- if ( $invoice = wcpdf_get_invoice( $order ) ) {
387
- if ( isset( $_POST['wcpdf_invoice_date'] ) ) {
388
- $date = $_POST['wcpdf_invoice_date'];
389
- $hour = !empty( $_POST['wcpdf_invoice_date_hour'] ) ? $_POST['wcpdf_invoice_date_hour'] : '00';
390
- $minute = !empty( $_POST['wcpdf_invoice_date_minute'] ) ? $_POST['wcpdf_invoice_date_minute'] : '00';
391
- $invoice_date = "{$date} {$hour}:{$minute}:00";
392
- $invoice->set_date( $invoice_date );
393
- } elseif ( empty( $_POST['wcpdf_invoice_date'] ) && !empty( $_POST['_wcpdf_invoice_number'] ) ) {
394
- $invoice->set_date( current_time( 'timestamp', true ) );
395
- }
396
-
397
- if ( isset( $_POST['_wcpdf_invoice_number'] ) ) {
398
- $invoice->set_number( $_POST['_wcpdf_invoice_number'] );
399
- }
400
-
401
- $invoice->save();
402
- }
403
- }
404
- }
405
-
406
- /**
407
- * Send emails manually
408
- */
409
- public function send_emails( $post_id, $post ) {
410
- if ( ! empty( $_POST['wpo_wcpdf_send_emails'] ) ) {
411
- $order = wc_get_order( $post_id );
412
- $action = wc_clean( $_POST['wpo_wcpdf_send_emails'] );
413
- if ( strstr( $action, 'send_email_' ) ) {
414
- // Switch back to the site locale.
415
- wc_switch_to_site_locale();
416
- do_action( 'woocommerce_before_resend_order_emails', $order );
417
- // Ensure gateways are loaded in case they need to insert data into the emails.
418
- WC()->payment_gateways();
419
- WC()->shipping();
420
- // Load mailer.
421
- $mailer = WC()->mailer();
422
- $email_to_send = str_replace( 'send_email_', '', $action );
423
- $mails = $mailer->get_emails();
424
- if ( ! empty( $mails ) ) {
425
- foreach ( $mails as $mail ) {
426
- if ( $mail->id == $email_to_send ) {
427
- $mail->trigger( $order->get_id(), $order );
428
- /* translators: %s: email title */
429
- $order->add_order_note( sprintf( __( '%s email notification manually sent.', 'woocommerce-pdf-invoices-packing-slips' ), $mail->title ), false, true );
430
- }
431
- }
432
- }
433
- do_action( 'woocommerce_after_resend_order_email', $order, $email_to_send );
434
- // Restore user locale.
435
- wc_restore_locale();
436
- // Change the post saved message.
437
- add_filter( 'redirect_post_location', function( $location ) {
438
- // messages in includes/admin/class-wc-admin-post-types.php
439
- // 11 => 'Order updated and sent.'
440
- return add_query_arg( 'message', 11, $location );
441
- } );
442
- }
443
- }
444
- }
445
-
446
- /**
447
- * Add invoice number to order search scope
448
- */
449
- public function search_fields ( $custom_fields ) {
450
- $custom_fields[] = '_wcpdf_invoice_number';
451
- $custom_fields[] = '_wcpdf_formatted_invoice_number';
452
- return $custom_fields;
453
- }
454
-
455
- /**
456
- * Check if this is a shop_order page (edit or list)
457
- */
458
- public function is_order_page() {
459
- global $post_type;
460
- if( $post_type == 'shop_order' ) {
461
- return true;
462
- } else {
463
- return false;
464
- }
465
- }
466
-
467
- /**
468
- * Add invoice number to order search scope
469
- */
470
- public function invoice_number_column_sortable( $columns ) {
471
- $columns['pdf_invoice_number'] = 'pdf_invoice_number';
472
- return $columns;
473
- }
474
-
475
- public function sort_by_invoice_number( $query ) {
476
- if( ! is_admin() )
477
- return;
478
- $orderby = $query->get( 'orderby');
479
- if( 'pdf_invoice_number' == $orderby ) {
480
- $query->set('meta_key','_wcpdf_invoice_number');
481
- $query->set('orderby','meta_value_num');
482
- }
483
- }
484
-
485
- }
486
-
487
- endif; // class_exists
488
-
489
  return new Admin();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Admin' ) ) :
13
+
14
+ class Admin {
15
+ function __construct() {
16
+ add_action( 'woocommerce_admin_order_actions_end', array( $this, 'add_listing_actions' ) );
17
+ add_filter( 'manage_edit-shop_order_columns', array( $this, 'add_invoice_number_column' ), 999 );
18
+ add_action( 'manage_shop_order_posts_custom_column', array( $this, 'invoice_number_column_data' ), 2 );
19
+ add_action( 'add_meta_boxes_shop_order', array( $this, 'add_meta_boxes' ) );
20
+ add_action( 'admin_footer', array( $this, 'bulk_actions' ) );
21
+ add_filter( 'woocommerce_shop_order_search_fields', array( $this, 'search_fields' ) );
22
+
23
+ add_action( 'save_post', array( $this,'save_invoice_number_date' ) );
24
+
25
+ // manually send emails
26
+ // WooCommerce core processes order actions at priority 50
27
+ add_action( 'woocommerce_process_shop_order_meta', array( $this, 'send_emails' ), 60, 2 );
28
+
29
+ add_action( 'admin_notices', array( $this, 'review_plugin_notice' ) );
30
+
31
+ add_action( 'init', array( $this, 'setup_wizard') );
32
+ // add_action( 'wpo_wcpdf_after_pdf', array( $this,'update_pdf_counter' ), 10, 2 );
33
+
34
+ add_filter( 'manage_edit-shop_order_sortable_columns', array( $this, 'invoice_number_column_sortable' ) );
35
+ add_filter( 'pre_get_posts', array( $this, 'sort_by_invoice_number' ) );
36
+ }
37
+
38
+ // display review admin notice after 100 pdf downloads
39
+ public function review_plugin_notice() {
40
+ if ( $this->is_order_page() === false && !( isset( $_GET['page'] ) && $_GET['page'] == 'wpo_wcpdf_options_page' ) ) {
41
+ return;
42
+ }
43
+
44
+ if ( get_option( 'wpo_wcpdf_review_notice_dismissed' ) !== false ) {
45
+ return;
46
+ } else {
47
+ if ( isset( $_GET['wpo_wcpdf_dismis_review'] ) ) {
48
+ update_option( 'wpo_wcpdf_review_notice_dismissed', true );
49
+ return;
50
+ }
51
+
52
+ // keep track of how many days this notice is show so we can remove it after 7 days
53
+ $notice_shown_on = get_option( 'wpo_wcpdf_review_notice_shown', array() );
54
+ $today = date('Y-m-d');
55
+ if ( !in_array($today, $notice_shown_on) ) {
56
+ $notice_shown_on[] = $today;
57
+ update_option( 'wpo_wcpdf_review_notice_shown', $notice_shown_on );
58
+ }
59
+ // count number of days review is shown, dismiss forever if shown more than 7
60
+ if (count($notice_shown_on) > 7) {
61
+ update_option( 'wpo_wcpdf_review_notice_dismissed', true );
62
+ return;
63
+ }
64
+
65
+ // get invoice count to determine whether notice should be shown
66
+ $invoice_count = $this->get_invoice_count();
67
+ if ( $invoice_count > 100 ) {
68
+ $rounded_count = (int) substr( (string) $invoice_count, 0, 1 ) * pow( 10, strlen( (string) $invoice_count ) - 1);
69
+ ?>
70
+ <div class="notice notice-info is-dismissible wpo-wcpdf-review-notice">
71
+ <h3><?php printf( __( 'Wow, you have created more than %d invoices with our plugin!', 'woocommerce-pdf-invoices-packing-slips' ), $rounded_count ); ?></h3>
72
+ <p><?php _e( 'It would mean a lot to us if you would quickly give our plugin a 5-star rating. Help us spread the word and boost our motivation!', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
73
+ <ul>
74
+ <li><a href="https://wordpress.org/support/plugin/woocommerce-pdf-invoices-packing-slips/reviews/?rate=5#new-post" class="button"><?php _e( 'Yes you deserve it!', 'woocommerce-pdf-invoices-packing-slips' ); ?></span></a></li>
75
+ <li><a href="<?php echo esc_url( add_query_arg( 'wpo_wcpdf_dismis_review', true ) ); ?>" class="wpo-wcpdf-dismiss"><?php _e( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ); ?> / <?php _e( 'Already did!', 'woocommerce-pdf-invoices-packing-slips' ); ?></a></li>
76
+ <li><a href="mailto:support@wpovernight.com?Subject=Here%20is%20how%20I%20think%20you%20can%20do%20better"><?php _e( 'Actually, I have a complaint...', 'woocommerce-pdf-invoices-packing-slips' ); ?></a></li>
77
+ </ul>
78
+ </div>
79
+ <!-- Hide extensions ad if this is shown -->
80
+ <style>.wcpdf-extensions-ad { display: none; }</style>
81
+ <?php
82
+ }
83
+ }
84
+ }
85
+
86
+ public function setup_wizard() {
87
+ // Setup/welcome
88
+ if ( ! empty( $_GET['page'] ) ) {
89
+ switch ( $_GET['page'] ) {
90
+ case 'wpo-wcpdf-setup' :
91
+ include_once( WPO_WCPDF()->plugin_path() . '/includes/class-wcpdf-setup-wizard.php' );
92
+ break;
93
+ }
94
+ }
95
+ }
96
+
97
+ public function get_invoice_count() {
98
+ global $wpdb;
99
+ $invoice_count = $wpdb->get_var( $wpdb->prepare( "SELECT count(*) FROM {$wpdb->postmeta} WHERE meta_key = %s", '_wcpdf_invoice_number' ) );
100
+ return (int) $invoice_count;
101
+ }
102
+
103
+ public function update_pdf_counter( $document_type, $document ) {
104
+ if ( in_array( $document_type, array('invoice','packing-slip') ) ) {
105
+ $pdf_count = (int) get_option( 'wpo_wcpdf_count_'.$document_type, 0 );
106
+ update_option( 'wpo_wcpdf_count_'.$document_type, $pdf_count + 1 );
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Add PDF actions to the orders listing
112
+ */
113
+ public function add_listing_actions( $order ) {
114
+ // do not show buttons for trashed orders
115
+ if ( $order->get_status() == 'trash' ) {
116
+ return;
117
+ }
118
+
119
+ $listing_actions = array();
120
+ $documents = WPO_WCPDF()->documents->get_documents();
121
+ foreach ($documents as $document) {
122
+ $listing_actions[$document->get_type()] = array(
123
+ 'url' => wp_nonce_url( admin_url( "admin-ajax.php?action=generate_wpo_wcpdf&document_type={$document->get_type()}&order_ids=" . WCX_Order::get_id( $order ) ), 'generate_wpo_wcpdf' ),
124
+ 'img' => !empty($document->icon) ? $document->icon : WPO_WCPDF()->plugin_url() . "/assets/images/generic_document.png",
125
+ 'alt' => "PDF " . $document->get_title(),
126
+ );
127
+ }
128
+
129
+ $listing_actions = apply_filters( 'wpo_wcpdf_listing_actions', $listing_actions, $order );
130
+
131
+ foreach ($listing_actions as $action => $data) {
132
+ ?>
133
+ <a href="<?php echo $data['url']; ?>" class="button tips wpo_wcpdf <?php echo $action; ?>" target="_blank" alt="<?php echo $data['alt']; ?>" data-tip="<?php echo $data['alt']; ?>">
134
+ <img src="<?php echo $data['img']; ?>" alt="<?php echo $data['alt']; ?>" width="16">
135
+ </a>
136
+ <?php
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Create additional Shop Order column for Invoice Numbers
142
+ * @param array $columns shop order columns
143
+ */
144
+ public function add_invoice_number_column( $columns ) {
145
+ // get invoice settings
146
+ $invoice = wcpdf_get_invoice( null );
147
+ $invoice_settings = $invoice->get_settings();
148
+ if ( !isset( $invoice_settings['invoice_number_column'] ) ) {
149
+ return $columns;
150
+ }
151
+
152
+ // put the column after the Status column
153
+ $new_columns = array_slice($columns, 0, 2, true) +
154
+ array( 'pdf_invoice_number' => __( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ) ) +
155
+ array_slice($columns, 2, count($columns) - 1, true) ;
156
+ return $new_columns;
157
+ }
158
+
159
+ /**
160
+ * Display Invoice Number in Shop Order column (if available)
161
+ * @param string $column column slug
162
+ */
163
+ public function invoice_number_column_data( $column ) {
164
+ global $post, $the_order;
165
+
166
+ if ( $column == 'pdf_invoice_number' ) {
167
+ if ( empty( $the_order ) || WCX_Order::get_id( $the_order ) != $post->ID ) {
168
+ $order = WCX::get_order( $post->ID );
169
+ if ( $invoice = wcpdf_get_invoice( $order ) ) {
170
+ echo $invoice->get_number();
171
+ }
172
+ do_action( 'wcpdf_invoice_number_column_end', $order );
173
+ } else {
174
+ if ( $invoice = wcpdf_get_invoice( $the_order ) ) {
175
+ echo $invoice->get_number();
176
+ }
177
+ do_action( 'wcpdf_invoice_number_column_end', $the_order );
178
+ }
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Add the meta box on the single order page
184
+ */
185
+ public function add_meta_boxes() {
186
+ // resend order emails
187
+ if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '3.2', '>=' ) ) {
188
+ add_meta_box(
189
+ 'wpo_wcpdf_send_emails',
190
+ __( 'Send order email', 'woocommerce-pdf-invoices-packing-slips' ),
191
+ array( $this, 'send_order_email_meta_box' ),
192
+ 'shop_order',
193
+ 'side',
194
+ 'high'
195
+ );
196
+ }
197
+
198
+ // create PDF buttons
199
+ add_meta_box(
200
+ 'wpo_wcpdf-box',
201
+ __( 'Create PDF', 'woocommerce-pdf-invoices-packing-slips' ),
202
+ array( $this, 'pdf_actions_meta_box' ),
203
+ 'shop_order',
204
+ 'side',
205
+ 'default'
206
+ );
207
+
208
+ // Invoice number & date
209
+ add_meta_box(
210
+ 'wpo_wcpdf-data-input-box',
211
+ __( 'PDF Invoice data', 'woocommerce-pdf-invoices-packing-slips' ),
212
+ array( $this, 'data_input_box_content' ),
213
+ 'shop_order',
214
+ 'normal',
215
+ 'default'
216
+ );
217
+ }
218
+
219
+ /**
220
+ * Resend order emails
221
+ */
222
+ public function send_order_email_meta_box( $post ) {
223
+ ?>
224
+ <ul class="wpo_wcpdf_send_emails submitbox">
225
+ <li class="wide" id="actions">
226
+ <select name="wpo_wcpdf_send_emails">
227
+ <option value=""></option>
228
+ <?php
229
+ $mailer = WC()->mailer();
230
+ $available_emails = apply_filters( 'woocommerce_resend_order_emails_available', array( 'new_order', 'cancelled_order', 'customer_processing_order', 'customer_completed_order', 'customer_invoice' ) );
231
+ $mails = $mailer->get_emails();
232
+ if ( ! empty( $mails ) && ! empty( $available_emails ) ) { ?>
233
+ <?php
234
+ foreach ( $mails as $mail ) {
235
+ if ( in_array( $mail->id, $available_emails ) && 'no' !== $mail->enabled ) {
236
+ echo '<option value="send_email_' . esc_attr( $mail->id ) . '">' . esc_html( $mail->title ) . '</option>';
237
+ }
238
+ } ?>
239
+ <?php
240
+ }
241
+ ?>
242
+ </select>
243
+ <input type="submit" class="button save_order button-primary" name="save" value="<?php esc_attr_e( 'Save order & send email', 'woocommerce-pdf-invoices-packing-slips' ); ?>" />
244
+ <?php
245
+ $title = __( 'Send email', 'woocommerce-pdf-invoices-packing-slips' );
246
+ $url = wp_nonce_url( add_query_arg('wpo_wcpdf_action','resend_email'), 'generate_wpo_wcpdf' );
247
+ // printf('<a href="%s" class="button wpo_wcpdf_send_email"><span>%s</span></a>')
248
+ ?>
249
+ </li>
250
+ </ul>
251
+ <?php
252
+ }
253
+
254
+ /**
255
+ * Create the meta box content on the single order page
256
+ */
257
+ public function pdf_actions_meta_box( $post ) {
258
+ global $post_id;
259
+
260
+ $meta_box_actions = array();
261
+ $documents = WPO_WCPDF()->documents->get_documents();
262
+ foreach ($documents as $document) {
263
+ $meta_box_actions[$document->get_type()] = array(
264
+ 'url' => wp_nonce_url( admin_url( "admin-ajax.php?action=generate_wpo_wcpdf&document_type={$document->get_type()}&order_ids=" . $post_id ), 'generate_wpo_wcpdf' ),
265
+ 'alt' => esc_attr( "PDF " . $document->get_title() ),
266
+ 'title' => "PDF " . $document->get_title(),
267
+ );
268
+ }
269
+
270
+ $meta_box_actions = apply_filters( 'wpo_wcpdf_meta_box_actions', $meta_box_actions, $post_id );
271
+
272
+ ?>
273
+ <ul class="wpo_wcpdf-actions">
274
+ <?php
275
+ foreach ($meta_box_actions as $document_type => $data) {
276
+ printf('<li><a href="%1$s" class="button" target="_blank" alt="%2$s">%3$s</a></li>', $data['url'], $data['alt'],$data['title']);
277
+ }
278
+ ?>
279
+ </ul>
280
+ <?php
281
+ }
282
+
283
+ /**
284
+ * Add metabox for invoice number & date
285
+ */
286
+ public function data_input_box_content ( $post ) {
287
+ $order = WCX::get_order( $post->ID );
288
+
289
+ do_action( 'wpo_wcpdf_meta_box_start', $post->ID );
290
+
291
+ if ( $invoice = wcpdf_get_invoice( $order ) ) {
292
+ $invoice_number = $invoice->get_number();
293
+ $invoice_date = $invoice->get_date();
294
+ ?>
295
+ <div class="wcpdf-data-fields">
296
+ <h4><?php echo $invoice->get_title(); ?><?php if ($invoice->exists()) : ?><span id="" class="wpo-wcpdf-edit-date-number dashicons dashicons-edit"></span><?php endif; ?></h4>
297
+
298
+ <!-- Read only -->
299
+ <div class="read-only">
300
+ <?php if ($invoice->exists()) : ?>
301
+ <div class="invoice-number">
302
+ <p class="form-field _wcpdf_invoice_number_field ">
303
+ <p>
304
+ <span><strong><?php _e( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ); ?>:</strong></span>
305
+ <span><?php if (!empty($invoice_number)) echo $invoice_number->get_formatted(); ?></span>
306
+ </p>
307
+ </p>
308
+ </div>
309
+
310
+ <div class="invoice-date">
311
+ <p class="form-field form-field-wide">
312
+ <p>
313
+ <span><strong><?php _e( 'Invoice Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></strong></span>
314
+ <span><?php if (!empty($invoice_date)) echo $invoice_date->date_i18n( wc_date_format().' @ '.wc_time_format() ); ?></span>
315
+ </p>
316
+ </p>
317
+ </div>
318
+ <?php else : ?>
319
+ <span class="wpo-wcpdf-set-date-number button"><?php _e( 'Set invoice number & date', 'woocommerce-pdf-invoices-packing-slips' ) ?></span>
320
+ <?php endif; ?>
321
+ </div>
322
+
323
+ <!-- Editable -->
324
+ <div class="editable">
325
+ <p class="form-field _wcpdf_invoice_number_field ">
326
+ <label for="_wcpdf_invoice_number"><?php _e( 'Invoice Number (unformatted!)', 'woocommerce-pdf-invoices-packing-slips' ); ?>:</label>
327
+ <?php if ( $invoice->exists() && !empty($invoice_number) ) : ?>
328
+ <input type="text" class="short" style="" name="_wcpdf_invoice_number" id="_wcpdf_invoice_number" value="<?php echo $invoice_number->get_plain(); ?>" disabled="disabled">
329
+ <?php else : ?>
330
+ <input type="text" class="short" style="" name="_wcpdf_invoice_number" id="_wcpdf_invoice_number" value="" disabled="disabled">
331
+ <?php endif; ?>
332
+ </p>
333
+ <p class="form-field form-field-wide">
334
+ <label for="wcpdf_invoice_date"><?php _e( 'Invoice Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></label>
335
+ <?php if ( $invoice->exists() && !empty($invoice_date) ) : ?>
336
+ <input type="text" class="date-picker-field" name="wcpdf_invoice_date" id="wcpdf_invoice_date" maxlength="10" value="<?php echo $invoice_date->date_i18n( 'Y-m-d' ); ?>" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" disabled="disabled"/>@<input type="number" class="hour" placeholder="<?php _e( 'h', 'woocommerce' ) ?>" name="wcpdf_invoice_date_hour" id="wcpdf_invoice_date_hour" min="0" max="23" size="2" value="<?php echo $invoice_date->date_i18n( 'H' ) ?>" pattern="([01]?[0-9]{1}|2[0-3]{1})" />:<input type="number" class="minute" placeholder="<?php _e( 'm', 'woocommerce' ) ?>" name="wcpdf_invoice_date_minute" id="wcpdf_invoice_date_minute" min="0" max="59" size="2" value="<?php echo $invoice_date->date_i18n( 'i' ); ?>" pattern="[0-5]{1}[0-9]{1}" />
337
+ <?php else : ?>
338
+ <input type="text" class="date-picker-field" name="wcpdf_invoice_date" id="wcpdf_invoice_date" maxlength="10" disabled="disabled" value="" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />@<input type="number" class="hour" disabled="disabled" placeholder="<?php _e( 'h', 'woocommerce' ) ?>" name="wcpdf_invoice_date_hour" id="wcpdf_invoice_date_hour" min="0" max="23" size="2" value="" pattern="([01]?[0-9]{1}|2[0-3]{1})" />:<input type="number" class="minute" placeholder="<?php _e( 'm', 'woocommerce' ) ?>" name="wcpdf_invoice_date_minute" id="wcpdf_invoice_date_minute" min="0" max="59" size="2" value="" pattern="[0-5]{1}[0-9]{1}" disabled="disabled" />
339
+ <?php endif; ?>
340
+ </p>
341
+ </div>
342
+ </div>
343
+ <?php
344
+ }
345
+
346
+ do_action( 'wpo_wcpdf_meta_box_end', $post->ID );
347
+ }
348
+
349
+ /**
350
+ * Add actions to menu
351
+ */
352
+ public function bulk_actions() {
353
+ if ( $this->is_order_page() ) {
354
+ $bulk_actions = array();
355
+ $documents = WPO_WCPDF()->documents->get_documents();
356
+ foreach ($documents as $document) {
357
+ $bulk_actions[$document->get_type()] = "PDF " . $document->get_title();
358
+ }
359
+
360
+ $bulk_actions = apply_filters( 'wpo_wcpdf_bulk_actions', $bulk_actions );
361
+
362
+ ?>
363
+ <script type="text/javascript">
364
+ jQuery(document).ready(function() {
365
+ <?php foreach ($bulk_actions as $action => $title) { ?>
366
+ jQuery('<option>').val('<?php echo $action; ?>').html('<?php echo esc_attr( $title ); ?>').appendTo("select[name='action'], select[name='action2']");
367
+ <?php } ?>
368
+ });
369
+ </script>
370
+ <?php
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Save invoice number
376
+ */
377
+ public function save_invoice_number_date($post_id) {
378
+ $post_type = get_post_type( $post_id );
379
+ if( $post_type == 'shop_order' ) {
380
+ // bail if this is not an actual 'Save order' action
381
+ if (!isset($_POST['action']) || $_POST['action'] != 'editpost') {
382
+ return;
383
+ }
384
+
385
+ $order = WCX::get_order( $post_id );
386
+ if ( $invoice = wcpdf_get_invoice( $order ) ) {
387
+ if ( isset( $_POST['wcpdf_invoice_date'] ) ) {
388
+ $date = $_POST['wcpdf_invoice_date'];
389
+ $hour = !empty( $_POST['wcpdf_invoice_date_hour'] ) ? $_POST['wcpdf_invoice_date_hour'] : '00';
390
+ $minute = !empty( $_POST['wcpdf_invoice_date_minute'] ) ? $_POST['wcpdf_invoice_date_minute'] : '00';
391
+ $invoice_date = "{$date} {$hour}:{$minute}:00";
392
+ $invoice->set_date( $invoice_date );
393
+ } elseif ( empty( $_POST['wcpdf_invoice_date'] ) && !empty( $_POST['_wcpdf_invoice_number'] ) ) {
394
+ $invoice->set_date( current_time( 'timestamp', true ) );
395
+ }
396
+
397
+ if ( isset( $_POST['_wcpdf_invoice_number'] ) ) {
398
+ $invoice->set_number( $_POST['_wcpdf_invoice_number'] );
399
+ }
400
+
401
+ $invoice->save();
402
+ }
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Send emails manually
408
+ */
409
+ public function send_emails( $post_id, $post ) {
410
+ if ( ! empty( $_POST['wpo_wcpdf_send_emails'] ) ) {
411
+ $order = wc_get_order( $post_id );
412
+ $action = wc_clean( $_POST['wpo_wcpdf_send_emails'] );
413
+ if ( strstr( $action, 'send_email_' ) ) {
414
+ // Switch back to the site locale.
415
+ wc_switch_to_site_locale();
416
+ do_action( 'woocommerce_before_resend_order_emails', $order );
417
+ // Ensure gateways are loaded in case they need to insert data into the emails.
418
+ WC()->payment_gateways();
419
+ WC()->shipping();
420
+ // Load mailer.
421
+ $mailer = WC()->mailer();
422
+ $email_to_send = str_replace( 'send_email_', '', $action );
423
+ $mails = $mailer->get_emails();
424
+ if ( ! empty( $mails ) ) {
425
+ foreach ( $mails as $mail ) {
426
+ if ( $mail->id == $email_to_send ) {
427
+ $mail->trigger( $order->get_id(), $order );
428
+ /* translators: %s: email title */
429
+ $order->add_order_note( sprintf( __( '%s email notification manually sent.', 'woocommerce-pdf-invoices-packing-slips' ), $mail->title ), false, true );
430
+ }
431
+ }
432
+ }
433
+ do_action( 'woocommerce_after_resend_order_email', $order, $email_to_send );
434
+ // Restore user locale.
435
+ wc_restore_locale();
436
+ // Change the post saved message.
437
+ add_filter( 'redirect_post_location', function( $location ) {
438
+ // messages in includes/admin/class-wc-admin-post-types.php
439
+ // 11 => 'Order updated and sent.'
440
+ return add_query_arg( 'message', 11, $location );
441
+ } );
442
+ }
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Add invoice number to order search scope
448
+ */
449
+ public function search_fields ( $custom_fields ) {
450
+ $custom_fields[] = '_wcpdf_invoice_number';
451
+ $custom_fields[] = '_wcpdf_formatted_invoice_number';
452
+ return $custom_fields;
453
+ }
454
+
455
+ /**
456
+ * Check if this is a shop_order page (edit or list)
457
+ */
458
+ public function is_order_page() {
459
+ global $post_type;
460
+ if( $post_type == 'shop_order' ) {
461
+ return true;
462
+ } else {
463
+ return false;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Add invoice number to order search scope
469
+ */
470
+ public function invoice_number_column_sortable( $columns ) {
471
+ $columns['pdf_invoice_number'] = 'pdf_invoice_number';
472
+ return $columns;
473
+ }
474
+
475
+ public function sort_by_invoice_number( $query ) {
476
+ if( ! is_admin() )
477
+ return;
478
+ $orderby = $query->get( 'orderby');
479
+ if( 'pdf_invoice_number' == $orderby ) {
480
+ $query->set('meta_key','_wcpdf_invoice_number');
481
+ $query->set('orderby','meta_value_num');
482
+ }
483
+ }
484
+
485
+ }
486
+
487
+ endif; // class_exists
488
+
489
  return new Admin();
includes/class-wcpdf-frontend.php CHANGED
@@ -50,9 +50,15 @@ class Frontend {
50
 
51
  // Check if invoice has been created already or if status allows download (filter your own array of allowed statuses)
52
  if ( $invoice_allowed || in_array(WCX_Order::get_status( $order ), apply_filters( 'wpo_wcpdf_myaccount_allowed_order_statuses', array() ) ) ) {
 
 
 
 
 
 
53
  $actions['invoice'] = array(
54
  'url' => $pdf_url,
55
- 'name' => apply_filters( 'wpo_wcpdf_myaccount_button_text', __( 'Download invoice (PDF)', 'woocommerce-pdf-invoices-packing-slips' ) )
56
  );
57
  }
58
  }
50
 
51
  // Check if invoice has been created already or if status allows download (filter your own array of allowed statuses)
52
  if ( $invoice_allowed || in_array(WCX_Order::get_status( $order ), apply_filters( 'wpo_wcpdf_myaccount_allowed_order_statuses', array() ) ) ) {
53
+ $document_title = $invoice->get_setting('title');
54
+ if ( !empty($document_title) && !empty(array_filter($document_title)) ) {
55
+ $button_text = sprintf ( __( 'Download %s (PDF)', 'woocommerce-pdf-invoices-packing-slips' ), $invoice->get_title() );
56
+ } else {
57
+ $button_text = __( 'Download invoice (PDF)', 'woocommerce-pdf-invoices-packing-slips' );
58
+ }
59
  $actions['invoice'] = array(
60
  'url' => $pdf_url,
61
+ 'name' => apply_filters( 'wpo_wcpdf_myaccount_button_text', $button_text, $invoice )
62
  );
63
  }
64
  }
includes/class-wcpdf-install.php CHANGED
@@ -1,359 +1,359 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Install' ) ) :
13
-
14
- class Install {
15
-
16
- function __construct() {
17
- // run lifecycle methods
18
- if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
19
- add_action( 'wp_loaded', array( $this, 'do_install' ) );
20
- }
21
- }
22
-
23
- /** Lifecycle methods *******************************************************
24
- * Because register_activation_hook only runs when the plugin is manually
25
- * activated by the user, we're checking the current version against the
26
- * version stored in the database
27
- ****************************************************************************/
28
-
29
- /**
30
- * Handles version checking
31
- */
32
- public function do_install() {
33
- // only install when woocommerce is active
34
- if ( !WPO_WCPDF()->is_woocommerce_activated() ) {
35
- return;
36
- }
37
-
38
- $version_setting = 'wpo_wcpdf_version';
39
- $installed_version = get_option( $version_setting );
40
-
41
- // installed version lower than plugin version?
42
- if ( version_compare( $installed_version, WPO_WCPDF_VERSION, '<' ) ) {
43
-
44
- if ( ! $installed_version ) {
45
- $this->install();
46
- } else {
47
- $this->upgrade( $installed_version );
48
- }
49
-
50
- // new version number
51
- update_option( $version_setting, WPO_WCPDF_VERSION );
52
- } elseif ( $installed_version && version_compare( $installed_version, WPO_WCPDF_VERSION, '>' ) ) {
53
- $this->downgrade( $installed_version );
54
- // downgrade version number
55
- update_option( $version_setting, WPO_WCPDF_VERSION );
56
- }
57
- }
58
-
59
-
60
- /**
61
- * Plugin install method. Perform any installation tasks here
62
- */
63
- protected function install() {
64
- // only install when php 5.3 or higher
65
- if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
66
- return;
67
- }
68
-
69
- // check if upgrading from versionless (1.4.14 and older)
70
- if ( get_option('wpo_wcpdf_general_settings') ) {
71
- $this->upgrade( 'versionless' );
72
- return;
73
- }
74
-
75
- // Create temp folders
76
- $tmp_base = WPO_WCPDF()->main->get_tmp_base();
77
-
78
- // check if tmp folder exists => if not, initialize
79
- if ( !@is_dir( $tmp_base ) ) {
80
- WPO_WCPDF()->main->init_tmp( $tmp_base );
81
- }
82
-
83
- // Unsupported currency symbols
84
- $unsupported_symbols = array (
85
- 'AED',
86
- 'AFN',
87
- 'BDT',
88
- 'BHD',
89
- 'BTC',
90
- 'CRC',
91
- 'DZD',
92
- 'GEL',
93
- 'GHS',
94
- 'ILS',
95
- 'INR',
96
- 'IQD',
97
- 'IRR',
98
- 'IRT',
99
- 'JOD',
100
- 'KHR',
101
- 'KPW',
102
- 'KRW',
103
- 'KWD',
104
- 'LAK',
105
- 'LBP',
106
- 'LKR',
107
- 'LYD',
108
- 'MAD',
109
- 'MNT',
110
- 'MUR',
111
- 'MVR',
112
- 'NPR',
113
- 'OMR',
114
- 'PHP',
115
- 'PKR',
116
- 'PYG',
117
- 'QAR',
118
- 'RUB',
119
- 'SAR',
120
- 'SCR',
121
- 'SDG',
122
- 'SYP',
123
- 'THB',
124
- 'TND',
125
- 'TRY',
126
- 'UAH',
127
- 'YER',
128
- );
129
-
130
- // set default settings
131
- $settings_defaults = array(
132
- 'wpo_wcpdf_settings_general' => array(
133
- 'download_display' => 'display',
134
- 'template_path' => WPO_WCPDF()->plugin_path() . '/templates/Simple',
135
- 'currency_font' => ( in_array( get_woocommerce_currency(), $unsupported_symbols ) ) ? 1 : '',
136
- 'paper_size' => 'a4',
137
- // 'header_logo' => '',
138
- // 'shop_name' => array(),
139
- // 'shop_address' => array(),
140
- // 'footer' => array(),
141
- // 'extra_1' => array(),
142
- // 'extra_2' => array(),
143
- // 'extra_3' => array(),
144
- ),
145
- 'wpo_wcpdf_documents_settings_invoice' => array(
146
- 'enabled' => 1,
147
- // 'attach_to_email_ids' => array(),
148
- // 'display_shipping_address' => '',
149
- // 'display_email' => '',
150
- // 'display_phone' => '',
151
- // 'display_date' => '',
152
- // 'display_number' => '',
153
- // 'number_format' => array(),
154
- // 'reset_number_yearly' => '',
155
- // 'my_account_buttons' => '',
156
- // 'invoice_number_column' => '',
157
- // 'disable_free' => '',
158
- ),
159
- 'wpo_wcpdf_documents_settings_packing-slip' => array(
160
- 'enabled' => 1,
161
- // 'display_billing_address' => '',
162
- // 'display_email' => '',
163
- // 'display_phone' => '',
164
- ),
165
- // 'wpo_wcpdf_settings_debug' => array(
166
- // 'legacy_mode' => '',
167
- // 'enable_debug' => '',
168
- // 'html_output' => '',
169
- // ),
170
- );
171
- foreach ($settings_defaults as $option => $defaults) {
172
- update_option( $option, $defaults );
173
- }
174
- }
175
-
176
- /**
177
- * Plugin upgrade method. Perform any required upgrades here
178
- *
179
- * @param string $installed_version the currently installed ('old') version
180
- */
181
- protected function upgrade( $installed_version ) {
182
- // only upgrade when php 5.3 or higher
183
- if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
184
- return;
185
- }
186
-
187
- // sync fonts on every upgrade!
188
- $tmp_base = WPO_WCPDF()->main->get_tmp_base();
189
-
190
- // check if tmp folder exists => if not, initialize
191
- if ( !@is_dir( $tmp_base ) ) {
192
- WPO_WCPDF()->main->init_tmp( $tmp_base );
193
- } else {
194
- $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
195
- // don't try merging fonts with local when updating pre 2.0
196
- $pre_2 = ( $installed_version == 'versionless' || version_compare( $installed_version, '2.0-dev', '<' ) );
197
- $merge_with_local = $pre_2 ? false : true;
198
- WPO_WCPDF()->main->copy_fonts( $font_path, $merge_with_local );
199
- }
200
-
201
- // 1.5.28 update: copy next invoice number to separate setting
202
- if ( $installed_version == 'versionless' || version_compare( $installed_version, '1.5.28', '<' ) ) {
203
- $template_settings = get_option( 'wpo_wcpdf_template_settings' );
204
- $next_invoice_number = isset($template_settings['next_invoice_number'])?$template_settings['next_invoice_number']:'';
205
- update_option( 'wpo_wcpdf_next_invoice_number', $next_invoice_number );
206
- }
207
-
208
- // 2.0-dev update: reorganize settings
209
- if ( $installed_version == 'versionless' || version_compare( $installed_version, '2.0-dev', '<' ) ) {
210
- $old_settings = array(
211
- 'wpo_wcpdf_general_settings' => get_option( 'wpo_wcpdf_general_settings' ),
212
- 'wpo_wcpdf_template_settings' => get_option( 'wpo_wcpdf_template_settings' ),
213
- 'wpo_wcpdf_debug_settings' => get_option( 'wpo_wcpdf_debug_settings' ),
214
- );
215
-
216
- // combine invoice number formatting in array
217
- $old_settings['wpo_wcpdf_template_settings']['invoice_number_formatting'] = array();
218
- $format_option_keys = array('padding','suffix','prefix');
219
- foreach ($format_option_keys as $format_option_key) {
220
- if (isset($old_settings['wpo_wcpdf_template_settings']["invoice_number_formatting_{$format_option_key}"])) {
221
- $old_settings['wpo_wcpdf_template_settings']['invoice_number_formatting'][$format_option_key] = $old_settings['wpo_wcpdf_template_settings']["invoice_number_formatting_{$format_option_key}"];
222
- }
223
- }
224
-
225
- // convert abbreviated email_ids
226
- if (isset($old_settings['wpo_wcpdf_general_settings']['email_pdf'])) {
227
- foreach ($old_settings['wpo_wcpdf_general_settings']['email_pdf'] as $email_id => $value) {
228
- if ($email_id == 'completed' || $email_id == 'processing') {
229
- $old_settings['wpo_wcpdf_general_settings']['email_pdf']["customer_{$email_id}_order"] = $value;
230
- unset($old_settings['wpo_wcpdf_general_settings']['email_pdf'][$email_id]);
231
- }
232
- }
233
- }
234
-
235
- // Migrate template path
236
- // forward slash for consistency/compatibility
237
- $template_path = str_replace('\\','/', $old_settings['wpo_wcpdf_template_settings']['template_path']);
238
- // strip abspath (forward slashed) if included
239
- $template_path = str_replace( str_replace('\\','/', ABSPATH), '', $template_path );
240
- // strip pdf subfolder from templates path
241
- $template_path = str_replace( '/templates/pdf/', '/templates/', $template_path );
242
- $old_settings['wpo_wcpdf_template_settings']['template_path'] = $template_path;
243
-
244
- // map new settings to old
245
- $settings_map = array(
246
- 'wpo_wcpdf_settings_general' => array(
247
- 'download_display' => array( 'wpo_wcpdf_general_settings' => 'download_display' ),
248
- 'template_path' => array( 'wpo_wcpdf_template_settings' => 'template_path' ),
249
- 'currency_font' => array( 'wpo_wcpdf_template_settings' => 'currency_font' ),
250
- 'paper_size' => array( 'wpo_wcpdf_template_settings' => 'paper_size' ),
251
- 'header_logo' => array( 'wpo_wcpdf_template_settings' => 'header_logo' ),
252
- 'shop_name' => array( 'wpo_wcpdf_template_settings' => 'shop_name' ),
253
- 'shop_address' => array( 'wpo_wcpdf_template_settings' => 'shop_address' ),
254
- 'footer' => array( 'wpo_wcpdf_template_settings' => 'footer' ),
255
- 'extra_1' => array( 'wpo_wcpdf_template_settings' => 'extra_1' ),
256
- 'extra_2' => array( 'wpo_wcpdf_template_settings' => 'extra_2' ),
257
- 'extra_3' => array( 'wpo_wcpdf_template_settings' => 'extra_3' ),
258
- ),
259
- 'wpo_wcpdf_documents_settings_invoice' => array(
260
- 'attach_to_email_ids' => array( 'wpo_wcpdf_general_settings' => 'email_pdf' ),
261
- 'display_shipping_address' => array( 'wpo_wcpdf_template_settings' => 'invoice_shipping_address' ),
262
- 'display_email' => array( 'wpo_wcpdf_template_settings' => 'invoice_email' ),
263
- 'display_phone' => array( 'wpo_wcpdf_template_settings' => 'invoice_phone' ),
264
- 'display_date' => array( 'wpo_wcpdf_template_settings' => 'display_date' ),
265
- 'display_number' => array( 'wpo_wcpdf_template_settings' => 'display_number' ),
266
- 'number_format' => array( 'wpo_wcpdf_template_settings' => 'invoice_number_formatting' ),
267
- 'reset_number_yearly' => array( 'wpo_wcpdf_template_settings' => 'yearly_reset_invoice_number' ),
268
- 'my_account_buttons' => array( 'wpo_wcpdf_general_settings' => 'my_account_buttons' ),
269
- 'invoice_number_column' => array( 'wpo_wcpdf_general_settings' => 'invoice_number_column' ),
270
- 'disable_free' => array( 'wpo_wcpdf_general_settings' => 'disable_free' ),
271
- ),
272
- 'wpo_wcpdf_documents_settings_packing-slip' => array(
273
- 'display_billing_address' => array( 'wpo_wcpdf_template_settings' => 'packing_slip_billing_address' ),
274
- 'display_email' => array( 'wpo_wcpdf_template_settings' => 'packing_slip_email' ),
275
- 'display_phone' => array( 'wpo_wcpdf_template_settings' => 'packing_slip_phone' ),
276
- ),
277
- 'wpo_wcpdf_settings_debug' => array(
278
- 'enable_debug' => array( 'wpo_wcpdf_debug_settings' => 'enable_debug' ),
279
- 'html_output' => array( 'wpo_wcpdf_debug_settings' => 'html_output' ),
280
- ),
281
- );
282
-
283
- // walk through map
284
- foreach ($settings_map as $new_option => $new_settings_keys) {
285
- ${$new_option} = array();
286
- foreach ($new_settings_keys as $new_key => $old_setting ) {
287
- $old_key = reset($old_setting);
288
- $old_option = key($old_setting);
289
- if (!empty($old_settings[$old_option][$old_key])) {
290
- // turn translatable fields into array
291
- $translatable_fields = array('shop_name','shop_address','footer','extra_1','extra_2','extra_3');
292
- if (in_array($new_key, $translatable_fields)) {
293
- ${$new_option}[$new_key] = array( 'default' => $old_settings[$old_option][$old_key] );
294
- } else {
295
- ${$new_option}[$new_key] = $old_settings[$old_option][$old_key];
296
- }
297
- }
298
- }
299
-
300
- // auto enable invoice & packing slip
301
- $enabled = array( 'wpo_wcpdf_documents_settings_invoice', 'wpo_wcpdf_documents_settings_packing-slip' );
302
- if ( in_array( $new_option, $enabled ) ) {
303
- ${$new_option}['enabled'] = 1;
304
- }
305
-
306
- // auto enable legacy mode
307
- if ( $new_option == 'wpo_wcpdf_settings_debug' ) {
308
- ${$new_option}['legacy_mode'] = 1;
309
- }
310
-
311
- // merge with existing settings
312
- ${$new_option."_old"} = get_option( $new_option, ${$new_option} ); // second argument loads new as default in case the settings did not exist yet
313
- ${$new_option} = (array) ${$new_option} + (array) ${$new_option."_old"}; // duplicate options take new options as default
314
-
315
- // store new option values
316
- update_option( $new_option, ${$new_option} );
317
- }
318
- }
319
-
320
- // 2.0-beta-2 update: copy next number to separate db store
321
- if ( version_compare( $installed_version, '2.0-beta-2', '<' ) ) {
322
- // load number store class (just in case)
323
- include_once( WPO_WCPDF()->plugin_path() . '/includes/documents/class-wcpdf-sequential-number-store.php' );
324
-
325
- $next_number = get_option( 'wpo_wcpdf_next_invoice_number' );
326
- if (!empty($next_number)) {
327
- $number_store = new \WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store( 'invoice_number' );
328
- $number_store->set_next( (int) $next_number );
329
- }
330
- // we're not deleting this option yet to make downgrading possible
331
- // delete_option( 'wpo_wcpdf_next_invoice_number' ); // clean up after ourselves
332
- }
333
-
334
- }
335
-
336
- /**
337
- * Plugin downgrade method. Perform any required downgrades here
338
- *
339
- *
340
- * @param string $installed_version the currently installed ('old') version (actually higher since this is a downgrade)
341
- */
342
- protected function downgrade( $installed_version ) {
343
- // make sure fonts match with version: copy from plugin folder
344
- $tmp_base = WPO_WCPDF()->main->get_tmp_base();
345
-
346
- // check if tmp folder exists => if not, initialize
347
- if ( !@is_dir( $tmp_base ) ) {
348
- WPO_WCPDF()->main->init_tmp( $tmp_base );
349
- } else {
350
- $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
351
- WPO_WCPDF()->main->copy_fonts( $font_path );
352
- }
353
- }
354
-
355
- }
356
-
357
- endif; // class_exists
358
-
359
  return new Install();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Install' ) ) :
13
+
14
+ class Install {
15
+
16
+ function __construct() {
17
+ // run lifecycle methods
18
+ if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
19
+ add_action( 'wp_loaded', array( $this, 'do_install' ) );
20
+ }
21
+ }
22
+
23
+ /** Lifecycle methods *******************************************************
24
+ * Because register_activation_hook only runs when the plugin is manually
25
+ * activated by the user, we're checking the current version against the
26
+ * version stored in the database
27
+ ****************************************************************************/
28
+
29
+ /**
30
+ * Handles version checking
31
+ */
32
+ public function do_install() {
33
+ // only install when woocommerce is active
34
+ if ( !WPO_WCPDF()->is_woocommerce_activated() ) {
35
+ return;
36
+ }
37
+
38
+ $version_setting = 'wpo_wcpdf_version';
39
+ $installed_version = get_option( $version_setting );
40
+
41
+ // installed version lower than plugin version?
42
+ if ( version_compare( $installed_version, WPO_WCPDF_VERSION, '<' ) ) {
43
+
44
+ if ( ! $installed_version ) {
45
+ $this->install();
46
+ } else {
47
+ $this->upgrade( $installed_version );
48
+ }
49
+
50
+ // new version number
51
+ update_option( $version_setting, WPO_WCPDF_VERSION );
52
+ } elseif ( $installed_version && version_compare( $installed_version, WPO_WCPDF_VERSION, '>' ) ) {
53
+ $this->downgrade( $installed_version );
54
+ // downgrade version number
55
+ update_option( $version_setting, WPO_WCPDF_VERSION );
56
+ }
57
+ }
58
+
59
+
60
+ /**
61
+ * Plugin install method. Perform any installation tasks here
62
+ */
63
+ protected function install() {
64
+ // only install when php 5.3 or higher
65
+ if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
66
+ return;
67
+ }
68
+
69
+ // check if upgrading from versionless (1.4.14 and older)
70
+ if ( get_option('wpo_wcpdf_general_settings') ) {
71
+ $this->upgrade( 'versionless' );
72
+ return;
73
+ }
74
+
75
+ // Create temp folders
76
+ $tmp_base = WPO_WCPDF()->main->get_tmp_base();
77
+
78
+ // check if tmp folder exists => if not, initialize
79
+ if ( !@is_dir( $tmp_base ) ) {
80
+ WPO_WCPDF()->main->init_tmp( $tmp_base );
81
+ }
82
+
83
+ // Unsupported currency symbols
84
+ $unsupported_symbols = array (
85
+ 'AED',
86
+ 'AFN',
87
+ 'BDT',
88
+ 'BHD',
89
+ 'BTC',
90
+ 'CRC',
91
+ 'DZD',
92
+ 'GEL',
93
+ 'GHS',
94
+ 'ILS',
95
+ 'INR',
96
+ 'IQD',
97
+ 'IRR',
98
+ 'IRT',
99
+ 'JOD',
100
+ 'KHR',
101
+ 'KPW',
102
+ 'KRW',
103
+ 'KWD',
104
+ 'LAK',
105
+ 'LBP',
106
+ 'LKR',
107
+ 'LYD',
108
+ 'MAD',
109
+ 'MNT',
110
+ 'MUR',
111
+ 'MVR',
112
+ 'NPR',
113
+ 'OMR',
114
+ 'PHP',
115
+ 'PKR',
116
+ 'PYG',
117
+ 'QAR',
118
+ 'RUB',
119
+ 'SAR',
120
+ 'SCR',
121
+ 'SDG',
122
+ 'SYP',
123
+ 'THB',
124
+ 'TND',
125
+ 'TRY',
126
+ 'UAH',
127
+ 'YER',
128
+ );
129
+
130
+ // set default settings
131
+ $settings_defaults = array(
132
+ 'wpo_wcpdf_settings_general' => array(
133
+ 'download_display' => 'display',
134
+ 'template_path' => WPO_WCPDF()->plugin_path() . '/templates/Simple',
135
+ 'currency_font' => ( in_array( get_woocommerce_currency(), $unsupported_symbols ) ) ? 1 : '',
136
+ 'paper_size' => 'a4',
137
+ // 'header_logo' => '',
138
+ // 'shop_name' => array(),
139
+ // 'shop_address' => array(),
140
+ // 'footer' => array(),
141
+ // 'extra_1' => array(),
142
+ // 'extra_2' => array(),
143
+ // 'extra_3' => array(),
144
+ ),
145
+ 'wpo_wcpdf_documents_settings_invoice' => array(
146
+ 'enabled' => 1,
147
+ // 'attach_to_email_ids' => array(),
148
+ // 'display_shipping_address' => '',
149
+ // 'display_email' => '',
150
+ // 'display_phone' => '',
151
+ // 'display_date' => '',
152
+ // 'display_number' => '',
153
+ // 'number_format' => array(),
154
+ // 'reset_number_yearly' => '',
155
+ // 'my_account_buttons' => '',
156
+ // 'invoice_number_column' => '',
157
+ // 'disable_free' => '',
158
+ ),
159
+ 'wpo_wcpdf_documents_settings_packing-slip' => array(
160
+ 'enabled' => 1,
161
+ // 'display_billing_address' => '',
162
+ // 'display_email' => '',
163
+ // 'display_phone' => '',
164
+ ),
165
+ // 'wpo_wcpdf_settings_debug' => array(
166
+ // 'legacy_mode' => '',
167
+ // 'enable_debug' => '',
168
+ // 'html_output' => '',
169
+ // ),
170
+ );
171
+ foreach ($settings_defaults as $option => $defaults) {
172
+ update_option( $option, $defaults );
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Plugin upgrade method. Perform any required upgrades here
178
+ *
179
+ * @param string $installed_version the currently installed ('old') version
180
+ */
181
+ protected function upgrade( $installed_version ) {
182
+ // only upgrade when php 5.3 or higher
183
+ if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
184
+ return;
185
+ }
186
+
187
+ // sync fonts on every upgrade!
188
+ $tmp_base = WPO_WCPDF()->main->get_tmp_base();
189
+
190
+ // check if tmp folder exists => if not, initialize
191
+ if ( !@is_dir( $tmp_base ) ) {
192
+ WPO_WCPDF()->main->init_tmp( $tmp_base );
193
+ } else {
194
+ $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
195
+ // don't try merging fonts with local when updating pre 2.0
196
+ $pre_2 = ( $installed_version == 'versionless' || version_compare( $installed_version, '2.0-dev', '<' ) );
197
+ $merge_with_local = $pre_2 ? false : true;
198
+ WPO_WCPDF()->main->copy_fonts( $font_path, $merge_with_local );
199
+ }
200
+
201
+ // 1.5.28 update: copy next invoice number to separate setting
202
+ if ( $installed_version == 'versionless' || version_compare( $installed_version, '1.5.28', '<' ) ) {
203
+ $template_settings = get_option( 'wpo_wcpdf_template_settings' );
204
+ $next_invoice_number = isset($template_settings['next_invoice_number'])?$template_settings['next_invoice_number']:'';
205
+ update_option( 'wpo_wcpdf_next_invoice_number', $next_invoice_number );
206
+ }
207
+
208
+ // 2.0-dev update: reorganize settings
209
+ if ( $installed_version == 'versionless' || version_compare( $installed_version, '2.0-dev', '<' ) ) {
210
+ $old_settings = array(
211
+ 'wpo_wcpdf_general_settings' => get_option( 'wpo_wcpdf_general_settings' ),
212
+ 'wpo_wcpdf_template_settings' => get_option( 'wpo_wcpdf_template_settings' ),
213
+ 'wpo_wcpdf_debug_settings' => get_option( 'wpo_wcpdf_debug_settings' ),
214
+ );
215
+
216
+ // combine invoice number formatting in array
217
+ $old_settings['wpo_wcpdf_template_settings']['invoice_number_formatting'] = array();
218
+ $format_option_keys = array('padding','suffix','prefix');
219
+ foreach ($format_option_keys as $format_option_key) {
220
+ if (isset($old_settings['wpo_wcpdf_template_settings']["invoice_number_formatting_{$format_option_key}"])) {
221
+ $old_settings['wpo_wcpdf_template_settings']['invoice_number_formatting'][$format_option_key] = $old_settings['wpo_wcpdf_template_settings']["invoice_number_formatting_{$format_option_key}"];
222
+ }
223
+ }
224
+
225
+ // convert abbreviated email_ids
226
+ if (isset($old_settings['wpo_wcpdf_general_settings']['email_pdf'])) {
227
+ foreach ($old_settings['wpo_wcpdf_general_settings']['email_pdf'] as $email_id => $value) {
228
+ if ($email_id == 'completed' || $email_id == 'processing') {
229
+ $old_settings['wpo_wcpdf_general_settings']['email_pdf']["customer_{$email_id}_order"] = $value;
230
+ unset($old_settings['wpo_wcpdf_general_settings']['email_pdf'][$email_id]);
231
+ }
232
+ }
233
+ }
234
+
235
+ // Migrate template path
236
+ // forward slash for consistency/compatibility
237
+ $template_path = str_replace('\\','/', $old_settings['wpo_wcpdf_template_settings']['template_path']);
238
+ // strip abspath (forward slashed) if included
239
+ $template_path = str_replace( str_replace('\\','/', ABSPATH), '', $template_path );
240
+ // strip pdf subfolder from templates path
241
+ $template_path = str_replace( '/templates/pdf/', '/templates/', $template_path );
242
+ $old_settings['wpo_wcpdf_template_settings']['template_path'] = $template_path;
243
+
244
+ // map new settings to old
245
+ $settings_map = array(
246
+ 'wpo_wcpdf_settings_general' => array(
247
+ 'download_display' => array( 'wpo_wcpdf_general_settings' => 'download_display' ),
248
+ 'template_path' => array( 'wpo_wcpdf_template_settings' => 'template_path' ),
249
+ 'currency_font' => array( 'wpo_wcpdf_template_settings' => 'currency_font' ),
250
+ 'paper_size' => array( 'wpo_wcpdf_template_settings' => 'paper_size' ),
251
+ 'header_logo' => array( 'wpo_wcpdf_template_settings' => 'header_logo' ),
252
+ 'shop_name' => array( 'wpo_wcpdf_template_settings' => 'shop_name' ),
253
+ 'shop_address' => array( 'wpo_wcpdf_template_settings' => 'shop_address' ),
254
+ 'footer' => array( 'wpo_wcpdf_template_settings' => 'footer' ),
255
+ 'extra_1' => array( 'wpo_wcpdf_template_settings' => 'extra_1' ),
256
+ 'extra_2' => array( 'wpo_wcpdf_template_settings' => 'extra_2' ),
257
+ 'extra_3' => array( 'wpo_wcpdf_template_settings' => 'extra_3' ),
258
+ ),
259
+ 'wpo_wcpdf_documents_settings_invoice' => array(
260
+ 'attach_to_email_ids' => array( 'wpo_wcpdf_general_settings' => 'email_pdf' ),
261
+ 'display_shipping_address' => array( 'wpo_wcpdf_template_settings' => 'invoice_shipping_address' ),
262
+ 'display_email' => array( 'wpo_wcpdf_template_settings' => 'invoice_email' ),
263
+ 'display_phone' => array( 'wpo_wcpdf_template_settings' => 'invoice_phone' ),
264
+ 'display_date' => array( 'wpo_wcpdf_template_settings' => 'display_date' ),
265
+ 'display_number' => array( 'wpo_wcpdf_template_settings' => 'display_number' ),
266
+ 'number_format' => array( 'wpo_wcpdf_template_settings' => 'invoice_number_formatting' ),
267
+ 'reset_number_yearly' => array( 'wpo_wcpdf_template_settings' => 'yearly_reset_invoice_number' ),
268
+ 'my_account_buttons' => array( 'wpo_wcpdf_general_settings' => 'my_account_buttons' ),
269
+ 'invoice_number_column' => array( 'wpo_wcpdf_general_settings' => 'invoice_number_column' ),
270
+ 'disable_free' => array( 'wpo_wcpdf_general_settings' => 'disable_free' ),
271
+ ),
272
+ 'wpo_wcpdf_documents_settings_packing-slip' => array(
273
+ 'display_billing_address' => array( 'wpo_wcpdf_template_settings' => 'packing_slip_billing_address' ),
274
+ 'display_email' => array( 'wpo_wcpdf_template_settings' => 'packing_slip_email' ),
275
+ 'display_phone' => array( 'wpo_wcpdf_template_settings' => 'packing_slip_phone' ),
276
+ ),
277
+ 'wpo_wcpdf_settings_debug' => array(
278
+ 'enable_debug' => array( 'wpo_wcpdf_debug_settings' => 'enable_debug' ),
279
+ 'html_output' => array( 'wpo_wcpdf_debug_settings' => 'html_output' ),
280
+ ),
281
+ );
282
+
283
+ // walk through map
284
+ foreach ($settings_map as $new_option => $new_settings_keys) {
285
+ ${$new_option} = array();
286
+ foreach ($new_settings_keys as $new_key => $old_setting ) {
287
+ $old_key = reset($old_setting);
288
+ $old_option = key($old_setting);
289
+ if (!empty($old_settings[$old_option][$old_key])) {
290
+ // turn translatable fields into array
291
+ $translatable_fields = array('shop_name','shop_address','footer','extra_1','extra_2','extra_3');
292
+ if (in_array($new_key, $translatable_fields)) {
293
+ ${$new_option}[$new_key] = array( 'default' => $old_settings[$old_option][$old_key] );
294
+ } else {
295
+ ${$new_option}[$new_key] = $old_settings[$old_option][$old_key];
296
+ }
297
+ }
298
+ }
299
+
300
+ // auto enable invoice & packing slip
301
+ $enabled = array( 'wpo_wcpdf_documents_settings_invoice', 'wpo_wcpdf_documents_settings_packing-slip' );
302
+ if ( in_array( $new_option, $enabled ) ) {
303
+ ${$new_option}['enabled'] = 1;
304
+ }
305
+
306
+ // auto enable legacy mode
307
+ if ( $new_option == 'wpo_wcpdf_settings_debug' ) {
308
+ ${$new_option}['legacy_mode'] = 1;
309
+ }
310
+
311
+ // merge with existing settings
312
+ ${$new_option."_old"} = get_option( $new_option, ${$new_option} ); // second argument loads new as default in case the settings did not exist yet
313
+ ${$new_option} = (array) ${$new_option} + (array) ${$new_option."_old"}; // duplicate options take new options as default
314
+
315
+ // store new option values
316
+ update_option( $new_option, ${$new_option} );
317
+ }
318
+ }
319
+
320
+ // 2.0-beta-2 update: copy next number to separate db store
321
+ if ( version_compare( $installed_version, '2.0-beta-2', '<' ) ) {
322
+ // load number store class (just in case)
323
+ include_once( WPO_WCPDF()->plugin_path() . '/includes/documents/class-wcpdf-sequential-number-store.php' );
324
+
325
+ $next_number = get_option( 'wpo_wcpdf_next_invoice_number' );
326
+ if (!empty($next_number)) {
327
+ $number_store = new \WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store( 'invoice_number' );
328
+ $number_store->set_next( (int) $next_number );
329
+ }
330
+ // we're not deleting this option yet to make downgrading possible
331
+ // delete_option( 'wpo_wcpdf_next_invoice_number' ); // clean up after ourselves
332
+ }
333
+
334
+ }
335
+
336
+ /**
337
+ * Plugin downgrade method. Perform any required downgrades here
338
+ *
339
+ *
340
+ * @param string $installed_version the currently installed ('old') version (actually higher since this is a downgrade)
341
+ */
342
+ protected function downgrade( $installed_version ) {
343
+ // make sure fonts match with version: copy from plugin folder
344
+ $tmp_base = WPO_WCPDF()->main->get_tmp_base();
345
+
346
+ // check if tmp folder exists => if not, initialize
347
+ if ( !@is_dir( $tmp_base ) ) {
348
+ WPO_WCPDF()->main->init_tmp( $tmp_base );
349
+ } else {
350
+ $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
351
+ WPO_WCPDF()->main->copy_fonts( $font_path );
352
+ }
353
+ }
354
+
355
+ }
356
+
357
+ endif; // class_exists
358
+
359
  return new Install();
includes/class-wcpdf-main.php CHANGED
@@ -1,502 +1,502 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Main' ) ) :
13
-
14
- class Main {
15
-
16
- function __construct() {
17
- add_action( 'wp_ajax_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' ) );
18
- add_filter( 'woocommerce_email_attachments', array( $this, 'attach_pdf_to_email' ), 99, 3 );
19
- add_filter( 'wpo_wcpdf_custom_attachment_condition', array( $this, 'disable_free_attachment'), 10, 4 );
20
-
21
- if ( isset(WPO_WCPDF()->settings->debug_settings['enable_debug']) ) {
22
- $this->enable_debug();
23
- }
24
- if ( isset(WPO_WCPDF()->settings->debug_settings['html_output']) ) {
25
- add_filter( 'wpo_wcpdf_use_path', '__return_false' );
26
- }
27
-
28
- // include template specific custom functions
29
- $template_path = WPO_WCPDF()->settings->get_template_path();
30
- if ( file_exists( $template_path . '/template-functions.php' ) ) {
31
- require_once( $template_path . '/template-functions.php' );
32
- }
33
-
34
- // page numbers & currency filters
35
- add_action( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
36
- add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
37
- if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
38
- add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
39
- }
40
-
41
- // scheduled attachments cleanup - disabled for now
42
- // add_action( 'wp_scheduled_delete', array( $this, 'attachments_cleanup') );
43
- }
44
-
45
- /**
46
- * Attach PDF to WooCommerce email
47
- */
48
- public function attach_pdf_to_email ( $attachments, $email_id, $order ) {
49
- // check if all variables properly set
50
- if ( !is_object( $order ) || !isset( $email_id ) ) {
51
- return $attachments;
52
- }
53
-
54
- // Skip User emails
55
- if ( get_class( $order ) == 'WP_User' ) {
56
- return $attachments;
57
- }
58
-
59
- $order_id = WCX_Order::get_id( $order );
60
-
61
- if ( get_class( $order ) !== 'WC_Order' && $order_id == false ) {
62
- return $attachments;
63
- }
64
-
65
- // WooCommerce Booking compatibility
66
- if ( get_post_type( $order_id ) == 'wc_booking' && isset($order->order) ) {
67
- // $order is actually a WC_Booking object!
68
- $order = $order->order;
69
- }
70
-
71
- // do not process low stock notifications, user emails etc!
72
- if ( in_array( $email_id, array( 'no_stock', 'low_stock', 'backorder', 'customer_new_account', 'customer_reset_password' ) ) || get_post_type( $order_id ) != 'shop_order' ) {
73
- return $attachments;
74
- }
75
-
76
- $tmp_path = $this->get_tmp_path('attachments');
77
-
78
- // clear pdf files from temp folder (from http://stackoverflow.com/a/13468943/1446634)
79
- // array_map('unlink', ( glob( $tmp_path.'*.pdf' ) ? glob( $tmp_path.'*.pdf' ) : array() ) );
80
-
81
- // disable deprecation notices during email sending
82
- add_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
83
-
84
- $attach_to_document_types = $this->get_documents_for_email( $email_id, $order );
85
- foreach ( $attach_to_document_types as $document_type ) {
86
- do_action( 'wpo_wcpdf_before_attachment_creation', $order, $email_id, $document_type );
87
-
88
- try {
89
- // prepare document
90
- $document = wcpdf_get_document( $document_type, (array) $order_id, true );
91
- if ( !$document ) { // something went wrong, continue trying with other documents
92
- continue;
93
- }
94
-
95
- // get pdf data & store
96
- $pdf_data = $document->get_pdf();
97
- $filename = $document->get_filename();
98
- $pdf_path = $tmp_path . $filename;
99
- file_put_contents ( $pdf_path, $pdf_data );
100
- $attachments[] = $pdf_path;
101
-
102
- do_action( 'wpo_wcpdf_email_attachment', $pdf_path, $document_type );
103
- } catch (Exception $e) {
104
- error_log($e->getMessage());
105
- continue;
106
- }
107
- }
108
-
109
- remove_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
110
-
111
- return $attachments;
112
- }
113
-
114
- public function get_documents_for_email( $email_id, $order ) {
115
- $documents = WPO_WCPDF()->documents->get_documents();
116
-
117
- $attach_documents = array();
118
- foreach ($documents as $document) {
119
- $attach_documents[ $document->get_type() ] = $document->get_attach_to_email_ids();
120
- }
121
- $attach_documents = apply_filters('wpo_wcpdf_attach_documents', $attach_documents );
122
-
123
- $document_types = array();
124
- foreach ($attach_documents as $document_type => $attach_to_email_ids ) {
125
- // legacy settings: convert abbreviated email_ids
126
- foreach ($attach_to_email_ids as $key => $attach_to_email_id) {
127
- if ($attach_to_email_id == 'completed' || $attach_to_email_id == 'processing') {
128
- $attach_to_email_ids[$key] = "customer_" . $attach_to_email_id . "_order";
129
- }
130
- }
131
-
132
- $extra_condition = apply_filters('wpo_wcpdf_custom_attachment_condition', true, $order, $email_id, $document_type );
133
- if ( in_array( $email_id, $attach_to_email_ids ) && $extra_condition === true ) {
134
- $document_types[] = $document_type;
135
- }
136
- }
137
-
138
- return $document_types;
139
- }
140
-
141
- /**
142
- * Load and generate the template output with ajax
143
- */
144
- public function generate_pdf_ajax() {
145
- // Check the nonce
146
- if( empty( $_GET['action'] ) || !check_admin_referer( $_GET['action'] ) ) {
147
- wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
148
- }
149
-
150
- // Check if all parameters are set
151
- if ( empty( $_GET['document_type'] ) && !empty( $_GET['template_type'] ) ) {
152
- $_GET['document_type'] = $_GET['template_type'];
153
- }
154
-
155
- if ( empty( $_GET['order_ids'] ) ) {
156
- wp_die( __( "You haven't selected any orders", 'woocommerce-pdf-invoices-packing-slips' ) );
157
- }
158
-
159
- if( empty( $_GET['document_type'] ) ) {
160
- wp_die( __( 'Some of the export parameters are missing.', 'woocommerce-pdf-invoices-packing-slips' ) );
161
- }
162
-
163
- // Generate the output
164
- $document_type = sanitize_text_field( $_GET['document_type'] );
165
-
166
- $order_ids = (array) array_map( 'absint', explode( 'x', $_GET['order_ids'] ) );
167
- // Process oldest first: reverse $order_ids array
168
- $order_ids = array_reverse( $order_ids );
169
-
170
- // set default is allowed
171
- $allowed = true;
172
-
173
- // check if user is logged in
174
- if ( ! is_user_logged_in() ) {
175
- $allowed = false;
176
- }
177
-
178
- // Check the user privileges
179
- if( !( current_user_can( 'manage_woocommerce_orders' ) || current_user_can( 'edit_shop_orders' ) ) && !isset( $_GET['my-account'] ) ) {
180
- $allowed = false;
181
- }
182
-
183
- // User call from my-account page
184
- if ( !current_user_can('manage_options') && isset( $_GET['my-account'] ) ) {
185
- // Only for single orders!
186
- if ( count( $order_ids ) > 1 ) {
187
- $allowed = false;
188
- }
189
-
190
- // Check if current user is owner of order IMPORTANT!!!
191
- if ( ! current_user_can( 'view_order', $order_ids[0] ) ) {
192
- $allowed = false;
193
- }
194
- }
195
-
196
- $allowed = apply_filters( 'wpo_wcpdf_check_privs', $allowed, $order_ids );
197
-
198
- if ( ! $allowed ) {
199
- wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
200
- }
201
-
202
- // if we got here, we're safe to go!
203
- try {
204
- $document = wcpdf_get_document( $document_type, $order_ids, true );
205
-
206
- if ( $document ) {
207
- $output_format = WPO_WCPDF()->settings->get_output_format( $document_type );
208
- switch ( $output_format ) {
209
- case 'html':
210
- $document->output_html();
211
- break;
212
- case 'pdf':
213
- default:
214
- if ( has_action( 'wpo_wcpdf_created_manually' ) ) {
215
- do_action( 'wpo_wcpdf_created_manually', $document->get_pdf(), $document->get_filename() );
216
- }
217
- $output_mode = WPO_WCPDF()->settings->get_output_mode( $document_type );
218
- $document->output_pdf( $output_mode );
219
- break;
220
- }
221
- } else {
222
- wp_die( sprintf( __( "Document of type '%s' for the selected order(s) could not be generated", 'woocommerce-pdf-invoices-packing-slips' ), $document_type ) );
223
- }
224
- } catch (Exception $e) {
225
- echo $e->getMessage();
226
- }
227
-
228
- exit;
229
- }
230
-
231
- /**
232
- * Return tmp path for different plugin processes
233
- */
234
- public function get_tmp_path ( $type = '' ) {
235
- $tmp_base = $this->get_tmp_base();
236
- // check if tmp folder exists => if not, initialize
237
- if ( !@is_dir( $tmp_base ) ) {
238
- $this->init_tmp( $tmp_base );
239
- }
240
-
241
- if ( empty( $type ) ) {
242
- return $tmp_base;
243
- }
244
-
245
- switch ( $type ) {
246
- case 'dompdf':
247
- $tmp_path = $tmp_base . 'dompdf';
248
- break;
249
- case 'font_cache':
250
- case 'fonts':
251
- $tmp_path = $tmp_base . 'fonts';
252
- break;
253
- case 'attachments':
254
- $tmp_path = $tmp_base . 'attachments/';
255
- break;
256
- default:
257
- $tmp_path = $tmp_base . $type;
258
- break;
259
- }
260
-
261
- // double check for existence, in case tmp_base was installed, but subfolder not created
262
- if ( !@is_dir( $tmp_path ) ) {
263
- @mkdir( $tmp_path );
264
- }
265
-
266
- return $tmp_path;
267
- }
268
-
269
- /**
270
- * return the base tmp folder (usually uploads)
271
- */
272
- public function get_tmp_base () {
273
- // wp_upload_dir() is used to set the base temp folder, under which a
274
- // 'wpo_wcpdf' folder and several subfolders are created
275
- //
276
- // wp_upload_dir() will:
277
- // * default to WP_CONTENT_DIR/uploads
278
- // * UNLESS the ‘UPLOADS’ constant is defined in wp-config (http://codex.wordpress.org/Editing_wp-config.php#Moving_uploads_folder)
279
- //
280
- // May also be overridden by the wpo_wcpdf_tmp_path filter
281
-
282
- $upload_dir = wp_upload_dir();
283
- $upload_base = trailingslashit( $upload_dir['basedir'] );
284
- $tmp_base = trailingslashit( apply_filters( 'wpo_wcpdf_tmp_path', $upload_base . 'wpo_wcpdf/' ) );
285
- return $tmp_base;
286
- }
287
-
288
- /**
289
- * Install/create plugin tmp folders
290
- */
291
- public function init_tmp ( $tmp_base ) {
292
- // create plugin base temp folder
293
- @mkdir( $tmp_base );
294
-
295
- // create subfolders & protect
296
- $subfolders = array( 'attachments', 'fonts', 'dompdf' );
297
- foreach ( $subfolders as $subfolder ) {
298
- $path = $tmp_base . $subfolder . '/';
299
- @mkdir( $path );
300
-
301
- // copy font files
302
- if ( $subfolder == 'fonts' ) {
303
- $this->copy_fonts( $path, false );
304
- }
305
-
306
- // create .htaccess file and empty index.php to protect in case an open webfolder is used!
307
- @file_put_contents( $path . '.htaccess', 'deny from all' );
308
- @touch( $path . 'index.php' );
309
- }
310
-
311
- }
312
-
313
- /**
314
- * Copy DOMPDF fonts to wordpress tmp folder
315
- */
316
- public function copy_fonts ( $path, $merge_with_local = true ) {
317
- $path = trailingslashit( $path );
318
- $dompdf_font_dir = WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/";
319
-
320
- // get local font dir from filtered options
321
- $dompdf_options = apply_filters( 'wpo_wcpdf_dompdf_options', array(
322
- 'defaultFont' => 'dejavu sans',
323
- 'tempDir' => $this->get_tmp_path('dompdf'),
324
- 'logOutputFile' => $this->get_tmp_path('dompdf') . "/log.htm",
325
- 'fontDir' => $this->get_tmp_path('fonts'),
326
- 'fontCache' => $this->get_tmp_path('fonts'),
327
- 'isRemoteEnabled' => true,
328
- 'isFontSubsettingEnabled' => true,
329
- 'isHtml5ParserEnabled' => true,
330
- ) );
331
- $fontDir = $dompdf_options['fontDir'];
332
-
333
- // merge font family cache with local/custom if present
334
- $font_cache_files = array(
335
- 'cache' => 'dompdf_font_family_cache.php',
336
- 'cache_dist' => 'dompdf_font_family_cache.dist.php',
337
- );
338
- foreach ( $font_cache_files as $font_cache_name => $font_cache_filename ) {
339
- $plugin_fonts = @require $dompdf_font_dir . $font_cache_filename;
340
- if ( $merge_with_local && is_readable( $path . $font_cache_filename ) ) {
341
- $local_fonts = @require $path . $font_cache_filename;
342
- if (is_array($local_fonts) && is_array($plugin_fonts)) {
343
- // merge local & plugin fonts, plugin fonts overwrite (update) local fonts
344
- // while custom local fonts are retained
345
- $local_fonts = array_merge($local_fonts, $plugin_fonts);
346
- // create readable array with $fontDir in place of the actual folder for portability
347
- $fonts_export = var_export($local_fonts,true);
348
- $fonts_export = str_replace('\'' . $fontDir , '$fontDir . \'', $fonts_export);
349
- $cacheData = sprintf("<?php return %s;%s?>", $fonts_export, PHP_EOL );
350
- // write file with merged cache data
351
- file_put_contents($path . $font_cache_filename, $cacheData);
352
- } else { // empty local file
353
- copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
354
- }
355
- } else {
356
- // we couldn't read the local font cache file so we're simply copying over plugin cache file
357
- copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
358
- }
359
- }
360
-
361
- // first try the easy way with glob!
362
- if ( function_exists('glob') ) {
363
- $files = glob($dompdf_font_dir."*.*");
364
- foreach($files as $file){
365
- $filename = basename($file);
366
- if( !is_dir($file) && is_readable($file) && !in_array($filename, $font_cache_files)) {
367
- $dest = $path . $filename;
368
- copy($file, $dest);
369
- }
370
- }
371
- } else {
372
- // fallback method using font cache file (glob is disabled on some servers with disable_functions)
373
- $extensions = array( '.ttf', '.ufm', '.ufm.php', '.afm', '.afm.php' );
374
- $fontDir = untrailingslashit($dompdf_font_dir);
375
- $plugin_fonts = @require $dompdf_font_dir . $font_cache_files['cache'];
376
-
377
- foreach ($plugin_fonts as $font_family => $filenames) {
378
- foreach ($filenames as $filename) {
379
- foreach ($extensions as $extension) {
380
- $file = $filename.$extension;
381
- if (file_exists($file)) {
382
- $dest = $path . basename($file);
383
- copy($file, $dest);
384
- }
385
- }
386
- }
387
- }
388
- }
389
- }
390
-
391
- public function disable_free_attachment( $attach, $order, $email_id, $document_type ) {
392
- // prevent fatal error for non-order objects
393
- if ( !method_exists( $order, 'get_total' ) ) {
394
- return false;
395
- }
396
-
397
- $document_settings = WPO_WCPDF()->settings->get_document_settings( $document_type );
398
- // echo '<pre>';var_dump($document_type);echo '</pre>';
399
- // error_log( var_export($document_settings,true) );
400
-
401
- // check order total & setting
402
- $order_total = $order->get_total();
403
- if ( $order_total == 0 && isset( $document_settings['disable_free'] ) ) {
404
- return false;
405
- }
406
-
407
- return $attach;
408
- }
409
-
410
- /**
411
- * Adds spans around placeholders to be able to make replacement (page count) and css (page number)
412
- */
413
- public function format_page_number_placeholders ( $html, $document ) {
414
- $html = str_replace('{{PAGE_COUNT}}', '<span class="pagecount">^C^</span>', $html);
415
- $html = str_replace('{{PAGE_NUM}}', '<span class="pagenum"></span>', $html );
416
- return $html;
417
- }
418
-
419
- /**
420
- * Replace {{PAGE_COUNT}} placeholder with total page count
421
- */
422
- public function page_number_replacements ( $dompdf, $html ) {
423
- $placeholder = '^C^';
424
- // create placeholder version with ASCII 0 spaces (dompdf 0.8)
425
- $placeholder_0 = '';
426
- $placeholder_chars = str_split($placeholder);
427
- foreach ($placeholder_chars as $placeholder_char) {
428
- $placeholder_0 .= chr(0).$placeholder_char;
429
- }
430
-
431
- // check if placeholder is used
432
- if (strpos($html, $placeholder) !== false ) {
433
- foreach ($dompdf->get_canvas()->get_cpdf()->objects as &$object) {
434
- if (array_key_exists("c", $object) && strpos($object["c"], $placeholder) !== false ) {
435
- $object["c"] = str_replace( array($placeholder,$placeholder_0) , $dompdf->get_canvas()->get_page_count() , $object["c"] );
436
- } elseif (array_key_exists("c", $object) && strpos($object["c"], $placeholder_0) !== false ) {
437
- $object["c"] = str_replace( array($placeholder,$placeholder_0) , chr(0).$dompdf->get_canvas()->get_page_count() , $object["c"] );
438
- }
439
- }
440
- }
441
-
442
- return $dompdf;
443
- }
444
-
445
- /**
446
- * Use currency symbol font (when enabled in options)
447
- */
448
- public function use_currency_font ( $document_type, $document ) {
449
- add_filter( 'woocommerce_currency_symbol', array( $this, 'wrap_currency_symbol' ), 999, 2);
450
- add_action( 'wpo_wcpdf_custom_styles', array($this, 'currency_symbol_font_styles' ) );
451
- }
452
-
453
- public function wrap_currency_symbol( $currency_symbol, $currency ) {
454
- $currency_symbol = sprintf( '<span class="wcpdf-currency-symbol">%s</span>', $currency_symbol );
455
- return $currency_symbol;
456
- }
457
-
458
- public function currency_symbol_font_styles () {
459
- ?>
460
- .wcpdf-currency-symbol { font-family: 'Currencies'; }
461
- <?php
462
- }
463
-
464
- /**
465
- * Remove attachments older than 1 week (daily, hooked into wp_scheduled_delete )
466
- */
467
- public function attachments_cleanup() {
468
- if ( !function_exists("glob") || !function_exists('filemtime')) {
469
- // glob is disabled
470
- return;
471
- }
472
-
473
- $delete_timestamp = time() - ( DAY_IN_SECONDS * 7 );
474
-
475
- $tmp_path = $this->get_tmp_path('attachments');
476
-
477
- if ( $files = glob( $tmp_path.'*.pdf' ) ) { // get all pdf files
478
- foreach( $files as $file ) {
479
- if( is_file( $file ) ) {
480
- $file_timestamp = filemtime( $file );
481
- if ( !empty( $file_timestamp ) && $file_timestamp < $delete_timestamp ) {
482
- @unlink($file);
483
- }
484
- }
485
- }
486
- }
487
-
488
- }
489
-
490
- /**
491
- * Enable PHP error output
492
- */
493
- public function enable_debug () {
494
- error_reporting( E_ALL );
495
- ini_set( 'display_errors', 1 );
496
- }
497
-
498
- }
499
-
500
- endif; // class_exists
501
-
502
- return new Main();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Main' ) ) :
13
+
14
+ class Main {
15
+
16
+ function __construct() {
17
+ add_action( 'wp_ajax_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' ) );
18
+ add_filter( 'woocommerce_email_attachments', array( $this, 'attach_pdf_to_email' ), 99, 3 );
19
+ add_filter( 'wpo_wcpdf_custom_attachment_condition', array( $this, 'disable_free_attachment'), 10, 4 );
20
+
21
+ if ( isset(WPO_WCPDF()->settings->debug_settings['enable_debug']) ) {
22
+ $this->enable_debug();
23
+ }
24
+ if ( isset(WPO_WCPDF()->settings->debug_settings['html_output']) ) {
25
+ add_filter( 'wpo_wcpdf_use_path', '__return_false' );
26
+ }
27
+
28
+ // include template specific custom functions
29
+ $template_path = WPO_WCPDF()->settings->get_template_path();
30
+ if ( file_exists( $template_path . '/template-functions.php' ) ) {
31
+ require_once( $template_path . '/template-functions.php' );
32
+ }
33
+
34
+ // page numbers & currency filters
35
+ add_action( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
36
+ add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
37
+ if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
38
+ add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
39
+ }
40
+
41
+ // scheduled attachments cleanup - disabled for now
42
+ // add_action( 'wp_scheduled_delete', array( $this, 'attachments_cleanup') );
43
+ }
44
+
45
+ /**
46
+ * Attach PDF to WooCommerce email
47
+ */
48
+ public function attach_pdf_to_email ( $attachments, $email_id, $order ) {
49
+ // check if all variables properly set
50
+ if ( !is_object( $order ) || !isset( $email_id ) ) {
51
+ return $attachments;
52
+ }
53
+
54
+ // Skip User emails
55
+ if ( get_class( $order ) == 'WP_User' ) {
56
+ return $attachments;
57
+ }
58
+
59
+ $order_id = WCX_Order::get_id( $order );
60
+
61
+ if ( get_class( $order ) !== 'WC_Order' && $order_id == false ) {
62
+ return $attachments;
63
+ }
64
+
65
+ // WooCommerce Booking compatibility
66
+ if ( get_post_type( $order_id ) == 'wc_booking' && isset($order->order) ) {
67
+ // $order is actually a WC_Booking object!
68
+ $order = $order->order;
69
+ }
70
+
71
+ // do not process low stock notifications, user emails etc!
72
+ if ( in_array( $email_id, array( 'no_stock', 'low_stock', 'backorder', 'customer_new_account', 'customer_reset_password' ) ) || get_post_type( $order_id ) != 'shop_order' ) {
73
+ return $attachments;
74
+ }
75
+
76
+ $tmp_path = $this->get_tmp_path('attachments');
77
+
78
+ // clear pdf files from temp folder (from http://stackoverflow.com/a/13468943/1446634)
79
+ // array_map('unlink', ( glob( $tmp_path.'*.pdf' ) ? glob( $tmp_path.'*.pdf' ) : array() ) );
80
+
81
+ // disable deprecation notices during email sending
82
+ add_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
83
+
84
+ $attach_to_document_types = $this->get_documents_for_email( $email_id, $order );
85
+ foreach ( $attach_to_document_types as $document_type ) {
86
+ do_action( 'wpo_wcpdf_before_attachment_creation', $order, $email_id, $document_type );
87
+
88
+ try {
89
+ // prepare document
90
+ $document = wcpdf_get_document( $document_type, (array) $order_id, true );
91
+ if ( !$document ) { // something went wrong, continue trying with other documents
92
+ continue;
93
+ }
94
+
95
+ // get pdf data & store
96
+ $pdf_data = $document->get_pdf();
97
+ $filename = $document->get_filename();
98
+ $pdf_path = $tmp_path . $filename;
99
+ file_put_contents ( $pdf_path, $pdf_data );
100
+ $attachments[] = $pdf_path;
101
+
102
+ do_action( 'wpo_wcpdf_email_attachment', $pdf_path, $document_type );
103
+ } catch (Exception $e) {
104
+ error_log($e->getMessage());
105
+ continue;
106
+ }
107
+ }
108
+
109
+ remove_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
110
+
111
+ return $attachments;
112
+ }
113
+
114
+ public function get_documents_for_email( $email_id, $order ) {
115
+ $documents = WPO_WCPDF()->documents->get_documents();
116
+
117
+ $attach_documents = array();
118
+ foreach ($documents as $document) {
119
+ $attach_documents[ $document->get_type() ] = $document->get_attach_to_email_ids();
120
+ }
121
+ $attach_documents = apply_filters('wpo_wcpdf_attach_documents', $attach_documents );
122
+
123
+ $document_types = array();
124
+ foreach ($attach_documents as $document_type => $attach_to_email_ids ) {
125
+ // legacy settings: convert abbreviated email_ids
126
+ foreach ($attach_to_email_ids as $key => $attach_to_email_id) {
127
+ if ($attach_to_email_id == 'completed' || $attach_to_email_id == 'processing') {
128
+ $attach_to_email_ids[$key] = "customer_" . $attach_to_email_id . "_order";
129
+ }
130
+ }
131
+
132
+ $extra_condition = apply_filters('wpo_wcpdf_custom_attachment_condition', true, $order, $email_id, $document_type );
133
+ if ( in_array( $email_id, $attach_to_email_ids ) && $extra_condition === true ) {
134
+ $document_types[] = $document_type;
135
+ }
136
+ }
137
+
138
+ return $document_types;
139
+ }
140
+
141
+ /**
142
+ * Load and generate the template output with ajax
143
+ */
144
+ public function generate_pdf_ajax() {
145
+ // Check the nonce
146
+ if( empty( $_GET['action'] ) || !check_admin_referer( $_GET['action'] ) ) {
147
+ wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
148
+ }
149
+
150
+ // Check if all parameters are set
151
+ if ( empty( $_GET['document_type'] ) && !empty( $_GET['template_type'] ) ) {
152
+ $_GET['document_type'] = $_GET['template_type'];
153
+ }
154
+
155
+ if ( empty( $_GET['order_ids'] ) ) {
156
+ wp_die( __( "You haven't selected any orders", 'woocommerce-pdf-invoices-packing-slips' ) );
157
+ }
158
+
159
+ if( empty( $_GET['document_type'] ) ) {
160
+ wp_die( __( 'Some of the export parameters are missing.', 'woocommerce-pdf-invoices-packing-slips' ) );
161
+ }
162
+
163
+ // Generate the output
164
+ $document_type = sanitize_text_field( $_GET['document_type'] );
165
+
166
+ $order_ids = (array) array_map( 'absint', explode( 'x', $_GET['order_ids'] ) );
167
+ // Process oldest first: reverse $order_ids array
168
+ $order_ids = array_reverse( $order_ids );
169
+
170
+ // set default is allowed
171
+ $allowed = true;
172
+
173
+ // check if user is logged in
174
+ if ( ! is_user_logged_in() ) {
175
+ $allowed = false;
176
+ }
177
+
178
+ // Check the user privileges
179
+ if( !( current_user_can( 'manage_woocommerce_orders' ) || current_user_can( 'edit_shop_orders' ) ) && !isset( $_GET['my-account'] ) ) {
180
+ $allowed = false;
181
+ }
182
+
183
+ // User call from my-account page
184
+ if ( !current_user_can('manage_options') && isset( $_GET['my-account'] ) ) {
185
+ // Only for single orders!
186
+ if ( count( $order_ids ) > 1 ) {
187
+ $allowed = false;
188
+ }
189
+
190
+ // Check if current user is owner of order IMPORTANT!!!
191
+ if ( ! current_user_can( 'view_order', $order_ids[0] ) ) {
192
+ $allowed = false;
193
+ }
194
+ }
195
+
196
+ $allowed = apply_filters( 'wpo_wcpdf_check_privs', $allowed, $order_ids );
197
+
198
+ if ( ! $allowed ) {
199
+ wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
200
+ }
201
+
202
+ // if we got here, we're safe to go!
203
+ try {
204
+ $document = wcpdf_get_document( $document_type, $order_ids, true );
205
+
206
+ if ( $document ) {
207
+ $output_format = WPO_WCPDF()->settings->get_output_format( $document_type );
208
+ switch ( $output_format ) {
209
+ case 'html':
210
+ $document->output_html();
211
+ break;
212
+ case 'pdf':
213
+ default:
214
+ if ( has_action( 'wpo_wcpdf_created_manually' ) ) {
215
+ do_action( 'wpo_wcpdf_created_manually', $document->get_pdf(), $document->get_filename() );
216
+ }
217
+ $output_mode = WPO_WCPDF()->settings->get_output_mode( $document_type );
218
+ $document->output_pdf( $output_mode );
219
+ break;
220
+ }
221
+ } else {
222
+ wp_die( sprintf( __( "Document of type '%s' for the selected order(s) could not be generated", 'woocommerce-pdf-invoices-packing-slips' ), $document_type ) );
223
+ }
224
+ } catch (Exception $e) {
225
+ echo $e->getMessage();
226
+ }
227
+
228
+ exit;
229
+ }
230
+
231
+ /**
232
+ * Return tmp path for different plugin processes
233
+ */
234
+ public function get_tmp_path ( $type = '' ) {
235
+ $tmp_base = $this->get_tmp_base();
236
+ // check if tmp folder exists => if not, initialize
237
+ if ( !@is_dir( $tmp_base ) ) {
238
+ $this->init_tmp( $tmp_base );
239
+ }
240
+
241
+ if ( empty( $type ) ) {
242
+ return $tmp_base;
243
+ }
244
+
245
+ switch ( $type ) {
246
+ case 'dompdf':
247
+ $tmp_path = $tmp_base . 'dompdf';
248
+ break;
249
+ case 'font_cache':
250
+ case 'fonts':
251
+ $tmp_path = $tmp_base . 'fonts';
252
+ break;
253
+ case 'attachments':
254
+ $tmp_path = $tmp_base . 'attachments/';
255
+ break;
256
+ default:
257
+ $tmp_path = $tmp_base . $type;
258
+ break;
259
+ }
260
+
261
+ // double check for existence, in case tmp_base was installed, but subfolder not created
262
+ if ( !@is_dir( $tmp_path ) ) {
263
+ @mkdir( $tmp_path );
264
+ }
265
+
266
+ return $tmp_path;
267
+ }
268
+
269
+ /**
270
+ * return the base tmp folder (usually uploads)
271
+ */
272
+ public function get_tmp_base () {
273
+ // wp_upload_dir() is used to set the base temp folder, under which a
274
+ // 'wpo_wcpdf' folder and several subfolders are created
275
+ //
276
+ // wp_upload_dir() will:
277
+ // * default to WP_CONTENT_DIR/uploads
278
+ // * UNLESS the ‘UPLOADS’ constant is defined in wp-config (http://codex.wordpress.org/Editing_wp-config.php#Moving_uploads_folder)
279
+ //
280
+ // May also be overridden by the wpo_wcpdf_tmp_path filter
281
+
282
+ $upload_dir = wp_upload_dir();
283
+ $upload_base = trailingslashit( $upload_dir['basedir'] );
284
+ $tmp_base = trailingslashit( apply_filters( 'wpo_wcpdf_tmp_path', $upload_base . 'wpo_wcpdf/' ) );
285
+ return $tmp_base;
286
+ }
287
+
288
+ /**
289
+ * Install/create plugin tmp folders
290
+ */
291
+ public function init_tmp ( $tmp_base ) {
292
+ // create plugin base temp folder
293
+ @mkdir( $tmp_base );
294
+
295
+ // create subfolders & protect
296
+ $subfolders = array( 'attachments', 'fonts', 'dompdf' );
297
+ foreach ( $subfolders as $subfolder ) {
298
+ $path = $tmp_base . $subfolder . '/';
299
+ @mkdir( $path );
300
+
301
+ // copy font files
302
+ if ( $subfolder == 'fonts' ) {
303
+ $this->copy_fonts( $path, false );
304
+ }
305
+
306
+ // create .htaccess file and empty index.php to protect in case an open webfolder is used!
307
+ @file_put_contents( $path . '.htaccess', 'deny from all' );
308
+ @touch( $path . 'index.php' );
309
+ }
310
+
311
+ }
312
+
313
+ /**
314
+ * Copy DOMPDF fonts to wordpress tmp folder
315
+ */
316
+ public function copy_fonts ( $path, $merge_with_local = true ) {
317
+ $path = trailingslashit( $path );
318
+ $dompdf_font_dir = WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/";
319
+
320
+ // get local font dir from filtered options
321
+ $dompdf_options = apply_filters( 'wpo_wcpdf_dompdf_options', array(
322
+ 'defaultFont' => 'dejavu sans',
323
+ 'tempDir' => $this->get_tmp_path('dompdf'),
324
+ 'logOutputFile' => $this->get_tmp_path('dompdf') . "/log.htm",
325
+ 'fontDir' => $this->get_tmp_path('fonts'),
326
+ 'fontCache' => $this->get_tmp_path('fonts'),
327
+ 'isRemoteEnabled' => true,
328
+ 'isFontSubsettingEnabled' => true,
329
+ 'isHtml5ParserEnabled' => true,
330
+ ) );
331
+ $fontDir = $dompdf_options['fontDir'];
332
+
333
+ // merge font family cache with local/custom if present
334
+ $font_cache_files = array(
335
+ 'cache' => 'dompdf_font_family_cache.php',
336
+ 'cache_dist' => 'dompdf_font_family_cache.dist.php',
337
+ );
338
+ foreach ( $font_cache_files as $font_cache_name => $font_cache_filename ) {
339
+ $plugin_fonts = @require $dompdf_font_dir . $font_cache_filename;
340
+ if ( $merge_with_local && is_readable( $path . $font_cache_filename ) ) {
341
+ $local_fonts = @require $path . $font_cache_filename;
342
+ if (is_array($local_fonts) && is_array($plugin_fonts)) {
343
+ // merge local & plugin fonts, plugin fonts overwrite (update) local fonts
344
+ // while custom local fonts are retained
345
+ $local_fonts = array_merge($local_fonts, $plugin_fonts);
346
+ // create readable array with $fontDir in place of the actual folder for portability
347
+ $fonts_export = var_export($local_fonts,true);
348
+ $fonts_export = str_replace('\'' . $fontDir , '$fontDir . \'', $fonts_export);
349
+ $cacheData = sprintf("<?php return %s;%s?>", $fonts_export, PHP_EOL );
350
+ // write file with merged cache data
351
+ file_put_contents($path . $font_cache_filename, $cacheData);
352
+ } else { // empty local file
353
+ copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
354
+ }
355
+ } else {
356
+ // we couldn't read the local font cache file so we're simply copying over plugin cache file
357
+ copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
358
+ }
359
+ }
360
+
361
+ // first try the easy way with glob!
362
+ if ( function_exists('glob') ) {
363
+ $files = glob($dompdf_font_dir."*.*");
364
+ foreach($files as $file){
365
+ $filename = basename($file);
366
+ if( !is_dir($file) && is_readable($file) && !in_array($filename, $font_cache_files)) {
367
+ $dest = $path . $filename;
368
+ copy($file, $dest);
369
+ }
370
+ }
371
+ } else {
372
+ // fallback method using font cache file (glob is disabled on some servers with disable_functions)
373
+ $extensions = array( '.ttf', '.ufm', '.ufm.php', '.afm', '.afm.php' );
374
+ $fontDir = untrailingslashit($dompdf_font_dir);
375
+ $plugin_fonts = @require $dompdf_font_dir . $font_cache_files['cache'];
376
+
377
+ foreach ($plugin_fonts as $font_family => $filenames) {
378
+ foreach ($filenames as $filename) {
379
+ foreach ($extensions as $extension) {
380
+ $file = $filename.$extension;
381
+ if (file_exists($file)) {
382
+ $dest = $path . basename($file);
383
+ copy($file, $dest);
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ public function disable_free_attachment( $attach, $order, $email_id, $document_type ) {
392
+ // prevent fatal error for non-order objects
393
+ if ( !method_exists( $order, 'get_total' ) ) {
394
+ return false;
395
+ }
396
+
397
+ $document_settings = WPO_WCPDF()->settings->get_document_settings( $document_type );
398
+ // echo '<pre>';var_dump($document_type);echo '</pre>';
399
+ // error_log( var_export($document_settings,true) );
400
+
401
+ // check order total & setting
402
+ $order_total = $order->get_total();
403
+ if ( $order_total == 0 && isset( $document_settings['disable_free'] ) ) {
404
+ return false;
405
+ }
406
+
407
+ return $attach;
408
+ }
409
+
410
+ /**
411
+ * Adds spans around placeholders to be able to make replacement (page count) and css (page number)
412
+ */
413
+ public function format_page_number_placeholders ( $html, $document ) {
414
+ $html = str_replace('{{PAGE_COUNT}}', '<span class="pagecount">^C^</span>', $html);
415
+ $html = str_replace('{{PAGE_NUM}}', '<span class="pagenum"></span>', $html );
416
+ return $html;
417
+ }
418
+
419
+ /**
420
+ * Replace {{PAGE_COUNT}} placeholder with total page count
421
+ */
422
+ public function page_number_replacements ( $dompdf, $html ) {
423
+ $placeholder = '^C^';
424
+ // create placeholder version with ASCII 0 spaces (dompdf 0.8)
425
+ $placeholder_0 = '';
426
+ $placeholder_chars = str_split($placeholder);
427
+ foreach ($placeholder_chars as $placeholder_char) {
428
+ $placeholder_0 .= chr(0).$placeholder_char;
429
+ }
430
+
431
+ // check if placeholder is used
432
+ if (strpos($html, $placeholder) !== false ) {
433
+ foreach ($dompdf->get_canvas()->get_cpdf()->objects as &$object) {
434
+ if (array_key_exists("c", $object) && strpos($object["c"], $placeholder) !== false ) {
435
+ $object["c"] = str_replace( array($placeholder,$placeholder_0) , $dompdf->get_canvas()->get_page_count() , $object["c"] );
436
+ } elseif (array_key_exists("c", $object) && strpos($object["c"], $placeholder_0) !== false ) {
437
+ $object["c"] = str_replace( array($placeholder,$placeholder_0) , chr(0).$dompdf->get_canvas()->get_page_count() , $object["c"] );
438
+ }
439
+ }
440
+ }
441
+
442
+ return $dompdf;
443
+ }
444
+
445
+ /**
446
+ * Use currency symbol font (when enabled in options)
447
+ */
448
+ public function use_currency_font ( $document_type, $document ) {
449
+ add_filter( 'woocommerce_currency_symbol', array( $this, 'wrap_currency_symbol' ), 999, 2);
450
+ add_action( 'wpo_wcpdf_custom_styles', array($this, 'currency_symbol_font_styles' ) );
451
+ }
452
+
453
+ public function wrap_currency_symbol( $currency_symbol, $currency ) {
454
+ $currency_symbol = sprintf( '<span class="wcpdf-currency-symbol">%s</span>', $currency_symbol );
455
+ return $currency_symbol;
456
+ }
457
+
458
+ public function currency_symbol_font_styles () {
459
+ ?>
460
+ .wcpdf-currency-symbol { font-family: 'Currencies'; }
461
+ <?php
462
+ }
463
+
464
+ /**
465
+ * Remove attachments older than 1 week (daily, hooked into wp_scheduled_delete )
466
+ */
467
+ public function attachments_cleanup() {
468
+ if ( !function_exists("glob") || !function_exists('filemtime')) {
469
+ // glob is disabled
470
+ return;
471
+ }
472
+
473
+ $delete_timestamp = time() - ( DAY_IN_SECONDS * 7 );
474
+
475
+ $tmp_path = $this->get_tmp_path('attachments');
476
+
477
+ if ( $files = glob( $tmp_path.'*.pdf' ) ) { // get all pdf files
478
+ foreach( $files as $file ) {
479
+ if( is_file( $file ) ) {
480
+ $file_timestamp = filemtime( $file );
481
+ if ( !empty( $file_timestamp ) && $file_timestamp < $delete_timestamp ) {
482
+ @unlink($file);
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ }
489
+
490
+ /**
491
+ * Enable PHP error output
492
+ */
493
+ public function enable_debug () {
494
+ error_reporting( E_ALL );
495
+ ini_set( 'display_errors', 1 );
496
+ }
497
+
498
+ }
499
+
500
+ endif; // class_exists
501
+
502
+ return new Main();
includes/class-wcpdf-pdf-maker.php CHANGED
@@ -1,60 +1,60 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use Dompdf\Dompdf;
5
- use Dompdf\Options;
6
-
7
- if ( ! defined( 'ABSPATH' ) ) {
8
- exit; // Exit if accessed directly
9
- }
10
-
11
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\PDF_Maker' ) ) :
12
-
13
- class PDF_Maker {
14
- public $html;
15
- public $settings;
16
-
17
- public function __construct( $html, $settings = array() ) {
18
- $this->html = $html;
19
-
20
- $default_settings = array(
21
- 'paper_size' => 'A4',
22
- 'paper_orientation' => 'portrait',
23
- 'font_subsetting' => false,
24
- );
25
- $this->settings = $settings + $default_settings;
26
- }
27
-
28
- public function output() {
29
- if ( empty( $this->html ) ) {
30
- return;
31
- }
32
-
33
- require WPO_WCPDF()->plugin_path() . '/vendor/autoload.php';
34
-
35
- // set options
36
- $options = new Options( apply_filters( 'wpo_wcpdf_dompdf_options', array(
37
- 'defaultFont' => 'dejavu sans',
38
- 'tempDir' => WPO_WCPDF()->main->get_tmp_path('dompdf'),
39
- 'logOutputFile' => WPO_WCPDF()->main->get_tmp_path('dompdf') . "/log.htm",
40
- 'fontDir' => WPO_WCPDF()->main->get_tmp_path('fonts'),
41
- 'fontCache' => WPO_WCPDF()->main->get_tmp_path('fonts'),
42
- 'isRemoteEnabled' => true,
43
- 'isFontSubsettingEnabled' => $this->settings['font_subsetting'],
44
- // HTML5 parser requires iconv
45
- 'isHtml5ParserEnabled' => ( isset(WPO_WCPDF()->settings->debug_settings['use_html5_parser']) && extension_loaded('iconv') ) ? true : false,
46
- ) ) );
47
-
48
- // instantiate and use the dompdf class
49
- $dompdf = new Dompdf( $options );
50
- $dompdf->loadHtml( $this->html );
51
- $dompdf->setPaper( $this->settings['paper_size'], $this->settings['paper_orientation'] );
52
- $dompdf = apply_filters( 'wpo_wcpdf_before_dompdf_render', $dompdf, $this->html );
53
- $dompdf->render();
54
- $dompdf = apply_filters( 'wpo_wcpdf_after_dompdf_render', $dompdf, $this->html );
55
-
56
- return $dompdf->output();
57
- }
58
- }
59
-
60
- endif; // class_exists
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use Dompdf\Dompdf;
5
+ use Dompdf\Options;
6
+
7
+ if ( ! defined( 'ABSPATH' ) ) {
8
+ exit; // Exit if accessed directly
9
+ }
10
+
11
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\PDF_Maker' ) ) :
12
+
13
+ class PDF_Maker {
14
+ public $html;
15
+ public $settings;
16
+
17
+ public function __construct( $html, $settings = array() ) {
18
+ $this->html = $html;
19
+
20
+ $default_settings = array(
21
+ 'paper_size' => 'A4',
22
+ 'paper_orientation' => 'portrait',
23
+ 'font_subsetting' => false,
24
+ );
25
+ $this->settings = $settings + $default_settings;
26
+ }
27
+
28
+ public function output() {
29
+ if ( empty( $this->html ) ) {
30
+ return;
31
+ }
32
+
33
+ require WPO_WCPDF()->plugin_path() . '/vendor/autoload.php';
34
+
35
+ // set options
36
+ $options = new Options( apply_filters( 'wpo_wcpdf_dompdf_options', array(
37
+ 'defaultFont' => 'dejavu sans',
38
+ 'tempDir' => WPO_WCPDF()->main->get_tmp_path('dompdf'),
39
+ 'logOutputFile' => WPO_WCPDF()->main->get_tmp_path('dompdf') . "/log.htm",
40
+ 'fontDir' => WPO_WCPDF()->main->get_tmp_path('fonts'),
41
+ 'fontCache' => WPO_WCPDF()->main->get_tmp_path('fonts'),
42
+ 'isRemoteEnabled' => true,
43
+ 'isFontSubsettingEnabled' => $this->settings['font_subsetting'],
44
+ // HTML5 parser requires iconv
45
+ 'isHtml5ParserEnabled' => ( isset(WPO_WCPDF()->settings->debug_settings['use_html5_parser']) && extension_loaded('iconv') ) ? true : false,
46
+ ) ) );
47
+
48
+ // instantiate and use the dompdf class
49
+ $dompdf = new Dompdf( $options );
50
+ $dompdf->loadHtml( $this->html );
51
+ $dompdf->setPaper( $this->settings['paper_size'], $this->settings['paper_orientation'] );
52
+ $dompdf = apply_filters( 'wpo_wcpdf_before_dompdf_render', $dompdf, $this->html );
53
+ $dompdf->render();
54
+ $dompdf = apply_filters( 'wpo_wcpdf_after_dompdf_render', $dompdf, $this->html );
55
+
56
+ return $dompdf->output();
57
+ }
58
+ }
59
+
60
+ endif; // class_exists
includes/class-wcpdf-settings-callbacks.php CHANGED
@@ -1,503 +1,503 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store;
5
-
6
- if ( ! defined( 'ABSPATH' ) ) {
7
- exit; // Exit if accessed directly
8
- }
9
-
10
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_Callbacks' ) ) :
11
-
12
- class Settings_Callbacks {
13
- /**
14
- * Section null callback.
15
- *
16
- * @return void.
17
- */
18
- public function section() {
19
- }
20
-
21
- /**
22
- * Debug section callback.
23
- *
24
- * @return void.
25
- */
26
- public function debug_section() {
27
- _e( '<b>Warning!</b> The settings below are meant for debugging/development only. Do not use them on a live website!' , 'woocommerce-pdf-invoices-packing-slips' );
28
- }
29
-
30
- /**
31
- * Custom fields section callback.
32
- *
33
- * @return void.
34
- */
35
- public function custom_fields_section() {
36
- _e( 'These are used for the (optional) footer columns in the <em>Modern (Premium)</em> template, but can also be used for other elements in your custom template' , 'woocommerce-pdf-invoices-packing-slips' );
37
- }
38
-
39
- /**
40
- * Checkbox callback.
41
- *
42
- * args:
43
- * option_name - name of the main option
44
- * id - key of the setting
45
- * value - value if not 1 (optional)
46
- * default - default setting (optional)
47
- * description - description (optional)
48
- *
49
- * @return void.
50
- */
51
- public function checkbox( $args ) {
52
- extract( $this->normalize_settings_args( $args ) );
53
-
54
- // output checkbox
55
- printf( '<input type="checkbox" id="%1$s" name="%2$s" value="%3$s"%4$s />', $id, $setting_name, $value, checked( $value, $current, false ) );
56
-
57
- // output description.
58
- if ( isset( $description ) ) {
59
- printf( '<p class="description">%s</p>', $description );
60
- }
61
- }
62
-
63
- /**
64
- * Text input callback.
65
- *
66
- * args:
67
- * option_name - name of the main option
68
- * id - key of the setting
69
- * size - size of the text input (em)
70
- * default - default setting (optional)
71
- * description - description (optional)
72
- * type - type (optional)
73
- *
74
- * @return void.
75
- */
76
- public function text_input( $args ) {
77
- extract( $this->normalize_settings_args( $args ) );
78
-
79
- if (empty($type)) {
80
- $type = 'text';
81
- }
82
-
83
- printf( '<input type="%1$s" id="%2$s" name="%3$s" value="%4$s" size="%5$s" placeholder="%6$s"/>', $type, $id, $setting_name, esc_attr( $current ), $size, $placeholder );
84
-
85
- // output description.
86
- if ( isset( $description ) ) {
87
- printf( '<p class="description">%s</p>', $description );
88
- }
89
- }
90
-
91
- // Single text option (not part of any settings array)
92
- public function singular_text_element( $args ) {
93
- $option_name = $args['option_name'];
94
- $id = $args['id'];
95
- $size = isset( $args['size'] ) ? $args['size'] : '25';
96
- $class = isset( $args['translatable'] ) && $args['translatable'] === true ? 'translatable' : '';
97
-
98
- $option = get_option( $option_name );
99
-
100
- if ( isset( $option ) ) {
101
- $current = $option;
102
- } else {
103
- $current = isset( $args['default'] ) ? $args['default'] : '';
104
- }
105
-
106
- $html = sprintf( '<input type="text" id="%1$s" name="%2$s" value="%3$s" size="%4$s" class="%5$s"/>', $id, $option_name, $current, $size, $class );
107
-
108
- // Displays option description.
109
- if ( isset( $args['description'] ) ) {
110
- $html .= sprintf( '<p class="description">%s</p>', $args['description'] );
111
- }
112
-
113
- echo $html;
114
- }
115
-
116
-
117
- /**
118
- * Textarea callback.
119
- *
120
- * args:
121
- * option_name - name of the main option
122
- * id - key of the setting
123
- * width - width of the text input (em)
124
- * height - height of the text input (lines)
125
- * default - default setting (optional)
126
- * description - description (optional)
127
- *
128
- * @return void.
129
- */
130
- public function textarea( $args ) {
131
- extract( $this->normalize_settings_args( $args ) );
132
-
133
- printf( '<textarea id="%1$s" name="%2$s" cols="%4$s" rows="%5$s" placeholder="%6$s"/>%3$s</textarea>', $id, $setting_name, esc_textarea( $current ), $width, $height, $placeholder );
134
-
135
- // output description.
136
- if ( isset( $description ) ) {
137
- printf( '<p class="description">%s</p>', $description );
138
- }
139
- }
140
-
141
- /**
142
- * Select element callback.
143
- *
144
- * @param array $args Field arguments.
145
- *
146
- * @return string Select field.
147
- */
148
- public function select( $args ) {
149
- extract( $this->normalize_settings_args( $args ) );
150
-
151
- printf( '<select id="%1$s" name="%2$s">', $id, $setting_name );
152
-
153
- foreach ( $options as $key => $label ) {
154
- printf( '<option value="%s"%s>%s</option>', $key, selected( $current, $key, false ), $label );
155
- }
156
-
157
- echo '</select>';
158
-
159
- if (isset($custom)) {
160
- printf( '<div class="%1$s_custom custom">', $id );
161
-
162
- if (is_callable( array( $this, $custom['type'] ) ) ) {
163
- $this->{$custom['type']}( $custom['args'] );
164
- }
165
- echo '</div>';
166
- ?>
167
- <script type="text/javascript">
168
- jQuery(document).ready(function($) {
169
- function check_<?php echo $id; ?>_custom() {
170
- var custom = $('#<?php echo $id; ?>').val();
171
- if (custom == 'custom') {
172
- $( '.<?php echo $id; ?>_custom').show();
173
- } else {
174
- $( '.<?php echo $id; ?>_custom').hide();
175
- }
176
- }
177
-
178
- check_<?php echo $id; ?>_custom();
179
-
180
- $( '#<?php echo $id; ?>' ).change(function() {
181
- check_<?php echo $id; ?>_custom();
182
- });
183
-
184
- });
185
- </script>
186
- <?php
187
- }
188
-
189
- // Displays option description.
190
- if ( isset( $args['description'] ) ) {
191
- printf( '<p class="description">%s</p>', $args['description'] );
192
- }
193
-
194
- }
195
-
196
- public function radio_button( $args ) {
197
- extract( $this->normalize_settings_args( $args ) );
198
-
199
- foreach ( $options as $key => $label ) {
200
- printf( '<input type="radio" class="radio" id="%1$s[%3$s]" name="%2$s" value="%3$s"%4$s />', $id, $setting_name, $key, checked( $current, $key, false ) );
201
- printf( '<label for="%1$s[%3$s]"> %4$s</label><br>', $id, $setting_name, $key, $label);
202
- }
203
-
204
-
205
- // Displays option description.
206
- if ( isset( $args['description'] ) ) {
207
- printf( '<p class="description">%s</p>', $args['description'] );
208
- }
209
-
210
- }
211
-
212
- /**
213
- * Multiple text element callback.
214
- * @param array $args Field arguments.
215
- * @return string Text input field.
216
- */
217
- public function multiple_text_input( $args ) {
218
- extract( $this->normalize_settings_args( $args ) );
219
-
220
- if (!empty($header)) {
221
- echo "<p><strong>{$header}</strong>:</p>";
222
- }
223
-
224
- printf('<p class="%s multiple-text-input">', $id);
225
- foreach ($fields as $name => $field) {
226
- $size = $field['size'];
227
- $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
228
-
229
- if (isset($field['label_width'])) {
230
- $style = sprintf( 'style="display:inline-block; width:%1$s;"', $field['label_width'] );
231
- } else {
232
- $style = '';
233
- }
234
-
235
- $description = isset( $field['description'] ) ? '<span style="font-style:italic;">'.$field['description'].'</span>' : '';
236
-
237
- // output field label
238
- if (isset($field['label'])) {
239
- printf( '<label for="%1$s_%2$s" %3$s>%4$s:</label>', $id, $name, $style, $field['label'] );
240
- }
241
-
242
- // output field
243
- $field_current = isset($current[$name]) ? $current[$name] : '';
244
- $type = isset( $field['type'] ) ? $field['type'] : 'text';
245
- printf( '<input type="%1$s" id="%2$s_%4$s" name="%3$s[%4$s]" value="%5$s" size="%6$s" placeholder="%7$s"/> %8$s<br/>', $type, $id, $setting_name, $name, esc_attr( $field_current ), $size, $placeholder, $description );
246
- }
247
- echo "</p>";
248
-
249
- // Displays option description.
250
- if ( isset( $args['description'] ) ) {
251
- printf( '<p class="description">%s</p>', $args['description'] );
252
- }
253
- }
254
-
255
- /**
256
- * Multiple text element callback.
257
- * @param array $args Field arguments.
258
- * @return string Text input field.
259
- */
260
- public function multiple_checkboxes( $args ) {
261
- extract( $this->normalize_settings_args( $args ) );
262
-
263
- foreach ($fields as $name => $label) {
264
- // $label = $field['label'];
265
-
266
- // output checkbox
267
- $field_current = isset($current[$name]) ? $current[$name] : '';
268
- printf( '<input type="checkbox" id="%1$s_%3$s" name="%2$s[%3$s]" value="%4$s"%5$s />', $id, $setting_name, $name, $value, checked( $value, $field_current, false ) );
269
-
270
- // output field label
271
- printf( '<label for="%1$s_%2$s">%3$s</label><br>', $id, $name, $label );
272
-
273
- }
274
-
275
- // Displays option description.
276
- if ( isset( $args['description'] ) ) {
277
- printf( '<p class="description">%s</p>', $args['description'] );
278
- }
279
- }
280
-
281
- /**
282
- * Media upload callback.
283
- *
284
- * @param array $args Field arguments.
285
- *
286
- * @return string Media upload button & preview.
287
- */
288
- public function media_upload( $args ) {
289
- extract( $this->normalize_settings_args( $args ) );
290
-
291
- if( !empty($current) ) {
292
- $attachment = wp_get_attachment_image_src( $current, 'full', false );
293
-
294
- $attachment_src = $attachment[0];
295
- $attachment_width = $attachment[1];
296
- $attachment_height = $attachment[2];
297
- $attachment_resolution = round($attachment_height/(3/2.54));
298
-
299
- printf('<img src="%1$s" style="display:block" id="img-%4$s"/>', $attachment_src, $attachment_width, $attachment_height, $id );
300
- printf('<div class="attachment-resolution"><p class="description">%s: %sdpi (default height = 3cm)</p></div>', __('Image resolution'), $attachment_resolution );
301
- printf('<span class="button wpo_remove_image_button" data-input_id="%1$s">%2$s</span>', $id, $remove_button_text );
302
- }
303
-
304
- printf( '<input id="%1$s" name="%2$s" type="hidden" value="%3$s" />', $id, $setting_name, $current );
305
-
306
- printf( '<span class="button wpo_upload_image_button %4$s" data-uploader_title="%1$s" data-uploader_button_text="%2$s" data-remove_button_text="%3$s" data-input_id="%4$s">%2$s</span>', $uploader_title, $uploader_button_text, $remove_button_text, $id );
307
-
308
- // Displays option description.
309
- if ( isset( $description ) ) {
310
- printf( '<p class="description">%s</p>', $description );
311
- }
312
- }
313
-
314
- /**
315
- * Next document number edit callback.
316
- *
317
- * @param array $args Field arguments.
318
- */
319
- public function next_number_edit( $args ) {
320
- extract( $args );
321
- $number_store_method = WPO_WCPDF()->settings->get_sequential_number_store_method();
322
- $number_store = new Sequential_Number_Store( $store, $number_store_method );
323
- $next_number = $number_store->get_next();
324
- $nonce = wp_create_nonce( "wpo_wcpdf_next_{$store}" );
325
- printf( '<input id="next_%1$s" class="next-number-input" type="text" size="%2$s" value="%3$s" disabled="disabled" data-store="%1$s" data-nonce="%4$s"/> <span class="edit-next-number dashicons dashicons-edit"></span><span class="save-next-number button secondary" style="display:none;">%5$s</span>', $store, $size, $next_number, $nonce, __( 'Save', 'woocommerce-pdf-invoices-packing-slips' ) );
326
- // Displays option description.
327
- if ( isset( $description ) ) {
328
- printf( '<p class="description">%s</p>', $description );
329
- }
330
- }
331
-
332
- /**
333
- * Wrapper function to create tabs for settings in different languages
334
- * @param [type] $args [description]
335
- * @param [type] $callback [description]
336
- * @return [type] [description]
337
- */
338
- public function i18n_wrap ( $args ) {
339
- extract( $this->normalize_settings_args( $args ) );
340
-
341
- if ( $languages = $this->get_languages() ) {
342
- printf( '<div id="%s-%s-translations" class="translations">', $option_name, $id)
343
- ?>
344
- <ul>
345
- <?php foreach ( $languages as $lang_code => $language_name ) {
346
- $translation_id = "{$option_name}_{$id}_{$lang_code}";
347
- printf('<li><a href="#%s">%s</a></li>', $translation_id, $language_name );
348
- }
349
- ?>
350
- </ul>
351
- <?php foreach ( $languages as $lang_code => $language_name ) {
352
- $translation_id = "{$option_name}_{$id}_{$lang_code}";
353
- printf( '<div id="%s">', $translation_id );
354
- $args['lang'] = $lang_code;
355
- // don't use internationalized placeholders since they're not translated,
356
- // to avoid confusion (user thinking they're all the same)
357
- if ( $callback == 'multiple_text_input' ) {
358
- foreach ($fields as $key => $field_args) {
359
- if (!empty($field_args['placeholder']) && isset($field_args['i18n_placeholder'])) {
360
- $args['fields'][$key]['placeholder'] = '';
361
- }
362
- }
363
- } else {
364
- if (!empty($args['placeholder']) && isset($args['i18n_placeholder'])) {
365
- $args['placeholder'] = '';
366
- }
367
- }
368
- // specific description for internationalized fields (to compensate for missing placeholder)
369
- if (!empty($args['i18n_description'])) {
370
- $args['description'] = $args['i18n_description'];
371
- }
372
- call_user_func( array( $this, $callback ), $args );
373
- echo '</div>';
374
- }
375
- ?>
376
-
377
- </div>
378
- <?php
379
- } else {
380
- $args['lang'] = 'default';
381
- call_user_func( array( $this, $callback ), $args );
382
- }
383
- }
384
-
385
- public function get_languages () {
386
- $multilingual = function_exists('icl_get_languages');
387
- // $multilingual = true; // for development
388
-
389
- if ($multilingual) {
390
- // use this instead of function call for development outside of WPML
391
- // $icl_get_languages = 'a:3:{s:2:"en";a:8:{s:2:"id";s:1:"1";s:6:"active";s:1:"1";s:11:"native_name";s:7:"English";s:7:"missing";s:1:"0";s:15:"translated_name";s:7:"English";s:13:"language_code";s:2:"en";s:16:"country_flag_url";s:43:"http://yourdomain/wpmlpath/res/flags/en.png";s:3:"url";s:23:"http://yourdomain/about";}s:2:"fr";a:8:{s:2:"id";s:1:"4";s:6:"active";s:1:"0";s:11:"native_name";s:9:"Français";s:7:"missing";s:1:"0";s:15:"translated_name";s:6:"French";s:13:"language_code";s:2:"fr";s:16:"country_flag_url";s:43:"http://yourdomain/wpmlpath/res/flags/fr.png";s:3:"url";s:29:"http://yourdomain/fr/a-propos";}s:2:"it";a:8:{s:2:"id";s:2:"27";s:6:"active";s:1:"0";s:11:"native_name";s:8:"Italiano";s:7:"missing";s:1:"0";s:15:"translated_name";s:7:"Italian";s:13:"language_code";s:2:"it";s:16:"country_flag_url";s:43:"http://yourdomain/wpmlpath/res/flags/it.png";s:3:"url";s:26:"http://yourdomain/it/circa";}}';
392
- // $icl_get_languages = unserialize($icl_get_languages);
393
-
394
- $icl_get_languages = icl_get_languages('skip_missing=0');
395
- $languages = array();
396
- foreach ($icl_get_languages as $lang => $data) {
397
- $languages[$data['language_code']] = $data['native_name'];
398
- }
399
- } else {
400
- return false;
401
- }
402
-
403
- return $languages;
404
- }
405
-
406
- public function normalize_settings_args ( $args ) {
407
- $args['value'] = isset( $args['value'] ) ? $args['value'] : 1;
408
-
409
- $args['placeholder'] = isset( $args['placeholder'] ) ? $args['placeholder'] : '';
410
-
411
- // get main settings array
412
- $option = get_option( $args['option_name'] );
413
-
414
- $args['setting_name'] = "{$args['option_name']}[{$args['id']}]";
415
-
416
- if ( !isset($args['lang']) && !empty($args['translatable']) ) {
417
- $args['lang'] = 'default';
418
- }
419
-
420
- if (isset($args['lang'])) {
421
- // i18n settings name
422
- $args['setting_name'] = "{$args['setting_name']}[{$args['lang']}]";
423
- // copy current option value if set
424
-
425
- if ( $args['lang'] == 'default' && !empty($option[$args['id']]) && !isset( $option[$args['id']]['default'] ) ) {
426
- // we're switching back from WPML to normal
427
- // try english first
428
- if ( isset( $option[$args['id']]['en'] ) ) {
429
- $args['current'] = $option[$args['id']]['en'];
430
- } elseif ( is_array( $option[$args['id']] ) ) {
431
- // fallback to the first language if english not found
432
- $first = array_shift($option[$args['id']]);
433
- if (!empty($first)) {
434
- $args['current'] = $first;
435
- }
436
- } elseif ( is_string( $option[$args['id']] ) ) {
437
- $args['current'] = $option[$args['id']];
438
- } else {
439
- // nothing, really?
440
- $args['current'] = '';
441
- }
442
- } else {
443
- if ( isset( $option[$args['id']][$args['lang']] ) ) {
444
- $args['current'] = $option[$args['id']][$args['lang']];
445
- } elseif (isset( $option[$args['id']]['default'] )) {
446
- $args['current'] = $option[$args['id']]['default'];
447
- }
448
- }
449
- } else {
450
- // copy current option value if set
451
- if ( isset( $option[$args['id']] ) ) {
452
- $args['current'] = $option[$args['id']];
453
- }
454
- }
455
-
456
- // falback to default or empty if no value in option
457
- if ( !isset($args['current']) ) {
458
- $args['current'] = isset( $args['default'] ) ? $args['default'] : '';
459
- }
460
-
461
- return $args;
462
- }
463
-
464
- /**
465
- * Validate options.
466
- *
467
- * @param array $input options to valid.
468
- *
469
- * @return array validated options.
470
- */
471
- public function validate( $input ) {
472
- // echo '<pre>';var_dump($input);die('</pre>');
473
- // Create our array for storing the validated options.
474
- $output = array();
475
-
476
- if (empty($input) || !is_array($input)) {
477
- return $input;
478
- }
479
-
480
- // Loop through each of the incoming options.
481
- foreach ( $input as $key => $value ) {
482
-
483
- // Check to see if the current option has a value. If so, process it.
484
- if ( isset( $input[$key] ) ) {
485
- if ( is_array( $input[$key] ) ) {
486
- foreach ( $input[$key] as $sub_key => $sub_value ) {
487
- $output[$key][$sub_key] = $input[$key][$sub_key];
488
- }
489
- } else {
490
- $output[$key] = $input[$key];
491
- }
492
- }
493
- }
494
-
495
- // Return the array processing any additional functions filtered by this action.
496
- return apply_filters( 'wpo_wcpdf_validate_input', $output, $input );
497
- }
498
- }
499
-
500
-
501
- endif; // class_exists
502
-
503
  return new Settings_Callbacks();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store;
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit; // Exit if accessed directly
8
+ }
9
+
10
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_Callbacks' ) ) :
11
+
12
+ class Settings_Callbacks {
13
+ /**
14
+ * Section null callback.
15
+ *
16
+ * @return void.
17
+ */
18
+ public function section() {
19
+ }
20
+
21
+ /**
22
+ * Debug section callback.
23
+ *
24
+ * @return void.
25
+ */
26
+ public function debug_section() {
27
+ _e( '<b>Warning!</b> The settings below are meant for debugging/development only. Do not use them on a live website!' , 'woocommerce-pdf-invoices-packing-slips' );
28
+ }
29
+
30
+ /**
31
+ * Custom fields section callback.
32
+ *
33
+ * @return void.
34
+ */
35
+ public function custom_fields_section() {
36
+ _e( 'These are used for the (optional) footer columns in the <em>Modern (Premium)</em> template, but can also be used for other elements in your custom template' , 'woocommerce-pdf-invoices-packing-slips' );
37
+ }
38
+
39
+ /**
40
+ * Checkbox callback.
41
+ *
42
+ * args:
43
+ * option_name - name of the main option
44
+ * id - key of the setting
45
+ * value - value if not 1 (optional)
46
+ * default - default setting (optional)
47
+ * description - description (optional)
48
+ *
49
+ * @return void.
50
+ */
51
+ public function checkbox( $args ) {
52
+ extract( $this->normalize_settings_args( $args ) );
53
+
54
+ // output checkbox
55
+ printf( '<input type="checkbox" id="%1$s" name="%2$s" value="%3$s"%4$s />', $id, $setting_name, $value, checked( $value, $current, false ) );
56
+
57
+ // output description.
58
+ if ( isset( $description ) ) {
59
+ printf( '<p class="description">%s</p>', $description );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Text input callback.
65
+ *
66
+ * args:
67
+ * option_name - name of the main option
68
+ * id - key of the setting
69
+ * size - size of the text input (em)
70
+ * default - default setting (optional)
71
+ * description - description (optional)
72
+ * type - type (optional)
73
+ *
74
+ * @return void.
75
+ */
76
+ public function text_input( $args ) {
77
+ extract( $this->normalize_settings_args( $args ) );
78
+
79
+ if (empty($type)) {
80
+ $type = 'text';
81
+ }
82
+
83
+ printf( '<input type="%1$s" id="%2$s" name="%3$s" value="%4$s" size="%5$s" placeholder="%6$s"/>', $type, $id, $setting_name, esc_attr( $current ), $size, $placeholder );
84
+
85
+ // output description.
86
+ if ( isset( $description ) ) {
87
+ printf( '<p class="description">%s</p>', $description );
88
+ }
89
+ }
90
+
91
+ // Single text option (not part of any settings array)
92
+ public function singular_text_element( $args ) {
93
+ $option_name = $args['option_name'];
94
+ $id = $args['id'];
95
+ $size = isset( $args['size'] ) ? $args['size'] : '25';
96
+ $class = isset( $args['translatable'] ) && $args['translatable'] === true ? 'translatable' : '';
97
+
98
+ $option = get_option( $option_name );
99
+
100
+ if ( isset( $option ) ) {
101
+ $current = $option;
102
+ } else {
103
+ $current = isset( $args['default'] ) ? $args['default'] : '';
104
+ }
105
+
106
+ $html = sprintf( '<input type="text" id="%1$s" name="%2$s" value="%3$s" size="%4$s" class="%5$s"/>', $id, $option_name, $current, $size, $class );
107
+
108
+ // Displays option description.
109
+ if ( isset( $args['description'] ) ) {
110
+ $html .= sprintf( '<p class="description">%s</p>', $args['description'] );
111
+ }
112
+
113
+ echo $html;
114
+ }
115
+
116
+
117
+ /**
118
+ * Textarea callback.
119
+ *
120
+ * args:
121
+ * option_name - name of the main option
122
+ * id - key of the setting
123
+ * width - width of the text input (em)
124
+ * height - height of the text input (lines)
125
+ * default - default setting (optional)
126
+ * description - description (optional)
127
+ *
128
+ * @return void.
129
+ */
130
+ public function textarea( $args ) {
131
+ extract( $this->normalize_settings_args( $args ) );
132
+
133
+ printf( '<textarea id="%1$s" name="%2$s" cols="%4$s" rows="%5$s" placeholder="%6$s"/>%3$s</textarea>', $id, $setting_name, esc_textarea( $current ), $width, $height, $placeholder );
134
+
135
+ // output description.
136
+ if ( isset( $description ) ) {
137
+ printf( '<p class="description">%s</p>', $description );
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Select element callback.
143
+ *
144
+ * @param array $args Field arguments.
145
+ *
146
+ * @return string Select field.
147
+ */
148
+ public function select( $args ) {
149
+ extract( $this->normalize_settings_args( $args ) );
150
+
151
+ printf( '<select id="%1$s" name="%2$s">', $id, $setting_name );
152
+
153
+ foreach ( $options as $key => $label ) {
154
+ printf( '<option value="%s"%s>%s</option>', $key, selected( $current, $key, false ), $label );
155
+ }
156
+
157
+ echo '</select>';
158
+
159
+ if (isset($custom)) {
160
+ printf( '<div class="%1$s_custom custom">', $id );
161
+
162
+ if (is_callable( array( $this, $custom['type'] ) ) ) {
163
+ $this->{$custom['type']}( $custom['args'] );
164
+ }
165
+ echo '</div>';
166
+ ?>
167
+ <script type="text/javascript">
168
+ jQuery(document).ready(function($) {
169
+ function check_<?php echo $id; ?>_custom() {
170
+ var custom = $('#<?php echo $id; ?>').val();
171
+ if (custom == 'custom') {
172
+ $( '.<?php echo $id; ?>_custom').show();
173
+ } else {
174
+ $( '.<?php echo $id; ?>_custom').hide();
175
+ }
176
+ }
177
+
178
+ check_<?php echo $id; ?>_custom();
179
+
180
+ $( '#<?php echo $id; ?>' ).change(function() {
181
+ check_<?php echo $id; ?>_custom();
182
+ });
183
+
184
+ });
185
+ </script>
186
+ <?php
187
+ }
188
+
189
+ // Displays option description.
190
+ if ( isset( $args['description'] ) ) {
191
+ printf( '<p class="description">%s</p>', $args['description'] );
192
+ }
193
+
194
+ }
195
+
196
+ public function radio_button( $args ) {
197
+ extract( $this->normalize_settings_args( $args ) );
198
+
199
+ foreach ( $options as $key => $label ) {
200
+ printf( '<input type="radio" class="radio" id="%1$s[%3$s]" name="%2$s" value="%3$s"%4$s />', $id, $setting_name, $key, checked( $current, $key, false ) );
201
+ printf( '<label for="%1$s[%3$s]"> %4$s</label><br>', $id, $setting_name, $key, $label);
202
+ }
203
+
204
+
205
+ // Displays option description.
206
+ if ( isset( $args['description'] ) ) {
207
+ printf( '<p class="description">%s</p>', $args['description'] );
208
+ }
209
+
210
+ }
211
+
212
+ /**
213
+ * Multiple text element callback.
214
+ * @param array $args Field arguments.
215
+ * @return string Text input field.
216
+ */
217
+ public function multiple_text_input( $args ) {
218
+ extract( $this->normalize_settings_args( $args ) );
219
+
220
+ if (!empty($header)) {
221
+ echo "<p><strong>{$header}</strong>:</p>";
222
+ }
223
+
224
+ printf('<p class="%s multiple-text-input">', $id);
225
+ foreach ($fields as $name => $field) {
226
+ $size = $field['size'];
227
+ $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
228
+
229
+ if (isset($field['label_width'])) {
230
+ $style = sprintf( 'style="display:inline-block; width:%1$s;"', $field['label_width'] );
231
+ } else {
232
+ $style = '';
233
+ }
234
+
235
+ $description = isset( $field['description'] ) ? '<span style="font-style:italic;">'.$field['description'].'</span>' : '';
236
+
237
+ // output field label
238
+ if (isset($field['label'])) {
239
+ printf( '<label for="%1$s_%2$s" %3$s>%4$s:</label>', $id, $name, $style, $field['label'] );
240
+ }
241
+
242
+ // output field
243
+ $field_current = isset($current[$name]) ? $current[$name] : '';
244
+ $type = isset( $field['type'] ) ? $field['type'] : 'text';
245
+ printf( '<input type="%1$s" id="%2$s_%4$s" name="%3$s[%4$s]" value="%5$s" size="%6$s" placeholder="%7$s"/> %8$s<br/>', $type, $id, $setting_name, $name, esc_attr( $field_current ), $size, $placeholder, $description );
246
+ }
247
+ echo "</p>";
248
+
249
+ // Displays option description.
250
+ if ( isset( $args['description'] ) ) {
251
+ printf( '<p class="description">%s</p>', $args['description'] );
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Multiple text element callback.
257
+ * @param array $args Field arguments.
258
+ * @return string Text input field.
259
+ */
260
+ public function multiple_checkboxes( $args ) {
261
+ extract( $this->normalize_settings_args( $args ) );
262
+
263
+ foreach ($fields as $name => $label) {
264
+ // $label = $field['label'];
265
+
266
+ // output checkbox
267
+ $field_current = isset($current[$name]) ? $current[$name] : '';
268
+ printf( '<input type="checkbox" id="%1$s_%3$s" name="%2$s[%3$s]" value="%4$s"%5$s />', $id, $setting_name, $name, $value, checked( $value, $field_current, false ) );
269
+
270
+ // output field label
271
+ printf( '<label for="%1$s_%2$s">%3$s</label><br>', $id, $name, $label );
272
+
273
+ }
274
+
275
+ // Displays option description.
276
+ if ( isset( $args['description'] ) ) {
277
+ printf( '<p class="description">%s</p>', $args['description'] );
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Media upload callback.
283
+ *
284
+ * @param array $args Field arguments.
285
+ *
286
+ * @return string Media upload button & preview.
287
+ */
288
+ public function media_upload( $args ) {
289
+ extract( $this->normalize_settings_args( $args ) );
290
+
291
+ if( !empty($current) ) {
292
+ $attachment = wp_get_attachment_image_src( $current, 'full', false );
293
+
294
+ $attachment_src = $attachment[0];
295
+ $attachment_width = $attachment[1];
296
+ $attachment_height = $attachment[2];
297
+ $attachment_resolution = round($attachment_height/(3/2.54));
298
+
299
+ printf('<img src="%1$s" style="display:block" id="img-%4$s"/>', $attachment_src, $attachment_width, $attachment_height, $id );
300
+ printf('<div class="attachment-resolution"><p class="description">%s: %sdpi (default height = 3cm)</p></div>', __('Image resolution'), $attachment_resolution );
301
+ printf('<span class="button wpo_remove_image_button" data-input_id="%1$s">%2$s</span>', $id, $remove_button_text );
302
+ }
303
+
304
+ printf( '<input id="%1$s" name="%2$s" type="hidden" value="%3$s" />', $id, $setting_name, $current );
305
+
306
+ printf( '<span class="button wpo_upload_image_button %4$s" data-uploader_title="%1$s" data-uploader_button_text="%2$s" data-remove_button_text="%3$s" data-input_id="%4$s">%2$s</span>', $uploader_title, $uploader_button_text, $remove_button_text, $id );
307
+
308
+ // Displays option description.
309
+ if ( isset( $description ) ) {
310
+ printf( '<p class="description">%s</p>', $description );
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Next document number edit callback.
316
+ *
317
+ * @param array $args Field arguments.
318
+ */
319
+ public function next_number_edit( $args ) {
320
+ extract( $args );
321
+ $number_store_method = WPO_WCPDF()->settings->get_sequential_number_store_method();
322
+ $number_store = new Sequential_Number_Store( $store, $number_store_method );
323
+ $next_number = $number_store->get_next();
324
+ $nonce = wp_create_nonce( "wpo_wcpdf_next_{$store}" );
325
+ printf( '<input id="next_%1$s" class="next-number-input" type="text" size="%2$s" value="%3$s" disabled="disabled" data-store="%1$s" data-nonce="%4$s"/> <span class="edit-next-number dashicons dashicons-edit"></span><span class="save-next-number button secondary" style="display:none;">%5$s</span>', $store, $size, $next_number, $nonce, __( 'Save', 'woocommerce-pdf-invoices-packing-slips' ) );
326
+ // Displays option description.
327
+ if ( isset( $description ) ) {
328
+ printf( '<p class="description">%s</p>', $description );
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Wrapper function to create tabs for settings in different languages
334
+ * @param [type] $args [description]
335
+ * @param [type] $callback [description]
336
+ * @return [type] [description]
337
+ */
338
+ public function i18n_wrap ( $args ) {
339
+ extract( $this->normalize_settings_args( $args ) );
340
+
341
+ if ( $languages = $this->get_languages() ) {
342
+ printf( '<div id="%s-%s-translations" class="translations">', $option_name, $id)
343
+ ?>
344
+ <ul>
345
+ <?php foreach ( $languages as $lang_code => $language_name ) {
346
+ $translation_id = "{$option_name}_{$id}_{$lang_code}";
347
+ printf('<li><a href="#%s">%s</a></li>', $translation_id, $language_name );
348
+ }
349
+ ?>
350
+ </ul>
351
+ <?php foreach ( $languages as $lang_code => $language_name ) {
352
+ $translation_id = "{$option_name}_{$id}_{$lang_code}";
353
+ printf( '<div id="%s">', $translation_id );
354
+ $args['lang'] = $lang_code;
355
+ // don't use internationalized placeholders since they're not translated,
356
+ // to avoid confusion (user thinking they're all the same)
357
+ if ( $callback == 'multiple_text_input' ) {
358
+ foreach ($fields as $key => $field_args) {
359
+ if (!empty($field_args['placeholder']) && isset($field_args['i18n_placeholder'])) {
360
+ $args['fields'][$key]['placeholder'] = '';
361
+ }
362
+ }
363
+ } else {
364
+ if (!empty($args['placeholder']) && isset($args['i18n_placeholder'])) {
365
+ $args['placeholder'] = '';
366
+ }
367
+ }
368
+ // specific description for internationalized fields (to compensate for missing placeholder)
369
+ if (!empty($args['i18n_description'])) {
370
+ $args['description'] = $args['i18n_description'];
371
+ }
372
+ call_user_func( array( $this, $callback ), $args );
373
+ echo '</div>';
374
+ }
375
+ ?>
376
+
377
+ </div>
378
+ <?php
379
+ } else {
380
+ $args['lang'] = 'default';
381
+ call_user_func( array( $this, $callback ), $args );
382
+ }
383
+ }
384
+
385
+ public function get_languages () {
386
+ $multilingual = function_exists('icl_get_languages');
387
+ // $multilingual = true; // for development
388
+
389
+ if ($multilingual) {
390
+ // use this instead of function call for development outside of WPML
391
+ // $icl_get_languages = 'a:3:{s:2:"en";a:8:{s:2:"id";s:1:"1";s:6:"active";s:1:"1";s:11:"native_name";s:7:"English";s:7:"missing";s:1:"0";s:15:"translated_name";s:7:"English";s:13:"language_code";s:2:"en";s:16:"country_flag_url";s:43:"http://yourdomain/wpmlpath/res/flags/en.png";s:3:"url";s:23:"http://yourdomain/about";}s:2:"fr";a:8:{s:2:"id";s:1:"4";s:6:"active";s:1:"0";s:11:"native_name";s:9:"Français";s:7:"missing";s:1:"0";s:15:"translated_name";s:6:"French";s:13:"language_code";s:2:"fr";s:16:"country_flag_url";s:43:"http://yourdomain/wpmlpath/res/flags/fr.png";s:3:"url";s:29:"http://yourdomain/fr/a-propos";}s:2:"it";a:8:{s:2:"id";s:2:"27";s:6:"active";s:1:"0";s:11:"native_name";s:8:"Italiano";s:7:"missing";s:1:"0";s:15:"translated_name";s:7:"Italian";s:13:"language_code";s:2:"it";s:16:"country_flag_url";s:43:"http://yourdomain/wpmlpath/res/flags/it.png";s:3:"url";s:26:"http://yourdomain/it/circa";}}';
392
+ // $icl_get_languages = unserialize($icl_get_languages);
393
+
394
+ $icl_get_languages = icl_get_languages('skip_missing=0');
395
+ $languages = array();
396
+ foreach ($icl_get_languages as $lang => $data) {
397
+ $languages[$data['language_code']] = $data['native_name'];
398
+ }
399
+ } else {
400
+ return false;
401
+ }
402
+
403
+ return $languages;
404
+ }
405
+
406
+ public function normalize_settings_args ( $args ) {
407
+ $args['value'] = isset( $args['value'] ) ? $args['value'] : 1;
408
+
409
+ $args['placeholder'] = isset( $args['placeholder'] ) ? $args['placeholder'] : '';
410
+
411
+ // get main settings array
412
+ $option = get_option( $args['option_name'] );
413
+
414
+ $args['setting_name'] = "{$args['option_name']}[{$args['id']}]";
415
+
416
+ if ( !isset($args['lang']) && !empty($args['translatable']) ) {
417
+ $args['lang'] = 'default';
418
+ }
419
+
420
+ if (isset($args['lang'])) {
421
+ // i18n settings name
422
+ $args['setting_name'] = "{$args['setting_name']}[{$args['lang']}]";
423
+ // copy current option value if set
424
+
425
+ if ( $args['lang'] == 'default' && !empty($option[$args['id']]) && !isset( $option[$args['id']]['default'] ) ) {
426
+ // we're switching back from WPML to normal
427
+ // try english first
428
+ if ( isset( $option[$args['id']]['en'] ) ) {
429
+ $args['current'] = $option[$args['id']]['en'];
430
+ } elseif ( is_array( $option[$args['id']] ) ) {
431
+ // fallback to the first language if english not found
432
+ $first = array_shift($option[$args['id']]);
433
+ if (!empty($first)) {
434
+ $args['current'] = $first;
435
+ }
436
+ } elseif ( is_string( $option[$args['id']] ) ) {
437
+ $args['current'] = $option[$args['id']];
438
+ } else {
439
+ // nothing, really?
440
+ $args['current'] = '';
441
+ }
442
+ } else {
443
+ if ( isset( $option[$args['id']][$args['lang']] ) ) {
444
+ $args['current'] = $option[$args['id']][$args['lang']];
445
+ } elseif (isset( $option[$args['id']]['default'] )) {
446
+ $args['current'] = $option[$args['id']]['default'];
447
+ }
448
+ }
449
+ } else {
450
+ // copy current option value if set
451
+ if ( isset( $option[$args['id']] ) ) {
452
+ $args['current'] = $option[$args['id']];
453
+ }
454
+ }
455
+
456
+ // falback to default or empty if no value in option
457
+ if ( !isset($args['current']) ) {
458
+ $args['current'] = isset( $args['default'] ) ? $args['default'] : '';
459
+ }
460
+
461
+ return $args;
462
+ }
463
+
464
+ /**
465
+ * Validate options.
466
+ *
467
+ * @param array $input options to valid.
468
+ *
469
+ * @return array validated options.
470
+ */
471
+ public function validate( $input ) {
472
+ // echo '<pre>';var_dump($input);die('</pre>');
473
+ // Create our array for storing the validated options.
474
+ $output = array();
475
+
476
+ if (empty($input) || !is_array($input)) {
477
+ return $input;
478
+ }
479
+
480
+ // Loop through each of the incoming options.
481
+ foreach ( $input as $key => $value ) {
482
+
483
+ // Check to see if the current option has a value. If so, process it.
484
+ if ( isset( $input[$key] ) ) {
485
+ if ( is_array( $input[$key] ) ) {
486
+ foreach ( $input[$key] as $sub_key => $sub_value ) {
487
+ $output[$key][$sub_key] = $input[$key][$sub_key];
488
+ }
489
+ } else {
490
+ $output[$key] = $input[$key];
491
+ }
492
+ }
493
+ }
494
+
495
+ // Return the array processing any additional functions filtered by this action.
496
+ return apply_filters( 'wpo_wcpdf_validate_input', $output, $input );
497
+ }
498
+ }
499
+
500
+
501
+ endif; // class_exists
502
+
503
  return new Settings_Callbacks();
includes/class-wcpdf-settings-debug.php CHANGED
@@ -1,196 +1,196 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_Debug' ) ) :
9
-
10
- class Settings_Debug {
11
-
12
- function __construct() {
13
- add_action( 'admin_init', array( $this, 'init_settings' ) );
14
- add_action( 'wpo_wcpdf_settings_output_debug', array( $this, 'output' ), 10, 1 );
15
- add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'debug_tools' ), 10, 2 );
16
- }
17
-
18
- public function output( $section ) {
19
- settings_fields( "wpo_wcpdf_settings_debug" );
20
- do_settings_sections( "wpo_wcpdf_settings_debug" );
21
-
22
- submit_button();
23
- }
24
-
25
- public function debug_tools( $tab, $section ) {
26
- if ($tab !== 'debug') {
27
- return;
28
- }
29
- ?>
30
- <form method="post">
31
- <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="install_fonts">
32
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Reinstall fonts', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
33
- <?php
34
- if (isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'install_fonts') {
35
- $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
36
-
37
- // clear folder first
38
- if ( function_exists("glob") && $files = glob( $font_path.'/*.*' ) ) {
39
- $exclude_files = array( 'index.php', '.htaccess' );
40
- foreach($files as $file) {
41
- if( is_file($file) && !in_array( basename($file), $exclude_files ) ) {
42
- unlink($file);
43
- }
44
- }
45
- }
46
-
47
- WPO_WCPDF()->main->copy_fonts( $font_path );
48
- printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Fonts reinstalled!', 'woocommerce-pdf-invoices-packing-slips' ) );
49
- }
50
- ?>
51
- </form>
52
- <form method="post">
53
- <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="clear_tmp">
54
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Remove temporary files', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
55
- <?php
56
- if (isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'clear_tmp') {
57
- $tmp_path = WPO_WCPDF()->main->get_tmp_path('attachments');
58
-
59
- if ( !function_exists("glob") ) {
60
- // glob is disabled
61
- printf('<div class="notice notice-error"><p>%s<br><code>%s</code></p></div>', __( "Unable to read temporary folder contents!", 'woocommerce-pdf-invoices-packing-slips' ), $tmp_path);
62
- } else {
63
- $success = 0;
64
- $error = 0;
65
- if ( $files = glob($tmp_path.'*.pdf') ) { // get all pdf files
66
- foreach($files as $file) {
67
- if(is_file($file)) {
68
- // delete file
69
- if ( unlink($file) === true ) {
70
- $success++;
71
- } else {
72
- $error++;
73
- }
74
- }
75
- }
76
-
77
- if ($error > 0) {
78
- $message = sprintf( __( 'Unable to delete %d files! (deleted %d)', 'woocommerce-pdf-invoices-packing-slips' ), $error, $success);
79
- printf('<div class="notice notice-error"><p>%s</p></div>', $message);
80
- } else {
81
- $message = sprintf( __( 'Successfully deleted %d files!', 'woocommerce-pdf-invoices-packing-slips' ), $success );
82
- printf('<div class="notice notice-success"><p>%s</p></div>', $message);
83
- }
84
- } else {
85
- printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Nothing to delete!', 'woocommerce-pdf-invoices-packing-slips' ) );
86
- }
87
- }
88
- }
89
- ?>
90
- </form>
91
- <form method="post">
92
- <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="delete_legacy_settings">
93
- <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Delete legacy (1.X) settings', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
94
- <?php
95
- if (isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'delete_legacy_settings') {
96
- // delete options
97
- delete_option( 'wpo_wcpdf_general_settings' );
98
- delete_option( 'wpo_wcpdf_template_settings' );
99
- delete_option( 'wpo_wcpdf_debug_settings' );
100
- // and delete cache of these options, just in case...
101
- wp_cache_delete( 'wpo_wcpdf_general_settings','options' );
102
- wp_cache_delete( 'wpo_wcpdf_template_settings','options' );
103
- wp_cache_delete( 'wpo_wcpdf_debug_settings','options' );
104
-
105
- printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Legacy settings deleted!', 'woocommerce-pdf-invoices-packing-slips' ) );
106
- }
107
- ?>
108
- </form>
109
- <?php
110
- include( WPO_WCPDF()->plugin_path() . '/includes/views/dompdf-status.php' );
111
- }
112
-
113
- public function init_settings() {
114
- // Register settings.
115
- $page = $option_group = $option_name = 'wpo_wcpdf_settings_debug';
116
-
117
- $settings_fields = array(
118
- array(
119
- 'type' => 'section',
120
- 'id' => 'debug_settings',
121
- 'title' => __( 'Debug settings', 'woocommerce-pdf-invoices-packing-slips' ),
122
- 'callback' => 'section',
123
- ),
124
- array(
125
- 'type' => 'setting',
126
- 'id' => 'legacy_mode',
127
- 'title' => __( 'Legacy mode', 'woocommerce-pdf-invoices-packing-slips' ),
128
- 'callback' => 'checkbox',
129
- 'section' => 'debug_settings',
130
- 'args' => array(
131
- 'option_name' => $option_name,
132
- 'id' => 'legacy_mode',
133
- 'description' => __( "Legacy mode ensures compatibility with templates and filters from previous versions.", 'woocommerce-pdf-invoices-packing-slips' ),
134
- )
135
- ),
136
- array(
137
- 'type' => 'setting',
138
- 'id' => 'calculate_document_numbers',
139
- 'title' => __( 'Calculate document numbers (slow)', 'woocommerce-pdf-invoices-packing-slips' ),
140
- 'callback' => 'checkbox',
141
- 'section' => 'debug_settings',
142
- 'args' => array(
143
- 'option_name' => $option_name,
144
- 'id' => 'calculate_document_numbers',
145
- 'description' => __( "Document numbers (such as invoice numbers) are generated using AUTO_INCREMENT by default. Use this setting if your database auto increments with more than 1.", 'woocommerce-pdf-invoices-packing-slips' ),
146
- )
147
- ),
148
- array(
149
- 'type' => 'setting',
150
- 'id' => 'enable_debug',
151
- 'title' => __( 'Enable debug output', 'woocommerce-pdf-invoices-packing-slips' ),
152
- 'callback' => 'checkbox',
153
- 'section' => 'debug_settings',
154
- 'args' => array(
155
- 'option_name' => $option_name,
156
- 'id' => 'enable_debug',
157
- 'description' => __( "Enable this option to output plugin errors if you're getting a blank page or other PDF generation issues", 'woocommerce-pdf-invoices-packing-slips' ) . '<br>' .
158
- __( '<b>Caution!</b> This setting may reveal errors (from other plugins) in other places on your site too, therefor this is not recommended to leave it enabled on live sites.', 'woocommerce-pdf-invoices-packing-slips' ),
159
- )
160
- ),
161
- array(
162
- 'type' => 'setting',
163
- 'id' => 'html_output',
164
- 'title' => __( 'Output to HTML', 'woocommerce-pdf-invoices-packing-slips' ),
165
- 'callback' => 'checkbox',
166
- 'section' => 'debug_settings',
167
- 'args' => array(
168
- 'option_name' => $option_name,
169
- 'id' => 'html_output',
170
- 'description' => __( 'Send the template output as HTML to the browser instead of creating a PDF.', 'woocommerce-pdf-invoices-packing-slips' ),
171
- )
172
- ),
173
- array(
174
- 'type' => 'setting',
175
- 'id' => 'use_html5_parser',
176
- 'title' => __( 'Use alternative HTML5 parser to parse HTML', 'woocommerce-pdf-invoices-packing-slips' ),
177
- 'callback' => 'checkbox',
178
- 'section' => 'debug_settings',
179
- 'args' => array(
180
- 'option_name' => $option_name,
181
- 'id' => 'use_html5_parser',
182
- )
183
- ),
184
- );
185
-
186
- // allow plugins to alter settings fields
187
- $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_debug', $settings_fields, $page, $option_group, $option_name );
188
- WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
189
- return;
190
- }
191
-
192
- }
193
-
194
- endif; // class_exists
195
-
196
  return new Settings_Debug();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_Debug' ) ) :
9
+
10
+ class Settings_Debug {
11
+
12
+ function __construct() {
13
+ add_action( 'admin_init', array( $this, 'init_settings' ) );
14
+ add_action( 'wpo_wcpdf_settings_output_debug', array( $this, 'output' ), 10, 1 );
15
+ add_action( 'wpo_wcpdf_after_settings_page', array( $this, 'debug_tools' ), 10, 2 );
16
+ }
17
+
18
+ public function output( $section ) {
19
+ settings_fields( "wpo_wcpdf_settings_debug" );
20
+ do_settings_sections( "wpo_wcpdf_settings_debug" );
21
+
22
+ submit_button();
23
+ }
24
+
25
+ public function debug_tools( $tab, $section ) {
26
+ if ($tab !== 'debug') {
27
+ return;
28
+ }
29
+ ?>
30
+ <form method="post">
31
+ <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="install_fonts">
32
+ <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Reinstall fonts', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
33
+ <?php
34
+ if (isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'install_fonts') {
35
+ $font_path = WPO_WCPDF()->main->get_tmp_path( 'fonts' );
36
+
37
+ // clear folder first
38
+ if ( function_exists("glob") && $files = glob( $font_path.'/*.*' ) ) {
39
+ $exclude_files = array( 'index.php', '.htaccess' );
40
+ foreach($files as $file) {
41
+ if( is_file($file) && !in_array( basename($file), $exclude_files ) ) {
42
+ unlink($file);
43
+ }
44
+ }
45
+ }
46
+
47
+ WPO_WCPDF()->main->copy_fonts( $font_path );
48
+ printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Fonts reinstalled!', 'woocommerce-pdf-invoices-packing-slips' ) );
49
+ }
50
+ ?>
51
+ </form>
52
+ <form method="post">
53
+ <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="clear_tmp">
54
+ <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Remove temporary files', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
55
+ <?php
56
+ if (isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'clear_tmp') {
57
+ $tmp_path = WPO_WCPDF()->main->get_tmp_path('attachments');
58
+
59
+ if ( !function_exists("glob") ) {
60
+ // glob is disabled
61
+ printf('<div class="notice notice-error"><p>%s<br><code>%s</code></p></div>', __( "Unable to read temporary folder contents!", 'woocommerce-pdf-invoices-packing-slips' ), $tmp_path);
62
+ } else {
63
+ $success = 0;
64
+ $error = 0;
65
+ if ( $files = glob($tmp_path.'*.pdf') ) { // get all pdf files
66
+ foreach($files as $file) {
67
+ if(is_file($file)) {
68
+ // delete file
69
+ if ( unlink($file) === true ) {
70
+ $success++;
71
+ } else {
72
+ $error++;
73
+ }
74
+ }
75
+ }
76
+
77
+ if ($error > 0) {
78
+ $message = sprintf( __( 'Unable to delete %d files! (deleted %d)', 'woocommerce-pdf-invoices-packing-slips' ), $error, $success);
79
+ printf('<div class="notice notice-error"><p>%s</p></div>', $message);
80
+ } else {
81
+ $message = sprintf( __( 'Successfully deleted %d files!', 'woocommerce-pdf-invoices-packing-slips' ), $success );
82
+ printf('<div class="notice notice-success"><p>%s</p></div>', $message);
83
+ }
84
+ } else {
85
+ printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Nothing to delete!', 'woocommerce-pdf-invoices-packing-slips' ) );
86
+ }
87
+ }
88
+ }
89
+ ?>
90
+ </form>
91
+ <form method="post">
92
+ <input type="hidden" name="wpo_wcpdf_debug_tools_action" value="delete_legacy_settings">
93
+ <input type="submit" name="submit" id="submit" class="button" value="<?php _e( 'Delete legacy (1.X) settings', 'woocommerce-pdf-invoices-packing-slips' ); ?>">
94
+ <?php
95
+ if (isset($_POST['wpo_wcpdf_debug_tools_action']) && $_POST['wpo_wcpdf_debug_tools_action'] == 'delete_legacy_settings') {
96
+ // delete options
97
+ delete_option( 'wpo_wcpdf_general_settings' );
98
+ delete_option( 'wpo_wcpdf_template_settings' );
99
+ delete_option( 'wpo_wcpdf_debug_settings' );
100
+ // and delete cache of these options, just in case...
101
+ wp_cache_delete( 'wpo_wcpdf_general_settings','options' );
102
+ wp_cache_delete( 'wpo_wcpdf_template_settings','options' );
103
+ wp_cache_delete( 'wpo_wcpdf_debug_settings','options' );
104
+
105
+ printf('<div class="notice notice-success"><p>%s</p></div>', __( 'Legacy settings deleted!', 'woocommerce-pdf-invoices-packing-slips' ) );
106
+ }
107
+ ?>
108
+ </form>
109
+ <?php
110
+ include( WPO_WCPDF()->plugin_path() . '/includes/views/dompdf-status.php' );
111
+ }
112
+
113
+ public function init_settings() {
114
+ // Register settings.
115
+ $page = $option_group = $option_name = 'wpo_wcpdf_settings_debug';
116
+
117
+ $settings_fields = array(
118
+ array(
119
+ 'type' => 'section',
120
+ 'id' => 'debug_settings',
121
+ 'title' => __( 'Debug settings', 'woocommerce-pdf-invoices-packing-slips' ),
122
+ 'callback' => 'section',
123
+ ),
124
+ array(
125
+ 'type' => 'setting',
126
+ 'id' => 'legacy_mode',
127
+ 'title' => __( 'Legacy mode', 'woocommerce-pdf-invoices-packing-slips' ),
128
+ 'callback' => 'checkbox',
129
+ 'section' => 'debug_settings',
130
+ 'args' => array(
131
+ 'option_name' => $option_name,
132
+ 'id' => 'legacy_mode',
133
+ 'description' => __( "Legacy mode ensures compatibility with templates and filters from previous versions.", 'woocommerce-pdf-invoices-packing-slips' ),
134
+ )
135
+ ),
136
+ array(
137
+ 'type' => 'setting',
138
+ 'id' => 'calculate_document_numbers',
139
+ 'title' => __( 'Calculate document numbers (slow)', 'woocommerce-pdf-invoices-packing-slips' ),
140
+ 'callback' => 'checkbox',
141
+ 'section' => 'debug_settings',
142
+ 'args' => array(
143
+ 'option_name' => $option_name,
144
+ 'id' => 'calculate_document_numbers',
145
+ 'description' => __( "Document numbers (such as invoice numbers) are generated using AUTO_INCREMENT by default. Use this setting if your database auto increments with more than 1.", 'woocommerce-pdf-invoices-packing-slips' ),
146
+ )
147
+ ),
148
+ array(
149
+ 'type' => 'setting',
150
+ 'id' => 'enable_debug',
151
+ 'title' => __( 'Enable debug output', 'woocommerce-pdf-invoices-packing-slips' ),
152
+ 'callback' => 'checkbox',
153
+ 'section' => 'debug_settings',
154
+ 'args' => array(
155
+ 'option_name' => $option_name,
156
+ 'id' => 'enable_debug',
157
+ 'description' => __( "Enable this option to output plugin errors if you're getting a blank page or other PDF generation issues", 'woocommerce-pdf-invoices-packing-slips' ) . '<br>' .
158
+ __( '<b>Caution!</b> This setting may reveal errors (from other plugins) in other places on your site too, therefor this is not recommended to leave it enabled on live sites.', 'woocommerce-pdf-invoices-packing-slips' ),
159
+ )
160
+ ),
161
+ array(
162
+ 'type' => 'setting',
163
+ 'id' => 'html_output',
164
+ 'title' => __( 'Output to HTML', 'woocommerce-pdf-invoices-packing-slips' ),
165
+ 'callback' => 'checkbox',
166
+ 'section' => 'debug_settings',
167
+ 'args' => array(
168
+ 'option_name' => $option_name,
169
+ 'id' => 'html_output',
170
+ 'description' => __( 'Send the template output as HTML to the browser instead of creating a PDF.', 'woocommerce-pdf-invoices-packing-slips' ),
171
+ )
172
+ ),
173
+ array(
174
+ 'type' => 'setting',
175
+ 'id' => 'use_html5_parser',
176
+ 'title' => __( 'Use alternative HTML5 parser to parse HTML', 'woocommerce-pdf-invoices-packing-slips' ),
177
+ 'callback' => 'checkbox',
178
+ 'section' => 'debug_settings',
179
+ 'args' => array(
180
+ 'option_name' => $option_name,
181
+ 'id' => 'use_html5_parser',
182
+ )
183
+ ),
184
+ );
185
+
186
+ // allow plugins to alter settings fields
187
+ $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_debug', $settings_fields, $page, $option_group, $option_name );
188
+ WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
189
+ return;
190
+ }
191
+
192
+ }
193
+
194
+ endif; // class_exists
195
+
196
  return new Settings_Debug();
includes/class-wcpdf-settings-documents.php CHANGED
@@ -22,6 +22,7 @@ class Settings_Documents {
22
  }
23
 
24
  public function output( $section ) {
 
25
  if ( !empty( $section ) ) {
26
  $documents = WPO_WCPDF()->documents->get_documents('all');
27
  ?>
22
  }
23
 
24
  public function output( $section ) {
25
+ $section = !empty($section) ? $section : 'invoice';
26
  if ( !empty( $section ) ) {
27
  $documents = WPO_WCPDF()->documents->get_documents('all');
28
  ?>
includes/class-wcpdf-settings-general.php CHANGED
@@ -1,299 +1,299 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_General' ) ) :
9
-
10
- class Settings_General {
11
-
12
- function __construct() {
13
- add_action( 'admin_init', array( $this, 'init_settings' ) );
14
- add_action( 'wpo_wcpdf_settings_output_general', array( $this, 'output' ), 10, 1 );
15
- add_action( 'wpo_wcpdf_before_settings', array( $this, 'attachment_settings_hint' ), 10, 2 );
16
- }
17
-
18
- public function output( $section ) {
19
- settings_fields( "wpo_wcpdf_settings_general" );
20
- do_settings_sections( "wpo_wcpdf_settings_general" );
21
-
22
- submit_button();
23
- }
24
-
25
- public function init_settings() {
26
- $page = $option_group = $option_name = 'wpo_wcpdf_settings_general';
27
-
28
- $template_base_path = ( defined( 'WC_TEMPLATE_PATH' ) ? WC_TEMPLATE_PATH : $GLOBALS['woocommerce']->template_url );
29
- $theme_template_path = get_stylesheet_directory() . '/' . $template_base_path;
30
- $wp_content_dir = str_replace( ABSPATH, '', WP_CONTENT_DIR );
31
- $theme_template_path = substr($theme_template_path, strpos($theme_template_path, $wp_content_dir)) . 'pdf/yourtemplate';
32
- $plugin_template_path = "{$wp_content_dir}/plugins/woocommerce-pdf-invoices-packing-slips/templates/Simple";
33
-
34
- $settings_fields = array(
35
- array(
36
- 'type' => 'section',
37
- 'id' => 'general_settings',
38
- 'title' => __( 'General settings', 'woocommerce-pdf-invoices-packing-slips' ),
39
- 'callback' => 'section',
40
- ),
41
- array(
42
- 'type' => 'setting',
43
- 'id' => 'download_display',
44
- 'title' => __( 'How do you want to view the PDF?', 'woocommerce-pdf-invoices-packing-slips' ),
45
- 'callback' => 'select',
46
- 'section' => 'general_settings',
47
- 'args' => array(
48
- 'option_name' => $option_name,
49
- 'id' => 'download_display',
50
- 'options' => array(
51
- 'download' => __( 'Download the PDF' , 'woocommerce-pdf-invoices-packing-slips' ),
52
- 'display' => __( 'Open the PDF in a new browser tab/window' , 'woocommerce-pdf-invoices-packing-slips' ),
53
- ),
54
- )
55
- ),
56
- array(
57
- 'type' => 'setting',
58
- 'id' => 'template_path',
59
- 'title' => __( 'Choose a template', 'woocommerce-pdf-invoices-packing-slips' ),
60
- 'callback' => 'select',
61
- 'section' => 'general_settings',
62
- 'args' => array(
63
- 'option_name' => $option_name,
64
- 'id' => 'template_path',
65
- 'options' => $this->find_templates(),
66
- 'description' => sprintf( __( 'Want to use your own template? Copy all the files from <code>%s</code> to your (child) theme in <code>%s</code> to customize them' , 'woocommerce-pdf-invoices-packing-slips' ), $plugin_template_path, $theme_template_path),
67
- )
68
- ),
69
- array(
70
- 'type' => 'setting',
71
- 'id' => 'paper_size',
72
- 'title' => __( 'Paper size', 'woocommerce-pdf-invoices-packing-slips' ),
73
- 'callback' => 'select',
74
- 'section' => 'general_settings',
75
- 'args' => array(
76
- 'option_name' => $option_name,
77
- 'id' => 'paper_size',
78
- 'options' => apply_filters( 'wpo_wcpdf_template_settings_paper_size', array(
79
- 'a4' => __( 'A4' , 'woocommerce-pdf-invoices-packing-slips' ),
80
- 'letter' => __( 'Letter' , 'woocommerce-pdf-invoices-packing-slips' ),
81
- ) ),
82
- )
83
- ),
84
- array(
85
- 'type' => 'setting',
86
- 'id' => 'currency_font',
87
- 'title' => __( 'Extended currency symbol support', 'woocommerce-pdf-invoices-packing-slips' ),
88
- 'callback' => 'checkbox',
89
- 'section' => 'general_settings',
90
- 'args' => array(
91
- 'option_name' => $option_name,
92
- 'id' => 'currency_font',
93
- 'description' => __( 'Enable this if your currency symbol is not displaying properly' , 'woocommerce-pdf-invoices-packing-slips' ),
94
- )
95
- ),
96
- array(
97
- 'type' => 'setting',
98
- 'id' => 'font_subsetting',
99
- 'title' => __( 'Enable font subsetting', 'woocommerce-pdf-invoices-packing-slips' ),
100
- 'callback' => 'checkbox',
101
- 'section' => 'general_settings',
102
- 'args' => array(
103
- 'option_name' => $option_name,
104
- 'id' => 'font_subsetting',
105
- 'description' => __( "Font subsetting can reduce file size by only including the characters that are used in the PDF, but limits the ability to edit PDF files later. Recommended if you're using an Asian font." , 'woocommerce-pdf-invoices-packing-slips' ),
106
- )
107
- ),
108
- array(
109
- 'type' => 'setting',
110
- 'id' => 'header_logo',
111
- 'title' => __( 'Shop header/logo', 'woocommerce-pdf-invoices-packing-slips' ),
112
- 'callback' => 'media_upload',
113
- 'section' => 'general_settings',
114
- 'args' => array(
115
- 'option_name' => $option_name,
116
- 'id' => 'header_logo',
117
- 'uploader_title' => __( 'Select or upload your invoice header/logo', 'woocommerce-pdf-invoices-packing-slips' ),
118
- 'uploader_button_text' => __( 'Set image', 'woocommerce-pdf-invoices-packing-slips' ),
119
- 'remove_button_text' => __( 'Remove image', 'woocommerce-pdf-invoices-packing-slips' ),
120
- //'description' => __( '...', 'woocommerce-pdf-invoices-packing-slips' ),
121
- )
122
- ),
123
- array(
124
- 'type' => 'setting',
125
- 'id' => 'shop_name',
126
- 'title' => __( 'Shop Name', 'woocommerce-pdf-invoices-packing-slips' ),
127
- 'callback' => 'text_input',
128
- 'section' => 'general_settings',
129
- 'args' => array(
130
- 'option_name' => $option_name,
131
- 'id' => 'shop_name',
132
- 'size' => '72',
133
- 'translatable' => true,
134
- )
135
- ),
136
- array(
137
- 'type' => 'setting',
138
- 'id' => 'shop_address',
139
- 'title' => __( 'Shop Address', 'woocommerce-pdf-invoices-packing-slips' ),
140
- 'callback' => 'textarea',
141
- 'section' => 'general_settings',
142
- 'args' => array(
143
- 'option_name' => $option_name,
144
- 'id' => 'shop_address',
145
- 'width' => '72',
146
- 'height' => '8',
147
- 'translatable' => true,
148
- //'description' => __( '...', 'woocommerce-pdf-invoices-packing-slips' ),
149
- )
150
- ),
151
- array(
152
- 'type' => 'setting',
153
- 'id' => 'footer',
154
- 'title' => __( 'Footer: terms & conditions, policies, etc.', 'woocommerce-pdf-invoices-packing-slips' ),
155
- 'callback' => 'textarea',
156
- 'section' => 'general_settings',
157
- 'args' => array(
158
- 'option_name' => $option_name,
159
- 'id' => 'footer',
160
- 'width' => '72',
161
- 'height' => '4',
162
- 'translatable' => true,
163
- //'description' => __( '...', 'woocommerce-pdf-invoices-packing-slips' ),
164
- )
165
- ),
166
- array(
167
- 'type' => 'section',
168
- 'id' => 'extra_template_fields',
169
- 'title' => __( 'Extra template fields', 'woocommerce-pdf-invoices-packing-slips' ),
170
- 'callback' => 'custom_fields_section',
171
- ),
172
- array(
173
- 'type' => 'setting',
174
- 'id' => 'extra_1',
175
- 'title' => __( 'Extra field 1', 'woocommerce-pdf-invoices-packing-slips' ),
176
- 'callback' => 'textarea',
177
- 'section' => 'extra_template_fields',
178
- 'args' => array(
179
- 'option_name' => $option_name,
180
- 'id' => 'extra_1',
181
- 'width' => '72',
182
- 'height' => '8',
183
- 'description' => __( 'This is footer column 1 in the <i>Modern (Premium)</i> template', 'woocommerce-pdf-invoices-packing-slips' ),
184
- 'translatable' => true,
185
- )
186
- ),
187
- array(
188
- 'type' => 'setting',
189
- 'id' => 'extra_2',
190
- 'title' => __( 'Extra field 2', 'woocommerce-pdf-invoices-packing-slips' ),
191
- 'callback' => 'textarea',
192
- 'section' => 'extra_template_fields',
193
- 'args' => array(
194
- 'option_name' => $option_name,
195
- 'id' => 'extra_2',
196
- 'width' => '72',
197
- 'height' => '8',
198
- 'description' => __( 'This is footer column 2 in the <i>Modern (Premium)</i> template', 'woocommerce-pdf-invoices-packing-slips' ),
199
- 'translatable' => true,
200
- )
201
- ),
202
- array(
203
- 'type' => 'setting',
204
- 'id' => 'extra_3',
205
- 'title' => __( 'Extra field 3', 'woocommerce-pdf-invoices-packing-slips' ),
206
- 'callback' => 'textarea',
207
- 'section' => 'extra_template_fields',
208
- 'args' => array(
209
- 'option_name' => $option_name,
210
- 'id' => 'extra_3',
211
- 'width' => '72',
212
- 'height' => '8',
213
- 'description' => __( 'This is footer column 3 in the <i>Modern (Premium)</i> template', 'woocommerce-pdf-invoices-packing-slips' ),
214
- 'translatable' => true,
215
- )
216
- ),
217
- );
218
-
219
- // allow plugins to alter settings fields
220
- $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_general', $settings_fields, $page, $option_group, $option_name );
221
- WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
222
- return;
223
- }
224
-
225
- public function attachment_settings_hint( $active_tab, $active_section ) {
226
- // save or check option to hide attachments settings hint
227
- if ( isset( $_GET['wpo_wcpdf_hide_attachments_hint'] ) ) {
228
- update_option( 'wpo_wcpdf_hide_attachments_hint', true );
229
- $hide_hint = true;
230
- } else {
231
- $hide_hint = get_option( 'wpo_wcpdf_hide_attachments_hint' );
232
- }
233
-
234
- if ( $active_tab == 'general' && !$hide_hint ) {
235
- $documents = WPO_WCPDF()->documents->get_documents();
236
-
237
- foreach ($documents as $document) {
238
- if ( $document->get_type() == 'invoice' ) {
239
- $invoice_email_ids = $document->get_attach_to_email_ids();
240
- if (empty($invoice_email_ids)) {
241
- include_once( WPO_WCPDF()->plugin_path() . '/includes/views/attachment-settings-hint.php' );
242
- }
243
- }
244
- }
245
- }
246
- }
247
-
248
- /**
249
- * List templates in plugin folder, theme folder & child theme folder
250
- * @return array template path => template name
251
- */
252
- public function find_templates() {
253
- $installed_templates = array();
254
-
255
- // get base paths
256
- $template_base_path = ( defined( 'WC_TEMPLATE_PATH' ) ? WC_TEMPLATE_PATH : $GLOBALS['woocommerce']->template_url );
257
- $template_base_path = untrailingslashit( $template_base_path );
258
- $template_paths = array (
259
- // note the order: child-theme before theme, so that array_unique filters out parent doubles
260
- 'default' => WPO_WCPDF()->plugin_path() . '/templates/',
261
- 'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
262
- 'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
263
- );
264
-
265
- $template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
266
-
267
- if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
268
- $forwardslash_basepath = str_replace('\\','/', ABSPATH);
269
- } else {
270
- $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
271
- }
272
-
273
- foreach ($template_paths as $template_source => $template_path) {
274
- $dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR);
275
-
276
- foreach ($dirs as $dir) {
277
- // we're stripping abspath to make the plugin settings more portable
278
- $forwardslash_dir = str_replace('\\','/', $dir);
279
- $installed_templates[ str_replace( $forwardslash_basepath, '', $forwardslash_dir ) ] = basename($dir);
280
- }
281
- }
282
-
283
- // remove parent doubles
284
- $installed_templates = array_unique($installed_templates);
285
-
286
- if (empty($installed_templates)) {
287
- // fallback to Simple template for servers with glob() disabled
288
- $simple_template_path = str_replace( ABSPATH, '', $template_paths['default'] . 'Simple' );
289
- $installed_templates[$simple_template_path] = 'Simple';
290
- }
291
-
292
- return apply_filters( 'wpo_wcpdf_templates', $installed_templates );
293
- }
294
-
295
- }
296
-
297
- endif; // class_exists
298
-
299
  return new Settings_General();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings_General' ) ) :
9
+
10
+ class Settings_General {
11
+
12
+ function __construct() {
13
+ add_action( 'admin_init', array( $this, 'init_settings' ) );
14
+ add_action( 'wpo_wcpdf_settings_output_general', array( $this, 'output' ), 10, 1 );
15
+ add_action( 'wpo_wcpdf_before_settings', array( $this, 'attachment_settings_hint' ), 10, 2 );
16
+ }
17
+
18
+ public function output( $section ) {
19
+ settings_fields( "wpo_wcpdf_settings_general" );
20
+ do_settings_sections( "wpo_wcpdf_settings_general" );
21
+
22
+ submit_button();
23
+ }
24
+
25
+ public function init_settings() {
26
+ $page = $option_group = $option_name = 'wpo_wcpdf_settings_general';
27
+
28
+ $template_base_path = ( defined( 'WC_TEMPLATE_PATH' ) ? WC_TEMPLATE_PATH : $GLOBALS['woocommerce']->template_url );
29
+ $theme_template_path = get_stylesheet_directory() . '/' . $template_base_path;
30
+ $wp_content_dir = str_replace( ABSPATH, '', WP_CONTENT_DIR );
31
+ $theme_template_path = substr($theme_template_path, strpos($theme_template_path, $wp_content_dir)) . 'pdf/yourtemplate';
32
+ $plugin_template_path = "{$wp_content_dir}/plugins/woocommerce-pdf-invoices-packing-slips/templates/Simple";
33
+
34
+ $settings_fields = array(
35
+ array(
36
+ 'type' => 'section',
37
+ 'id' => 'general_settings',
38
+ 'title' => __( 'General settings', 'woocommerce-pdf-invoices-packing-slips' ),
39
+ 'callback' => 'section',
40
+ ),
41
+ array(
42
+ 'type' => 'setting',
43
+ 'id' => 'download_display',
44
+ 'title' => __( 'How do you want to view the PDF?', 'woocommerce-pdf-invoices-packing-slips' ),
45
+ 'callback' => 'select',
46
+ 'section' => 'general_settings',
47
+ 'args' => array(
48
+ 'option_name' => $option_name,
49
+ 'id' => 'download_display',
50
+ 'options' => array(
51
+ 'download' => __( 'Download the PDF' , 'woocommerce-pdf-invoices-packing-slips' ),
52
+ 'display' => __( 'Open the PDF in a new browser tab/window' , 'woocommerce-pdf-invoices-packing-slips' ),
53
+ ),
54
+ )
55
+ ),
56
+ array(
57
+ 'type' => 'setting',
58
+ 'id' => 'template_path',
59
+ 'title' => __( 'Choose a template', 'woocommerce-pdf-invoices-packing-slips' ),
60
+ 'callback' => 'select',
61
+ 'section' => 'general_settings',
62
+ 'args' => array(
63
+ 'option_name' => $option_name,
64
+ 'id' => 'template_path',
65
+ 'options' => $this->find_templates(),
66
+ 'description' => sprintf( __( 'Want to use your own template? Copy all the files from <code>%s</code> to your (child) theme in <code>%s</code> to customize them' , 'woocommerce-pdf-invoices-packing-slips' ), $plugin_template_path, $theme_template_path),
67
+ )
68
+ ),
69
+ array(
70
+ 'type' => 'setting',
71
+ 'id' => 'paper_size',
72
+ 'title' => __( 'Paper size', 'woocommerce-pdf-invoices-packing-slips' ),
73
+ 'callback' => 'select',
74
+ 'section' => 'general_settings',
75
+ 'args' => array(
76
+ 'option_name' => $option_name,
77
+ 'id' => 'paper_size',
78
+ 'options' => apply_filters( 'wpo_wcpdf_template_settings_paper_size', array(
79
+ 'a4' => __( 'A4' , 'woocommerce-pdf-invoices-packing-slips' ),
80
+ 'letter' => __( 'Letter' , 'woocommerce-pdf-invoices-packing-slips' ),
81
+ ) ),
82
+ )
83
+ ),
84
+ array(
85
+ 'type' => 'setting',
86
+ 'id' => 'currency_font',
87
+ 'title' => __( 'Extended currency symbol support', 'woocommerce-pdf-invoices-packing-slips' ),
88
+ 'callback' => 'checkbox',
89
+ 'section' => 'general_settings',
90
+ 'args' => array(
91
+ 'option_name' => $option_name,
92
+ 'id' => 'currency_font',
93
+ 'description' => __( 'Enable this if your currency symbol is not displaying properly' , 'woocommerce-pdf-invoices-packing-slips' ),
94
+ )
95
+ ),
96
+ array(
97
+ 'type' => 'setting',
98
+ 'id' => 'font_subsetting',
99
+ 'title' => __( 'Enable font subsetting', 'woocommerce-pdf-invoices-packing-slips' ),
100
+ 'callback' => 'checkbox',
101
+ 'section' => 'general_settings',
102
+ 'args' => array(
103
+ 'option_name' => $option_name,
104
+ 'id' => 'font_subsetting',
105
+ 'description' => __( "Font subsetting can reduce file size by only including the characters that are used in the PDF, but limits the ability to edit PDF files later. Recommended if you're using an Asian font." , 'woocommerce-pdf-invoices-packing-slips' ),
106
+ )
107
+ ),
108
+ array(
109
+ 'type' => 'setting',
110
+ 'id' => 'header_logo',
111
+ 'title' => __( 'Shop header/logo', 'woocommerce-pdf-invoices-packing-slips' ),
112
+ 'callback' => 'media_upload',
113
+ 'section' => 'general_settings',
114
+ 'args' => array(
115
+ 'option_name' => $option_name,
116
+ 'id' => 'header_logo',
117
+ 'uploader_title' => __( 'Select or upload your invoice header/logo', 'woocommerce-pdf-invoices-packing-slips' ),
118
+ 'uploader_button_text' => __( 'Set image', 'woocommerce-pdf-invoices-packing-slips' ),
119
+ 'remove_button_text' => __( 'Remove image', 'woocommerce-pdf-invoices-packing-slips' ),
120
+ //'description' => __( '...', 'woocommerce-pdf-invoices-packing-slips' ),
121
+ )
122
+ ),
123
+ array(
124
+ 'type' => 'setting',
125
+ 'id' => 'shop_name',
126
+ 'title' => __( 'Shop Name', 'woocommerce-pdf-invoices-packing-slips' ),
127
+ 'callback' => 'text_input',
128
+ 'section' => 'general_settings',
129
+ 'args' => array(
130
+ 'option_name' => $option_name,
131
+ 'id' => 'shop_name',
132
+ 'size' => '72',
133
+ 'translatable' => true,
134
+ )
135
+ ),
136
+ array(
137
+ 'type' => 'setting',
138
+ 'id' => 'shop_address',
139
+ 'title' => __( 'Shop Address', 'woocommerce-pdf-invoices-packing-slips' ),
140
+ 'callback' => 'textarea',
141
+ 'section' => 'general_settings',
142
+ 'args' => array(
143
+ 'option_name' => $option_name,
144
+ 'id' => 'shop_address',
145
+ 'width' => '72',
146
+ 'height' => '8',
147
+ 'translatable' => true,
148
+ //'description' => __( '...', 'woocommerce-pdf-invoices-packing-slips' ),
149
+ )
150
+ ),
151
+ array(
152
+ 'type' => 'setting',
153
+ 'id' => 'footer',
154
+ 'title' => __( 'Footer: terms & conditions, policies, etc.', 'woocommerce-pdf-invoices-packing-slips' ),
155
+ 'callback' => 'textarea',
156
+ 'section' => 'general_settings',
157
+ 'args' => array(
158
+ 'option_name' => $option_name,
159
+ 'id' => 'footer',
160
+ 'width' => '72',
161
+ 'height' => '4',
162
+ 'translatable' => true,
163
+ //'description' => __( '...', 'woocommerce-pdf-invoices-packing-slips' ),
164
+ )
165
+ ),
166
+ array(
167
+ 'type' => 'section',
168
+ 'id' => 'extra_template_fields',
169
+ 'title' => __( 'Extra template fields', 'woocommerce-pdf-invoices-packing-slips' ),
170
+ 'callback' => 'custom_fields_section',
171
+ ),
172
+ array(
173
+ 'type' => 'setting',
174
+ 'id' => 'extra_1',
175
+ 'title' => __( 'Extra field 1', 'woocommerce-pdf-invoices-packing-slips' ),
176
+ 'callback' => 'textarea',
177
+ 'section' => 'extra_template_fields',
178
+ 'args' => array(
179
+ 'option_name' => $option_name,
180
+ 'id' => 'extra_1',
181
+ 'width' => '72',
182
+ 'height' => '8',
183
+ 'description' => __( 'This is footer column 1 in the <i>Modern (Premium)</i> template', 'woocommerce-pdf-invoices-packing-slips' ),
184
+ 'translatable' => true,
185
+ )
186
+ ),
187
+ array(
188
+ 'type' => 'setting',
189
+ 'id' => 'extra_2',
190
+ 'title' => __( 'Extra field 2', 'woocommerce-pdf-invoices-packing-slips' ),
191
+ 'callback' => 'textarea',
192
+ 'section' => 'extra_template_fields',
193
+ 'args' => array(
194
+ 'option_name' => $option_name,
195
+ 'id' => 'extra_2',
196
+ 'width' => '72',
197
+ 'height' => '8',
198
+ 'description' => __( 'This is footer column 2 in the <i>Modern (Premium)</i> template', 'woocommerce-pdf-invoices-packing-slips' ),
199
+ 'translatable' => true,
200
+ )
201
+ ),
202
+ array(
203
+ 'type' => 'setting',
204
+ 'id' => 'extra_3',
205
+ 'title' => __( 'Extra field 3', 'woocommerce-pdf-invoices-packing-slips' ),
206
+ 'callback' => 'textarea',
207
+ 'section' => 'extra_template_fields',
208
+ 'args' => array(
209
+ 'option_name' => $option_name,
210
+ 'id' => 'extra_3',
211
+ 'width' => '72',
212
+ 'height' => '8',
213
+ 'description' => __( 'This is footer column 3 in the <i>Modern (Premium)</i> template', 'woocommerce-pdf-invoices-packing-slips' ),
214
+ 'translatable' => true,
215
+ )
216
+ ),
217
+ );
218
+
219
+ // allow plugins to alter settings fields
220
+ $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_general', $settings_fields, $page, $option_group, $option_name );
221
+ WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
222
+ return;
223
+ }
224
+
225
+ public function attachment_settings_hint( $active_tab, $active_section ) {
226
+ // save or check option to hide attachments settings hint
227
+ if ( isset( $_GET['wpo_wcpdf_hide_attachments_hint'] ) ) {
228
+ update_option( 'wpo_wcpdf_hide_attachments_hint', true );
229
+ $hide_hint = true;
230
+ } else {
231
+ $hide_hint = get_option( 'wpo_wcpdf_hide_attachments_hint' );
232
+ }
233
+
234
+ if ( $active_tab == 'general' && !$hide_hint ) {
235
+ $documents = WPO_WCPDF()->documents->get_documents();
236
+
237
+ foreach ($documents as $document) {
238
+ if ( $document->get_type() == 'invoice' ) {
239
+ $invoice_email_ids = $document->get_attach_to_email_ids();
240
+ if (empty($invoice_email_ids)) {
241
+ include_once( WPO_WCPDF()->plugin_path() . '/includes/views/attachment-settings-hint.php' );
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * List templates in plugin folder, theme folder & child theme folder
250
+ * @return array template path => template name
251
+ */
252
+ public function find_templates() {
253
+ $installed_templates = array();
254
+
255
+ // get base paths
256
+ $template_base_path = ( defined( 'WC_TEMPLATE_PATH' ) ? WC_TEMPLATE_PATH : $GLOBALS['woocommerce']->template_url );
257
+ $template_base_path = untrailingslashit( $template_base_path );
258
+ $template_paths = array (
259
+ // note the order: child-theme before theme, so that array_unique filters out parent doubles
260
+ 'default' => WPO_WCPDF()->plugin_path() . '/templates/',
261
+ 'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
262
+ 'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
263
+ );
264
+
265
+ $template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
266
+
267
+ if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
268
+ $forwardslash_basepath = str_replace('\\','/', ABSPATH);
269
+ } else {
270
+ $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
271
+ }
272
+
273
+ foreach ($template_paths as $template_source => $template_path) {
274
+ $dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR);
275
+
276
+ foreach ($dirs as $dir) {
277
+ // we're stripping abspath to make the plugin settings more portable
278
+ $forwardslash_dir = str_replace('\\','/', $dir);
279
+ $installed_templates[ str_replace( $forwardslash_basepath, '', $forwardslash_dir ) ] = basename($dir);
280
+ }
281
+ }
282
+
283
+ // remove parent doubles
284
+ $installed_templates = array_unique($installed_templates);
285
+
286
+ if (empty($installed_templates)) {
287
+ // fallback to Simple template for servers with glob() disabled
288
+ $simple_template_path = str_replace( ABSPATH, '', $template_paths['default'] . 'Simple' );
289
+ $installed_templates[$simple_template_path] = 'Simple';
290
+ }
291
+
292
+ return apply_filters( 'wpo_wcpdf_templates', $installed_templates );
293
+ }
294
+
295
+ }
296
+
297
+ endif; // class_exists
298
+
299
  return new Settings_General();
includes/class-wcpdf-settings.php CHANGED
@@ -1,255 +1,255 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- use WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store;
5
-
6
- if ( ! defined( 'ABSPATH' ) ) {
7
- exit; // Exit if accessed directly
8
- }
9
-
10
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings' ) ) :
11
-
12
- class Settings {
13
- public $options_page_hook;
14
-
15
- function __construct() {
16
- $this->callbacks = include( 'class-wcpdf-settings-callbacks.php' );
17
-
18
- // include settings classes
19
- $this->general = include( 'class-wcpdf-settings-general.php' );
20
- $this->documents = include( 'class-wcpdf-settings-documents.php' );
21
- $this->debug = include( 'class-wcpdf-settings-debug.php' );
22
-
23
-
24
- // Settings menu item
25
- add_action( 'admin_menu', array( $this, 'menu' ) ); // Add menu.
26
- // Links on plugin page
27
- add_filter( 'plugin_action_links_'.WPO_WCPDF()->plugin_basename, array( $this, 'add_settings_link' ) );
28
- add_filter( 'plugin_row_meta', array( $this, 'add_support_links' ), 10, 2 );
29
-
30
- // settings capabilities
31
- add_filter( 'option_page_capability_wpo_wcpdf_general_settings', array( $this, 'settings_capabilities' ) );
32
-
33
- $this->general_settings = get_option('wpo_wcpdf_settings_general');
34
- $this->debug_settings = get_option('wpo_wcpdf_settings_debug');
35
-
36
- // admin notice for auto_increment_increment
37
- // add_action( 'admin_notices', array( $this, 'check_auto_increment_increment') );
38
-
39
- // AJAX set number store
40
- add_action( 'wp_ajax_wpo_wcpdf_set_next_number', array($this, 'set_number_store' ));
41
- }
42
-
43
- public function menu() {
44
- $parent_slug = 'woocommerce';
45
-
46
- $this->options_page_hook = add_submenu_page(
47
- $parent_slug,
48
- __( 'PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ),
49
- __( 'PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ),
50
- 'manage_woocommerce',
51
- 'wpo_wcpdf_options_page',
52
- array( $this, 'settings_page' )
53
- );
54
- }
55
-
56
- /**
57
- * Add settings link to plugins page
58
- */
59
- public function add_settings_link( $links ) {
60
- $action_links = array(
61
- 'settings' => '<a href="admin.php?page=wpo_wcpdf_options_page">'. __( 'Settings', 'woocommerce' ) . '</a>',
62
- );
63
-
64
- return array_merge( $action_links, $links );
65
- }
66
-
67
- /**
68
- * Add various support links to plugin page
69
- * after meta (version, authors, site)
70
- */
71
- public function add_support_links( $links, $file ) {
72
- if ( $file == WPO_WCPDF()->plugin_basename ) {
73
- $row_meta = array(
74
- 'docs' => '<a href="http://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/" target="_blank" title="' . __( 'Documentation', 'woocommerce-pdf-invoices-packing-slips' ) . '">' . __( 'Documentation', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
75
- 'support' => '<a href="https://wordpress.org/support/plugin/woocommerce-pdf-invoices-packing-slips" target="_blank" title="' . __( 'Support Forum', 'woocommerce-pdf-invoices-packing-slips' ) . '">' . __( 'Support Forum', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
76
- );
77
-
78
- return array_merge( $links, $row_meta );
79
- }
80
- return (array) $links;
81
- }
82
-
83
- function check_auto_increment_increment() {
84
- global $wpdb;
85
- $row = $wpdb->get_row("SHOW VARIABLES LIKE 'auto_increment_increment'");
86
- if ( !empty($row) && !empty($row->Value) && $row->Value != 1 ) {
87
- $error = sprintf( __( "<strong>Warning!</strong> Your database has an AUTO_INCREMENT step size of %s, your invoice numbers may not be sequential. Enable the 'Calculate document numbers (slow)' setting in the Status tab to use an alternate method." , 'woocommerce-pdf-invoices-packing-slips' ), $row->Value );
88
- printf( '<div class="error"><p>%s</p></div>', $error );
89
- }
90
- }
91
-
92
-
93
- public function settings_page() {
94
- $settings_tabs = apply_filters( 'wpo_wcpdf_settings_tabs', array (
95
- 'general' => __('General', 'woocommerce-pdf-invoices-packing-slips' ),
96
- 'documents' => __('Documents', 'woocommerce-pdf-invoices-packing-slips' ),
97
- )
98
- );
99
-
100
- // add status tab last in row
101
- $settings_tabs['debug'] = __('Status', 'woocommerce-pdf-invoices-packing-slips' );
102
-
103
- $active_tab = isset( $_GET[ 'tab' ] ) ? sanitize_text_field( $_GET[ 'tab' ] ) : 'general';
104
- $active_section = isset( $_GET[ 'section' ] ) ? sanitize_text_field( $_GET[ 'section' ] ) : '';
105
-
106
- include('views/wcpdf-settings-page.php');
107
- }
108
-
109
- public function add_settings_fields( $settings_fields, $page, $option_group, $option_name ) {
110
- foreach ( $settings_fields as $settings_field ) {
111
- if (!isset($settings_field['callback'])) {
112
- continue;
113
- } elseif ( is_callable( array( $this->callbacks, $settings_field['callback'] ) ) ) {
114
- $callback = array( $this->callbacks, $settings_field['callback'] );
115
- } elseif ( is_callable( $settings_field['callback'] ) ) {
116
- $callback = $settings_field['callback'];
117
- } else {
118
- continue;
119
- }
120
-
121
- if ( $settings_field['type'] == 'section' ) {
122
- add_settings_section(
123
- $settings_field['id'],
124
- $settings_field['title'],
125
- $callback,
126
- $page
127
- );
128
- } else {
129
- add_settings_field(
130
- $settings_field['id'],
131
- $settings_field['title'],
132
- $callback,
133
- $page,
134
- $settings_field['section'],
135
- $settings_field['args']
136
- );
137
- // register option separately for singular options
138
- if (is_string($settings_field['callback']) && $settings_field['callback'] == 'singular_text_element') {
139
- register_setting( $option_group, $settings_field['args']['option_name'], array( $this->callbacks, 'validate' ) );
140
- }
141
- }
142
- }
143
- // $page, $option_group & $option_name are all the same...
144
- register_setting( $option_group, $option_name, array( $this->callbacks, 'validate' ) );
145
- add_filter( 'option_page_capability_'.$page, array( $this, 'settings_capabilities' ) );
146
-
147
- }
148
-
149
- /**
150
- * Set capability for settings page
151
- */
152
- public function settings_capabilities() {
153
- return 'manage_woocommerce';
154
- }
155
-
156
- public function get_common_document_settings() {
157
- $common_settings = array(
158
- 'paper_size' => isset( $this->general_settings['paper_size'] ) ? $this->general_settings['paper_size'] : '',
159
- 'font_subsetting' => isset( $this->general_settings['font_subsetting'] ) || ( defined("DOMPDF_ENABLE_FONTSUBSETTING") && DOMPDF_ENABLE_FONTSUBSETTING === true ) ? true : false,
160
- 'header_logo' => isset( $this->general_settings['header_logo'] ) ? $this->general_settings['header_logo'] : '',
161
- 'shop_name' => isset( $this->general_settings['shop_name'] ) ? $this->general_settings['shop_name'] : '',
162
- 'shop_address' => isset( $this->general_settings['shop_address'] ) ? $this->general_settings['shop_address'] : '',
163
- 'footer' => isset( $this->general_settings['footer'] ) ? $this->general_settings['footer'] : '',
164
- 'extra_1' => isset( $this->general_settings['extra_1'] ) ? $this->general_settings['extra_1'] : '',
165
- 'extra_2' => isset( $this->general_settings['extra_2'] ) ? $this->general_settings['extra_2'] : '',
166
- 'extra_3' => isset( $this->general_settings['extra_3'] ) ? $this->general_settings['extra_3'] : '',
167
- );
168
- return $common_settings;
169
- }
170
-
171
- public function get_document_settings( $document_type ) {
172
- $documents = WPO_WCPDF()->documents->get_documents('all');
173
- foreach ($documents as $document) {
174
- if ( $document->get_type() == $document_type ) {
175
- return $document->settings;
176
- }
177
- }
178
- return false;
179
- }
180
-
181
- public function get_output_format() {
182
- if ( isset( $this->debug_settings['html_output'] ) ) {
183
- $output_format = 'html';
184
- } else {
185
- $output_format = 'pdf';
186
- }
187
- return $output_format;
188
- }
189
-
190
- public function get_output_mode() {
191
- if ( isset( WPO_WCPDF()->settings->general_settings['download_display'] ) ) {
192
- switch ( WPO_WCPDF()->settings->general_settings['download_display'] ) {
193
- case 'display':
194
- $output_mode = 'inline';
195
- break;
196
- case 'download':
197
- default:
198
- $output_mode = 'download';
199
- break;
200
- }
201
- } else {
202
- $output_mode = 'download';
203
- }
204
- return $output_mode;
205
- }
206
-
207
- public function get_template_path( $document_type = NULL ) {
208
- $template_path = isset( $this->general_settings['template_path'] )?$this->general_settings['template_path']:'';
209
- // forward slash for consistency
210
- $template_path = str_replace('\\','/', $template_path);
211
-
212
- // add base path, checking if it's not already there
213
- // alternative setups like Bedrock have WP_CONTENT_DIR & ABSPATH separated
214
- if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
215
- $forwardslash_basepath = str_replace('\\','/', ABSPATH);
216
- } else {
217
- // bedrock e.a
218
- $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
219
- }
220
-
221
- if ( strpos( $template_path, $forwardslash_basepath ) === false ) {
222
- $template_path = $forwardslash_basepath . $template_path;
223
- }
224
-
225
- return $template_path;
226
- }
227
-
228
- public function set_number_store() {
229
- check_ajax_referer( "wpo_wcpdf_next_{$_POST['store']}", 'security' );
230
- $number = isset( $_POST['number'] ) ? (int) $_POST['number'] : 0;
231
- $number_store_method = $this->get_sequential_number_store_method();
232
- $number_store = new Sequential_Number_Store( $_POST['store'], $number_store_method );
233
- $number_store->set_next( $number );
234
- echo "next number ({$_POST['store']}) set to {$number}";
235
- die();
236
- }
237
-
238
- public function get_sequential_number_store_method() {
239
- global $wpdb;
240
- $method = isset( $this->debug_settings['calculate_document_numbers'] ) ? 'calculate' : 'auto_increment';
241
-
242
- // safety first - always use calculate when auto_increment_increment is not 1
243
- $row = $wpdb->get_row("SHOW VARIABLES LIKE 'auto_increment_increment'");
244
- if ( !empty($row) && !empty($row->Value) && $row->Value != 1 ) {
245
- $method = 'calculate';
246
- }
247
-
248
- return $method;
249
- }
250
-
251
- }
252
-
253
- endif; // class_exists
254
-
255
  return new Settings();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ use WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store;
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit; // Exit if accessed directly
8
+ }
9
+
10
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Settings' ) ) :
11
+
12
+ class Settings {
13
+ public $options_page_hook;
14
+
15
+ function __construct() {
16
+ $this->callbacks = include( 'class-wcpdf-settings-callbacks.php' );
17
+
18
+ // include settings classes
19
+ $this->general = include( 'class-wcpdf-settings-general.php' );
20
+ $this->documents = include( 'class-wcpdf-settings-documents.php' );
21
+ $this->debug = include( 'class-wcpdf-settings-debug.php' );
22
+
23
+
24
+ // Settings menu item
25
+ add_action( 'admin_menu', array( $this, 'menu' ) ); // Add menu.
26
+ // Links on plugin page
27
+ add_filter( 'plugin_action_links_'.WPO_WCPDF()->plugin_basename, array( $this, 'add_settings_link' ) );
28
+ add_filter( 'plugin_row_meta', array( $this, 'add_support_links' ), 10, 2 );
29
+
30
+ // settings capabilities
31
+ add_filter( 'option_page_capability_wpo_wcpdf_general_settings', array( $this, 'settings_capabilities' ) );
32
+
33
+ $this->general_settings = get_option('wpo_wcpdf_settings_general');
34
+ $this->debug_settings = get_option('wpo_wcpdf_settings_debug');
35
+
36
+ // admin notice for auto_increment_increment
37
+ // add_action( 'admin_notices', array( $this, 'check_auto_increment_increment') );
38
+
39
+ // AJAX set number store
40
+ add_action( 'wp_ajax_wpo_wcpdf_set_next_number', array($this, 'set_number_store' ));
41
+ }
42
+
43
+ public function menu() {
44
+ $parent_slug = 'woocommerce';
45
+
46
+ $this->options_page_hook = add_submenu_page(
47
+ $parent_slug,
48
+ __( 'PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ),
49
+ __( 'PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ),
50
+ 'manage_woocommerce',
51
+ 'wpo_wcpdf_options_page',
52
+ array( $this, 'settings_page' )
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Add settings link to plugins page
58
+ */
59
+ public function add_settings_link( $links ) {
60
+ $action_links = array(
61
+ 'settings' => '<a href="admin.php?page=wpo_wcpdf_options_page">'. __( 'Settings', 'woocommerce' ) . '</a>',
62
+ );
63
+
64
+ return array_merge( $action_links, $links );
65
+ }
66
+
67
+ /**
68
+ * Add various support links to plugin page
69
+ * after meta (version, authors, site)
70
+ */
71
+ public function add_support_links( $links, $file ) {
72
+ if ( $file == WPO_WCPDF()->plugin_basename ) {
73
+ $row_meta = array(
74
+ 'docs' => '<a href="http://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/" target="_blank" title="' . __( 'Documentation', 'woocommerce-pdf-invoices-packing-slips' ) . '">' . __( 'Documentation', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
75
+ 'support' => '<a href="https://wordpress.org/support/plugin/woocommerce-pdf-invoices-packing-slips" target="_blank" title="' . __( 'Support Forum', 'woocommerce-pdf-invoices-packing-slips' ) . '">' . __( 'Support Forum', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
76
+ );
77
+
78
+ return array_merge( $links, $row_meta );
79
+ }
80
+ return (array) $links;
81
+ }
82
+
83
+ function check_auto_increment_increment() {
84
+ global $wpdb;
85
+ $row = $wpdb->get_row("SHOW VARIABLES LIKE 'auto_increment_increment'");
86
+ if ( !empty($row) && !empty($row->Value) && $row->Value != 1 ) {
87
+ $error = sprintf( __( "<strong>Warning!</strong> Your database has an AUTO_INCREMENT step size of %s, your invoice numbers may not be sequential. Enable the 'Calculate document numbers (slow)' setting in the Status tab to use an alternate method." , 'woocommerce-pdf-invoices-packing-slips' ), $row->Value );
88
+ printf( '<div class="error"><p>%s</p></div>', $error );
89
+ }
90
+ }
91
+
92
+
93
+ public function settings_page() {
94
+ $settings_tabs = apply_filters( 'wpo_wcpdf_settings_tabs', array (
95
+ 'general' => __('General', 'woocommerce-pdf-invoices-packing-slips' ),
96
+ 'documents' => __('Documents', 'woocommerce-pdf-invoices-packing-slips' ),
97
+ )
98
+ );
99
+
100
+ // add status tab last in row
101
+ $settings_tabs['debug'] = __('Status', 'woocommerce-pdf-invoices-packing-slips' );
102
+
103
+ $active_tab = isset( $_GET[ 'tab' ] ) ? sanitize_text_field( $_GET[ 'tab' ] ) : 'general';
104
+ $active_section = isset( $_GET[ 'section' ] ) ? sanitize_text_field( $_GET[ 'section' ] ) : '';
105
+
106
+ include('views/wcpdf-settings-page.php');
107
+ }
108
+
109
+ public function add_settings_fields( $settings_fields, $page, $option_group, $option_name ) {
110
+ foreach ( $settings_fields as $settings_field ) {
111
+ if (!isset($settings_field['callback'])) {
112
+ continue;
113
+ } elseif ( is_callable( array( $this->callbacks, $settings_field['callback'] ) ) ) {
114
+ $callback = array( $this->callbacks, $settings_field['callback'] );
115
+ } elseif ( is_callable( $settings_field['callback'] ) ) {
116
+ $callback = $settings_field['callback'];
117
+ } else {
118
+ continue;
119
+ }
120
+
121
+ if ( $settings_field['type'] == 'section' ) {
122
+ add_settings_section(
123
+ $settings_field['id'],
124
+ $settings_field['title'],
125
+ $callback,
126
+ $page
127
+ );
128
+ } else {
129
+ add_settings_field(
130
+ $settings_field['id'],
131
+ $settings_field['title'],
132
+ $callback,
133
+ $page,
134
+ $settings_field['section'],
135
+ $settings_field['args']
136
+ );
137
+ // register option separately for singular options
138
+ if (is_string($settings_field['callback']) && $settings_field['callback'] == 'singular_text_element') {
139
+ register_setting( $option_group, $settings_field['args']['option_name'], array( $this->callbacks, 'validate' ) );
140
+ }
141
+ }
142
+ }
143
+ // $page, $option_group & $option_name are all the same...
144
+ register_setting( $option_group, $option_name, array( $this->callbacks, 'validate' ) );
145
+ add_filter( 'option_page_capability_'.$page, array( $this, 'settings_capabilities' ) );
146
+
147
+ }
148
+
149
+ /**
150
+ * Set capability for settings page
151
+ */
152
+ public function settings_capabilities() {
153
+ return 'manage_woocommerce';
154
+ }
155
+
156
+ public function get_common_document_settings() {
157
+ $common_settings = array(
158
+ 'paper_size' => isset( $this->general_settings['paper_size'] ) ? $this->general_settings['paper_size'] : '',
159
+ 'font_subsetting' => isset( $this->general_settings['font_subsetting'] ) || ( defined("DOMPDF_ENABLE_FONTSUBSETTING") && DOMPDF_ENABLE_FONTSUBSETTING === true ) ? true : false,
160
+ 'header_logo' => isset( $this->general_settings['header_logo'] ) ? $this->general_settings['header_logo'] : '',
161
+ 'shop_name' => isset( $this->general_settings['shop_name'] ) ? $this->general_settings['shop_name'] : '',
162
+ 'shop_address' => isset( $this->general_settings['shop_address'] ) ? $this->general_settings['shop_address'] : '',
163
+ 'footer' => isset( $this->general_settings['footer'] ) ? $this->general_settings['footer'] : '',
164
+ 'extra_1' => isset( $this->general_settings['extra_1'] ) ? $this->general_settings['extra_1'] : '',
165
+ 'extra_2' => isset( $this->general_settings['extra_2'] ) ? $this->general_settings['extra_2'] : '',
166
+ 'extra_3' => isset( $this->general_settings['extra_3'] ) ? $this->general_settings['extra_3'] : '',
167
+ );
168
+ return $common_settings;
169
+ }
170
+
171
+ public function get_document_settings( $document_type ) {
172
+ $documents = WPO_WCPDF()->documents->get_documents('all');
173
+ foreach ($documents as $document) {
174
+ if ( $document->get_type() == $document_type ) {
175
+ return $document->settings;
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+
181
+ public function get_output_format() {
182
+ if ( isset( $this->debug_settings['html_output'] ) ) {
183
+ $output_format = 'html';
184
+ } else {
185
+ $output_format = 'pdf';
186
+ }
187
+ return $output_format;
188
+ }
189
+
190
+ public function get_output_mode() {
191
+ if ( isset( WPO_WCPDF()->settings->general_settings['download_display'] ) ) {
192
+ switch ( WPO_WCPDF()->settings->general_settings['download_display'] ) {
193
+ case 'display':
194
+ $output_mode = 'inline';
195
+ break;
196
+ case 'download':
197
+ default:
198
+ $output_mode = 'download';
199
+ break;
200
+ }
201
+ } else {
202
+ $output_mode = 'download';
203
+ }
204
+ return $output_mode;
205
+ }
206
+
207
+ public function get_template_path( $document_type = NULL ) {
208
+ $template_path = isset( $this->general_settings['template_path'] )?$this->general_settings['template_path']:'';
209
+ // forward slash for consistency
210
+ $template_path = str_replace('\\','/', $template_path);
211
+
212
+ // add base path, checking if it's not already there
213
+ // alternative setups like Bedrock have WP_CONTENT_DIR & ABSPATH separated
214
+ if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
215
+ $forwardslash_basepath = str_replace('\\','/', ABSPATH);
216
+ } else {
217
+ // bedrock e.a
218
+ $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
219
+ }
220
+
221
+ if ( strpos( $template_path, $forwardslash_basepath ) === false ) {
222
+ $template_path = $forwardslash_basepath . $template_path;
223
+ }
224
+
225
+ return $template_path;
226
+ }
227
+
228
+ public function set_number_store() {
229
+ check_ajax_referer( "wpo_wcpdf_next_{$_POST['store']}", 'security' );
230
+ $number = isset( $_POST['number'] ) ? (int) $_POST['number'] : 0;
231
+ $number_store_method = $this->get_sequential_number_store_method();
232
+ $number_store = new Sequential_Number_Store( $_POST['store'], $number_store_method );
233
+ $number_store->set_next( $number );
234
+ echo "next number ({$_POST['store']}) set to {$number}";
235
+ die();
236
+ }
237
+
238
+ public function get_sequential_number_store_method() {
239
+ global $wpdb;
240
+ $method = isset( $this->debug_settings['calculate_document_numbers'] ) ? 'calculate' : 'auto_increment';
241
+
242
+ // safety first - always use calculate when auto_increment_increment is not 1
243
+ $row = $wpdb->get_row("SHOW VARIABLES LIKE 'auto_increment_increment'");
244
+ if ( !empty($row) && !empty($row->Value) && $row->Value != 1 ) {
245
+ $method = 'calculate';
246
+ }
247
+
248
+ return $method;
249
+ }
250
+
251
+ }
252
+
253
+ endif; // class_exists
254
+
255
  return new Settings();
includes/class-wcpdf-setup-wizard.php CHANGED
@@ -1,247 +1,247 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Setup_Wizard' ) ) :
9
-
10
- class Setup_Wizard {
11
-
12
- /** @var string Currenct Step */
13
- private $step = '';
14
-
15
- /** @var array Steps for the setup wizard */
16
- private $steps = array();
17
-
18
- public function __construct() {
19
- if ( current_user_can( 'manage_woocommerce' ) ) {
20
- add_action( 'admin_menu', array( $this, 'admin_menus' ) );
21
- add_action( 'admin_init', array( $this, 'setup_wizard' ) );
22
- }
23
-
24
- }
25
-
26
- /**
27
- * Add admin menus/screens.
28
- */
29
- public function admin_menus() {
30
- add_dashboard_page( '', '', 'manage_options', 'wpo-wcpdf-setup', '' );
31
- }
32
-
33
- /**
34
- * Show the setup wizard.
35
- */
36
- public function setup_wizard() {
37
- if ( empty( $_GET['page'] ) || 'wpo-wcpdf-setup' !== $_GET['page'] ) {
38
- return;
39
- }
40
- $this->steps = array(
41
- 'shop-name' => array(
42
- 'name' => __( 'Shop Name', 'woocommerce-pdf-invoices-packing-slips' ),
43
- 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/shop-name.php',
44
- ),
45
- 'logo' => array(
46
- 'name' => __( 'Your logo', 'woocommerce-pdf-invoices-packing-slips' ),
47
- 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/logo.php',
48
- ),
49
- 'attach-to' => array(
50
- 'name' => __( 'Attachments', 'woocommerce-pdf-invoices-packing-slips' ),
51
- 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/attach-to.php',
52
- ),
53
- 'display-options' => array(
54
- 'name' => __( 'Display options', 'woocommerce-pdf-invoices-packing-slips' ),
55
- 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/display-options.php',
56
- ),
57
- 'paper-format' => array(
58
- 'name' => __( 'Paper format', 'woocommerce-pdf-invoices-packing-slips' ),
59
- 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/paper-format.php',
60
- ),
61
- 'good-to-go' => array(
62
- 'name' => __( 'Ready!', 'woocommerce-pdf-invoices-packing-slips' ),
63
- 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/good-to-go.php',
64
- ),
65
- );
66
- $this->step = isset( $_GET['step'] ) ? sanitize_key( $_GET['step'] ) : current( array_keys( $this->steps ) );
67
-
68
- wp_enqueue_style(
69
- 'wpo-wcpdf-setup',
70
- WPO_WCPDF()->plugin_url() . '/assets/css/setup-wizard.css',
71
- array( 'dashicons', 'install' ),
72
- WPO_WCPDF_VERSION
73
- );
74
- wp_register_script(
75
- 'wpo-wcpdf-media-upload',
76
- WPO_WCPDF()->plugin_url() . '/assets/js/media-upload.js',
77
- array( 'jquery', 'media-editor', 'mce-view' ),
78
- WPO_WCPDF_VERSION
79
- );
80
- wp_register_script(
81
- 'wpo-wcpdf-setup',
82
- WPO_WCPDF()->plugin_url() . '/assets/js/setup-wizard.js',
83
- array( 'jquery', 'wpo-wcpdf-media-upload' ),
84
- WPO_WCPDF_VERSION
85
- );
86
- wp_enqueue_media();
87
-
88
-
89
-
90
- $step_keys = array_keys($this->steps);
91
- if ( end( $step_keys ) === $this->step ) {
92
- wp_register_script(
93
- 'wpo-wcpdf-setup-confetti',
94
- WPO_WCPDF()->plugin_url() . '/assets/js/confetti.js',
95
- array( 'jquery' ),
96
- WPO_WCPDF_VERSION
97
- );
98
- }
99
-
100
- if ( ! empty( $_POST['save_step'] ) ) {
101
- $this->save_step();
102
- // echo '<pre>';var_dump($_POST);echo '</pre>';die();
103
- // call_user_func( $this->steps[ $this->step ]['handler'], $this );
104
- }
105
-
106
- ob_start();
107
- $this->setup_wizard_header();
108
- $this->setup_wizard_steps();
109
- $this->setup_wizard_content();
110
- $this->setup_wizard_footer();
111
- exit;
112
- }
113
-
114
- /**
115
- * Setup Wizard Header.
116
- */
117
- public function setup_wizard_header() {
118
- ?>
119
- <!DOCTYPE html>
120
- <html <?php language_attributes(); ?> class="wpo-wizzard">
121
- <head>
122
- <meta name="viewport" content="width=device-width" />
123
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
124
- <title><?php esc_html_e( 'WooCommerce PDF Invoices & Packing Slips &rsaquo; Setup Wizard', 'woocommerce-pdf-invoices-packing-slips' ); ?></title>
125
- <?php wp_print_scripts( 'wpo-wcpdf-setup' ); ?>
126
- <?php wp_print_scripts( 'wpo-wcpdf-setup-confetti' ); ?>
127
- <?php do_action( 'admin_print_styles' ); ?>
128
- <?php do_action( 'admin_head' ); ?>
129
- </head>
130
- <body class="wpo-wcpdf-setup wp-core-ui">
131
- <?php if( $this->step == 'good-to-go' ) { echo "<div id='confetti'></div>"; } ?>
132
- <form method="post">
133
- <?php
134
- }
135
-
136
- /**
137
- * Output the steps.
138
- */
139
- public function setup_wizard_steps() {
140
- $output_steps = $this->steps;
141
- // array_shift( $output_steps );
142
- ?>
143
- <div class="wpo-setup-card">
144
- <h1 class="wpo-plugin-title">PDF Invoices & Packing Slips</h1>
145
- <ol class="wpo-progress-bar">
146
- <?php foreach ( $output_steps as $step_key => $step ) : ?>
147
- <a href="<?php echo $this->get_step_link($step_key); ?>" ><li><div class="wpo-progress-marker <?php
148
- if ( $step_key === $this->step ) {
149
- echo 'active';
150
- } elseif ( array_search( $this->step, array_keys( $this->steps ) ) > array_search( $step_key, array_keys( $this->steps ) ) ) {
151
- echo 'completed';
152
- }
153
- ?>"><?php //echo esc_html( $step['name'] ); ?></div></li></a>
154
- <?php endforeach; ?>
155
- </ol>
156
- <?php
157
- }
158
-
159
- /**
160
- * Output the content for the current step.
161
- */
162
- public function setup_wizard_content() {
163
- echo '<div class="wpo-setup-content">';
164
- include( $this->steps[ $this->step ]['view'] );
165
- echo '</div>';
166
- }
167
-
168
- /**
169
- * Setup Wizard Footer.
170
- */
171
- public function setup_wizard_footer() {
172
- ?>
173
- <div class="wpo-setup-buttons">
174
- <?php if ($step = $this->get_step(-1)): ?>
175
- <a href="<?php echo $this->get_step_link($step); ?>" class="wpo-button-previous"><?php _e( 'Previous', 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
176
- <?php endif ?>
177
- <!-- <input type="submit" class="wpo-button-next" value="Next" /> -->
178
- <?php if ($step = $this->get_step(1)): ?>
179
- <?php wp_nonce_field( 'wpo-wcpdf-setup' ); ?>
180
- <input type="submit" class="wpo-button-next" value="<?php esc_attr_e( 'Next', 'woocommerce-pdf-invoices-packing-slips' ); ?>" name="save_step" />
181
- <a href="<?php echo $this->get_step_link($step); ?>" class="wpo-skip-step"><?php _e( 'Skip this step', 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
182
- <?php else: ?>
183
- <a href="<?php echo $this->get_step_link($step); ?>" class="wpo-button-next"><?php _e( 'Finish', 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
184
- <?php endif ?>
185
- </div>
186
- </div>
187
- </form>
188
- <?php do_action( 'admin_footer' ); // for media uploader templates ?>
189
- </body>
190
- </html>
191
- <?php
192
- }
193
-
194
- public function get_step_link( $step ) {
195
- $step_keys = array_keys( $this->steps );
196
- if ( end( $step_keys ) === $this->step && empty($step)) {
197
- return admin_url();
198
- }
199
- return add_query_arg( 'step', $step );
200
- }
201
-
202
-
203
- public function get_step( $delta ) {
204
- $step_keys = array_keys( $this->steps );
205
- $current_step_pos = array_search( $this->step, $step_keys );
206
- $new_step_pos = $current_step_pos + $delta;
207
- if (isset($step_keys[$new_step_pos])) {
208
- return $step_keys[$new_step_pos];
209
- } else {
210
- return false;
211
- }
212
- }
213
-
214
- public function save_step() {
215
- if ( isset( $this->steps[ $this->step ]['handler'] ) ) {
216
- check_admin_referer( 'wpo-wcpdf-setup' );
217
- // for doing more than just saving an option value
218
- call_user_func( $this->steps[ $this->step ]['handler'] );
219
- } else {
220
- if (!empty($_POST['wcpdf_settings']) && is_array($_POST['wcpdf_settings'])) {
221
- check_admin_referer( 'wpo-wcpdf-setup' );
222
- foreach ($_POST['wcpdf_settings'] as $option => $settings) {
223
- // sanitize posted settings
224
- foreach ($settings as $key => $value) {
225
- if (is_array($value)) {
226
- $settings[$key] = array_map('sanitize_text_field', $value);
227
- } else {
228
- $settings[$key] = sanitize_text_field( $value );
229
- }
230
- }
231
- $current_settings = get_option( $option, array() );
232
- $new_settings = $settings + $current_settings;
233
- // echo "<pre>".var_export($settings,true)."</pre>";
234
- // echo "<pre>".var_export($new_settings,true)."</pre>";die();
235
- update_option( $option, $new_settings );
236
- }
237
- }
238
- }
239
-
240
- wp_redirect( esc_url_raw( $this->get_step_link( $this->get_step(1) ) ) );
241
- }
242
-
243
- }
244
-
245
- endif; // class_exists
246
-
247
  return new Setup_Wizard();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Setup_Wizard' ) ) :
9
+
10
+ class Setup_Wizard {
11
+
12
+ /** @var string Currenct Step */
13
+ private $step = '';
14
+
15
+ /** @var array Steps for the setup wizard */
16
+ private $steps = array();
17
+
18
+ public function __construct() {
19
+ if ( current_user_can( 'manage_woocommerce' ) ) {
20
+ add_action( 'admin_menu', array( $this, 'admin_menus' ) );
21
+ add_action( 'admin_init', array( $this, 'setup_wizard' ) );
22
+ }
23
+
24
+ }
25
+
26
+ /**
27
+ * Add admin menus/screens.
28
+ */
29
+ public function admin_menus() {
30
+ add_dashboard_page( '', '', 'manage_options', 'wpo-wcpdf-setup', '' );
31
+ }
32
+
33
+ /**
34
+ * Show the setup wizard.
35
+ */
36
+ public function setup_wizard() {
37
+ if ( empty( $_GET['page'] ) || 'wpo-wcpdf-setup' !== $_GET['page'] ) {
38
+ return;
39
+ }
40
+ $this->steps = array(
41
+ 'shop-name' => array(
42
+ 'name' => __( 'Shop Name', 'woocommerce-pdf-invoices-packing-slips' ),
43
+ 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/shop-name.php',
44
+ ),
45
+ 'logo' => array(
46
+ 'name' => __( 'Your logo', 'woocommerce-pdf-invoices-packing-slips' ),
47
+ 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/logo.php',
48
+ ),
49
+ 'attach-to' => array(
50
+ 'name' => __( 'Attachments', 'woocommerce-pdf-invoices-packing-slips' ),
51
+ 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/attach-to.php',
52
+ ),
53
+ 'display-options' => array(
54
+ 'name' => __( 'Display options', 'woocommerce-pdf-invoices-packing-slips' ),
55
+ 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/display-options.php',
56
+ ),
57
+ 'paper-format' => array(
58
+ 'name' => __( 'Paper format', 'woocommerce-pdf-invoices-packing-slips' ),
59
+ 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/paper-format.php',
60
+ ),
61
+ 'good-to-go' => array(
62
+ 'name' => __( 'Ready!', 'woocommerce-pdf-invoices-packing-slips' ),
63
+ 'view' => WPO_WCPDF()->plugin_path() . '/includes/views/setup-wizard/good-to-go.php',
64
+ ),
65
+ );
66
+ $this->step = isset( $_GET['step'] ) ? sanitize_key( $_GET['step'] ) : current( array_keys( $this->steps ) );
67
+
68
+ wp_enqueue_style(
69
+ 'wpo-wcpdf-setup',
70
+ WPO_WCPDF()->plugin_url() . '/assets/css/setup-wizard.css',
71
+ array( 'dashicons', 'install' ),
72
+ WPO_WCPDF_VERSION
73
+ );
74
+ wp_register_script(
75
+ 'wpo-wcpdf-media-upload',
76
+ WPO_WCPDF()->plugin_url() . '/assets/js/media-upload.js',
77
+ array( 'jquery', 'media-editor', 'mce-view' ),
78
+ WPO_WCPDF_VERSION
79
+ );
80
+ wp_register_script(
81
+ 'wpo-wcpdf-setup',
82
+ WPO_WCPDF()->plugin_url() . '/assets/js/setup-wizard.js',
83
+ array( 'jquery', 'wpo-wcpdf-media-upload' ),
84
+ WPO_WCPDF_VERSION
85
+ );
86
+ wp_enqueue_media();
87
+
88
+
89
+
90
+ $step_keys = array_keys($this->steps);
91
+ if ( end( $step_keys ) === $this->step ) {
92
+ wp_register_script(
93
+ 'wpo-wcpdf-setup-confetti',
94
+ WPO_WCPDF()->plugin_url() . '/assets/js/confetti.js',
95
+ array( 'jquery' ),
96
+ WPO_WCPDF_VERSION
97
+ );
98
+ }
99
+
100
+ if ( ! empty( $_POST['save_step'] ) ) {
101
+ $this->save_step();
102
+ // echo '<pre>';var_dump($_POST);echo '</pre>';die();
103
+ // call_user_func( $this->steps[ $this->step ]['handler'], $this );
104
+ }
105
+
106
+ ob_start();
107
+ $this->setup_wizard_header();
108
+ $this->setup_wizard_steps();
109
+ $this->setup_wizard_content();
110
+ $this->setup_wizard_footer();
111
+ exit;
112
+ }
113
+
114
+ /**
115
+ * Setup Wizard Header.
116
+ */
117
+ public function setup_wizard_header() {
118
+ ?>
119
+ <!DOCTYPE html>
120
+ <html <?php language_attributes(); ?> class="wpo-wizzard">
121
+ <head>
122
+ <meta name="viewport" content="width=device-width" />
123
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
124
+ <title><?php esc_html_e( 'WooCommerce PDF Invoices & Packing Slips &rsaquo; Setup Wizard', 'woocommerce-pdf-invoices-packing-slips' ); ?></title>
125
+ <?php wp_print_scripts( 'wpo-wcpdf-setup' ); ?>
126
+ <?php wp_print_scripts( 'wpo-wcpdf-setup-confetti' ); ?>
127
+ <?php do_action( 'admin_print_styles' ); ?>
128
+ <?php do_action( 'admin_head' ); ?>
129
+ </head>
130
+ <body class="wpo-wcpdf-setup wp-core-ui">
131
+ <?php if( $this->step == 'good-to-go' ) { echo "<div id='confetti'></div>"; } ?>
132
+ <form method="post">
133
+ <?php
134
+ }
135
+
136
+ /**
137
+ * Output the steps.
138
+ */
139
+ public function setup_wizard_steps() {
140
+ $output_steps = $this->steps;
141
+ // array_shift( $output_steps );
142
+ ?>
143
+ <div class="wpo-setup-card">
144
+ <h1 class="wpo-plugin-title">PDF Invoices & Packing Slips</h1>
145
+ <ol class="wpo-progress-bar">
146
+ <?php foreach ( $output_steps as $step_key => $step ) : ?>
147
+ <a href="<?php echo $this->get_step_link($step_key); ?>" ><li><div class="wpo-progress-marker <?php
148
+ if ( $step_key === $this->step ) {
149
+ echo 'active';
150
+ } elseif ( array_search( $this->step, array_keys( $this->steps ) ) > array_search( $step_key, array_keys( $this->steps ) ) ) {
151
+ echo 'completed';
152
+ }
153
+ ?>"><?php //echo esc_html( $step['name'] ); ?></div></li></a>
154
+ <?php endforeach; ?>
155
+ </ol>
156
+ <?php
157
+ }
158
+
159
+ /**
160
+ * Output the content for the current step.
161
+ */
162
+ public function setup_wizard_content() {
163
+ echo '<div class="wpo-setup-content">';
164
+ include( $this->steps[ $this->step ]['view'] );
165
+ echo '</div>';
166
+ }
167
+
168
+ /**
169
+ * Setup Wizard Footer.
170
+ */
171
+ public function setup_wizard_footer() {
172
+ ?>
173
+ <div class="wpo-setup-buttons">
174
+ <?php if ($step = $this->get_step(-1)): ?>
175
+ <a href="<?php echo $this->get_step_link($step); ?>" class="wpo-button-previous"><?php _e( 'Previous', 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
176
+ <?php endif ?>
177
+ <!-- <input type="submit" class="wpo-button-next" value="Next" /> -->
178
+ <?php if ($step = $this->get_step(1)): ?>
179
+ <?php wp_nonce_field( 'wpo-wcpdf-setup' ); ?>
180
+ <input type="submit" class="wpo-button-next" value="<?php esc_attr_e( 'Next', 'woocommerce-pdf-invoices-packing-slips' ); ?>" name="save_step" />
181
+ <a href="<?php echo $this->get_step_link($step); ?>" class="wpo-skip-step"><?php _e( 'Skip this step', 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
182
+ <?php else: ?>
183
+ <a href="<?php echo $this->get_step_link($step); ?>" class="wpo-button-next"><?php _e( 'Finish', 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
184
+ <?php endif ?>
185
+ </div>
186
+ </div>
187
+ </form>
188
+ <?php do_action( 'admin_footer' ); // for media uploader templates ?>
189
+ </body>
190
+ </html>
191
+ <?php
192
+ }
193
+
194
+ public function get_step_link( $step ) {
195
+ $step_keys = array_keys( $this->steps );
196
+ if ( end( $step_keys ) === $this->step && empty($step)) {
197
+ return admin_url();
198
+ }
199
+ return add_query_arg( 'step', $step );
200
+ }
201
+
202
+
203
+ public function get_step( $delta ) {
204
+ $step_keys = array_keys( $this->steps );
205
+ $current_step_pos = array_search( $this->step, $step_keys );
206
+ $new_step_pos = $current_step_pos + $delta;
207
+ if (isset($step_keys[$new_step_pos])) {
208
+ return $step_keys[$new_step_pos];
209
+ } else {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ public function save_step() {
215
+ if ( isset( $this->steps[ $this->step ]['handler'] ) ) {
216
+ check_admin_referer( 'wpo-wcpdf-setup' );
217
+ // for doing more than just saving an option value
218
+ call_user_func( $this->steps[ $this->step ]['handler'] );
219
+ } else {
220
+ if (!empty($_POST['wcpdf_settings']) && is_array($_POST['wcpdf_settings'])) {
221
+ check_admin_referer( 'wpo-wcpdf-setup' );
222
+ foreach ($_POST['wcpdf_settings'] as $option => $settings) {
223
+ // sanitize posted settings
224
+ foreach ($settings as $key => $value) {
225
+ if (is_array($value)) {
226
+ $settings[$key] = array_map('sanitize_text_field', $value);
227
+ } else {
228
+ $settings[$key] = sanitize_text_field( $value );
229
+ }
230
+ }
231
+ $current_settings = get_option( $option, array() );
232
+ $new_settings = $settings + $current_settings;
233
+ // echo "<pre>".var_export($settings,true)."</pre>";
234
+ // echo "<pre>".var_export($new_settings,true)."</pre>";die();
235
+ update_option( $option, $new_settings );
236
+ }
237
+ }
238
+ }
239
+
240
+ wp_redirect( esc_url_raw( $this->get_step_link( $this->get_step(1) ) ) );
241
+ }
242
+
243
+ }
244
+
245
+ endif; // class_exists
246
+
247
  return new Setup_Wizard();
includes/compatibility/class-wcpdf-compatibility-third-party-plugins.php CHANGED
@@ -1,162 +1,162 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Compatibility;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
- use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
-
9
- defined( 'ABSPATH' ) or exit;
10
-
11
- if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices\\Compatibility\\Third_Party_Plugins' ) ) :
12
-
13
- /**
14
- * Third party plugin compatibility class.
15
- *
16
- * @since 2.0
17
- */
18
- class Third_Party_Plugins {
19
- function __construct() {
20
- // WooCommerce Subscriptions compatibility
21
- if ( class_exists('WC_Subscriptions') ) {
22
- if ( version_compare( \WC_Subscriptions::$version, '2.0', '<' ) ) {
23
- add_action( 'woocommerce_subscriptions_renewal_order_created', array( $this, 'woocommerce_subscriptions_renewal_order_created' ), 10, 4 );
24
- } else {
25
- add_action( 'wcs_renewal_order_created', array( $this, 'wcs_renewal_order_created' ), 10, 2 );
26
- }
27
- }
28
-
29
- // WooCommerce Product Bundles compatibility (add row classes)
30
- if ( class_exists('WC_Bundles') ) {
31
- add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_product_bundles_classes' ), 10, 4 );
32
- }
33
-
34
- // WooCommerce Chained Products compatibility (add row classes)
35
- if ( class_exists('SA_WC_Chained_Products') ) {
36
- add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_chained_product_class' ), 10, 4 );
37
- }
38
-
39
- // WooCommerce Order Status & Actions Manager emails compatibility
40
- if (class_exists('WC_Custom_Status')) {
41
- add_filter( 'wpo_wcpdf_wc_emails', array( $this, 'wc_order_status_actions_emails' ), 10, 1 );
42
- }
43
-
44
- }
45
-
46
- /**
47
- * Reset invoice data for WooCommerce subscription renewal orders
48
- * https://wordpress.org/support/topic/subscription-renewal-duplicate-invoice-number?replies=6#post-6138110
49
- */
50
- public function woocommerce_subscriptions_renewal_order_created ( $renewal_order, $original_order, $product_id, $new_order_role ) {
51
- $this->reset_invoice_data( $renewal_order );
52
- return $renewal_order;
53
- }
54
-
55
- public function wcs_renewal_order_created ( $renewal_order, $subscription ) {
56
- $this->reset_invoice_data( $renewal_order );
57
- return $renewal_order;
58
- }
59
-
60
- public function reset_invoice_data ( $order ) {
61
- if ( ! is_object( $order ) ) {
62
- $order = wc_get_order( $order );
63
- }
64
- // delete invoice number, invoice date & invoice exists meta
65
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number' );
66
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number_data' );
67
- WCX_Order::delete_meta_data( $order, '_wcpdf_formatted_invoice_number' );
68
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_date' );
69
- WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_exists' );
70
- }
71
-
72
- /**
73
- * WooCommerce Product Bundles
74
- * @param string $classes CSS classes for item row (tr)
75
- * @param string $document_type PDF Document type
76
- * @param object $order WC_Order order
77
- * @param int $item_id WooCommerce Item ID
78
- */
79
- public function add_product_bundles_classes ( $classes, $document_type, $order, $item_id = '' ) {
80
- if ( empty($item_id) ) {
81
- // get item id from classes (backwards compatibility fix)
82
- $class_array = explode(' ', $classes);
83
- foreach ($class_array as $class) {
84
- if (is_numeric($class)) {
85
- $item_id = $class;
86
- break;
87
- }
88
- }
89
-
90
- // if still empty, we lost the item id somewhere :(
91
- if (empty($item_id)) {
92
- return $classes;
93
- }
94
- }
95
-
96
- if ( $bundled_by = WCX_Order::get_item_meta( $order, $item_id, '_bundled_by', true ) ) {
97
- $classes = $classes . ' bundled-item';
98
-
99
- // check bundled item visibility
100
- if ( $hidden = WCX_Order::get_item_meta( $order, $item_id, '_bundled_item_hidden', true ) ) {
101
- $classes = $classes . ' hidden';
102
- }
103
-
104
- return $classes;
105
- } elseif ( $bundled_items = WCX_Order::get_item_meta( $order, $item_id, '_bundled_items', true ) ) {
106
- return $classes . ' product-bundle';
107
- }
108
-
109
- return $classes;
110
- }
111
-
112
- /**
113
- * WooCommerce Chanined Products
114
- * @param string $classes CSS classes for item row (tr)
115
- * @param string $document_type PDF Document type
116
- * @param object $order WC_Order order
117
- * @param int $item_id WooCommerce Item ID
118
- */
119
- public function add_chained_product_class ( $classes, $document_type, $order, $item_id = '' ) {
120
- if ( empty($item_id) ) {
121
- // get item id from classes (backwards compatibility fix)
122
- $class_array = explode(' ', $classes);
123
- foreach ($class_array as $class) {
124
- if (is_numeric($class)) {
125
- $item_id = $class;
126
- break;
127
- }
128
- }
129
-
130
- // if still empty, we lost the item id somewhere :(
131
- if (empty($item_id)) {
132
- return $classes;
133
- }
134
- }
135
-
136
- if ( $chained_product_of = WCX_Order::get_item_meta( $order, $item_id, '_chained_product_of', true ) ) {
137
- return $classes . ' chained-product';
138
- }
139
-
140
- return $classes;
141
- }
142
-
143
- /**
144
- * WooCommerce Order Status & Actions Manager emails compatibility
145
- */
146
- public function wc_order_status_actions_emails ( $emails ) {
147
- // get list of custom statuses from WooCommerce Custom Order Status & Actions
148
- // status slug => status name
149
- $custom_statuses = \WC_Custom_Status::get_status_list_names();
150
- // append _email to slug (=email_id) and add to emails list
151
- foreach ($custom_statuses as $status_slug => $status_name) {
152
- $emails[$status_slug.'_email'] = $status_name;
153
- }
154
- return $emails;
155
- }
156
-
157
- }
158
-
159
-
160
- endif; // Class exists check
161
-
162
- return new Third_Party_Plugins();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Compatibility;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+ use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices\\Compatibility\\Third_Party_Plugins' ) ) :
12
+
13
+ /**
14
+ * Third party plugin compatibility class.
15
+ *
16
+ * @since 2.0
17
+ */
18
+ class Third_Party_Plugins {
19
+ function __construct() {
20
+ // WooCommerce Subscriptions compatibility
21
+ if ( class_exists('WC_Subscriptions') ) {
22
+ if ( version_compare( \WC_Subscriptions::$version, '2.0', '<' ) ) {
23
+ add_action( 'woocommerce_subscriptions_renewal_order_created', array( $this, 'woocommerce_subscriptions_renewal_order_created' ), 10, 4 );
24
+ } else {
25
+ add_action( 'wcs_renewal_order_created', array( $this, 'wcs_renewal_order_created' ), 10, 2 );
26
+ }
27
+ }
28
+
29
+ // WooCommerce Product Bundles compatibility (add row classes)
30
+ if ( class_exists('WC_Bundles') ) {
31
+ add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_product_bundles_classes' ), 10, 4 );
32
+ }
33
+
34
+ // WooCommerce Chained Products compatibility (add row classes)
35
+ if ( class_exists('SA_WC_Chained_Products') ) {
36
+ add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_chained_product_class' ), 10, 4 );
37
+ }
38
+
39
+ // WooCommerce Order Status & Actions Manager emails compatibility
40
+ if (class_exists('WC_Custom_Status')) {
41
+ add_filter( 'wpo_wcpdf_wc_emails', array( $this, 'wc_order_status_actions_emails' ), 10, 1 );
42
+ }
43
+
44
+ }
45
+
46
+ /**
47
+ * Reset invoice data for WooCommerce subscription renewal orders
48
+ * https://wordpress.org/support/topic/subscription-renewal-duplicate-invoice-number?replies=6#post-6138110
49
+ */
50
+ public function woocommerce_subscriptions_renewal_order_created ( $renewal_order, $original_order, $product_id, $new_order_role ) {
51
+ $this->reset_invoice_data( $renewal_order );
52
+ return $renewal_order;
53
+ }
54
+
55
+ public function wcs_renewal_order_created ( $renewal_order, $subscription ) {
56
+ $this->reset_invoice_data( $renewal_order );
57
+ return $renewal_order;
58
+ }
59
+
60
+ public function reset_invoice_data ( $order ) {
61
+ if ( ! is_object( $order ) ) {
62
+ $order = wc_get_order( $order );
63
+ }
64
+ // delete invoice number, invoice date & invoice exists meta
65
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number' );
66
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number_data' );
67
+ WCX_Order::delete_meta_data( $order, '_wcpdf_formatted_invoice_number' );
68
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_date' );
69
+ WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_exists' );
70
+ }
71
+
72
+ /**
73
+ * WooCommerce Product Bundles
74
+ * @param string $classes CSS classes for item row (tr)
75
+ * @param string $document_type PDF Document type
76
+ * @param object $order WC_Order order
77
+ * @param int $item_id WooCommerce Item ID
78
+ */
79
+ public function add_product_bundles_classes ( $classes, $document_type, $order, $item_id = '' ) {
80
+ if ( empty($item_id) ) {
81
+ // get item id from classes (backwards compatibility fix)
82
+ $class_array = explode(' ', $classes);
83
+ foreach ($class_array as $class) {
84
+ if (is_numeric($class)) {
85
+ $item_id = $class;
86
+ break;
87
+ }
88
+ }
89
+
90
+ // if still empty, we lost the item id somewhere :(
91
+ if (empty($item_id)) {
92
+ return $classes;
93
+ }
94
+ }
95
+
96
+ if ( $bundled_by = WCX_Order::get_item_meta( $order, $item_id, '_bundled_by', true ) ) {
97
+ $classes = $classes . ' bundled-item';
98
+
99
+ // check bundled item visibility
100
+ if ( $hidden = WCX_Order::get_item_meta( $order, $item_id, '_bundled_item_hidden', true ) ) {
101
+ $classes = $classes . ' hidden';
102
+ }
103
+
104
+ return $classes;
105
+ } elseif ( $bundled_items = WCX_Order::get_item_meta( $order, $item_id, '_bundled_items', true ) ) {
106
+ return $classes . ' product-bundle';
107
+ }
108
+
109
+ return $classes;
110
+ }
111
+
112
+ /**
113
+ * WooCommerce Chanined Products
114
+ * @param string $classes CSS classes for item row (tr)
115
+ * @param string $document_type PDF Document type
116
+ * @param object $order WC_Order order
117
+ * @param int $item_id WooCommerce Item ID
118
+ */
119
+ public function add_chained_product_class ( $classes, $document_type, $order, $item_id = '' ) {
120
+ if ( empty($item_id) ) {
121
+ // get item id from classes (backwards compatibility fix)
122
+ $class_array = explode(' ', $classes);
123
+ foreach ($class_array as $class) {
124
+ if (is_numeric($class)) {
125
+ $item_id = $class;
126
+ break;
127
+ }
128
+ }
129
+
130
+ // if still empty, we lost the item id somewhere :(
131
+ if (empty($item_id)) {
132
+ return $classes;
133
+ }
134
+ }
135
+
136
+ if ( $chained_product_of = WCX_Order::get_item_meta( $order, $item_id, '_chained_product_of', true ) ) {
137
+ return $classes . ' chained-product';
138
+ }
139
+
140
+ return $classes;
141
+ }
142
+
143
+ /**
144
+ * WooCommerce Order Status & Actions Manager emails compatibility
145
+ */
146
+ public function wc_order_status_actions_emails ( $emails ) {
147
+ // get list of custom statuses from WooCommerce Custom Order Status & Actions
148
+ // status slug => status name
149
+ $custom_statuses = \WC_Custom_Status::get_status_list_names();
150
+ // append _email to slug (=email_id) and add to emails list
151
+ foreach ($custom_statuses as $status_slug => $status_name) {
152
+ $emails[$status_slug.'_email'] = $status_name;
153
+ }
154
+ return $emails;
155
+ }
156
+
157
+ }
158
+
159
+
160
+ endif; // Class exists check
161
+
162
+ return new Third_Party_Plugins();
includes/documents/abstract-wcpdf-order-document-methods.php CHANGED
@@ -1,1030 +1,1030 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document_Methods' ) ) :
13
-
14
- /**
15
- * Abstract Order Methods
16
- *
17
- * Collection of methods to be used on orders within a Document
18
- * Created as abstract rather than traits to support PHP versions older than 5.4
19
- *
20
- * @class \WPO\WC\PDF_Invoices\Documents\Order_Document_Methods
21
- * @version 2.0
22
- * @category Class
23
- * @author Ewout Fernhout
24
- */
25
-
26
- abstract class Order_Document_Methods extends Order_Document {
27
- public function is_refund( $order ) {
28
- if ( method_exists( $order, 'get_type') ) { // WC 3.0+
29
- $is_refund = $order->get_type() == 'shop_order_refund';
30
- } else {
31
- $is_refund = get_post_type( WCX_Order::get_id( $order ) ) == 'shop_order_refund';
32
- }
33
-
34
- return $is_refund;
35
- }
36
-
37
- public function get_refund_parent_id( $order ) {
38
- if ( method_exists( $order, 'get_parent_id') ) { // WC3.0+
39
- $parent_order_id = $order->get_parent_id();
40
- } else {
41
- $parent_order_id = wp_get_post_parent_id( WCX_Order::get_id( $order ) );
42
- }
43
-
44
- return $parent_order_id;
45
- }
46
-
47
-
48
- public function get_refund_parent( $order ) {
49
- // only try if this is actually a refund
50
- if ( ! $this->is_refund( $order ) ) {
51
- return $order;
52
- }
53
-
54
- $parent_order_id = $this->get_refund_parent_id( $order );
55
- $order = WCX::get_order( $parent_order_id );
56
- return $order;
57
- }
58
-
59
- /**
60
- * Check if billing address and shipping address are equal
61
- */
62
- public function ships_to_different_address() {
63
- // always prefer parent address for refunds
64
- if ( $this->is_refund( $this->order ) ) {
65
- $order = $this->get_refund_parent( $this->order );
66
- } else {
67
- $order = $this->order;
68
- }
69
-
70
- $address_comparison_fields = apply_filters( 'wpo_wcpdf_address_comparison_fields', array(
71
- 'first_name',
72
- 'last_name',
73
- 'company',
74
- 'address_1',
75
- 'address_2',
76
- 'city',
77
- 'state',
78
- 'postcode',
79
- 'country'
80
- ), $this );
81
-
82
- foreach ($address_comparison_fields as $address_field) {
83
- $billing_field = WCX_Order::get_prop( $order, "billing_{$address_field}", 'view');
84
- $shipping_field = WCX_Order::get_prop( $order, "shipping_{$address_field}", 'view');
85
- if ( $shipping_field != $billing_field ) {
86
- // this address field is different -> ships to different address!
87
- return true;
88
- }
89
- }
90
-
91
- //if we got here, it means the addresses are equal -> doesn't ship to different address!
92
- return apply_filters( 'wpo_wcpdf_ships_to_different_address', false, $order, $this );
93
- }
94
-
95
- /**
96
- * Return/Show billing address
97
- */
98
- public function get_billing_address() {
99
- // always prefer parent billing address for refunds
100
- if ( $this->is_refund( $this->order ) ) {
101
- // temporarily switch order to make all filters / order calls work correctly
102
- $refund = $this->order;
103
- $this->order = $this->get_refund_parent( $this->order );
104
- $address = apply_filters( 'wpo_wcpdf_billing_address', $this->order->get_formatted_billing_address(), $this );
105
- // switch back & unset
106
- $this->order = $refund;
107
- unset($refund);
108
- } elseif ( $address = $this->order->get_formatted_billing_address() ) {
109
- // regular shop_order
110
- $address = apply_filters( 'wpo_wcpdf_billing_address', $address, $this );
111
- } else {
112
- // no address
113
- $address = apply_filters( 'wpo_wcpdf_billing_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
114
- }
115
-
116
- return $address;
117
- }
118
- public function billing_address() {
119
- echo $this->get_billing_address();
120
- }
121
-
122
- /**
123
- * Return/Show billing email
124
- */
125
- public function get_billing_email() {
126
- $billing_email = WCX_Order::get_prop( $this->order, 'billing_email', 'view' );
127
-
128
- if ( !$billing_email && $this->is_refund( $this->order ) ) {
129
- // try parent
130
- $parent_order = $this->get_refund_parent( $this->order );
131
- $billing_email = WCX_Order::get_prop( $parent_order, 'billing_email', 'view' );
132
- }
133
-
134
- return apply_filters( 'wpo_wcpdf_billing_email', $billing_email, $this );
135
- }
136
- public function billing_email() {
137
- echo $this->get_billing_email();
138
- }
139
-
140
- /**
141
- * Return/Show billing phone
142
- */
143
- public function get_billing_phone() {
144
- $billing_phone = WCX_Order::get_prop( $this->order, 'billing_phone', 'view' );
145
-
146
- if ( !$billing_phone && $this->is_refund( $this->order ) ) {
147
- // try parent
148
- $parent_order = $this->get_refund_parent( $this->order );
149
- $billing_phone = WCX_Order::get_prop( $parent_order, 'billing_phone', 'view' );
150
- }
151
-
152
- return apply_filters( 'wpo_wcpdf_billing_phone', $billing_phone, $this );
153
- }
154
- public function billing_phone() {
155
- echo $this->get_billing_phone();
156
- }
157
-
158
- /**
159
- * Return/Show shipping address
160
- */
161
- public function get_shipping_address() {
162
- // always prefer parent shipping address for refunds
163
- if ( $this->is_refund( $this->order ) ) {
164
- // temporarily switch order to make all filters / order calls work correctly
165
- $refund = $this->order;
166
- $this->order = $this->get_refund_parent( $this->order );
167
- $address = apply_filters( 'wpo_wcpdf_shipping_address', $this->order->get_formatted_shipping_address(), $this );
168
- // switch back & unset
169
- $this->order = $refund;
170
- unset($refund);
171
- } elseif ( $address = $this->order->get_formatted_shipping_address() ) {
172
- // regular shop_order
173
- $address = apply_filters( 'wpo_wcpdf_shipping_address', $address, $this );
174
- } else {
175
- // no address
176
- $address = apply_filters( 'wpo_wcpdf_shipping_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
177
- }
178
-
179
- return $address;
180
- }
181
- public function shipping_address() {
182
- echo $this->get_shipping_address();
183
- }
184
-
185
- /**
186
- * Return/Show a custom field
187
- */
188
- public function get_custom_field( $field_name ) {
189
- $custom_field = WCX_Order::get_meta( $this->order, $field_name, true );
190
- // if not found, try prefixed with underscore
191
- if ( !$custom_field && substr( $field_name, 0, 1 ) !== '_' ) {
192
- $custom_field = WCX_Order::get_meta( $this->order, "_{$field_name}", true );
193
- }
194
-
195
- // WC3.0 fallback to properties
196
- $property = str_replace('-', '_', sanitize_title( ltrim($field_name, '_') ) );
197
- if ( !$custom_field && is_callable( array( $this->order, "get_{$property}" ) ) ) {
198
- $custom_field = $this->order->{"get_{$property}"}( 'view' );
199
- }
200
-
201
- // fallback to parent for refunds
202
- if ( !$custom_field && $this->is_refund( $this->order ) ) {
203
- $parent_order = $this->get_refund_parent( $this->order );
204
- $custom_field = WCX_Order::get_meta( $parent_order, $field_name, true );
205
-
206
- // WC3.0 fallback to properties
207
- if ( !$custom_field && is_callable( array( $parent_order, "get_{$property}" ) ) ) {
208
- $custom_field = $parent_order->{"get_{$property}"}( 'view' );
209
- }
210
- }
211
-
212
- return apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this );
213
- }
214
- public function custom_field( $field_name, $field_label = '', $display_empty = false ) {
215
- $custom_field = $this->get_custom_field( $field_name );
216
- if (!empty($field_label)){
217
- // add a a trailing space to the label
218
- $field_label .= ' ';
219
- }
220
-
221
- if (!empty($custom_field) || $display_empty) {
222
- echo $field_label . nl2br ($custom_field);
223
- }
224
- }
225
-
226
- /**
227
- * Return/show product attribute
228
- */
229
- public function get_product_attribute( $attribute_name, $product ) {
230
- // first, check the text attributes
231
- $attributes = $product->get_attributes();
232
- $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
233
- if (array_key_exists( sanitize_title( $attribute_name ), $attributes) ) {
234
- $attribute = $product->get_attribute ( $attribute_name );
235
- } elseif (array_key_exists( sanitize_title( $attribute_key ), $attributes) ) {
236
- $attribute = $product->get_attribute ( $attribute_key );
237
- }
238
-
239
- if (empty($attribute)) {
240
- // not a text attribute, try attribute taxonomy
241
- $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
242
- $product_id = WCX_Product::get_prop($product, 'id');
243
- $product_terms = @wc_get_product_terms( $product_id, $attribute_key, array( 'fields' => 'names' ) );
244
- // check if not empty, then display
245
- if ( !empty($product_terms) ) {
246
- $attribute = array_shift( $product_terms );
247
- }
248
- }
249
-
250
- // WC3.0+ fallback parent product for variations
251
- if ( empty($attribute) && version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) && $product->is_type( 'variation' ) ) {
252
- $product = wc_get_product( $product->get_parent_id() );
253
- $attribute = $this->get_product_attribute( $attribute_name, $product );
254
- }
255
-
256
- return isset($attribute) ? $attribute : false;
257
- }
258
- public function product_attribute( $attribute_name, $product ) {
259
- echo $this->get_product_attribute( $attribute_name, $product );
260
- }
261
-
262
- /**
263
- * Return/Show order notes
264
- * could use $order->get_customer_order_notes(), but that filters out private notes already
265
- */
266
- public function get_order_notes( $filter = 'customer' ) {
267
- if ( $this->is_refund( $this->order ) ) {
268
- $post_id = $this->get_refund_parent_id( $this->order );
269
- } else {
270
- $post_id = $this->order_id;
271
- }
272
-
273
- if ( empty( $post_id ) ) {
274
- return; // prevent order notes from all orders showing when document is not loaded properly
275
- }
276
-
277
- $args = array(
278
- 'post_id' => $post_id,
279
- 'approve' => 'approve',
280
- 'type' => 'order_note'
281
- );
282
-
283
- remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
284
-
285
- $notes = get_comments( $args );
286
-
287
- add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
288
-
289
- if ( $notes ) {
290
- foreach( $notes as $key => $note ) {
291
- if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
292
- unset($notes[$key]);
293
- }
294
- if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
295
- unset($notes[$key]);
296
- }
297
- }
298
- return $notes;
299
- }
300
- }
301
- public function order_notes( $filter = 'customer' ) {
302
- $notes = $this->get_order_notes( $filter );
303
- if ( $notes ) {
304
- foreach( $notes as $note ) {
305
- ?>
306
- <div class="note_content">
307
- <?php echo wpautop( wptexturize( wp_kses_post( $note->comment_content ) ) ); ?>
308
- </div>
309
- <?php
310
- }
311
- }
312
- }
313
-
314
- /**
315
- * Return/Show the current date
316
- */
317
- public function get_current_date() {
318
- return apply_filters( 'wpo_wcpdf_date', date_i18n( get_option( 'date_format' ) ) );
319
- }
320
- public function current_date() {
321
- echo $this->get_current_date();
322
- }
323
-
324
- /**
325
- * Return/Show payment method
326
- */
327
- public function get_payment_method() {
328
- $payment_method_label = __( 'Payment method', 'woocommerce-pdf-invoices-packing-slips' );
329
-
330
- if ( $this->is_refund( $this->order ) ) {
331
- $parent_order = $this->get_refund_parent( $this->order );
332
- $payment_method_title = WCX_Order::get_prop( $parent_order, 'payment_method_title', 'view' );
333
- } else {
334
- $payment_method_title = WCX_Order::get_prop( $this->order, 'payment_method_title', 'view' );
335
- }
336
-
337
- $payment_method = __( $payment_method_title, 'woocommerce' );
338
-
339
- return apply_filters( 'wpo_wcpdf_payment_method', $payment_method, $this );
340
- }
341
- public function payment_method() {
342
- echo $this->get_payment_method();
343
- }
344
-
345
- /**
346
- * Return/Show shipping method
347
- */
348
- public function get_shipping_method() {
349
- $shipping_method_label = __( 'Shipping method', 'woocommerce-pdf-invoices-packing-slips' );
350
- $shipping_method = __( $this->order->get_shipping_method(), 'woocommerce' );
351
- return apply_filters( 'wpo_wcpdf_shipping_method', $shipping_method, $this );
352
- }
353
- public function shipping_method() {
354
- echo $this->get_shipping_method();
355
- }
356
-
357
- /**
358
- * Return/Show order number
359
- */
360
- public function get_order_number() {
361
- // try parent first
362
- if ( $this->is_refund( $this->order ) ) {
363
- $parent_order = $this->get_refund_parent( $this->order );
364
- $order_number = $parent_order->get_order_number();
365
- } else {
366
- $order_number = $this->order->get_order_number();
367
- }
368
-
369
- // Trim the hash to have a clean number but still
370
- // support any filters that were applied before.
371
- $order_number = ltrim($order_number, '#');
372
- return apply_filters( 'wpo_wcpdf_order_number', $order_number, $this );
373
- }
374
- public function order_number() {
375
- echo $this->get_order_number();
376
- }
377
-
378
- /**
379
- * Return/Show the order date
380
- */
381
- public function get_order_date() {
382
- if ( $this->is_refund( $this->order ) ) {
383
- $parent_order = $this->get_refund_parent( $this->order );
384
- $order_date = WCX_Order::get_prop( $parent_order, 'date_created' );
385
- } else {
386
- $order_date = WCX_Order::get_prop( $this->order, 'date_created' );
387
- }
388
-
389
- $date = $order_date->date_i18n( get_option( 'date_format' ) );
390
- $mysql_date = $order_date->date( "Y-m-d H:i:s" );
391
- return apply_filters( 'wpo_wcpdf_order_date', $date, $mysql_date, $this );
392
- }
393
- public function order_date() {
394
- echo $this->get_order_date();
395
- }
396
-
397
- /**
398
- * Return the order items
399
- */
400
- public function get_order_items() {
401
- $items = $this->order->get_items();
402
- $data_list = array();
403
-
404
- if( sizeof( $items ) > 0 ) {
405
- foreach ( $items as $item_id => $item ) {
406
- // Array with data for the pdf template
407
- $data = array();
408
-
409
- // Set the item_id
410
- $data['item_id'] = $item_id;
411
-
412
- // Set the id
413
- $data['product_id'] = $item['product_id'];
414
- $data['variation_id'] = $item['variation_id'];
415
-
416
- // Set item name
417
- $data['name'] = $item['name'];
418
-
419
- // Set item quantity
420
- $data['quantity'] = $item['qty'];
421
-
422
- // Set the line total (=after discount)
423
- $data['line_total'] = $this->format_price( $item['line_total'] );
424
- $data['single_line_total'] = $this->format_price( $item['line_total'] / max( 1, abs( $item['qty'] ) ) );
425
- $data['line_tax'] = $this->format_price( $item['line_tax'] );
426
- $data['single_line_tax'] = $this->format_price( $item['line_tax'] / max( 1, abs( $item['qty'] ) ) );
427
-
428
- $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' );
429
- $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data );
430
-
431
- // Set the line subtotal (=before discount)
432
- $data['line_subtotal'] = $this->format_price( $item['line_subtotal'] );
433
- $data['line_subtotal_tax'] = $this->format_price( $item['line_subtotal_tax'] );
434
- $data['ex_price'] = $this->get_formatted_item_price( $item, 'total', 'excl' );
435
- $data['price'] = $this->get_formatted_item_price( $item, 'total' );
436
- $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings
437
-
438
- // Calculate the single price with the same rules as the formatted line subtotal (!)
439
- // = before discount
440
- $data['ex_single_price'] = $this->get_formatted_item_price( $item, 'single', 'excl' );
441
- $data['single_price'] = $this->get_formatted_item_price( $item, 'single' );
442
-
443
- // Pass complete item array
444
- $data['item'] = $item;
445
-
446
- // Get the product to add more info
447
- $product = $this->order->get_product_from_item( $item );
448
-
449
- // Checking fo existance, thanks to MDesigner0
450
- if( !empty( $product ) ) {
451
- // Thumbnail (full img tag)
452
- $data['thumbnail'] = $this->get_thumbnail( $product );
453
-
454
- // Set item SKU
455
- $data['sku'] = $product->get_sku();
456
-
457
- // Set item weight
458
- $data['weight'] = $product->get_weight();
459
-
460
- // Set item dimensions
461
- $data['dimensions'] = WCX_Product::get_dimensions( $product );
462
-
463
- // Pass complete product object
464
- $data['product'] = $product;
465
-
466
- } else {
467
- $data['product'] = null;
468
- }
469
-
470
- // Set item meta
471
- if (function_exists('wc_display_item_meta')) { // WC3.0+
472
- $data['meta'] = wc_display_item_meta( $item, array(
473
- 'echo' => false,
474
- ) );
475
- } else {
476
- if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) {
477
- $meta = new \WC_Order_Item_Meta( $item['item_meta'], $product );
478
- } else { // pass complete item for WC2.4+
479
- $meta = new \WC_Order_Item_Meta( $item, $product );
480
- }
481
- $data['meta'] = $meta->display( false, true );
482
- }
483
-
484
- $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order, $this->get_type() );
485
- }
486
- }
487
-
488
- return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order, $this->get_type() );
489
- }
490
-
491
- /**
492
- * Get the tax rates/percentages for a given tax class
493
- * @param string $tax_class tax class slug
494
- * @return string $tax_rates imploded list of tax rates
495
- */
496
- public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '' ) {
497
- // first try the easy wc2.2+ way, using line_tax_data
498
- if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) {
499
- $tax_rates = array();
500
-
501
- $line_taxes = $line_tax_data['subtotal'];
502
- foreach ( $line_taxes as $tax_id => $tax ) {
503
- if ( isset($tax) && $tax !== '' ) {
504
- $tax_rates[] = $this->get_tax_rate_by_id( $tax_id ) . ' %';
505
- }
506
- }
507
-
508
- $tax_rates = implode(' ,', $tax_rates );
509
- return $tax_rates;
510
- }
511
-
512
- if ( $line_tax == 0 ) {
513
- return '-'; // no need to determine tax rate...
514
- }
515
-
516
- if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) {
517
- // WC 2.1 or newer is used
518
- $tax = new \WC_Tax();
519
- $taxes = $tax->get_rates( $tax_class );
520
-
521
- $tax_rates = array();
522
-
523
- foreach ($taxes as $tax) {
524
- $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %';
525
- }
526
-
527
- if (empty($tax_rates)) {
528
- // one last try: manually calculate
529
- if ( $line_total != 0) {
530
- $tax_rates[] = round( ($line_tax / $line_total)*100, 1 ).' %';
531
- } else {
532
- $tax_rates[] = '-';
533
- }
534
- }
535
-
536
- $tax_rates = implode(' ,', $tax_rates );
537
- } else {
538
- // Backwards compatibility/fallback: calculate tax from line items
539
- if ( $line_total != 0) {
540
- $tax_rates = round( ($line_tax / $line_total)*100, 1 ).' %';
541
- } else {
542
- $tax_rates = '-';
543
- }
544
- }
545
-
546
- return $tax_rates;
547
- }
548
-
549
- /**
550
- * Returns the percentage rate (float) for a given tax rate ID.
551
- * @param int $rate_id woocommerce tax rate id
552
- * @return float $rate percentage rate
553
- */
554
- public function get_tax_rate_by_id( $rate_id ) {
555
- global $wpdb;
556
- $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) );
557
- return (float) $rate;
558
- }
559
-
560
- /**
561
- * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce
562
- * @return array $tax_rate_ids keyed by id
563
- */
564
- public function get_tax_rate_ids() {
565
- global $wpdb;
566
- $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" );
567
-
568
- $tax_rate_ids = array();
569
- foreach ($rates as $rate) {
570
- // var_dump($rate->tax_rate_id);
571
- // die($rate);
572
- $rate_id = $rate->tax_rate_id;
573
- unset($rate->tax_rate_id);
574
- $tax_rate_ids[$rate_id] = (array) $rate;
575
- }
576
-
577
- return $tax_rate_ids;
578
- }
579
-
580
- /**
581
- * Returns the main product image ID
582
- * Adapted from the WC_Product class
583
- * (does not support thumbnail sizes)
584
- *
585
- * @access public
586
- * @return string
587
- */
588
- public function get_thumbnail_id ( $product ) {
589
- global $woocommerce;
590
-
591
- $product_id = WCX_Product::get_id( $product );
592
-
593
- if ( has_post_thumbnail( $product_id ) ) {
594
- $thumbnail_id = get_post_thumbnail_id ( $product_id );
595
- } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) {
596
- $thumbnail_id = get_post_thumbnail_id ( $parent_id );
597
- } else {
598
- $thumbnail_id = false;
599
- }
600
-
601
- return $thumbnail_id;
602
- }
603
-
604
- /**
605
- * Returns the thumbnail image tag
606
- *
607
- * uses the internal WooCommerce/WP functions and extracts the image url or path
608
- * rather than the thumbnail ID, to simplify the code and make it possible to
609
- * filter for different thumbnail sizes
610
- *
611
- * @access public
612
- * @return string
613
- */
614
- public function get_thumbnail ( $product ) {
615
- // Get default WooCommerce img tag (url/http)
616
- $size = apply_filters( 'wpo_wcpdf_thumbnail_size', 'shop_thumbnail' );
617
- $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) );
618
-
619
- // Extract the url from img
620
- preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url );
621
- $thumbnail_url = array_pop($thumbnail_url);
622
- // remove http/https from image tag url to avoid mixed origin conflicts
623
- $contextless_thumbnail_url = ltrim( str_replace(array('http://','https://'), '', $thumbnail_url ), '/' );
624
-
625
- // convert url to path
626
- if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
627
- $forwardslash_basepath = str_replace('\\','/', ABSPATH);
628
- $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(get_site_url()));
629
- } else {
630
- // bedrock e.a
631
- $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
632
- $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(WP_CONTENT_URL));
633
- }
634
- $thumbnail_path = str_replace( $contextless_site_url, trailingslashit( $forwardslash_basepath ), $contextless_thumbnail_url);
635
-
636
- // fallback if thumbnail file doesn't exist
637
- if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) {
638
- if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) {
639
- $thumbnail_path = get_attached_file( $thumbnail_id );
640
- }
641
- }
642
-
643
- // Thumbnail (full img tag)
644
- if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path) ) {
645
- // load img with server path by default
646
- $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path );
647
- } elseif ( apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path) ) {
648
- // should use paths but file not found, replace // with http(s):// for dompdf compatibility
649
- if ( substr( $thumbnail_url, 0, 2 ) === "//" ) {
650
- $prefix = is_ssl() ? 'https://' : 'http://';
651
- $https_thumbnail_url = $prefix . ltrim( $thumbnail_url, '/' );
652
- }
653
- $thumbnail_img_tag_url = str_replace($thumbnail_url, $https_thumbnail_url, $thumbnail_img_tag_url);
654
- $thumbnail = $thumbnail_img_tag_url;
655
- } else {
656
- // load img with http url when filtered
657
- $thumbnail = $thumbnail_img_tag_url;
658
- }
659
-
660
- // die($thumbnail);
661
- return $thumbnail;
662
- }
663
-
664
- /**
665
- * Return the order totals listing
666
- */
667
- public function get_woocommerce_totals() {
668
- // get totals and remove the semicolon
669
- $totals = apply_filters( 'wpo_wcpdf_raw_order_totals', $this->order->get_order_item_totals(), $this->order );
670
-
671
- // remove the colon for every label
672
- foreach ( $totals as $key => $total ) {
673
- $label = $total['label'];
674
- $colon = strrpos( $label, ':' );
675
- if( $colon !== false ) {
676
- $label = substr_replace( $label, '', $colon, 1 );
677
- }
678
- $totals[$key]['label'] = $label;
679
- }
680
-
681
- // WC2.4 fix order_total for refunded orders
682
- // not if this is the actual refund!
683
- if ( ! $this->is_refund( $this->order ) ) {
684
- $total_refunded = method_exists($this->order, 'get_total_refunded') ? $this->order->get_total_refunded() : 0;
685
- if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '>=' ) && isset($totals['order_total']) && $total_refunded ) {
686
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
687
- $tax_display = get_option( 'woocommerce_tax_display_cart' );
688
- } else {
689
- $tax_display = WCX_Order::get_prop( $this->order, 'tax_display_cart' );
690
- }
691
-
692
- $totals['order_total']['value'] = wc_price( $this->order->get_total(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) );
693
- $order_total = $this->order->get_total();
694
- $tax_string = '';
695
-
696
- // Tax for inclusive prices
697
- if ( wc_tax_enabled() && 'incl' == $tax_display ) {
698
- $tax_string_array = array();
699
- if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
700
- foreach ( $this->order->get_tax_totals() as $code => $tax ) {
701
- $tax_amount = $tax->formatted_amount;
702
- $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
703
- }
704
- } else {
705
- $tax_string_array[] = sprintf( '%s %s', wc_price( $this->order->get_total_tax() - $this->order->get_total_tax_refunded(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) ), WC()->countries->tax_or_vat() );
706
- }
707
- if ( ! empty( $tax_string_array ) ) {
708
- if ( version_compare( WOOCOMMERCE_VERSION, '2.6', '>=' ) ) {
709
- $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
710
- } else {
711
- // use old capitalized string
712
- $tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
713
- }
714
- }
715
- }
716
-
717
- $totals['order_total']['value'] .= $tax_string;
718
- }
719
-
720
- // remove refund lines (shouldn't be in invoice)
721
- foreach ( $totals as $key => $total ) {
722
- if ( strpos($key, 'refund_') !== false ) {
723
- unset( $totals[$key] );
724
- }
725
- }
726
-
727
- }
728
-
729
- return apply_filters( 'wpo_wcpdf_woocommerce_totals', $totals, $this->order, $this->get_type() );
730
- }
731
-
732
- /**
733
- * Return/show the order subtotal
734
- */
735
- public function get_order_subtotal( $tax = 'excl', $discount = 'incl' ) { // set $tax to 'incl' to include tax, same for $discount
736
- //$compound = ($discount == 'incl')?true:false;
737
- $subtotal = $this->order->get_subtotal_to_display( false, $tax );
738
-
739
- $subtotal = ($pos = strpos($subtotal, ' <small')) ? substr($subtotal, 0, $pos) : $subtotal; //removing the 'excluding tax' text
740
-
741
- $subtotal = array (
742
- 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips' ),
743
- 'value' => $subtotal,
744
- );
745
-
746
- return apply_filters( 'wpo_wcpdf_order_subtotal', $subtotal, $tax, $discount, $this );
747
- }
748
- public function order_subtotal( $tax = 'excl', $discount = 'incl' ) {
749
- $subtotal = $this->get_order_subtotal( $tax, $discount );
750
- echo $subtotal['value'];
751
- }
752
-
753
- /**
754
- * Return/show the order shipping costs
755
- */
756
- public function get_order_shipping( $tax = 'excl' ) { // set $tax to 'incl' to include tax
757
- $shipping_cost = WCX_Order::get_prop( $this->order, 'shipping_total', 'view' );
758
- $shipping_tax = WCX_Order::get_prop( $this->order, 'shipping_tax', 'view' );
759
-
760
- if ($tax == 'excl' ) {
761
- $formatted_shipping_cost = $this->format_price( $shipping_cost );
762
- } else {
763
- $formatted_shipping_cost = $this->format_price( $shipping_cost + $shipping_tax );
764
- }
765
-
766
- $shipping = array (
767
- 'label' => __('Shipping', 'woocommerce-pdf-invoices-packing-slips' ),
768
- 'value' => $formatted_shipping_cost,
769
- 'tax' => $this->format_price( $shipping_tax ),
770
- );
771
- return apply_filters( 'wpo_wcpdf_order_shipping', $shipping, $tax, $this );
772
- }
773
- public function order_shipping( $tax = 'excl' ) {
774
- $shipping = $this->get_order_shipping( $tax );
775
- echo $shipping['value'];
776
- }
777
-
778
- /**
779
- * Return/show the total discount
780
- */
781
- public function get_order_discount( $type = 'total', $tax = 'incl' ) {
782
- if ( $tax == 'incl' ) {
783
- switch ($type) {
784
- case 'cart':
785
- // Cart Discount - pre-tax discounts. (deprecated in WC2.3)
786
- $discount_value = $this->order->get_cart_discount();
787
- break;
788
- case 'order':
789
- // Order Discount - post-tax discounts. (deprecated in WC2.3)
790
- $discount_value = $this->order->get_order_discount();
791
- break;
792
- case 'total':
793
- // Total Discount
794
- if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
795
- $discount_value = $this->order->get_total_discount( false ); // $ex_tax = false
796
- } else {
797
- // WC2.2 and older: recalculate to include tax
798
- $discount_value = 0;
799
- $items = $this->order->get_items();;
800
- if( sizeof( $items ) > 0 ) {
801
- foreach( $items as $item ) {
802
- $discount_value += ($item['line_subtotal'] + $item['line_subtotal_tax']) - ($item['line_total'] + $item['line_tax']);
803
- }
804
- }
805
- }
806
-
807
- break;
808
- default:
809
- // Total Discount - Cart & Order Discounts combined
810
- $discount_value = $this->order->get_total_discount();
811
- break;
812
- }
813
- } else { // calculate discount excluding tax
814
- if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
815
- $discount_value = $this->order->get_total_discount( true ); // $ex_tax = true
816
- } else {
817
- // WC2.2 and older: recalculate to exclude tax
818
- $discount_value = 0;
819
-
820
- $items = $this->order->get_items();;
821
- if( sizeof( $items ) > 0 ) {
822
- foreach( $items as $item ) {
823
- $discount_value += ($item['line_subtotal'] - $item['line_total']);
824
- }
825
- }
826
- }
827
- }
828
-
829
- $discount = array (
830
- 'label' => __('Discount', 'woocommerce-pdf-invoices-packing-slips' ),
831
- 'value' => $this->format_price( $discount_value ),
832
- 'raw_value' => $discount_value,
833
- );
834
-
835
- if ( round( $discount_value, 3 ) != 0 ) {
836
- return apply_filters( 'wpo_wcpdf_order_discount', $discount, $type, $tax, $this );
837
- }
838
- }
839
- public function order_discount( $type = 'total', $tax = 'incl' ) {
840
- $discount = $this->get_order_discount( $type, $tax );
841
- echo $discount['value'];
842
- }
843
-
844
- /**
845
- * Return the order fees
846
- */
847
- public function get_order_fees( $tax = 'excl' ) {
848
- if ( $_fees = $this->order->get_fees() ) {
849
- foreach( $_fees as $id => $fee ) {
850
- if ($tax == 'excl' ) {
851
- $fee_price = $this->format_price( $fee['line_total'] );
852
- } else {
853
- $fee_price = $this->format_price( $fee['line_total'] + $fee['line_tax'] );
854
- }
855
-
856
- $fees[ $id ] = array(
857
- 'label' => $fee['name'],
858
- 'value' => $fee_price,
859
- 'line_total' => $this->format_price( $fee['line_total'] ),
860
- 'line_tax' => $this->format_price( $fee['line_tax'] )
861
- );
862
- }
863
- return $fees;
864
- }
865
- }
866
-
867
- /**
868
- * Return the order taxes
869
- */
870
- public function get_order_taxes() {
871
- $tax_label = __( 'VAT', 'woocommerce-pdf-invoices-packing-slips' ); // register alternate label translation
872
- $tax_label = __( 'Tax rate', 'woocommerce-pdf-invoices-packing-slips' );
873
- $tax_rate_ids = $this->get_tax_rate_ids();
874
- if ( $order_taxes = $this->order->get_taxes() ) {
875
- foreach ( $order_taxes as $key => $tax ) {
876
- if ( WCX::is_wc_version_gte_3_0() ) {
877
- $taxes[ $key ] = array(
878
- 'label' => $tax->get_label(),
879
- 'value' => $this->format_price( $tax->get_tax_total() + $tax->get_shipping_tax_total() ),
880
- 'rate_id' => $tax->get_rate_id(),
881
- 'tax_amount' => $tax->get_tax_total(),
882
- 'shipping_tax_amount' => $tax->get_shipping_tax_total(),
883
- 'rate' => isset( $tax_rate_ids[ $tax->get_rate_id() ] ) ? ( (float) $tax_rate_ids[$tax->get_rate_id()]['tax_rate'] ) . ' %': '',
884
- );
885
- } else {
886
- $taxes[ $key ] = array(
887
- 'label' => isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ],
888
- 'value' => $this->format_price( ( $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ] ) ),
889
- 'rate_id' => $tax['rate_id'],
890
- 'tax_amount' => $tax['tax_amount'],
891
- 'shipping_tax_amount' => $tax['shipping_tax_amount'],
892
- 'rate' => isset( $tax_rate_ids[ $tax['rate_id'] ] ) ? ( (float) $tax_rate_ids[$tax['rate_id']]['tax_rate'] ) . ' %': '',
893
- );
894
- }
895
-
896
- }
897
-
898
- return apply_filters( 'wpo_wcpdf_order_taxes', $taxes, $this );
899
- }
900
- }
901
-
902
- /**
903
- * Return/show the order grand total
904
- */
905
- public function get_order_grand_total( $tax = 'incl' ) {
906
- if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) {
907
- // WC 2.1 or newer is used
908
- $total_unformatted = $this->order->get_total();
909
- } else {
910
- // Backwards compatibility
911
- $total_unformatted = $this->order->get_order_total();
912
- }
913
-
914
- if ($tax == 'excl' ) {
915
- $total = $this->format_price( $total_unformatted - $this->order->get_total_tax() );
916
- $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' );
917
- } else {
918
- $total = $this->format_price( ( $total_unformatted ) );
919
- $label = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' );
920
- }
921
-
922
- $grand_total = array(
923
- 'label' => $label,
924
- 'value' => $total,
925
- );
926
-
927
- return apply_filters( 'wpo_wcpdf_order_grand_total', $grand_total, $tax, $this );
928
- }
929
- public function order_grand_total( $tax = 'incl' ) {
930
- $grand_total = $this->get_order_grand_total( $tax );
931
- echo $grand_total['value'];
932
- }
933
-
934
-
935
- /**
936
- * Return/Show shipping notes
937
- */
938
- public function get_shipping_notes() {
939
- if ( $this->is_refund( $this->order ) ) {
940
- // return reason for refund if order is a refund
941
- if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
942
- $shipping_notes = $this->order->get_reason();
943
- } elseif ( method_exists($this->order, 'get_refund_reason') ) {
944
- $shipping_notes = $this->order->get_refund_reason();
945
- } else {
946
- $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
947
- }
948
- } else {
949
- $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
950
- }
951
- return apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this );
952
- }
953
- public function shipping_notes() {
954
- echo $this->get_shipping_notes();
955
- }
956
-
957
- /**
958
- * wrapper for wc_price, ensuring currency is always passed
959
- */
960
- public function format_price( $price, $args = array() ) {
961
- if ( function_exists( 'wc_price' ) ) { // WC 2.1+
962
- $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' );
963
- $formatted_price = wc_price( $price, $args );
964
- } else {
965
- $formatted_price = woocommerce_price( $price );
966
- }
967
-
968
- return $formatted_price;
969
- }
970
- public function wc_price( $price, $args = array() ) {
971
- return $this->format_price( $price, $args );
972
- }
973
-
974
- /**
975
- * Gets price - formatted for display.
976
- *
977
- * @access public
978
- * @param mixed $item
979
- * @return string
980
- */
981
- public function get_formatted_item_price ( $item, $type, $tax_display = '' ) {
982
- if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
983
- return;
984
- }
985
-
986
- $divide_by = ($type == 'single' && $item['qty'] != 0 )?abs($item['qty']):1; //divide by 1 if $type is not 'single' (thus 'total')
987
- if ( $tax_display == 'excl' ) {
988
- $item_price = $this->format_price( ($this->order->get_line_subtotal( $item )) / $divide_by );
989
- } else {
990
- $item_price = $this->format_price( ($this->order->get_line_subtotal( $item, true )) / $divide_by );
991
- }
992
-
993
- return $item_price;
994
- }
995
-
996
- public function get_invoice_number() {
997
- // Call the woocommerce_invoice_number filter and let third-party plugins set a number.
998
- // Default is null, so we can detect whether a plugin has set the invoice number
999
- $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $this->order_id );
1000
- if ($third_party_invoice_number !== null) {
1001
- return $third_party_invoice_number;
1002
- }
1003
-
1004
- if ( $invoice_number = $this->get_number('invoice') ) {
1005
- return $formatted_invoice_number = $invoice_number->get_formatted();
1006
- } else {
1007
- return '';
1008
- }
1009
- }
1010
-
1011
- public function invoice_number() {
1012
- echo $this->get_invoice_number();
1013
- }
1014
-
1015
- public function get_invoice_date() {
1016
- if ( $invoice_date = $this->get_date('invoice') ) {
1017
- return $invoice_date->date_i18n( apply_filters( 'wpo_wcpdf_date_format', wc_date_format(), $this ) );
1018
- } else {
1019
- return '';
1020
- }
1021
- }
1022
-
1023
- public function invoice_date() {
1024
- echo $this->get_invoice_date();
1025
- }
1026
-
1027
-
1028
- }
1029
-
1030
  endif; // class_exists
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document_Methods' ) ) :
13
+
14
+ /**
15
+ * Abstract Order Methods
16
+ *
17
+ * Collection of methods to be used on orders within a Document
18
+ * Created as abstract rather than traits to support PHP versions older than 5.4
19
+ *
20
+ * @class \WPO\WC\PDF_Invoices\Documents\Order_Document_Methods
21
+ * @version 2.0
22
+ * @category Class
23
+ * @author Ewout Fernhout
24
+ */
25
+
26
+ abstract class Order_Document_Methods extends Order_Document {
27
+ public function is_refund( $order ) {
28
+ if ( method_exists( $order, 'get_type') ) { // WC 3.0+
29
+ $is_refund = $order->get_type() == 'shop_order_refund';
30
+ } else {
31
+ $is_refund = get_post_type( WCX_Order::get_id( $order ) ) == 'shop_order_refund';
32
+ }
33
+
34
+ return $is_refund;
35
+ }
36
+
37
+ public function get_refund_parent_id( $order ) {
38
+ if ( method_exists( $order, 'get_parent_id') ) { // WC3.0+
39
+ $parent_order_id = $order->get_parent_id();
40
+ } else {
41
+ $parent_order_id = wp_get_post_parent_id( WCX_Order::get_id( $order ) );
42
+ }
43
+
44
+ return $parent_order_id;
45
+ }
46
+
47
+
48
+ public function get_refund_parent( $order ) {
49
+ // only try if this is actually a refund
50
+ if ( ! $this->is_refund( $order ) ) {
51
+ return $order;
52
+ }
53
+
54
+ $parent_order_id = $this->get_refund_parent_id( $order );
55
+ $order = WCX::get_order( $parent_order_id );
56
+ return $order;
57
+ }
58
+
59
+ /**
60
+ * Check if billing address and shipping address are equal
61
+ */
62
+ public function ships_to_different_address() {
63
+ // always prefer parent address for refunds
64
+ if ( $this->is_refund( $this->order ) ) {
65
+ $order = $this->get_refund_parent( $this->order );
66
+ } else {
67
+ $order = $this->order;
68
+ }
69
+
70
+ $address_comparison_fields = apply_filters( 'wpo_wcpdf_address_comparison_fields', array(
71
+ 'first_name',
72
+ 'last_name',
73
+ 'company',
74
+ 'address_1',
75
+ 'address_2',
76
+ 'city',
77
+ 'state',
78
+ 'postcode',
79
+ 'country'
80
+ ), $this );
81
+
82
+ foreach ($address_comparison_fields as $address_field) {
83
+ $billing_field = WCX_Order::get_prop( $order, "billing_{$address_field}", 'view');
84
+ $shipping_field = WCX_Order::get_prop( $order, "shipping_{$address_field}", 'view');
85
+ if ( $shipping_field != $billing_field ) {
86
+ // this address field is different -> ships to different address!
87
+ return true;
88
+ }
89
+ }
90
+
91
+ //if we got here, it means the addresses are equal -> doesn't ship to different address!
92
+ return apply_filters( 'wpo_wcpdf_ships_to_different_address', false, $order, $this );
93
+ }
94
+
95
+ /**
96
+ * Return/Show billing address
97
+ */
98
+ public function get_billing_address() {
99
+ // always prefer parent billing address for refunds
100
+ if ( $this->is_refund( $this->order ) ) {
101
+ // temporarily switch order to make all filters / order calls work correctly
102
+ $refund = $this->order;
103
+ $this->order = $this->get_refund_parent( $this->order );
104
+ $address = apply_filters( 'wpo_wcpdf_billing_address', $this->order->get_formatted_billing_address(), $this );
105
+ // switch back & unset
106
+ $this->order = $refund;
107
+ unset($refund);
108
+ } elseif ( $address = $this->order->get_formatted_billing_address() ) {
109
+ // regular shop_order
110
+ $address = apply_filters( 'wpo_wcpdf_billing_address', $address, $this );
111
+ } else {
112
+ // no address
113
+ $address = apply_filters( 'wpo_wcpdf_billing_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
114
+ }
115
+
116
+ return $address;
117
+ }
118
+ public function billing_address() {
119
+ echo $this->get_billing_address();
120
+ }
121
+
122
+ /**
123
+ * Return/Show billing email
124
+ */
125
+ public function get_billing_email() {
126
+ $billing_email = WCX_Order::get_prop( $this->order, 'billing_email', 'view' );
127
+
128
+ if ( !$billing_email && $this->is_refund( $this->order ) ) {
129
+ // try parent
130
+ $parent_order = $this->get_refund_parent( $this->order );
131
+ $billing_email = WCX_Order::get_prop( $parent_order, 'billing_email', 'view' );
132
+ }
133
+
134
+ return apply_filters( 'wpo_wcpdf_billing_email', $billing_email, $this );
135
+ }
136
+ public function billing_email() {
137
+ echo $this->get_billing_email();
138
+ }
139
+
140
+ /**
141
+ * Return/Show billing phone
142
+ */
143
+ public function get_billing_phone() {
144
+ $billing_phone = WCX_Order::get_prop( $this->order, 'billing_phone', 'view' );
145
+
146
+ if ( !$billing_phone && $this->is_refund( $this->order ) ) {
147
+ // try parent
148
+ $parent_order = $this->get_refund_parent( $this->order );
149
+ $billing_phone = WCX_Order::get_prop( $parent_order, 'billing_phone', 'view' );
150
+ }
151
+
152
+ return apply_filters( 'wpo_wcpdf_billing_phone', $billing_phone, $this );
153
+ }
154
+ public function billing_phone() {
155
+ echo $this->get_billing_phone();
156
+ }
157
+
158
+ /**
159
+ * Return/Show shipping address
160
+ */
161
+ public function get_shipping_address() {
162
+ // always prefer parent shipping address for refunds
163
+ if ( $this->is_refund( $this->order ) ) {
164
+ // temporarily switch order to make all filters / order calls work correctly
165
+ $refund = $this->order;
166
+ $this->order = $this->get_refund_parent( $this->order );
167
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', $this->order->get_formatted_shipping_address(), $this );
168
+ // switch back & unset
169
+ $this->order = $refund;
170
+ unset($refund);
171
+ } elseif ( $address = $this->order->get_formatted_shipping_address() ) {
172
+ // regular shop_order
173
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', $address, $this );
174
+ } else {
175
+ // no address
176
+ $address = apply_filters( 'wpo_wcpdf_shipping_address', __('N/A', 'woocommerce-pdf-invoices-packing-slips' ), $this );
177
+ }
178
+
179
+ return $address;
180
+ }
181
+ public function shipping_address() {
182
+ echo $this->get_shipping_address();
183
+ }
184
+
185
+ /**
186
+ * Return/Show a custom field
187
+ */
188
+ public function get_custom_field( $field_name ) {
189
+ $custom_field = WCX_Order::get_meta( $this->order, $field_name, true );
190
+ // if not found, try prefixed with underscore
191
+ if ( !$custom_field && substr( $field_name, 0, 1 ) !== '_' ) {
192
+ $custom_field = WCX_Order::get_meta( $this->order, "_{$field_name}", true );
193
+ }
194
+
195
+ // WC3.0 fallback to properties
196
+ $property = str_replace('-', '_', sanitize_title( ltrim($field_name, '_') ) );
197
+ if ( !$custom_field && is_callable( array( $this->order, "get_{$property}" ) ) ) {
198
+ $custom_field = $this->order->{"get_{$property}"}( 'view' );
199
+ }
200
+
201
+ // fallback to parent for refunds
202
+ if ( !$custom_field && $this->is_refund( $this->order ) ) {
203
+ $parent_order = $this->get_refund_parent( $this->order );
204
+ $custom_field = WCX_Order::get_meta( $parent_order, $field_name, true );
205
+
206
+ // WC3.0 fallback to properties
207
+ if ( !$custom_field && is_callable( array( $parent_order, "get_{$property}" ) ) ) {
208
+ $custom_field = $parent_order->{"get_{$property}"}( 'view' );
209
+ }
210
+ }
211
+
212
+ return apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this );
213
+ }
214
+ public function custom_field( $field_name, $field_label = '', $display_empty = false ) {
215
+ $custom_field = $this->get_custom_field( $field_name );
216
+ if (!empty($field_label)){
217
+ // add a a trailing space to the label
218
+ $field_label .= ' ';
219
+ }
220
+
221
+ if (!empty($custom_field) || $display_empty) {
222
+ echo $field_label . nl2br ($custom_field);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Return/show product attribute
228
+ */
229
+ public function get_product_attribute( $attribute_name, $product ) {
230
+ // first, check the text attributes
231
+ $attributes = $product->get_attributes();
232
+ $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
233
+ if (array_key_exists( sanitize_title( $attribute_name ), $attributes) ) {
234
+ $attribute = $product->get_attribute ( $attribute_name );
235
+ } elseif (array_key_exists( sanitize_title( $attribute_key ), $attributes) ) {
236
+ $attribute = $product->get_attribute ( $attribute_key );
237
+ }
238
+
239
+ if (empty($attribute)) {
240
+ // not a text attribute, try attribute taxonomy
241
+ $attribute_key = @wc_attribute_taxonomy_name( $attribute_name );
242
+ $product_id = WCX_Product::get_prop($product, 'id');
243
+ $product_terms = @wc_get_product_terms( $product_id, $attribute_key, array( 'fields' => 'names' ) );
244
+ // check if not empty, then display
245
+ if ( !empty($product_terms) ) {
246
+ $attribute = array_shift( $product_terms );
247
+ }
248
+ }
249
+
250
+ // WC3.0+ fallback parent product for variations
251
+ if ( empty($attribute) && version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) && $product->is_type( 'variation' ) ) {
252
+ $product = wc_get_product( $product->get_parent_id() );
253
+ $attribute = $this->get_product_attribute( $attribute_name, $product );
254
+ }
255
+
256
+ return isset($attribute) ? $attribute : false;
257
+ }
258
+ public function product_attribute( $attribute_name, $product ) {
259
+ echo $this->get_product_attribute( $attribute_name, $product );
260
+ }
261
+
262
+ /**
263
+ * Return/Show order notes
264
+ * could use $order->get_customer_order_notes(), but that filters out private notes already
265
+ */
266
+ public function get_order_notes( $filter = 'customer' ) {
267
+ if ( $this->is_refund( $this->order ) ) {
268
+ $post_id = $this->get_refund_parent_id( $this->order );
269
+ } else {
270
+ $post_id = $this->order_id;
271
+ }
272
+
273
+ if ( empty( $post_id ) ) {
274
+ return; // prevent order notes from all orders showing when document is not loaded properly
275
+ }
276
+
277
+ $args = array(
278
+ 'post_id' => $post_id,
279
+ 'approve' => 'approve',
280
+ 'type' => 'order_note'
281
+ );
282
+
283
+ remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
284
+
285
+ $notes = get_comments( $args );
286
+
287
+ add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
288
+
289
+ if ( $notes ) {
290
+ foreach( $notes as $key => $note ) {
291
+ if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
292
+ unset($notes[$key]);
293
+ }
294
+ if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) {
295
+ unset($notes[$key]);
296
+ }
297
+ }
298
+ return $notes;
299
+ }
300
+ }
301
+ public function order_notes( $filter = 'customer' ) {
302
+ $notes = $this->get_order_notes( $filter );
303
+ if ( $notes ) {
304
+ foreach( $notes as $note ) {
305
+ ?>
306
+ <div class="note_content">
307
+ <?php echo wpautop( wptexturize( wp_kses_post( $note->comment_content ) ) ); ?>
308
+ </div>
309
+ <?php
310
+ }
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Return/Show the current date
316
+ */
317
+ public function get_current_date() {
318
+ return apply_filters( 'wpo_wcpdf_date', date_i18n( get_option( 'date_format' ) ) );
319
+ }
320
+ public function current_date() {
321
+ echo $this->get_current_date();
322
+ }
323
+
324
+ /**
325
+ * Return/Show payment method
326
+ */
327
+ public function get_payment_method() {
328
+ $payment_method_label = __( 'Payment method', 'woocommerce-pdf-invoices-packing-slips' );
329
+
330
+ if ( $this->is_refund( $this->order ) ) {
331
+ $parent_order = $this->get_refund_parent( $this->order );
332
+ $payment_method_title = WCX_Order::get_prop( $parent_order, 'payment_method_title', 'view' );
333
+ } else {
334
+ $payment_method_title = WCX_Order::get_prop( $this->order, 'payment_method_title', 'view' );
335
+ }
336
+
337
+ $payment_method = __( $payment_method_title, 'woocommerce' );
338
+
339
+ return apply_filters( 'wpo_wcpdf_payment_method', $payment_method, $this );
340
+ }
341
+ public function payment_method() {
342
+ echo $this->get_payment_method();
343
+ }
344
+
345
+ /**
346
+ * Return/Show shipping method
347
+ */
348
+ public function get_shipping_method() {
349
+ $shipping_method_label = __( 'Shipping method', 'woocommerce-pdf-invoices-packing-slips' );
350
+ $shipping_method = __( $this->order->get_shipping_method(), 'woocommerce' );
351
+ return apply_filters( 'wpo_wcpdf_shipping_method', $shipping_method, $this );
352
+ }
353
+ public function shipping_method() {
354
+ echo $this->get_shipping_method();
355
+ }
356
+
357
+ /**
358
+ * Return/Show order number
359
+ */
360
+ public function get_order_number() {
361
+ // try parent first
362
+ if ( $this->is_refund( $this->order ) ) {
363
+ $parent_order = $this->get_refund_parent( $this->order );
364
+ $order_number = $parent_order->get_order_number();
365
+ } else {
366
+ $order_number = $this->order->get_order_number();
367
+ }
368
+
369
+ // Trim the hash to have a clean number but still
370
+ // support any filters that were applied before.
371
+ $order_number = ltrim($order_number, '#');
372
+ return apply_filters( 'wpo_wcpdf_order_number', $order_number, $this );
373
+ }
374
+ public function order_number() {
375
+ echo $this->get_order_number();
376
+ }
377
+
378
+ /**
379
+ * Return/Show the order date
380
+ */
381
+ public function get_order_date() {
382
+ if ( $this->is_refund( $this->order ) ) {
383
+ $parent_order = $this->get_refund_parent( $this->order );
384
+ $order_date = WCX_Order::get_prop( $parent_order, 'date_created' );
385
+ } else {
386
+ $order_date = WCX_Order::get_prop( $this->order, 'date_created' );
387
+ }
388
+
389
+ $date = $order_date->date_i18n( get_option( 'date_format' ) );
390
+ $mysql_date = $order_date->date( "Y-m-d H:i:s" );
391
+ return apply_filters( 'wpo_wcpdf_order_date', $date, $mysql_date, $this );
392
+ }
393
+ public function order_date() {
394
+ echo $this->get_order_date();
395
+ }
396
+
397
+ /**
398
+ * Return the order items
399
+ */
400
+ public function get_order_items() {
401
+ $items = $this->order->get_items();
402
+ $data_list = array();
403
+
404
+ if( sizeof( $items ) > 0 ) {
405
+ foreach ( $items as $item_id => $item ) {
406
+ // Array with data for the pdf template
407
+ $data = array();
408
+
409
+ // Set the item_id
410
+ $data['item_id'] = $item_id;
411
+
412
+ // Set the id
413
+ $data['product_id'] = $item['product_id'];
414
+ $data['variation_id'] = $item['variation_id'];
415
+
416
+ // Set item name
417
+ $data['name'] = $item['name'];
418
+
419
+ // Set item quantity
420
+ $data['quantity'] = $item['qty'];
421
+
422
+ // Set the line total (=after discount)
423
+ $data['line_total'] = $this->format_price( $item['line_total'] );
424
+ $data['single_line_total'] = $this->format_price( $item['line_total'] / max( 1, abs( $item['qty'] ) ) );
425
+ $data['line_tax'] = $this->format_price( $item['line_tax'] );
426
+ $data['single_line_tax'] = $this->format_price( $item['line_tax'] / max( 1, abs( $item['qty'] ) ) );
427
+
428
+ $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' );
429
+ $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data );
430
+
431
+ // Set the line subtotal (=before discount)
432
+ $data['line_subtotal'] = $this->format_price( $item['line_subtotal'] );
433
+ $data['line_subtotal_tax'] = $this->format_price( $item['line_subtotal_tax'] );
434
+ $data['ex_price'] = $this->get_formatted_item_price( $item, 'total', 'excl' );
435
+ $data['price'] = $this->get_formatted_item_price( $item, 'total' );
436
+ $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings
437
+
438
+ // Calculate the single price with the same rules as the formatted line subtotal (!)
439
+ // = before discount
440
+ $data['ex_single_price'] = $this->get_formatted_item_price( $item, 'single', 'excl' );
441
+ $data['single_price'] = $this->get_formatted_item_price( $item, 'single' );
442
+
443
+ // Pass complete item array
444
+ $data['item'] = $item;
445
+
446
+ // Get the product to add more info
447
+ $product = $this->order->get_product_from_item( $item );
448
+
449
+ // Checking fo existance, thanks to MDesigner0
450
+ if( !empty( $product ) ) {
451
+ // Thumbnail (full img tag)
452
+ $data['thumbnail'] = $this->get_thumbnail( $product );
453
+
454
+ // Set item SKU
455
+ $data['sku'] = $product->get_sku();
456
+
457
+ // Set item weight
458
+ $data['weight'] = $product->get_weight();
459
+
460
+ // Set item dimensions
461
+ $data['dimensions'] = WCX_Product::get_dimensions( $product );
462
+
463
+ // Pass complete product object
464
+ $data['product'] = $product;
465
+
466
+ } else {
467
+ $data['product'] = null;
468
+ }
469
+
470
+ // Set item meta
471
+ if (function_exists('wc_display_item_meta')) { // WC3.0+
472
+ $data['meta'] = wc_display_item_meta( $item, array(
473
+ 'echo' => false,
474
+ ) );
475
+ } else {
476
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) {
477
+ $meta = new \WC_Order_Item_Meta( $item['item_meta'], $product );
478
+ } else { // pass complete item for WC2.4+
479
+ $meta = new \WC_Order_Item_Meta( $item, $product );
480
+ }
481
+ $data['meta'] = $meta->display( false, true );
482
+ }
483
+
484
+ $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order, $this->get_type() );
485
+ }
486
+ }
487
+
488
+ return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order, $this->get_type() );
489
+ }
490
+
491
+ /**
492
+ * Get the tax rates/percentages for a given tax class
493
+ * @param string $tax_class tax class slug
494
+ * @return string $tax_rates imploded list of tax rates
495
+ */
496
+ public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '' ) {
497
+ // first try the easy wc2.2+ way, using line_tax_data
498
+ if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) {
499
+ $tax_rates = array();
500
+
501
+ $line_taxes = $line_tax_data['subtotal'];
502
+ foreach ( $line_taxes as $tax_id => $tax ) {
503
+ if ( isset($tax) && $tax !== '' ) {
504
+ $tax_rates[] = $this->get_tax_rate_by_id( $tax_id ) . ' %';
505
+ }
506
+ }
507
+
508
+ $tax_rates = implode(' ,', $tax_rates );
509
+ return $tax_rates;
510
+ }
511
+
512
+ if ( $line_tax == 0 ) {
513
+ return '-'; // no need to determine tax rate...
514
+ }
515
+
516
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) {
517
+ // WC 2.1 or newer is used
518
+ $tax = new \WC_Tax();
519
+ $taxes = $tax->get_rates( $tax_class );
520
+
521
+ $tax_rates = array();
522
+
523
+ foreach ($taxes as $tax) {
524
+ $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %';
525
+ }
526
+
527
+ if (empty($tax_rates)) {
528
+ // one last try: manually calculate
529
+ if ( $line_total != 0) {
530
+ $tax_rates[] = round( ($line_tax / $line_total)*100, 1 ).' %';
531
+ } else {
532
+ $tax_rates[] = '-';
533
+ }
534
+ }
535
+
536
+ $tax_rates = implode(' ,', $tax_rates );
537
+ } else {
538
+ // Backwards compatibility/fallback: calculate tax from line items
539
+ if ( $line_total != 0) {
540
+ $tax_rates = round( ($line_tax / $line_total)*100, 1 ).' %';
541
+ } else {
542
+ $tax_rates = '-';
543
+ }
544
+ }
545
+
546
+ return $tax_rates;
547
+ }
548
+
549
+ /**
550
+ * Returns the percentage rate (float) for a given tax rate ID.
551
+ * @param int $rate_id woocommerce tax rate id
552
+ * @return float $rate percentage rate
553
+ */
554
+ public function get_tax_rate_by_id( $rate_id ) {
555
+ global $wpdb;
556
+ $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) );
557
+ return (float) $rate;
558
+ }
559
+
560
+ /**
561
+ * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce
562
+ * @return array $tax_rate_ids keyed by id
563
+ */
564
+ public function get_tax_rate_ids() {
565
+ global $wpdb;
566
+ $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" );
567
+
568
+ $tax_rate_ids = array();
569
+ foreach ($rates as $rate) {
570
+ // var_dump($rate->tax_rate_id);
571
+ // die($rate);
572
+ $rate_id = $rate->tax_rate_id;
573
+ unset($rate->tax_rate_id);
574
+ $tax_rate_ids[$rate_id] = (array) $rate;
575
+ }
576
+
577
+ return $tax_rate_ids;
578
+ }
579
+
580
+ /**
581
+ * Returns the main product image ID
582
+ * Adapted from the WC_Product class
583
+ * (does not support thumbnail sizes)
584
+ *
585
+ * @access public
586
+ * @return string
587
+ */
588
+ public function get_thumbnail_id ( $product ) {
589
+ global $woocommerce;
590
+
591
+ $product_id = WCX_Product::get_id( $product );
592
+
593
+ if ( has_post_thumbnail( $product_id ) ) {
594
+ $thumbnail_id = get_post_thumbnail_id ( $product_id );
595
+ } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) {
596
+ $thumbnail_id = get_post_thumbnail_id ( $parent_id );
597
+ } else {
598
+ $thumbnail_id = false;
599
+ }
600
+
601
+ return $thumbnail_id;
602
+ }
603
+
604
+ /**
605
+ * Returns the thumbnail image tag
606
+ *
607
+ * uses the internal WooCommerce/WP functions and extracts the image url or path
608
+ * rather than the thumbnail ID, to simplify the code and make it possible to
609
+ * filter for different thumbnail sizes
610
+ *
611
+ * @access public
612
+ * @return string
613
+ */
614
+ public function get_thumbnail ( $product ) {
615
+ // Get default WooCommerce img tag (url/http)
616
+ $size = apply_filters( 'wpo_wcpdf_thumbnail_size', 'shop_thumbnail' );
617
+ $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) );
618
+
619
+ // Extract the url from img
620
+ preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url );
621
+ $thumbnail_url = array_pop($thumbnail_url);
622
+ // remove http/https from image tag url to avoid mixed origin conflicts
623
+ $contextless_thumbnail_url = ltrim( str_replace(array('http://','https://'), '', $thumbnail_url ), '/' );
624
+
625
+ // convert url to path
626
+ if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
627
+ $forwardslash_basepath = str_replace('\\','/', ABSPATH);
628
+ $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(get_site_url()));
629
+ } else {
630
+ // bedrock e.a
631
+ $forwardslash_basepath = str_replace('\\','/', WP_CONTENT_DIR);
632
+ $contextless_site_url = str_replace(array('http://','https://'), '', trailingslashit(WP_CONTENT_URL));
633
+ }
634
+ $thumbnail_path = str_replace( $contextless_site_url, trailingslashit( $forwardslash_basepath ), $contextless_thumbnail_url);
635
+
636
+ // fallback if thumbnail file doesn't exist
637
+ if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) {
638
+ if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) {
639
+ $thumbnail_path = get_attached_file( $thumbnail_id );
640
+ }
641
+ }
642
+
643
+ // Thumbnail (full img tag)
644
+ if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path) ) {
645
+ // load img with server path by default
646
+ $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path );
647
+ } elseif ( apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path) ) {
648
+ // should use paths but file not found, replace // with http(s):// for dompdf compatibility
649
+ if ( substr( $thumbnail_url, 0, 2 ) === "//" ) {
650
+ $prefix = is_ssl() ? 'https://' : 'http://';
651
+ $https_thumbnail_url = $prefix . ltrim( $thumbnail_url, '/' );
652
+ }
653
+ $thumbnail_img_tag_url = str_replace($thumbnail_url, $https_thumbnail_url, $thumbnail_img_tag_url);
654
+ $thumbnail = $thumbnail_img_tag_url;
655
+ } else {
656
+ // load img with http url when filtered
657
+ $thumbnail = $thumbnail_img_tag_url;
658
+ }
659
+
660
+ // die($thumbnail);
661
+ return $thumbnail;
662
+ }
663
+
664
+ /**
665
+ * Return the order totals listing
666
+ */
667
+ public function get_woocommerce_totals() {
668
+ // get totals and remove the semicolon
669
+ $totals = apply_filters( 'wpo_wcpdf_raw_order_totals', $this->order->get_order_item_totals(), $this->order );
670
+
671
+ // remove the colon for every label
672
+ foreach ( $totals as $key => $total ) {
673
+ $label = $total['label'];
674
+ $colon = strrpos( $label, ':' );
675
+ if( $colon !== false ) {
676
+ $label = substr_replace( $label, '', $colon, 1 );
677
+ }
678
+ $totals[$key]['label'] = $label;
679
+ }
680
+
681
+ // WC2.4 fix order_total for refunded orders
682
+ // not if this is the actual refund!
683
+ if ( ! $this->is_refund( $this->order ) ) {
684
+ $total_refunded = method_exists($this->order, 'get_total_refunded') ? $this->order->get_total_refunded() : 0;
685
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '>=' ) && isset($totals['order_total']) && $total_refunded ) {
686
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
687
+ $tax_display = get_option( 'woocommerce_tax_display_cart' );
688
+ } else {
689
+ $tax_display = WCX_Order::get_prop( $this->order, 'tax_display_cart' );
690
+ }
691
+
692
+ $totals['order_total']['value'] = wc_price( $this->order->get_total(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) );
693
+ $order_total = $this->order->get_total();
694
+ $tax_string = '';
695
+
696
+ // Tax for inclusive prices
697
+ if ( wc_tax_enabled() && 'incl' == $tax_display ) {
698
+ $tax_string_array = array();
699
+ if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
700
+ foreach ( $this->order->get_tax_totals() as $code => $tax ) {
701
+ $tax_amount = $tax->formatted_amount;
702
+ $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
703
+ }
704
+ } else {
705
+ $tax_string_array[] = sprintf( '%s %s', wc_price( $this->order->get_total_tax() - $this->order->get_total_tax_refunded(), array( 'currency' => WCX_Order::get_prop( $this->order, 'currency' ) ) ), WC()->countries->tax_or_vat() );
706
+ }
707
+ if ( ! empty( $tax_string_array ) ) {
708
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.6', '>=' ) ) {
709
+ $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
710
+ } else {
711
+ // use old capitalized string
712
+ $tax_string = ' ' . sprintf( __( '(Includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
713
+ }
714
+ }
715
+ }
716
+
717
+ $totals['order_total']['value'] .= $tax_string;
718
+ }
719
+
720
+ // remove refund lines (shouldn't be in invoice)
721
+ foreach ( $totals as $key => $total ) {
722
+ if ( strpos($key, 'refund_') !== false ) {
723
+ unset( $totals[$key] );
724
+ }
725
+ }
726
+
727
+ }
728
+
729
+ return apply_filters( 'wpo_wcpdf_woocommerce_totals', $totals, $this->order, $this->get_type() );
730
+ }
731
+
732
+ /**
733
+ * Return/show the order subtotal
734
+ */
735
+ public function get_order_subtotal( $tax = 'excl', $discount = 'incl' ) { // set $tax to 'incl' to include tax, same for $discount
736
+ //$compound = ($discount == 'incl')?true:false;
737
+ $subtotal = $this->order->get_subtotal_to_display( false, $tax );
738
+
739
+ $subtotal = ($pos = strpos($subtotal, ' <small')) ? substr($subtotal, 0, $pos) : $subtotal; //removing the 'excluding tax' text
740
+
741
+ $subtotal = array (
742
+ 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips' ),
743
+ 'value' => $subtotal,
744
+ );
745
+
746
+ return apply_filters( 'wpo_wcpdf_order_subtotal', $subtotal, $tax, $discount, $this );
747
+ }
748
+ public function order_subtotal( $tax = 'excl', $discount = 'incl' ) {
749
+ $subtotal = $this->get_order_subtotal( $tax, $discount );
750
+ echo $subtotal['value'];
751
+ }
752
+
753
+ /**
754
+ * Return/show the order shipping costs
755
+ */
756
+ public function get_order_shipping( $tax = 'excl' ) { // set $tax to 'incl' to include tax
757
+ $shipping_cost = WCX_Order::get_prop( $this->order, 'shipping_total', 'view' );
758
+ $shipping_tax = WCX_Order::get_prop( $this->order, 'shipping_tax', 'view' );
759
+
760
+ if ($tax == 'excl' ) {
761
+ $formatted_shipping_cost = $this->format_price( $shipping_cost );
762
+ } else {
763
+ $formatted_shipping_cost = $this->format_price( $shipping_cost + $shipping_tax );
764
+ }
765
+
766
+ $shipping = array (
767
+ 'label' => __('Shipping', 'woocommerce-pdf-invoices-packing-slips' ),
768
+ 'value' => $formatted_shipping_cost,
769
+ 'tax' => $this->format_price( $shipping_tax ),
770
+ );
771
+ return apply_filters( 'wpo_wcpdf_order_shipping', $shipping, $tax, $this );
772
+ }
773
+ public function order_shipping( $tax = 'excl' ) {
774
+ $shipping = $this->get_order_shipping( $tax );
775
+ echo $shipping['value'];
776
+ }
777
+
778
+ /**
779
+ * Return/show the total discount
780
+ */
781
+ public function get_order_discount( $type = 'total', $tax = 'incl' ) {
782
+ if ( $tax == 'incl' ) {
783
+ switch ($type) {
784
+ case 'cart':
785
+ // Cart Discount - pre-tax discounts. (deprecated in WC2.3)
786
+ $discount_value = $this->order->get_cart_discount();
787
+ break;
788
+ case 'order':
789
+ // Order Discount - post-tax discounts. (deprecated in WC2.3)
790
+ $discount_value = $this->order->get_order_discount();
791
+ break;
792
+ case 'total':
793
+ // Total Discount
794
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
795
+ $discount_value = $this->order->get_total_discount( false ); // $ex_tax = false
796
+ } else {
797
+ // WC2.2 and older: recalculate to include tax
798
+ $discount_value = 0;
799
+ $items = $this->order->get_items();;
800
+ if( sizeof( $items ) > 0 ) {
801
+ foreach( $items as $item ) {
802
+ $discount_value += ($item['line_subtotal'] + $item['line_subtotal_tax']) - ($item['line_total'] + $item['line_tax']);
803
+ }
804
+ }
805
+ }
806
+
807
+ break;
808
+ default:
809
+ // Total Discount - Cart & Order Discounts combined
810
+ $discount_value = $this->order->get_total_discount();
811
+ break;
812
+ }
813
+ } else { // calculate discount excluding tax
814
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.3' ) >= 0 ) {
815
+ $discount_value = $this->order->get_total_discount( true ); // $ex_tax = true
816
+ } else {
817
+ // WC2.2 and older: recalculate to exclude tax
818
+ $discount_value = 0;
819
+
820
+ $items = $this->order->get_items();;
821
+ if( sizeof( $items ) > 0 ) {
822
+ foreach( $items as $item ) {
823
+ $discount_value += ($item['line_subtotal'] - $item['line_total']);
824
+ }
825
+ }
826
+ }
827
+ }
828
+
829
+ $discount = array (
830
+ 'label' => __('Discount', 'woocommerce-pdf-invoices-packing-slips' ),
831
+ 'value' => $this->format_price( $discount_value ),
832
+ 'raw_value' => $discount_value,
833
+ );
834
+
835
+ if ( round( $discount_value, 3 ) != 0 ) {
836
+ return apply_filters( 'wpo_wcpdf_order_discount', $discount, $type, $tax, $this );
837
+ }
838
+ }
839
+ public function order_discount( $type = 'total', $tax = 'incl' ) {
840
+ $discount = $this->get_order_discount( $type, $tax );
841
+ echo $discount['value'];
842
+ }
843
+
844
+ /**
845
+ * Return the order fees
846
+ */
847
+ public function get_order_fees( $tax = 'excl' ) {
848
+ if ( $_fees = $this->order->get_fees() ) {
849
+ foreach( $_fees as $id => $fee ) {
850
+ if ($tax == 'excl' ) {
851
+ $fee_price = $this->format_price( $fee['line_total'] );
852
+ } else {
853
+ $fee_price = $this->format_price( $fee['line_total'] + $fee['line_tax'] );
854
+ }
855
+
856
+ $fees[ $id ] = array(
857
+ 'label' => $fee['name'],
858
+ 'value' => $fee_price,
859
+ 'line_total' => $this->format_price( $fee['line_total'] ),
860
+ 'line_tax' => $this->format_price( $fee['line_tax'] )
861
+ );
862
+ }
863
+ return $fees;
864
+ }
865
+ }
866
+
867
+ /**
868
+ * Return the order taxes
869
+ */
870
+ public function get_order_taxes() {
871
+ $tax_label = __( 'VAT', 'woocommerce-pdf-invoices-packing-slips' ); // register alternate label translation
872
+ $tax_label = __( 'Tax rate', 'woocommerce-pdf-invoices-packing-slips' );
873
+ $tax_rate_ids = $this->get_tax_rate_ids();
874
+ if ( $order_taxes = $this->order->get_taxes() ) {
875
+ foreach ( $order_taxes as $key => $tax ) {
876
+ if ( WCX::is_wc_version_gte_3_0() ) {
877
+ $taxes[ $key ] = array(
878
+ 'label' => $tax->get_label(),
879
+ 'value' => $this->format_price( $tax->get_tax_total() + $tax->get_shipping_tax_total() ),
880
+ 'rate_id' => $tax->get_rate_id(),
881
+ 'tax_amount' => $tax->get_tax_total(),
882
+ 'shipping_tax_amount' => $tax->get_shipping_tax_total(),
883
+ 'rate' => isset( $tax_rate_ids[ $tax->get_rate_id() ] ) ? ( (float) $tax_rate_ids[$tax->get_rate_id()]['tax_rate'] ) . ' %': '',
884
+ );
885
+ } else {
886
+ $taxes[ $key ] = array(
887
+ 'label' => isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ],
888
+ 'value' => $this->format_price( ( $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ] ) ),
889
+ 'rate_id' => $tax['rate_id'],
890
+ 'tax_amount' => $tax['tax_amount'],
891
+ 'shipping_tax_amount' => $tax['shipping_tax_amount'],
892
+ 'rate' => isset( $tax_rate_ids[ $tax['rate_id'] ] ) ? ( (float) $tax_rate_ids[$tax['rate_id']]['tax_rate'] ) . ' %': '',
893
+ );
894
+ }
895
+
896
+ }
897
+
898
+ return apply_filters( 'wpo_wcpdf_order_taxes', $taxes, $this );
899
+ }
900
+ }
901
+
902
+ /**
903
+ * Return/show the order grand total
904
+ */
905
+ public function get_order_grand_total( $tax = 'incl' ) {
906
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) {
907
+ // WC 2.1 or newer is used
908
+ $total_unformatted = $this->order->get_total();
909
+ } else {
910
+ // Backwards compatibility
911
+ $total_unformatted = $this->order->get_order_total();
912
+ }
913
+
914
+ if ($tax == 'excl' ) {
915
+ $total = $this->format_price( $total_unformatted - $this->order->get_total_tax() );
916
+ $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' );
917
+ } else {
918
+ $total = $this->format_price( ( $total_unformatted ) );
919
+ $label = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' );
920
+ }
921
+
922
+ $grand_total = array(
923
+ 'label' => $label,
924
+ 'value' => $total,
925
+ );
926
+
927
+ return apply_filters( 'wpo_wcpdf_order_grand_total', $grand_total, $tax, $this );
928
+ }
929
+ public function order_grand_total( $tax = 'incl' ) {
930
+ $grand_total = $this->get_order_grand_total( $tax );
931
+ echo $grand_total['value'];
932
+ }
933
+
934
+
935
+ /**
936
+ * Return/Show shipping notes
937
+ */
938
+ public function get_shipping_notes() {
939
+ if ( $this->is_refund( $this->order ) ) {
940
+ // return reason for refund if order is a refund
941
+ if ( version_compare( WOOCOMMERCE_VERSION, '3.0', '>=' ) ) {
942
+ $shipping_notes = $this->order->get_reason();
943
+ } elseif ( method_exists($this->order, 'get_refund_reason') ) {
944
+ $shipping_notes = $this->order->get_refund_reason();
945
+ } else {
946
+ $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
947
+ }
948
+ } else {
949
+ $shipping_notes = wpautop( wptexturize( WCX_Order::get_prop( $this->order, 'customer_note', 'view' ) ) );
950
+ }
951
+ return apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this );
952
+ }
953
+ public function shipping_notes() {
954
+ echo $this->get_shipping_notes();
955
+ }
956
+
957
+ /**
958
+ * wrapper for wc_price, ensuring currency is always passed
959
+ */
960
+ public function format_price( $price, $args = array() ) {
961
+ if ( function_exists( 'wc_price' ) ) { // WC 2.1+
962
+ $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' );
963
+ $formatted_price = wc_price( $price, $args );
964
+ } else {
965
+ $formatted_price = woocommerce_price( $price );
966
+ }
967
+
968
+ return $formatted_price;
969
+ }
970
+ public function wc_price( $price, $args = array() ) {
971
+ return $this->format_price( $price, $args );
972
+ }
973
+
974
+ /**
975
+ * Gets price - formatted for display.
976
+ *
977
+ * @access public
978
+ * @param mixed $item
979
+ * @return string
980
+ */
981
+ public function get_formatted_item_price ( $item, $type, $tax_display = '' ) {
982
+ if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
983
+ return;
984
+ }
985
+
986
+ $divide_by = ($type == 'single' && $item['qty'] != 0 )?abs($item['qty']):1; //divide by 1 if $type is not 'single' (thus 'total')
987
+ if ( $tax_display == 'excl' ) {
988
+ $item_price = $this->format_price( ($this->order->get_line_subtotal( $item )) / $divide_by );
989
+ } else {
990
+ $item_price = $this->format_price( ($this->order->get_line_subtotal( $item, true )) / $divide_by );
991
+ }
992
+
993
+ return $item_price;
994
+ }
995
+
996
+ public function get_invoice_number() {
997
+ // Call the woocommerce_invoice_number filter and let third-party plugins set a number.
998
+ // Default is null, so we can detect whether a plugin has set the invoice number
999
+ $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $this->order_id );
1000
+ if ($third_party_invoice_number !== null) {
1001
+ return $third_party_invoice_number;
1002
+ }
1003
+
1004
+ if ( $invoice_number = $this->get_number('invoice') ) {
1005
+ return $formatted_invoice_number = $invoice_number->get_formatted();
1006
+ } else {
1007
+ return '';
1008
+ }
1009
+ }
1010
+
1011
+ public function invoice_number() {
1012
+ echo $this->get_invoice_number();
1013
+ }
1014
+
1015
+ public function get_invoice_date() {
1016
+ if ( $invoice_date = $this->get_date('invoice') ) {
1017
+ return $invoice_date->date_i18n( apply_filters( 'wpo_wcpdf_date_format', wc_date_format(), $this ) );
1018
+ } else {
1019
+ return '';
1020
+ }
1021
+ }
1022
+
1023
+ public function invoice_date() {
1024
+ echo $this->get_invoice_date();
1025
+ }
1026
+
1027
+
1028
+ }
1029
+
1030
  endif; // class_exists
includes/documents/abstract-wcpdf-order-document.php CHANGED
@@ -1,676 +1,677 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
- use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit; // Exit if accessed directly
11
- }
12
-
13
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document' ) ) :
14
-
15
- /**
16
- * Abstract Document
17
- *
18
- * Handles generic pdf document & order data and database interaction
19
- * which is extended by both Invoices & Packing Slips
20
- *
21
- * @class \WPO\WC\PDF_Invoices\Documents\Order_Document
22
- * @version 2.0
23
- * @category Class
24
- * @author Ewout Fernhout
25
- */
26
-
27
- abstract class Order_Document {
28
- /**
29
- * Document type.
30
- * @var String
31
- */
32
- public $type;
33
-
34
- /**
35
- * Document slug.
36
- * @var String
37
- */
38
- public $slug;
39
-
40
- /**
41
- * Document title.
42
- * @var string
43
- */
44
- public $title;
45
-
46
- /**
47
- * Document icon.
48
- * @var string
49
- */
50
- public $icon;
51
-
52
- /**
53
- * WC Order object
54
- * @var object
55
- */
56
- public $order;
57
-
58
- /**
59
- * WC Order ID
60
- * @var object
61
- */
62
- public $order_id;
63
-
64
- /**
65
- * Document settings.
66
- * @var array
67
- */
68
- public $settings;
69
-
70
- /**
71
- * TRUE if document is enabled.
72
- * @var bool
73
- */
74
- public $enabled;
75
-
76
- /**
77
- * Linked documents, used for data retrieval
78
- * @var array
79
- */
80
- protected $linked_documents = array();
81
-
82
- /**
83
- * Core data for this object. Name value pairs (name + default value).
84
- * @var array
85
- */
86
- protected $data = array();
87
-
88
- /**
89
- * Init/load the order object.
90
- *
91
- * @param int|object|WC_Order $order Order to init.
92
- */
93
- public function __construct( $order = 0 ) {
94
- if ( is_numeric( $order ) && $order > 0 ) {
95
- $this->order_id = $order;
96
- $this->order = WCX::get_order( $order_id );
97
- } elseif ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) {
98
- $this->order_id = WCX_Order::get_id( $order );
99
- $this->order = $order;
100
- }
101
-
102
- // set properties
103
- $this->slug = str_replace('-', '_', $this->type);
104
-
105
- // load settings
106
- $this->settings = $this->get_settings();
107
- $this->enabled = $this->get_setting( 'enabled', false );
108
-
109
- // load data
110
- if ( $this->order ) {
111
- $this->read_data( $this->order );
112
- if ( WPO_WCPDF()->legacy_mode_enabled() ) {
113
- global $wpo_wcpdf;
114
- $wpo_wcpdf->export->order = $this->order;
115
- $wpo_wcpdf->export->document = $this;
116
- $wpo_wcpdf->export->order_id = $this->order_id;
117
- $wpo_wcpdf->export->template_type = $this->type;
118
- }
119
- }
120
-
121
- }
122
-
123
- public function init_settings() {
124
- return ;
125
- }
126
-
127
- public function get_settings() {
128
- $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
129
- $document_settings = get_option( 'wpo_wcpdf_documents_settings_'.$this->get_type() );
130
- return (array) $document_settings + (array) $common_settings;
131
- }
132
-
133
- public function get_setting( $key, $default = '' ) {
134
- $setting = isset( $this->settings[$key] ) ? $this->settings[$key] : $default;
135
- return $setting;
136
- }
137
-
138
- public function get_attach_to_email_ids() {
139
- $email_ids = isset( $this->settings['attach_to_email_ids'] ) ? array_keys( $this->settings['attach_to_email_ids'] ) : array();
140
- return $email_ids;
141
- }
142
-
143
- public function get_type() {
144
- return $this->type;
145
- }
146
-
147
- public function is_enabled() {
148
- return apply_filters( 'wpo_wcpdf_document_is_enabled', $this->enabled, $this->type );
149
- }
150
-
151
- public function get_hook_prefix() {
152
- return 'wpo_wcpdf_' . $this->slug . '_get_';
153
- }
154
-
155
- public function read_data( $order ) {
156
- $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number_data", true );
157
- // fallback to legacy data for number
158
- if ( empty( $number ) ) {
159
- $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number", true );
160
- $formatted_number = WCX_Order::get_meta( $order, "_wcpdf_formatted_{$this->slug}_number", true );
161
- if (!empty($formatted_number)) {
162
- $number = compact( 'number', 'formatted_number' );
163
- }
164
- }
165
-
166
- // pass data to setter functions
167
- $this->set_data( array(
168
- // always load date before number, because date is used in number formatting
169
- 'date' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_date", true ),
170
- 'number' => $number,
171
- ), $order );
172
-
173
- return;
174
- }
175
-
176
- public function init() {
177
- $this->set_date( current_time( 'timestamp', true ) );
178
- }
179
-
180
- public function save( $order = null ) {
181
- $order = empty( $order ) ? $this->order : $order;
182
- if ( empty( $order ) ) {
183
- return; // nowhere to save to...
184
- }
185
-
186
- foreach ($this->data as $key => $value) {
187
- if ( empty( $value ) ) {
188
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}" );
189
- if ( $key == 'date' ) {
190
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted" );
191
- } elseif ( $key == 'number' ) {
192
- WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data" );
193
- }
194
- } else {
195
- if ( $key == 'date' ) {
196
- // store dates as timestamp and formatted as mysql time
197
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->getTimestamp() );
198
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted", $value->date( 'Y-m-d H:i:s' ) );
199
- } elseif ( $key == 'number' ) {
200
- // store both formatted number and number data
201
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->formatted_number );
202
- WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data", $value->to_array() );
203
- }
204
- }
205
- }
206
- }
207
-
208
- public function exists() {
209
- return !empty( $this->data['number'] );
210
- }
211
-
212
- /*
213
- |--------------------------------------------------------------------------
214
- | Data getters
215
- |--------------------------------------------------------------------------
216
- */
217
-
218
- public function get_data( $key, $document_type = '', $order = null, $context = 'view' ) {
219
- $document_type = empty( $document_type ) ? $this->type : $document_type;
220
- $order = empty( $order ) ? $this->order : $order;
221
-
222
- // redirect get_data call for linked documents
223
- if ( $document_type != $this->type ) {
224
- if ( !isset( $this->linked_documents[ $document_type ] ) ) {
225
- // always assume parent for documents linked to credit notes
226
- if ($this->type == 'credit-note') {
227
- $order = $this->get_refund_parent( $order );
228
- }
229
- // order is not loaded to avoid overhead - we pass this by reference directly to the read_data method instead
230
- $this->linked_documents[ $document_type ] = wcpdf_get_document( $document_type, null );
231
- $this->linked_documents[ $document_type ]->read_data( $order );
232
- }
233
- return $this->linked_documents[ $document_type ]->get_data( $key, $document_type );
234
- }
235
-
236
- $value = null;
237
-
238
- if ( array_key_exists( $key, $this->data ) ) {
239
- $value = $this->data[ $key ];
240
-
241
- if ( 'view' === $context ) {
242
- $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
243
- }
244
- }
245
-
246
- return $value;
247
- }
248
-
249
- public function get_number( $document_type = '', $order = null, $context = 'view' ) {
250
- return $this->get_data( 'number', $document_type, $order, $context );
251
- }
252
-
253
- public function get_date( $document_type = '', $order = null, $context = 'view' ) {
254
- return $this->get_data( 'date', $document_type, $order, $context );
255
- }
256
-
257
- public function get_title() {
258
- return apply_filters( "wpo_wcpdf_{$this->slug}_title", $this->title, $this );
259
- }
260
-
261
- /*
262
- |--------------------------------------------------------------------------
263
- | Data setters
264
- |--------------------------------------------------------------------------
265
- |
266
- | Functions for setting order data. These should not update anything in the
267
- | order itself and should only change what is stored in the class
268
- | object.
269
- */
270
-
271
- public function set_data( $data, $order ) {
272
- $order = empty( $order ) ? $this->order : $order;
273
- foreach ($data as $key => $value) {
274
- $setter = "set_$key";
275
- if ( is_callable( array( $this, $setter ) ) ) {
276
- $this->$setter( $value, $order );
277
- } else {
278
- $this->data[ $key ] = $value;
279
- }
280
- }
281
- }
282
-
283
- public function set_date( $value, $order = null ) {
284
- $order = empty( $order ) ? $this->order : $order;
285
- try {
286
- if ( empty( $value ) ) {
287
- $this->data[ 'date' ] = null;
288
- return;
289
- }
290
-
291
- if ( is_a( $value, 'WC_DateTime' ) ) {
292
- $datetime = $value;
293
- } elseif ( is_numeric( $value ) ) {
294
- // Timestamps are handled as UTC timestamps in all cases.
295
- $datetime = new WC_DateTime( "@{$value}", new \DateTimeZone( 'UTC' ) );
296
- } else {
297
- // Strings are defined in local WP timezone. Convert to UTC.
298
- if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) {
299
- $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
300
- $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
301
- } else {
302
- $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
303
- }
304
- $datetime = new WC_DateTime( "@{$timestamp}", new \DateTimeZone( 'UTC' ) );
305
- }
306
-
307
- // Set local timezone or offset.
308
- if ( get_option( 'timezone_string' ) ) {
309
- $datetime->setTimezone( new \DateTimeZone( wc_timezone_string() ) );
310
- } else {
311
- $datetime->set_utc_offset( wc_timezone_offset() );
312
- }
313
-
314
- $this->data[ 'date' ] = $datetime;
315
- } catch ( Exception $e ) {}
316
-
317
-
318
- }
319
-
320
- public function set_number( $value, $order = null ) {
321
- $order = empty( $order ) ? $this->order : $order;
322
-
323
- if ( is_array( $value ) ) {
324
- $filtered_value = array_filter( $value );
325
- }
326
-
327
- if ( empty( $value ) || ( is_array( $value ) && empty( $filtered_value ) ) ) {
328
- $document_number = null;
329
- } elseif ( $value instanceof Document_Number ) {
330
- // WCPDF 2.0 number data
331
- $document_number = $value;
332
- } elseif ( is_array( $value ) ) {
333
- // WCPDF 2.0 number data as array
334
- $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
335
- } else {
336
- // plain number
337
- $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
338
- }
339
-
340
- $this->data[ 'number' ] = $document_number;
341
- }
342
-
343
- /*
344
- |--------------------------------------------------------------------------
345
- | Settings getters / outputters
346
- |--------------------------------------------------------------------------
347
- */
348
-
349
- public function get_number_settings() {
350
- $number_settings = isset($this->settings['number_format'])?$this->settings['number_format']:array();
351
- return apply_filters( 'wpo_wcpdf_document_number_settings', $number_settings, $this );
352
- }
353
-
354
- /**
355
- * Output template styles
356
- */
357
- public function template_styles() {
358
- $css = apply_filters( 'wpo_wcpdf_template_styles_file', $this->locate_template_file( "style.css" ) );
359
-
360
- ob_start();
361
- if (file_exists($css)) {
362
- include($css);
363
- }
364
- $css = ob_get_clean();
365
- $css = apply_filters( 'wpo_wcpdf_template_styles', $css, $this );
366
-
367
- echo $css;
368
- }
369
-
370
- public function has_header_logo() {
371
- return !empty( $this->settings['header_logo'] );
372
- }
373
-
374
- /**
375
- * Return logo id
376
- */
377
- public function get_header_logo_id() {
378
- if ( !empty( $this->settings['header_logo'] ) ) {
379
- return apply_filters( 'wpo_wcpdf_header_logo_id', $this->settings['header_logo'], $this );
380
- }
381
- }
382
-
383
- /**
384
- * Show logo html
385
- */
386
- public function header_logo() {
387
- if ($this->get_header_logo_id()) {
388
- $attachment_id = $this->get_header_logo_id();
389
- $company = $this->get_shop_name();
390
- if( $attachment_id ) {
391
- $attachment = wp_get_attachment_image_src( $attachment_id, 'full', false );
392
-
393
- $attachment_src = $attachment[0];
394
- $attachment_width = $attachment[1];
395
- $attachment_height = $attachment[2];
396
-
397
- $attachment_path = get_attached_file( $attachment_id );
398
-
399
- if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($attachment_path) ) {
400
- $src = $attachment_path;
401
- } else {
402
- $src = $attachment_src;
403
- }
404
-
405
- printf('<img src="%1$s" width="%2$d" height="%3$d" alt="%4$s" />', $src, $attachment_width, $attachment_height, esc_attr( $company ) );
406
- }
407
- }
408
- }
409
-
410
- public function get_settings_text( $settings_key, $default = false, $autop = true ) {
411
- if ( !empty( $this->settings[$settings_key]['default'] ) ) {
412
- $text = wptexturize( trim( $this->settings[$settings_key]['default'] ) );
413
- if ($autop === true) {
414
- $text = wpautop( $text );
415
- }
416
- } else {
417
- $text = $default;
418
- }
419
- return apply_filters( "wpo_wcpdf_{$settings_key}", $text, $this );
420
- }
421
-
422
- /**
423
- * Return/Show custom company name or default to blog name
424
- */
425
- public function get_shop_name() {
426
- $default = get_bloginfo( 'name' );
427
- return $this->get_settings_text( 'shop_name', $default, false );
428
- }
429
- public function shop_name() {
430
- echo $this->get_shop_name();
431
- }
432
-
433
- /**
434
- * Return/Show shop/company address if provided
435
- */
436
- public function get_shop_address() {
437
- return $this->get_settings_text( 'shop_address' );
438
- }
439
- public function shop_address() {
440
- echo $this->get_shop_address();
441
- }
442
-
443
- /**
444
- * Return/Show shop/company footer imprint, copyright etc.
445
- */
446
- public function get_footer() {
447
- return $this->get_settings_text( 'footer' );
448
- }
449
- public function footer() {
450
- echo $this->get_footer();
451
- }
452
-
453
- /**
454
- * Return/Show Extra field 1
455
- */
456
- public function get_extra_1() {
457
- return $this->get_settings_text( 'extra_1' );
458
-
459
- }
460
- public function extra_1() {
461
- echo $this->get_extra_1();
462
- }
463
-
464
- /**
465
- * Return/Show Extra field 2
466
- */
467
- public function get_extra_2() {
468
- return $this->get_settings_text( 'extra_2' );
469
- }
470
- public function extra_2() {
471
- echo $this->get_extra_2();
472
- }
473
-
474
- /**
475
- * Return/Show Extra field 3
476
- */
477
- public function get_extra_3() {
478
- return $this->get_settings_text( 'extra_3' );
479
- }
480
- public function extra_3() {
481
- echo $this->get_extra_3();
482
- }
483
-
484
- /*
485
- |--------------------------------------------------------------------------
486
- | Output functions
487
- |--------------------------------------------------------------------------
488
- */
489
-
490
- public function get_pdf() {
491
- do_action( 'wpo_wcpdf_before_pdf', $this->get_type(), $this );
492
-
493
- $pdf_settings = array(
494
- 'paper_size' => apply_filters( 'wpo_wcpdf_paper_format', $this->get_setting( 'paper_size', 'A4' ), $this->get_type() ),
495
- 'paper_orientation' => apply_filters( 'wpo_wcpdf_paper_orientation', 'portrait', $this->get_type() ),
496
- 'font_subsetting' => $this->get_setting( 'font_subsetting', false ),
497
- );
498
- $pdf_maker = wcpdf_get_pdf_maker( $this->get_html(), $pdf_settings );
499
- $pdf = $pdf_maker->output();
500
-
501
- do_action( 'wpo_wcpdf_after_pdf', $this->get_type(), $this );
502
- do_action( 'wpo_wcpdf_pdf_created', $pdf, $this );
503
-
504
- return $pdf;
505
- }
506
-
507
- public function get_html( $args = array() ) {
508
- do_action( 'wpo_wcpdf_before_html', $this->get_type(), $this );
509
- $default_args = array (
510
- 'wrap_html_content' => true,
511
- );
512
- $args = $args + $default_args;
513
-
514
- $html = $this->render_template( $this->locate_template_file( "{$this->type}.php" ) );
515
- if ($args['wrap_html_content']) {
516
- $html = $this->wrap_html_content( $html );
517
- }
518
-
519
- // clean up special characters
520
- if ( function_exists('utf8_decode') && function_exists('mb_convert_encoding') ) {
521
- $html = utf8_decode(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
522
- }
523
-
524
- do_action( 'wpo_wcpdf_after_html', $this->get_type(), $this );
525
-
526
- return apply_filters( 'wpo_wcpdf_get_html', $html, $this );
527
- }
528
-
529
- public function output_pdf( $output_mode = 'download' ) {
530
- $pdf = $this->get_pdf();
531
- wcpdf_pdf_headers( $this->get_filename(), $output_mode, $pdf );
532
- echo $pdf;
533
- die();
534
- }
535
-
536
- public function output_html() {
537
- echo $this->get_html();
538
- die();
539
- }
540
-
541
- public function wrap_html_content( $content ) {
542
- if ( WPO_WCPDF()->legacy_mode_enabled() ) {
543
- $GLOBALS['wpo_wcpdf']->export->output_body = $content;
544
- }
545
-
546
- $html = $this->render_template( $this->locate_template_file( "html-document-wrapper.php" ), array(
547
- 'content' => $content,
548
- )
549
- );
550
- return $html;
551
- }
552
-
553
- public function get_filename( $context = 'download', $args = array() ) {
554
- $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
555
-
556
- $name = $this->get_type();
557
- if ( get_post_type( $this->order_id ) == 'shop_order_refund' ) {
558
- $number = $this->order_id;
559
- } else {
560
- $number = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
561
- }
562
-
563
- if ( $order_count == 1 ) {
564
- $suffix = $number;
565
- } else {
566
- $suffix = date('Y-m-d'); // 2020-11-11
567
- }
568
-
569
- $filename = $name . '-' . $suffix . '.pdf';
570
-
571
- // Filter filename
572
- $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
573
- $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
574
-
575
- // sanitize filename (after filters to prevent human errors)!
576
- return sanitize_file_name( $filename );
577
- }
578
-
579
- public function get_template_path() {
580
- return WPO_WCPDF()->settings->get_template_path();
581
- }
582
-
583
- public function locate_template_file( $file ) {
584
- if (empty($file)) {
585
- $file = $this->type.'.php';
586
- }
587
- $path = WPO_WCPDF()->settings->get_template_path( $file );
588
- $file_path = "{$path}/{$file}";
589
-
590
- $fallback_file_path = WPO_WCPDF()->plugin_path() . '/templates/Simple/' . $file;
591
- if ( !file_exists( $file_path ) && file_exists( $fallback_file_path ) ) {
592
- $file_path = $fallback_file_path;
593
- }
594
-
595
- $file_path = apply_filters( 'wpo_wcpdf_template_file', $file_path, $this->type, $this->order );
596
-
597
- return $file_path;
598
- }
599
-
600
- public function render_template( $file, $args = array() ) {
601
- do_action( 'wpo_wcpdf_process_template', $this->get_type(), $this );
602
-
603
- if ( ! empty( $args ) && is_array( $args ) ) {
604
- extract( $args );
605
- }
606
- ob_start();
607
- if (file_exists($file)) {
608
- include($file);
609
- }
610
- return ob_get_clean();
611
- }
612
-
613
- /*
614
- |--------------------------------------------------------------------------
615
- | Settings helper functions
616
- |--------------------------------------------------------------------------
617
- */
618
-
619
- /**
620
- * get all emails registered in WooCommerce
621
- * @param boolean $remove_defaults switch to remove default woocommerce emails
622
- * @return array $emails list of all email ids/slugs and names
623
- */
624
- public function get_wc_emails() {
625
- // get emails from WooCommerce
626
- global $woocommerce;
627
- $mailer = $woocommerce->mailer();
628
- $wc_emails = $mailer->get_emails();
629
-
630
- $non_order_emails = array(
631
- 'customer_note',
632
- 'customer_reset_password',
633
- 'customer_new_account'
634
- );
635
-
636
- $emails = array();
637
- foreach ($wc_emails as $class => $email) {
638
- if ( !in_array( $email->id, $non_order_emails ) ) {
639
- switch ($email->id) {
640
- case 'new_order':
641
- $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Admin email', 'woocommerce-pdf-invoices-packing-slips' ) );
642
- break;
643
- case 'customer_invoice':
644
- $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Manual email', 'woocommerce-pdf-invoices-packing-slips' ) );
645
- break;
646
- default:
647
- $emails[$email->id] = $email->title;
648
- break;
649
- }
650
- }
651
- }
652
-
653
- return apply_filters( 'wpo_wcpdf_wc_emails', $emails );
654
- }
655
-
656
- // get list of WooCommerce statuses
657
- public function get_wc_order_status_list() {
658
- if ( version_compare( WOOCOMMERCE_VERSION, '2.2', '<' ) ) {
659
- $statuses = (array) get_terms( 'shop_order_status', array( 'hide_empty' => 0, 'orderby' => 'id' ) );
660
- foreach ( $statuses as $status ) {
661
- $order_statuses[esc_attr( $status->slug )] = esc_html__( $status->name, 'woocommerce' );
662
- }
663
- } else {
664
- $statuses = wc_get_order_statuses();
665
- foreach ( $statuses as $status_slug => $status ) {
666
- $status_slug = 'wc-' === substr( $status_slug, 0, 3 ) ? substr( $status_slug, 3 ) : $status_slug;
667
- $order_statuses[$status_slug] = $status;
668
- }
669
- }
670
- return $order_statuses;
671
- }
672
-
673
-
674
- }
675
-
676
- endif; // class_exists
 
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+ use WPO\WC\PDF_Invoices\Compatibility\WC_DateTime;
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit; // Exit if accessed directly
11
+ }
12
+
13
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Order_Document' ) ) :
14
+
15
+ /**
16
+ * Abstract Document
17
+ *
18
+ * Handles generic pdf document & order data and database interaction
19
+ * which is extended by both Invoices & Packing Slips
20
+ *
21
+ * @class \WPO\WC\PDF_Invoices\Documents\Order_Document
22
+ * @version 2.0
23
+ * @category Class
24
+ * @author Ewout Fernhout
25
+ */
26
+
27
+ abstract class Order_Document {
28
+ /**
29
+ * Document type.
30
+ * @var String
31
+ */
32
+ public $type;
33
+
34
+ /**
35
+ * Document slug.
36
+ * @var String
37
+ */
38
+ public $slug;
39
+
40
+ /**
41
+ * Document title.
42
+ * @var string
43
+ */
44
+ public $title;
45
+
46
+ /**
47
+ * Document icon.
48
+ * @var string
49
+ */
50
+ public $icon;
51
+
52
+ /**
53
+ * WC Order object
54
+ * @var object
55
+ */
56
+ public $order;
57
+
58
+ /**
59
+ * WC Order ID
60
+ * @var object
61
+ */
62
+ public $order_id;
63
+
64
+ /**
65
+ * Document settings.
66
+ * @var array
67
+ */
68
+ public $settings;
69
+
70
+ /**
71
+ * TRUE if document is enabled.
72
+ * @var bool
73
+ */
74
+ public $enabled;
75
+
76
+ /**
77
+ * Linked documents, used for data retrieval
78
+ * @var array
79
+ */
80
+ protected $linked_documents = array();
81
+
82
+ /**
83
+ * Core data for this object. Name value pairs (name + default value).
84
+ * @var array
85
+ */
86
+ protected $data = array();
87
+
88
+ /**
89
+ * Init/load the order object.
90
+ *
91
+ * @param int|object|WC_Order $order Order to init.
92
+ */
93
+ public function __construct( $order = 0 ) {
94
+ if ( is_numeric( $order ) && $order > 0 ) {
95
+ $this->order_id = $order;
96
+ $this->order = WCX::get_order( $order_id );
97
+ } elseif ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) {
98
+ $this->order_id = WCX_Order::get_id( $order );
99
+ $this->order = $order;
100
+ }
101
+
102
+ // set properties
103
+ $this->slug = str_replace('-', '_', $this->type);
104
+
105
+ // load settings
106
+ $this->settings = $this->get_settings();
107
+ $this->enabled = $this->get_setting( 'enabled', false );
108
+
109
+ // load data
110
+ if ( $this->order ) {
111
+ $this->read_data( $this->order );
112
+ if ( WPO_WCPDF()->legacy_mode_enabled() ) {
113
+ global $wpo_wcpdf;
114
+ $wpo_wcpdf->export->order = $this->order;
115
+ $wpo_wcpdf->export->document = $this;
116
+ $wpo_wcpdf->export->order_id = $this->order_id;
117
+ $wpo_wcpdf->export->template_type = $this->type;
118
+ }
119
+ }
120
+
121
+ }
122
+
123
+ public function init_settings() {
124
+ return ;
125
+ }
126
+
127
+ public function get_settings() {
128
+ $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
129
+ $document_settings = get_option( 'wpo_wcpdf_documents_settings_'.$this->get_type() );
130
+ return (array) $document_settings + (array) $common_settings;
131
+ }
132
+
133
+ public function get_setting( $key, $default = '' ) {
134
+ $setting = isset( $this->settings[$key] ) ? $this->settings[$key] : $default;
135
+ return $setting;
136
+ }
137
+
138
+ public function get_attach_to_email_ids() {
139
+ $email_ids = isset( $this->settings['attach_to_email_ids'] ) ? array_keys( $this->settings['attach_to_email_ids'] ) : array();
140
+ return $email_ids;
141
+ }
142
+
143
+ public function get_type() {
144
+ return $this->type;
145
+ }
146
+
147
+ public function is_enabled() {
148
+ return apply_filters( 'wpo_wcpdf_document_is_enabled', $this->enabled, $this->type );
149
+ }
150
+
151
+ public function get_hook_prefix() {
152
+ return 'wpo_wcpdf_' . $this->slug . '_get_';
153
+ }
154
+
155
+ public function read_data( $order ) {
156
+ $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number_data", true );
157
+ // fallback to legacy data for number
158
+ if ( empty( $number ) ) {
159
+ $number = WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_number", true );
160
+ $formatted_number = WCX_Order::get_meta( $order, "_wcpdf_formatted_{$this->slug}_number", true );
161
+ if (!empty($formatted_number)) {
162
+ $number = compact( 'number', 'formatted_number' );
163
+ }
164
+ }
165
+
166
+ // pass data to setter functions
167
+ $this->set_data( array(
168
+ // always load date before number, because date is used in number formatting
169
+ 'date' => WCX_Order::get_meta( $order, "_wcpdf_{$this->slug}_date", true ),
170
+ 'number' => $number,
171
+ ), $order );
172
+
173
+ return;
174
+ }
175
+
176
+ public function init() {
177
+ $this->set_date( current_time( 'timestamp', true ) );
178
+ do_action( 'wpo_wcpdf_init_document', $this );
179
+ }
180
+
181
+ public function save( $order = null ) {
182
+ $order = empty( $order ) ? $this->order : $order;
183
+ if ( empty( $order ) ) {
184
+ return; // nowhere to save to...
185
+ }
186
+
187
+ foreach ($this->data as $key => $value) {
188
+ if ( empty( $value ) ) {
189
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}" );
190
+ if ( $key == 'date' ) {
191
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted" );
192
+ } elseif ( $key == 'number' ) {
193
+ WCX_Order::delete_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data" );
194
+ }
195
+ } else {
196
+ if ( $key == 'date' ) {
197
+ // store dates as timestamp and formatted as mysql time
198
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->getTimestamp() );
199
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_formatted", $value->date( 'Y-m-d H:i:s' ) );
200
+ } elseif ( $key == 'number' ) {
201
+ // store both formatted number and number data
202
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}", $value->formatted_number );
203
+ WCX_Order::update_meta_data( $order, "_wcpdf_{$this->slug}_{$key}_data", $value->to_array() );
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ public function exists() {
210
+ return !empty( $this->data['number'] );
211
+ }
212
+
213
+ /*
214
+ |--------------------------------------------------------------------------
215
+ | Data getters
216
+ |--------------------------------------------------------------------------
217
+ */
218
+
219
+ public function get_data( $key, $document_type = '', $order = null, $context = 'view' ) {
220
+ $document_type = empty( $document_type ) ? $this->type : $document_type;
221
+ $order = empty( $order ) ? $this->order : $order;
222
+
223
+ // redirect get_data call for linked documents
224
+ if ( $document_type != $this->type ) {
225
+ if ( !isset( $this->linked_documents[ $document_type ] ) ) {
226
+ // always assume parent for documents linked to credit notes
227
+ if ($this->type == 'credit-note') {
228
+ $order = $this->get_refund_parent( $order );
229
+ }
230
+ // order is not loaded to avoid overhead - we pass this by reference directly to the read_data method instead
231
+ $this->linked_documents[ $document_type ] = wcpdf_get_document( $document_type, null );
232
+ $this->linked_documents[ $document_type ]->read_data( $order );
233
+ }
234
+ return $this->linked_documents[ $document_type ]->get_data( $key, $document_type );
235
+ }
236
+
237
+ $value = null;
238
+
239
+ if ( array_key_exists( $key, $this->data ) ) {
240
+ $value = $this->data[ $key ];
241
+
242
+ if ( 'view' === $context ) {
243
+ $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
244
+ }
245
+ }
246
+
247
+ return $value;
248
+ }
249
+
250
+ public function get_number( $document_type = '', $order = null, $context = 'view' ) {
251
+ return $this->get_data( 'number', $document_type, $order, $context );
252
+ }
253
+
254
+ public function get_date( $document_type = '', $order = null, $context = 'view' ) {
255
+ return $this->get_data( 'date', $document_type, $order, $context );
256
+ }
257
+
258
+ public function get_title() {
259
+ return apply_filters( "wpo_wcpdf_{$this->slug}_title", $this->title, $this );
260
+ }
261
+
262
+ /*
263
+ |--------------------------------------------------------------------------
264
+ | Data setters
265
+ |--------------------------------------------------------------------------
266
+ |
267
+ | Functions for setting order data. These should not update anything in the
268
+ | order itself and should only change what is stored in the class
269
+ | object.
270
+ */
271
+
272
+ public function set_data( $data, $order ) {
273
+ $order = empty( $order ) ? $this->order : $order;
274
+ foreach ($data as $key => $value) {
275
+ $setter = "set_$key";
276
+ if ( is_callable( array( $this, $setter ) ) ) {
277
+ $this->$setter( $value, $order );
278
+ } else {
279
+ $this->data[ $key ] = $value;
280
+ }
281
+ }
282
+ }
283
+
284
+ public function set_date( $value, $order = null ) {
285
+ $order = empty( $order ) ? $this->order : $order;
286
+ try {
287
+ if ( empty( $value ) ) {
288
+ $this->data[ 'date' ] = null;
289
+ return;
290
+ }
291
+
292
+ if ( is_a( $value, 'WC_DateTime' ) ) {
293
+ $datetime = $value;
294
+ } elseif ( is_numeric( $value ) ) {
295
+ // Timestamps are handled as UTC timestamps in all cases.
296
+ $datetime = new WC_DateTime( "@{$value}", new \DateTimeZone( 'UTC' ) );
297
+ } else {
298
+ // Strings are defined in local WP timezone. Convert to UTC.
299
+ if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) {
300
+ $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
301
+ $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
302
+ } else {
303
+ $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
304
+ }
305
+ $datetime = new WC_DateTime( "@{$timestamp}", new \DateTimeZone( 'UTC' ) );
306
+ }
307
+
308
+ // Set local timezone or offset.
309
+ if ( get_option( 'timezone_string' ) ) {
310
+ $datetime->setTimezone( new \DateTimeZone( wc_timezone_string() ) );
311
+ } else {
312
+ $datetime->set_utc_offset( wc_timezone_offset() );
313
+ }
314
+
315
+ $this->data[ 'date' ] = $datetime;
316
+ } catch ( Exception $e ) {}
317
+
318
+
319
+ }
320
+
321
+ public function set_number( $value, $order = null ) {
322
+ $order = empty( $order ) ? $this->order : $order;
323
+
324
+ if ( is_array( $value ) ) {
325
+ $filtered_value = array_filter( $value );
326
+ }
327
+
328
+ if ( empty( $value ) || ( is_array( $value ) && empty( $filtered_value ) ) ) {
329
+ $document_number = null;
330
+ } elseif ( $value instanceof Document_Number ) {
331
+ // WCPDF 2.0 number data
332
+ $document_number = $value;
333
+ } elseif ( is_array( $value ) ) {
334
+ // WCPDF 2.0 number data as array
335
+ $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
336
+ } else {
337
+ // plain number
338
+ $document_number = new Document_Number( $value, $this->get_number_settings(), $this, $order );
339
+ }
340
+
341
+ $this->data[ 'number' ] = $document_number;
342
+ }
343
+
344
+ /*
345
+ |--------------------------------------------------------------------------
346
+ | Settings getters / outputters
347
+ |--------------------------------------------------------------------------
348
+ */
349
+
350
+ public function get_number_settings() {
351
+ $number_settings = isset($this->settings['number_format'])?$this->settings['number_format']:array();
352
+ return apply_filters( 'wpo_wcpdf_document_number_settings', $number_settings, $this );
353
+ }
354
+
355
+ /**
356
+ * Output template styles
357
+ */
358
+ public function template_styles() {
359
+ $css = apply_filters( 'wpo_wcpdf_template_styles_file', $this->locate_template_file( "style.css" ) );
360
+
361
+ ob_start();
362
+ if (file_exists($css)) {
363
+ include($css);
364
+ }
365
+ $css = ob_get_clean();
366
+ $css = apply_filters( 'wpo_wcpdf_template_styles', $css, $this );
367
+
368
+ echo $css;
369
+ }
370
+
371
+ public function has_header_logo() {
372
+ return !empty( $this->settings['header_logo'] );
373
+ }
374
+
375
+ /**
376
+ * Return logo id
377
+ */
378
+ public function get_header_logo_id() {
379
+ if ( !empty( $this->settings['header_logo'] ) ) {
380
+ return apply_filters( 'wpo_wcpdf_header_logo_id', $this->settings['header_logo'], $this );
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Show logo html
386
+ */
387
+ public function header_logo() {
388
+ if ($this->get_header_logo_id()) {
389
+ $attachment_id = $this->get_header_logo_id();
390
+ $company = $this->get_shop_name();
391
+ if( $attachment_id ) {
392
+ $attachment = wp_get_attachment_image_src( $attachment_id, 'full', false );
393
+
394
+ $attachment_src = $attachment[0];
395
+ $attachment_width = $attachment[1];
396
+ $attachment_height = $attachment[2];
397
+
398
+ $attachment_path = get_attached_file( $attachment_id );
399
+
400
+ if ( apply_filters('wpo_wcpdf_use_path', true) && file_exists($attachment_path) ) {
401
+ $src = $attachment_path;
402
+ } else {
403
+ $src = $attachment_src;
404
+ }
405
+
406
+ printf('<img src="%1$s" width="%2$d" height="%3$d" alt="%4$s" />', $src, $attachment_width, $attachment_height, esc_attr( $company ) );
407
+ }
408
+ }
409
+ }
410
+
411
+ public function get_settings_text( $settings_key, $default = false, $autop = true ) {
412
+ if ( !empty( $this->settings[$settings_key]['default'] ) ) {
413
+ $text = wptexturize( trim( $this->settings[$settings_key]['default'] ) );
414
+ if ($autop === true) {
415
+ $text = wpautop( $text );
416
+ }
417
+ } else {
418
+ $text = $default;
419
+ }
420
+ return apply_filters( "wpo_wcpdf_{$settings_key}", $text, $this );
421
+ }
422
+
423
+ /**
424
+ * Return/Show custom company name or default to blog name
425
+ */
426
+ public function get_shop_name() {
427
+ $default = get_bloginfo( 'name' );
428
+ return $this->get_settings_text( 'shop_name', $default, false );
429
+ }
430
+ public function shop_name() {
431
+ echo $this->get_shop_name();
432
+ }
433
+
434
+ /**
435
+ * Return/Show shop/company address if provided
436
+ */
437
+ public function get_shop_address() {
438
+ return $this->get_settings_text( 'shop_address' );
439
+ }
440
+ public function shop_address() {
441
+ echo $this->get_shop_address();
442
+ }
443
+
444
+ /**
445
+ * Return/Show shop/company footer imprint, copyright etc.
446
+ */
447
+ public function get_footer() {
448
+ return $this->get_settings_text( 'footer' );
449
+ }
450
+ public function footer() {
451
+ echo $this->get_footer();
452
+ }
453
+
454
+ /**
455
+ * Return/Show Extra field 1
456
+ */
457
+ public function get_extra_1() {
458
+ return $this->get_settings_text( 'extra_1' );
459
+
460
+ }
461
+ public function extra_1() {
462
+ echo $this->get_extra_1();
463
+ }
464
+
465
+ /**
466
+ * Return/Show Extra field 2
467
+ */
468
+ public function get_extra_2() {
469
+ return $this->get_settings_text( 'extra_2' );
470
+ }
471
+ public function extra_2() {
472
+ echo $this->get_extra_2();
473
+ }
474
+
475
+ /**
476
+ * Return/Show Extra field 3
477
+ */
478
+ public function get_extra_3() {
479
+ return $this->get_settings_text( 'extra_3' );
480
+ }
481
+ public function extra_3() {
482
+ echo $this->get_extra_3();
483
+ }
484
+
485
+ /*
486
+ |--------------------------------------------------------------------------
487
+ | Output functions
488
+ |--------------------------------------------------------------------------
489
+ */
490
+
491
+ public function get_pdf() {
492
+ do_action( 'wpo_wcpdf_before_pdf', $this->get_type(), $this );
493
+
494
+ $pdf_settings = array(
495
+ 'paper_size' => apply_filters( 'wpo_wcpdf_paper_format', $this->get_setting( 'paper_size', 'A4' ), $this->get_type() ),
496
+ 'paper_orientation' => apply_filters( 'wpo_wcpdf_paper_orientation', 'portrait', $this->get_type() ),
497
+ 'font_subsetting' => $this->get_setting( 'font_subsetting', false ),
498
+ );
499
+ $pdf_maker = wcpdf_get_pdf_maker( $this->get_html(), $pdf_settings );
500
+ $pdf = $pdf_maker->output();
501
+
502
+ do_action( 'wpo_wcpdf_after_pdf', $this->get_type(), $this );
503
+ do_action( 'wpo_wcpdf_pdf_created', $pdf, $this );
504
+
505
+ return $pdf;
506
+ }
507
+
508
+ public function get_html( $args = array() ) {
509
+ do_action( 'wpo_wcpdf_before_html', $this->get_type(), $this );
510
+ $default_args = array (
511
+ 'wrap_html_content' => true,
512
+ );
513
+ $args = $args + $default_args;
514
+
515
+ $html = $this->render_template( $this->locate_template_file( "{$this->type}.php" ) );
516
+ if ($args['wrap_html_content']) {
517
+ $html = $this->wrap_html_content( $html );
518
+ }
519
+
520
+ // clean up special characters
521
+ if ( function_exists('utf8_decode') && function_exists('mb_convert_encoding') ) {
522
+ $html = utf8_decode(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
523
+ }
524
+
525
+ do_action( 'wpo_wcpdf_after_html', $this->get_type(), $this );
526
+
527
+ return apply_filters( 'wpo_wcpdf_get_html', $html, $this );
528
+ }
529
+
530
+ public function output_pdf( $output_mode = 'download' ) {
531
+ $pdf = $this->get_pdf();
532
+ wcpdf_pdf_headers( $this->get_filename(), $output_mode, $pdf );
533
+ echo $pdf;
534
+ die();
535
+ }
536
+
537
+ public function output_html() {
538
+ echo $this->get_html();
539
+ die();
540
+ }
541
+
542
+ public function wrap_html_content( $content ) {
543
+ if ( WPO_WCPDF()->legacy_mode_enabled() ) {
544
+ $GLOBALS['wpo_wcpdf']->export->output_body = $content;
545
+ }
546
+
547
+ $html = $this->render_template( $this->locate_template_file( "html-document-wrapper.php" ), array(
548
+ 'content' => $content,
549
+ )
550
+ );
551
+ return $html;
552
+ }
553
+
554
+ public function get_filename( $context = 'download', $args = array() ) {
555
+ $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
556
+
557
+ $name = $this->get_type();
558
+ if ( get_post_type( $this->order_id ) == 'shop_order_refund' ) {
559
+ $number = $this->order_id;
560
+ } else {
561
+ $number = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
562
+ }
563
+
564
+ if ( $order_count == 1 ) {
565
+ $suffix = $number;
566
+ } else {
567
+ $suffix = date('Y-m-d'); // 2020-11-11
568
+ }
569
+
570
+ $filename = $name . '-' . $suffix . '.pdf';
571
+
572
+ // Filter filename
573
+ $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
574
+ $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
575
+
576
+ // sanitize filename (after filters to prevent human errors)!
577
+ return sanitize_file_name( $filename );
578
+ }
579
+
580
+ public function get_template_path() {
581
+ return WPO_WCPDF()->settings->get_template_path();
582
+ }
583
+
584
+ public function locate_template_file( $file ) {
585
+ if (empty($file)) {
586
+ $file = $this->type.'.php';
587
+ }
588
+ $path = WPO_WCPDF()->settings->get_template_path( $file );
589
+ $file_path = "{$path}/{$file}";
590
+
591
+ $fallback_file_path = WPO_WCPDF()->plugin_path() . '/templates/Simple/' . $file;
592
+ if ( !file_exists( $file_path ) && file_exists( $fallback_file_path ) ) {
593
+ $file_path = $fallback_file_path;
594
+ }
595
+
596
+ $file_path = apply_filters( 'wpo_wcpdf_template_file', $file_path, $this->type, $this->order );
597
+
598
+ return $file_path;
599
+ }
600
+
601
+ public function render_template( $file, $args = array() ) {
602
+ do_action( 'wpo_wcpdf_process_template', $this->get_type(), $this );
603
+
604
+ if ( ! empty( $args ) && is_array( $args ) ) {
605
+ extract( $args );
606
+ }
607
+ ob_start();
608
+ if (file_exists($file)) {
609
+ include($file);
610
+ }
611
+ return ob_get_clean();
612
+ }
613
+
614
+ /*
615
+ |--------------------------------------------------------------------------
616
+ | Settings helper functions
617
+ |--------------------------------------------------------------------------
618
+ */
619
+
620
+ /**
621
+ * get all emails registered in WooCommerce
622
+ * @param boolean $remove_defaults switch to remove default woocommerce emails
623
+ * @return array $emails list of all email ids/slugs and names
624
+ */
625
+ public function get_wc_emails() {
626
+ // get emails from WooCommerce
627
+ global $woocommerce;
628
+ $mailer = $woocommerce->mailer();
629
+ $wc_emails = $mailer->get_emails();
630
+
631
+ $non_order_emails = array(
632
+ 'customer_note',
633
+ 'customer_reset_password',
634
+ 'customer_new_account'
635
+ );
636
+
637
+ $emails = array();
638
+ foreach ($wc_emails as $class => $email) {
639
+ if ( !in_array( $email->id, $non_order_emails ) ) {
640
+ switch ($email->id) {
641
+ case 'new_order':
642
+ $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Admin email', 'woocommerce-pdf-invoices-packing-slips' ) );
643
+ break;
644
+ case 'customer_invoice':
645
+ $emails[$email->id] = sprintf('%s (%s)', $email->title, __( 'Manual email', 'woocommerce-pdf-invoices-packing-slips' ) );
646
+ break;
647
+ default:
648
+ $emails[$email->id] = $email->title;
649
+ break;
650
+ }
651
+ }
652
+ }
653
+
654
+ return apply_filters( 'wpo_wcpdf_wc_emails', $emails );
655
+ }
656
+
657
+ // get list of WooCommerce statuses
658
+ public function get_wc_order_status_list() {
659
+ if ( version_compare( WOOCOMMERCE_VERSION, '2.2', '<' ) ) {
660
+ $statuses = (array) get_terms( 'shop_order_status', array( 'hide_empty' => 0, 'orderby' => 'id' ) );
661
+ foreach ( $statuses as $status ) {
662
+ $order_statuses[esc_attr( $status->slug )] = esc_html__( $status->name, 'woocommerce' );
663
+ }
664
+ } else {
665
+ $statuses = wc_get_order_statuses();
666
+ foreach ( $statuses as $status_slug => $status ) {
667
+ $status_slug = 'wc-' === substr( $status_slug, 0, 3 ) ? substr( $status_slug, 3 ) : $status_slug;
668
+ $order_statuses[$status_slug] = $status;
669
+ }
670
+ }
671
+ return $order_statuses;
672
+ }
673
+
674
+
675
+ }
676
+
677
+ endif; // class_exists
includes/documents/class-wcpdf-invoice.php CHANGED
@@ -1,353 +1,353 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Invoice' ) ) :
13
-
14
- /**
15
- * Invoice Document
16
- *
17
- * @class \WPO\WC\PDF_Invoices\Documents\Invoice
18
- * @version 2.0
19
- * @category Class
20
- * @author Ewout Fernhout
21
- */
22
-
23
- class Invoice extends Order_Document_Methods {
24
- /**
25
- * Init/load the order object.
26
- *
27
- * @param int|object|WC_Order $order Order to init.
28
- */
29
- public function __construct( $order = 0 ) {
30
- // set properties
31
- $this->type = 'invoice';
32
- $this->title = __( 'Invoice', 'woocommerce-pdf-invoices-packing-slips' );
33
- $this->icon = WPO_WCPDF()->plugin_url() . "/assets/images/invoice.png";
34
-
35
- // Call parent constructor
36
- parent::__construct( $order );
37
- }
38
-
39
- public function get_title() {
40
- // override/not using $this->title to allow for language switching!
41
- return apply_filters( "wpo_wcpdf_{$this->slug}_title", __( 'Invoice', 'woocommerce-pdf-invoices-packing-slips' ), $this );
42
- }
43
-
44
- public function init() {
45
- $this->set_date( current_time( 'timestamp', true ) );
46
- $this->init_number();
47
- }
48
-
49
- public function init_number() {
50
- global $wpdb;
51
- // If a third-party plugin claims to generate invoice numbers, trigger this instead
52
- if ( apply_filters( 'woocommerce_invoice_number_by_plugin', false ) || apply_filters( 'wpo_wcpdf_external_invoice_number_enabled', false, $this ) ) {
53
- $invoice_number = apply_filters( 'woocommerce_generate_invoice_number', null, $this->order );
54
- $invoice_number = apply_filters( 'wpo_wcpdf_external_invoice_number', $invoice_number, $this );
55
- if ( is_numeric($invoice_number) || $invoice_number instanceof Document_Number ) {
56
- $this->set_number( $invoice_number );
57
- } else {
58
- // invoice number is not numeric, treat as formatted
59
- // try to extract meaningful number data
60
- $formatted_number = $invoice_number;
61
- $number = (int) preg_replace('/\D/', '', $invoice_number);
62
- $invoice_number = compact( 'number', 'formatted_number' );
63
- $this->set_number( $invoice_number );
64
- }
65
- return $invoice_number;
66
- }
67
-
68
- $number_store_method = WPO_WCPDF()->settings->get_sequential_number_store_method();
69
- $number_store = new Sequential_Number_Store( 'invoice_number', $number_store_method );
70
- // reset invoice number yearly
71
- if ( isset( $this->settings['reset_number_yearly'] ) ) {
72
- $current_year = date("Y");
73
- $last_number_year = $number_store->get_last_date('Y');
74
- // check if we need to reset
75
- if ( $current_year != $last_number_year ) {
76
- $number_store->set_next( 1 );
77
- }
78
- }
79
-
80
- $invoice_date = $this->get_date();
81
- $invoice_number = $number_store->increment( $this->order_id, $invoice_date->date_i18n( 'Y-m-d H:i:s' ) );
82
-
83
- $this->set_number( $invoice_number );
84
-
85
- return $invoice_number;
86
- }
87
-
88
- public function get_settings() {
89
- $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
90
- $document_settings = get_option( 'wpo_wcpdf_documents_settings_invoice' );
91
- return (array) $document_settings + (array) $common_settings;
92
- }
93
-
94
- public function get_filename( $context = 'download', $args = array() ) {
95
- $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
96
-
97
- $name = _n( 'invoice', 'invoices', $order_count, 'woocommerce-pdf-invoices-packing-slips' );
98
-
99
- if ( $order_count == 1 ) {
100
- if ( isset( $this->settings['display_number'] ) ) {
101
- $suffix = (string) $this->get_number();
102
- } else {
103
- if ( empty( $this->order ) ) {
104
- $order = WCX::get_order ( $order_ids[0] );
105
- $suffix = method_exists( $order, 'get_order_number' ) ? $order->get_order_number() : '';
106
- } else {
107
- $suffix = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
108
- }
109
- }
110
- } else {
111
- $suffix = date('Y-m-d'); // 2020-11-11
112
- }
113
-
114
- $filename = $name . '-' . $suffix . '.pdf';
115
-
116
- // Filter filename
117
- $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
118
- $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
119
-
120
- // sanitize filename (after filters to prevent human errors)!
121
- return sanitize_file_name( $filename );
122
- }
123
-
124
-
125
- /**
126
- * Initialise settings
127
- */
128
- public function init_settings() {
129
- // Register settings.
130
- $page = $option_group = $option_name = 'wpo_wcpdf_documents_settings_invoice';
131
-
132
- $settings_fields = array(
133
- array(
134
- 'type' => 'section',
135
- 'id' => 'invoice',
136
- 'title' => '',
137
- 'callback' => 'section',
138
- ),
139
- array(
140
- 'type' => 'setting',
141
- 'id' => 'enabled',
142
- 'title' => __( 'Enable', 'woocommerce-pdf-invoices-packing-slips' ),
143
- 'callback' => 'checkbox',
144
- 'section' => 'invoice',
145
- 'args' => array(
146
- 'option_name' => $option_name,
147
- 'id' => 'enabled',
148
- )
149
- ),
150
- array(
151
- 'type' => 'setting',
152
- 'id' => 'attach_to_email_ids',
153
- 'title' => __( 'Attach to:', 'woocommerce-pdf-invoices-packing-slips' ),
154
- 'callback' => 'multiple_checkboxes',
155
- 'section' => 'invoice',
156
- 'args' => array(
157
- 'option_name' => $option_name,
158
- 'id' => 'attach_to_email_ids',
159
- 'fields' => $this->get_wc_emails(),
160
- 'description' => !is_writable( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ) ? '<span class="wpo-warning">' . sprintf( __( 'It looks like the temp folder (<code>%s</code>) is not writable, check the permissions for this folder! Without having write access to this folder, the plugin will not be able to email invoices.', 'woocommerce-pdf-invoices-packing-slips' ), WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ).'</span>':'',
161
- )
162
- ),
163
- array(
164
- 'type' => 'setting',
165
- 'id' => 'display_shipping_address',
166
- 'title' => __( 'Display shipping address', 'woocommerce-pdf-invoices-packing-slips' ),
167
- 'callback' => 'checkbox',
168
- 'section' => 'invoice',
169
- 'args' => array(
170
- 'option_name' => $option_name,
171
- 'id' => 'display_shipping_address',
172
- 'description' => __( 'Display shipping address (in addition to the default billing address) if different from billing address', 'woocommerce-pdf-invoices-packing-slips' ),
173
- )
174
- ),
175
- array(
176
- 'type' => 'setting',
177
- 'id' => 'display_email',
178
- 'title' => __( 'Display email address', 'woocommerce-pdf-invoices-packing-slips' ),
179
- 'callback' => 'checkbox',
180
- 'section' => 'invoice',
181
- 'args' => array(
182
- 'option_name' => $option_name,
183
- 'id' => 'display_email',
184
- )
185
- ),
186
- array(
187
- 'type' => 'setting',
188
- 'id' => 'display_phone',
189
- 'title' => __( 'Display phone number', 'woocommerce-pdf-invoices-packing-slips' ),
190
- 'callback' => 'checkbox',
191
- 'section' => 'invoice',
192
- 'args' => array(
193
- 'option_name' => $option_name,
194
- 'id' => 'display_phone',
195
- )
196
- ),
197
- array(
198
- 'type' => 'setting',
199
- 'id' => 'display_date',
200
- 'title' => __( 'Display invoice date', 'woocommerce-pdf-invoices-packing-slips' ),
201
- 'callback' => 'checkbox',
202
- 'section' => 'invoice',
203
- 'args' => array(
204
- 'option_name' => $option_name,
205
- 'id' => 'display_date',
206
- 'value' => 'invoice_date',
207
- )
208
- ),
209
- array(
210
- 'type' => 'setting',
211
- 'id' => 'display_number',
212
- 'title' => __( 'Display invoice number', 'woocommerce-pdf-invoices-packing-slips' ),
213
- 'callback' => 'checkbox',
214
- 'section' => 'invoice',
215
- 'args' => array(
216
- 'option_name' => $option_name,
217
- 'id' => 'display_number',
218
- 'value' => 'invoice_number',
219
- )
220
- ),
221
- array(
222
- 'type' => 'setting',
223
- 'id' => 'next_invoice_number',
224
- 'title' => __( 'Next invoice number (without prefix/suffix etc.)', 'woocommerce-pdf-invoices-packing-slips' ),
225
- 'callback' => 'next_number_edit',
226
- 'section' => 'invoice',
227
- 'args' => array(
228
- 'store' => 'invoice_number',
229
- 'size' => '10',
230
- 'description' => __( 'This is the number that will be used for the next document. By default, numbering starts from 1 and increases for every new document. Note that if you override this and set it lower than the current/highest number, this could create duplicate numbers!', 'woocommerce-pdf-invoices-packing-slips' ),
231
- )
232
- ),
233
- array(
234
- 'type' => 'setting',
235
- 'id' => 'number_format',
236
- 'title' => __( 'Number format', 'woocommerce-pdf-invoices-packing-slips' ),
237
- 'callback' => 'multiple_text_input',
238
- 'section' => 'invoice',
239
- 'args' => array(
240
- 'option_name' => $option_name,
241
- 'id' => 'number_format',
242
- 'fields' => array(
243
- 'prefix' => array(
244
- 'placeholder' => __( 'Prefix' , 'woocommerce-pdf-invoices-packing-slips' ),
245
- 'size' => 20,
246
- 'description' => __( 'to use the invoice year and/or month, use [invoice_year] or [invoice_month] respectively' , 'woocommerce-pdf-invoices-packing-slips' ),
247
- ),
248
- 'suffix' => array(
249
- 'placeholder' => __( 'Suffix' , 'woocommerce-pdf-invoices-packing-slips' ),
250
- 'size' => 20,
251
- 'description' => '',
252
- ),
253
- 'padding' => array(
254
- 'placeholder' => __( 'Padding' , 'woocommerce-pdf-invoices-packing-slips' ),
255
- 'size' => 20,
256
- 'type' => 'number',
257
- 'description' => __( 'enter the number of digits here - enter "6" to display 42 as 000042' , 'woocommerce-pdf-invoices-packing-slips' ),
258
- ),
259
- ),
260
- 'description' => __( 'note: if you have already created a custom invoice number format with a filter, the above settings will be ignored' , 'woocommerce-pdf-invoices-packing-slips' ),
261
- )
262
- ),
263
- array(
264
- 'type' => 'setting',
265
- 'id' => 'reset_number_yearly',
266
- 'title' => __( 'Reset invoice number yearly', 'woocommerce-pdf-invoices-packing-slips' ),
267
- 'callback' => 'checkbox',
268
- 'section' => 'invoice',
269
- 'args' => array(
270
- 'option_name' => $option_name,
271
- 'id' => 'reset_number_yearly',
272
- )
273
- ),
274
- array(
275
- 'type' => 'setting',
276
- 'id' => 'my_account_buttons',
277
- 'title' => __( 'Allow My Account invoice download', 'woocommerce-pdf-invoices-packing-slips' ),
278
- 'callback' => 'select',
279
- 'section' => 'invoice',
280
- 'args' => array(
281
- 'option_name' => $option_name,
282
- 'id' => 'my_account_buttons',
283
- 'options' => array(
284
- 'available' => __( 'Only when an invoice is already created/emailed' , 'woocommerce-pdf-invoices-packing-slips' ),
285
- 'custom' => __( 'Only for specific order statuses (define below)' , 'woocommerce-pdf-invoices-packing-slips' ),
286
- 'always' => __( 'Always' , 'woocommerce-pdf-invoices-packing-slips' ),
287
- 'never' => __( 'Never' , 'woocommerce-pdf-invoices-packing-slips' ),
288
- ),
289
- 'custom' => array(
290
- 'type' => 'multiple_checkboxes',
291
- 'args' => array(
292
- 'option_name' => $option_name,
293
- 'id' => 'my_account_restrict',
294
- 'fields' => $this->get_wc_order_status_list(),
295
- ),
296
- ),
297
- )
298
- ),
299
- array(
300
- 'type' => 'setting',
301
- 'id' => 'invoice_number_column',
302
- 'title' => __( 'Enable invoice number column in the orders list', 'woocommerce-pdf-invoices-packing-slips' ),
303
- 'callback' => 'checkbox',
304
- 'section' => 'invoice',
305
- 'args' => array(
306
- 'option_name' => $option_name,
307
- 'id' => 'invoice_number_column',
308
- )
309
- ),
310
- array(
311
- 'type' => 'setting',
312
- 'id' => 'disable_free',
313
- 'title' => __( 'Disable for free products', 'woocommerce-pdf-invoices-packing-slips' ),
314
- 'callback' => 'checkbox',
315
- 'section' => 'invoice',
316
- 'args' => array(
317
- 'option_name' => $option_name,
318
- 'id' => 'disable_free',
319
- 'description' => __( "Disable automatic creation/attachment when only free products are ordered", 'woocommerce-pdf-invoices-packing-slips' ),
320
- )
321
- ),
322
- );
323
-
324
-
325
- // remove/rename some fields when invoice number is controlled externally
326
- if( apply_filters('woocommerce_invoice_number_by_plugin', false) ) {
327
- $remove_settings = array( 'next_invoice_number', 'number_format', 'reset_number_yearly' );
328
- foreach ($settings_fields as $key => $settings_field) {
329
- if (in_array($settings_field['id'], $remove_settings)) {
330
- unset($settings_fields[$key]);
331
- } elseif ( $settings_field['id'] == 'display_number' ) {
332
- // alternate description for invoice number
333
- $invoice_number_desc = __( 'Invoice numbers are created by a third-party extension.', 'woocommerce-pdf-invoices-packing-slips' );
334
- if ( esc_attr( apply_filters( 'woocommerce_invoice_number_configuration_link', null ) ) ) {
335
- $invoice_number_desc .= ' '.sprintf(__( 'Configure it <a href="%s">here</a>.', 'woocommerce-pdf-invoices-packing-slips' ), $config_link);
336
- }
337
- $settings_fields[$key]['args']['description'] = '<i>'.$invoice_number_desc.'</i>';
338
- }
339
- }
340
- }
341
-
342
- // allow plugins to alter settings fields
343
- $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_documents_invoice', $settings_fields, $page, $option_group, $option_name );
344
- WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
345
- return;
346
-
347
- }
348
-
349
- }
350
-
351
- endif; // class_exists
352
-
353
  return new Invoice();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Invoice' ) ) :
13
+
14
+ /**
15
+ * Invoice Document
16
+ *
17
+ * @class \WPO\WC\PDF_Invoices\Documents\Invoice
18
+ * @version 2.0
19
+ * @category Class
20
+ * @author Ewout Fernhout
21
+ */
22
+
23
+ class Invoice extends Order_Document_Methods {
24
+ /**
25
+ * Init/load the order object.
26
+ *
27
+ * @param int|object|WC_Order $order Order to init.
28
+ */
29
+ public function __construct( $order = 0 ) {
30
+ // set properties
31
+ $this->type = 'invoice';
32
+ $this->title = __( 'Invoice', 'woocommerce-pdf-invoices-packing-slips' );
33
+ $this->icon = WPO_WCPDF()->plugin_url() . "/assets/images/invoice.png";
34
+
35
+ // Call parent constructor
36
+ parent::__construct( $order );
37
+ }
38
+
39
+ public function get_title() {
40
+ // override/not using $this->title to allow for language switching!
41
+ return apply_filters( "wpo_wcpdf_{$this->slug}_title", __( 'Invoice', 'woocommerce-pdf-invoices-packing-slips' ), $this );
42
+ }
43
+
44
+ public function init() {
45
+ $this->set_date( current_time( 'timestamp', true ) );
46
+ $this->init_number();
47
+ }
48
+
49
+ public function init_number() {
50
+ global $wpdb;
51
+ // If a third-party plugin claims to generate invoice numbers, trigger this instead
52
+ if ( apply_filters( 'woocommerce_invoice_number_by_plugin', false ) || apply_filters( 'wpo_wcpdf_external_invoice_number_enabled', false, $this ) ) {
53
+ $invoice_number = apply_filters( 'woocommerce_generate_invoice_number', null, $this->order );
54
+ $invoice_number = apply_filters( 'wpo_wcpdf_external_invoice_number', $invoice_number, $this );
55
+ if ( is_numeric($invoice_number) || $invoice_number instanceof Document_Number ) {
56
+ $this->set_number( $invoice_number );
57
+ } else {
58
+ // invoice number is not numeric, treat as formatted
59
+ // try to extract meaningful number data
60
+ $formatted_number = $invoice_number;
61
+ $number = (int) preg_replace('/\D/', '', $invoice_number);
62
+ $invoice_number = compact( 'number', 'formatted_number' );
63
+ $this->set_number( $invoice_number );
64
+ }
65
+ return $invoice_number;
66
+ }
67
+
68
+ $number_store_method = WPO_WCPDF()->settings->get_sequential_number_store_method();
69
+ $number_store = new Sequential_Number_Store( 'invoice_number', $number_store_method );
70
+ // reset invoice number yearly
71
+ if ( isset( $this->settings['reset_number_yearly'] ) ) {
72
+ $current_year = date("Y");
73
+ $last_number_year = $number_store->get_last_date('Y');
74
+ // check if we need to reset
75
+ if ( $current_year != $last_number_year ) {
76
+ $number_store->set_next( 1 );
77
+ }
78
+ }
79
+
80
+ $invoice_date = $this->get_date();
81
+ $invoice_number = $number_store->increment( $this->order_id, $invoice_date->date_i18n( 'Y-m-d H:i:s' ) );
82
+
83
+ $this->set_number( $invoice_number );
84
+
85
+ return $invoice_number;
86
+ }
87
+
88
+ public function get_settings() {
89
+ $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
90
+ $document_settings = get_option( 'wpo_wcpdf_documents_settings_invoice' );
91
+ return (array) $document_settings + (array) $common_settings;
92
+ }
93
+
94
+ public function get_filename( $context = 'download', $args = array() ) {
95
+ $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
96
+
97
+ $name = _n( 'invoice', 'invoices', $order_count, 'woocommerce-pdf-invoices-packing-slips' );
98
+
99
+ if ( $order_count == 1 ) {
100
+ if ( isset( $this->settings['display_number'] ) ) {
101
+ $suffix = (string) $this->get_number();
102
+ } else {
103
+ if ( empty( $this->order ) ) {
104
+ $order = WCX::get_order ( $order_ids[0] );
105
+ $suffix = method_exists( $order, 'get_order_number' ) ? $order->get_order_number() : '';
106
+ } else {
107
+ $suffix = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
108
+ }
109
+ }
110
+ } else {
111
+ $suffix = date('Y-m-d'); // 2020-11-11
112
+ }
113
+
114
+ $filename = $name . '-' . $suffix . '.pdf';
115
+
116
+ // Filter filename
117
+ $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
118
+ $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
119
+
120
+ // sanitize filename (after filters to prevent human errors)!
121
+ return sanitize_file_name( $filename );
122
+ }
123
+
124
+
125
+ /**
126
+ * Initialise settings
127
+ */
128
+ public function init_settings() {
129
+ // Register settings.
130
+ $page = $option_group = $option_name = 'wpo_wcpdf_documents_settings_invoice';
131
+
132
+ $settings_fields = array(
133
+ array(
134
+ 'type' => 'section',
135
+ 'id' => 'invoice',
136
+ 'title' => '',
137
+ 'callback' => 'section',
138
+ ),
139
+ array(
140
+ 'type' => 'setting',
141
+ 'id' => 'enabled',
142
+ 'title' => __( 'Enable', 'woocommerce-pdf-invoices-packing-slips' ),
143
+ 'callback' => 'checkbox',
144
+ 'section' => 'invoice',
145
+ 'args' => array(
146
+ 'option_name' => $option_name,
147
+ 'id' => 'enabled',
148
+ )
149
+ ),
150
+ array(
151
+ 'type' => 'setting',
152
+ 'id' => 'attach_to_email_ids',
153
+ 'title' => __( 'Attach to:', 'woocommerce-pdf-invoices-packing-slips' ),
154
+ 'callback' => 'multiple_checkboxes',
155
+ 'section' => 'invoice',
156
+ 'args' => array(
157
+ 'option_name' => $option_name,
158
+ 'id' => 'attach_to_email_ids',
159
+ 'fields' => $this->get_wc_emails(),
160
+ 'description' => !is_writable( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ) ? '<span class="wpo-warning">' . sprintf( __( 'It looks like the temp folder (<code>%s</code>) is not writable, check the permissions for this folder! Without having write access to this folder, the plugin will not be able to email invoices.', 'woocommerce-pdf-invoices-packing-slips' ), WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ).'</span>':'',
161
+ )
162
+ ),
163
+ array(
164
+ 'type' => 'setting',
165
+ 'id' => 'display_shipping_address',
166
+ 'title' => __( 'Display shipping address', 'woocommerce-pdf-invoices-packing-slips' ),
167
+ 'callback' => 'checkbox',
168
+ 'section' => 'invoice',
169
+ 'args' => array(
170
+ 'option_name' => $option_name,
171
+ 'id' => 'display_shipping_address',
172
+ 'description' => __( 'Display shipping address (in addition to the default billing address) if different from billing address', 'woocommerce-pdf-invoices-packing-slips' ),
173
+ )
174
+ ),
175
+ array(
176
+ 'type' => 'setting',
177
+ 'id' => 'display_email',
178
+ 'title' => __( 'Display email address', 'woocommerce-pdf-invoices-packing-slips' ),
179
+ 'callback' => 'checkbox',
180
+ 'section' => 'invoice',
181
+ 'args' => array(
182
+ 'option_name' => $option_name,
183
+ 'id' => 'display_email',
184
+ )
185
+ ),
186
+ array(
187
+ 'type' => 'setting',
188
+ 'id' => 'display_phone',
189
+ 'title' => __( 'Display phone number', 'woocommerce-pdf-invoices-packing-slips' ),
190
+ 'callback' => 'checkbox',
191
+ 'section' => 'invoice',
192
+ 'args' => array(
193
+ 'option_name' => $option_name,
194
+ 'id' => 'display_phone',
195
+ )
196
+ ),
197
+ array(
198
+ 'type' => 'setting',
199
+ 'id' => 'display_date',
200
+ 'title' => __( 'Display invoice date', 'woocommerce-pdf-invoices-packing-slips' ),
201
+ 'callback' => 'checkbox',
202
+ 'section' => 'invoice',
203
+ 'args' => array(
204
+ 'option_name' => $option_name,
205
+ 'id' => 'display_date',
206
+ 'value' => 'invoice_date',
207
+ )
208
+ ),
209
+ array(
210
+ 'type' => 'setting',
211
+ 'id' => 'display_number',
212
+ 'title' => __( 'Display invoice number', 'woocommerce-pdf-invoices-packing-slips' ),
213
+ 'callback' => 'checkbox',
214
+ 'section' => 'invoice',
215
+ 'args' => array(
216
+ 'option_name' => $option_name,
217
+ 'id' => 'display_number',
218
+ 'value' => 'invoice_number',
219
+ )
220
+ ),
221
+ array(
222
+ 'type' => 'setting',
223
+ 'id' => 'next_invoice_number',
224
+ 'title' => __( 'Next invoice number (without prefix/suffix etc.)', 'woocommerce-pdf-invoices-packing-slips' ),
225
+ 'callback' => 'next_number_edit',
226
+ 'section' => 'invoice',
227
+ 'args' => array(
228
+ 'store' => 'invoice_number',
229
+ 'size' => '10',
230
+ 'description' => __( 'This is the number that will be used for the next document. By default, numbering starts from 1 and increases for every new document. Note that if you override this and set it lower than the current/highest number, this could create duplicate numbers!', 'woocommerce-pdf-invoices-packing-slips' ),
231
+ )
232
+ ),
233
+ array(
234
+ 'type' => 'setting',
235
+ 'id' => 'number_format',
236
+ 'title' => __( 'Number format', 'woocommerce-pdf-invoices-packing-slips' ),
237
+ 'callback' => 'multiple_text_input',
238
+ 'section' => 'invoice',
239
+ 'args' => array(
240
+ 'option_name' => $option_name,
241
+ 'id' => 'number_format',
242
+ 'fields' => array(
243
+ 'prefix' => array(
244
+ 'placeholder' => __( 'Prefix' , 'woocommerce-pdf-invoices-packing-slips' ),
245
+ 'size' => 20,
246
+ 'description' => __( 'to use the invoice year and/or month, use [invoice_year] or [invoice_month] respectively' , 'woocommerce-pdf-invoices-packing-slips' ),
247
+ ),
248
+ 'suffix' => array(
249
+ 'placeholder' => __( 'Suffix' , 'woocommerce-pdf-invoices-packing-slips' ),
250
+ 'size' => 20,
251
+ 'description' => '',
252
+ ),
253
+ 'padding' => array(
254
+ 'placeholder' => __( 'Padding' , 'woocommerce-pdf-invoices-packing-slips' ),
255
+ 'size' => 20,
256
+ 'type' => 'number',
257
+ 'description' => __( 'enter the number of digits here - enter "6" to display 42 as 000042' , 'woocommerce-pdf-invoices-packing-slips' ),
258
+ ),
259
+ ),
260
+ 'description' => __( 'note: if you have already created a custom invoice number format with a filter, the above settings will be ignored' , 'woocommerce-pdf-invoices-packing-slips' ),
261
+ )
262
+ ),
263
+ array(
264
+ 'type' => 'setting',
265
+ 'id' => 'reset_number_yearly',
266
+ 'title' => __( 'Reset invoice number yearly', 'woocommerce-pdf-invoices-packing-slips' ),
267
+ 'callback' => 'checkbox',
268
+ 'section' => 'invoice',
269
+ 'args' => array(
270
+ 'option_name' => $option_name,
271
+ 'id' => 'reset_number_yearly',
272
+ )
273
+ ),
274
+ array(
275
+ 'type' => 'setting',
276
+ 'id' => 'my_account_buttons',
277
+ 'title' => __( 'Allow My Account invoice download', 'woocommerce-pdf-invoices-packing-slips' ),
278
+ 'callback' => 'select',
279
+ 'section' => 'invoice',
280
+ 'args' => array(
281
+ 'option_name' => $option_name,
282
+ 'id' => 'my_account_buttons',
283
+ 'options' => array(
284
+ 'available' => __( 'Only when an invoice is already created/emailed' , 'woocommerce-pdf-invoices-packing-slips' ),
285
+ 'custom' => __( 'Only for specific order statuses (define below)' , 'woocommerce-pdf-invoices-packing-slips' ),
286
+ 'always' => __( 'Always' , 'woocommerce-pdf-invoices-packing-slips' ),
287
+ 'never' => __( 'Never' , 'woocommerce-pdf-invoices-packing-slips' ),
288
+ ),
289
+ 'custom' => array(
290
+ 'type' => 'multiple_checkboxes',
291
+ 'args' => array(
292
+ 'option_name' => $option_name,
293
+ 'id' => 'my_account_restrict',
294
+ 'fields' => $this->get_wc_order_status_list(),
295
+ ),
296
+ ),
297
+ )
298
+ ),
299
+ array(
300
+ 'type' => 'setting',
301
+ 'id' => 'invoice_number_column',
302
+ 'title' => __( 'Enable invoice number column in the orders list', 'woocommerce-pdf-invoices-packing-slips' ),
303
+ 'callback' => 'checkbox',
304
+ 'section' => 'invoice',
305
+ 'args' => array(
306
+ 'option_name' => $option_name,
307
+ 'id' => 'invoice_number_column',
308
+ )
309
+ ),
310
+ array(
311
+ 'type' => 'setting',
312
+ 'id' => 'disable_free',
313
+ 'title' => __( 'Disable for free products', 'woocommerce-pdf-invoices-packing-slips' ),
314
+ 'callback' => 'checkbox',
315
+ 'section' => 'invoice',
316
+ 'args' => array(
317
+ 'option_name' => $option_name,
318
+ 'id' => 'disable_free',
319
+ 'description' => __( "Disable automatic creation/attachment when only free products are ordered", 'woocommerce-pdf-invoices-packing-slips' ),
320
+ )
321
+ ),
322
+ );
323
+
324
+
325
+ // remove/rename some fields when invoice number is controlled externally
326
+ if( apply_filters('woocommerce_invoice_number_by_plugin', false) ) {
327
+ $remove_settings = array( 'next_invoice_number', 'number_format', 'reset_number_yearly' );
328
+ foreach ($settings_fields as $key => $settings_field) {
329
+ if (in_array($settings_field['id'], $remove_settings)) {
330
+ unset($settings_fields[$key]);
331
+ } elseif ( $settings_field['id'] == 'display_number' ) {
332
+ // alternate description for invoice number
333
+ $invoice_number_desc = __( 'Invoice numbers are created by a third-party extension.', 'woocommerce-pdf-invoices-packing-slips' );
334
+ if ( esc_attr( apply_filters( 'woocommerce_invoice_number_configuration_link', null ) ) ) {
335
+ $invoice_number_desc .= ' '.sprintf(__( 'Configure it <a href="%s">here</a>.', 'woocommerce-pdf-invoices-packing-slips' ), $config_link);
336
+ }
337
+ $settings_fields[$key]['args']['description'] = '<i>'.$invoice_number_desc.'</i>';
338
+ }
339
+ }
340
+ }
341
+
342
+ // allow plugins to alter settings fields
343
+ $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_documents_invoice', $settings_fields, $page, $option_group, $option_name );
344
+ WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
345
+ return;
346
+
347
+ }
348
+
349
+ }
350
+
351
+ endif; // class_exists
352
+
353
  return new Invoice();
includes/documents/class-wcpdf-packing-slip.php CHANGED
@@ -1,150 +1,150 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
- use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
- use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly
10
- }
11
-
12
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Packing_Slip' ) ) :
13
-
14
- /**
15
- * Packing Slip Document
16
- *
17
- * @class \WPO\WC\PDF_Invoices\Documents\Packing_Slip
18
- * @version 2.0
19
- * @category Class
20
- * @author Ewout Fernhout
21
- */
22
-
23
- class Packing_Slip extends Order_Document_Methods {
24
- /**
25
- * Init/load the order object.
26
- *
27
- * @param int|object|WC_Order $order Order to init.
28
- */
29
- public function __construct( $order = 0 ) {
30
- // set properties
31
- $this->type = 'packing-slip';
32
- $this->title = __( 'Packing Slip', 'woocommerce-pdf-invoices-packing-slips' );
33
- $this->icon = WPO_WCPDF()->plugin_url() . "/assets/images/packing-slip.png";
34
-
35
- // Call parent constructor
36
- parent::__construct( $order );
37
- }
38
-
39
- public function get_title() {
40
- // override/not using $this->title to allow for language switching!
41
- return apply_filters( "wpo_wcpdf_{$this->slug}_title", __( 'Packing Slip', 'woocommerce-pdf-invoices-packing-slips' ), $this );
42
- }
43
-
44
- public function get_settings() {
45
- $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
46
- $document_settings = get_option( 'wpo_wcpdf_documents_settings_packing-slip' );
47
- return (array) $document_settings + (array) $common_settings;
48
- }
49
-
50
- public function get_filename( $context = 'download', $args = array() ) {
51
- $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
52
-
53
- $name = _n( 'packing-slip', 'packing-slips', $order_count, 'woocommerce-pdf-invoices-packing-slips' );
54
-
55
- if ( $order_count == 1 ) {
56
- if ( isset( $this->settings['display_number'] ) ) {
57
- $suffix = (string) $this->get_number();
58
- } else {
59
- if ( empty( $this->order ) ) {
60
- $order = WCX::get_order ( $order_ids[0] );
61
- $suffix = method_exists( $order, 'get_order_number' ) ? $order->get_order_number() : '';
62
- } else {
63
- $suffix = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
64
- }
65
- }
66
- } else {
67
- $suffix = date('Y-m-d'); // 2020-11-11
68
- }
69
-
70
- $filename = $name . '-' . $suffix . '.pdf';
71
-
72
- // Filter filename
73
- $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
74
- $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
75
-
76
- // sanitize filename (after filters to prevent human errors)!
77
- return sanitize_file_name( $filename );
78
- }
79
-
80
- public function init_settings() {
81
- // Register settings.
82
- $page = $option_group = $option_name = 'wpo_wcpdf_documents_settings_packing-slip';
83
-
84
- $settings_fields = array(
85
- array(
86
- 'type' => 'section',
87
- 'id' => 'packing_slip',
88
- 'title' => '',
89
- 'callback' => 'section',
90
- ),
91
- array(
92
- 'type' => 'setting',
93
- 'id' => 'enabled',
94
- 'title' => __( 'Enable', 'woocommerce-pdf-invoices-packing-slips' ),
95
- 'callback' => 'checkbox',
96
- 'section' => 'packing_slip',
97
- 'args' => array(
98
- 'option_name' => $option_name,
99
- 'id' => 'enabled',
100
- )
101
- ),
102
- array(
103
- 'type' => 'setting',
104
- 'id' => 'display_billing_address',
105
- 'title' => __( 'Display billing address', 'woocommerce-pdf-invoices-packing-slips' ),
106
- 'callback' => 'checkbox',
107
- 'section' => 'packing_slip',
108
- 'args' => array(
109
- 'option_name' => $option_name,
110
- 'id' => 'display_billing_address',
111
- 'description' => __( 'Display billing address (in addition to the default shipping address) if different from shipping address', 'woocommerce-pdf-invoices-packing-slips' ),
112
- )
113
- ),
114
- array(
115
- 'type' => 'setting',
116
- 'id' => 'display_email',
117
- 'title' => __( 'Display email address', 'woocommerce-pdf-invoices-packing-slips' ),
118
- 'callback' => 'checkbox',
119
- 'section' => 'packing_slip',
120
- 'args' => array(
121
- 'option_name' => $option_name,
122
- 'id' => 'display_email',
123
- )
124
- ),
125
- array(
126
- 'type' => 'setting',
127
- 'id' => 'display_phone',
128
- 'title' => __( 'Display phone number', 'woocommerce-pdf-invoices-packing-slips' ),
129
- 'callback' => 'checkbox',
130
- 'section' => 'packing_slip',
131
- 'args' => array(
132
- 'option_name' => $option_name,
133
- 'id' => 'display_phone',
134
- )
135
- ),
136
- );
137
-
138
-
139
- // allow plugins to alter settings fields
140
- $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_documents_packing_slip', $settings_fields, $page, $option_group, $option_name );
141
- WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
142
- return;
143
-
144
- }
145
-
146
- }
147
-
148
- endif; // class_exists
149
-
150
  return new Packing_Slip();
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ use WPO\WC\PDF_Invoices\Compatibility\WC_Core as WCX;
5
+ use WPO\WC\PDF_Invoices\Compatibility\Order as WCX_Order;
6
+ use WPO\WC\PDF_Invoices\Compatibility\Product as WCX_Product;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Packing_Slip' ) ) :
13
+
14
+ /**
15
+ * Packing Slip Document
16
+ *
17
+ * @class \WPO\WC\PDF_Invoices\Documents\Packing_Slip
18
+ * @version 2.0
19
+ * @category Class
20
+ * @author Ewout Fernhout
21
+ */
22
+
23
+ class Packing_Slip extends Order_Document_Methods {
24
+ /**
25
+ * Init/load the order object.
26
+ *
27
+ * @param int|object|WC_Order $order Order to init.
28
+ */
29
+ public function __construct( $order = 0 ) {
30
+ // set properties
31
+ $this->type = 'packing-slip';
32
+ $this->title = __( 'Packing Slip', 'woocommerce-pdf-invoices-packing-slips' );
33
+ $this->icon = WPO_WCPDF()->plugin_url() . "/assets/images/packing-slip.png";
34
+
35
+ // Call parent constructor
36
+ parent::__construct( $order );
37
+ }
38
+
39
+ public function get_title() {
40
+ // override/not using $this->title to allow for language switching!
41
+ return apply_filters( "wpo_wcpdf_{$this->slug}_title", __( 'Packing Slip', 'woocommerce-pdf-invoices-packing-slips' ), $this );
42
+ }
43
+
44
+ public function get_settings() {
45
+ $common_settings = WPO_WCPDF()->settings->get_common_document_settings();
46
+ $document_settings = get_option( 'wpo_wcpdf_documents_settings_packing-slip' );
47
+ return (array) $document_settings + (array) $common_settings;
48
+ }
49
+
50
+ public function get_filename( $context = 'download', $args = array() ) {
51
+ $order_count = isset($args['order_ids']) ? count($args['order_ids']) : 1;
52
+
53
+ $name = _n( 'packing-slip', 'packing-slips', $order_count, 'woocommerce-pdf-invoices-packing-slips' );
54
+
55
+ if ( $order_count == 1 ) {
56
+ if ( isset( $this->settings['display_number'] ) ) {
57
+ $suffix = (string) $this->get_number();
58
+ } else {
59
+ if ( empty( $this->order ) ) {
60
+ $order = WCX::get_order ( $order_ids[0] );
61
+ $suffix = method_exists( $order, 'get_order_number' ) ? $order->get_order_number() : '';
62
+ } else {
63
+ $suffix = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : '';
64
+ }
65
+ }
66
+ } else {
67
+ $suffix = date('Y-m-d'); // 2020-11-11
68
+ }
69
+
70
+ $filename = $name . '-' . $suffix . '.pdf';
71
+
72
+ // Filter filename
73
+ $order_ids = isset($args['order_ids']) ? $args['order_ids'] : array( $this->order_id );
74
+ $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $this->get_type(), $order_ids, $context );
75
+
76
+ // sanitize filename (after filters to prevent human errors)!
77
+ return sanitize_file_name( $filename );
78
+ }
79
+
80
+ public function init_settings() {
81
+ // Register settings.
82
+ $page = $option_group = $option_name = 'wpo_wcpdf_documents_settings_packing-slip';
83
+
84
+ $settings_fields = array(
85
+ array(
86
+ 'type' => 'section',
87
+ 'id' => 'packing_slip',
88
+ 'title' => '',
89
+ 'callback' => 'section',
90
+ ),
91
+ array(
92
+ 'type' => 'setting',
93
+ 'id' => 'enabled',
94
+ 'title' => __( 'Enable', 'woocommerce-pdf-invoices-packing-slips' ),
95
+ 'callback' => 'checkbox',
96
+ 'section' => 'packing_slip',
97
+ 'args' => array(
98
+ 'option_name' => $option_name,
99
+ 'id' => 'enabled',
100
+ )
101
+ ),
102
+ array(
103
+ 'type' => 'setting',
104
+ 'id' => 'display_billing_address',
105
+ 'title' => __( 'Display billing address', 'woocommerce-pdf-invoices-packing-slips' ),
106
+ 'callback' => 'checkbox',
107
+ 'section' => 'packing_slip',
108
+ 'args' => array(
109
+ 'option_name' => $option_name,
110
+ 'id' => 'display_billing_address',
111
+ 'description' => __( 'Display billing address (in addition to the default shipping address) if different from shipping address', 'woocommerce-pdf-invoices-packing-slips' ),
112
+ )
113
+ ),
114
+ array(
115
+ 'type' => 'setting',
116
+ 'id' => 'display_email',
117
+ 'title' => __( 'Display email address', 'woocommerce-pdf-invoices-packing-slips' ),
118
+ 'callback' => 'checkbox',
119
+ 'section' => 'packing_slip',
120
+ 'args' => array(
121
+ 'option_name' => $option_name,
122
+ 'id' => 'display_email',
123
+ )
124
+ ),
125
+ array(
126
+ 'type' => 'setting',
127
+ 'id' => 'display_phone',
128
+ 'title' => __( 'Display phone number', 'woocommerce-pdf-invoices-packing-slips' ),
129
+ 'callback' => 'checkbox',
130
+ 'section' => 'packing_slip',
131
+ 'args' => array(
132
+ 'option_name' => $option_name,
133
+ 'id' => 'display_phone',
134
+ )
135
+ ),
136
+ );
137
+
138
+
139
+ // allow plugins to alter settings fields
140
+ $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_documents_packing_slip', $settings_fields, $page, $option_group, $option_name );
141
+ WPO_WCPDF()->settings->add_settings_fields( $settings_fields, $page, $option_group, $option_name );
142
+ return;
143
+
144
+ }
145
+
146
+ }
147
+
148
+ endif; // class_exists
149
+
150
  return new Packing_Slip();
includes/documents/class-wcpdf-sequential-number-store.php CHANGED
@@ -1,167 +1,167 @@
1
- <?php
2
- namespace WPO\WC\PDF_Invoices\Documents;
3
-
4
- if ( ! defined( 'ABSPATH' ) ) {
5
- exit; // Exit if accessed directly
6
- }
7
-
8
- if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Sequential_Number_Store' ) ) :
9
-
10
- /**
11
- * Class handling database interaction for sequential numbers
12
- *
13
- * @class \WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store
14
- * @version 2.0
15
- * @category Class
16
- * @author Ewout Fernhout
17
- */
18
-
19
- class Sequential_Number_Store {
20
- /**
21
- * Name of the table that stores the number sequence (without the wp_wcpdf_ table prefix)
22
- * @var String
23
- */
24
- public $table_name;
25
-
26
- /**
27
- * Number store method, either 'auto_increment' or 'calculate'
28
- * @var String
29
- */
30
- public $method;
31
-
32
- public function __construct( $table_name, $method = 'auto_increment' ) {
33
- global $wpdb;
34
- $this->table_name = "{$wpdb->prefix}wcpdf_{$table_name}"; // i.e. wp_wcpdf_invoice_number
35
- $this->method = $method;
36
-
37
- $this->init();
38
- }
39
-
40
- public function init() {
41
- global $wpdb;
42
- // check if table exists
43
- if( $wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") == $this->table_name) {
44
- // check calculated_number column if using 'calculate' method
45
- if ( $this->method == 'calculate' ) {
46
- $column_exists = $wpdb->get_var("SHOW COLUMNS FROM `{$this->table_name}` LIKE 'calculated_number'");
47
- if (empty($column_exists)) {
48
- $wpdb->query("ALTER TABLE {$this->table_name} ADD calculated_number int (16)");
49
- }
50
- }
51
- return; // no further business
52
- }
53
-
54
- // create table (in case of concurrent requests, this does no harm if it already exists)
55
- $charset_collate = $wpdb->get_charset_collate();
56
- // dbDelta is a sensitive kid, so we omit indentation
57
- $sql = "CREATE TABLE {$this->table_name} (
58
- id int(16) NOT NULL AUTO_INCREMENT,
59
- order_id int(16),
60
- date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
61
- calculated_number int (16),
62
- PRIMARY KEY (id)
63
- ) $charset_collate;";
64
-
65
- require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
66
- $result = dbDelta( $sql );
67
-
68
- return $result;
69
- }
70
-
71
- /**
72
- * Consume/create the next number and return it
73
- * @param integer $order_id WooCommerce Order ID
74
- * @param string $date Local date, formatted as Y-m-d H:i:s
75
- * @return int Number that was consumed/created
76
- */
77
- public function increment( $order_id = 0, $date = null ) {
78
- global $wpdb;
79
- if ( empty( $date ) ) {
80
- $date = get_date_from_gmt( date( 'Y-m-d H:i:s' ) );
81
- }
82
-
83
- do_action( 'wpo_wcpdf_before_sequential_number_increment', $this, $order_id, $date );
84
-
85
- $data = array(
86
- 'order_id' => (int) $order_id,
87
- 'date' => $date,
88
- );
89
-
90
- if ( $this->method == 'auto_increment' ) {
91
- $wpdb->insert( $this->table_name, $data );
92
- $number = $wpdb->insert_id;
93
- } elseif ( $this->method == 'calculate' ) {
94
- $number = $data['calculated_number'] = $this->get_next();
95
- $wpdb->insert( $this->table_name, $data );
96
- }
97
-
98
- // return generated number
99
- return $number;
100
- }
101
-
102
- /**
103
- * Get the number that will be used on the next increment
104
- * @return int next number
105
- */
106
- public function get_next() {
107
- global $wpdb;
108
- if ( $this->method == 'auto_increment' ) {
109
- // get next auto_increment value
110
- $table_status = $wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->table_name}'");
111
- $next = $table_status->Auto_increment;
112
- } elseif ( $this->method == 'calculate' ) {
113
- $last_row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
114
- if ( empty( $last_row ) ) {
115
- $next = 1;
116
- } elseif ( !empty( $last_row->calculated_number ) ) {
117
- $next = (int) $last_row->calculated_number + 1;
118
- } else {
119
- $next = (int) $last_row->id + 1;
120
- }
121
- }
122
- return $next;
123
- }
124
-
125
- /**
126
- * Set the number that will be used on the next increment
127
- */
128
- public function set_next( $number = 1 ) {
129
- global $wpdb;
130
-
131
- // delete all rows
132
- $delete = $wpdb->query("TRUNCATE TABLE {$this->table_name}");
133
-
134
- // set auto_increment
135
- if ( $number > 1 ) {
136
- // if AUTO_INCREMENT is not 1, we need to make sure we have a 'highest value' in case of server restarts
137
- // https://serverfault.com/questions/228690/mysql-auto-increment-fields-resets-by-itself
138
- $highest_number = (int) $number - 1;
139
- $wpdb->query("ALTER TABLE {$this->table_name} AUTO_INCREMENT={$highest_number};");
140
- $data = array(
141
- 'order_id' => 0,
142
- 'date' => get_date_from_gmt( date( 'Y-m-d H:i:s' ) ),
143
- );
144
-
145
- if ( $this->method == 'calculate' ) {
146
- $data['calculated_number'] = $highest_number;
147
- }
148
-
149
- // after this insert, AUTO_INCREMENT will be equal to $number
150
- $wpdb->insert( $this->table_name, $data );
151
- } else {
152
- // simple scenario, no need to insert any rows
153
- $wpdb->query("ALTER TABLE {$this->table_name} AUTO_INCREMENT={$number};");
154
- }
155
- }
156
-
157
- public function get_last_date( $format = 'Y-m-d H:i:s' ) {
158
- global $wpdb;
159
- $row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
160
- $date = isset( $row->date ) ? $row->date : 'now';
161
- $formatted_date = date( $format, strtotime( $date ) );
162
-
163
- return $formatted_date;
164
- }
165
- }
166
-
167
- endif; // class_exists
1
+ <?php
2
+ namespace WPO\WC\PDF_Invoices\Documents;
3
+
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit; // Exit if accessed directly
6
+ }
7
+
8
+ if ( !class_exists( '\\WPO\\WC\\PDF_Invoices\\Documents\\Sequential_Number_Store' ) ) :
9
+
10
+ /**
11
+ * Class handling database interaction for sequential numbers
12
+ *
13
+ * @class \WPO\WC\PDF_Invoices\Documents\Sequential_Number_Store
14
+ * @version 2.0
15
+ * @category Class
16
+ * @author Ewout Fernhout
17
+ */
18
+
19
+ class Sequential_Number_Store {
20
+ /**
21
+ * Name of the table that stores the number sequence (without the wp_wcpdf_ table prefix)
22
+ * @var String
23
+ */
24
+ public $table_name;
25
+
26
+ /**
27
+ * Number store method, either 'auto_increment' or 'calculate'
28
+ * @var String
29
+ */
30
+ public $method;
31
+
32
+ public function __construct( $table_name, $method = 'auto_increment' ) {
33
+ global $wpdb;
34
+ $this->table_name = "{$wpdb->prefix}wcpdf_{$table_name}"; // i.e. wp_wcpdf_invoice_number
35
+ $this->method = $method;
36
+
37
+ $this->init();
38
+ }
39
+
40
+ public function init() {
41
+ global $wpdb;
42
+ // check if table exists
43
+ if( $wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") == $this->table_name) {
44
+ // check calculated_number column if using 'calculate' method
45
+ if ( $this->method == 'calculate' ) {
46
+ $column_exists = $wpdb->get_var("SHOW COLUMNS FROM `{$this->table_name}` LIKE 'calculated_number'");
47
+ if (empty($column_exists)) {
48
+ $wpdb->query("ALTER TABLE {$this->table_name} ADD calculated_number int (16)");
49
+ }
50
+ }
51
+ return; // no further business
52
+ }
53
+
54
+ // create table (in case of concurrent requests, this does no harm if it already exists)
55
+ $charset_collate = $wpdb->get_charset_collate();
56
+ // dbDelta is a sensitive kid, so we omit indentation
57
+ $sql = "CREATE TABLE {$this->table_name} (
58
+ id int(16) NOT NULL AUTO_INCREMENT,
59
+ order_id int(16),
60
+ date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
61
+ calculated_number int (16),
62
+ PRIMARY KEY (id)
63
+ ) $charset_collate;";
64
+
65
+ require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
66
+ $result = dbDelta( $sql );
67
+
68
+ return $result;
69
+ }
70
+
71
+ /**
72
+ * Consume/create the next number and return it
73
+ * @param integer $order_id WooCommerce Order ID
74
+ * @param string $date Local date, formatted as Y-m-d H:i:s
75
+ * @return int Number that was consumed/created
76
+ */
77
+ public function increment( $order_id = 0, $date = null ) {
78
+ global $wpdb;
79
+ if ( empty( $date ) ) {
80
+ $date = get_date_from_gmt( date( 'Y-m-d H:i:s' ) );
81
+ }
82
+
83
+ do_action( 'wpo_wcpdf_before_sequential_number_increment', $this, $order_id, $date );
84
+
85
+ $data = array(
86
+ 'order_id' => (int) $order_id,
87
+ 'date' => $date,
88
+ );
89
+
90
+ if ( $this->method == 'auto_increment' ) {
91
+ $wpdb->insert( $this->table_name, $data );
92
+ $number = $wpdb->insert_id;
93
+ } elseif ( $this->method == 'calculate' ) {
94
+ $number = $data['calculated_number'] = $this->get_next();
95
+ $wpdb->insert( $this->table_name, $data );
96
+ }
97
+
98
+ // return generated number
99
+ return $number;
100
+ }
101
+
102
+ /**
103
+ * Get the number that will be used on the next increment
104
+ * @return int next number
105
+ */
106
+ public function get_next() {
107
+ global $wpdb;
108
+ if ( $this->method == 'auto_increment' ) {
109
+ // get next auto_increment value
110
+ $table_status = $wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->table_name}'");
111
+ $next = $table_status->Auto_increment;
112
+ } elseif ( $this->method == 'calculate' ) {
113
+ $last_row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
114
+ if ( empty( $last_row ) ) {
115
+ $next = 1;
116
+ } elseif ( !empty( $last_row->calculated_number ) ) {
117
+ $next = (int) $last_row->calculated_number + 1;
118
+ } else {
119
+ $next = (int) $last_row->id + 1;
120
+ }
121
+ }
122
+ return $next;
123
+ }
124
+
125
+ /**
126
+ * Set the number that will be used on the next increment
127
+ */
128
+ public function set_next( $number = 1 ) {
129
+ global $wpdb;
130
+
131
+ // delete all rows
132
+ $delete = $wpdb->query("TRUNCATE TABLE {$this->table_name}");
133
+
134
+ // set auto_increment
135
+ if ( $number > 1 ) {
136
+ // if AUTO_INCREMENT is not 1, we need to make sure we have a 'highest value' in case of server restarts
137
+ // https://serverfault.com/questions/228690/mysql-auto-increment-fields-resets-by-itself
138
+ $highest_number = (int) $number - 1;
139
+ $wpdb->query("ALTER TABLE {$this->table_name} AUTO_INCREMENT={$highest_number};");
140
+ $data = array(
141
+ 'order_id' => 0,
142
+ 'date' => get_date_from_gmt( date( 'Y-m-d H:i:s' ) ),
143
+ );
144
+
145
+ if ( $this->method == 'calculate' ) {
146
+ $data['calculated_number'] = $highest_number;
147
+ }
148
+
149
+ // after this insert, AUTO_INCREMENT will be equal to $number
150
+ $wpdb->insert( $this->table_name, $data );
151
+ } else {
152
+ // simple scenario, no need to insert any rows
153
+ $wpdb->query("ALTER TABLE {$this->table_name} AUTO_INCREMENT={$number};");
154
+ }
155
+ }
156
+
157
+ public function get_last_date( $format = 'Y-m-d H:i:s' ) {
158
+ global $wpdb;
159
+ $row = $wpdb->get_row( "SELECT * FROM {$this->table_name} WHERE id = ( SELECT MAX(id) from {$this->table_name} )" );
160
+ $date = isset( $row->date ) ? $row->date : 'now';
161
+ $formatted_date = date( $format, strtotime( $date ) );
162
+
163
+ return $formatted_date;
164
+ }
165
+ }
166
+
167
+ endif; // class_exists
includes/views/attachment-settings-hint.php CHANGED
@@ -1,23 +1,23 @@
1
- <?php
2
- $invoice_settings_url = add_query_arg( array(
3
- 'tab' => 'documents',
4
- 'section' => 'invoice',
5
- ) );
6
- ?>
7
- <style>
8
- .wcpdf-attachment-settings-hint {
9
- display: inline-block;
10
- background: #fff;
11
- border-left: 4px solid #cc99c2 !important;
12
- -webkit-box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
13
- box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
14
- padding: 15px;
15
- margin-top: 15px;
16
- font-size: 120%;
17
- }
18
- </style>
19
- <!-- <div id="message" class="updated woocommerce-message"> -->
20
- <div class="wcpdf-attachment-settings-hint">
21
- <?php printf(__( "It looks like you haven't setup any email attachments yet, check the settings under <b>%sDocuments > Invoice%s</b>", 'woocommerce-pdf-invoices-packing-slips' ), '<a href="'.$invoice_settings_url.'">', '</a>'); ?><br>
22
- <?php printf('<a href="%s" style="font">%s</a>', add_query_arg( 'wpo_wcpdf_hide_attachments_hint', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) ); ?>
23
- </div>
1
+ <?php
2
+ $invoice_settings_url = add_query_arg( array(
3
+ 'tab' => 'documents',
4
+ 'section' => 'invoice',
5
+ ) );
6
+ ?>
7
+ <style>
8
+ .wcpdf-attachment-settings-hint {
9
+ display: inline-block;
10
+ background: #fff;
11
+ border-left: 4px solid #cc99c2 !important;
12
+ -webkit-box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
13
+ box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
14
+ padding: 15px;
15
+ margin-top: 15px;
16
+ font-size: 120%;
17
+ }
18
+ </style>
19
+ <!-- <div id="message" class="updated woocommerce-message"> -->
20
+ <div class="wcpdf-attachment-settings-hint">
21
+ <?php printf(__( "It looks like you haven't setup any email attachments yet, check the settings under <b>%sDocuments > Invoice%s</b>", 'woocommerce-pdf-invoices-packing-slips' ), '<a href="'.$invoice_settings_url.'">', '</a>'); ?><br>
22
+ <?php printf('<a href="%s" style="font">%s</a>', add_query_arg( 'wpo_wcpdf_hide_attachments_hint', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) ); ?>
23
+ </div>
includes/views/dompdf-status.php CHANGED
@@ -1,203 +1,203 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly
4
- }
5
-
6
- $memory_limit = function_exists('wc_let_to_num')?wc_let_to_num( WP_MEMORY_LIMIT ):woocommerce_let_to_num( WP_MEMORY_LIMIT );
7
- $php_mem_limit = function_exists( 'memory_get_usage' ) ? @ini_get( 'memory_limit' ) : '-';
8
-
9
- $server_configs = array(
10
- "DOMDocument extension" => array(
11
- "required" => true,
12
- "value" => phpversion("DOM"),
13
- "result" => class_exists("DOMDocument"),
14
- ),
15
- "MBString extension" => array(
16
- "required" => true,
17
- "value" => phpversion("mbstring"),
18
- "result" => function_exists("mb_send_mail"),
19
- "fallback" => "Recommended, will use fallback functions",
20
- ),
21
- "GD" => array(
22
- "required" => true,
23
- "value" => phpversion("gd"),
24
- "result" => function_exists("imagecreate"),
25
- "fallback" => "Required if you have images in your documents",
26
- ),
27
- // "PCRE" => array(
28
- // "required" => true,
29
- // "value" => phpversion("pcre"),
30
- // "result" => function_exists("preg_match") && @preg_match("/./u", "a"),
31
- // "failure" => "PCRE is required with Unicode support (the \"u\" modifier)",
32
- // ),
33
- "Zlib" => array(
34
- "required" => "To compress PDF documents",
35
- "value" => phpversion("zlib"),
36
- "result" => function_exists("gzcompress"),
37
- "fallback" => "Recommended to compress PDF documents",
38
- ),
39
- "opcache" => array(
40
- "required" => "For better performances",
41
- "value" => null,
42
- "result" => false,
43
- "fallback" => "Recommended for better performances",
44
- ),
45
- "GMagick or IMagick" => array(
46
- "required" => "Better with transparent PNG images",
47
- "value" => null,
48
- "result" => extension_loaded("gmagick") || extension_loaded("imagick"),
49
- "fallback" => "Recommended for better performances",
50
- ),
51
- "glob()" => array(
52
- "required" => "Required to detect custom templates and to clear the temp folder periodically",
53
- "value" => null,
54
- "result" => function_exists("glob"),
55
- "fallback" => "Check php disable_functions",
56
- ),
57
- "WP Memory Limit" => array(
58
- "required" => 'Recommended: 128MB (more for plugin-heavy setups)<br/>See: <a href="http://codex.wordpress.org/Editing_wp-config.php#Increasing_memory_allocated_to_PHP">Increasing memory allocated to PHP</a>',
59
- "value" => sprintf("WordPress: %s, PHP: %s", WP_MEMORY_LIMIT, $php_mem_limit ),
60
- "result" => $memory_limit > 67108864,
61
- ),
62
- 'allow_url_fopen' => array (
63
- 'required' => 'Allow remote stylesheets and images',
64
- 'value' => null,
65
- 'result' => ini_get("allow_url_fopen"),
66
- "fallback" => "allow_url_fopen disabled",
67
- ),
68
- );
69
-
70
- if (($xc = extension_loaded("xcache")) || ($apc = extension_loaded("apc")) || ($zop = extension_loaded("Zend OPcache")) || ($op = extension_loaded("opcache"))) {
71
- $server_configs["opcache"]["result"] = true;
72
- $server_configs["opcache"]["value"] = (
73
- $xc ? "XCache ".phpversion("xcache") : (
74
- $apc ? "APC ".phpversion("apc") : (
75
- $zop ? "Zend OPCache ".phpversion("Zend OPcache") : "PHP OPCache ".phpversion("opcache")
76
- )
77
- )
78
- );
79
- }
80
- if (($gm = extension_loaded("gmagick")) || ($im = extension_loaded("imagick"))) {
81
- $server_configs["GMagick or IMagick"]["value"] = ($im ? "IMagick ".phpversion("imagick") : "GMagick ".phpversion("gmagick"));
82
- }
83
-
84
- ?>
85
-
86
- <h3 id="system">System Configuration</h3>
87
-
88
- <table cellspacing="1px" cellpadding="4px" style="background-color: white; padding: 5px; border: 1px solid #ccc;">
89
- <tr>
90
- <th align="left">&nbsp;</th>
91
- <th align="left">Required</th>
92
- <th align="left">Present</th>
93
- </tr>
94
-
95
- <?php foreach($server_configs as $label => $server_config) {
96
- if ($server_config["result"]) {
97
- $background = "#9e4";
98
- $color = "black";
99
- } elseif (isset($server_config["fallback"])) {
100
- $background = "#FCC612";
101
- $color = "black";
102
- } else {
103
- $background = "#f43";
104
- $color = "white";
105
- }
106
- ?>
107
- <tr>
108
- <td class="title"><?php echo $label; ?></td>
109
- <td><?php echo ($server_config["required"] === true ? "Yes" : $server_config["required"]); ?></td>
110
- <td style="background-color:<?php echo $background; ?>; color:<?php echo $color; ?>">
111
- <?php
112
- echo $server_config["value"];
113
- if ($server_config["result"] && !$server_config["value"]) echo "Yes";
114
- if (!$server_config["result"]) {
115
- if (isset($server_config["fallback"])) {
116
- echo "<div>No. ".$server_config["fallback"]."</div>";
117
- }
118
- if (isset($server_config["failure"])) {
119
- echo "<div>".$server_config["failure"]."</div>";
120
- }
121
- }
122
- ?>
123
- </td>
124
- </tr>
125
- <?php } ?>
126
-
127
- </table>
128
-
129
- <?php
130
- $permissions = array(
131
- 'WCPDF_TEMP_DIR' => array (
132
- 'description' => 'Central temporary plugin folder',
133
- 'value' => WPO_WCPDF()->main->get_tmp_path(),
134
- 'status' => (is_writable( WPO_WCPDF()->main->get_tmp_path() ) ? "ok" : "failed"),
135
- 'status_message' => (is_writable( WPO_WCPDF()->main->get_tmp_path() ) ? "Writable" : "Not writable"),
136
- ),
137
- 'WCPDF_ATTACHMENT_DIR' => array (
138
- 'description' => 'Temporary attachments folder',
139
- 'value' => trailingslashit( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ),
140
- 'status' => (is_writable( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ) ? "ok" : "failed"),
141
- 'status_message' => (is_writable( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ) ? "Writable" : "Not writable"),
142
- ),
143
- 'DOMPDF_TEMP_DIR' => array (
144
- 'description' => 'Temporary DOMPDF folder',
145
- 'value' => trailingslashit(WPO_WCPDF()->main->get_tmp_path( 'dompdf' )),
146
- 'status' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'dompdf' )) ? "ok" : "failed"),
147
- 'status_message' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'dompdf' )) ? "Writable" : "Not writable"),
148
- ),
149
- 'DOMPDF_FONT_DIR' => array (
150
- 'description' => 'DOMPDF fonts folder (needs to be writable for custom/remote fonts)',
151
- 'value' => trailingslashit(WPO_WCPDF()->main->get_tmp_path( 'fonts' )),
152
- 'status' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'fonts' )) ? "ok" : "failed"),
153
- 'status_message' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'fonts' )) ? "Writable" : "Not writable"),
154
- ),
155
- );
156
-
157
- $upload_dir = wp_upload_dir();
158
- $upload_base = trailingslashit( $upload_dir['basedir'] );
159
-
160
- ?>
161
- <br />
162
- <h3 id="system">Write Permissions</h3>
163
- <table cellspacing="1px" cellpadding="4px" style="background-color: white; padding: 5px; border: 1px solid #ccc;">
164
- <tr>
165
- <th align="left">Description</th>
166
- <th align="left">Value</th>
167
- <th align="left">Status</th>
168
- </tr>
169
- <?php
170
- foreach ($permissions as $permission) {
171
- if ($permission['status'] == 'ok') {
172
- $background = "#9e4";
173
- $color = "black";
174
- } else {
175
- $background = "#f43";
176
- $color = "white";
177
- }
178
- ?>
179
- <tr>
180
- <td><?php echo $permission['description']; ?></td>
181
- <td><?php echo $permission['value']; ?></td>
182
- <td style="background-color:<?php echo $background; ?>; color:<?php echo $color; ?>"><?php echo $permission['status_message']; ?></td>
183
- </tr>
184
-
185
- <?php } ?>
186
-
187
- </table>
188
-
189
- <p>
190
- The central temp folder is <code><?php echo WPO_WCPDF()->main->get_tmp_path(); ?></code>.
191
- By default, this folder is created in the WordPress uploads folder (<code><?php echo $upload_base; ?></code>),
192
- which can be defined by setting <code>UPLOADS</code> in wp-config.php.
193
- Alternatively, you can control the specific folder for PDF invoices by using the
194
- <code>wpo_wcpdf_tmp_path</code> filter. Make sure this folder is writable and that the
195
- subfolders <code>attachments</code>, <code>dompdf</code> and <code>fonts</code>
196
- are present (these will be created by the plugin if the central temp folder is writable).<br>
197
- <br>
198
- If the temporary folders were not automatically created by the plugin, verify that all the font
199
- files (from <code><?php echo WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/"; ?></code>)
200
- are copied to the fonts folder.
201
- Normally, this is fully automated, but if your server has strict security settings, this automated
202
- copying may have been prohibited. In that case, you also need to make sure these folders get
203
  synchronized on plugin updates!
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit; // Exit if accessed directly
4
+ }
5
+
6
+ $memory_limit = function_exists('wc_let_to_num')?wc_let_to_num( WP_MEMORY_LIMIT ):woocommerce_let_to_num( WP_MEMORY_LIMIT );
7
+ $php_mem_limit = function_exists( 'memory_get_usage' ) ? @ini_get( 'memory_limit' ) : '-';
8
+
9
+ $server_configs = array(
10
+ "DOMDocument extension" => array(
11
+ "required" => true,
12
+ "value" => phpversion("DOM"),
13
+ "result" => class_exists("DOMDocument"),
14
+ ),
15
+ "MBString extension" => array(
16
+ "required" => true,
17
+ "value" => phpversion("mbstring"),
18
+ "result" => function_exists("mb_send_mail"),
19
+ "fallback" => "Recommended, will use fallback functions",
20
+ ),
21
+ "GD" => array(
22
+ "required" => true,
23
+ "value" => phpversion("gd"),
24
+ "result" => function_exists("imagecreate"),
25
+ "fallback" => "Required if you have images in your documents",
26
+ ),
27
+ // "PCRE" => array(
28
+ // "required" => true,
29
+ // "value" => phpversion("pcre"),
30
+ // "result" => function_exists("preg_match") && @preg_match("/./u", "a"),
31
+ // "failure" => "PCRE is required with Unicode support (the \"u\" modifier)",
32
+ // ),
33
+ "Zlib" => array(
34
+ "required" => "To compress PDF documents",
35
+ "value" => phpversion("zlib"),
36
+ "result" => function_exists("gzcompress"),
37
+ "fallback" => "Recommended to compress PDF documents",
38
+ ),
39
+ "opcache" => array(
40
+ "required" => "For better performances",
41
+ "value" => null,
42
+ "result" => false,
43
+ "fallback" => "Recommended for better performances",
44
+ ),
45
+ "GMagick or IMagick" => array(
46
+ "required" => "Better with transparent PNG images",
47
+ "value" => null,
48
+ "result" => extension_loaded("gmagick") || extension_loaded("imagick"),
49
+ "fallback" => "Recommended for better performances",
50
+ ),
51
+ "glob()" => array(
52
+ "required" => "Required to detect custom templates and to clear the temp folder periodically",
53
+ "value" => null,
54
+ "result" => function_exists("glob"),
55
+ "fallback" => "Check php disable_functions",
56
+ ),
57
+ "WP Memory Limit" => array(
58
+ "required" => 'Recommended: 128MB (more for plugin-heavy setups)<br/>See: <a href="http://codex.wordpress.org/Editing_wp-config.php#Increasing_memory_allocated_to_PHP">Increasing memory allocated to PHP</a>',
59
+ "value" => sprintf("WordPress: %s, PHP: %s", WP_MEMORY_LIMIT, $php_mem_limit ),
60
+ "result" => $memory_limit > 67108864,
61
+ ),
62
+ 'allow_url_fopen' => array (
63
+ 'required' => 'Allow remote stylesheets and images',
64
+ 'value' => null,
65
+ 'result' => ini_get("allow_url_fopen"),
66
+ "fallback" => "allow_url_fopen disabled",
67
+ ),
68
+ );
69
+
70
+ if (($xc = extension_loaded("xcache")) || ($apc = extension_loaded("apc")) || ($zop = extension_loaded("Zend OPcache")) || ($op = extension_loaded("opcache"))) {
71
+ $server_configs["opcache"]["result"] = true;
72
+ $server_configs["opcache"]["value"] = (
73
+ $xc ? "XCache ".phpversion("xcache") : (
74
+ $apc ? "APC ".phpversion("apc") : (
75
+ $zop ? "Zend OPCache ".phpversion("Zend OPcache") : "PHP OPCache ".phpversion("opcache")
76
+ )
77
+ )
78
+ );
79
+ }
80
+ if (($gm = extension_loaded("gmagick")) || ($im = extension_loaded("imagick"))) {
81
+ $server_configs["GMagick or IMagick"]["value"] = ($im ? "IMagick ".phpversion("imagick") : "GMagick ".phpversion("gmagick"));
82
+ }
83
+
84
+ ?>
85
+
86
+ <h3 id="system">System Configuration</h3>
87
+
88
+ <table cellspacing="1px" cellpadding="4px" style="background-color: white; padding: 5px; border: 1px solid #ccc;">
89
+ <tr>
90
+ <th align="left">&nbsp;</th>
91
+ <th align="left">Required</th>
92
+ <th align="left">Present</th>
93
+ </tr>
94
+
95
+ <?php foreach($server_configs as $label => $server_config) {
96
+ if ($server_config["result"]) {
97
+ $background = "#9e4";
98
+ $color = "black";
99
+ } elseif (isset($server_config["fallback"])) {
100
+ $background = "#FCC612";
101
+ $color = "black";
102
+ } else {
103
+ $background = "#f43";
104
+ $color = "white";
105
+ }
106
+ ?>
107
+ <tr>
108
+ <td class="title"><?php echo $label; ?></td>
109
+ <td><?php echo ($server_config["required"] === true ? "Yes" : $server_config["required"]); ?></td>
110
+ <td style="background-color:<?php echo $background; ?>; color:<?php echo $color; ?>">
111
+ <?php
112
+ echo $server_config["value"];
113
+ if ($server_config["result"] && !$server_config["value"]) echo "Yes";
114
+ if (!$server_config["result"]) {
115
+ if (isset($server_config["fallback"])) {
116
+ echo "<div>No. ".$server_config["fallback"]."</div>";
117
+ }
118
+ if (isset($server_config["failure"])) {
119
+ echo "<div>".$server_config["failure"]."</div>";
120
+ }
121
+ }
122
+ ?>
123
+ </td>
124
+ </tr>
125
+ <?php } ?>
126
+
127
+ </table>
128
+
129
+ <?php
130
+ $permissions = array(
131
+ 'WCPDF_TEMP_DIR' => array (
132
+ 'description' => 'Central temporary plugin folder',
133
+ 'value' => WPO_WCPDF()->main->get_tmp_path(),
134
+ 'status' => (is_writable( WPO_WCPDF()->main->get_tmp_path() ) ? "ok" : "failed"),
135
+ 'status_message' => (is_writable( WPO_WCPDF()->main->get_tmp_path() ) ? "Writable" : "Not writable"),
136
+ ),
137
+ 'WCPDF_ATTACHMENT_DIR' => array (
138
+ 'description' => 'Temporary attachments folder',
139
+ 'value' => trailingslashit( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ),
140
+ 'status' => (is_writable( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ) ? "ok" : "failed"),
141
+ 'status_message' => (is_writable( WPO_WCPDF()->main->get_tmp_path( 'attachments' ) ) ? "Writable" : "Not writable"),
142
+ ),
143
+ 'DOMPDF_TEMP_DIR' => array (
144
+ 'description' => 'Temporary DOMPDF folder',
145
+ 'value' => trailingslashit(WPO_WCPDF()->main->get_tmp_path( 'dompdf' )),
146
+ 'status' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'dompdf' )) ? "ok" : "failed"),
147
+ 'status_message' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'dompdf' )) ? "Writable" : "Not writable"),
148
+ ),
149
+ 'DOMPDF_FONT_DIR' => array (
150
+ 'description' => 'DOMPDF fonts folder (needs to be writable for custom/remote fonts)',
151
+ 'value' => trailingslashit(WPO_WCPDF()->main->get_tmp_path( 'fonts' )),
152
+ 'status' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'fonts' )) ? "ok" : "failed"),
153
+ 'status_message' => (is_writable(WPO_WCPDF()->main->get_tmp_path( 'fonts' )) ? "Writable" : "Not writable"),
154
+ ),
155
+ );
156
+
157
+ $upload_dir = wp_upload_dir();
158
+ $upload_base = trailingslashit( $upload_dir['basedir'] );
159
+
160
+ ?>
161
+ <br />
162
+ <h3 id="system">Write Permissions</h3>
163
+ <table cellspacing="1px" cellpadding="4px" style="background-color: white; padding: 5px; border: 1px solid #ccc;">
164
+ <tr>
165
+ <th align="left">Description</th>
166
+ <th align="left">Value</th>
167
+ <th align="left">Status</th>
168
+ </tr>
169
+ <?php
170
+ foreach ($permissions as $permission) {
171
+ if ($permission['status'] == 'ok') {
172
+ $background = "#9e4";
173
+ $color = "black";
174
+ } else {
175
+ $background = "#f43";
176
+ $color = "white";
177
+ }
178
+ ?>
179
+ <tr>
180
+ <td><?php echo $permission['description']; ?></td>
181
+ <td><?php echo $permission['value']; ?></td>
182
+ <td style="background-color:<?php echo $background; ?>; color:<?php echo $color; ?>"><?php echo $permission['status_message']; ?></td>
183
+ </tr>
184
+
185
+ <?php } ?>
186
+
187
+ </table>
188
+
189
+ <p>
190
+ The central temp folder is <code><?php echo WPO_WCPDF()->main->get_tmp_path(); ?></code>.
191
+ By default, this folder is created in the WordPress uploads folder (<code><?php echo $upload_base; ?></code>),
192
+ which can be defined by setting <code>UPLOADS</code> in wp-config.php.
193
+ Alternatively, you can control the specific folder for PDF invoices by using the
194
+ <code>wpo_wcpdf_tmp_path</code> filter. Make sure this folder is writable and that the
195
+ subfolders <code>attachments</code>, <code>dompdf</code> and <code>fonts</code>
196
+ are present (these will be created by the plugin if the central temp folder is writable).<br>
197
+ <br>
198
+ If the temporary folders were not automatically created by the plugin, verify that all the font
199
+ files (from <code><?php echo WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/"; ?></code>)
200
+ are copied to the fonts folder.
201
+ Normally, this is fully automated, but if your server has strict security settings, this automated
202
+ copying may have been prohibited. In that case, you also need to make sure these folders get
203
  synchronized on plugin updates!
includes/views/setup-wizard/attach-to.php CHANGED
@@ -1,23 +1,23 @@
1
- <div class="wpo-step-description">
2
- <h2><?php _e( 'Attach too...', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
- <p><?php _e( 'Select to which emails you would like to attach your invoice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
- </div>
5
- <div class="wpo-setup-input">
6
- <?php
7
- $current_settings = get_option( 'wpo_wcpdf_documents_settings_invoice', array() );
8
- // echo "<pre>".var_dump($current_settings)."</pre>";
9
- // load invoice to reuse method to get wc emails
10
- $invoice = wcpdf_get_invoice( null );
11
- $wc_emails = $invoice->get_wc_emails();
12
- foreach ($wc_emails as $email_id => $name) {
13
- if (!empty($current_settings['attach_to_email_ids'][$email_id])) {
14
- $checked = 'checked';
15
- } else {
16
- $checked = '';
17
- }
18
- printf('<input type="hidden" value="" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][attach_to_email_ids][%1$s]">
19
- <input type="checkbox" %3$s name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][attach_to_email_ids][%1$s]" value="1">
20
- <span class="checkbox">%2$s</span><br>', $email_id, $name, $checked);
21
- }
22
- ?>
23
- </div>
1
+ <div class="wpo-step-description">
2
+ <h2><?php _e( 'Attach too...', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
+ <p><?php _e( 'Select to which emails you would like to attach your invoice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
+ </div>
5
+ <div class="wpo-setup-input">
6
+ <?php
7
+ $current_settings = get_option( 'wpo_wcpdf_documents_settings_invoice', array() );
8
+ // echo "<pre>".var_dump($current_settings)."</pre>";
9
+ // load invoice to reuse method to get wc emails
10
+ $invoice = wcpdf_get_invoice( null );
11
+ $wc_emails = $invoice->get_wc_emails();
12
+ foreach ($wc_emails as $email_id => $name) {
13
+ if (!empty($current_settings['attach_to_email_ids'][$email_id])) {
14
+ $checked = 'checked';
15
+ } else {
16
+ $checked = '';
17
+ }
18
+ printf('<input type="hidden" value="" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][attach_to_email_ids][%1$s]">
19
+ <input type="checkbox" %3$s name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][attach_to_email_ids][%1$s]" value="1">
20
+ <span class="checkbox">%2$s</span><br>', $email_id, $name, $checked);
21
+ }
22
+ ?>
23
+ </div>
includes/views/setup-wizard/display-options.php CHANGED
@@ -1,19 +1,19 @@
1
- <div class="wpo-step-description">
2
- <h2><?php _e( 'Display options', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
- <p><?php _e( 'Select some additional display options for your invoice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
- </div>
5
- <div class="wpo-setup-input">
6
- <?php
7
- $current_settings = get_option( 'wpo_wcpdf_documents_settings_invoice', array() );
8
- ?>
9
- <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_shipping_address]" value="">
10
- <input type="checkbox" <?php echo !empty($current_settings['display_shipping_address']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_shipping_address]" value="1"><span class="checkbox"><?php _e( 'Display shipping address', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
11
- <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_email]" value="">
12
- <input type="checkbox" <?php echo !empty($current_settings['display_email']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_email]" value="1"><span class="checkbox"><?php _e( 'Display email address', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
13
- <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_phone]" value="">
14
- <input type="checkbox" <?php echo !empty($current_settings['display_phone']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_phone]" value="1"><span class="checkbox"><?php _e( 'Display phone number', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
15
- <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_date]" value="">
16
- <input type="checkbox" <?php echo !empty($current_settings['display_date']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_date]" value="invoice_date"><span class="checkbox"><?php _e( 'Display invoice date', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
17
- <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_number]" value="">
18
- <input type="checkbox" <?php echo !empty($current_settings['display_number']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_number]" value="invoice_number"><span class="checkbox"><?php _e( 'Display invoice number', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
19
  </div>
1
+ <div class="wpo-step-description">
2
+ <h2><?php _e( 'Display options', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
+ <p><?php _e( 'Select some additional display options for your invoice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
+ </div>
5
+ <div class="wpo-setup-input">
6
+ <?php
7
+ $current_settings = get_option( 'wpo_wcpdf_documents_settings_invoice', array() );
8
+ ?>
9
+ <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_shipping_address]" value="">
10
+ <input type="checkbox" <?php echo !empty($current_settings['display_shipping_address']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_shipping_address]" value="1"><span class="checkbox"><?php _e( 'Display shipping address', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
11
+ <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_email]" value="">
12
+ <input type="checkbox" <?php echo !empty($current_settings['display_email']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_email]" value="1"><span class="checkbox"><?php _e( 'Display email address', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
13
+ <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_phone]" value="">
14
+ <input type="checkbox" <?php echo !empty($current_settings['display_phone']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_phone]" value="1"><span class="checkbox"><?php _e( 'Display phone number', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
15
+ <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_date]" value="">
16
+ <input type="checkbox" <?php echo !empty($current_settings['display_date']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_date]" value="invoice_date"><span class="checkbox"><?php _e( 'Display invoice date', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
17
+ <input type="hidden" name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_number]" value="">
18
+ <input type="checkbox" <?php echo !empty($current_settings['display_number']) ? 'checked' : ''; ?> name="wcpdf_settings[wpo_wcpdf_documents_settings_invoice][display_number]" value="invoice_number"><span class="checkbox"><?php _e( 'Display invoice number', 'woocommerce-pdf-invoices-packing-slips' ); ?></span><br>
19
  </div>
includes/views/setup-wizard/good-to-go.php CHANGED
@@ -1,6 +1,6 @@
1
- <div class="wpo-step-description wpo-final">
2
- <h1><?php _e( 'You are good to go!' , 'woocommerce-pdf-invoices-packing-slips' ); ?></h1>
3
- <p><?php _e( 'If you have any questions please have a look at our documentation:', 'woocommerce-pdf-invoices-packing-slips' ); ?><br>
4
- <a href="https://docs.wpovernight.com/category/woocommerce-pdf-invoices-packing-slips/" target="_blank"><?php _e( 'Invoices & Packing Slips' , 'woocommerce-pdf-invoices-packing-slips' ); ?></a></p>
5
- <h2><?php _e( 'Happy selling!' , 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
6
  </div>
1
+ <div class="wpo-step-description wpo-final">
2
+ <h1><?php _e( 'You are good to go!' , 'woocommerce-pdf-invoices-packing-slips' ); ?></h1>
3
+ <p><?php _e( 'If you have any questions please have a look at our documentation:', 'woocommerce-pdf-invoices-packing-slips' ); ?><br>
4
+ <a href="https://docs.wpovernight.com/category/woocommerce-pdf-invoices-packing-slips/" target="_blank"><?php _e( 'Invoices & Packing Slips' , 'woocommerce-pdf-invoices-packing-slips' ); ?></a></p>
5
+ <h2><?php _e( 'Happy selling!' , 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
6
  </div>
includes/views/setup-wizard/logo.php CHANGED
@@ -1,9 +1,9 @@
1
- <div class="wpo-step-description">
2
- <h2><?php _e( 'Your logo' , 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
- <p><?php _e( 'Set the header image that will display on your invoice.' , 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
- </div>
5
- <div class="wpo-setup-input">
6
- <img src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" width="100%" height="20px" alt="" id="img-header_logo"/>
7
- <input id="header_logo" name="wcpdf_settings[wpo_wcpdf_settings_general][header_logo]" type="hidden" value="" />
8
- <span class="button wpo_upload_image_button header_logo" data-uploader_title="<?php _e( 'Select or upload your invoice header/logo', 'woocommerce-pdf-invoices-packing-slips' ); ?>" data-uploader_button_text="<?php _e( 'Set image', 'woocommerce-pdf-invoices-packing-slips' ); ?>" data-remove_button_text="<?php _e( 'Remove image', 'woocommerce-pdf-invoices-packing-slips' ); ?>" data-input_id="header_logo"><?php _e( 'Set image', 'woocommerce-pdf-invoices-packing-slips' ); ?></span>
9
  </div>
1
+ <div class="wpo-step-description">
2
+ <h2><?php _e( 'Your logo' , 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
+ <p><?php _e( 'Set the header image that will display on your invoice.' , 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
+ </div>
5
+ <div class="wpo-setup-input">
6
+ <img src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" width="100%" height="20px" alt="" id="img-header_logo"/>
7
+ <input id="header_logo" name="wcpdf_settings[wpo_wcpdf_settings_general][header_logo]" type="hidden" value="" />
8
+ <span class="button wpo_upload_image_button header_logo" data-uploader_title="<?php _e( 'Select or upload your invoice header/logo', 'woocommerce-pdf-invoices-packing-slips' ); ?>" data-uploader_button_text="<?php _e( 'Set image', 'woocommerce-pdf-invoices-packing-slips' ); ?>" data-remove_button_text="<?php _e( 'Remove image', 'woocommerce-pdf-invoices-packing-slips' ); ?>" data-input_id="header_logo"><?php _e( 'Set image', 'woocommerce-pdf-invoices-packing-slips' ); ?></span>
9
  </div>
includes/views/setup-wizard/paper-format.php CHANGED
@@ -1,14 +1,14 @@
1
- <div class="wpo-step-description">
2
- <h2><?php _e( 'Paper format', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
- <p><?php _e( 'Select the paper format for your invoice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
- </div>
5
- <div class="wpo-setup-input">
6
- <?php
7
- $current_settings = get_option( 'wpo_wcpdf_settings_general', array() );
8
- ?>
9
-
10
- <select name="wcpdf_settings[wpo_wcpdf_settings_general][paper_size]">
11
- <option <?php echo $current_settings['paper_size'] == 'a4' ? 'selected' : ''; ?> value="a4"><?php _e( 'A4', 'woocommerce-pdf-invoices-packing-slips' ); ?></option>
12
- <option <?php echo $current_settings['paper_size'] == 'letter' ? 'selected' : ''; ?> value="letter"><?php _e( 'Letter', 'woocommerce-pdf-invoices-packing-slips' ); ?></option>
13
- </select>
14
  </div>
1
+ <div class="wpo-step-description">
2
+ <h2><?php _e( 'Paper format', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
+ <p><?php _e( 'Select the paper format for your invoice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
+ </div>
5
+ <div class="wpo-setup-input">
6
+ <?php
7
+ $current_settings = get_option( 'wpo_wcpdf_settings_general', array() );
8
+ ?>
9
+
10
+ <select name="wcpdf_settings[wpo_wcpdf_settings_general][paper_size]">
11
+ <option <?php echo $current_settings['paper_size'] == 'a4' ? 'selected' : ''; ?> value="a4"><?php _e( 'A4', 'woocommerce-pdf-invoices-packing-slips' ); ?></option>
12
+ <option <?php echo $current_settings['paper_size'] == 'letter' ? 'selected' : ''; ?> value="letter"><?php _e( 'Letter', 'woocommerce-pdf-invoices-packing-slips' ); ?></option>
13
+ </select>
14
  </div>
includes/views/setup-wizard/shop-name.php CHANGED
@@ -1,11 +1,11 @@
1
- <div class="wpo-step-description">
2
- <h2><?php _e( 'Enter your shop name', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
- <p><?php _e( 'Lets quickly setup your invoice. Please enter the name and address of your shop in the fields on the right.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
- </div>
5
- <div class="wpo-setup-input">
6
- <?php
7
- $current_settings = get_option( 'wpo_wcpdf_settings_general', array() );
8
- ?>
9
- <input type="text" class="shop-name" placeholder="Shop name" name="wcpdf_settings[wpo_wcpdf_settings_general][shop_name][default]" value="<?php echo !empty($current_settings['shop_name']) ? array_pop($current_settings['shop_name']) : get_bloginfo( 'name' ); ?>">
10
- <textarea class="shop-address" placeholder="Shop address" name="wcpdf_settings[wpo_wcpdf_settings_general][shop_address][default]"><?php echo isset($current_settings['shop_address']) ? array_pop($current_settings['shop_address']) : ''; ?></textarea>
11
  </div>
1
+ <div class="wpo-step-description">
2
+ <h2><?php _e( 'Enter your shop name', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
3
+ <p><?php _e( 'Lets quickly setup your invoice. Please enter the name and address of your shop in the fields on the right.', 'woocommerce-pdf-invoices-packing-slips' ); ?></p>
4
+ </div>
5
+ <div class="wpo-setup-input">
6
+ <?php
7
+ $current_settings = get_option( 'wpo_wcpdf_settings_general', array() );
8
+ ?>
9
+ <input type="text" class="shop-name" placeholder="Shop name" name="wcpdf_settings[wpo_wcpdf_settings_general][shop_name][default]" value="<?php echo !empty($current_settings['shop_name']) ? array_pop($current_settings['shop_name']) : get_bloginfo( 'name' ); ?>">
10
+ <textarea class="shop-address" placeholder="Shop address" name="wcpdf_settings[wpo_wcpdf_settings_general][shop_address][default]"><?php echo isset($current_settings['shop_address']) ? array_pop($current_settings['shop_address']) : ''; ?></textarea>
11
  </div>
includes/views/wcpdf-extensions.php CHANGED
@@ -1,131 +1,131 @@
1
- <script type="text/javascript">
2
- jQuery(document).ready(function() {
3
- jQuery('.extensions .more').hide();
4
-
5
- jQuery('.extensions > li').click(function() {
6
- jQuery(this).toggleClass('expanded');
7
- jQuery(this).find('.more').slideToggle();
8
- });
9
- });
10
- </script>
11
-
12
- <div class="wcpdf-extensions-ad">
13
- <?php $no_pro = !class_exists('WooCommerce_PDF_IPS_Pro') && !class_exists('WooCommerce_PDF_IPS_Dropbox') && !class_exists('WooCommerce_PDF_IPS_Templates'); ?>
14
- <img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/wpo-helper.png'; ?>" class="wpo-helper">
15
- <h3><?php _e( 'Check out these premium extensions!', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
16
- <i>(<?php _e( 'click items to read more', 'woocommerce-pdf-invoices-packing-slips' ); ?>)</i>
17
- <ul class="extensions">
18
- <?php if ( $no_pro ): ?>
19
- <!-- No Pro extensions: Ad for PDF bundle -->
20
- <li>
21
- <?php _e('Premium PDF Invoice bundle: Everything you need for a perfect invoicing system', 'woocommerce-pdf-invoices-packing-slips' )?>
22
- <div class="more" style="display:none;">
23
- <h4><?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the all our premium extensions:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h4>
24
- <?php _e( 'Professional features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
25
- <ul>
26
- <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
27
- <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
28
- <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
29
- <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
30
- <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
31
- <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
32
- </ul>
33
- <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
34
- <ul>
35
- <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
36
- <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
37
- </ul>
38
- <?php _e('Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?>
39
- <ul>
40
- <li><?php _e( 'This extension conveniently uploads all the invoices (and other pdf documents from the professional extension) that are emailed to your customers to Dropbox. The best way to keep your invoice administration up to date!', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
41
- </ul>
42
- <br>
43
- <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Bundle", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
44
- </div>
45
- </li>
46
- <?php endif; ?>
47
- <?php
48
- // NO BUNDLE: separate ads
49
- if (!class_exists('WooCommerce_PDF_IPS_Pro') && !$no_pro) {
50
- ?>
51
- <li>
52
- <?php _e('Go Pro: Proforma invoices, credit notes (=refunds) & more!', 'woocommerce-pdf-invoices-packing-slips' )?>
53
- <div class="more" style="display:none;">
54
- <?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the following features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
55
- <ul>
56
- <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
57
- <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
58
- <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
59
- <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
60
- <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
61
- <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
62
- <li><?php _e( 'Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?></li>
63
- </ul>
64
- <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Professional!", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
65
- </li>
66
- <?php } ?>
67
-
68
- <?php
69
- if (!class_exists('WPO_WC_Smart_Reminder_Emails')) {
70
- ?>
71
- <li>
72
- <?php _e('Automatically send payment reminders to your customers', 'woocommerce-pdf-invoices-packing-slips' )?>
73
- <div class="more" style="display:none;">
74
- <?php _e('WooCommerce Smart Reminder emails', 'woocommerce-pdf-invoices-packing-slips' )?>
75
- <ul>
76
- <li><?php _e( '<b>Completely automatic</b> scheduled emails', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
77
- <li><?php _e( '<b>Rich text editor</b> for the email text, including placeholders for data from the order (name, order total, etc)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
78
- <li><?php _e( 'Configure the exact requirements for sending an email (time after order, order status, payment method)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
79
- <li><?php _e( 'Fully <b>WPML Compatible</b> – emails will be automatically sent in the order language.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
80
- <li><?php _e( '<b>Super versatile!</b> Can be used for any kind of reminder email (review reminders, repeat purchases)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
81
- <li><b><?php _e( 'Integrates seamlessly with the PDF Invoices & Packing Slips plugin', 'woocommerce-pdf-invoices-packing-slips' ); ?></b></li>
82
- </ul>
83
- <a href="https://wpovernight.com/downloads/woocommerce-reminder-emails-payment-reminders/" target="_blank"><?php _e("Get WooCommerce Smart Reminder Emails", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
84
- </div>
85
- </li>
86
- <?php } ?>
87
-
88
- <?php
89
- if (!class_exists('WooCommerce_Ext_PrintOrders')) {
90
- ?>
91
- <li>
92
- <?php _e('Automatically send new orders or packing slips to your printer, as soon as the customer orders!', 'woocommerce-pdf-invoices-packing-slips' )?>
93
- <div class="more" style="display:none;">
94
- <table>
95
- <tr>
96
- <td><img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/cloud-print.png'; ?>" class="cloud-logo"></td>
97
- <td>
98
- <?php _e( 'Check out the WooCommerce Automatic Order Printing extension from our partners at Simba Hosting', 'woocommerce-pdf-invoices-packing-slips' ); ?><br/>
99
- <a href="https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2" target="_blank"><?php _e("WooCommerce Automatic Order Printing", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
100
- </td>
101
- </tr>
102
- </table>
103
- </div>
104
- </li>
105
- <?php } ?>
106
-
107
- <?php
108
- if (!class_exists('WooCommerce_PDF_IPS_Templates') && !class_exists('WPO_WCPDF_Templates') && !$no_pro) {
109
- $template_link = '<a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/" target="_blank">wpovernight.com</a>';
110
- $email_link = '<a href="mailto:support@wpovernight.com">support@wpovernight.com</a>'
111
- ?>
112
- <li>
113
- <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
114
- <div class="more" style="display:none;">
115
- <ul>
116
- <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
117
- <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
118
- <li><?php printf( __("Check out the Premium PDF Invoice & Packing Slips templates at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $template_link );?></li>
119
- <li><?php printf( __("For custom templates, contact us at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $email_link );?></li>
120
- </ul>
121
- </div>
122
- </li>
123
- <?php } ?>
124
- </ul>
125
- <?php
126
- // link to hide message when one of the premium extensions is installed
127
- if ( class_exists('WooCommerce_PDF_IPS_Pro') || class_exists('WooCommerce_PDF_IPS_Dropbox') || class_exists('WooCommerce_PDF_IPS_Templates') || class_exists('WooCommerce_Ext_PrintOrders') || class_exists('WPO_WC_Smart_Reminder_Emails') ) {
128
- printf('<a href="%s" style="display:inline-block; margin-top: 10px;">%s</a>', add_query_arg( 'wpo_wcpdf_hide_extensions_ad', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) );
129
- }
130
- ?>
131
  </div>
1
+ <script type="text/javascript">
2
+ jQuery(document).ready(function() {
3
+ jQuery('.extensions .more').hide();
4
+
5
+ jQuery('.extensions > li').click(function() {
6
+ jQuery(this).toggleClass('expanded');
7
+ jQuery(this).find('.more').slideToggle();
8
+ });
9
+ });
10
+ </script>
11
+
12
+ <div class="wcpdf-extensions-ad">
13
+ <?php $no_pro = !class_exists('WooCommerce_PDF_IPS_Pro') && !class_exists('WooCommerce_PDF_IPS_Dropbox') && !class_exists('WooCommerce_PDF_IPS_Templates'); ?>
14
+ <img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/wpo-helper.png'; ?>" class="wpo-helper">
15
+ <h3><?php _e( 'Check out these premium extensions!', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
16
+ <i>(<?php _e( 'click items to read more', 'woocommerce-pdf-invoices-packing-slips' ); ?>)</i>
17
+ <ul class="extensions">
18
+ <?php if ( $no_pro ): ?>
19
+ <!-- No Pro extensions: Ad for PDF bundle -->
20
+ <li>
21
+ <?php _e('Premium PDF Invoice bundle: Everything you need for a perfect invoicing system', 'woocommerce-pdf-invoices-packing-slips' )?>
22
+ <div class="more" style="display:none;">
23
+ <h4><?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the all our premium extensions:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h4>
24
+ <?php _e( 'Professional features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
25
+ <ul>
26
+ <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
27
+ <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
28
+ <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
29
+ <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
30
+ <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
31
+ <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
32
+ </ul>
33
+ <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
34
+ <ul>
35
+ <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
36
+ <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
37
+ </ul>
38
+ <?php _e('Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?>
39
+ <ul>
40
+ <li><?php _e( 'This extension conveniently uploads all the invoices (and other pdf documents from the professional extension) that are emailed to your customers to Dropbox. The best way to keep your invoice administration up to date!', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
41
+ </ul>
42
+ <br>
43
+ <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Bundle", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
44
+ </div>
45
+ </li>
46
+ <?php endif; ?>
47
+ <?php
48
+ // NO BUNDLE: separate ads
49
+ if (!class_exists('WooCommerce_PDF_IPS_Pro') && !$no_pro) {
50
+ ?>
51
+ <li>
52
+ <?php _e('Go Pro: Proforma invoices, credit notes (=refunds) & more!', 'woocommerce-pdf-invoices-packing-slips' )?>
53
+ <div class="more" style="display:none;">
54
+ <?php _e( 'Supercharge WooCommerce PDF Invoices & Packing Slips with the following features:', 'woocommerce-pdf-invoices-packing-slips' ); ?>
55
+ <ul>
56
+ <li><?php _e( 'Email/print/download <b>PDF Credit Notes & Proforma invoices</b>', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
57
+ <li><?php _e( 'Send out a separate <b>notification email</b> with (or without) PDF invoices/packing slips, for example to a drop-shipper or a supplier.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
58
+ <li><?php _e( 'Attach <b>up to 3 static files</b> (for example a terms & conditions document) to the WooCommerce emails of your choice.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
59
+ <li><?php _e( 'Use <b>separate numbering systems</b> and/or format for proforma invoices and credit notes or utilize the main invoice numbering system', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
60
+ <li><?php _e( '<b>Customize</b> the <b>shipping & billing address</b> format to include additional custom fields, font sizes etc. without the need to create a custom template.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
61
+ <li><?php _e( 'Use the plugin in multilingual <b>WPML</b> setups', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
62
+ <li><?php _e( 'Upload automatically to dropbox', 'woocommerce-pdf-invoices-packing-slips' )?></li>
63
+ </ul>
64
+ <a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/" target="_blank"><?php _e("Get WooCommerce PDF Invoices & Packing Slips Professional!", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
65
+ </li>
66
+ <?php } ?>
67
+
68
+ <?php
69
+ if (!class_exists('WPO_WC_Smart_Reminder_Emails')) {
70
+ ?>
71
+ <li>
72
+ <?php _e('Automatically send payment reminders to your customers', 'woocommerce-pdf-invoices-packing-slips' )?>
73
+ <div class="more" style="display:none;">
74
+ <?php _e('WooCommerce Smart Reminder emails', 'woocommerce-pdf-invoices-packing-slips' )?>
75
+ <ul>
76
+ <li><?php _e( '<b>Completely automatic</b> scheduled emails', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
77
+ <li><?php _e( '<b>Rich text editor</b> for the email text, including placeholders for data from the order (name, order total, etc)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
78
+ <li><?php _e( 'Configure the exact requirements for sending an email (time after order, order status, payment method)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
79
+ <li><?php _e( 'Fully <b>WPML Compatible</b> – emails will be automatically sent in the order language.', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
80
+ <li><?php _e( '<b>Super versatile!</b> Can be used for any kind of reminder email (review reminders, repeat purchases)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
81
+ <li><b><?php _e( 'Integrates seamlessly with the PDF Invoices & Packing Slips plugin', 'woocommerce-pdf-invoices-packing-slips' ); ?></b></li>
82
+ </ul>
83
+ <a href="https://wpovernight.com/downloads/woocommerce-reminder-emails-payment-reminders/" target="_blank"><?php _e("Get WooCommerce Smart Reminder Emails", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
84
+ </div>
85
+ </li>
86
+ <?php } ?>
87
+
88
+ <?php
89
+ if (!class_exists('WooCommerce_Ext_PrintOrders')) {
90
+ ?>
91
+ <li>
92
+ <?php _e('Automatically send new orders or packing slips to your printer, as soon as the customer orders!', 'woocommerce-pdf-invoices-packing-slips' )?>
93
+ <div class="more" style="display:none;">
94
+ <table>
95
+ <tr>
96
+ <td><img src="<?php echo WPO_WCPDF()->plugin_url() . '/assets/images/cloud-print.png'; ?>" class="cloud-logo"></td>
97
+ <td>
98
+ <?php _e( 'Check out the WooCommerce Automatic Order Printing extension from our partners at Simba Hosting', 'woocommerce-pdf-invoices-packing-slips' ); ?><br/>
99
+ <a href="https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2" target="_blank"><?php _e("WooCommerce Automatic Order Printing", 'woocommerce-pdf-invoices-packing-slips' ); ?></a>
100
+ </td>
101
+ </tr>
102
+ </table>
103
+ </div>
104
+ </li>
105
+ <?php } ?>
106
+
107
+ <?php
108
+ if (!class_exists('WooCommerce_PDF_IPS_Templates') && !class_exists('WPO_WCPDF_Templates') && !$no_pro) {
109
+ $template_link = '<a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/" target="_blank">wpovernight.com</a>';
110
+ $email_link = '<a href="mailto:support@wpovernight.com">support@wpovernight.com</a>'
111
+ ?>
112
+ <li>
113
+ <?php _e('Advanced, customizable templates', 'woocommerce-pdf-invoices-packing-slips' )?>
114
+ <div class="more" style="display:none;">
115
+ <ul>
116
+ <li><?php _e( 'Completely customize the invoice contents (prices, taxes, thumbnails) to your needs with a drag & drop customizer', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
117
+ <li><?php _e( 'Two extra stylish premade templates (Modern & Business)', 'woocommerce-pdf-invoices-packing-slips' ); ?></li>
118
+ <li><?php printf( __("Check out the Premium PDF Invoice & Packing Slips templates at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $template_link );?></li>
119
+ <li><?php printf( __("For custom templates, contact us at %s.", 'woocommerce-pdf-invoices-packing-slips' ), $email_link );?></li>
120
+ </ul>
121
+ </div>
122
+ </li>
123
+ <?php } ?>
124
+ </ul>
125
+ <?php
126
+ // link to hide message when one of the premium extensions is installed
127
+ if ( class_exists('WooCommerce_PDF_IPS_Pro') || class_exists('WooCommerce_PDF_IPS_Dropbox') || class_exists('WPO_WCPDF_Templates') || class_exists('WooCommerce_PDF_IPS_Templates') || class_exists('WooCommerce_Ext_PrintOrders') || class_exists('WPO_WC_Smart_Reminder_Emails') ) {
128
+ printf('<a href="%s" style="display:inline-block; margin-top: 10px;">%s</a>', add_query_arg( 'wpo_wcpdf_hide_extensions_ad', 'true' ), __( 'Hide this message', 'woocommerce-pdf-invoices-packing-slips' ) );
129
+ }
130
+ ?>
131
  </div>
includes/views/wcpdf-settings-page.php CHANGED
@@ -1,51 +1,51 @@
1
- <script type="text/javascript">
2
- jQuery( function( $ ) {
3
- $("#footer-thankyou").html("If you like <strong>WooCommerce PDF Invoices & Packing Slips</strong> please leave us a <a href='https://wordpress.org/support/view/plugin-reviews/woocommerce-pdf-invoices-packing-slips?rate=5#postform'>★★★★★</a> rating. A huge thank you in advance!");
4
- });
5
- </script>
6
- <div class="wrap">
7
- <div class="icon32" id="icon-options-general"><br /></div>
8
- <h2><?php _e( 'WooCommerce PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
9
- <h2 class="nav-tab-wrapper">
10
- <?php
11
- foreach ($settings_tabs as $tab_slug => $tab_title ) {
12
- $tab_link = esc_url("?page=wpo_wcpdf_options_page&tab={$tab_slug}");
13
- printf('<a href="%1$s" class="nav-tab nav-tab-%2$s %3$s">%4$s</a>', $tab_link, $tab_slug, (($active_tab == $tab_slug) ? 'nav-tab-active' : ''), $tab_title);
14
- }
15
- ?>
16
- </h2>
17
-
18
- <?php
19
- do_action( 'wpo_wcpdf_before_settings_page', $active_tab, $active_section );
20
-
21
- // save or check option to hide extensions ad
22
- if ( isset( $_GET['wpo_wcpdf_hide_extensions_ad'] ) ) {
23
- update_option( 'wpo_wcpdf_hide_extensions_ad', true );
24
- $hide_ad = true;
25
- } else {
26
- $hide_ad = get_option( 'wpo_wcpdf_hide_extensions_ad' );
27
- }
28
-
29
- if ( !$hide_ad && !( class_exists('WooCommerce_PDF_IPS_Pro') && class_exists('WooCommerce_PDF_IPS_Dropbox') && class_exists('WooCommerce_PDF_IPS_Templates') && class_exists('WooCommerce_Ext_PrintOrders') ) ) {
30
- include('wcpdf-extensions.php');
31
- }
32
-
33
- ?>
34
- <form method="post" action="options.php" id="wpo-wcpdf-settings" class="<?php echo "{$active_tab} {$active_section}"; ?>">
35
- <?php
36
- do_action( 'wpo_wcpdf_before_settings', $active_tab, $active_section );
37
- if ( has_action( 'wpo_wcpdf_settings_output_'.$active_tab ) ) {
38
- do_action( 'wpo_wcpdf_settings_output_'.$active_tab, $active_section );
39
- } else {
40
- // legacy settings
41
- settings_fields( "wpo_wcpdf_{$active_tab}_settings" );
42
- do_settings_sections( "wpo_wcpdf_{$active_tab}_settings" );
43
-
44
- submit_button();
45
- }
46
- do_action( 'wpo_wcpdf_after_settings', $active_tab, $active_section );
47
- ?>
48
-
49
- </form>
50
- <?php do_action( 'wpo_wcpdf_after_settings_page', $active_tab, $active_section ); ?>
51
- </div>
1
+ <script type="text/javascript">
2
+ jQuery( function( $ ) {
3
+ $("#footer-thankyou").html("If you like <strong>WooCommerce PDF Invoices & Packing Slips</strong> please leave us a <a href='https://wordpress.org/support/view/plugin-reviews/woocommerce-pdf-invoices-packing-slips?rate=5#postform'>★★★★★</a> rating. A huge thank you in advance!");
4
+ });
5
+ </script>
6
+ <div class="wrap">
7
+ <div class="icon32" id="icon-options-general"><br /></div>
8
+ <h2><?php _e( 'WooCommerce PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ); ?></h2>
9
+ <h2 class="nav-tab-wrapper">
10
+ <?php
11
+ foreach ($settings_tabs as $tab_slug => $tab_title ) {
12
+ $tab_link = esc_url("?page=wpo_wcpdf_options_page&tab={$tab_slug}");
13
+ printf('<a href="%1$s" class="nav-tab nav-tab-%2$s %3$s">%4$s</a>', $tab_link, $tab_slug, (($active_tab == $tab_slug) ? 'nav-tab-active' : ''), $tab_title);
14
+ }
15
+ ?>
16
+ </h2>
17
+
18
+ <?php
19
+ do_action( 'wpo_wcpdf_before_settings_page', $active_tab, $active_section );
20
+
21
+ // save or check option to hide extensions ad
22
+ if ( isset( $_GET['wpo_wcpdf_hide_extensions_ad'] ) ) {
23
+ update_option( 'wpo_wcpdf_hide_extensions_ad', true );
24
+ $hide_ad = true;
25
+ } else {
26
+ $hide_ad = get_option( 'wpo_wcpdf_hide_extensions_ad' );
27
+ }
28
+
29
+ if ( !$hide_ad && !( class_exists('WooCommerce_PDF_IPS_Pro') && class_exists('WooCommerce_PDF_IPS_Dropbox') && class_exists('WooCommerce_PDF_IPS_Templates') && class_exists('WooCommerce_Ext_PrintOrders') ) ) {
30
+ include('wcpdf-extensions.php');
31
+ }
32
+
33
+ ?>
34
+ <form method="post" action="options.php" id="wpo-wcpdf-settings" class="<?php echo "{$active_tab} {$active_section}"; ?>">
35
+ <?php
36
+ do_action( 'wpo_wcpdf_before_settings', $active_tab, $active_section );
37
+ if ( has_action( 'wpo_wcpdf_settings_output_'.$active_tab ) ) {
38
+ do_action( 'wpo_wcpdf_settings_output_'.$active_tab, $active_section );
39
+ } else {
40
+ // legacy settings
41
+ settings_fields( "wpo_wcpdf_{$active_tab}_settings" );
42
+ do_settings_sections( "wpo_wcpdf_{$active_tab}_settings" );
43
+
44
+ submit_button();
45
+ }
46
+ do_action( 'wpo_wcpdf_after_settings', $active_tab, $active_section );
47
+ ?>
48
+
49
+ </form>
50
+ <?php do_action( 'wpo_wcpdf_after_settings_page', $active_tab, $active_section ); ?>
51
+ </div>
readme.txt CHANGED
@@ -1,221 +1,227 @@
1
- === WooCommerce PDF Invoices & Packing Slips ===
2
- Contributors: pomegranate
3
- Donate link: https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/
4
- Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice, packing slip, export, email, bulk, automatic
5
- Requires at least: 3.5
6
- Tested up to: 4.9
7
- Requires PHP: 5.3
8
- Stable tag: 2.1.1
9
- License: GPLv2 or later
10
- License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
-
12
- Create, print & automatically email PDF invoices & packing slips for WooCommerce orders.
13
-
14
- == Description ==
15
-
16
- This WooCommerce extension automatically adds a PDF invoice to the order confirmation emails sent out to your customers. Includes a basic template (additional templates are available from [WP Overnight](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)) as well as the possibility to modify/create your own templates. In addition, you can choose to download or print invoices and packing slips from the WooCommerce order admin.
17
-
18
- = Main features =
19
- * Automatically attach invoice PDF to WooCommerce emails of your choice
20
- * Download the PDF invoice / packing slip from the order admin page
21
- * Generate PDF invoices / packings slips in bulk
22
- * **Fully customizable** HTML/CSS invoice templates
23
- * Download invoices from the My Account page
24
- * Sequential invoice numbers - with custom formatting
25
- * **Available in: Czech, Dutch, English, Finnish, French, German, Hungarian, Italian, Japanese (see FAQ for adding custom fonts!), Norwegian, Polish, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish & Ukrainian**
26
-
27
- In addition to this, we offer several premium extensions:
28
-
29
- * Create/email PDF Proforma Invoices, Credit Notes (for Refunds), email Packing Slips & more with [WooCommerce PDF Invoices & Packing Slips Professional](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
30
- * Upload all invoices automatically to Dropbox with [WooCommerce PDF Invoices & Packing Slips to Dropbox](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-dropbox/)
31
- * Automatically send new orders or packing slips to your printer, as soon as the customer orders! [WooCommerce Automatic Order Printing](https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2) (from our partners at Simba Hosting)
32
- * More advanced & stylish templates with [WooCommerce PDF Invoices & Packing Slips Premium Templates](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)
33
-
34
- = Fully customizable =
35
- In addition to a number of default settings (including a custom header/logo) and several layout fields that you can use out of the box, the plugin contains HTML/CSS based templates that allow for customization & full control over the PDF output. Copy the templates to your theme folder and you don't have to worry that your customizations will be overwritten when you update the plugin.
36
-
37
- * Insert customer header image/logo
38
- * Modify shop data / footer / disclaimer etc. on the invoices & packing slips
39
- * Select paper size (Letter or A4)
40
- * Translation ready
41
-
42
- == Installation ==
43
-
44
- = Minimum Requirements =
45
-
46
- * WooCommerce 2.2 or later
47
- * WordPress 3.5 or later
48
-
49
- = Automatic installation =
50
- Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't even need to leave your web browser. To do an automatic install of WooCommerce PDF Invoices & Packing Slips, log in to your WordPress admin panel, navigate to the Plugins menu and click Add New.
51
-
52
- In the search field type "WooCommerce PDF Invoices & Packing Slips" and click Search Plugins. You can install it by simply clicking Install Now. After clicking that link you will be asked if you're sure you want to install the plugin. Click yes and WordPress will automatically complete the installation. After installation has finished, click the 'activate plugin' link.
53
-
54
- = Manual installation via the WordPress interface =
55
- 1. Download the plugin zip file to your computer
56
- 2. Go to the WordPress admin panel menu Plugins > Add New
57
- 3. Choose upload
58
- 4. Upload the plugin zip file, the plugin will now be installed
59
- 5. After installation has finished, click the 'activate plugin' link
60
-
61
- = Manual installation via FTP =
62
- 1. Download the plugin file to your computer and unzip it
63
- 2. Using an FTP program, or your hosting control panel, upload the unzipped plugin folder to your WordPress installation's wp-content/plugins/ directory.
64
- 3. Activate the plugin from the Plugins menu within the WordPress admin.
65
-
66
- == Frequently Asked Questions ==
67
-
68
- = Where can I find the documentation? =
69
-
70
- [WooCommerce PDF Invoices & Packing Slips documentation](http://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/)
71
-
72
- = It's not working! =
73
-
74
- Check out our step by step diagnostic instructions here: https://wordpress.org/support/topic/read-this-first-9/
75
-
76
-
77
-
78
-
79
-
80
- = Where can I find more templates? =
81
-
82
- Go to [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/) to checkout more templates! These include templates with more tax details and product thumbnails. Need a custom templates? Contact us at support@wpovernight.com for more information.
83
-
84
- = Can I create/send a proforma invoice or a credit note? =
85
- This is a feature of our Professional extension, which can be found at [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
86
-
87
- = Can I contribute to the code? =
88
- You're more than welcome! This plugin is hosted on github, where you can post issues or make pull requests.
89
- https://github.com/wpovernight/woocommerce-pdf-invoices-packing-slips
90
-
91
- = How can I display the HTML/CSS source for debugging/developing templates? =
92
- There's a setting on the Status tab of the settings page that allows you to toggle HTML output. Don't forget to turn if off after you're done testing!
93
-
94
-
95
- == Screenshots ==
96
-
97
- 1. Simple invoice PDF
98
- 2. Simple packing slip PDF
99
- 3. Quickly print individual invoices or packing slips from the order list
100
- 4. Print invoices or packing slips in bulk
101
- 5. Attach invoices to any WooCommerce email
102
- 6. Set shop name, address, header logo, etc.
103
-
104
- == Changelog ==
105
-
106
- = 2.1.1 =
107
- * Fix: WooCommerce Order Status & Actions Manager emails compatibility
108
- * Feature: sort orders by invoice number column
109
- * Tweak: pass document object to title filters
110
- * Tweak: use title getter in template files (instead of title string)
111
-
112
- = 2.1.0 =
113
- * Feature: WooCommerce Order Status & Actions Manager emails compatibility
114
- * Fix: Better url fallback for images stored in cloud
115
- * Update: dompdf library updated to 0.8.2 - DOMDocument parser set to default again
116
-
117
- = 2.0.15 =
118
- * Fix: Prevent saving invoice number/date from order details page when not edited
119
-
120
- = 2.0.14 =
121
- * Feature: Manually resend specific order emails in WooCommerce 3.2+
122
- * Tweak: Show full size logo preview in settings
123
- * Tweak: Custom field fallback to underscore prefixed meta key
124
- * Dev: added `wpo_wcpdf_before_sequential_number_increment` action
125
-
126
- = 2.0.13 =
127
- * Fix: Minor XSS issue on settings screens by escaping and sanitizing 'tab' & 'section' GET variables. Discovered by Detectify.
128
- * Fix: Pakistani Rupee Symbol
129
- * Feature: Automatically enable extended currency symbol support for currencies not supported by Open Sans
130
- * Dev: added `wpo_wcpdf_document_number_settings` filter
131
-
132
- = 2.0.12 =
133
- * Option: Use different HTML parser (debug settings)
134
-
135
- = 2.0.11 =
136
- * Fix: Improved fonts update routine (now preserves custom fonts)
137
- * Fix: Enable HTML5 parser by default (fixes issues with libxml)
138
- * Tweak: Show both PHP & WP Memory limit in Status tab
139
-
140
- = 2.0.10 =
141
- * Fix: Set invoice number backend button
142
- * Fix: Thumbail paths
143
- * Tweak: Make dompdf options filterable
144
-
145
- = 2.0.9 =
146
- * Feature: use `[invoice_date="ymd"]` in invoice number prefix or suffix to include a specific date format in the invoice number
147
- * Fix: Postmeta table prefix for invoice counter
148
- * Fix: 0% tax rates
149
-
150
- = 2.0.8 =
151
- * Feature: Add support for Bedrock / alternative folder structures
152
- * Dev: Filter for merged documents
153
- * Fix: Better attributes fallback for product variations
154
-
155
- = 2.0.7 =
156
- * Feature: Added button to delete legacy settings
157
- * Feature: Option to enable font subsetting
158
- * Fix: Invoice number sequence for databases with alternative auto_increment_increment settings
159
- * Fix: Fallback function for MB String (mb_stripos)
160
-
161
- = 2.0.6 =
162
- * Feature: Improved third party invoice number filters (`wpo_wcpdf_external_invoice_number_enabled` & `wpo_wcpdf_external_invoice_number`)
163
- * Fix: Underline position for Open Sans font
164
- * Fix: Invoice number auto_increment for servers that restarted frequently
165
- * Fix: Dompdf log file location (preventing open base_dir notices breaking PDF header)
166
- * Fix: 1.6.6 Settings migration duplicates merging
167
- * Tweak: Clear fonts folder when manually reinstalling fonts
168
-
169
- = 2.0.5 =
170
- * Feature: Remove temporary files (Status tab)
171
- * Fix: Page number replacement
172
- * Tweak: Fallback functions for MB String extension
173
- * Tweak: Improved wpo_wcpdf_check_privs usability for my account privileges
174
- * Legacy support: added wc_price alias for format_price method in document
175
-
176
- = 2.0.4 =
177
- * Fix: Apply filters for custom invoice number formatting in document too
178
- * Fix: Parent fallback for missing dates from refunds
179
-
180
- = 2.0.3 =
181
- * Fix: Better support for legacy invoice number filter (`wpo_wcpdf_invoice_number` - replaced by `wpo_wcpdf_formatted_document_number`)
182
- * Fix: Document number formatting fallback to order date if no document date available
183
- * Fix: Updated classmap: PSR loading didn't work on some installations
184
- * Fix: Prevent order notes from all orders showing when document is not loaded properly in filter
185
- * Tweak: Disable deprecation notices during email sending
186
- * Tweak: ignore outdated language packs
187
-
188
- = 2.0.2 =
189
- * Fix: order notes using correct order_id
190
- * Fix: WC3.0 deprecation notice for currency
191
- * Fix: Avoid crashing on PHP5.2 and older
192
- * Fix: Only use PHP MB String when present
193
- * Fix: Remote images
194
- * Fix: Download option
195
-
196
- = 2.0.1 =
197
- * Fix: PHP 5.4 issue
198
-
199
- = 2.0.0 =
200
- * New: Better structured & more advanced settings for documents
201
- * New: Option to enable & disable Packing Slips or Invoices
202
- * New: Invoice number sequence stored separately for improved speed & performance
203
- * New: Completely rewritten codebase for more flexibility & better reliability
204
- * New: Updated PDF library to DOMPDF 0.8
205
- * New: PDF Library made pluggable (by using the `wpo_wcpdf_pdf_maker` filter)
206
- * New: lots of new functions & filters to allow developers to hook into the plugin
207
- * Changed: **$wpo_wcpdf variable is now deprecated** (legacy mode available & automatically enabled on update)
208
- * Fix: Improved PHP 7 & 7.1 support
209
- * Fix: Positive prices for refunds
210
- * Fix: Use parent for attributes retrieved for product variations
211
- * Fix: Set content type to PDF for download
212
-
213
- = 1.6.6 =
214
- * Feature: Facilitate downgrading from 2.0 (re-installing fonts & resetting version)
215
- * Fix: Update currencies font (added Georgian Lari)
216
- * Translations: Added Indonesian
217
-
218
- == Upgrade Notice ==
219
-
220
- = 2.1.1 =
 
 
 
 
 
 
221
  2.0 is a BIG update! Make a full site backup before upgrading!
1
+ === WooCommerce PDF Invoices & Packing Slips ===
2
+ Contributors: pomegranate
3
+ Donate link: https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-bundle/
4
+ Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice, packing slip, export, email, bulk, automatic
5
+ Requires at least: 3.5
6
+ Tested up to: 4.9
7
+ Requires PHP: 5.3
8
+ Stable tag: 2.1.2
9
+ License: GPLv2 or later
10
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
+
12
+ Create, print & automatically email PDF invoices & packing slips for WooCommerce orders.
13
+
14
+ == Description ==
15
+
16
+ This WooCommerce extension automatically adds a PDF invoice to the order confirmation emails sent out to your customers. Includes a basic template (additional templates are available from [WP Overnight](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)) as well as the possibility to modify/create your own templates. In addition, you can choose to download or print invoices and packing slips from the WooCommerce order admin.
17
+
18
+ = Main features =
19
+ * Automatically attach invoice PDF to WooCommerce emails of your choice
20
+ * Download the PDF invoice / packing slip from the order admin page
21
+ * Generate PDF invoices / packings slips in bulk
22
+ * **Fully customizable** HTML/CSS invoice templates
23
+ * Download invoices from the My Account page
24
+ * Sequential invoice numbers - with custom formatting
25
+ * **Available in: Czech, Dutch, English, Finnish, French, German, Hungarian, Italian, Japanese (see FAQ for adding custom fonts!), Norwegian, Polish, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish & Ukrainian**
26
+
27
+ In addition to this, we offer several premium extensions:
28
+
29
+ * Create/email PDF Proforma Invoices, Credit Notes (for Refunds), email Packing Slips & more with [WooCommerce PDF Invoices & Packing Slips Professional](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
30
+ * Upload all invoices automatically to Dropbox with [WooCommerce PDF Invoices & Packing Slips to Dropbox](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-dropbox/)
31
+ * Automatically send new orders or packing slips to your printer, as soon as the customer orders! [WooCommerce Automatic Order Printing](https://www.simbahosting.co.uk/s3/product/woocommerce-automatic-order-printing/?affiliates=2) (from our partners at Simba Hosting)
32
+ * More advanced & stylish templates with [WooCommerce PDF Invoices & Packing Slips Premium Templates](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/)
33
+
34
+ = Fully customizable =
35
+ In addition to a number of default settings (including a custom header/logo) and several layout fields that you can use out of the box, the plugin contains HTML/CSS based templates that allow for customization & full control over the PDF output. Copy the templates to your theme folder and you don't have to worry that your customizations will be overwritten when you update the plugin.
36
+
37
+ * Insert customer header image/logo
38
+ * Modify shop data / footer / disclaimer etc. on the invoices & packing slips
39
+ * Select paper size (Letter or A4)
40
+ * Translation ready
41
+
42
+ == Installation ==
43
+
44
+ = Minimum Requirements =
45
+
46
+ * WooCommerce 2.2 or later
47
+ * WordPress 3.5 or later
48
+
49
+ = Automatic installation =
50
+ Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't even need to leave your web browser. To do an automatic install of WooCommerce PDF Invoices & Packing Slips, log in to your WordPress admin panel, navigate to the Plugins menu and click Add New.
51
+
52
+ In the search field type "WooCommerce PDF Invoices & Packing Slips" and click Search Plugins. You can install it by simply clicking Install Now. After clicking that link you will be asked if you're sure you want to install the plugin. Click yes and WordPress will automatically complete the installation. After installation has finished, click the 'activate plugin' link.
53
+
54
+ = Manual installation via the WordPress interface =
55
+ 1. Download the plugin zip file to your computer
56
+ 2. Go to the WordPress admin panel menu Plugins > Add New
57
+ 3. Choose upload
58
+ 4. Upload the plugin zip file, the plugin will now be installed
59
+ 5. After installation has finished, click the 'activate plugin' link
60
+
61
+ = Manual installation via FTP =
62
+ 1. Download the plugin file to your computer and unzip it
63
+ 2. Using an FTP program, or your hosting control panel, upload the unzipped plugin folder to your WordPress installation's wp-content/plugins/ directory.
64
+ 3. Activate the plugin from the Plugins menu within the WordPress admin.
65
+
66
+ == Frequently Asked Questions ==
67
+
68
+ = Where can I find the documentation? =
69
+
70
+ [WooCommerce PDF Invoices & Packing Slips documentation](http://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/)
71
+
72
+ = It's not working! =
73
+
74
+ Check out our step by step diagnostic instructions here: https://wordpress.org/support/topic/read-this-first-9/
75
+
76
+
77
+
78
+
79
+
80
+ = Where can I find more templates? =
81
+
82
+ Go to [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-premium-templates/) to checkout more templates! These include templates with more tax details and product thumbnails. Need a custom templates? Contact us at support@wpovernight.com for more information.
83
+
84
+ = Can I create/send a proforma invoice or a credit note? =
85
+ This is a feature of our Professional extension, which can be found at [wpovernight.com](https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/)
86
+
87
+ = Can I contribute to the code? =
88
+ You're more than welcome! This plugin is hosted on github, where you can post issues or make pull requests.
89
+ https://github.com/wpovernight/woocommerce-pdf-invoices-packing-slips
90
+
91
+ = How can I display the HTML/CSS source for debugging/developing templates? =
92
+ There's a setting on the Status tab of the settings page that allows you to toggle HTML output. Don't forget to turn if off after you're done testing!
93
+
94
+
95
+ == Screenshots ==
96
+
97
+ 1. Simple invoice PDF
98
+ 2. Simple packing slip PDF
99
+ 3. Quickly print individual invoices or packing slips from the order list
100
+ 4. Print invoices or packing slips in bulk
101
+ 5. Attach invoices to any WooCommerce email
102
+ 6. Set shop name, address, header logo, etc.
103
+
104
+ == Changelog ==
105
+
106
+ = 2.1.2 =
107
+ * Feature: New action wpo_wcpdf_init_document
108
+ * Fix: Use title getters for my-account and backend buttons
109
+ * Fix: Legacy Premium Templates reference
110
+ * Tweak: Skip documents overview in settings, default to invoice
111
+
112
+ = 2.1.1 =
113
+ * Fix: WooCommerce Order Status & Actions Manager emails compatibility
114
+ * Feature: sort orders by invoice number column
115
+ * Tweak: pass document object to title filters
116
+ * Tweak: use title getter in template files (instead of title string)
117
+
118
+ = 2.1.0 =
119
+ * Feature: WooCommerce Order Status & Actions Manager emails compatibility
120
+ * Fix: Better url fallback for images stored in cloud
121
+ * Update: dompdf library updated to 0.8.2 - DOMDocument parser set to default again
122
+
123
+ = 2.0.15 =
124
+ * Fix: Prevent saving invoice number/date from order details page when not edited
125
+
126
+ = 2.0.14 =
127
+ * Feature: Manually resend specific order emails in WooCommerce 3.2+
128
+ * Tweak: Show full size logo preview in settings
129
+ * Tweak: Custom field fallback to underscore prefixed meta key
130
+ * Dev: added `wpo_wcpdf_before_sequential_number_increment` action
131
+
132
+ = 2.0.13 =
133
+ * Fix: Minor XSS issue on settings screens by escaping and sanitizing 'tab' & 'section' GET variables. Discovered by Detectify.
134
+ * Fix: Pakistani Rupee Symbol
135
+ * Feature: Automatically enable extended currency symbol support for currencies not supported by Open Sans
136
+ * Dev: added `wpo_wcpdf_document_number_settings` filter
137
+
138
+ = 2.0.12 =
139
+ * Option: Use different HTML parser (debug settings)
140
+
141
+ = 2.0.11 =
142
+ * Fix: Improved fonts update routine (now preserves custom fonts)
143
+ * Fix: Enable HTML5 parser by default (fixes issues with libxml)
144
+ * Tweak: Show both PHP & WP Memory limit in Status tab
145
+
146
+ = 2.0.10 =
147
+ * Fix: Set invoice number backend button
148
+ * Fix: Thumbail paths
149
+ * Tweak: Make dompdf options filterable
150
+
151
+ = 2.0.9 =
152
+ * Feature: use `[invoice_date="ymd"]` in invoice number prefix or suffix to include a specific date format in the invoice number
153
+ * Fix: Postmeta table prefix for invoice counter
154
+ * Fix: 0% tax rates
155
+
156
+ = 2.0.8 =
157
+ * Feature: Add support for Bedrock / alternative folder structures
158
+ * Dev: Filter for merged documents
159
+ * Fix: Better attributes fallback for product variations
160
+
161
+ = 2.0.7 =
162
+ * Feature: Added button to delete legacy settings
163
+ * Feature: Option to enable font subsetting
164
+ * Fix: Invoice number sequence for databases with alternative auto_increment_increment settings
165
+ * Fix: Fallback function for MB String (mb_stripos)
166
+
167
+ = 2.0.6 =
168
+ * Feature: Improved third party invoice number filters (`wpo_wcpdf_external_invoice_number_enabled` & `wpo_wcpdf_external_invoice_number`)
169
+ * Fix: Underline position for Open Sans font
170
+ * Fix: Invoice number auto_increment for servers that restarted frequently
171
+ * Fix: Dompdf log file location (preventing open base_dir notices breaking PDF header)
172
+ * Fix: 1.6.6 Settings migration duplicates merging
173
+ * Tweak: Clear fonts folder when manually reinstalling fonts
174
+
175
+ = 2.0.5 =
176
+ * Feature: Remove temporary files (Status tab)
177
+ * Fix: Page number replacement
178
+ * Tweak: Fallback functions for MB String extension
179
+ * Tweak: Improved wpo_wcpdf_check_privs usability for my account privileges
180
+ * Legacy support: added wc_price alias for format_price method in document
181
+
182
+ = 2.0.4 =
183
+ * Fix: Apply filters for custom invoice number formatting in document too
184
+ * Fix: Parent fallback for missing dates from refunds
185
+
186
+ = 2.0.3 =
187
+ * Fix: Better support for legacy invoice number filter (`wpo_wcpdf_invoice_number` - replaced by `wpo_wcpdf_formatted_document_number`)
188
+ * Fix: Document number formatting fallback to order date if no document date available
189
+ * Fix: Updated classmap: PSR loading didn't work on some installations
190
+ * Fix: Prevent order notes from all orders showing when document is not loaded properly in filter
191
+ * Tweak: Disable deprecation notices during email sending
192
+ * Tweak: ignore outdated language packs
193
+
194
+ = 2.0.2 =
195
+ * Fix: order notes using correct order_id
196
+ * Fix: WC3.0 deprecation notice for currency
197
+ * Fix: Avoid crashing on PHP5.2 and older
198
+ * Fix: Only use PHP MB String when present
199
+ * Fix: Remote images
200
+ * Fix: Download option
201
+
202
+ = 2.0.1 =
203
+ * Fix: PHP 5.4 issue
204
+
205
+ = 2.0.0 =
206
+ * New: Better structured & more advanced settings for documents
207
+ * New: Option to enable & disable Packing Slips or Invoices
208
+ * New: Invoice number sequence stored separately for improved speed & performance
209
+ * New: Completely rewritten codebase for more flexibility & better reliability
210
+ * New: Updated PDF library to DOMPDF 0.8
211
+ * New: PDF Library made pluggable (by using the `wpo_wcpdf_pdf_maker` filter)
212
+ * New: lots of new functions & filters to allow developers to hook into the plugin
213
+ * Changed: **$wpo_wcpdf variable is now deprecated** (legacy mode available & automatically enabled on update)
214
+ * Fix: Improved PHP 7 & 7.1 support
215
+ * Fix: Positive prices for refunds
216
+ * Fix: Use parent for attributes retrieved for product variations
217
+ * Fix: Set content type to PDF for download
218
+
219
+ = 1.6.6 =
220
+ * Feature: Facilitate downgrading from 2.0 (re-installing fonts & resetting version)
221
+ * Fix: Update currencies font (added Georgian Lari)
222
+ * Translations: Added Indonesian
223
+
224
+ == Upgrade Notice ==
225
+
226
+ = 2.1.1 =
227
  2.0 is a BIG update! Make a full site backup before upgrading!
templates/Simple/invoice.php CHANGED
@@ -1,144 +1,144 @@
1
- <?php do_action( 'wpo_wcpdf_before_document', $this->type, $this->order ); ?>
2
-
3
- <table class="head container">
4
- <tr>
5
- <td class="header">
6
- <?php
7
- if( $this->has_header_logo() ) {
8
- $this->header_logo();
9
- } else {
10
- echo $this->get_title();
11
- }
12
- ?>
13
- </td>
14
- <td class="shop-info">
15
- <div class="shop-name"><h3><?php $this->shop_name(); ?></h3></div>
16
- <div class="shop-address"><?php $this->shop_address(); ?></div>
17
- </td>
18
- </tr>
19
- </table>
20
-
21
- <h1 class="document-type-label">
22
- <?php if( $this->has_header_logo() ) echo $this->get_title(); ?>
23
- </h1>
24
-
25
- <?php do_action( 'wpo_wcpdf_after_document_label', $this->type, $this->order ); ?>
26
-
27
- <table class="order-data-addresses">
28
- <tr>
29
- <td class="address billing-address">
30
- <!-- <h3><?php _e( 'Billing Address:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3> -->
31
- <?php $this->billing_address(); ?>
32
- <?php if ( isset($this->settings['display_email']) ) { ?>
33
- <div class="billing-email"><?php $this->billing_email(); ?></div>
34
- <?php } ?>
35
- <?php if ( isset($this->settings['display_phone']) ) { ?>
36
- <div class="billing-phone"><?php $this->billing_phone(); ?></div>
37
- <?php } ?>
38
- </td>
39
- <td class="address shipping-address">
40
- <?php if ( isset($this->settings['display_shipping_address']) && $this->ships_to_different_address()) { ?>
41
- <h3><?php _e( 'Ship To:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
42
- <?php $this->shipping_address(); ?>
43
- <?php } ?>
44
- </td>
45
- <td class="order-data">
46
- <table>
47
- <?php do_action( 'wpo_wcpdf_before_order_data', $this->type, $this->order ); ?>
48
- <?php if ( isset($this->settings['display_number']) ) { ?>
49
- <tr class="invoice-number">
50
- <th><?php _e( 'Invoice Number:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
51
- <td><?php $this->invoice_number(); ?></td>
52
- </tr>
53
- <?php } ?>
54
- <?php if ( isset($this->settings['display_date']) ) { ?>
55
- <tr class="invoice-date">
56
- <th><?php _e( 'Invoice Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
57
- <td><?php $this->invoice_date(); ?></td>
58
- </tr>
59
- <?php } ?>
60
- <tr class="order-number">
61
- <th><?php _e( 'Order Number:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
62
- <td><?php $this->order_number(); ?></td>
63
- </tr>
64
- <tr class="order-date">
65
- <th><?php _e( 'Order Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
66
- <td><?php $this->order_date(); ?></td>
67
- </tr>
68
- <tr class="payment-method">
69
- <th><?php _e( 'Payment Method:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
70
- <td><?php $this->payment_method(); ?></td>
71
- </tr>
72
- <?php do_action( 'wpo_wcpdf_after_order_data', $this->type, $this->order ); ?>
73
- </table>
74
- </td>
75
- </tr>
76
- </table>
77
-
78
- <?php do_action( 'wpo_wcpdf_before_order_details', $this->type, $this->order ); ?>
79
-
80
- <table class="order-details">
81
- <thead>
82
- <tr>
83
- <th class="product"><?php _e('Product', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
84
- <th class="quantity"><?php _e('Quantity', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
85
- <th class="price"><?php _e('Price', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
86
- </tr>
87
- </thead>
88
- <tbody>
89
- <?php $items = $this->get_order_items(); if( sizeof( $items ) > 0 ) : foreach( $items as $item_id => $item ) : ?>
90
- <tr class="<?php echo apply_filters( 'wpo_wcpdf_item_row_class', $item_id, $this->type, $this->order, $item_id ); ?>">
91
- <td class="product">
92
- <?php $description_label = __( 'Description', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
93
- <span class="item-name"><?php echo $item['name']; ?></span>
94
- <?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?>
95
- <span class="item-meta"><?php echo $item['meta']; ?></span>
96
- <dl class="meta">
97
- <?php $description_label = __( 'SKU', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
98
- <?php if( !empty( $item['sku'] ) ) : ?><dt class="sku"><?php _e( 'SKU:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="sku"><?php echo $item['sku']; ?></dd><?php endif; ?>
99
- <?php if( !empty( $item['weight'] ) ) : ?><dt class="weight"><?php _e( 'Weight:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="weight"><?php echo $item['weight']; ?><?php echo get_option('woocommerce_weight_unit'); ?></dd><?php endif; ?>
100
- </dl>
101
- <?php do_action( 'wpo_wcpdf_after_item_meta', $this->type, $item, $this->order ); ?>
102
- </td>
103
- <td class="quantity"><?php echo $item['quantity']; ?></td>
104
- <td class="price"><?php echo $item['order_price']; ?></td>
105
- </tr>
106
- <?php endforeach; endif; ?>
107
- </tbody>
108
- <tfoot>
109
- <tr class="no-borders">
110
- <td class="no-borders">
111
- <div class="customer-notes">
112
- <?php do_action( 'wpo_wcpdf_before_customer_notes', $this->type, $this->order ); ?>
113
- <?php if ( $this->get_shipping_notes() ) : ?>
114
- <h3><?php _e( 'Customer Notes', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
115
- <?php $this->shipping_notes(); ?>
116
- <?php endif; ?>
117
- <?php do_action( 'wpo_wcpdf_after_customer_notes', $this->type, $this->order ); ?>
118
- </div>
119
- </td>
120
- <td class="no-borders" colspan="2">
121
- <table class="totals">
122
- <tfoot>
123
- <?php foreach( $this->get_woocommerce_totals() as $key => $total ) : ?>
124
- <tr class="<?php echo $key; ?>">
125
- <td class="no-borders"></td>
126
- <th class="description"><?php echo $total['label']; ?></th>
127
- <td class="price"><span class="totals-price"><?php echo $total['value']; ?></span></td>
128
- </tr>
129
- <?php endforeach; ?>
130
- </tfoot>
131
- </table>
132
- </td>
133
- </tr>
134
- </tfoot>
135
- </table>
136
-
137
- <?php do_action( 'wpo_wcpdf_after_order_details', $this->type, $this->order ); ?>
138
-
139
- <?php if ( $this->get_footer() ): ?>
140
- <div id="footer">
141
- <?php $this->footer(); ?>
142
- </div><!-- #letter-footer -->
143
- <?php endif; ?>
144
- <?php do_action( 'wpo_wcpdf_after_document', $this->type, $this->order ); ?>
1
+ <?php do_action( 'wpo_wcpdf_before_document', $this->type, $this->order ); ?>
2
+
3
+ <table class="head container">
4
+ <tr>
5
+ <td class="header">
6
+ <?php
7
+ if( $this->has_header_logo() ) {
8
+ $this->header_logo();
9
+ } else {
10
+ echo $this->get_title();
11
+ }
12
+ ?>
13
+ </td>
14
+ <td class="shop-info">
15
+ <div class="shop-name"><h3><?php $this->shop_name(); ?></h3></div>
16
+ <div class="shop-address"><?php $this->shop_address(); ?></div>
17
+ </td>
18
+ </tr>
19
+ </table>
20
+
21
+ <h1 class="document-type-label">
22
+ <?php if( $this->has_header_logo() ) echo $this->get_title(); ?>
23
+ </h1>
24
+
25
+ <?php do_action( 'wpo_wcpdf_after_document_label', $this->type, $this->order ); ?>
26
+
27
+ <table class="order-data-addresses">
28
+ <tr>
29
+ <td class="address billing-address">
30
+ <!-- <h3><?php _e( 'Billing Address:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3> -->
31
+ <?php $this->billing_address(); ?>
32
+ <?php if ( isset($this->settings['display_email']) ) { ?>
33
+ <div class="billing-email"><?php $this->billing_email(); ?></div>
34
+ <?php } ?>
35
+ <?php if ( isset($this->settings['display_phone']) ) { ?>
36
+ <div class="billing-phone"><?php $this->billing_phone(); ?></div>
37
+ <?php } ?>
38
+ </td>
39
+ <td class="address shipping-address">
40
+ <?php if ( isset($this->settings['display_shipping_address']) && $this->ships_to_different_address()) { ?>
41
+ <h3><?php _e( 'Ship To:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
42
+ <?php $this->shipping_address(); ?>
43
+ <?php } ?>
44
+ </td>
45
+ <td class="order-data">
46
+ <table>
47
+ <?php do_action( 'wpo_wcpdf_before_order_data', $this->type, $this->order ); ?>
48
+ <?php if ( isset($this->settings['display_number']) ) { ?>
49
+ <tr class="invoice-number">
50
+ <th><?php _e( 'Invoice Number:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
51
+ <td><?php $this->invoice_number(); ?></td>
52
+ </tr>
53
+ <?php } ?>
54
+ <?php if ( isset($this->settings['display_date']) ) { ?>
55
+ <tr class="invoice-date">
56
+ <th><?php _e( 'Invoice Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
57
+ <td><?php $this->invoice_date(); ?></td>
58
+ </tr>
59
+ <?php } ?>
60
+ <tr class="order-number">
61
+ <th><?php _e( 'Order Number:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
62
+ <td><?php $this->order_number(); ?></td>
63
+ </tr>
64
+ <tr class="order-date">
65
+ <th><?php _e( 'Order Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
66
+ <td><?php $this->order_date(); ?></td>
67
+ </tr>
68
+ <tr class="payment-method">
69
+ <th><?php _e( 'Payment Method:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
70
+ <td><?php $this->payment_method(); ?></td>
71
+ </tr>
72
+ <?php do_action( 'wpo_wcpdf_after_order_data', $this->type, $this->order ); ?>
73
+ </table>
74
+ </td>
75
+ </tr>
76
+ </table>
77
+
78
+ <?php do_action( 'wpo_wcpdf_before_order_details', $this->type, $this->order ); ?>
79
+
80
+ <table class="order-details">
81
+ <thead>
82
+ <tr>
83
+ <th class="product"><?php _e('Product', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
84
+ <th class="quantity"><?php _e('Quantity', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
85
+ <th class="price"><?php _e('Price', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
86
+ </tr>
87
+ </thead>
88
+ <tbody>
89
+ <?php $items = $this->get_order_items(); if( sizeof( $items ) > 0 ) : foreach( $items as $item_id => $item ) : ?>
90
+ <tr class="<?php echo apply_filters( 'wpo_wcpdf_item_row_class', $item_id, $this->type, $this->order, $item_id ); ?>">
91
+ <td class="product">
92
+ <?php $description_label = __( 'Description', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
93
+ <span class="item-name"><?php echo $item['name']; ?></span>
94
+ <?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?>
95
+ <span class="item-meta"><?php echo $item['meta']; ?></span>
96
+ <dl class="meta">
97
+ <?php $description_label = __( 'SKU', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
98
+ <?php if( !empty( $item['sku'] ) ) : ?><dt class="sku"><?php _e( 'SKU:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="sku"><?php echo $item['sku']; ?></dd><?php endif; ?>
99
+ <?php if( !empty( $item['weight'] ) ) : ?><dt class="weight"><?php _e( 'Weight:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="weight"><?php echo $item['weight']; ?><?php echo get_option('woocommerce_weight_unit'); ?></dd><?php endif; ?>
100
+ </dl>
101
+ <?php do_action( 'wpo_wcpdf_after_item_meta', $this->type, $item, $this->order ); ?>
102
+ </td>
103
+ <td class="quantity"><?php echo $item['quantity']; ?></td>
104
+ <td class="price"><?php echo $item['order_price']; ?></td>
105
+ </tr>
106
+ <?php endforeach; endif; ?>
107
+ </tbody>
108
+ <tfoot>
109
+ <tr class="no-borders">
110
+ <td class="no-borders">
111
+ <div class="customer-notes">
112
+ <?php do_action( 'wpo_wcpdf_before_customer_notes', $this->type, $this->order ); ?>
113
+ <?php if ( $this->get_shipping_notes() ) : ?>
114
+ <h3><?php _e( 'Customer Notes', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
115
+ <?php $this->shipping_notes(); ?>
116
+ <?php endif; ?>
117
+ <?php do_action( 'wpo_wcpdf_after_customer_notes', $this->type, $this->order ); ?>
118
+ </div>
119
+ </td>
120
+ <td class="no-borders" colspan="2">
121
+ <table class="totals">
122
+ <tfoot>
123
+ <?php foreach( $this->get_woocommerce_totals() as $key => $total ) : ?>
124
+ <tr class="<?php echo $key; ?>">
125
+ <td class="no-borders"></td>
126
+ <th class="description"><?php echo $total['label']; ?></th>
127
+ <td class="price"><span class="totals-price"><?php echo $total['value']; ?></span></td>
128
+ </tr>
129
+ <?php endforeach; ?>
130
+ </tfoot>
131
+ </table>
132
+ </td>
133
+ </tr>
134
+ </tfoot>
135
+ </table>
136
+
137
+ <?php do_action( 'wpo_wcpdf_after_order_details', $this->type, $this->order ); ?>
138
+
139
+ <?php if ( $this->get_footer() ): ?>
140
+ <div id="footer">
141
+ <?php $this->footer(); ?>
142
+ </div><!-- #letter-footer -->
143
+ <?php endif; ?>
144
+ <?php do_action( 'wpo_wcpdf_after_document', $this->type, $this->order ); ?>
templates/Simple/packing-slip.php CHANGED
@@ -1,113 +1,113 @@
1
- <?php do_action( 'wpo_wcpdf_before_document', $this->type, $this->order ); ?>
2
-
3
- <table class="head container">
4
- <tr>
5
- <td class="header">
6
- <?php
7
- if( $this->has_header_logo() ) {
8
- $this->header_logo();
9
- } else {
10
- echo $this->get_title();
11
- }
12
- ?>
13
- </td>
14
- <td class="shop-info">
15
- <div class="shop-name"><h3><?php $this->shop_name(); ?></h3></div>
16
- <div class="shop-address"><?php $this->shop_address(); ?></div>
17
- </td>
18
- </tr>
19
- </table>
20
-
21
- <h1 class="document-type-label">
22
- <?php if( $this->has_header_logo() ) echo $this->get_title(); ?>
23
- </h1>
24
-
25
- <?php do_action( 'wpo_wcpdf_after_document_label', $this->type, $this->order ); ?>
26
-
27
- <table class="order-data-addresses">
28
- <tr>
29
- <td class="address shipping-address">
30
- <!-- <h3><?php _e( 'Shipping Address:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3> -->
31
- <?php $this->shipping_address(); ?>
32
- <?php if ( isset($this->settings['display_email']) ) { ?>
33
- <div class="billing-email"><?php $this->billing_email(); ?></div>
34
- <?php } ?>
35
- <?php if ( isset($this->settings['display_phone']) ) { ?>
36
- <div class="billing-phone"><?php $this->billing_phone(); ?></div>
37
- <?php } ?>
38
- </td>
39
- <td class="address billing-address">
40
- <?php if ( isset($this->settings['display_billing_address']) && $this->ships_to_different_address()) { ?>
41
- <h3><?php _e( 'Billing Address:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
42
- <?php $this->billing_address(); ?>
43
- <?php } ?>
44
- </td>
45
- <td class="order-data">
46
- <table>
47
- <?php do_action( 'wpo_wcpdf_before_order_data', $this->type, $this->order ); ?>
48
- <tr class="order-number">
49
- <th><?php _e( 'Order Number:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
50
- <td><?php $this->order_number(); ?></td>
51
- </tr>
52
- <tr class="order-date">
53
- <th><?php _e( 'Order Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
54
- <td><?php $this->order_date(); ?></td>
55
- </tr>
56
- <tr class="shipping-method">
57
- <th><?php _e( 'Shipping Method:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
58
- <td><?php $this->shipping_method(); ?></td>
59
- </tr>
60
- <?php do_action( 'wpo_wcpdf_after_order_data', $this->type, $this->order ); ?>
61
- </table>
62
- </td>
63
- </tr>
64
- </table>
65
-
66
- <?php do_action( 'wpo_wcpdf_before_order_details', $this->type, $this->order ); ?>
67
-
68
- <table class="order-details">
69
- <thead>
70
- <tr>
71
- <th class="product"><?php _e('Product', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
72
- <th class="quantity"><?php _e('Quantity', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
73
- </tr>
74
- </thead>
75
- <tbody>
76
- <?php $items = $this->get_order_items(); if( sizeof( $items ) > 0 ) : foreach( $items as $item_id => $item ) : ?>
77
- <tr class="<?php echo apply_filters( 'wpo_wcpdf_item_row_class', $item_id, $this->type, $this->order, $item_id ); ?>">
78
- <td class="product">
79
- <?php $description_label = __( 'Description', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
80
- <span class="item-name"><?php echo $item['name']; ?></span>
81
- <?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?>
82
- <span class="item-meta"><?php echo $item['meta']; ?></span>
83
- <dl class="meta">
84
- <?php $description_label = __( 'SKU', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
85
- <?php if( !empty( $item['sku'] ) ) : ?><dt class="sku"><?php _e( 'SKU:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="sku"><?php echo $item['sku']; ?></dd><?php endif; ?>
86
- <?php if( !empty( $item['weight'] ) ) : ?><dt class="weight"><?php _e( 'Weight:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="weight"><?php echo $item['weight']; ?><?php echo get_option('woocommerce_weight_unit'); ?></dd><?php endif; ?>
87
- </dl>
88
- <?php do_action( 'wpo_wcpdf_after_item_meta', $this->type, $item, $this->order ); ?>
89
- </td>
90
- <td class="quantity"><?php echo $item['quantity']; ?></td>
91
- </tr>
92
- <?php endforeach; endif; ?>
93
- </tbody>
94
- </table>
95
-
96
- <?php do_action( 'wpo_wcpdf_after_order_details', $this->type, $this->order ); ?>
97
-
98
- <?php do_action( 'wpo_wcpdf_before_customer_notes', $this->type, $this->order ); ?>
99
- <div class="customer-notes">
100
- <?php if ( $this->get_shipping_notes() ) : ?>
101
- <h3><?php _e( 'Customer Notes', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
102
- <?php $this->shipping_notes(); ?>
103
- <?php endif; ?>
104
- </div>
105
- <?php do_action( 'wpo_wcpdf_after_customer_notes', $this->type, $this->order ); ?>
106
-
107
- <?php if ( $this->get_footer() ): ?>
108
- <div id="footer">
109
- <?php $this->footer(); ?>
110
- </div><!-- #letter-footer -->
111
- <?php endif; ?>
112
-
113
  <?php do_action( 'wpo_wcpdf_after_document', $this->type, $this->order ); ?>
1
+ <?php do_action( 'wpo_wcpdf_before_document', $this->type, $this->order ); ?>
2
+
3
+ <table class="head container">
4
+ <tr>
5
+ <td class="header">
6
+ <?php
7
+ if( $this->has_header_logo() ) {
8
+ $this->header_logo();
9
+ } else {
10
+ echo $this->get_title();
11
+ }
12
+ ?>
13
+ </td>
14
+ <td class="shop-info">
15
+ <div class="shop-name"><h3><?php $this->shop_name(); ?></h3></div>
16
+ <div class="shop-address"><?php $this->shop_address(); ?></div>
17
+ </td>
18
+ </tr>
19
+ </table>
20
+
21
+ <h1 class="document-type-label">
22
+ <?php if( $this->has_header_logo() ) echo $this->get_title(); ?>
23
+ </h1>
24
+
25
+ <?php do_action( 'wpo_wcpdf_after_document_label', $this->type, $this->order ); ?>
26
+
27
+ <table class="order-data-addresses">
28
+ <tr>
29
+ <td class="address shipping-address">
30
+ <!-- <h3><?php _e( 'Shipping Address:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3> -->
31
+ <?php $this->shipping_address(); ?>
32
+ <?php if ( isset($this->settings['display_email']) ) { ?>
33
+ <div class="billing-email"><?php $this->billing_email(); ?></div>
34
+ <?php } ?>
35
+ <?php if ( isset($this->settings['display_phone']) ) { ?>
36
+ <div class="billing-phone"><?php $this->billing_phone(); ?></div>
37
+ <?php } ?>
38
+ </td>
39
+ <td class="address billing-address">
40
+ <?php if ( isset($this->settings['display_billing_address']) && $this->ships_to_different_address()) { ?>
41
+ <h3><?php _e( 'Billing Address:', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
42
+ <?php $this->billing_address(); ?>
43
+ <?php } ?>
44
+ </td>
45
+ <td class="order-data">
46
+ <table>
47
+ <?php do_action( 'wpo_wcpdf_before_order_data', $this->type, $this->order ); ?>
48
+ <tr class="order-number">
49
+ <th><?php _e( 'Order Number:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
50
+ <td><?php $this->order_number(); ?></td>
51
+ </tr>
52
+ <tr class="order-date">
53
+ <th><?php _e( 'Order Date:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
54
+ <td><?php $this->order_date(); ?></td>
55
+ </tr>
56
+ <tr class="shipping-method">
57
+ <th><?php _e( 'Shipping Method:', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
58
+ <td><?php $this->shipping_method(); ?></td>
59
+ </tr>
60
+ <?php do_action( 'wpo_wcpdf_after_order_data', $this->type, $this->order ); ?>
61
+ </table>
62
+ </td>
63
+ </tr>
64
+ </table>
65
+
66
+ <?php do_action( 'wpo_wcpdf_before_order_details', $this->type, $this->order ); ?>
67
+
68
+ <table class="order-details">
69
+ <thead>
70
+ <tr>
71
+ <th class="product"><?php _e('Product', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
72
+ <th class="quantity"><?php _e('Quantity', 'woocommerce-pdf-invoices-packing-slips' ); ?></th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ <?php $items = $this->get_order_items(); if( sizeof( $items ) > 0 ) : foreach( $items as $item_id => $item ) : ?>
77
+ <tr class="<?php echo apply_filters( 'wpo_wcpdf_item_row_class', $item_id, $this->type, $this->order, $item_id ); ?>">
78
+ <td class="product">
79
+ <?php $description_label = __( 'Description', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
80
+ <span class="item-name"><?php echo $item['name']; ?></span>
81
+ <?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?>
82
+ <span class="item-meta"><?php echo $item['meta']; ?></span>
83
+ <dl class="meta">
84
+ <?php $description_label = __( 'SKU', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
85
+ <?php if( !empty( $item['sku'] ) ) : ?><dt class="sku"><?php _e( 'SKU:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="sku"><?php echo $item['sku']; ?></dd><?php endif; ?>
86
+ <?php if( !empty( $item['weight'] ) ) : ?><dt class="weight"><?php _e( 'Weight:', 'woocommerce-pdf-invoices-packing-slips' ); ?></dt><dd class="weight"><?php echo $item['weight']; ?><?php echo get_option('woocommerce_weight_unit'); ?></dd><?php endif; ?>
87
+ </dl>
88
+ <?php do_action( 'wpo_wcpdf_after_item_meta', $this->type, $item, $this->order ); ?>
89
+ </td>
90
+ <td class="quantity"><?php echo $item['quantity']; ?></td>
91
+ </tr>
92
+ <?php endforeach; endif; ?>
93
+ </tbody>
94
+ </table>
95
+
96
+ <?php do_action( 'wpo_wcpdf_after_order_details', $this->type, $this->order ); ?>
97
+
98
+ <?php do_action( 'wpo_wcpdf_before_customer_notes', $this->type, $this->order ); ?>
99
+ <div class="customer-notes">
100
+ <?php if ( $this->get_shipping_notes() ) : ?>
101
+ <h3><?php _e( 'Customer Notes', 'woocommerce-pdf-invoices-packing-slips' ); ?></h3>
102
+ <?php $this->shipping_notes(); ?>
103
+ <?php endif; ?>
104
+ </div>
105
+ <?php do_action( 'wpo_wcpdf_after_customer_notes', $this->type, $this->order ); ?>
106
+
107
+ <?php if ( $this->get_footer() ): ?>
108
+ <div id="footer">
109
+ <?php $this->footer(); ?>
110
+ </div><!-- #letter-footer -->
111
+ <?php endif; ?>
112
+
113
  <?php do_action( 'wpo_wcpdf_after_document', $this->type, $this->order ); ?>
vendor/autoload.php CHANGED
@@ -1,7 +1,7 @@
1
- <?php
2
-
3
- // autoload.php @generated by Composer
4
-
5
- require_once __DIR__ . '/composer/autoload_real.php';
6
-
7
- return ComposerAutoloaderInit50d65aade31e72e54f6b791559872608::getLoader();
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInit50d65aade31e72e54f6b791559872608::getLoader();
vendor/composer/ClassLoader.php CHANGED
@@ -1,445 +1,445 @@
1
- <?php
2
-
3
- /*
4
- * This file is part of Composer.
5
- *
6
- * (c) Nils Adermann <naderman@naderman.de>
7
- * Jordi Boggiano <j.boggiano@seld.be>
8
- *
9
- * For the full copyright and license information, please view the LICENSE
10
- * file that was distributed with this source code.
11
- */
12
-
13
- namespace Composer\Autoload;
14
-
15
- /**
16
- * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
- *
18
- * $loader = new \Composer\Autoload\ClassLoader();
19
- *
20
- * // register classes with namespaces
21
- * $loader->add('Symfony\Component', __DIR__.'/component');
22
- * $loader->add('Symfony', __DIR__.'/framework');
23
- *
24
- * // activate the autoloader
25
- * $loader->register();
26
- *
27
- * // to enable searching the include path (eg. for PEAR packages)
28
- * $loader->setUseIncludePath(true);
29
- *
30
- * In this example, if you try to use a class in the Symfony\Component
31
- * namespace or one of its children (Symfony\Component\Console for instance),
32
- * the autoloader will first look for the class under the component/
33
- * directory, and it will then fallback to the framework/ directory if not
34
- * found before giving up.
35
- *
36
- * This class is loosely based on the Symfony UniversalClassLoader.
37
- *
38
- * @author Fabien Potencier <fabien@symfony.com>
39
- * @author Jordi Boggiano <j.boggiano@seld.be>
40
- * @see http://www.php-fig.org/psr/psr-0/
41
- * @see http://www.php-fig.org/psr/psr-4/
42
- */
43
- class ClassLoader
44
- {
45
- // PSR-4
46
- private $prefixLengthsPsr4 = array();
47
- private $prefixDirsPsr4 = array();
48
- private $fallbackDirsPsr4 = array();
49
-
50
- // PSR-0
51
- private $prefixesPsr0 = array();
52
- private $fallbackDirsPsr0 = array();
53
-
54
- private $useIncludePath = false;
55
- private $classMap = array();
56
- private $classMapAuthoritative = false;
57
- private $missingClasses = array();
58
- private $apcuPrefix;
59
-
60
- public function getPrefixes()
61
- {
62
- if (!empty($this->prefixesPsr0)) {
63
- return call_user_func_array('array_merge', $this->prefixesPsr0);
64
- }
65
-
66
- return array();
67
- }
68
-
69
- public function getPrefixesPsr4()
70
- {
71
- return $this->prefixDirsPsr4;
72
- }
73
-
74
- public function getFallbackDirs()
75
- {
76
- return $this->fallbackDirsPsr0;
77
- }
78
-
79
- public function getFallbackDirsPsr4()
80
- {
81
- return $this->fallbackDirsPsr4;
82
- }
83
-
84
- public function getClassMap()
85
- {
86
- return $this->classMap;
87
- }
88
-
89
- /**
90
- * @param array $classMap Class to filename map
91
- */
92
- public function addClassMap(array $classMap)
93
- {
94
- if ($this->classMap) {
95
- $this->classMap = array_merge($this->classMap, $classMap);
96
- } else {
97
- $this->classMap = $classMap;
98
- }
99
- }
100
-
101
- /**
102
- * Registers a set of PSR-0 directories for a given prefix, either
103
- * appending or prepending to the ones previously set for this prefix.
104
- *
105
- * @param string $prefix The prefix
106
- * @param array|string $paths The PSR-0 root directories
107
- * @param bool $prepend Whether to prepend the directories
108
- */
109
- public function add($prefix, $paths, $prepend = false)
110
- {
111
- if (!$prefix) {
112
- if ($prepend) {
113
- $this->fallbackDirsPsr0 = array_merge(
114
- (array) $paths,
115
- $this->fallbackDirsPsr0
116
- );
117
- } else {
118
- $this->fallbackDirsPsr0 = array_merge(
119
- $this->fallbackDirsPsr0,
120
- (array) $paths
121
- );
122
- }
123
-
124
- return;
125
- }
126
-
127
- $first = $prefix[0];
128
- if (!isset($this->prefixesPsr0[$first][$prefix])) {
129
- $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
-
131
- return;
132
- }
133
- if ($prepend) {
134
- $this->prefixesPsr0[$first][$prefix] = array_merge(
135
- (array) $paths,
136
- $this->prefixesPsr0[$first][$prefix]
137
- );
138
- } else {
139
- $this->prefixesPsr0[$first][$prefix] = array_merge(
140
- $this->prefixesPsr0[$first][$prefix],
141
- (array) $paths
142
- );
143
- }
144
- }
145
-
146
- /**
147
- * Registers a set of PSR-4 directories for a given namespace, either
148
- * appending or prepending to the ones previously set for this namespace.
149
- *
150
- * @param string $prefix The prefix/namespace, with trailing '\\'
151
- * @param array|string $paths The PSR-4 base directories
152
- * @param bool $prepend Whether to prepend the directories
153
- *
154
- * @throws \InvalidArgumentException
155
- */
156
- public function addPsr4($prefix, $paths, $prepend = false)
157
- {
158
- if (!$prefix) {
159
- // Register directories for the root namespace.
160
- if ($prepend) {
161
- $this->fallbackDirsPsr4 = array_merge(
162
- (array) $paths,
163
- $this->fallbackDirsPsr4
164
- );
165
- } else {
166
- $this->fallbackDirsPsr4 = array_merge(
167
- $this->fallbackDirsPsr4,
168
- (array) $paths
169
- );
170
- }
171
- } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172
- // Register directories for a new namespace.
173
- $length = strlen($prefix);
174
- if ('\\' !== $prefix[$length - 1]) {
175
- throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176
- }
177
- $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178
- $this->prefixDirsPsr4[$prefix] = (array) $paths;
179
- } elseif ($prepend) {
180
- // Prepend directories for an already registered namespace.
181
- $this->prefixDirsPsr4[$prefix] = array_merge(
182
- (array) $paths,
183
- $this->prefixDirsPsr4[$prefix]
184
- );
185
- } else {
186
- // Append directories for an already registered namespace.
187
- $this->prefixDirsPsr4[$prefix] = array_merge(
188
- $this->prefixDirsPsr4[$prefix],
189
- (array) $paths
190
- );
191
- }
192
- }
193
-
194
- /**
195
- * Registers a set of PSR-0 directories for a given prefix,
196
- * replacing any others previously set for this prefix.
197
- *
198
- * @param string $prefix The prefix
199
- * @param array|string $paths The PSR-0 base directories
200
- */
201
- public function set($prefix, $paths)
202
- {
203
- if (!$prefix) {
204
- $this->fallbackDirsPsr0 = (array) $paths;
205
- } else {
206
- $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207
- }
208
- }
209
-
210
- /**
211
- * Registers a set of PSR-4 directories for a given namespace,
212
- * replacing any others previously set for this namespace.
213
- *
214
- * @param string $prefix The prefix/namespace, with trailing '\\'
215
- * @param array|string $paths The PSR-4 base directories
216
- *
217
- * @throws \InvalidArgumentException
218
- */
219
- public function setPsr4($prefix, $paths)
220
- {
221
- if (!$prefix) {
222
- $this->fallbackDirsPsr4 = (array) $paths;
223
- } else {
224
- $length = strlen($prefix);
225
- if ('\\' !== $prefix[$length - 1]) {
226
- throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227
- }
228
- $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229
- $this->prefixDirsPsr4[$prefix] = (array) $paths;
230
- }
231
- }
232
-
233
- /**
234
- * Turns on searching the include path for class files.
235
- *
236
- * @param bool $useIncludePath
237
- */
238
- public function setUseIncludePath($useIncludePath)
239
- {
240
- $this->useIncludePath = $useIncludePath;
241
- }
242
-
243
- /**
244
- * Can be used to check if the autoloader uses the include path to check
245
- * for classes.
246
- *
247
- * @return bool
248
- */
249
- public function getUseIncludePath()
250
- {
251
- return $this->useIncludePath;
252
- }
253
-
254
- /**
255
- * Turns off searching the prefix and fallback directories for classes
256
- * that have not been registered with the class map.
257
- *
258
- * @param bool $classMapAuthoritative
259
- */
260
- public function setClassMapAuthoritative($classMapAuthoritative)
261
- {
262
- $this->classMapAuthoritative = $classMapAuthoritative;
263
- }
264
-
265
- /**
266
- * Should class lookup fail if not found in the current class map?
267
- *
268
- * @return bool
269
- */
270
- public function isClassMapAuthoritative()
271
- {
272
- return $this->classMapAuthoritative;
273
- }
274
-
275
- /**
276
- * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277
- *
278
- * @param string|null $apcuPrefix
279
- */
280
- public function setApcuPrefix($apcuPrefix)
281
- {
282
- $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283
- }
284
-
285
- /**
286
- * The APCu prefix in use, or null if APCu caching is not enabled.
287
- *
288
- * @return string|null
289
- */
290
- public function getApcuPrefix()
291
- {
292
- return $this->apcuPrefix;
293
- }
294
-
295
- /**
296
- * Registers this instance as an autoloader.
297
- *
298
- * @param bool $prepend Whether to prepend the autoloader or not
299
- */
300
- public function register($prepend = false)
301
- {
302
- spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303
- }
304
-
305
- /**
306
- * Unregisters this instance as an autoloader.
307
- */
308
- public function unregister()
309
- {
310
- spl_autoload_unregister(array($this, 'loadClass'));
311
- }
312
-
313
- /**
314
- * Loads the given class or interface.
315
- *
316
- * @param string $class The name of the class
317
- * @return bool|null True if loaded, null otherwise
318
- */
319
- public function loadClass($class)
320
- {
321
- if ($file = $this->findFile($class)) {
322
- includeFile($file);
323
-
324
- return true;
325
- }
326
- }
327
-
328
- /**
329
- * Finds the path to the file where the class is defined.
330
- *
331
- * @param string $class The name of the class
332
- *
333
- * @return string|false The path if found, false otherwise
334
- */
335
- public function findFile($class)
336
- {
337
- // class map lookup
338
- if (isset($this->classMap[$class])) {
339
- return $this->classMap[$class];
340
- }
341
- if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342
- return false;
343
- }
344
- if (null !== $this->apcuPrefix) {
345
- $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346
- if ($hit) {
347
- return $file;
348
- }
349
- }
350
-
351
- $file = $this->findFileWithExtension($class, '.php');
352
-
353
- // Search for Hack files if we are running on HHVM
354
- if (false === $file && defined('HHVM_VERSION')) {
355
- $file = $this->findFileWithExtension($class, '.hh');
356
- }
357
-
358
- if (null !== $this->apcuPrefix) {
359
- apcu_add($this->apcuPrefix.$class, $file);
360
- }
361
-
362
- if (false === $file) {
363
- // Remember that this class does not exist.
364
- $this->missingClasses[$class] = true;
365
- }
366
-
367
- return $file;
368
- }
369
-
370
- private function findFileWithExtension($class, $ext)
371
- {
372
- // PSR-4 lookup
373
- $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
-
375
- $first = $class[0];
376
- if (isset($this->prefixLengthsPsr4[$first])) {
377
- $subPath = $class;
378
- while (false !== $lastPos = strrpos($subPath, '\\')) {
379
- $subPath = substr($subPath, 0, $lastPos);
380
- $search = $subPath.'\\';
381
- if (isset($this->prefixDirsPsr4[$search])) {
382
- foreach ($this->prefixDirsPsr4[$search] as $dir) {
383
- $length = $this->prefixLengthsPsr4[$first][$search];
384
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
385
- return $file;
386
- }
387
- }
388
- }
389
- }
390
- }
391
-
392
- // PSR-4 fallback dirs
393
- foreach ($this->fallbackDirsPsr4 as $dir) {
394
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395
- return $file;
396
- }
397
- }
398
-
399
- // PSR-0 lookup
400
- if (false !== $pos = strrpos($class, '\\')) {
401
- // namespaced class name
402
- $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403
- . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404
- } else {
405
- // PEAR-like class name
406
- $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407
- }
408
-
409
- if (isset($this->prefixesPsr0[$first])) {
410
- foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411
- if (0 === strpos($class, $prefix)) {
412
- foreach ($dirs as $dir) {
413
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414
- return $file;
415
- }
416
- }
417
- }
418
- }
419
- }
420
-
421
- // PSR-0 fallback dirs
422
- foreach ($this->fallbackDirsPsr0 as $dir) {
423
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424
- return $file;
425
- }
426
- }
427
-
428
- // PSR-0 include paths.
429
- if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430
- return $file;
431
- }
432
-
433
- return false;
434
- }
435
- }
436
-
437
- /**
438
- * Scope isolated include.
439
- *
440
- * Prevents access to $this/self from included files.
441
- */
442
- function includeFile($file)
443
- {
444
- include $file;
445
- }
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see http://www.php-fig.org/psr/psr-0/
41
+ * @see http://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ // PSR-4
46
+ private $prefixLengthsPsr4 = array();
47
+ private $prefixDirsPsr4 = array();
48
+ private $fallbackDirsPsr4 = array();
49
+
50
+ // PSR-0
51
+ private $prefixesPsr0 = array();
52
+ private $fallbackDirsPsr0 = array();
53
+
54
+ private $useIncludePath = false;
55
+ private $classMap = array();
56
+ private $classMapAuthoritative = false;
57
+ private $missingClasses = array();
58
+ private $apcuPrefix;
59
+
60
+ public function getPrefixes()
61
+ {
62
+ if (!empty($this->prefixesPsr0)) {
63
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
64
+ }
65
+
66
+ return array();
67
+ }
68
+
69
+ public function getPrefixesPsr4()
70
+ {
71
+ return $this->prefixDirsPsr4;
72
+ }
73
+
74
+ public function getFallbackDirs()
75
+ {
76
+ return $this->fallbackDirsPsr0;
77
+ }
78
+
79
+ public function getFallbackDirsPsr4()
80
+ {
81
+ return $this->fallbackDirsPsr4;
82
+ }
83
+
84
+ public function getClassMap()
85
+ {
86
+ return $this->classMap;
87
+ }
88
+
89
+ /**
90
+ * @param array $classMap Class to filename map
91
+ */
92
+ public function addClassMap(array $classMap)
93
+ {
94
+ if ($this->classMap) {
95
+ $this->classMap = array_merge($this->classMap, $classMap);
96
+ } else {
97
+ $this->classMap = $classMap;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Registers a set of PSR-0 directories for a given prefix, either
103
+ * appending or prepending to the ones previously set for this prefix.
104
+ *
105
+ * @param string $prefix The prefix
106
+ * @param array|string $paths The PSR-0 root directories
107
+ * @param bool $prepend Whether to prepend the directories
108
+ */
109
+ public function add($prefix, $paths, $prepend = false)
110
+ {
111
+ if (!$prefix) {
112
+ if ($prepend) {
113
+ $this->fallbackDirsPsr0 = array_merge(
114
+ (array) $paths,
115
+ $this->fallbackDirsPsr0
116
+ );
117
+ } else {
118
+ $this->fallbackDirsPsr0 = array_merge(
119
+ $this->fallbackDirsPsr0,
120
+ (array) $paths
121
+ );
122
+ }
123
+
124
+ return;
125
+ }
126
+
127
+ $first = $prefix[0];
128
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
129
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
+
131
+ return;
132
+ }
133
+ if ($prepend) {
134
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
135
+ (array) $paths,
136
+ $this->prefixesPsr0[$first][$prefix]
137
+ );
138
+ } else {
139
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
140
+ $this->prefixesPsr0[$first][$prefix],
141
+ (array) $paths
142
+ );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Registers a set of PSR-4 directories for a given namespace, either
148
+ * appending or prepending to the ones previously set for this namespace.
149
+ *
150
+ * @param string $prefix The prefix/namespace, with trailing '\\'
151
+ * @param array|string $paths The PSR-4 base directories
152
+ * @param bool $prepend Whether to prepend the directories
153
+ *
154
+ * @throws \InvalidArgumentException
155
+ */
156
+ public function addPsr4($prefix, $paths, $prepend = false)
157
+ {
158
+ if (!$prefix) {
159
+ // Register directories for the root namespace.
160
+ if ($prepend) {
161
+ $this->fallbackDirsPsr4 = array_merge(
162
+ (array) $paths,
163
+ $this->fallbackDirsPsr4
164
+ );
165
+ } else {
166
+ $this->fallbackDirsPsr4 = array_merge(
167
+ $this->fallbackDirsPsr4,
168
+ (array) $paths
169
+ );
170
+ }
171
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172
+ // Register directories for a new namespace.
173
+ $length = strlen($prefix);
174
+ if ('\\' !== $prefix[$length - 1]) {
175
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176
+ }
177
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
179
+ } elseif ($prepend) {
180
+ // Prepend directories for an already registered namespace.
181
+ $this->prefixDirsPsr4[$prefix] = array_merge(
182
+ (array) $paths,
183
+ $this->prefixDirsPsr4[$prefix]
184
+ );
185
+ } else {
186
+ // Append directories for an already registered namespace.
187
+ $this->prefixDirsPsr4[$prefix] = array_merge(
188
+ $this->prefixDirsPsr4[$prefix],
189
+ (array) $paths
190
+ );
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Registers a set of PSR-0 directories for a given prefix,
196
+ * replacing any others previously set for this prefix.
197
+ *
198
+ * @param string $prefix The prefix
199
+ * @param array|string $paths The PSR-0 base directories
200
+ */
201
+ public function set($prefix, $paths)
202
+ {
203
+ if (!$prefix) {
204
+ $this->fallbackDirsPsr0 = (array) $paths;
205
+ } else {
206
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Registers a set of PSR-4 directories for a given namespace,
212
+ * replacing any others previously set for this namespace.
213
+ *
214
+ * @param string $prefix The prefix/namespace, with trailing '\\'
215
+ * @param array|string $paths The PSR-4 base directories
216
+ *
217
+ * @throws \InvalidArgumentException
218
+ */
219
+ public function setPsr4($prefix, $paths)
220
+ {
221
+ if (!$prefix) {
222
+ $this->fallbackDirsPsr4 = (array) $paths;
223
+ } else {
224
+ $length = strlen($prefix);
225
+ if ('\\' !== $prefix[$length - 1]) {
226
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227
+ }
228
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Turns on searching the include path for class files.
235
+ *
236
+ * @param bool $useIncludePath
237
+ */
238
+ public function setUseIncludePath($useIncludePath)
239
+ {
240
+ $this->useIncludePath = $useIncludePath;
241
+ }
242
+
243
+ /**
244
+ * Can be used to check if the autoloader uses the include path to check
245
+ * for classes.
246
+ *
247
+ * @return bool
248
+ */
249
+ public function getUseIncludePath()
250
+ {
251
+ return $this->useIncludePath;
252
+ }
253
+
254
+ /**
255
+ * Turns off searching the prefix and fallback directories for classes
256
+ * that have not been registered with the class map.
257
+ *
258
+ * @param bool $classMapAuthoritative
259
+ */
260
+ public function setClassMapAuthoritative($classMapAuthoritative)
261
+ {
262
+ $this->classMapAuthoritative = $classMapAuthoritative;
263
+ }
264
+
265
+ /**
266
+ * Should class lookup fail if not found in the current class map?
267
+ *
268
+ * @return bool
269
+ */
270
+ public function isClassMapAuthoritative()
271
+ {
272
+ return $this->classMapAuthoritative;
273
+ }
274
+
275
+ /**
276
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277
+ *
278
+ * @param string|null $apcuPrefix
279
+ */
280
+ public function setApcuPrefix($apcuPrefix)
281
+ {
282
+ $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283
+ }
284
+
285
+ /**
286
+ * The APCu prefix in use, or null if APCu caching is not enabled.
287
+ *
288
+ * @return string|null
289
+ */
290
+ public function getApcuPrefix()
291
+ {
292
+ return $this->apcuPrefix;
293
+ }
294
+
295
+ /**
296
+ * Registers this instance as an autoloader.
297
+ *
298
+ * @param bool $prepend Whether to prepend the autoloader or not
299
+ */
300
+ public function register($prepend = false)
301
+ {
302
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303
+ }
304
+
305
+ /**
306
+ * Unregisters this instance as an autoloader.
307
+ */
308
+ public function unregister()
309
+ {
310
+ spl_autoload_unregister(array($this, 'loadClass'));
311
+ }
312
+
313
+ /**
314
+ * Loads the given class or interface.
315
+ *
316
+ * @param string $class The name of the class
317
+ * @return bool|null True if loaded, null otherwise
318
+ */
319
+ public function loadClass($class)
320
+ {
321
+ if ($file = $this->findFile($class)) {
322
+ includeFile($file);
323
+
324
+ return true;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Finds the path to the file where the class is defined.
330
+ *
331
+ * @param string $class The name of the class
332
+ *
333
+ * @return string|false The path if found, false otherwise
334
+ */
335
+ public function findFile($class)
336
+ {
337
+ // class map lookup
338
+ if (isset($this->classMap[$class])) {
339
+ return $this->classMap[$class];
340
+ }
341
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342
+ return false;
343
+ }
344
+ if (null !== $this->apcuPrefix) {
345
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346
+ if ($hit) {
347
+ return $file;
348
+ }
349
+ }
350
+
351
+ $file = $this->findFileWithExtension($class, '.php');
352
+
353
+ // Search for Hack files if we are running on HHVM
354
+ if (false === $file && defined('HHVM_VERSION')) {
355
+ $file = $this->findFileWithExtension($class, '.hh');
356
+ }
357
+
358
+ if (null !== $this->apcuPrefix) {
359
+ apcu_add($this->apcuPrefix.$class, $file);
360
+ }
361
+
362
+ if (false === $file) {
363
+ // Remember that this class does not exist.
364
+ $this->missingClasses[$class] = true;
365
+ }
366
+
367
+ return $file;
368
+ }
369
+
370
+ private function findFileWithExtension($class, $ext)
371
+ {
372
+ // PSR-4 lookup
373
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
+
375
+ $first = $class[0];
376
+ if (isset($this->prefixLengthsPsr4[$first])) {
377
+ $subPath = $class;
378
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
379
+ $subPath = substr($subPath, 0, $lastPos);
380
+ $search = $subPath.'\\';
381
+ if (isset($this->prefixDirsPsr4[$search])) {
382
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
383
+ $length = $this->prefixLengthsPsr4[$first][$search];
384
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
385
+ return $file;
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ // PSR-4 fallback dirs
393
+ foreach ($this->fallbackDirsPsr4 as $dir) {
394
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395
+ return $file;
396
+ }
397
+ }
398
+
399
+ // PSR-0 lookup
400
+ if (false !== $pos = strrpos($class, '\\')) {
401
+ // namespaced class name
402
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404
+ } else {
405
+ // PEAR-like class name
406
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407
+ }
408
+
409
+ if (isset($this->prefixesPsr0[$first])) {
410
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411
+ if (0 === strpos($class, $prefix)) {
412
+ foreach ($dirs as $dir) {
413
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414
+ return $file;
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // PSR-0 fallback dirs
422
+ foreach ($this->fallbackDirsPsr0 as $dir) {
423
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424
+ return $file;
425
+ }
426
+ }
427
+
428
+ // PSR-0 include paths.
429
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430
+ return $file;
431
+ }
432
+
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Scope isolated include.
439
+ *
440
+ * Prevents access to $this/self from included files.
441
+ */
442
+ function includeFile($file)
443
+ {
444
+ include $file;
445
+ }
vendor/composer/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
-
2
- Copyright (c) Nils Adermann, Jordi Boggiano
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining a copy
5
- of this software and associated documentation files (the "Software"), to deal
6
- in the Software without restriction, including without limitation the rights
7
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the Software is furnished
9
- to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all
12
- copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- THE SOFTWARE.
21
-
1
+
2
+ Copyright (c) Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_real.php CHANGED
@@ -1,52 +1,52 @@
1
- <?php
2
-
3
- // autoload_real.php @generated by Composer
4
-
5
- class ComposerAutoloaderInit50d65aade31e72e54f6b791559872608
6
- {
7
- private static $loader;
8
-
9
- public static function loadClassLoader($class)
10
- {
11
- if ('Composer\Autoload\ClassLoader' === $class) {
12
- require __DIR__ . '/ClassLoader.php';
13
- }
14
- }
15
-
16
- public static function getLoader()
17
- {
18
- if (null !== self::$loader) {
19
- return self::$loader;
20
- }
21
-
22
- spl_autoload_register(array('ComposerAutoloaderInit50d65aade31e72e54f6b791559872608', 'loadClassLoader'), true, true);
23
- self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInit50d65aade31e72e54f6b791559872608', 'loadClassLoader'));
25
-
26
- $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
- if ($useStaticLoader) {
28
- require_once __DIR__ . '/autoload_static.php';
29
-
30
- call_user_func(\Composer\Autoload\ComposerStaticInit50d65aade31e72e54f6b791559872608::getInitializer($loader));
31
- } else {
32
- $map = require __DIR__ . '/autoload_namespaces.php';
33
- foreach ($map as $namespace => $path) {
34
- $loader->set($namespace, $path);
35
- }
36
-
37
- $map = require __DIR__ . '/autoload_psr4.php';
38
- foreach ($map as $namespace => $path) {
39
- $loader->setPsr4($namespace, $path);
40
- }
41
-
42
- $classMap = require __DIR__ . '/autoload_classmap.php';
43
- if ($classMap) {
44
- $loader->addClassMap($classMap);
45
- }
46
- }
47
-
48
- $loader->register(true);
49
-
50
- return $loader;
51
- }
52
- }
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInit50d65aade31e72e54f6b791559872608
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ public static function getLoader()
17
+ {
18
+ if (null !== self::$loader) {
19
+ return self::$loader;
20
+ }
21
+
22
+ spl_autoload_register(array('ComposerAutoloaderInit50d65aade31e72e54f6b791559872608', 'loadClassLoader'), true, true);
23
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit50d65aade31e72e54f6b791559872608', 'loadClassLoader'));
25
+
26
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
+ if ($useStaticLoader) {
28
+ require_once __DIR__ . '/autoload_static.php';
29
+
30
+ call_user_func(\Composer\Autoload\ComposerStaticInit50d65aade31e72e54f6b791559872608::getInitializer($loader));
31
+ } else {
32
+ $map = require __DIR__ . '/autoload_namespaces.php';
33
+ foreach ($map as $namespace => $path) {
34
+ $loader->set($namespace, $path);
35
+ }
36
+
37
+ $map = require __DIR__ . '/autoload_psr4.php';
38
+ foreach ($map as $namespace => $path) {
39
+ $loader->setPsr4($namespace, $path);
40
+ }
41
+
42
+ $classMap = require __DIR__ . '/autoload_classmap.php';
43
+ if ($classMap) {
44
+ $loader->addClassMap($classMap);
45
+ }
46
+ }
47
+
48
+ $loader->register(true);
49
+
50
+ return $loader;
51
+ }
52
+ }
vendor/composer/installed.json CHANGED
@@ -1,193 +1,193 @@
1
- [
2
- {
3
- "name": "sabberworm/php-css-parser",
4
- "version": "8.1.0",
5
- "version_normalized": "8.1.0.0",
6
- "source": {
7
- "type": "git",
8
- "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
9
- "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef"
10
- },
11
- "dist": {
12
- "type": "zip",
13
- "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/850cbbcbe7fbb155387a151ea562897a67e242ef",
14
- "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef",
15
- "shasum": ""
16
- },
17
- "require": {
18
- "php": ">=5.3.2"
19
- },
20
- "require-dev": {
21
- "phpunit/phpunit": "*"
22
- },
23
- "time": "2016-07-19T19:14:21+00:00",
24
- "type": "library",
25
- "installation-source": "dist",
26
- "autoload": {
27
- "psr-0": {
28
- "Sabberworm\\CSS": "lib/"
29
- }
30
- },
31
- "notification-url": "https://packagist.org/downloads/",
32
- "license": [
33
- "MIT"
34
- ],
35
- "authors": [
36
- {
37
- "name": "Raphael Schweikert"
38
- }
39
- ],
40
- "description": "Parser for CSS Files written in PHP",
41
- "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
42
- "keywords": [
43
- "css",
44
- "parser",
45
- "stylesheet"
46
- ]
47
- },
48
- {
49
- "name": "phenx/php-svg-lib",
50
- "version": "v0.3",
51
- "version_normalized": "0.3.0.0",
52
- "source": {
53
- "type": "git",
54
- "url": "https://github.com/PhenX/php-svg-lib.git",
55
- "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa"
56
- },
57
- "dist": {
58
- "type": "zip",
59
- "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
60
- "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
61
- "shasum": ""
62
- },
63
- "require": {
64
- "sabberworm/php-css-parser": "8.1.*"
65
- },
66
- "require-dev": {
67
- "phpunit/phpunit": "~5.0"
68
- },
69
- "time": "2017-05-24T10:07:27+00:00",
70
- "type": "library",
71
- "installation-source": "dist",
72
- "autoload": {
73
- "psr-0": {
74
- "Svg\\": "src/"
75
- }
76
- },
77
- "notification-url": "https://packagist.org/downloads/",
78
- "license": [
79
- "LGPL-3.0"
80
- ],
81
- "authors": [
82
- {
83
- "name": "Fabien Ménager",
84
- "email": "fabien.menager@gmail.com"
85
- }
86
- ],
87
- "description": "A library to read, parse and export to PDF SVG files.",
88
- "homepage": "https://github.com/PhenX/php-svg-lib"
89
- },
90
- {
91
- "name": "phenx/php-font-lib",
92
- "version": "0.5.1",
93
- "version_normalized": "0.5.1.0",
94
- "source": {
95
- "type": "git",
96
- "url": "https://github.com/PhenX/php-font-lib.git",
97
- "reference": "760148820110a1ae0936e5cc35851e25a938bc97"
98
- },
99
- "dist": {
100
- "type": "zip",
101
- "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97",
102
- "reference": "760148820110a1ae0936e5cc35851e25a938bc97",
103
- "shasum": ""
104
- },
105
- "require-dev": {
106
- "phpunit/phpunit": "^4.8"
107
- },
108
- "time": "2017-09-13T16:14:37+00:00",
109
- "type": "library",
110
- "installation-source": "dist",
111
- "autoload": {
112
- "psr-4": {
113
- "FontLib\\": "src/FontLib"
114
- }
115
- },
116
- "notification-url": "https://packagist.org/downloads/",
117
- "license": [
118
- "LGPL-3.0"
119
- ],
120
- "authors": [
121
- {
122
- "name": "Fabien Ménager",
123
- "email": "fabien.menager@gmail.com"
124
- }
125
- ],
126
- "description": "A library to read, parse, export and make subsets of different types of font files.",
127
- "homepage": "https://github.com/PhenX/php-font-lib"
128
- },
129
- {
130
- "name": "dompdf/dompdf",
131
- "version": "v0.8.2",
132
- "version_normalized": "0.8.2.0",
133
- "source": {
134
- "type": "git",
135
- "url": "https://github.com/dompdf/dompdf.git",
136
- "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6"
137
- },
138
- "dist": {
139
- "type": "zip",
140
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5113accd9ae5d466077cce5208dcf3fb871bf8f6",
141
- "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6",
142
- "shasum": ""
143
- },
144
- "require": {
145
- "ext-dom": "*",
146
- "ext-gd": "*",
147
- "ext-mbstring": "*",
148
- "phenx/php-font-lib": "0.5.*",
149
- "phenx/php-svg-lib": "0.3.*",
150
- "php": ">=5.4.0"
151
- },
152
- "require-dev": {
153
- "phpunit/phpunit": "4.8.*",
154
- "squizlabs/php_codesniffer": "2.*"
155
- },
156
- "time": "2017-11-26T14:49:08+00:00",
157
- "type": "library",
158
- "extra": {
159
- "branch-alias": {
160
- "dev-develop": "0.7-dev"
161
- }
162
- },
163
- "installation-source": "dist",
164
- "autoload": {
165
- "psr-4": {
166
- "Dompdf\\": "src/"
167
- },
168
- "classmap": [
169
- "lib/"
170
- ]
171
- },
172
- "notification-url": "https://packagist.org/downloads/",
173
- "license": [
174
- "LGPL-2.1"
175
- ],
176
- "authors": [
177
- {
178
- "name": "Fabien Ménager",
179
- "email": "fabien.menager@gmail.com"
180
- },
181
- {
182
- "name": "Brian Sweeney",
183
- "email": "eclecticgeek@gmail.com"
184
- },
185
- {
186
- "name": "Gabriel Bull",
187
- "email": "me@gabrielbull.com"
188
- }
189
- ],
190
- "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
191
- "homepage": "https://github.com/dompdf/dompdf"
192
- }
193
- ]
1
+ [
2
+ {
3
+ "name": "sabberworm/php-css-parser",
4
+ "version": "8.1.0",
5
+ "version_normalized": "8.1.0.0",
6
+ "source": {
7
+ "type": "git",
8
+ "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
9
+ "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef"
10
+ },
11
+ "dist": {
12
+ "type": "zip",
13
+ "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/850cbbcbe7fbb155387a151ea562897a67e242ef",
14
+ "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef",
15
+ "shasum": ""
16
+ },
17
+ "require": {
18
+ "php": ">=5.3.2"
19
+ },
20
+ "require-dev": {
21
+ "phpunit/phpunit": "*"
22
+ },
23
+ "time": "2016-07-19T19:14:21+00:00",
24
+ "type": "library",
25
+ "installation-source": "dist",
26
+ "autoload": {
27
+ "psr-0": {
28
+ "Sabberworm\\CSS": "lib/"
29
+ }
30
+ },
31
+ "notification-url": "https://packagist.org/downloads/",
32
+ "license": [
33
+ "MIT"
34
+ ],
35
+ "authors": [
36
+ {
37
+ "name": "Raphael Schweikert"
38
+ }
39
+ ],
40
+ "description": "Parser for CSS Files written in PHP",
41
+ "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
42
+ "keywords": [
43
+ "css",
44
+ "parser",
45
+ "stylesheet"
46
+ ]
47
+ },
48
+ {
49
+ "name": "phenx/php-svg-lib",
50
+ "version": "v0.3",
51
+ "version_normalized": "0.3.0.0",
52
+ "source": {
53
+ "type": "git",
54
+ "url": "https://github.com/PhenX/php-svg-lib.git",
55
+ "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa"
56
+ },
57
+ "dist": {
58
+ "type": "zip",
59
+ "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
60
+ "reference": "a85f7fe9fe08d093a4a8583cdd306b553ff918aa",
61
+ "shasum": ""
62
+ },
63
+ "require": {
64
+ "sabberworm/php-css-parser": "8.1.*"
65
+ },
66
+ "require-dev": {
67
+ "phpunit/phpunit": "~5.0"
68
+ },
69
+ "time": "2017-05-24T10:07:27+00:00",
70
+ "type": "library",
71
+ "installation-source": "dist",
72
+ "autoload": {
73
+ "psr-0": {
74
+ "Svg\\": "src/"
75
+ }
76
+ },
77
+ "notification-url": "https://packagist.org/downloads/",
78
+ "license": [
79
+ "LGPL-3.0"
80
+ ],
81
+ "authors": [
82
+ {
83
+ "name": "Fabien Ménager",
84
+ "email": "fabien.menager@gmail.com"
85
+ }
86
+ ],
87
+ "description": "A library to read, parse and export to PDF SVG files.",
88
+ "homepage": "https://github.com/PhenX/php-svg-lib"
89
+ },
90
+ {
91
+ "name": "phenx/php-font-lib",
92
+ "version": "0.5.1",
93
+ "version_normalized": "0.5.1.0",
94
+ "source": {
95
+ "type": "git",
96
+ "url": "https://github.com/PhenX/php-font-lib.git",
97
+ "reference": "760148820110a1ae0936e5cc35851e25a938bc97"
98
+ },
99
+ "dist": {
100
+ "type": "zip",
101
+ "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97",
102
+ "reference": "760148820110a1ae0936e5cc35851e25a938bc97",
103
+ "shasum": ""
104
+ },
105
+ "require-dev": {
106
+ "phpunit/phpunit": "^4.8"
107
+ },
108
+ "time": "2017-09-13T16:14:37+00:00",
109
+ "type": "library",
110
+ "installation-source": "dist",
111
+ "autoload": {
112
+ "psr-4": {
113
+ "FontLib\\": "src/FontLib"
114
+ }
115
+ },
116
+ "notification-url": "https://packagist.org/downloads/",
117
+ "license": [
118
+ "LGPL-3.0"
119
+ ],
120
+ "authors": [
121
+ {
122
+ "name": "Fabien Ménager",
123
+ "email": "fabien.menager@gmail.com"
124
+ }
125
+ ],
126
+ "description": "A library to read, parse, export and make subsets of different types of font files.",
127
+ "homepage": "https://github.com/PhenX/php-font-lib"
128
+ },
129
+ {
130
+ "name": "dompdf/dompdf",
131
+ "version": "v0.8.2",
132
+ "version_normalized": "0.8.2.0",
133
+ "source": {
134
+ "type": "git",
135
+ "url": "https://github.com/dompdf/dompdf.git",
136
+ "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6"
137
+ },
138
+ "dist": {
139
+ "type": "zip",
140
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5113accd9ae5d466077cce5208dcf3fb871bf8f6",
141
+ "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6",
142
+ "shasum": ""
143
+ },
144
+ "require": {
145
+ "ext-dom": "*",
146
+ "ext-gd": "*",
147
+ "ext-mbstring": "*",
148
+ "phenx/php-font-lib": "0.5.*",
149
+ "phenx/php-svg-lib": "0.3.*",
150
+ "php": ">=5.4.0"
151
+ },
152
+ "require-dev": {
153
+ "phpunit/phpunit": "4.8.*",
154
+ "squizlabs/php_codesniffer": "2.*"
155
+ },
156
+ "time": "2017-11-26T14:49:08+00:00",
157
+ "type": "library",
158
+ "extra": {
159
+ "branch-alias": {
160
+ "dev-develop": "0.7-dev"
161
+ }
162
+ },
163
+ "installation-source": "dist",
164
+ "autoload": {
165
+ "psr-4": {
166
+ "Dompdf\\": "src/"
167
+ },
168
+ "classmap": [
169
+ "lib/"
170
+ ]
171
+ },
172
+ "notification-url": "https://packagist.org/downloads/",
173
+ "license": [
174
+ "LGPL-2.1"
175
+ ],
176
+ "authors": [
177
+ {
178
+ "name": "Fabien Ménager",
179
+ "email": "fabien.menager@gmail.com"
180
+ },
181
+ {
182
+ "name": "Brian Sweeney",
183
+ "email": "eclecticgeek@gmail.com"
184
+ },
185
+ {
186
+ "name": "Gabriel Bull",
187
+ "email": "me@gabrielbull.com"
188
+ }
189
+ ],
190
+ "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
191
+ "homepage": "https://github.com/dompdf/dompdf"
192
+ }
193
+ ]
vendor/dompdf/dompdf/CONTRIBUTING.md CHANGED
@@ -1,65 +1,65 @@
1
- # How to contribute
2
-
3
- - [Getting help](#getting-help)
4
- - [Submitting bug reports](#submitting-bug-reports)
5
- - [Contributing code](#contributing-code)
6
-
7
- ## Getting help
8
-
9
- Community discussion, questions, and informal bug reporting is done on the
10
- [dompdf Google group](http://groups.google.com/group/dompdf). You may also
11
- seek help on
12
- [StackOverflow](http://stackoverflow.com/questions/tagged/dompdf).
13
-
14
- ## Submitting bug reports
15
-
16
- The preferred way to report bugs is to use the
17
- [GitHub issue tracker](http://github.com/dompdf/dompdf/issues). Before
18
- reporting a bug, read these pointers.
19
-
20
- **Please search inside the bug tracker to see if the bug you found is not already reported.**
21
-
22
- **Note:** The issue tracker is for *bugs* and *feature requests*, not requests for help.
23
- Questions should be asked on the
24
- [dompdf Google group](http://groups.google.com/group/dompdf) instead.
25
-
26
- ### Reporting bugs effectively
27
-
28
- - dompdf is maintained by volunteers. They don't owe you anything, so be
29
- polite. Reports with an indignant or belligerent tone tend to be moved to the
30
- bottom of the pile.
31
-
32
- - Include information about **the PHP version on which the problem occurred**. Even
33
- if you tested several PHP version on different servers, and the problem occurred
34
- in all of them, mention this fact in the bug report.
35
- Also include the operating system it's installed on. PHP configuration can also help,
36
- and server error logs (like Apache logs)
37
-
38
- - Mention which release of dompdf you're using (the zip, the master branch, etc).
39
- Preferably, try also with the current development snapshot, to ensure the
40
- problem has not already been fixed.
41
-
42
- - Mention very precisely what went wrong. "X is broken" is not a good bug
43
- report. What did you expect to happen? What happened instead? Describe the
44
- exact steps a maintainer has to take to make the problem occur. We can not
45
- fix something that we can not observe.
46
-
47
- - If the problem can not be reproduced in any of the demos included in the
48
- dompdf distribution, please provide an HTML document that demonstrates
49
- the problem. There are a few options to show us your code:
50
- - [JS Fiddle](http://jsfiddle.net/)
51
- - [dompdf debug helper](http://eclecticgeek.com/dompdf/debug.php) (provided by @bsweeney)
52
- - Include the HTML/CSS inside the bug report, with
53
- [code highlighting](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-code).
54
-
55
- ## Contributing code
56
-
57
- - Make sure you have a [GitHub Account](https://github.com/signup/free)
58
- - Fork [dompdf](https://github.com/dompdf/dompdf/)
59
- ([how to fork a repo](https://help.github.com/articles/fork-a-repo))
60
- - *Make your changes on the `develop` branch* or the most appropriate feature branch. Please only patch
61
- the master branch if you are attempting to address an urgent bug in the released code.
62
- - Add a simple test file in `www/test/`, with a comprehensive name.
63
- - Add a unit test in the ``test/Dompdf/Tests/`` directory.
64
- - Submit a pull request
65
- ([how to create a pull request](https://help.github.com/articles/fork-a-repo))
1
+ # How to contribute
2
+
3
+ - [Getting help](#getting-help)
4
+ - [Submitting bug reports](#submitting-bug-reports)
5
+ - [Contributing code](#contributing-code)
6
+
7
+ ## Getting help
8
+
9
+ Community discussion, questions, and informal bug reporting is done on the
10
+ [dompdf Google group](http://groups.google.com/group/dompdf). You may also
11
+ seek help on
12
+ [StackOverflow](http://stackoverflow.com/questions/tagged/dompdf).
13
+
14
+ ## Submitting bug reports
15
+
16
+ The preferred way to report bugs is to use the
17
+ [GitHub issue tracker](http://github.com/dompdf/dompdf/issues). Before
18
+ reporting a bug, read these pointers.
19
+
20
+ **Please search inside the bug tracker to see if the bug you found is not already reported.**
21
+
22
+ **Note:** The issue tracker is for *bugs* and *feature requests*, not requests for help.
23
+ Questions should be asked on the
24
+ [dompdf Google group](http://groups.google.com/group/dompdf) instead.
25
+
26
+ ### Reporting bugs effectively
27
+
28
+ - dompdf is maintained by volunteers. They don't owe you anything, so be
29
+ polite. Reports with an indignant or belligerent tone tend to be moved to the
30
+ bottom of the pile.
31
+
32
+ - Include information about **the PHP version on which the problem occurred**. Even
33
+ if you tested several PHP version on different servers, and the problem occurred
34
+ in all of them, mention this fact in the bug report.
35
+ Also include the operating system it's installed on. PHP configuration can also help,
36
+ and server error logs (like Apache logs)
37
+
38
+ - Mention which release of dompdf you're using (the zip, the master branch, etc).
39
+ Preferably, try also with the current development snapshot, to ensure the
40
+ problem has not already been fixed.
41
+
42
+ - Mention very precisely what went wrong. "X is broken" is not a good bug
43
+ report. What did you expect to happen? What happened instead? Describe the
44
+ exact steps a maintainer has to take to make the problem occur. We can not
45
+ fix something that we can not observe.
46
+
47
+ - If the problem can not be reproduced in any of the demos included in the
48
+ dompdf distribution, please provide an HTML document that demonstrates
49
+ the problem. There are a few options to show us your code:
50
+ - [JS Fiddle](http://jsfiddle.net/)
51
+ - [dompdf debug helper](http://eclecticgeek.com/dompdf/debug.php) (provided by @bsweeney)
52
+ - Include the HTML/CSS inside the bug report, with
53
+ [code highlighting](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-code).
54
+
55
+ ## Contributing code
56
+
57
+ - Make sure you have a [GitHub Account](https://github.com/signup/free)
58
+ - Fork [dompdf](https://github.com/dompdf/dompdf/)
59
+ ([how to fork a repo](https://help.github.com/articles/fork-a-repo))
60
+ - *Make your changes on the `develop` branch* or the most appropriate feature branch. Please only patch
61
+ the master branch if you are attempting to address an urgent bug in the released code.
62
+ - Add a simple test file in `www/test/`, with a comprehensive name.
63
+ - Add a unit test in the ``test/Dompdf/Tests/`` directory.
64
+ - Submit a pull request
65
+ ([how to create a pull request](https://help.github.com/articles/fork-a-repo))
vendor/dompdf/dompdf/README.md CHANGED
@@ -1,211 +1,211 @@
1
- Dompdf
2
- ======
3
-
4
- [![Build Status](https://travis-ci.org/dompdf/dompdf.png?branch=master)](https://travis-ci.org/dompdf/dompdf)
5
- [![Latest Stable Version](https://poser.pugx.org/dompdf/dompdf/v/stable.png)](https://packagist.org/packages/dompdf/dompdf)
6
- [![Total Downloads](https://poser.pugx.org/dompdf/dompdf/downloads.png)](https://packagist.org/packages/dompdf/dompdf)
7
- [![Latest Unstable Version](https://poser.pugx.org/dompdf/dompdf/v/unstable.png)](https://packagist.org/packages/dompdf/dompdf)
8
- [![License](https://poser.pugx.org/dompdf/dompdf/license.png)](https://packagist.org/packages/dompdf/dompdf)
9
-
10
- **Dompdf is an HTML to PDF converter**
11
-
12
- At its heart, dompdf is (mostly) a [CSS 2.1](http://www.w3.org/TR/CSS2/) compliant
13
- HTML layout and rendering engine written in PHP. It is a style-driven renderer:
14
- it will download and read external stylesheets, inline style tags, and the style
15
- attributes of individual HTML elements. It also supports most presentational
16
- HTML attributes.
17
-
18
- *This document applies to the latest stable code which may not reflect the current
19
- release. For released code please
20
- [navigate to the appropriate tag](https://github.com/dompdf/dompdf/tags).*
21
-
22
- ----
23
-
24
- **Check out the [demo](https://dompdf.net/examples.php) and ask any
25
- question on [StackOverflow](http://stackoverflow.com/questions/tagged/dompdf) or
26
- on the [Google Groups](http://groups.google.com/group/dompdf).**
27
-
28
- Follow us on [![Twitter](http://twitter-badges.s3.amazonaws.com/twitter-a.png)](http://www.twitter.com/dompdf) or
29
- [![Follow us on Google+](https://ssl.gstatic.com/images/icons/gplus-16.png)](https://plus.google.com/108710008521858993320?prsrc=3).
30
-
31
- ---
32
-
33
-
34
-
35
- ## Features
36
-
37
- * Handles most CSS 2.1 and a few CSS3 properties, including @import, @media &
38
- @page rules
39
- * Supports most presentational HTML 4.0 attributes
40
- * Supports external stylesheets, either local or through http/ftp (via
41
- fopen-wrappers)
42
- * Supports complex tables, including row & column spans, separate & collapsed
43
- border models, individual cell styling
44
- * Image support (gif, png (8, 24 and 32 bit with alpha channel), bmp & jpeg)
45
- * No dependencies on external PDF libraries, thanks to the R&OS PDF class
46
- * Inline PHP support
47
- * Basic SVG support
48
-
49
- ## Requirements
50
-
51
- * PHP version 5.4.0 or higher
52
- * DOM extension
53
- * GD extension
54
- * MBString extension
55
- * php-font-lib
56
- * php-svg-lib
57
-
58
- ### Recommendations
59
-
60
- * OPcache (OPcache, XCache, APC, etc.): improves performance
61
- * IMagick or GMagick extension: improves image processing performance
62
-
63
- Visit the wiki for more information:
64
- https://github.com/dompdf/dompdf/wiki/Requirements
65
-
66
- ## About Fonts & Character Encoding
67
-
68
- PDF documents internally support the following fonts: Helvetica, Times-Roman,
69
- Courier, Zapf-Dingbats, & Symbol. These fonts only support Windows ANSI
70
- encoding. In order for a PDF to display characters that are not available in
71
- Windows ANSI you must supply an external font. Dompdf will embed any referenced
72
- font in the PDF so long as it has been pre-loaded or is accessible to dompdf and
73
- reference in CSS @font-face rules. See the
74
- [font overview](https://github.com/dompdf/dompdf/wiki/About-Fonts-and-Character-Encoding)
75
- for more information on how to use fonts.
76
-
77
- The [DejaVu TrueType fonts](http://dejavu-fonts.org) have been pre-installed
78
- to give dompdf decent Unicode character coverage by default. To use the DejaVu
79
- fonts reference the font in your stylesheet, e.g. `body { font-family: DejaVu
80
- Sans; }` (for DejaVu Sans). The following DejaVu 2.34 fonts are available:
81
- DejaVu Sans, DejaVu Serif, and DejaVu Sans Mono.
82
-
83
- ## Easy Installation
84
-
85
- ### Install with composer
86
-
87
- To install with [Composer](https://getcomposer.org/), simply require the
88
- latest version of this package.
89
-
90
- ```bash
91
- composer require dompdf/dompdf
92
- ```
93
-
94
- Make sure that the autoload file from Composer is loaded.
95
-
96
- ```php
97
- // somewhere early in your project's loading, require the Composer autoloader
98
- // see: http://getcomposer.org/doc/00-intro.md
99
- require 'vendor/autoload.php';
100
-
101
- ```
102
-
103
- ### Download and install
104
-
105
- Download an archive of dompdf and extract it into the directory where dompdf
106
- will reside
107
- * You can download stable copies of dompdf from
108
- https://github.com/dompdf/dompdf/releases
109
- * Or download a nightly (the latest, unreleased code) from
110
- http://eclecticgeek.com/dompdf
111
-
112
- Use the packaged release autoloader to load dompdf, libraries,
113
- and helper functions in your PHP:
114
-
115
- ```php
116
- // include autoloader
117
- require_once 'dompdf/autoload.inc.php';
118
- ```
119
-
120
- ### Install with git
121
-
122
- From the command line, switch to the directory where dompdf will reside and run
123
- the following commands:
124
-
125
- ```sh
126
- git clone https://github.com/dompdf/dompdf.git
127
- cd dompdf
128
-
129
- git clone https://github.com/PhenX/php-font-lib.git lib/php-font-lib
130
- cd lib/php-font-lib
131
- git checkout 0.5.1
132
- cd ..
133
-
134
- git clone https://github.com/PhenX/php-svg-lib.git php-svg-lib
135
- cd php-svg-lib
136
- git checkout v0.3
137
- ```
138
-
139
- Require dompdf, libraries, and helper functions in your PHP:
140
-
141
- ```php
142
- require_once 'dompdf/lib/html5lib/Parser.php';
143
- require_once 'dompdf/lib/php-font-lib/src/FontLib/Autoloader.php';
144
- require_once 'dompdf/lib/php-svg-lib/src/autoload.php';
145
- require_once 'dompdf/src/Autoloader.php';
146
- Dompdf\Autoloader::register();
147
- ```
148
-
149
- ## Quick Start
150
-
151
- Just pass your HTML in to dompdf and stream the output:
152
-
153
- ```php
154
- // reference the Dompdf namespace
155
- use Dompdf\Dompdf;
156
-
157
- // instantiate and use the dompdf class
158
- $dompdf = new Dompdf();
159
- $dompdf->loadHtml('hello world');
160
-
161
- // (Optional) Setup the paper size and orientation
162
- $dompdf->setPaper('A4', 'landscape');
163
-
164
- // Render the HTML as PDF
165
- $dompdf->render();
166
-
167
- // Output the generated PDF to Browser
168
- $dompdf->stream();
169
- ```
170
-
171
- ### Setting Options
172
-
173
- Set options during dompdf instantiation:
174
-
175
- ```php
176
- use Dompdf\Dompdf;
177
- use Dompdf\Options;
178
-
179
- $options = new Options();
180
- $options->set('defaultFont', 'Courier');
181
- $dompdf = new Dompdf($options);
182
- ```
183
-
184
- or at run time
185
-
186
- ```php
187
- use Dompdf\Dompdf;
188
-
189
- $dompdf = new Dompdf();
190
- $dompdf->set_option('defaultFont', 'Courier');
191
- ```
192
-
193
- See [Dompdf\Options](src/Options.php) for a list of available options.
194
-
195
-
196
- ## Limitations (Known Issues)
197
-
198
- * Dompdf is not particularly tolerant to poorly-formed HTML input. To avoid
199
- any unexpected rendering issues you should either enable the built-in HTML5
200
- parser at runtime (`$dompdf->set_option('isHtml5ParserEnabled', true);`)
201
- or run your HTML through a HTML validator/cleaner (such as
202
- [Tidy](http://tidy.sourceforge.net) or the
203
- [W3C Markup Validation Service](http://validator.w3.org)).
204
- * Large files or large tables can take a while to render.
205
- * CSS float is in development and may not produce the desired result
206
-
207
- ---
208
-
209
- [![Donate button](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](http://goo.gl/DSvWf)
210
-
211
- *If you find this project useful, please consider making a donation. Any funds donated will be used to help further development on this project.)*
1
+ Dompdf
2
+ ======
3
+
4
+ [![Build Status](https://travis-ci.org/dompdf/dompdf.png?branch=master)](https://travis-ci.org/dompdf/dompdf)
5
+ [![Latest Stable Version](https://poser.pugx.org/dompdf/dompdf/v/stable.png)](https://packagist.org/packages/dompdf/dompdf)
6
+ [![Total Downloads](https://poser.pugx.org/dompdf/dompdf/downloads.png)](https://packagist.org/packages/dompdf/dompdf)
7
+ [![Latest Unstable Version](https://poser.pugx.org/dompdf/dompdf/v/unstable.png)](https://packagist.org/packages/dompdf/dompdf)
8
+ [![License](https://poser.pugx.org/dompdf/dompdf/license.png)](https://packagist.org/packages/dompdf/dompdf)
9
+
10
+ **Dompdf is an HTML to PDF converter**
11
+
12
+ At its heart, dompdf is (mostly) a [CSS 2.1](http://www.w3.org/TR/CSS2/) compliant
13
+ HTML layout and rendering engine written in PHP. It is a style-driven renderer:
14
+ it will download and read external stylesheets, inline style tags, and the style
15
+ attributes of individual HTML elements. It also supports most presentational
16
+ HTML attributes.
17
+
18
+ *This document applies to the latest stable code which may not reflect the current
19
+ release. For released code please
20
+ [navigate to the appropriate tag](https://github.com/dompdf/dompdf/tags).*
21
+
22
+ ----
23
+
24
+ **Check out the [demo](https://dompdf.net/examples.php) and ask any
25
+ question on [StackOverflow](http://stackoverflow.com/questions/tagged/dompdf) or
26
+ on the [Google Groups](http://groups.google.com/group/dompdf).**
27
+
28
+ Follow us on [![Twitter](http://twitter-badges.s3.amazonaws.com/twitter-a.png)](http://www.twitter.com/dompdf) or
29
+ [![Follow us on Google+](https://ssl.gstatic.com/images/icons/gplus-16.png)](https://plus.google.com/108710008521858993320?prsrc=3).
30
+
31
+ ---
32
+
33
+
34
+
35
+ ## Features
36
+
37
+ * Handles most CSS 2.1 and a few CSS3 properties, including @import, @media &
38
+ @page rules
39
+ * Supports most presentational HTML 4.0 attributes
40
+ * Supports external stylesheets, either local or through http/ftp (via
41
+ fopen-wrappers)
42
+ * Supports complex tables, including row & column spans, separate & collapsed
43
+ border models, individual cell styling
44
+ * Image support (gif, png (8, 24 and 32 bit with alpha channel), bmp & jpeg)
45
+ * No dependencies on external PDF libraries, thanks to the R&OS PDF class
46
+ * Inline PHP support
47
+ * Basic SVG support
48
+
49
+ ## Requirements
50
+
51
+ * PHP version 5.4.0 or higher
52
+ * DOM extension
53
+ * GD extension
54
+ * MBString extension
55
+ * php-font-lib
56
+ * php-svg-lib
57
+
58
+ ### Recommendations
59
+
60
+ * OPcache (OPcache, XCache, APC, etc.): improves performance
61
+ * IMagick or GMagick extension: improves image processing performance
62
+
63
+ Visit the wiki for more information:
64
+ https://github.com/dompdf/dompdf/wiki/Requirements
65
+
66
+ ## About Fonts & Character Encoding
67
+
68
+ PDF documents internally support the following fonts: Helvetica, Times-Roman,
69
+ Courier, Zapf-Dingbats, & Symbol. These fonts only support Windows ANSI
70
+ encoding. In order for a PDF to display characters that are not available in
71
+ Windows ANSI you must supply an external font. Dompdf will embed any referenced
72
+ font in the PDF so long as it has been pre-loaded or is accessible to dompdf and
73
+ reference in CSS @font-face rules. See the
74
+ [font overview](https://github.com/dompdf/dompdf/wiki/About-Fonts-and-Character-Encoding)
75
+ for more information on how to use fonts.
76
+
77
+ The [DejaVu TrueType fonts](http://dejavu-fonts.org) have been pre-installed
78
+ to give dompdf decent Unicode character coverage by default. To use the DejaVu
79
+ fonts reference the font in your stylesheet, e.g. `body { font-family: DejaVu
80
+ Sans; }` (for DejaVu Sans). The following DejaVu 2.34 fonts are available:
81
+ DejaVu Sans, DejaVu Serif, and DejaVu Sans Mono.
82
+
83
+ ## Easy Installation
84
+
85
+ ### Install with composer
86
+
87
+ To install with [Composer](https://getcomposer.org/), simply require the
88
+ latest version of this package.
89
+
90
+ ```bash
91
+ composer require dompdf/dompdf
92
+ ```
93
+
94
+ Make sure that the autoload file from Composer is loaded.
95
+
96
+ ```php
97
+ // somewhere early in your project's loading, require the Composer autoloader
98
+ // see: http://getcomposer.org/doc/00-intro.md
99
+ require 'vendor/autoload.php';
100
+
101
+ ```
102
+
103
+ ### Download and install
104
+
105
+ Download an archive of dompdf and extract it into the directory where dompdf
106
+ will reside
107
+ * You can download stable copies of dompdf from
108
+ https://github.com/dompdf/dompdf/releases
109
+ * Or download a nightly (the latest, unreleased code) from
110
+ http://eclecticgeek.com/dompdf
111
+
112
+ Use the packaged release autoloader to load dompdf, libraries,
113
+ and helper functions in your PHP:
114
+
115
+ ```php
116
+ // include autoloader
117
+ require_once 'dompdf/autoload.inc.php';
118
+ ```
119
+
120
+ ### Install with git
121
+
122
+ From the command line, switch to the directory where dompdf will reside and run
123
+ the following commands:
124
+
125
+ ```sh
126
+ git clone https://github.com/dompdf/dompdf.git
127
+ cd dompdf
128
+
129
+ git clone https://github.com/PhenX/php-font-lib.git lib/php-font-lib
130
+ cd lib/php-font-lib
131
+ git checkout 0.5.1
132
+ cd ..
133
+
134
+ git clone https://github.com/PhenX/php-svg-lib.git php-svg-lib
135
+ cd php-svg-lib
136
+ git checkout v0.3
137
+ ```
138
+
139
+ Require dompdf, libraries, and helper functions in your PHP:
140
+
141
+ ```php
142
+ require_once 'dompdf/lib/html5lib/Parser.php';
143
+ require_once 'dompdf/lib/php-font-lib/src/FontLib/Autoloader.php';
144
+ require_once 'dompdf/lib/php-svg-lib/src/autoload.php';
145
+ require_once 'dompdf/src/Autoloader.php';
146
+ Dompdf\Autoloader::register();
147
+ ```
148
+
149
+ ## Quick Start
150
+
151
+ Just pass your HTML in to dompdf and stream the output:
152
+
153
+ ```php
154
+ // reference the Dompdf namespace
155
+ use Dompdf\Dompdf;
156
+
157
+ // instantiate and use the dompdf class
158
+ $dompdf = new Dompdf();
159
+ $dompdf->loadHtml('hello world');
160
+
161
+ // (Optional) Setup the paper size and orientation
162
+ $dompdf->setPaper('A4', 'landscape');
163
+
164
+ // Render the HTML as PDF
165
+ $dompdf->render();
166
+
167
+ // Output the generated PDF to Browser
168
+ $dompdf->stream();
169
+ ```
170
+
171
+ ### Setting Options
172
+
173
+ Set options during dompdf instantiation:
174
+
175
+ ```php
176
+ use Dompdf\Dompdf;
177
+ use Dompdf\Options;
178
+
179
+ $options = new Options();
180
+ $options->set('defaultFont', 'Courier');
181
+ $dompdf = new Dompdf($options);
182
+ ```
183
+
184
+ or at run time
185
+
186
+ ```php
187
+ use Dompdf\Dompdf;
188
+
189
+ $dompdf = new Dompdf();
190
+ $dompdf->set_option('defaultFont', 'Courier');
191
+ ```
192
+
193
+ See [Dompdf\Options](src/Options.php) for a list of available options.
194
+
195
+
196
+ ## Limitations (Known Issues)
197
+
198
+ * Dompdf is not particularly tolerant to poorly-formed HTML input. To avoid
199
+ any unexpected rendering issues you should either enable the built-in HTML5
200
+ parser at runtime (`$dompdf->set_option('isHtml5ParserEnabled', true);`)
201
+ or run your HTML through a HTML validator/cleaner (such as
202
+ [Tidy](http://tidy.sourceforge.net) or the
203
+ [W3C Markup Validation Service](http://validator.w3.org)).
204
+ * Large files or large tables can take a while to render.
205
+ * CSS float is in development and may not produce the desired result
206
+
207
+ ---
208
+
209
+ [![Donate button](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](http://goo.gl/DSvWf)
210
+
211
+ *If you find this project useful, please consider making a donation. Any funds donated will be used to help further development on this project.)*
vendor/dompdf/dompdf/composer.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "dompdf/dompdf",
3
- "type": "library",
4
- "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
5
- "homepage": "https://github.com/dompdf/dompdf",
6
- "license": "LGPL-2.1",
7
- "authors": [
8
- {
9
- "name": "Fabien Ménager",
10
- "email": "fabien.menager@gmail.com"
11
- },
12
- {
13
- "name": "Brian Sweeney",
14
- "email": "eclecticgeek@gmail.com"
15
- },
16
- {
17
- "name": "Gabriel Bull",
18
- "email": "me@gabrielbull.com"
19
- }
20
- ],
21
- "autoload": {
22
- "psr-4" : {
23
- "Dompdf\\" : "src/"
24
- },
25
- "classmap" : ["lib/"]
26
- },
27
- "require": {
28
- "php": ">=5.4.0",
29
- "ext-gd": "*",
30
- "ext-dom": "*",
31
- "ext-mbstring": "*",
32
- "phenx/php-font-lib": "0.5.*",
33
- "phenx/php-svg-lib": "0.3.*"
34
- },
35
- "require-dev": {
36
- "phpunit/phpunit": "4.8.*",
37
- "squizlabs/php_codesniffer": "2.*"
38
- },
39
- "extra": {
40
- "branch-alias": {
41
- "dev-develop": "0.7-dev"
42
- }
43
- }
44
- }
1
+ {
2
+ "name": "dompdf/dompdf",
3
+ "type": "library",
4
+ "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
5
+ "homepage": "https://github.com/dompdf/dompdf",
6
+ "license": "LGPL-2.1",
7
+ "authors": [
8
+ {
9
+ "name": "Fabien Ménager",
10
+ "email": "fabien.menager@gmail.com"
11
+ },
12
+ {
13
+ "name": "Brian Sweeney",
14
+ "email": "eclecticgeek@gmail.com"
15
+ },
16
+ {
17
+ "name": "Gabriel Bull",
18
+ "email": "me@gabrielbull.com"
19
+ }
20
+ ],
21
+ "autoload": {
22
+ "psr-4" : {
23
+ "Dompdf\\" : "src/"
24
+ },
25
+ "classmap" : ["lib/"]
26
+ },
27
+ "require": {
28
+ "php": ">=5.4.0",
29
+ "ext-gd": "*",
30
+ "ext-dom": "*",
31
+ "ext-mbstring": "*",
32
+ "phenx/php-font-lib": "0.5.*",
33
+ "phenx/php-svg-lib": "0.3.*"
34
+ },
35
+ "require-dev": {
36
+ "phpunit/phpunit": "4.8.*",
37
+ "squizlabs/php_codesniffer": "2.*"
38
+ },
39
+ "extra": {
40
+ "branch-alias": {
41
+ "dev-develop": "0.7-dev"
42
+ }
43
+ }
44
+ }
vendor/dompdf/dompdf/lib/Cpdf.php CHANGED
@@ -1,5640 +1,5640 @@
1
- <?php
2
- /**
3
- * A PHP class to provide the basic functionality to create a pdf document without
4
- * any requirement for additional modules.
5
- *
6
- * Extended by Orion Richardson to support Unicode / UTF-8 characters using
7
- * TCPDF and others as a guide.
8
- *
9
- * @author Wayne Munro <pdf@ros.co.nz>
10
- * @author Orion Richardson <orionr@yahoo.com>
11
- * @author Helmut Tischer <htischer@weihenstephan.org>
12
- * @author Ryan H. Masten <ryan.masten@gmail.com>
13
- * @author Brian Sweeney <eclecticgeek@gmail.com>
14
- * @author Fabien Ménager <fabien.menager@gmail.com>
15
- * @license Public Domain http://creativecommons.org/licenses/publicdomain/
16
- * @package Cpdf
17
- */
18
- use FontLib\Font;
19
- use FontLib\BinaryStream;
20
-
21
- class Cpdf
22
- {
23
-
24
- /**
25
- * @var integer The current number of pdf objects in the document
26
- */
27
- public $numObj = 0;
28
-
29
- /**
30
- * @var array This array contains all of the pdf objects, ready for final assembly
31
- */
32
- public $objects = array();
33
-
34
- /**
35
- * @var integer The objectId (number within the objects array) of the document catalog
36
- */
37
- public $catalogId;
38
-
39
- /**
40
- * @var array Array carrying information about the fonts that the system currently knows about
41
- * Used to ensure that a font is not loaded twice, among other things
42
- */
43
- public $fonts = array();
44
-
45
- /**
46
- * @var string The default font metrics file to use if no other font has been loaded.
47
- * The path to the directory containing the font metrics should be included
48
- */
49
- public $defaultFont = './fonts/Helvetica.afm';
50
-
51
- /**
52
- * @string A record of the current font
53
- */
54
- public $currentFont = '';
55
-
56
- /**
57
- * @var string The current base font
58
- */
59
- public $currentBaseFont = '';
60
-
61
- /**
62
- * @var integer The number of the current font within the font array
63
- */
64
- public $currentFontNum = 0;
65
-
66
- /**
67
- * @var integer
68
- */
69
- public $currentNode;
70
-
71
- /**
72
- * @var integer Object number of the current page
73
- */
74
- public $currentPage;
75
-
76
- /**
77
- * @var integer Object number of the currently active contents block
78
- */
79
- public $currentContents;
80
-
81
- /**
82
- * @var integer Number of fonts within the system
83
- */
84
- public $numFonts = 0;
85
-
86
- /**
87
- * @var integer Number of graphic state resources used
88
- */
89
- private $numStates = 0;
90
-
91
- /**
92
- * @var array Number of graphic state resources used
93
- */
94
- private $gstates = array();
95
-
96
- /**
97
- * @var array Current color for fill operations, defaults to inactive value,
98
- * all three components should be between 0 and 1 inclusive when active
99
- */
100
- public $currentColor = null;
101
-
102
- /**
103
- * @var array Current color for stroke operations (lines etc.)
104
- */
105
- public $currentStrokeColor = null;
106
-
107
- /**
108
- * @var string Fill rule (nonzero or evenodd)
109
- */
110
- public $fillRule = "nonzero";
111
-
112
- /**
113
- * @var string Current style that lines are drawn in
114
- */
115
- public $currentLineStyle = '';
116
-
117
- /**
118
- * @var array Current line transparency (partial graphics state)
119
- */
120
- public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
121
-
122
- /**
123
- * array Current fill transparency (partial graphics state)
124
- */
125
- public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
126
-
127
- /**
128
- * @var array An array which is used to save the state of the document, mainly the colors and styles
129
- * it is used to temporarily change to another state, then change back to what it was before
130
- */
131
- public $stateStack = array();
132
-
133
- /**
134
- * @var integer Number of elements within the state stack
135
- */
136
- public $nStateStack = 0;
137
-
138
- /**
139
- * @var integer Number of page objects within the document
140
- */
141
- public $numPages = 0;
142
-
143
- /**
144
- * @var array Object Id storage stack
145
- */
146
- public $stack = array();
147
-
148
- /**
149
- * @var integer Number of elements within the object Id storage stack
150
- */
151
- public $nStack = 0;
152
-
153
- /**
154
- * an array which contains information about the objects which are not firmly attached to pages
155
- * these have been added with the addObject function
156
- */
157
- public $looseObjects = array();
158
-
159
- /**
160
- * array contains information about how the loose objects are to be added to the document
161
- */
162
- public $addLooseObjects = array();
163
-
164
- /**
165
- * @var integer The objectId of the information object for the document
166
- * this contains authorship, title etc.
167
- */
168
- public $infoObject = 0;
169
-
170
- /**
171
- * @var integer Number of images being tracked within the document
172
- */
173
- public $numImages = 0;
174
-
175
- /**
176
- * @var array An array containing options about the document
177
- * it defaults to turning on the compression of the objects
178
- */
179
- public $options = array('compression' => true);
180
-
181
- /**
182
- * @var integer The objectId of the first page of the document
183
- */
184
- public $firstPageId;
185
-
186
- /**
187
- * @var integer The object Id of the procset object
188
- */
189
- public $procsetObjectId;
190
-
191
- /**
192
- * @var array Store the information about the relationship between font families
193
- * this used so that the code knows which font is the bold version of another font, etc.
194
- * the value of this array is initialised in the constructor function.
195
- */
196
- public $fontFamilies = array();
197
-
198
- /**
199
- * @var string Folder for php serialized formats of font metrics files.
200
- * If empty string, use same folder as original metrics files.
201
- * This can be passed in from class creator.
202
- * If this folder does not exist or is not writable, Cpdf will be **much** slower.
203
- * Because of potential trouble with php safe mode, folder cannot be created at runtime.
204
- */
205
- public $fontcache = '';
206
-
207
- /**
208
- * @var integer The version of the font metrics cache file.
209
- * This value must be manually incremented whenever the internal font data structure is modified.
210
- */
211
- public $fontcacheVersion = 6;
212
-
213
- /**
214
- * @var string Temporary folder.
215
- * If empty string, will attempt system tmp folder.
216
- * This can be passed in from class creator.
217
- */
218
- public $tmp = '';
219
-
220
- /**
221
- * @var string Track if the current font is bolded or italicised
222
- */
223
- public $currentTextState = '';
224
-
225
- /**
226
- * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
227
- */
228
- public $messages = '';
229
-
230
- /**
231
- * @var string The encryption array for the document encryption is stored here
232
- */
233
- public $arc4 = '';
234
-
235
- /**
236
- * @var integer The object Id of the encryption information
237
- */
238
- public $arc4_objnum = 0;
239
-
240
- /**
241
- * @var string The file identifier, used to uniquely identify a pdf document
242
- */
243
- public $fileIdentifier = '';
244
-
245
- /**
246
- * @var boolean A flag to say if a document is to be encrypted or not
247
- */
248
- public $encrypted = false;
249
-
250
- /**
251
- * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
252
- */
253
- public $encryptionKey = '';
254
-
255
- /**
256
- * @var array Array which forms a stack to keep track of nested callback functions
257
- */
258
- public $callback = array();
259
-
260
- /**
261
- * @var integer The number of callback functions in the callback array
262
- */
263
- public $nCallback = 0;
264
-
265
- /**
266
- * @var array Store label->id pairs for named destinations, these will be used to replace internal links
267
- * done this way so that destinations can be defined after the location that links to them
268
- */
269
- public $destinations = array();
270
-
271
- /**
272
- * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
273
- * publiciables within the class, so that the user can rollback at will (from each 'start' command)
274
- * note that this includes the objects array, so these can be large.
275
- */
276
- public $checkpoint = '';
277
-
278
- /**
279
- * @var array Table of Image origin filenames and image labels which were already added with o_image().
280
- * Allows to merge identical images
281
- */
282
- public $imagelist = array();
283
-
284
- /**
285
- * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
286
- */
287
- public $isUnicode = false;
288
-
289
- /**
290
- * @var string the JavaScript code of the document
291
- */
292
- public $javascript = '';
293
-
294
- /**
295
- * @var boolean whether the compression is possible
296
- */
297
- protected $compressionReady = false;
298
-
299
- /**
300
- * @var array Current page size
301
- */
302
- protected $currentPageSize = array("width" => 0, "height" => 0);
303
-
304
- /**
305
- * @var array All the chars that will be required in the font subsets
306
- */
307
- protected $stringSubsets = array();
308
-
309
- /**
310
- * @var string The target internal encoding
311
- */
312
- static protected $targetEncoding = 'Windows-1252';
313
-
314
- /**
315
- * @var array The list of the core fonts
316
- */
317
- static protected $coreFonts = array(
318
- 'courier',
319
- 'courier-bold',
320
- 'courier-oblique',
321
- 'courier-boldoblique',
322
- 'helvetica',
323
- 'helvetica-bold',
324
- 'helvetica-oblique',
325
- 'helvetica-boldoblique',
326
- 'times-roman',
327
- 'times-bold',
328
- 'times-italic',
329
- 'times-bolditalic',
330
- 'symbol',
331
- 'zapfdingbats'
332
- );
333
-
334
- /**
335
- * Class constructor
336
- * This will start a new document
337
- *
338
- * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
339
- * @param boolean $isUnicode Whether text will be treated as Unicode or not.
340
- * @param string $fontcache The font cache folder
341
- * @param string $tmp The temporary folder
342
- */
343
- function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '')
344
- {
345
- $this->isUnicode = $isUnicode;
346
- $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
347
- $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
348
- $this->newDocument($pageSize);
349
-
350
- $this->compressionReady = function_exists('gzcompress');
351
-
352
- if (in_array('Windows-1252', mb_list_encodings())) {
353
- self::$targetEncoding = 'Windows-1252';
354
- }
355
-
356
- // also initialize the font families that are known about already
357
- $this->setFontFamily('init');
358
- }
359
-
360
- /**
361
- * Document object methods (internal use only)
362
- *
363
- * There is about one object method for each type of object in the pdf document
364
- * Each function has the same call list ($id,$action,$options).
365
- * $id = the object ID of the object, or what it is to be if it is being created
366
- * $action = a string specifying the action to be performed, though ALL must support:
367
- * 'new' - create the object with the id $id
368
- * 'out' - produce the output for the pdf object
369
- * $options = optional, a string or array containing the various parameters for the object
370
- *
371
- * These, in conjunction with the output function are the ONLY way for output to be produced
372
- * within the pdf 'file'.
373
- */
374
-
375
- /**
376
- * Destination object, used to specify the location for the user to jump to, presently on opening
377
- *
378
- * @param $id
379
- * @param $action
380
- * @param string $options
381
- * @return string|null
382
- */
383
- protected function o_destination($id, $action, $options = '')
384
- {
385
- switch ($action) {
386
- case 'new':
387
- $this->objects[$id] = array('t' => 'destination', 'info' => array());
388
- $tmp = '';
389
- switch ($options['type']) {
390
- case 'XYZ':
391
- /** @noinspection PhpMissingBreakStatementInspection */
392
- case 'FitR':
393
- $tmp = ' ' . $options['p3'] . $tmp;
394
- case 'FitH':
395
- case 'FitV':
396
- case 'FitBH':
397
- /** @noinspection PhpMissingBreakStatementInspection */
398
- case 'FitBV':
399
- $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
400
- case 'Fit':
401
- case 'FitB':
402
- $tmp = $options['type'] . $tmp;
403
- $this->objects[$id]['info']['string'] = $tmp;
404
- $this->objects[$id]['info']['page'] = $options['page'];
405
- }
406
- break;
407
-
408
- case 'out':
409
- $o = &$this->objects[$id];
410
-
411
- $tmp = $o['info'];
412
- $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
413
-
414
- return $res;
415
- }
416
-
417
- return null;
418
- }
419
-
420
- /**
421
- * set the viewer preferences
422
- *
423
- * @param $id
424
- * @param $action
425
- * @param string|array $options
426
- * @return string|null
427
- */
428
- protected function o_viewerPreferences($id, $action, $options = '')
429
- {
430
- switch ($action) {
431
- case 'new':
432
- $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array());
433
- break;
434
-
435
- case 'add':
436
- $o = &$this->objects[$id];
437
-
438
- foreach ($options as $k => $v) {
439
- switch ($k) {
440
- // Boolean keys
441
- case 'HideToolbar':
442
- case 'HideMenubar':
443
- case 'HideWindowUI':
444
- case 'FitWindow':
445
- case 'CenterWindow':
446
- case 'DisplayDocTitle':
447
- case 'PickTrayByPDFSize':
448
- $o['info'][$k] = (bool)$v;
449
- break;
450
-
451
- // Integer keys
452
- case 'NumCopies':
453
- $o['info'][$k] = (int)$v;
454
- break;
455
-
456
- // Name keys
457
- case 'ViewArea':
458
- case 'ViewClip':
459
- case 'PrintClip':
460
- case 'PrintArea':
461
- $o['info'][$k] = (string)$v;
462
- break;
463
-
464
- // Named with limited valid values
465
- case 'NonFullScreenPageMode':
466
- if (!in_array($v, array('UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'))) {
467
- continue;
468
- }
469
- $o['info'][$k] = $v;
470
- break;
471
-
472
- case 'Direction':
473
- if (!in_array($v, array('L2R', 'R2L'))) {
474
- continue;
475
- }
476
- $o['info'][$k] = $v;
477
- break;
478
-
479
- case 'PrintScaling':
480
- if (!in_array($v, array('None', 'AppDefault'))) {
481
- continue;
482
- }
483
- $o['info'][$k] = $v;
484
- break;
485
-
486
- case 'Duplex':
487
- if (!in_array($v, array('None', 'AppDefault'))) {
488
- continue;
489
- }
490
- $o['info'][$k] = $v;
491
- break;
492
-
493
- // Integer array
494
- case 'PrintPageRange':
495
- // Cast to integer array
496
- foreach ($v as $vK => $vV) {
497
- $v[$vK] = (int)$vV;
498
- }
499
- $o['info'][$k] = array_values($v);
500
- break;
501
- }
502
- }
503
- break;
504
-
505
- case 'out':
506
- $o = &$this->objects[$id];
507
- $res = "\n$id 0 obj\n<< ";
508
-
509
- foreach ($o['info'] as $k => $v) {
510
- if (is_string($v)) {
511
- $v = '/' . $v;
512
- } elseif (is_int($v)) {
513
- $v = (string) $v;
514
- } elseif (is_bool($v)) {
515
- $v = ($v ? 'true' : 'false');
516
- } elseif (is_array($v)) {
517
- $v = '[' . implode(' ', $v) . ']';
518
- }
519
- $res .= "\n/$k $v";
520
- }
521
- $res .= "\n>>\n";
522
-
523
- return $res;
524
- }
525
-
526
- return null;
527
- }
528
-
529
- /**
530
- * define the document catalog, the overall controller for the document
531
- *
532
- * @param $id
533
- * @param $action
534
- * @param string|array $options
535
- * @return string|null
536
- */
537
- protected function o_catalog($id, $action, $options = '')
538
- {
539
- if ($action !== 'new') {
540
- $o = &$this->objects[$id];
541
- }
542
-
543
- switch ($action) {
544
- case 'new':
545
- $this->objects[$id] = array('t' => 'catalog', 'info' => array());
546
- $this->catalogId = $id;
547
- break;
548
-
549
- case 'outlines':
550
- case 'pages':
551
- case 'openHere':
552
- case 'javascript':
553
- $o['info'][$action] = $options;
554
- break;
555
-
556
- case 'viewerPreferences':
557
- if (!isset($o['info']['viewerPreferences'])) {
558
- $this->numObj++;
559
- $this->o_viewerPreferences($this->numObj, 'new');
560
- $o['info']['viewerPreferences'] = $this->numObj;
561
- }
562
-
563
- $vp = $o['info']['viewerPreferences'];
564
- $this->o_viewerPreferences($vp, 'add', $options);
565
-
566
- break;
567
-
568
- case 'out':
569
- $res = "\n$id 0 obj\n<< /Type /Catalog";
570
-
571
- foreach ($o['info'] as $k => $v) {
572
- switch ($k) {
573
- case 'outlines':
574
- $res .= "\n/Outlines $v 0 R";
575
- break;
576
-
577
- case 'pages':
578
- $res .= "\n/Pages $v 0 R";
579
- break;
580
-
581
- case 'viewerPreferences':
582
- $res .= "\n/ViewerPreferences $v 0 R";
583
- break;
584
-
585
- case 'openHere':
586
- $res .= "\n/OpenAction $v 0 R";
587
- break;
588
-
589
- case 'javascript':
590
- $res .= "\n/Names <</JavaScript $v 0 R>>";
591
- break;
592
- }
593
- }
594
-
595
- $res .= " >>\nendobj";
596
-
597
- return $res;
598
- }
599
-
600
- return null;
601
- }
602
-
603
- /**
604
- * object which is a parent to the pages in the document
605
- *
606
- * @param $id
607
- * @param $action
608
- * @param string $options
609
- * @return string|null
610
- */
611
- protected function o_pages($id, $action, $options = '')
612
- {
613
- if ($action !== 'new') {
614
- $o = &$this->objects[$id];
615
- }
616
-
617
- switch ($action) {
618
- case 'new':
619
- $this->objects[$id] = array('t' => 'pages', 'info' => array());
620
- $this->o_catalog($this->catalogId, 'pages', $id);
621
- break;
622
-
623
- case 'page':
624
- if (!is_array($options)) {
625
- // then it will just be the id of the new page
626
- $o['info']['pages'][] = $options;
627
- } else {
628
- // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
629
- // and pos is either 'before' or 'after', saying where this page will fit.
630
- if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
631
- $i = array_search($options['rid'], $o['info']['pages']);
632
- if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
633
-
634
- // then there is a match
635
- // make a space
636
- switch ($options['pos']) {
637
- case 'before':
638
- $k = $i;
639
- break;
640
-
641
- case 'after':
642
- $k = $i + 1;
643
- break;
644
-
645
- default:
646
- $k = -1;
647
- break;
648
- }
649
-
650
- if ($k >= 0) {
651
- for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
652
- $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
653
- }
654
-
655
- $o['info']['pages'][$k] = $options['id'];
656
- }
657
- }
658
- }
659
- }
660
- break;
661
-
662
- case 'procset':
663
- $o['info']['procset'] = $options;
664
- break;
665
-
666
- case 'mediaBox':
667
- $o['info']['mediaBox'] = $options;
668
- // which should be an array of 4 numbers
669
- $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]);
670
- break;
671
-
672
- case 'font':
673
- $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']);
674
- break;
675
-
676
- case 'extGState':
677
- $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
678
- break;
679
-
680
- case 'xObject':
681
- $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']);
682
- break;
683
-
684
- case 'out':
685
- if (count($o['info']['pages'])) {
686
- $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
687
- foreach ($o['info']['pages'] as $v) {
688
- $res .= "$v 0 R\n";
689
- }
690
-
691
- $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
692
-
693
- if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
694
- isset($o['info']['procset']) ||
695
- (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
696
- ) {
697
- $res .= "\n/Resources <<";
698
-
699
- if (isset($o['info']['procset'])) {
700
- $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
701
- }
702
-
703
- if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
704
- $res .= "\n/Font << ";
705
- foreach ($o['info']['fonts'] as $finfo) {
706
- $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
707
- }
708
- $res .= "\n>>";
709
- }
710
-
711
- if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
712
- $res .= "\n/XObject << ";
713
- foreach ($o['info']['xObjects'] as $finfo) {
714
- $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
715
- }
716
- $res .= "\n>>";
717
- }
718
-
719
- if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
720
- $res .= "\n/ExtGState << ";
721
- foreach ($o['info']['extGStates'] as $gstate) {
722
- $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
723
- }
724
- $res .= "\n>>";
725
- }
726
-
727
- $res .= "\n>>";
728
- if (isset($o['info']['mediaBox'])) {
729
- $tmp = $o['info']['mediaBox'];
730
- $res .= "\n/MediaBox [" . sprintf(
731
- '%.3F %.3F %.3F %.3F',
732
- $tmp[0],
733
- $tmp[1],
734
- $tmp[2],
735
- $tmp[3]
736
- ) . ']';
737
- }
738
- }
739
-
740
- $res .= "\n >>\nendobj";
741
- } else {
742
- $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
743
- }
744
-
745
- return $res;
746
- }
747
-
748
- return null;
749
- }
750
-
751
- /**
752
- * define the outlines in the doc, empty for now
753
- *
754
- * @param $id
755
- * @param $action
756
- * @param string $options
757
- * @return string|null
758
- */
759
- protected function o_outlines($id, $action, $options = '')
760
- {
761
- if ($action !== 'new') {
762
- $o = &$this->objects[$id];
763
- }
764
-
765
- switch ($action) {
766
- case 'new':
767
- $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array()));
768
- $this->o_catalog($this->catalogId, 'outlines', $id);
769
- break;
770
-
771
- case 'outline':
772
- $o['info']['outlines'][] = $options;
773
- break;
774
-
775
- case 'out':
776
- if (count($o['info']['outlines'])) {
777
- $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
778
- foreach ($o['info']['outlines'] as $v) {
779
- $res .= "$v 0 R ";
780
- }
781
-
782
- $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
783
- } else {
784
- $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
785
- }
786
-
787
- return $res;
788
- }
789
-
790
- return null;
791
- }
792
-
793
- /**
794
- * an object to hold the font description
795
- *
796
- * @param $id
797
- * @param $action
798
- * @param string|array $options
799
- * @return string|null
800
- */
801
- protected function o_font($id, $action, $options = '')
802
- {
803
- if ($action !== 'new') {
804
- $o = &$this->objects[$id];
805
- }
806
-
807
- switch ($action) {
808
- case 'new':
809
- $this->objects[$id] = array(
810
- 't' => 'font',
811
- 'info' => array(
812
- 'name' => $options['name'],
813
- 'fontFileName' => $options['fontFileName'],
814
- 'SubType' => 'Type1'
815
- )
816
- );
817
- $fontNum = $this->numFonts;
818
- $this->objects[$id]['info']['fontNum'] = $fontNum;
819
-
820
- // deal with the encoding and the differences
821
- if (isset($options['differences'])) {
822
- // then we'll need an encoding dictionary
823
- $this->numObj++;
824
- $this->o_fontEncoding($this->numObj, 'new', $options);
825
- $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
826
- } else {
827
- if (isset($options['encoding'])) {
828
- // we can specify encoding here
829
- switch ($options['encoding']) {
830
- case 'WinAnsiEncoding':
831
- case 'MacRomanEncoding':
832
- case 'MacExpertEncoding':
833
- $this->objects[$id]['info']['encoding'] = $options['encoding'];
834
- break;
835
-
836
- case 'none':
837
- break;
838
-
839
- default:
840
- $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
841
- break;
842
- }
843
- } else {
844
- $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
845
- }
846
- }
847
-
848
- if ($this->fonts[$options['fontFileName']]['isUnicode']) {
849
- // For Unicode fonts, we need to incorporate font data into
850
- // sub-sections that are linked from the primary font section.
851
- // Look at o_fontGIDtoCID and o_fontDescendentCID functions
852
- // for more information.
853
- //
854
- // All of this code is adapted from the excellent changes made to
855
- // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
856
-
857
- $toUnicodeId = ++$this->numObj;
858
- $this->o_toUnicode($toUnicodeId, 'new');
859
- $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
860
-
861
- $cidFontId = ++$this->numObj;
862
- $this->o_fontDescendentCID($cidFontId, 'new', $options);
863
- $this->objects[$id]['info']['cidFont'] = $cidFontId;
864
- }
865
-
866
- // also tell the pages node about the new font
867
- $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
868
- break;
869
-
870
- case 'add':
871
- foreach ($options as $k => $v) {
872
- switch ($k) {
873
- case 'BaseFont':
874
- $o['info']['name'] = $v;
875
- break;
876
- case 'FirstChar':
877
- case 'LastChar':
878
- case 'Widths':
879
- case 'FontDescriptor':
880
- case 'SubType':
881
- $this->addMessage('o_font ' . $k . " : " . $v);
882
- $o['info'][$k] = $v;
883
- break;
884
- }
885
- }
886
-
887
- // pass values down to descendent font
888
- if (isset($o['info']['cidFont'])) {
889
- $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
890
- }
891
- break;
892
-
893
- case 'out':
894
- if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
895
- // For Unicode fonts, we need to incorporate font data into
896
- // sub-sections that are linked from the primary font section.
897
- // Look at o_fontGIDtoCID and o_fontDescendentCID functions
898
- // for more information.
899
- //
900
- // All of this code is adapted from the excellent changes made to
901
- // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
902
-
903
- $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
904
- $res .= "/BaseFont /" . $o['info']['name'] . "\n";
905
-
906
- // The horizontal identity mapping for 2-byte CIDs; may be used
907
- // with CIDFonts using any Registry, Ordering, and Supplement values.
908
- $res .= "/Encoding /Identity-H\n";
909
- $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
910
- $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
911
- $res .= ">>\n";
912
- $res .= "endobj";
913
- } else {
914
- $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
915
- $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
916
- $res .= "/BaseFont /" . $o['info']['name'] . "\n";
917
-
918
- if (isset($o['info']['encodingDictionary'])) {
919
- // then place a reference to the dictionary
920
- $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
921
- } else {
922
- if (isset($o['info']['encoding'])) {
923
- // use the specified encoding
924
- $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
925
- }
926
- }
927
-
928
- if (isset($o['info']['FirstChar'])) {
929
- $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
930
- }
931
-
932
- if (isset($o['info']['LastChar'])) {
933
- $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
934
- }
935
-
936
- if (isset($o['info']['Widths'])) {
937
- $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
938
- }
939
-
940
- if (isset($o['info']['FontDescriptor'])) {
941
- $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
942
- }
943
-
944
- $res .= ">>\n";
945
- $res .= "endobj";
946
- }
947
-
948
- return $res;
949
- }
950
-
951
- return null;
952
- }
953
-
954
- /**
955
- * A toUnicode section, needed for unicode fonts
956
- *
957
- * @param $id
958
- * @param $action
959
- * @return null|string
960
- */
961
- protected function o_toUnicode($id, $action)
962
- {
963
- switch ($action) {
964
- case 'new':
965
- $this->objects[$id] = array(
966
- 't' => 'toUnicode'
967
- );
968
- break;
969
- case 'add':
970
- break;
971
- case 'out':
972
- $ordering = '(UCS)';
973
- $registry = '(Adobe)';
974
-
975
- if ($this->encrypted) {
976
- $this->encryptInit($id);
977
- $ordering = $this->ARC4($ordering);
978
- $registry = $this->ARC4($registry);
979
- }
980
-
981
- $stream = <<<EOT
982
- /CIDInit /ProcSet findresource begin
983
- 12 dict begin
984
- begincmap
985
- /CIDSystemInfo
986
- <</Registry $registry
987
- /Ordering $ordering
988
- /Supplement 0
989
- >> def
990
- /CMapName /Adobe-Identity-UCS def
991
- /CMapType 2 def
992
- 1 begincodespacerange
993
- <0000> <FFFF>
994
- endcodespacerange
995
- 1 beginbfrange
996
- <0000> <FFFF> <0000>
997
- endbfrange
998
- endcmap
999
- CMapName currentdict /CMap defineresource pop
1000
- end
1001
- end
1002
- EOT;
1003
-
1004
- $res = "\n$id 0 obj\n";
1005
- $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1006
- $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1007
-
1008
- return $res;
1009
- }
1010
-
1011
- return null;
1012
- }
1013
-
1014
- /**
1015
- * a font descriptor, needed for including additional fonts
1016
- *
1017
- * @param $id
1018
- * @param $action
1019
- * @param string $options
1020
- * @return null|string
1021
- */
1022
- protected function o_fontDescriptor($id, $action, $options = '')
1023
- {
1024
- if ($action !== 'new') {
1025
- $o = &$this->objects[$id];
1026
- }
1027
-
1028
- switch ($action) {
1029
- case 'new':
1030
- $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options);
1031
- break;
1032
-
1033
- case 'out':
1034
- $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1035
- foreach ($o['info'] as $label => $value) {
1036
- switch ($label) {
1037
- case 'Ascent':
1038
- case 'CapHeight':
1039
- case 'Descent':
1040
- case 'Flags':
1041
- case 'ItalicAngle':
1042
- case 'StemV':
1043
- case 'AvgWidth':
1044
- case 'Leading':
1045
- case 'MaxWidth':
1046
- case 'MissingWidth':
1047
- case 'StemH':
1048
- case 'XHeight':
1049
- case 'CharSet':
1050
- if (mb_strlen($value, '8bit')) {
1051
- $res .= "/$label $value\n";
1052
- }
1053
-
1054
- break;
1055
- case 'FontFile':
1056
- case 'FontFile2':
1057
- case 'FontFile3':
1058
- $res .= "/$label $value 0 R\n";
1059
- break;
1060
-
1061
- case 'FontBBox':
1062
- $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1063
- break;
1064
-
1065
- case 'FontName':
1066
- $res .= "/$label /$value\n";
1067
- break;
1068
- }
1069
- }
1070
-
1071
- $res .= ">>\nendobj";
1072
-
1073
- return $res;
1074
- }
1075
-
1076
- return null;
1077
- }
1078
-
1079
- /**
1080
- * the font encoding
1081
- *
1082
- * @param $id
1083
- * @param $action
1084
- * @param string $options
1085
- * @return null|string
1086
- */
1087
- protected function o_fontEncoding($id, $action, $options = '')
1088
- {
1089
- if ($action !== 'new') {
1090
- $o = &$this->objects[$id];
1091
- }
1092
-
1093
- switch ($action) {
1094
- case 'new':
1095
- // the options array should contain 'differences' and maybe 'encoding'
1096
- $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options);
1097
- break;
1098
-
1099
- case 'out':
1100
- $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1101
- if (!isset($o['info']['encoding'])) {
1102
- $o['info']['encoding'] = 'WinAnsiEncoding';
1103
- }
1104
-
1105
- if ($o['info']['encoding'] !== 'none') {
1106
- $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1107
- }
1108
-
1109
- $res .= "/Differences \n[";
1110
-
1111
- $onum = -100;
1112
-
1113
- foreach ($o['info']['differences'] as $num => $label) {
1114
- if ($num != $onum + 1) {
1115
- // we cannot make use of consecutive numbering
1116
- $res .= "\n$num /$label";
1117
- } else {
1118
- $res .= " /$label";
1119
- }
1120
-
1121
- $onum = $num;
1122
- }
1123
-
1124
- $res .= "\n]\n>>\nendobj";
1125
-
1126
- return $res;
1127
- }
1128
-
1129
- return null;
1130
- }
1131
-
1132
- /**
1133
- * a descendent cid font, needed for unicode fonts
1134
- *
1135
- * @param $id
1136
- * @param $action
1137
- * @param string|array $options
1138
- * @return null|string
1139
- */
1140
- protected function o_fontDescendentCID($id, $action, $options = '')
1141
- {
1142
- if ($action !== 'new') {
1143
- $o = &$this->objects[$id];
1144
- }
1145
-
1146
- switch ($action) {
1147
- case 'new':
1148
- $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
1149
-
1150
- // we need a CID system info section
1151
- $cidSystemInfoId = ++$this->numObj;
1152
- $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1153
- $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1154
-
1155
- // and a CID to GID map
1156
- $cidToGidMapId = ++$this->numObj;
1157
- $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1158
- $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1159
- break;
1160
-
1161
- case 'add':
1162
- foreach ($options as $k => $v) {
1163
- switch ($k) {
1164
- case 'BaseFont':
1165
- $o['info']['name'] = $v;
1166
- break;
1167
-
1168
- case 'FirstChar':
1169
- case 'LastChar':
1170
- case 'MissingWidth':
1171
- case 'FontDescriptor':
1172
- case 'SubType':
1173
- $this->addMessage("o_fontDescendentCID $k : $v");
1174
- $o['info'][$k] = $v;
1175
- break;
1176
- }
1177
- }
1178
-
1179
- // pass values down to cid to gid map
1180
- $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1181
- break;
1182
-
1183
- case 'out':
1184
- $res = "\n$id 0 obj\n";
1185
- $res .= "<</Type /Font\n";
1186
- $res .= "/Subtype /CIDFontType2\n";
1187
- $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1188
- $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1189
- // if (isset($o['info']['FirstChar'])) {
1190
- // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1191
- // }
1192
-
1193
- // if (isset($o['info']['LastChar'])) {
1194
- // $res.= "/LastChar ".$o['info']['LastChar']."\n";
1195
- // }
1196
- if (isset($o['info']['FontDescriptor'])) {
1197
- $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1198
- }
1199
-
1200
- if (isset($o['info']['MissingWidth'])) {
1201
- $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1202
- }
1203
-
1204
- if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1205
- $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1206
- $w = '';
1207
- foreach ($cid_widths as $cid => $width) {
1208
- $w .= "$cid [$width] ";
1209
- }
1210
- $res .= "/W [$w]\n";
1211
- }
1212
-
1213
- $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1214
- $res .= ">>\n";
1215
- $res .= "endobj";
1216
-
1217
- return $res;
1218
- }
1219
-
1220
- return null;
1221
- }
1222
-
1223
- /**
1224
- * CID system info section, needed for unicode fonts
1225
- *
1226
- * @param $id
1227
- * @param $action
1228
- * @return null|string
1229
- */
1230
- protected function o_cidSystemInfo($id, $action)
1231
- {
1232
- switch ($action) {
1233
- case 'new':
1234
- $this->objects[$id] = array(
1235
- 't' => 'cidSystemInfo'
1236
- );
1237
- break;
1238
- case 'add':
1239
- break;
1240
- case 'out':
1241
- $ordering = '(UCS)';
1242
- $registry = '(Adobe)';
1243
-
1244
- if ($this->encrypted) {
1245
- $this->encryptInit($id);
1246
- $ordering = $this->ARC4($ordering);
1247
- $registry = $this->ARC4($registry);
1248
- }
1249
-
1250
-
1251
- $res = "\n$id 0 obj\n";
1252
-
1253
- $res .= '<</Registry ' . $registry . "\n"; // A string identifying an issuer of character collections
1254
- $res .= '/Ordering ' . $ordering . "\n"; // A string that uniquely names a character collection issued by a specific registry
1255
- $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1256
- $res .= ">>";
1257
-
1258
- $res .= "\nendobj";;
1259
-
1260
- return $res;
1261
- }
1262
-
1263
- return null;
1264
- }
1265
-
1266
- /**
1267
- * a font glyph to character map, needed for unicode fonts
1268
- *
1269
- * @param $id
1270
- * @param $action
1271
- * @param string $options
1272
- * @return null|string
1273
- */
1274
- protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1275
- {
1276
- if ($action !== 'new') {
1277
- $o = &$this->objects[$id];
1278
- }
1279
-
1280
- switch ($action) {
1281
- case 'new':
1282
- $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
1283
- break;
1284
-
1285
- case 'out':
1286
- $res = "\n$id 0 obj\n";
1287
- $fontFileName = $o['info']['fontFileName'];
1288
- $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1289
-
1290
- $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1291
- $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1292
-
1293
- if (!$compressed && isset($o['raw'])) {
1294
- $res .= $tmp;
1295
- } else {
1296
- $res .= "<<";
1297
-
1298
- if (!$compressed && $this->compressionReady && $this->options['compression']) {
1299
- // then implement ZLIB based compression on this content stream
1300
- $compressed = true;
1301
- $tmp = gzcompress($tmp, 6);
1302
- }
1303
- if ($compressed) {
1304
- $res .= "\n/Filter /FlateDecode";
1305
- }
1306
-
1307
- if ($this->encrypted) {
1308
- $this->encryptInit($id);
1309
- $tmp = $this->ARC4($tmp);
1310
- }
1311
-
1312
- $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1313
- }
1314
-
1315
- $res .= "\nendobj";
1316
-
1317
- return $res;
1318
- }
1319
-
1320
- return null;
1321
- }
1322
-
1323
- /**
1324
- * the document procset, solves some problems with printing to old PS printers
1325
- *
1326
- * @param $id
1327
- * @param $action
1328
- * @param string $options
1329
- * @return null|string
1330
- */
1331
- protected function o_procset($id, $action, $options = '')
1332
- {
1333
- if ($action !== 'new') {
1334
- $o = &$this->objects[$id];
1335
- }
1336
-
1337
- switch ($action) {
1338
- case 'new':
1339
- $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1));
1340
- $this->o_pages($this->currentNode, 'procset', $id);
1341
- $this->procsetObjectId = $id;
1342
- break;
1343
-
1344
- case 'add':
1345
- // this is to add new items to the procset list, despite the fact that this is considered
1346
- // obsolete, the items are required for printing to some postscript printers
1347
- switch ($options) {
1348
- case 'ImageB':
1349
- case 'ImageC':
1350
- case 'ImageI':
1351
- $o['info'][$options] = 1;
1352
- break;
1353
- }
1354
- break;
1355
-
1356
- case 'out':
1357
- $res = "\n$id 0 obj\n[";
1358
- foreach ($o['info'] as $label => $val) {
1359
- $res .= "/$label ";
1360
- }
1361
- $res .= "]\nendobj";
1362
-
1363
- return $res;
1364
- }
1365
-
1366
- return null;
1367
- }
1368
-
1369
- /**
1370
- * define the document information
1371
- *
1372
- * @param $id
1373
- * @param $action
1374
- * @param string $options
1375
- * @return null|string
1376
- */
1377
- protected function o_info($id, $action, $options = '')
1378
- {
1379
- switch ($action) {
1380
- case 'new':
1381
- $this->infoObject = $id;
1382
- $date = 'D:' . @date('Ymd');
1383
- $this->objects[$id] = array(
1384
- 't' => 'info',
1385
- 'info' => array(
1386
- 'Producer' => 'CPDF (dompdf)',
1387
- 'CreationDate' => $date
1388
- )
1389
- );
1390
- break;
1391
- case 'Title':
1392
- case 'Author':
1393
- case 'Subject':
1394
- case 'Keywords':
1395
- case 'Creator':
1396
- case 'Producer':
1397
- case 'CreationDate':
1398
- case 'ModDate':
1399
- case 'Trapped':
1400
- $this->objects[$id]['info'][$action] = $options;
1401
- break;
1402
-
1403
- case 'out':
1404
- $encrypted = $this->encrypted;
1405
- if ($encrypted) {
1406
- $this->encryptInit($id);
1407
- }
1408
-
1409
- $res = "\n$id 0 obj\n<<\n";
1410
- $o = &$this->objects[$id];
1411
- foreach ($o['info'] as $k => $v) {
1412
- $res .= "/$k (";
1413
-
1414
- // dates must be outputted as-is, without Unicode transformations
1415
- if ($k !== 'CreationDate' && $k !== 'ModDate') {
1416
- $v = $this->filterText($v, true, false);
1417
- }
1418
-
1419
- if ($encrypted) {
1420
- $v = $this->ARC4($v);
1421
- }
1422
-
1423
- $res .= $v;
1424
- $res .= ")\n";
1425
- }
1426
-
1427
- $res .= ">>\nendobj";
1428
-
1429
- return $res;
1430
- }
1431
-
1432
- return null;
1433
- }
1434
-
1435
- /**
1436
- * an action object, used to link to URLS initially
1437
- *
1438
- * @param $id
1439
- * @param $action
1440
- * @param string $options
1441
- * @return null|string
1442
- */
1443
- protected function o_action($id, $action, $options = '')
1444
- {
1445
- if ($action !== 'new') {
1446
- $o = &$this->objects[$id];
1447
- }
1448
-
1449
- switch ($action) {
1450
- case 'new':
1451
- if (is_array($options)) {
1452
- $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']);
1453
- } else {
1454
- // then assume a URI action
1455
- $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI');
1456
- }
1457
- break;
1458
-
1459
- case 'out':
1460
- if ($this->encrypted) {
1461
- $this->encryptInit($id);
1462
- }
1463
-
1464
- $res = "\n$id 0 obj\n<< /Type /Action";
1465
- switch ($o['type']) {
1466
- case 'ilink':
1467
- if (!isset($this->destinations[(string)$o['info']['label']])) {
1468
- break;
1469
- }
1470
-
1471
- // there will be an 'label' setting, this is the name of the destination
1472
- $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1473
- break;
1474
-
1475
- case 'URI':
1476
- $res .= "\n/S /URI\n/URI (";
1477
- if ($this->encrypted) {
1478
- $res .= $this->filterText($this->ARC4($o['info']), false, false);
1479
- } else {
1480
- $res .= $this->filterText($o['info'], false, false);
1481
- }
1482
-
1483
- $res .= ")";
1484
- break;
1485
- }
1486
-
1487
- $res .= "\n>>\nendobj";
1488
-
1489
- return $res;
1490
- }
1491
-
1492
- return null;
1493
- }
1494
-
1495
- /**
1496
- * an annotation object, this will add an annotation to the current page.
1497
- * initially will support just link annotations
1498
- *
1499
- * @param $id
1500
- * @param $action
1501
- * @param string $options
1502
- * @return null|string
1503
- */
1504
- protected function o_annotation($id, $action, $options = '')
1505
- {
1506
- if ($action !== 'new') {
1507
- $o = &$this->objects[$id];
1508
- }
1509
-
1510
- switch ($action) {
1511
- case 'new':
1512
- // add the annotation to the current page
1513
- $pageId = $this->currentPage;
1514
- $this->o_page($pageId, 'annot', $id);
1515
-
1516
- // and add the action object which is going to be required
1517
- switch ($options['type']) {
1518
- case 'link':
1519
- $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1520
- $this->numObj++;
1521
- $this->o_action($this->numObj, 'new', $options['url']);
1522
- $this->objects[$id]['info']['actionId'] = $this->numObj;
1523
- break;
1524
-
1525
- case 'ilink':
1526
- // this is to a named internal link
1527
- $label = $options['label'];
1528
- $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1529
- $this->numObj++;
1530
- $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label));
1531
- $this->objects[$id]['info']['actionId'] = $this->numObj;
1532
- break;
1533
- }
1534
- break;
1535
-
1536
- case 'out':
1537
- $res = "\n$id 0 obj\n<< /Type /Annot";
1538
- switch ($o['info']['type']) {
1539
- case 'link':
1540
- case 'ilink':
1541
- $res .= "\n/Subtype /Link";
1542
- break;
1543
- }
1544
- $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1545
- $res .= "\n/Border [0 0 0]";
1546
- $res .= "\n/H /I";
1547
- $res .= "\n/Rect [ ";
1548
-
1549
- foreach ($o['info']['rect'] as $v) {
1550
- $res .= sprintf("%.4F ", $v);
1551
- }
1552
-
1553
- $res .= "]";
1554
- $res .= "\n>>\nendobj";
1555
-
1556
- return $res;
1557
- }
1558
-
1559
- return null;
1560
- }
1561
-
1562
- /**
1563
- * a page object, it also creates a contents object to hold its contents
1564
- *
1565
- * @param $id
1566
- * @param $action
1567
- * @param string $options
1568
- * @return null|string
1569
- */
1570
- protected function o_page($id, $action, $options = '')
1571
- {
1572
- if ($action !== 'new') {
1573
- $o = &$this->objects[$id];
1574
- }
1575
-
1576
- switch ($action) {
1577
- case 'new':
1578
- $this->numPages++;
1579
- $this->objects[$id] = array(
1580
- 't' => 'page',
1581
- 'info' => array(
1582
- 'parent' => $this->currentNode,
1583
- 'pageNum' => $this->numPages,
1584
- 'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1585
- )
1586
- );
1587
-
1588
- if (is_array($options)) {
1589
- // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1590
- $options['id'] = $id;
1591
- $this->o_pages($this->currentNode, 'page', $options);
1592
- } else {
1593
- $this->o_pages($this->currentNode, 'page', $id);
1594
- }
1595
-
1596
- $this->currentPage = $id;
1597
- //make a contents object to go with this page
1598
- $this->numObj++;
1599
- $this->o_contents($this->numObj, 'new', $id);
1600
- $this->currentContents = $this->numObj;
1601
- $this->objects[$id]['info']['contents'] = array();
1602
- $this->objects[$id]['info']['contents'][] = $this->numObj;
1603
-
1604
- $match = ($this->numPages % 2 ? 'odd' : 'even');
1605
- foreach ($this->addLooseObjects as $oId => $target) {
1606
- if ($target === 'all' || $match === $target) {
1607
- $this->objects[$id]['info']['contents'][] = $oId;
1608
- }
1609
- }
1610
- break;
1611
-
1612
- case 'content':
1613
- $o['info']['contents'][] = $options;
1614
- break;
1615
-
1616
- case 'annot':
1617
- // add an annotation to this page
1618
- if (!isset($o['info']['annot'])) {
1619
- $o['info']['annot'] = array();
1620
- }
1621
-
1622
- // $options should contain the id of the annotation dictionary
1623
- $o['info']['annot'][] = $options;
1624
- break;
1625
-
1626
- case 'out':
1627
- $res = "\n$id 0 obj\n<< /Type /Page";
1628
- if (isset($o['info']['mediaBox'])) {
1629
- $tmp = $o['info']['mediaBox'];
1630
- $res .= "\n/MediaBox [" . sprintf(
1631
- '%.3F %.3F %.3F %.3F',
1632
- $tmp[0],
1633
- $tmp[1],
1634
- $tmp[2],
1635
- $tmp[3]
1636
- ) . ']';
1637
- }
1638
- $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1639
-
1640
- if (isset($o['info']['annot'])) {
1641
- $res .= "\n/Annots [";
1642
- foreach ($o['info']['annot'] as $aId) {
1643
- $res .= " $aId 0 R";
1644
- }
1645
- $res .= " ]";
1646
- }
1647
-
1648
- $count = count($o['info']['contents']);
1649
- if ($count == 1) {
1650
- $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1651
- } else {
1652
- if ($count > 1) {
1653
- $res .= "\n/Contents [\n";
1654
-
1655
- // reverse the page contents so added objects are below normal content
1656
- //foreach (array_reverse($o['info']['contents']) as $cId) {
1657
- // Back to normal now that I've got transparency working --Benj
1658
- foreach ($o['info']['contents'] as $cId) {
1659
- $res .= "$cId 0 R\n";
1660
- }
1661
- $res .= "]";
1662
- }
1663
- }
1664
-
1665
- $res .= "\n>>\nendobj";
1666
-
1667
- return $res;
1668
- }
1669
-
1670
- return null;
1671
- }
1672
-
1673
- /**
1674
- * the contents objects hold all of the content which appears on pages
1675
- *
1676
- * @param $id
1677
- * @param $action
1678
- * @param string|array $options
1679
- * @return null|string
1680
- */
1681
- protected function o_contents($id, $action, $options = '')
1682
- {
1683
- if ($action !== 'new') {
1684
- $o = &$this->objects[$id];
1685
- }
1686
-
1687
- switch ($action) {
1688
- case 'new':
1689
- $this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array());
1690
- if (mb_strlen($options, '8bit') && intval($options)) {
1691
- // then this contents is the primary for a page
1692
- $this->objects[$id]['onPage'] = $options;
1693
- } else {
1694
- if ($options === 'raw') {
1695
- // then this page contains some other type of system object
1696
- $this->objects[$id]['raw'] = 1;
1697
- }
1698
- }
1699
- break;
1700
-
1701
- case 'add':
1702
- // add more options to the declaration
1703
- foreach ($options as $k => $v) {
1704
- $o['info'][$k] = $v;
1705
- }
1706
-
1707
- case 'out':
1708
- $tmp = $o['c'];
1709
- $res = "\n$id 0 obj\n";
1710
-
1711
- if (isset($this->objects[$id]['raw'])) {
1712
- $res .= $tmp;
1713
- } else {
1714
- $res .= "<<";
1715
- if ($this->compressionReady && $this->options['compression']) {
1716
- // then implement ZLIB based compression on this content stream
1717
- $res .= " /Filter /FlateDecode";
1718
- $tmp = gzcompress($tmp, 6);
1719
- }
1720
-
1721
- if ($this->encrypted) {
1722
- $this->encryptInit($id);
1723
- $tmp = $this->ARC4($tmp);
1724
- }
1725
-
1726
- foreach ($o['info'] as $k => $v) {
1727
- $res .= "\n/$k $v";
1728
- }
1729
-
1730
- $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
1731
- }
1732
-
1733
- $res .= "\nendobj";
1734
-
1735
- return $res;
1736
- }
1737
-
1738
- return null;
1739
- }
1740
-
1741
- /**
1742
- * @param $id
1743
- * @param $action
1744
- * @return string|null
1745
- */
1746
- protected function o_embedjs($id, $action)
1747
- {
1748
- switch ($action) {
1749
- case 'new':
1750
- $this->objects[$id] = array(
1751
- 't' => 'embedjs',
1752
- 'info' => array(
1753
- 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
1754
- )
1755
- );
1756
- break;
1757
-
1758
- case 'out':
1759
- $o = &$this->objects[$id];
1760
- $res = "\n$id 0 obj\n<< ";
1761
- foreach ($o['info'] as $k => $v) {
1762
- $res .= "\n/$k $v";
1763
- }
1764
- $res .= "\n>>\nendobj";
1765
-
1766
- return $res;
1767
- }
1768
-
1769
- return null;
1770
- }
1771
-
1772
- /**
1773
- * @param $id
1774
- * @param $action
1775
- * @param string $code
1776
- * @return null|string
1777
- */
1778
- protected function o_javascript($id, $action, $code = '')
1779
- {
1780
- switch ($action) {
1781
- case 'new':
1782
- $this->objects[$id] = array(
1783
- 't' => 'javascript',
1784
- 'info' => array(
1785
- 'S' => '/JavaScript',
1786
- 'JS' => '(' . $this->filterText($code, true, false) . ')',
1787
- )
1788
- );
1789
- break;
1790
-
1791
- case 'out':
1792
- $o = &$this->objects[$id];
1793
- $res = "\n$id 0 obj\n<< ";
1794
-
1795
- foreach ($o['info'] as $k => $v) {
1796
- $res .= "\n/$k $v";
1797
- }
1798
- $res .= "\n>>\nendobj";
1799
-
1800
- return $res;
1801
- }
1802
-
1803
- return null;
1804
- }
1805
-
1806
- /**
1807
- * an image object, will be an XObject in the document, includes description and data
1808
- *
1809
- * @param $id
1810
- * @param $action
1811
- * @param string $options
1812
- * @return null|string
1813
- */
1814
- protected function o_image($id, $action, $options = '')
1815
- {
1816
- switch ($action) {
1817
- case 'new':
1818
- // make the new object
1819
- $this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array());
1820
-
1821
- $info =& $this->objects[$id]['info'];
1822
-
1823
- $info['Type'] = '/XObject';
1824
- $info['Subtype'] = '/Image';
1825
- $info['Width'] = $options['iw'];
1826
- $info['Height'] = $options['ih'];
1827
-
1828
- if (isset($options['masked']) && $options['masked']) {
1829
- $info['SMask'] = ($this->numObj - 1) . ' 0 R';
1830
- }
1831
-
1832
- if (!isset($options['type']) || $options['type'] === 'jpg') {
1833
- if (!isset($options['channels'])) {
1834
- $options['channels'] = 3;
1835
- }
1836
-
1837
- switch ($options['channels']) {
1838
- case 1:
1839
- $info['ColorSpace'] = '/DeviceGray';
1840
- break;
1841
- case 4:
1842
- $info['ColorSpace'] = '/DeviceCMYK';
1843
- break;
1844
- default:
1845
- $info['ColorSpace'] = '/DeviceRGB';
1846
- break;
1847
- }
1848
-
1849
- if ($info['ColorSpace'] === '/DeviceCMYK') {
1850
- $info['Decode'] = '[1 0 1 0 1 0 1 0]';
1851
- }
1852
-
1853
- $info['Filter'] = '/DCTDecode';
1854
- $info['BitsPerComponent'] = 8;
1855
- } else {
1856
- if ($options['type'] === 'png') {
1857
- $info['Filter'] = '/FlateDecode';
1858
- $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
1859
-
1860
- if ($options['isMask']) {
1861
- $info['ColorSpace'] = '/DeviceGray';
1862
- } else {
1863
- if (mb_strlen($options['pdata'], '8bit')) {
1864
- $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
1865
- $this->numObj++;
1866
- $this->o_contents($this->numObj, 'new');
1867
- $this->objects[$this->numObj]['c'] = $options['pdata'];
1868
- $tmp .= $this->numObj . ' 0 R';
1869
- $tmp .= ' ]';
1870
- $info['ColorSpace'] = $tmp;
1871
-
1872
- if (isset($options['transparency'])) {
1873
- $transparency = $options['transparency'];
1874
- switch ($transparency['type']) {
1875
- case 'indexed':
1876
- $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1877
- $info['Mask'] = $tmp;
1878
- break;
1879
-
1880
- case 'color-key':
1881
- $tmp = ' [ ' .
1882
- $transparency['r'] . ' ' . $transparency['r'] .
1883
- $transparency['g'] . ' ' . $transparency['g'] .
1884
- $transparency['b'] . ' ' . $transparency['b'] .
1885
- ' ] ';
1886
- $info['Mask'] = $tmp;
1887
- break;
1888
- }
1889
- }
1890
- } else {
1891
- if (isset($options['transparency'])) {
1892
- $transparency = $options['transparency'];
1893
-
1894
- switch ($transparency['type']) {
1895
- case 'indexed':
1896
- $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1897
- $info['Mask'] = $tmp;
1898
- break;
1899
-
1900
- case 'color-key':
1901
- $tmp = ' [ ' .
1902
- $transparency['r'] . ' ' . $transparency['r'] . ' ' .
1903
- $transparency['g'] . ' ' . $transparency['g'] . ' ' .
1904
- $transparency['b'] . ' ' . $transparency['b'] .
1905
- ' ] ';
1906
- $info['Mask'] = $tmp;
1907
- break;
1908
- }
1909
- }
1910
- $info['ColorSpace'] = '/' . $options['color'];
1911
- }
1912
- }
1913
-
1914
- $info['BitsPerComponent'] = $options['bitsPerComponent'];
1915
- }
1916
- }
1917
-
1918
- // assign it a place in the named resource dictionary as an external object, according to
1919
- // the label passed in with it.
1920
- $this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id));
1921
-
1922
- // also make sure that we have the right procset object for it.
1923
- $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
1924
- break;
1925
-
1926
- case 'out':
1927
- $o = &$this->objects[$id];
1928
- $tmp = &$o['data'];
1929
- $res = "\n$id 0 obj\n<<";
1930
-
1931
- foreach ($o['info'] as $k => $v) {
1932
- $res .= "\n/$k $v";
1933
- }
1934
-
1935
- if ($this->encrypted) {
1936
- $this->encryptInit($id);
1937
- $tmp = $this->ARC4($tmp);
1938
- }
1939
-
1940
- $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
1941
-
1942
- return $res;
1943
- }
1944
-
1945
- return null;
1946
- }
1947
-
1948
- /**
1949
- * graphics state object
1950
- *
1951
- * @param $id
1952
- * @param $action
1953
- * @param string $options
1954
- * @return null|string
1955
- */
1956
- protected function o_extGState($id, $action, $options = "")
1957
- {
1958
- static $valid_params = array(
1959
- "LW",
1960
- "LC",
1961
- "LC",
1962
- "LJ",
1963
- "ML",
1964
- "D",
1965
- "RI",
1966
- "OP",
1967
- "op",
1968
- "OPM",
1969
- "Font",
1970
- "BG",
1971
- "BG2",
1972
- "UCR",
1973
- "TR",
1974
- "TR2",
1975
- "HT",
1976
- "FL",
1977
- "SM",
1978
- "SA",
1979
- "BM",
1980
- "SMask",
1981
- "CA",
1982
- "ca",
1983
- "AIS",
1984
- "TK"
1985
- );
1986
-
1987
- switch ($action) {
1988
- case "new":
1989
- $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
1990
-
1991
- // Tell the pages about the new resource
1992
- $this->numStates++;
1993
- $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
1994
- break;
1995
-
1996
- case "out":
1997
- $o = &$this->objects[$id];
1998
- $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
1999
-
2000
- foreach ($o["info"] as $k => $v) {
2001
- if (!in_array($k, $valid_params)) {
2002
- continue;
2003
- }
2004
- $res .= "/$k $v\n";
2005
- }
2006
-
2007
- $res .= ">>\nendobj";
2008
-
2009
- return $res;
2010
- }
2011
-
2012
- return null;
2013
- }
2014
-
2015
- /**
2016
- * encryption object.
2017
- *
2018
- * @param $id
2019
- * @param $action
2020
- * @param string $options
2021
- * @return string|null
2022
- */
2023
- protected function o_encryption($id, $action, $options = '')
2024
- {
2025
- switch ($action) {
2026
- case 'new':
2027
- // make the new object
2028
- $this->objects[$id] = array('t' => 'encryption', 'info' => $options);
2029
- $this->arc4_objnum = $id;
2030
- break;
2031
-
2032
- case 'keys':
2033
- // figure out the additional parameters required
2034
- $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2035
- . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2036
- . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2037
- . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2038
-
2039
- $info = $this->objects[$id]['info'];
2040
-
2041
- $len = mb_strlen($info['owner'], '8bit');
2042
-
2043
- if ($len > 32) {
2044
- $owner = substr($info['owner'], 0, 32);
2045
- } else {
2046
- if ($len < 32) {
2047
- $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2048
- } else {
2049
- $owner = $info['owner'];
2050
- }
2051
- }
2052
-
2053
- $len = mb_strlen($info['user'], '8bit');
2054
- if ($len > 32) {
2055
- $user = substr($info['user'], 0, 32);
2056
- } else {
2057
- if ($len < 32) {
2058
- $user = $info['user'] . substr($pad, 0, 32 - $len);
2059
- } else {
2060
- $user = $info['user'];
2061
- }
2062
- }
2063
-
2064
- $tmp = $this->md5_16($owner);
2065
- $okey = substr($tmp, 0, 5);
2066
- $this->ARC4_init($okey);
2067
- $ovalue = $this->ARC4($user);
2068
- $this->objects[$id]['info']['O'] = $ovalue;
2069
-
2070
- // now make the u value, phew.
2071
- $tmp = $this->md5_16(
2072
- $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2073
- );
2074
-
2075
- $ukey = substr($tmp, 0, 5);
2076
- $this->ARC4_init($ukey);
2077
- $this->encryptionKey = $ukey;
2078
- $this->encrypted = true;
2079
- $uvalue = $this->ARC4($pad);
2080
- $this->objects[$id]['info']['U'] = $uvalue;
2081
- // initialize the arc4 array
2082
- break;
2083
-
2084
- case 'out':
2085
- $o = &$this->objects[$id];
2086
-
2087
- $res = "\n$id 0 obj\n<<";
2088
- $res .= "\n/Filter /Standard";
2089
- $res .= "\n/V 1";
2090
- $res .= "\n/R 2";
2091
- $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2092
- $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2093
- // and the p-value needs to be converted to account for the twos-complement approach
2094
- $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2095
- $res .= "\n/P " . ($o['info']['p']);
2096
- $res .= "\n>>\nendobj";
2097
-
2098
- return $res;
2099
- }
2100
-
2101
- return null;
2102
- }
2103
-
2104
- /**
2105
- * ARC4 functions
2106
- * A series of function to implement ARC4 encoding in PHP
2107
- */
2108
-
2109
- /**
2110
- * calculate the 16 byte version of the 128 bit md5 digest of the string
2111
- *
2112
- * @param $string
2113
- * @return string
2114
- */
2115
- function md5_16($string)
2116
- {
2117
- $tmp = md5($string);
2118
- $out = '';
2119
- for ($i = 0; $i <= 30; $i = $i + 2) {
2120
- $out .= chr(hexdec(substr($tmp, $i, 2)));
2121
- }
2122
-
2123
- return $out;
2124
- }
2125
-
2126
- /**
2127
- * initialize the encryption for processing a particular object
2128
- *
2129
- * @param $id
2130
- */
2131
- function encryptInit($id)
2132
- {
2133
- $tmp = $this->encryptionKey;
2134
- $hex = dechex($id);
2135
- if (mb_strlen($hex, '8bit') < 6) {
2136
- $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2137
- }
2138
- $tmp .= chr(hexdec(substr($hex, 4, 2)))
2139
- . chr(hexdec(substr($hex, 2, 2)))
2140
- . chr(hexdec(substr($hex, 0, 2)))
2141
- . chr(0)
2142
- . chr(0)
2143
- ;
2144
- $key = $this->md5_16($tmp);
2145
- $this->ARC4_init(substr($key, 0, 10));
2146
- }
2147
-
2148
- /**
2149
- * initialize the ARC4 encryption
2150
- *
2151
- * @param string $key
2152
- */
2153
- function ARC4_init($key = '')
2154
- {
2155
- $this->arc4 = '';
2156
-
2157
- // setup the control array
2158
- if (mb_strlen($key, '8bit') == 0) {
2159
- return;
2160
- }
2161
-
2162
- $k = '';
2163
- while (mb_strlen($k, '8bit') < 256) {
2164
- $k .= $key;
2165
- }
2166
-
2167
- $k = substr($k, 0, 256);
2168
- for ($i = 0; $i < 256; $i++) {
2169
- $this->arc4 .= chr($i);
2170
- }
2171
-
2172
- $j = 0;
2173
-
2174
- for ($i = 0; $i < 256; $i++) {
2175
- $t = $this->arc4[$i];
2176
- $j = ($j + ord($t) + ord($k[$i])) % 256;
2177
- $this->arc4[$i] = $this->arc4[$j];
2178
- $this->arc4[$j] = $t;
2179
- }
2180
- }
2181
-
2182
- /**
2183
- * ARC4 encrypt a text string
2184
- *
2185
- * @param $text
2186
- * @return string
2187
- */
2188
- function ARC4($text)
2189
- {
2190
- $len = mb_strlen($text, '8bit');
2191
- $a = 0;
2192
- $b = 0;
2193
- $c = $this->arc4;
2194
- $out = '';
2195
- for ($i = 0; $i < $len; $i++) {
2196
- $a = ($a + 1) % 256;
2197
- $t = $c[$a];
2198
- $b = ($b + ord($t)) % 256;
2199
- $c[$a] = $c[$b];
2200
- $c[$b] = $t;
2201
- $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
2202
- $out .= chr(ord($text[$i]) ^ $k);
2203
- }
2204
-
2205
- return $out;
2206
- }
2207
-
2208
- /**
2209
- * functions which can be called to adjust or add to the document
2210
- */
2211
-
2212
- /**
2213
- * add a link in the document to an external URL
2214
- *
2215
- * @param $url
2216
- * @param $x0
2217
- * @param $y0
2218
- * @param $x1
2219
- * @param $y1
2220
- */
2221
- function addLink($url, $x0, $y0, $x1, $y1)
2222
- {
2223
- $this->numObj++;
2224
- $info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1));
2225
- $this->o_annotation($this->numObj, 'new', $info);
2226
- }
2227
-
2228
- /**
2229
- * add a link in the document to an internal destination (ie. within the document)
2230
- *
2231
- * @param $label
2232
- * @param $x0
2233
- * @param $y0
2234
- * @param $x1
2235
- * @param $y1
2236
- */
2237
- function addInternalLink($label, $x0, $y0, $x1, $y1)
2238
- {
2239
- $this->numObj++;
2240
- $info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1));
2241
- $this->o_annotation($this->numObj, 'new', $info);
2242
- }
2243
-
2244
- /**
2245
- * set the encryption of the document
2246
- * can be used to turn it on and/or set the passwords which it will have.
2247
- * also the functions that the user will have are set here, such as print, modify, add
2248
- *
2249
- * @param string $userPass
2250
- * @param string $ownerPass
2251
- * @param array $pc
2252
- */
2253
- function setEncryption($userPass = '', $ownerPass = '', $pc = array())
2254
- {
2255
- $p = bindec("11000000");
2256
-
2257
- $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32);
2258
-
2259
- foreach ($pc as $k => $v) {
2260
- if ($v && isset($options[$k])) {
2261
- $p += $options[$k];
2262
- } else {
2263
- if (isset($options[$v])) {
2264
- $p += $options[$v];
2265
- }
2266
- }
2267
- }
2268
-
2269
- // implement encryption on the document
2270
- if ($this->arc4_objnum == 0) {
2271
- // then the block does not exist already, add it.
2272
- $this->numObj++;
2273
- if (mb_strlen($ownerPass) == 0) {
2274
- $ownerPass = $userPass;
2275
- }
2276
-
2277
- $this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p));
2278
- }
2279
- }
2280
-
2281
- /**
2282
- * should be used for internal checks, not implemented as yet
2283
- */
2284
- function checkAllHere()
2285
- {
2286
- }
2287
-
2288
- /**
2289
- * return the pdf stream as a string returned from the function
2290
- *
2291
- * @param bool $debug
2292
- * @return string
2293
- */
2294
- function output($debug = false)
2295
- {
2296
- if ($debug) {
2297
- // turn compression off
2298
- $this->options['compression'] = false;
2299
- }
2300
-
2301
- if ($this->javascript) {
2302
- $this->numObj++;
2303
-
2304
- $js_id = $this->numObj;
2305
- $this->o_embedjs($js_id, 'new');
2306
- $this->o_javascript(++$this->numObj, 'new', $this->javascript);
2307
-
2308
- $id = $this->catalogId;
2309
-
2310
- $this->o_catalog($id, 'javascript', $js_id);
2311
- }
2312
-
2313
- if ($this->fileIdentifier === '') {
2314
- $tmp = implode('', $this->objects[$this->infoObject]['info']);
2315
- $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
2316
- }
2317
-
2318
- if ($this->arc4_objnum) {
2319
- $this->o_encryption($this->arc4_objnum, 'keys');
2320
- $this->ARC4_init($this->encryptionKey);
2321
- }
2322
-
2323
- $this->checkAllHere();
2324
-
2325
- $xref = array();
2326
- $content = '%PDF-1.3';
2327
- $pos = mb_strlen($content, '8bit');
2328
-
2329
- foreach ($this->objects as $k => $v) {
2330
- $tmp = 'o_' . $v['t'];
2331
- $cont = $this->$tmp($k, 'out');
2332
- $content .= $cont;
2333
- $xref[] = $pos + 1; //+1 to account for \n at the start of each object
2334
- $pos += mb_strlen($cont, '8bit');
2335
- }
2336
-
2337
- $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
2338
-
2339
- foreach ($xref as $p) {
2340
- $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
2341
- }
2342
-
2343
- $content .= "trailer\n<<\n" .
2344
- '/Size ' . (count($xref) + 1) . "\n" .
2345
- '/Root 1 0 R' . "\n" .
2346
- '/Info ' . $this->infoObject . " 0 R\n"
2347
- ;
2348
-
2349
- // if encryption has been applied to this document then add the marker for this dictionary
2350
- if ($this->arc4_objnum > 0) {
2351
- $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
2352
- }
2353
-
2354
- $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
2355
-
2356
- // account for \n added at start of xref table
2357
- $pos++;
2358
-
2359
- $content .= ">>\nstartxref\n$pos\n%%EOF\n";
2360
-
2361
- return $content;
2362
- }
2363
-
2364
- /**
2365
- * initialize a new document
2366
- * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
2367
- * this function is called automatically by the constructor function
2368
- *
2369
- * @param array $pageSize
2370
- */
2371
- private function newDocument($pageSize = array(0, 0, 612, 792))
2372
- {
2373
- $this->numObj = 0;
2374
- $this->objects = array();
2375
-
2376
- $this->numObj++;
2377
- $this->o_catalog($this->numObj, 'new');
2378
-
2379
- $this->numObj++;
2380
- $this->o_outlines($this->numObj, 'new');
2381
-
2382
- $this->numObj++;
2383
- $this->o_pages($this->numObj, 'new');
2384
-
2385
- $this->o_pages($this->numObj, 'mediaBox', $pageSize);
2386
- $this->currentNode = 3;
2387
-
2388
- $this->numObj++;
2389
- $this->o_procset($this->numObj, 'new');
2390
-
2391
- $this->numObj++;
2392
- $this->o_info($this->numObj, 'new');
2393
-
2394
- $this->numObj++;
2395
- $this->o_page($this->numObj, 'new');
2396
-
2397
- // need to store the first page id as there is no way to get it to the user during
2398
- // startup
2399
- $this->firstPageId = $this->currentContents;
2400
- }
2401
-
2402
- /**
2403
- * open the font file and return a php structure containing it.
2404
- * first check if this one has been done before and saved in a form more suited to php
2405
- * note that if a php serialized version does not exist it will try and make one, but will
2406
- * require write access to the directory to do it... it is MUCH faster to have these serialized
2407
- * files.
2408
- *
2409
- * @param $font
2410
- */
2411
- private function openFont($font)
2412
- {
2413
- // assume that $font contains the path and file but not the extension
2414
- $name = basename($font);
2415
- $dir = dirname($font) . '/';
2416
-
2417
- $fontcache = $this->fontcache;
2418
- if ($fontcache == '') {
2419
- $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
2420
- }
2421
-
2422
- //$name filename without folder and extension of font metrics
2423
- //$dir folder of font metrics
2424
- //$fontcache folder of runtime created php serialized version of font metrics.
2425
- // If this is not given, the same folder as the font metrics will be used.
2426
- // Storing and reusing serialized versions improves speed much
2427
-
2428
- $this->addMessage("openFont: $font - $name");
2429
-
2430
- if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
2431
- $metrics_name = "$name.afm";
2432
- } else {
2433
- $metrics_name = "$name.ufm";
2434
- }
2435
-
2436
- $cache_name = "$metrics_name.php";
2437
- $this->addMessage("metrics: $metrics_name, cache: $cache_name");
2438
-
2439
- if (file_exists($fontcache . '/' . $cache_name)) {
2440
- $this->addMessage("openFont: php file exists $fontcache/$cache_name");
2441
- $this->fonts[$font] = require($fontcache . '/' . $cache_name);
2442
-
2443
- if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
2444
- // if the font file is old, then clear it out and prepare for re-creation
2445
- $this->addMessage('openFont: clear out, make way for new version.');
2446
- $this->fonts[$font] = null;
2447
- unset($this->fonts[$font]);
2448
- }
2449
- } else {
2450
- $old_cache_name = "php_$metrics_name";
2451
- if (file_exists($fontcache . '/' . $old_cache_name)) {
2452
- $this->addMessage(
2453
- "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
2454
- );
2455
- $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
2456
- file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
2457
-
2458
- $this->openFont($font);
2459
- return;
2460
- }
2461
- }
2462
-
2463
- if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
2464
- // then rebuild the php_<font>.afm file from the <font>.afm file
2465
- $this->addMessage("openFont: build php file from $dir$metrics_name");
2466
- $data = array();
2467
-
2468
- // 20 => 'space'
2469
- $data['codeToName'] = array();
2470
-
2471
- // Since we're not going to enable Unicode for the core fonts we need to use a font-based
2472
- // setting for Unicode support rather than a global setting.
2473
- $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
2474
-
2475
- $cidtogid = '';
2476
- if ($data['isUnicode']) {
2477
- $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
2478
- }
2479
-
2480
- $file = file($dir . $metrics_name);
2481
-
2482
- foreach ($file as $rowA) {
2483
- $row = trim($rowA);
2484
- $pos = strpos($row, ' ');
2485
-
2486
- if ($pos) {
2487
- // then there must be some keyword
2488
- $key = substr($row, 0, $pos);
2489
- switch ($key) {
2490
- case 'FontName':
2491
- case 'FullName':
2492
- case 'FamilyName':
2493
- case 'PostScriptName':
2494
- case 'Weight':
2495
- case 'ItalicAngle':
2496
- case 'IsFixedPitch':
2497
- case 'CharacterSet':
2498
- case 'UnderlinePosition':
2499
- case 'UnderlineThickness':
2500
- case 'Version':
2501
- case 'EncodingScheme':
2502
- case 'CapHeight':
2503
- case 'XHeight':
2504
- case 'Ascender':
2505
- case 'Descender':
2506
- case 'StdHW':
2507
- case 'StdVW':
2508
- case 'StartCharMetrics':
2509
- case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big.
2510
- $data[$key] = trim(substr($row, $pos));
2511
- break;
2512
-
2513
- case 'FontBBox':
2514
- $data[$key] = explode(' ', trim(substr($row, $pos)));
2515
- break;
2516
-
2517
- //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
2518
- case 'C': // Found in AFM files
2519
- $bits = explode(';', trim($row));
2520
- $dtmp = array();
2521
-
2522
- foreach ($bits as $bit) {
2523
- $bits2 = explode(' ', trim($bit));
2524
- if (mb_strlen($bits2[0], '8bit') == 0) {
2525
- continue;
2526
- }
2527
-
2528
- if (count($bits2) > 2) {
2529
- $dtmp[$bits2[0]] = array();
2530
- for ($i = 1; $i < count($bits2); $i++) {
2531
- $dtmp[$bits2[0]][] = $bits2[$i];
2532
- }
2533
- } else {
2534
- if (count($bits2) == 2) {
2535
- $dtmp[$bits2[0]] = $bits2[1];
2536
- }
2537
- }
2538
- }
2539
-
2540
- $c = (int)$dtmp['C'];
2541
- $n = $dtmp['N'];
2542
- $width = floatval($dtmp['WX']);
2543
-
2544
- if ($c >= 0) {
2545
- if ($c != hexdec($n)) {
2546
- $data['codeToName'][$c] = $n;
2547
- }
2548
- $data['C'][$c] = $width;
2549
- } else {
2550
- $data['C'][$n] = $width;
2551
- }
2552
-
2553
- if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2554
- $data['MissingWidth'] = $width;
2555
- }
2556
-
2557
- break;
2558
-
2559
- // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
2560
- case 'U': // Found in UFM files
2561
- if (!$data['isUnicode']) {
2562
- break;
2563
- }
2564
-
2565
- $bits = explode(';', trim($row));
2566
- $dtmp = array();
2567
-
2568
- foreach ($bits as $bit) {
2569
- $bits2 = explode(' ', trim($bit));
2570
- if (mb_strlen($bits2[0], '8bit') === 0) {
2571
- continue;
2572
- }
2573
-
2574
- if (count($bits2) > 2) {
2575
- $dtmp[$bits2[0]] = array();
2576
- for ($i = 1; $i < count($bits2); $i++) {
2577
- $dtmp[$bits2[0]][] = $bits2[$i];
2578
- }
2579
- } else {
2580
- if (count($bits2) == 2) {
2581
- $dtmp[$bits2[0]] = $bits2[1];
2582
- }
2583
- }
2584
- }
2585
-
2586
- $c = (int)$dtmp['U'];
2587
- $n = $dtmp['N'];
2588
- $glyph = $dtmp['G'];
2589
- $width = floatval($dtmp['WX']);
2590
-
2591
- if ($c >= 0) {
2592
- // Set values in CID to GID map
2593
- if ($c >= 0 && $c < 0xFFFF && $glyph) {
2594
- $cidtogid[$c * 2] = chr($glyph >> 8);
2595
- $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
2596
- }
2597
-
2598
- if ($c != hexdec($n)) {
2599
- $data['codeToName'][$c] = $n;
2600
- }
2601
- $data['C'][$c] = $width;
2602
- } else {
2603
- $data['C'][$n] = $width;
2604
- }
2605
-
2606
- if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2607
- $data['MissingWidth'] = $width;
2608
- }
2609
-
2610
- break;
2611
-
2612
- case 'KPX':
2613
- break; // don't include them as they are not used yet
2614
- //KPX Adieresis yacute -40
2615
- /*$bits = explode(' ', trim($row));
2616
- $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
2617
- break;*/
2618
- }
2619
- }
2620
- }
2621
-
2622
- if ($this->compressionReady && $this->options['compression']) {
2623
- // then implement ZLIB based compression on CIDtoGID string
2624
- $data['CIDtoGID_Compressed'] = true;
2625
- $cidtogid = gzcompress($cidtogid, 6);
2626
- }
2627
- $data['CIDtoGID'] = base64_encode($cidtogid);
2628
- $data['_version_'] = $this->fontcacheVersion;
2629
- $this->fonts[$font] = $data;
2630
-
2631
- //Because of potential trouble with php safe mode, expect that the folder already exists.
2632
- //If not existing, this will hit performance because of missing cached results.
2633
- if (is_dir($fontcache) && is_writable($fontcache)) {
2634
- file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
2635
- }
2636
- $data = null;
2637
- }
2638
-
2639
- if (!isset($this->fonts[$font])) {
2640
- $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
2641
- }
2642
-
2643
- //pre_r($this->messages);
2644
- }
2645
-
2646
- /**
2647
- * if the font is not loaded then load it and make the required object
2648
- * else just make it the current font
2649
- * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
2650
- * note that encoding='none' will need to be used for symbolic fonts
2651
- * and 'differences' => an array of mappings between numbers 0->255 and character names.
2652
- *
2653
- * @param $fontName
2654
- * @param string $encoding
2655
- * @param bool $set
2656
- * @return int
2657
- */
2658
- function selectFont($fontName, $encoding = '', $set = true)
2659
- {
2660
- $ext = substr($fontName, -4);
2661
- if ($ext === '.afm' || $ext === '.ufm') {
2662
- $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
2663
- }
2664
-
2665
- if (!isset($this->fonts[$fontName])) {
2666
- $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
2667
-
2668
- // load the file
2669
- $this->openFont($fontName);
2670
-
2671
- if (isset($this->fonts[$fontName])) {
2672
- $this->numObj++;
2673
- $this->numFonts++;
2674
-
2675
- $font = &$this->fonts[$fontName];
2676
-
2677
- $name = basename($fontName);
2678
- $dir = dirname($fontName) . '/';
2679
- $options = array('name' => $name, 'fontFileName' => $fontName);
2680
-
2681
- if (is_array($encoding)) {
2682
- // then encoding and differences might be set
2683
- if (isset($encoding['encoding'])) {
2684
- $options['encoding'] = $encoding['encoding'];
2685
- }
2686
-
2687
- if (isset($encoding['differences'])) {
2688
- $options['differences'] = $encoding['differences'];
2689
- }
2690
- } else {
2691
- if (mb_strlen($encoding, '8bit')) {
2692
- // then perhaps only the encoding has been set
2693
- $options['encoding'] = $encoding;
2694
- }
2695
- }
2696
-
2697
- $fontObj = $this->numObj;
2698
- $this->o_font($this->numObj, 'new', $options);
2699
- $font['fontNum'] = $this->numFonts;
2700
-
2701
- // if this is a '.afm' font, and there is a '.pfa' file to go with it (as there
2702
- // should be for all non-basic fonts), then load it into an object and put the
2703
- // references into the font object
2704
- $basefile = $fontName;
2705
-
2706
- $fbtype = '';
2707
- if (file_exists("$basefile.ttf")) {
2708
- $fbtype = 'ttf';
2709
- } elseif (file_exists("$basefile.TTF")) {
2710
- $fbtype = 'TTF';
2711
- } elseif (file_exists("$basefile.pfb")) {
2712
- $fbtype = 'pfb';
2713
- } elseif (file_exists("$basefile.PFB")) {
2714
- $fbtype = 'PFB';
2715
- }
2716
-
2717
- $fbfile = "$basefile.$fbtype";
2718
-
2719
- // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
2720
- // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
2721
- $this->addMessage('selectFont: checking for - ' . $fbfile);
2722
-
2723
- // OAR - I don't understand this old check
2724
- // if (substr($fontName, -4) === '.afm' && strlen($fbtype)) {
2725
- if ($fbtype) {
2726
- $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
2727
- // $fontObj = $this->numObj;
2728
- $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
2729
-
2730
- // find the array of font widths, and put that into an object.
2731
- $firstChar = -1;
2732
- $lastChar = 0;
2733
- $widths = array();
2734
- $cid_widths = array();
2735
-
2736
- foreach ($font['C'] as $num => $d) {
2737
- if (intval($num) > 0 || $num == '0') {
2738
- if (!$font['isUnicode']) {
2739
- // With Unicode, widths array isn't used
2740
- if ($lastChar > 0 && $num > $lastChar + 1) {
2741
- for ($i = $lastChar + 1; $i < $num; $i++) {
2742
- $widths[] = 0;
2743
- }
2744
- }
2745
- }
2746
-
2747
- $widths[] = $d;
2748
-
2749
- if ($font['isUnicode']) {
2750
- $cid_widths[$num] = $d;
2751
- }
2752
-
2753
- if ($firstChar == -1) {
2754
- $firstChar = $num;
2755
- }
2756
-
2757
- $lastChar = $num;
2758
- }
2759
- }
2760
-
2761
- // also need to adjust the widths for the differences array
2762
- if (isset($options['differences'])) {
2763
- foreach ($options['differences'] as $charNum => $charName) {
2764
- if ($charNum > $lastChar) {
2765
- if (!$font['isUnicode']) {
2766
- // With Unicode, widths array isn't used
2767
- for ($i = $lastChar + 1; $i <= $charNum; $i++) {
2768
- $widths[] = 0;
2769
- }
2770
- }
2771
-
2772
- $lastChar = $charNum;
2773
- }
2774
-
2775
- if (isset($font['C'][$charName])) {
2776
- $widths[$charNum - $firstChar] = $font['C'][$charName];
2777
- if ($font['isUnicode']) {
2778
- $cid_widths[$charName] = $font['C'][$charName];
2779
- }
2780
- }
2781
- }
2782
- }
2783
-
2784
- if ($font['isUnicode']) {
2785
- $font['CIDWidths'] = $cid_widths;
2786
- }
2787
-
2788
- $this->addMessage('selectFont: FirstChar = ' . $firstChar);
2789
- $this->addMessage('selectFont: LastChar = ' . $lastChar);
2790
-
2791
- $widthid = -1;
2792
-
2793
- if (!$font['isUnicode']) {
2794
- // With Unicode, widths array isn't used
2795
-
2796
- $this->numObj++;
2797
- $this->o_contents($this->numObj, 'new', 'raw');
2798
- $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
2799
- $widthid = $this->numObj;
2800
- }
2801
-
2802
- $missing_width = 500;
2803
- $stemV = 70;
2804
-
2805
- if (isset($font['MissingWidth'])) {
2806
- $missing_width = $font['MissingWidth'];
2807
- }
2808
- if (isset($font['StdVW'])) {
2809
- $stemV = $font['StdVW'];
2810
- } else {
2811
- if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
2812
- $stemV = 120;
2813
- }
2814
- }
2815
-
2816
- // load the pfb file, and put that into an object too.
2817
- // note that pdf supports only binary format type 1 font files, though there is a
2818
- // simple utility to convert them from pfa to pfb.
2819
- // FIXME: should we move font subset creation to CPDF::output? See notes in issue #750.
2820
- if (!$this->isUnicode || strtolower($fbtype) !== 'ttf' || empty($this->stringSubsets)) {
2821
- $data = file_get_contents($fbfile);
2822
- } else {
2823
- $this->stringSubsets[$fontName][] = 32; // Force space if not in yet
2824
-
2825
- $subset = $this->stringSubsets[$fontName];
2826
- sort($subset);
2827
-
2828
- // Load font
2829
- $font_obj = Font::load($fbfile);
2830
- $font_obj->parse();
2831
-
2832
- // Define subset
2833
- $font_obj->setSubset($subset);
2834
- $font_obj->reduce();
2835
-
2836
- // Write new font
2837
- $tmp_name = $this->tmp . "/" . basename($fbfile) . ".tmp." . uniqid();
2838
- $font_obj->open($tmp_name, BinaryStream::modeWrite);
2839
- $font_obj->encode(array("OS/2"));
2840
- $font_obj->close();
2841
-
2842
- // Parse the new font to get cid2gid and widths
2843
- $font_obj = Font::load($tmp_name);
2844
-
2845
- // Find Unicode char map table
2846
- $subtable = null;
2847
- foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
2848
- if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
2849
- $subtable = $_subtable;
2850
- break;
2851
- }
2852
- }
2853
-
2854
- if ($subtable) {
2855
- $glyphIndexArray = $subtable["glyphIndexArray"];
2856
- $hmtx = $font_obj->getData("hmtx");
2857
-
2858
- unset($glyphIndexArray[0xFFFF]);
2859
-
2860
- $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
2861
- $font['CIDWidths'] = array();
2862
- foreach ($glyphIndexArray as $cid => $gid) {
2863
- if ($cid >= 0 && $cid < 0xFFFF && $gid) {
2864
- $cidtogid[$cid * 2] = chr($gid >> 8);
2865
- $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
2866
- }
2867
-
2868
- $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
2869
- $font['CIDWidths'][$cid] = $width;
2870
- }
2871
-
2872
- $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
2873
- $font['CIDtoGID_Compressed'] = true;
2874
-
2875
- $data = file_get_contents($tmp_name);
2876
- } else {
2877
- $data = file_get_contents($fbfile);
2878
- }
2879
-
2880
- $font_obj->close();
2881
- unlink($tmp_name);
2882
- }
2883
-
2884
- // create the font descriptor
2885
- $this->numObj++;
2886
- $fontDescriptorId = $this->numObj;
2887
-
2888
- $this->numObj++;
2889
- $pfbid = $this->numObj;
2890
-
2891
- // determine flags (more than a little flakey, hopefully will not matter much)
2892
- $flags = 0;
2893
-
2894
- if ($font['ItalicAngle'] != 0) {
2895
- $flags += pow(2, 6);
2896
- }
2897
-
2898
- if ($font['IsFixedPitch'] === 'true') {
2899
- $flags += 1;
2900
- }
2901
-
2902
- $flags += pow(2, 5); // assume non-sybolic
2903
- $list = array(
2904
- 'Ascent' => 'Ascender',
2905
- 'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
2906
- 'MissingWidth' => 'MissingWidth',
2907
- 'Descent' => 'Descender',
2908
- 'FontBBox' => 'FontBBox',
2909
- 'ItalicAngle' => 'ItalicAngle'
2910
- );
2911
- $fdopt = array(
2912
- 'Flags' => $flags,
2913
- 'FontName' => $adobeFontName,
2914
- 'StemV' => $stemV
2915
- );
2916
-
2917
- foreach ($list as $k => $v) {
2918
- if (isset($font[$v])) {
2919
- $fdopt[$k] = $font[$v];
2920
- }
2921
- }
2922
-
2923
- if (strtolower($fbtype) === 'pfb') {
2924
- $fdopt['FontFile'] = $pfbid;
2925
- } elseif (strtolower($fbtype) === 'ttf') {
2926
- $fdopt['FontFile2'] = $pfbid;
2927
- }
2928
-
2929
- $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
2930
-
2931
- // embed the font program
2932
- $this->o_contents($this->numObj, 'new');
2933
- $this->objects[$pfbid]['c'] .= $data;
2934
-
2935
- // determine the cruicial lengths within this file
2936
- if (strtolower($fbtype) === 'pfb') {
2937
- $l1 = strpos($data, 'eexec') + 6;
2938
- $l2 = strpos($data, '00000000') - $l1;
2939
- $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
2940
- $this->o_contents(
2941
- $this->numObj,
2942
- 'add',
2943
- array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3)
2944
- );
2945
- } elseif (strtolower($fbtype) == 'ttf') {
2946
- $l1 = mb_strlen($data, '8bit');
2947
- $this->o_contents($this->numObj, 'add', array('Length1' => $l1));
2948
- }
2949
-
2950
- // tell the font object about all this new stuff
2951
- $tmp = array(
2952
- 'BaseFont' => $adobeFontName,
2953
- 'MissingWidth' => $missing_width,
2954
- 'Widths' => $widthid,
2955
- 'FirstChar' => $firstChar,
2956
- 'LastChar' => $lastChar,
2957
- 'FontDescriptor' => $fontDescriptorId
2958
- );
2959
-
2960
- if (strtolower($fbtype) === 'ttf') {
2961
- $tmp['SubType'] = 'TrueType';
2962
- }
2963
-
2964
- $this->addMessage("adding extra info to font.($fontObj)");
2965
-
2966
- foreach ($tmp as $fk => $fv) {
2967
- $this->addMessage("$fk : $fv");
2968
- }
2969
-
2970
- $this->o_font($fontObj, 'add', $tmp);
2971
- } else {
2972
- $this->addMessage(
2973
- 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
2974
- );
2975
- }
2976
-
2977
- // also set the differences here, note that this means that these will take effect only the
2978
- //first time that a font is selected, else they are ignored
2979
- if (isset($options['differences'])) {
2980
- $font['differences'] = $options['differences'];
2981
- }
2982
- }
2983
- }
2984
-
2985
- if ($set && isset($this->fonts[$fontName])) {
2986
- // so if for some reason the font was not set in the last one then it will not be selected
2987
- $this->currentBaseFont = $fontName;
2988
-
2989
- // the next lines mean that if a new font is selected, then the current text state will be
2990
- // applied to it as well.
2991
- $this->currentFont = $this->currentBaseFont;
2992
- $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
2993
-
2994
- //$this->setCurrentFont();
2995
- }
2996
-
2997
- return $this->currentFontNum;
2998
- //return $this->numObj;
2999
- }
3000
-
3001
- /**
3002
- * sets up the current font, based on the font families, and the current text state
3003
- * note that this system is quite flexible, a bold-italic font can be completely different to a
3004
- * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3005
- * This function is to be called whenever the currentTextState is changed, it will update
3006
- * the currentFont setting to whatever the appropriate family one is.
3007
- * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3008
- * This function will change the currentFont to whatever it should be, but will not change the
3009
- * currentBaseFont.
3010
- */
3011
- private function setCurrentFont()
3012
- {
3013
- // if (strlen($this->currentBaseFont) == 0){
3014
- // // then assume an initial font
3015
- // $this->selectFont($this->defaultFont);
3016
- // }
3017
- // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3018
- // if (strlen($this->currentTextState)
3019
- // && isset($this->fontFamilies[$cf])
3020
- // && isset($this->fontFamilies[$cf][$this->currentTextState])){
3021
- // // then we are in some state or another
3022
- // // and this font has a family, and the current setting exists within it
3023
- // // select the font, then return it
3024
- // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3025
- // $this->selectFont($nf,'',0);
3026
- // $this->currentFont = $nf;
3027
- // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3028
- // } else {
3029
- // // the this font must not have the right family member for the current state
3030
- // // simply assume the base font
3031
- $this->currentFont = $this->currentBaseFont;
3032
- $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3033
- // }
3034
- }
3035
-
3036
- /**
3037
- * function for the user to find out what the ID is of the first page that was created during
3038
- * startup - useful if they wish to add something to it later.
3039
- *
3040
- * @return int
3041
- */
3042
- function getFirstPageId()
3043
- {
3044
- return $this->firstPageId;
3045
- }
3046
-
3047
- /**
3048
- * add content to the currently active object
3049
- *
3050
- * @param $content
3051
- */
3052
- private function addContent($content)
3053
- {
3054
- $this->objects[$this->currentContents]['c'] .= $content;
3055
- }
3056
-
3057
- /**
3058
- * sets the color for fill operations
3059
- *
3060
- * @param $color
3061
- * @param bool $force
3062
- */
3063
- function setColor($color, $force = false)
3064
- {
3065
- $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3066
-
3067
- if (!$force && $this->currentColor == $new_color) {
3068
- return;
3069
- }
3070
-
3071
- if (isset($new_color[3])) {
3072
- $this->currentColor = $new_color;
3073
- $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3074
- } else {
3075
- if (isset($new_color[2])) {
3076
- $this->currentColor = $new_color;
3077
- $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3078
- }
3079
- }
3080
- }
3081
-
3082
- /**
3083
- * sets the color for fill operations
3084
- *
3085
- * @param $fillRule
3086
- */
3087
- function setFillRule($fillRule)
3088
- {
3089
- if (!in_array($fillRule, array("nonzero", "evenodd"))) {
3090
- return;
3091
- }
3092
-
3093
- $this->fillRule = $fillRule;
3094
- }
3095
-
3096
- /**
3097
- * sets the color for stroke operations
3098
- *
3099
- * @param $color
3100
- * @param bool $force
3101
- */
3102
- function setStrokeColor($color, $force = false)
3103
- {
3104
- $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3105
-
3106
- if (!$force && $this->currentStrokeColor == $new_color) {
3107
- return;
3108
- }
3109
-
3110
- if (isset($new_color[3])) {
3111
- $this->currentStrokeColor = $new_color;
3112
- $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3113
- } else {
3114
- if (isset($new_color[2])) {
3115
- $this->currentStrokeColor = $new_color;
3116
- $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3117
- }
3118
- }
3119
- }
3120
-
3121
- /**
3122
- * Set the graphics state for compositions
3123
- *
3124
- * @param $parameters
3125
- */
3126
- function setGraphicsState($parameters)
3127
- {
3128
- // Create a new graphics state object if necessary
3129
- if (($gstate = array_search($parameters, $this->gstates)) === false) {
3130
- $this->numObj++;
3131
- $this->o_extGState($this->numObj, 'new', $parameters);
3132
- $gstate = $this->numStates;
3133
- $this->gstates[$gstate] = $parameters;
3134
- }
3135
- $this->addContent("\n/GS$gstate gs");
3136
- }
3137
-
3138
- /**
3139
- * Set current blend mode & opacity for lines.
3140
- *
3141
- * Valid blend modes are:
3142
- *
3143
- * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3144
- * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3145
- * Exclusion
3146
- *
3147
- * @param string $mode the blend mode to use
3148
- * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3149
- */
3150
- function setLineTransparency($mode, $opacity)
3151
- {
3152
- static $blend_modes = array(
3153
- "Normal",
3154
- "Multiply",
3155
- "Screen",
3156
- "Overlay",
3157
- "Darken",
3158
- "Lighten",
3159
- "ColorDogde",
3160
- "ColorBurn",
3161
- "HardLight",
3162
- "SoftLight",
3163
- "Difference",
3164
- "Exclusion"
3165
- );
3166
-
3167
- if (!in_array($mode, $blend_modes)) {
3168
- $mode = "Normal";
3169
- }
3170
-
3171
- // Only create a new graphics state if required
3172
- if ($mode === $this->currentLineTransparency["mode"] &&
3173
- $opacity == $this->currentLineTransparency["opacity"]
3174
- ) {
3175
- return;
3176
- }
3177
-
3178
- $this->currentLineTransparency["mode"] = $mode;
3179
- $this->currentLineTransparency["opacity"] = $opacity;
3180
-
3181
- $options = array(
3182
- "BM" => "/$mode",
3183
- "CA" => (float)$opacity
3184
- );
3185
-
3186
- $this->setGraphicsState($options);
3187
- }
3188
-
3189
- /**
3190
- * Set current blend mode & opacity for filled objects.
3191
- *
3192
- * Valid blend modes are:
3193
- *
3194
- * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3195
- * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3196
- * Exclusion
3197
- *
3198
- * @param string $mode the blend mode to use
3199
- * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3200
- */
3201
- function setFillTransparency($mode, $opacity)
3202
- {
3203
- static $blend_modes = array(
3204
- "Normal",
3205
- "Multiply",
3206
- "Screen",
3207
- "Overlay",
3208
- "Darken",
3209
- "Lighten",
3210
- "ColorDogde",
3211
- "ColorBurn",
3212
- "HardLight",
3213
- "SoftLight",
3214
- "Difference",
3215
- "Exclusion"
3216
- );
3217
-
3218
- if (!in_array($mode, $blend_modes)) {
3219
- $mode = "Normal";
3220
- }
3221
-
3222
- if ($mode === $this->currentFillTransparency["mode"] &&
3223
- $opacity == $this->currentFillTransparency["opacity"]
3224
- ) {
3225
- return;
3226
- }
3227
-
3228
- $this->currentFillTransparency["mode"] = $mode;
3229
- $this->currentFillTransparency["opacity"] = $opacity;
3230
-
3231
- $options = array(
3232
- "BM" => "/$mode",
3233
- "ca" => (float)$opacity,
3234
- );
3235
-
3236
- $this->setGraphicsState($options);
3237
- }
3238
-
3239
- /**
3240
- * draw a line from one set of coordinates to another
3241
- *
3242
- * @param $x1
3243
- * @param $y1
3244
- * @param $x2
3245
- * @param $y2
3246
- * @param bool $stroke
3247
- */
3248
- function line($x1, $y1, $x2, $y2, $stroke = true)
3249
- {
3250
- $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3251
-
3252
- if ($stroke) {
3253
- $this->addContent(' S');
3254
- }
3255
- }
3256
-
3257
- /**
3258
- * draw a bezier curve based on 4 control points
3259
- *
3260
- * @param $x0
3261
- * @param $y0
3262
- * @param $x1
3263
- * @param $y1
3264
- * @param $x2
3265
- * @param $y2
3266
- * @param $x3
3267
- * @param $y3
3268
- */
3269
- function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3270
- {
3271
- // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3272
- // as the control points for the curve.
3273
- $this->addContent(
3274
- sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3275
- );
3276
- }
3277
-
3278
- /**
3279
- * draw a part of an ellipse
3280
- *
3281
- * @param $x0
3282
- * @param $y0
3283
- * @param $astart
3284
- * @param $afinish
3285
- * @param $r1
3286
- * @param int $r2
3287
- * @param int $angle
3288
- * @param int $nSeg
3289
- */
3290
- function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3291
- {
3292
- $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3293
- }
3294
-
3295
- /**
3296
- * draw a filled ellipse
3297
- *
3298
- * @param $x0
3299
- * @param $y0
3300
- * @param $r1
3301
- * @param int $r2
3302
- * @param int $angle
3303
- * @param int $nSeg
3304
- * @param int $astart
3305
- * @param int $afinish
3306
- */
3307
- function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3308
- {
3309
- $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3310
- }
3311
-
3312
- /**
3313
- * @param $x
3314
- * @param $y
3315
- */
3316
- function lineTo($x, $y)
3317
- {
3318
- $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3319
- }
3320
-
3321
- /**
3322
- * @param $x
3323
- * @param $y
3324
- */
3325
- function moveTo($x, $y)
3326
- {
3327
- $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3328
- }
3329
-
3330
- /**
3331
- * draw a bezier curve based on 4 control points
3332
- *
3333
- * @param $x1
3334
- * @param $y1
3335
- * @param $x2
3336
- * @param $y2
3337
- * @param $x3
3338
- * @param $y3
3339
- */
3340
- function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3341
- {
3342
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3343
- }
3344
-
3345
- /**
3346
- * draw a bezier curve based on 4 control points
3347
- */ function quadTo($cpx, $cpy, $x, $y)
3348
- {
3349
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3350
- }
3351
-
3352
- function closePath()
3353
- {
3354
- $this->addContent(' h');
3355
- }
3356
-
3357
- function endPath()
3358
- {
3359
- $this->addContent(' n');
3360
- }
3361
-
3362
- /**
3363
- * draw an ellipse
3364
- * note that the part and filled ellipse are just special cases of this function
3365
- *
3366
- * draws an ellipse in the current line style
3367
- * centered at $x0,$y0, radii $r1,$r2
3368
- * if $r2 is not set, then a circle is drawn
3369
- * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3370
- * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3371
- * pretty crappy shape at 2, as we are approximating with bezier curves.
3372
- *
3373
- * @param $x0
3374
- * @param $y0
3375
- * @param $r1
3376
- * @param int $r2
3377
- * @param int $angle
3378
- * @param int $nSeg
3379
- * @param int $astart
3380
- * @param int $afinish
3381
- * @param bool $close
3382
- * @param bool $fill
3383
- * @param bool $stroke
3384
- * @param bool $incomplete
3385
- */
3386
- function ellipse(
3387
- $x0,
3388
- $y0,
3389
- $r1,
3390
- $r2 = 0,
3391
- $angle = 0,
3392
- $nSeg = 8,
3393
- $astart = 0,
3394
- $afinish = 360,
3395
- $close = true,
3396
- $fill = false,
3397
- $stroke = true,
3398
- $incomplete = false
3399
- ) {
3400
- if ($r1 == 0) {
3401
- return;
3402
- }
3403
-
3404
- if ($r2 == 0) {
3405
- $r2 = $r1;
3406
- }
3407
-
3408
- if ($nSeg < 2) {
3409
- $nSeg = 2;
3410
- }
3411
-
3412
- $astart = deg2rad((float)$astart);
3413
- $afinish = deg2rad((float)$afinish);
3414
- $totalAngle = $afinish - $astart;
3415
-
3416
- $dt = $totalAngle / $nSeg;
3417
- $dtm = $dt / 3;
3418
-
3419
- if ($angle != 0) {
3420
- $a = -1 * deg2rad((float)$angle);
3421
-
3422
- $this->addContent(
3423
- sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
3424
- );
3425
-
3426
- $x0 = 0;
3427
- $y0 = 0;
3428
- }
3429
-
3430
- $t1 = $astart;
3431
- $a0 = $x0 + $r1 * cos($t1);
3432
- $b0 = $y0 + $r2 * sin($t1);
3433
- $c0 = -$r1 * sin($t1);
3434
- $d0 = $r2 * cos($t1);
3435
-
3436
- if (!$incomplete) {
3437
- $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
3438
- }
3439
-
3440
- for ($i = 1; $i <= $nSeg; $i++) {
3441
- // draw this bit of the total curve
3442
- $t1 = $i * $dt + $astart;
3443
- $a1 = $x0 + $r1 * cos($t1);
3444
- $b1 = $y0 + $r2 * sin($t1);
3445
- $c1 = -$r1 * sin($t1);
3446
- $d1 = $r2 * cos($t1);
3447
-
3448
- $this->addContent(
3449
- sprintf(
3450
- "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
3451
- ($a0 + $c0 * $dtm),
3452
- ($b0 + $d0 * $dtm),
3453
- ($a1 - $c1 * $dtm),
3454
- ($b1 - $d1 * $dtm),
3455
- $a1,
3456
- $b1
3457
- )
3458
- );
3459
-
3460
- $a0 = $a1;
3461
- $b0 = $b1;
3462
- $c0 = $c1;
3463
- $d0 = $d1;
3464
- }
3465
-
3466
- if (!$incomplete) {
3467
- if ($fill) {
3468
- $this->addContent(' f');
3469
- }
3470
-
3471
- if ($stroke) {
3472
- if ($close) {
3473
- $this->addContent(' s'); // small 's' signifies closing the path as well
3474
- } else {
3475
- $this->addContent(' S');
3476
- }
3477
- }
3478
- }
3479
-
3480
- if ($angle != 0) {
3481
- $this->addContent(' Q');
3482
- }
3483
- }
3484
-
3485
- /**
3486
- * this sets the line drawing style.
3487
- * width, is the thickness of the line in user units
3488
- * cap is the type of cap to put on the line, values can be 'butt','round','square'
3489
- * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
3490
- * end of the line.
3491
- * join can be 'miter', 'round', 'bevel'
3492
- * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
3493
- * on and off dashes.
3494
- * (2) represents 2 on, 2 off, 2 on , 2 off ...
3495
- * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
3496
- * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
3497
- *
3498
- * @param int $width
3499
- * @param string $cap
3500
- * @param string $join
3501
- * @param string $dash
3502
- * @param int $phase
3503
- */
3504
- function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
3505
- {
3506
- // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
3507
- $string = '';
3508
-
3509
- if ($width > 0) {
3510
- $string .= "$width w";
3511
- }
3512
-
3513
- $ca = array('butt' => 0, 'round' => 1, 'square' => 2);
3514
-
3515
- if (isset($ca[$cap])) {
3516
- $string .= " $ca[$cap] J";
3517
- }
3518
-
3519
- $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
3520
-
3521
- if (isset($ja[$join])) {
3522
- $string .= " $ja[$join] j";
3523
- }
3524
-
3525
- if (is_array($dash)) {
3526
- $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
3527
- }
3528
-
3529
- $this->currentLineStyle = $string;
3530
- $this->addContent("\n$string");
3531
- }
3532
-
3533
- /**
3534
- * draw a polygon, the syntax for this is similar to the GD polygon command
3535
- *
3536
- * @param $p
3537
- * @param $np
3538
- * @param bool $f
3539
- */
3540
- function polygon($p, $np, $f = false)
3541
- {
3542
- $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
3543
-
3544
- for ($i = 2; $i < $np * 2; $i = $i + 2) {
3545
- $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
3546
- }
3547
-
3548
- if ($f) {
3549
- $this->addContent(' f');
3550
- } else {
3551
- $this->addContent(' S');
3552
- }
3553
- }
3554
-
3555
- /**
3556
- * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3557
- * the coordinates of the upper-right corner
3558
- *
3559
- * @param $x1
3560
- * @param $y1
3561
- * @param $width
3562
- * @param $height
3563
- */
3564
- function filledRectangle($x1, $y1, $width, $height)
3565
- {
3566
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
3567
- }
3568
-
3569
- /**
3570
- * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3571
- * the coordinates of the upper-right corner
3572
- *
3573
- * @param $x1
3574
- * @param $y1
3575
- * @param $width
3576
- * @param $height
3577
- */
3578
- function rectangle($x1, $y1, $width, $height)
3579
- {
3580
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
3581
- }
3582
-
3583
- /**
3584
- * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3585
- * the coordinates of the upper-right corner
3586
- *
3587
- * @param $x1
3588
- * @param $y1
3589
- * @param $width
3590
- * @param $height
3591
- */
3592
- function rect($x1, $y1, $width, $height)
3593
- {
3594
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
3595
- }
3596
-
3597
- function stroke()
3598
- {
3599
- $this->addContent("\nS");
3600
- }
3601
-
3602
- function fill()
3603
- {
3604
- $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
3605
- }
3606
-
3607
- function fillStroke()
3608
- {
3609
- $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
3610
- }
3611
-
3612
- /**
3613
- * save the current graphic state
3614
- */
3615
- function save()
3616
- {
3617
- // we must reset the color cache or it will keep bad colors after clipping
3618
- $this->currentColor = null;
3619
- $this->currentStrokeColor = null;
3620
- $this->addContent("\nq");
3621
- }
3622
-
3623
- /**
3624
- * restore the last graphic state
3625
- */
3626
- function restore()
3627
- {
3628
- // we must reset the color cache or it will keep bad colors after clipping
3629
- $this->currentColor = null;
3630
- $this->currentStrokeColor = null;
3631
- $this->addContent("\nQ");
3632
- }
3633
-
3634
- /**
3635
- * draw a clipping rectangle, all the elements added after this will be clipped
3636
- *
3637
- * @param $x1
3638
- * @param $y1
3639
- * @param $width
3640
- * @param $height
3641
- */
3642
- function clippingRectangle($x1, $y1, $width, $height)
3643
- {
3644
- $this->save();
3645
- $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
3646
- }
3647
-
3648
- /**
3649
- * draw a clipping rounded rectangle, all the elements added after this will be clipped
3650
- *
3651
- * @param $x1
3652
- * @param $y1
3653
- * @param $w
3654
- * @param $h
3655
- * @param $rTL
3656
- * @param $rTR
3657
- * @param $rBR
3658
- * @param $rBL
3659
- */
3660
- function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
3661
- {
3662
- $this->save();
3663
-
3664
- // start: top edge, left end
3665
- $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
3666
-
3667
- // line: bottom edge, left end
3668
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
3669
-
3670
- // curve: bottom-left corner
3671
- $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
3672
-
3673
- // line: right edge, bottom end
3674
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
3675
-
3676
- // curve: bottom-right corner
3677
- $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
3678
-
3679
- // line: right edge, top end
3680
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
3681
-
3682
- // curve: bottom-right corner
3683
- $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
3684
-
3685
- // line: bottom edge, right end
3686
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
3687
-
3688
- // curve: top-right corner
3689
- $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
3690
-
3691
- // line: top edge, left end
3692
- $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
3693
-
3694
- // Close & clip
3695
- $this->addContent(" W n");
3696
- }
3697
-
3698
- /**
3699
- * ends the last clipping shape
3700
- */
3701
- function clippingEnd()
3702
- {
3703
- $this->restore();
3704
- }
3705
-
3706
- /**
3707
- * scale
3708
- *
3709
- * @param float $s_x scaling factor for width as percent
3710
- * @param float $s_y scaling factor for height as percent
3711
- * @param float $x Origin abscissa
3712
- * @param float $y Origin ordinate
3713
- */
3714
- function scale($s_x, $s_y, $x, $y)
3715
- {
3716
- $y = $this->currentPageSize["height"] - $y;
3717
-
3718
- $tm = array(
3719
- $s_x,
3720
- 0,
3721
- 0,
3722
- $s_y,
3723
- $x * (1 - $s_x),
3724
- $y * (1 - $s_y)
3725
- );
3726
-
3727
- $this->transform($tm);
3728
- }
3729
-
3730
- /**
3731
- * translate
3732
- *
3733
- * @param float $t_x movement to the right
3734
- * @param float $t_y movement to the bottom
3735
- */
3736
- function translate($t_x, $t_y)
3737
- {
3738
- $tm = array(
3739
- 1,
3740
- 0,
3741
- 0,
3742
- 1,
3743
- $t_x,
3744
- -$t_y
3745
- );
3746
-
3747
- $this->transform($tm);
3748
- }
3749
-
3750
- /**
3751
- * rotate
3752
- *
3753
- * @param float $angle angle in degrees for counter-clockwise rotation
3754
- * @param float $x Origin abscissa
3755
- * @param float $y Origin ordinate
3756
- */
3757
- function rotate($angle, $x, $y)
3758
- {
3759
- $y = $this->currentPageSize["height"] - $y;
3760
-
3761
- $a = deg2rad($angle);
3762
- $cos_a = cos($a);
3763
- $sin_a = sin($a);
3764
-
3765
- $tm = array(
3766
- $cos_a,
3767
- -$sin_a,
3768
- $sin_a,
3769
- $cos_a,
3770
- $x - $sin_a * $y - $cos_a * $x,
3771
- $y - $cos_a * $y + $sin_a * $x,
3772
- );
3773
-
3774
- $this->transform($tm);
3775
- }
3776
-
3777
- /**
3778
- * skew
3779
- *
3780
- * @param float $angle_x
3781
- * @param float $angle_y
3782
- * @param float $x Origin abscissa
3783
- * @param float $y Origin ordinate
3784
- */
3785
- function skew($angle_x, $angle_y, $x, $y)
3786
- {
3787
- $y = $this->currentPageSize["height"] - $y;
3788
-
3789
- $tan_x = tan(deg2rad($angle_x));
3790
- $tan_y = tan(deg2rad($angle_y));
3791
-
3792
- $tm = array(
3793
- 1,
3794
- -$tan_y,
3795
- -$tan_x,
3796
- 1,
3797
- $tan_x * $y,
3798
- $tan_y * $x,
3799
- );
3800
-
3801
- $this->transform($tm);
3802
- }
3803
-
3804
- /**
3805
- * apply graphic transformations
3806
- *
3807
- * @param array $tm transformation matrix
3808
- */
3809
- function transform($tm)
3810
- {
3811
- $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
3812
- }
3813
-
3814
- /**
3815
- * add a new page to the document
3816
- * this also makes the new page the current active object
3817
- *
3818
- * @param int $insert
3819
- * @param int $id
3820
- * @param string $pos
3821
- * @return int
3822
- */
3823
- function newPage($insert = 0, $id = 0, $pos = 'after')
3824
- {
3825
- // if there is a state saved, then go up the stack closing them
3826
- // then on the new page, re-open them with the right setings
3827
-
3828
- if ($this->nStateStack) {
3829
- for ($i = $this->nStateStack; $i >= 1; $i--) {
3830
- $this->restoreState($i);
3831
- }
3832
- }
3833
-
3834
- $this->numObj++;
3835
-
3836
- if ($insert) {
3837
- // the id from the ezPdf class is the id of the contents of the page, not the page object itself
3838
- // query that object to find the parent
3839
- $rid = $this->objects[$id]['onPage'];
3840
- $opt = array('rid' => $rid, 'pos' => $pos);
3841
- $this->o_page($this->numObj, 'new', $opt);
3842
- } else {
3843
- $this->o_page($this->numObj, 'new');
3844
- }
3845
-
3846
- // if there is a stack saved, then put that onto the page
3847
- if ($this->nStateStack) {
3848
- for ($i = 1; $i <= $this->nStateStack; $i++) {
3849
- $this->saveState($i);
3850
- }
3851
- }
3852
-
3853
- // and if there has been a stroke or fill color set, then transfer them
3854
- if (isset($this->currentColor)) {
3855
- $this->setColor($this->currentColor, true);
3856
- }
3857
-
3858
- if (isset($this->currentStrokeColor)) {
3859
- $this->setStrokeColor($this->currentStrokeColor, true);
3860
- }
3861
-
3862
- // if there is a line style set, then put this in too
3863
- if (mb_strlen($this->currentLineStyle, '8bit')) {
3864
- $this->addContent("\n$this->currentLineStyle");
3865
- }
3866
-
3867
- // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
3868
- return $this->currentContents;
3869
- }
3870
-
3871
- /**
3872
- * Streams the PDF to the client.
3873
- *
3874
- * @param string $filename The filename to present to the client.
3875
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
3876
- */
3877
- function stream($filename = "document.pdf", $options = array())
3878
- {
3879
- if (headers_sent()) {
3880
- die("Unable to stream pdf: headers already sent");
3881
- }
3882
-
3883
- if (!isset($options["compress"])) $options["compress"] = true;
3884
- if (!isset($options["Attachment"])) $options["Attachment"] = true;
3885
-
3886
- $debug = !$options['compress'];
3887
- $tmp = ltrim($this->output($debug));
3888
-
3889
- header("Cache-Control: private");
3890
- header("Content-Type: application/pdf");
3891
- header("Content-Length: " . mb_strlen($tmp, "8bit"));
3892
-
3893
- $filename = str_replace(array("\n", "'"), "", basename($filename, ".pdf")) . ".pdf";
3894
- $attachment = $options["Attachment"] ? "attachment" : "inline";
3895
-
3896
- $encoding = mb_detect_encoding($filename);
3897
- $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
3898
- $fallbackfilename = str_replace("\"", "", $fallbackfilename);
3899
- $encodedfilename = rawurlencode($filename);
3900
-
3901
- $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
3902
- if ($fallbackfilename !== $filename) {
3903
- $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
3904
- }
3905
- header($contentDisposition);
3906
-
3907
- echo $tmp;
3908
- flush();
3909
- }
3910
-
3911
- /**
3912
- * return the height in units of the current font in the given size
3913
- *
3914
- * @param $size
3915
- * @return float|int
3916
- */
3917
- function getFontHeight($size)
3918
- {
3919
- if (!$this->numFonts) {
3920
- $this->selectFont($this->defaultFont);
3921
- }
3922
-
3923
- $font = $this->fonts[$this->currentFont];
3924
-
3925
- // for the current font, and the given size, what is the height of the font in user units
3926
- if (isset($font['Ascender']) && isset($font['Descender'])) {
3927
- $h = $font['Ascender'] - $font['Descender'];
3928
- } else {
3929
- $h = $font['FontBBox'][3] - $font['FontBBox'][1];
3930
- }
3931
-
3932
- // have to adjust by a font offset for Windows fonts. unfortunately it looks like
3933
- // the bounding box calculations are wrong and I don't know why.
3934
- if (isset($font['FontHeightOffset'])) {
3935
- // For CourierNew from Windows this needs to be -646 to match the
3936
- // Adobe native Courier font.
3937
- //
3938
- // For FreeMono from GNU this needs to be -337 to match the
3939
- // Courier font.
3940
- //
3941
- // Both have been added manually to the .afm and .ufm files.
3942
- $h += (int)$font['FontHeightOffset'];
3943
- }
3944
-
3945
- return $size * $h / 1000;
3946
- }
3947
-
3948
- /**
3949
- * @param $size
3950
- * @return float|int
3951
- */
3952
- function getFontXHeight($size)
3953
- {
3954
- if (!$this->numFonts) {
3955
- $this->selectFont($this->defaultFont);
3956
- }
3957
-
3958
- $font = $this->fonts[$this->currentFont];
3959
-
3960
- // for the current font, and the given size, what is the height of the font in user units
3961
- if (isset($font['XHeight'])) {
3962
- $xh = $font['Ascender'] - $font['Descender'];
3963
- } else {
3964
- $xh = $this->getFontHeight($size) / 2;
3965
- }
3966
-
3967
- return $size * $xh / 1000;
3968
- }
3969
-
3970
- /**
3971
- * return the font descender, this will normally return a negative number
3972
- * if you add this number to the baseline, you get the level of the bottom of the font
3973
- * it is in the pdf user units
3974
- *
3975
- * @param $size
3976
- * @return float|int
3977
- */
3978
- function getFontDescender($size)
3979
- {
3980
- // note that this will most likely return a negative value
3981
- if (!$this->numFonts) {
3982
- $this->selectFont($this->defaultFont);
3983
- }
3984
-
3985
- //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
3986
- $h = $this->fonts[$this->currentFont]['Descender'];
3987
-
3988
- return $size * $h / 1000;
3989
- }
3990
-
3991
- /**
3992
- * filter the text, this is applied to all text just before being inserted into the pdf document
3993
- * it escapes the various things that need to be escaped, and so on
3994
- *
3995
- * @access private
3996
- *
3997
- * @param $text
3998
- * @param bool $bom
3999
- * @param bool $convert_encoding
4000
- * @return string
4001
- */
4002
- function filterText($text, $bom = true, $convert_encoding = true)
4003
- {
4004
- if (!$this->numFonts) {
4005
- $this->selectFont($this->defaultFont);
4006
- }
4007
-
4008
- if ($convert_encoding) {
4009
- $cf = $this->currentFont;
4010
- if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4011
- $text = $this->utf8toUtf16BE($text, $bom);
4012
- } else {
4013
- //$text = html_entity_decode($text, ENT_QUOTES);
4014
- $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4015
- }
4016
- } else if ($bom) {
4017
- $text = $this->utf8toUtf16BE($text, $bom);
4018
- }
4019
-
4020
- // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4021
- return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
4022
- }
4023
-
4024
- /**
4025
- * return array containing codepoints (UTF-8 character values) for the
4026
- * string passed in.
4027
- *
4028
- * based on the excellent TCPDF code by Nicola Asuni and the
4029
- * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4030
- *
4031
- * @access private
4032
- * @author Orion Richardson
4033
- * @since January 5, 2008
4034
- *
4035
- * @param string $text UTF-8 string to process
4036
- *
4037
- * @return array UTF-8 codepoints array for the string
4038
- */
4039
- function utf8toCodePointsArray(&$text)
4040
- {
4041
- $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4042
- $unicode = array(); // array containing unicode values
4043
- $bytes = array(); // array containing single character byte sequences
4044
- $numbytes = 1; // number of octets needed to represent the UTF-8 character
4045
-
4046
- for ($i = 0; $i < $length; $i++) {
4047
- $c = ord($text[$i]); // get one string character at time
4048
- if (count($bytes) === 0) { // get starting octect
4049
- if ($c <= 0x7F) {
4050
- $unicode[] = $c; // use the character "as is" because is ASCII
4051
- $numbytes = 1;
4052
- } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4053
- $bytes[] = ($c - 0xC0) << 0x06;
4054
- $numbytes = 2;
4055
- } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4056
- $bytes[] = ($c - 0xE0) << 0x0C;
4057
- $numbytes = 3;
4058
- } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4059
- $bytes[] = ($c - 0xF0) << 0x12;
4060
- $numbytes = 4;
4061
- } else {
4062
- // use replacement character for other invalid sequences
4063
- $unicode[] = 0xFFFD;
4064
- $bytes = array();
4065
- $numbytes = 1;
4066
- }
4067
- } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4068
- $bytes[] = $c - 0x80;
4069
- if (count($bytes) === $numbytes) {
4070
- // compose UTF-8 bytes to a single unicode value
4071
- $c = $bytes[0];
4072
- for ($j = 1; $j < $numbytes; $j++) {
4073
- $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4074
- }
4075
- if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) {
4076
- // The definition of UTF-8 prohibits encoding character numbers between
4077
- // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4078
- // encoding form (as surrogate pairs) and do not directly represent
4079
- // characters.
4080
- $unicode[] = 0xFFFD; // use replacement character
4081
- } else {
4082
- $unicode[] = $c; // add char to array
4083
- }
4084
- // reset data for next char
4085
- $bytes = array();
4086
- $numbytes = 1;
4087
- }
4088
- } else {
4089
- // use replacement character for other invalid sequences
4090
- $unicode[] = 0xFFFD;
4091
- $bytes = array();
4092
- $numbytes = 1;
4093
- }
4094
- }
4095
-
4096
- return $unicode;
4097
- }
4098
-
4099
- /**
4100
- * convert UTF-8 to UTF-16 with an additional byte order marker
4101
- * at the front if required.
4102
- *
4103
- * based on the excellent TCPDF code by Nicola Asuni and the
4104
- * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4105
- *
4106
- * @access private
4107
- * @author Orion Richardson
4108
- * @since January 5, 2008
4109
- *
4110
- * @param string $text UTF-8 string to process
4111
- * @param boolean $bom whether to add the byte order marker
4112
- *
4113
- * @return string UTF-16 result string
4114
- */
4115
- function utf8toUtf16BE(&$text, $bom = true)
4116
- {
4117
- $out = $bom ? "\xFE\xFF" : '';
4118
-
4119
- $unicode = $this->utf8toCodePointsArray($text);
4120
- foreach ($unicode as $c) {
4121
- if ($c === 0xFFFD) {
4122
- $out .= "\xFF\xFD"; // replacement character
4123
- } elseif ($c < 0x10000) {
4124
- $out .= chr($c >> 0x08) . chr($c & 0xFF);
4125
- } else {
4126
- $c -= 0x10000;
4127
- $w1 = 0xD800 | ($c >> 0x10);
4128
- $w2 = 0xDC00 | ($c & 0x3FF);
4129
- $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4130
- }
4131
- }
4132
-
4133
- return $out;
4134
- }
4135
-
4136
- /**
4137
- * given a start position and information about how text is to be laid out, calculate where
4138
- * on the page the text will end
4139
- *
4140
- * @param $x
4141
- * @param $y
4142
- * @param $angle
4143
- * @param $size
4144
- * @param $wa
4145
- * @param $text
4146
- * @return array
4147
- */
4148
- private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4149
- {
4150
- // given this information return an array containing x and y for the end position as elements 0 and 1
4151
- $w = $this->getTextWidth($size, $text);
4152
-
4153
- // need to adjust for the number of spaces in this text
4154
- $words = explode(' ', $text);
4155
- $nspaces = count($words) - 1;
4156
- $w += $wa * $nspaces;
4157
- $a = deg2rad((float)$angle);
4158
-
4159
- return array(cos($a) * $w + $x, -sin($a) * $w + $y);
4160
- }
4161
-
4162
- /**
4163
- * Callback method used by smallCaps
4164
- *
4165
- * @param array $matches
4166
- *
4167
- * @return string
4168
- */
4169
- function toUpper($matches)
4170
- {
4171
- return mb_strtoupper($matches[0]);
4172
- }
4173
-
4174
- function concatMatches($matches)
4175
- {
4176
- $str = "";
4177
- foreach ($matches as $match) {
4178
- $str .= $match[0];
4179
- }
4180
-
4181
- return $str;
4182
- }
4183
-
4184
- /**
4185
- * register text for font subsetting
4186
- *
4187
- * @param $font
4188
- * @param $text
4189
- */
4190
- function registerText($font, $text)
4191
- {
4192
- if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4193
- return;
4194
- }
4195
-
4196
- if (!isset($this->stringSubsets[$font])) {
4197
- $this->stringSubsets[$font] = array();
4198
- }
4199
-
4200
- $this->stringSubsets[$font] = array_unique(
4201
- array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4202
- );
4203
- }
4204
-
4205
- /**
4206
- * add text to the document, at a specified location, size and angle on the page
4207
- *
4208
- * @param $x
4209
- * @param $y
4210
- * @param $size
4211
- * @param $text
4212
- * @param int $angle
4213
- * @param int $wordSpaceAdjust
4214
- * @param int $charSpaceAdjust
4215
- * @param bool $smallCaps
4216
- */
4217
- function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4218
- {
4219
- if (!$this->numFonts) {
4220
- $this->selectFont($this->defaultFont);
4221
- }
4222
-
4223
- $text = str_replace(array("\r", "\n"), "", $text);
4224
-
4225
- if ($smallCaps) {
4226
- preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4227
- $lower = $this->concatMatches($matches);
4228
- d($lower);
4229
-
4230
- preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4231
- $other = $this->concatMatches($matches);
4232
- d($other);
4233
-
4234
- //$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4235
- }
4236
-
4237
- // if there are any open callbacks, then they should be called, to show the start of the line
4238
- if ($this->nCallback > 0) {
4239
- for ($i = $this->nCallback; $i > 0; $i--) {
4240
- // call each function
4241
- $info = array(
4242
- 'x' => $x,
4243
- 'y' => $y,
4244
- 'angle' => $angle,
4245
- 'status' => 'sol',
4246
- 'p' => $this->callback[$i]['p'],
4247
- 'nCallback' => $this->callback[$i]['nCallback'],
4248
- 'height' => $this->callback[$i]['height'],
4249
- 'descender' => $this->callback[$i]['descender']
4250
- );
4251
-
4252
- $func = $this->callback[$i]['f'];
4253
- $this->$func($info);
4254
- }
4255
- }
4256
-
4257
- if ($angle == 0) {
4258
- $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
4259
- } else {
4260
- $a = deg2rad((float)$angle);
4261
- $this->addContent(
4262
- sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
4263
- );
4264
- }
4265
-
4266
- if ($wordSpaceAdjust != 0) {
4267
- $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
4268
- }
4269
-
4270
- if ($charSpaceAdjust != 0) {
4271
- $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
4272
- }
4273
-
4274
- $len = mb_strlen($text);
4275
- $start = 0;
4276
-
4277
- if ($start < $len) {
4278
- $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start);
4279
- $place_text = $this->filterText($part, false);
4280
- // modify unicode text so that extra word spacing is manually implemented (bug #)
4281
- $cf = $this->currentFont;
4282
- if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) {
4283
- $space_scale = 1000 / $size;
4284
- $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
4285
- }
4286
- $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
4287
- $this->addContent(" [($place_text)] TJ");
4288
- }
4289
-
4290
- if ($wordSpaceAdjust != 0) {
4291
- $this->addContent(sprintf(" %.3F Tw", 0));
4292
- }
4293
-
4294
- if ($charSpaceAdjust != 0) {
4295
- $this->addContent(sprintf(" %.3F Tc", 0));
4296
- }
4297
-
4298
- $this->addContent(' ET');
4299
-
4300
- // if there are any open callbacks, then they should be called, to show the end of the line
4301
- if ($this->nCallback > 0) {
4302
- for ($i = $this->nCallback; $i > 0; $i--) {
4303
- // call each function
4304
- $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
4305
- $info = array(
4306
- 'x' => $tmp[0],
4307
- 'y' => $tmp[1],
4308
- 'angle' => $angle,
4309
- 'status' => 'eol',
4310
- 'p' => $this->callback[$i]['p'],
4311
- 'nCallback' => $this->callback[$i]['nCallback'],
4312
- 'height' => $this->callback[$i]['height'],
4313
- 'descender' => $this->callback[$i]['descender']
4314
- );
4315
- $func = $this->callback[$i]['f'];
4316
- $this->$func($info);
4317
- }
4318
- }
4319
- }
4320
-
4321
- /**
4322
- * calculate how wide a given text string will be on a page, at a given size.
4323
- * this can be called externally, but is also used by the other class functions
4324
- *
4325
- * @param $size
4326
- * @param $text
4327
- * @param int $word_spacing
4328
- * @param int $char_spacing
4329
- * @return float|int
4330
- */
4331
- function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
4332
- {
4333
- static $ord_cache = array();
4334
-
4335
- // this function should not change any of the settings, though it will need to
4336
- // track any directives which change during calculation, so copy them at the start
4337
- // and put them back at the end.
4338
- $store_currentTextState = $this->currentTextState;
4339
-
4340
- if (!$this->numFonts) {
4341
- $this->selectFont($this->defaultFont);
4342
- }
4343
-
4344
- $text = str_replace(array("\r", "\n"), "", $text);
4345
-
4346
- // converts a number or a float to a string so it can get the width
4347
- $text = "$text";
4348
-
4349
- // hmm, this is where it all starts to get tricky - use the font information to
4350
- // calculate the width of each character, add them up and convert to user units
4351
- $w = 0;
4352
- $cf = $this->currentFont;
4353
- $current_font = $this->fonts[$cf];
4354
- $space_scale = 1000 / ($size > 0 ? $size : 1);
4355
- $n_spaces = 0;
4356
-
4357
- if ($current_font['isUnicode']) {
4358
- // for Unicode, use the code points array to calculate width rather
4359
- // than just the string itself
4360
- $unicode = $this->utf8toCodePointsArray($text);
4361
-
4362
- foreach ($unicode as $char) {
4363
- // check if we have to replace character
4364
- if (isset($current_font['differences'][$char])) {
4365
- $char = $current_font['differences'][$char];
4366
- }
4367
-
4368
- if (isset($current_font['C'][$char])) {
4369
- $char_width = $current_font['C'][$char];
4370
-
4371
- // add the character width
4372
- $w += $char_width;
4373
-
4374
- // add additional padding for space
4375
- if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4376
- $w += $word_spacing * $space_scale;
4377
- $n_spaces++;
4378
- }
4379
- }
4380
- }
4381
-
4382
- // add additional char spacing
4383
- if ($char_spacing != 0) {
4384
- $w += $char_spacing * $space_scale * (count($unicode) + $n_spaces);
4385
- }
4386
-
4387
- } else {
4388
- // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
4389
- if ($this->isUnicode) {
4390
- $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
4391
- }
4392
-
4393
- $len = mb_strlen($text, 'Windows-1252');
4394
-
4395
- for ($i = 0; $i < $len; $i++) {
4396
- $c = $text[$i];
4397
- $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
4398
-
4399
- // check if we have to replace character
4400
- if (isset($current_font['differences'][$char])) {
4401
- $char = $current_font['differences'][$char];
4402
- }
4403
-
4404
- if (isset($current_font['C'][$char])) {
4405
- $char_width = $current_font['C'][$char];
4406
-
4407
- // add the character width
4408
- $w += $char_width;
4409
-
4410
- // add additional padding for space
4411
- if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4412
- $w += $word_spacing * $space_scale;
4413
- $n_spaces++;
4414
- }
4415
- }
4416
- }
4417
-
4418
- // add additional char spacing
4419
- if ($char_spacing != 0) {
4420
- $w += $char_spacing * $space_scale * ($len + $n_spaces);
4421
- }
4422
- }
4423
-
4424
- $this->currentTextState = $store_currentTextState;
4425
- $this->setCurrentFont();
4426
-
4427
- return $w * $size / 1000;
4428
- }
4429
-
4430
- /**
4431
- * this will be called at a new page to return the state to what it was on the
4432
- * end of the previous page, before the stack was closed down
4433
- * This is to get around not being able to have open 'q' across pages
4434
- *
4435
- * @param int $pageEnd
4436
- */
4437
- function saveState($pageEnd = 0)
4438
- {
4439
- if ($pageEnd) {
4440
- // this will be called at a new page to return the state to what it was on the
4441
- // end of the previous page, before the stack was closed down
4442
- // This is to get around not being able to have open 'q' across pages
4443
- $opt = $this->stateStack[$pageEnd];
4444
- // ok to use this as stack starts numbering at 1
4445
- $this->setColor($opt['col'], true);
4446
- $this->setStrokeColor($opt['str'], true);
4447
- $this->addContent("\n" . $opt['lin']);
4448
- // $this->currentLineStyle = $opt['lin'];
4449
- } else {
4450
- $this->nStateStack++;
4451
- $this->stateStack[$this->nStateStack] = array(
4452
- 'col' => $this->currentColor,
4453
- 'str' => $this->currentStrokeColor,
4454
- 'lin' => $this->currentLineStyle
4455
- );
4456
- }
4457
-
4458
- $this->save();
4459
- }
4460
-
4461
- /**
4462
- * restore a previously saved state
4463
- *
4464
- * @param int $pageEnd
4465
- */
4466
- function restoreState($pageEnd = 0)
4467
- {
4468
- if (!$pageEnd) {
4469
- $n = $this->nStateStack;
4470
- $this->currentColor = $this->stateStack[$n]['col'];
4471
- $this->currentStrokeColor = $this->stateStack[$n]['str'];
4472
- $this->addContent("\n" . $this->stateStack[$n]['lin']);
4473
- $this->currentLineStyle = $this->stateStack[$n]['lin'];
4474
- $this->stateStack[$n] = null;
4475
- unset($this->stateStack[$n]);
4476
- $this->nStateStack--;
4477
- }
4478
-
4479
- $this->restore();
4480
- }
4481
-
4482
- /**
4483
- * make a loose object, the output will go into this object, until it is closed, then will revert to
4484
- * the current one.
4485
- * this object will not appear until it is included within a page.
4486
- * the function will return the object number
4487
- *
4488
- * @return int
4489
- */
4490
- function openObject()
4491
- {
4492
- $this->nStack++;
4493
- $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4494
- // add a new object of the content type, to hold the data flow
4495
- $this->numObj++;
4496
- $this->o_contents($this->numObj, 'new');
4497
- $this->currentContents = $this->numObj;
4498
- $this->looseObjects[$this->numObj] = 1;
4499
-
4500
- return $this->numObj;
4501
- }
4502
-
4503
- /**
4504
- * open an existing object for editing
4505
- *
4506
- * @param $id
4507
- */
4508
- function reopenObject($id)
4509
- {
4510
- $this->nStack++;
4511
- $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4512
- $this->currentContents = $id;
4513
-
4514
- // also if this object is the primary contents for a page, then set the current page to its parent
4515
- if (isset($this->objects[$id]['onPage'])) {
4516
- $this->currentPage = $this->objects[$id]['onPage'];
4517
- }
4518
- }
4519
-
4520
- /**
4521
- * close an object
4522
- */
4523
- function closeObject()
4524
- {
4525
- // close the object, as long as there was one open in the first place, which will be indicated by
4526
- // an objectId on the stack.
4527
- if ($this->nStack > 0) {
4528
- $this->currentContents = $this->stack[$this->nStack]['c'];
4529
- $this->currentPage = $this->stack[$this->nStack]['p'];
4530
- $this->nStack--;
4531
- // easier to probably not worry about removing the old entries, they will be overwritten
4532
- // if there are new ones.
4533
- }
4534
- }
4535
-
4536
- /**
4537
- * stop an object from appearing on pages from this point on
4538
- *
4539
- * @param $id
4540
- */
4541
- function stopObject($id)
4542
- {
4543
- // if an object has been appearing on pages up to now, then stop it, this page will
4544
- // be the last one that could contain it.
4545
- if (isset($this->addLooseObjects[$id])) {
4546
- $this->addLooseObjects[$id] = '';
4547
- }
4548
- }
4549
-
4550
- /**
4551
- * after an object has been created, it wil only show if it has been added, using this function.
4552
- *
4553
- * @param $id
4554
- * @param string $options
4555
- */
4556
- function addObject($id, $options = 'add')
4557
- {
4558
- // add the specified object to the page
4559
- if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
4560
- // then it is a valid object, and it is not being added to itself
4561
- switch ($options) {
4562
- case 'all':
4563
- // then this object is to be added to this page (done in the next block) and
4564
- // all future new pages.
4565
- $this->addLooseObjects[$id] = 'all';
4566
-
4567
- case 'add':
4568
- if (isset($this->objects[$this->currentContents]['onPage'])) {
4569
- // then the destination contents is the primary for the page
4570
- // (though this object is actually added to that page)
4571
- $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
4572
- }
4573
- break;
4574
-
4575
- case 'even':
4576
- $this->addLooseObjects[$id] = 'even';
4577
- $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4578
- if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
4579
- $this->addObject($id);
4580
- // hacky huh :)
4581
- }
4582
- break;
4583
-
4584
- case 'odd':
4585
- $this->addLooseObjects[$id] = 'odd';
4586
- $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4587
- if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
4588
- $this->addObject($id);
4589
- // hacky huh :)
4590
- }
4591
- break;
4592
-
4593
- case 'next':
4594
- $this->addLooseObjects[$id] = 'all';
4595
- break;
4596
-
4597
- case 'nexteven':
4598
- $this->addLooseObjects[$id] = 'even';
4599
- break;
4600
-
4601
- case 'nextodd':
4602
- $this->addLooseObjects[$id] = 'odd';
4603
- break;
4604
- }
4605
- }
4606
- }
4607
-
4608
- /**
4609
- * return a storable representation of a specific object
4610
- *
4611
- * @param $id
4612
- * @return string|null
4613
- */
4614
- function serializeObject($id)
4615
- {
4616
- if (array_key_exists($id, $this->objects)) {
4617
- return serialize($this->objects[$id]);
4618
- }
4619
-
4620
- return null;
4621
- }
4622
-
4623
- /**
4624
- * restore an object from its stored representation. returns its new object id.
4625
- *
4626
- * @param $obj
4627
- * @return int
4628
- */
4629
- function restoreSerializedObject($obj)
4630
- {
4631
- $obj_id = $this->openObject();
4632
- $this->objects[$obj_id] = unserialize($obj);
4633
- $this->closeObject();
4634
-
4635
- return $obj_id;
4636
- }
4637
-
4638
- /**
4639
- * add content to the documents info object
4640
- *
4641
- * @param $label
4642
- * @param int $value
4643
- */
4644
- function addInfo($label, $value = 0)
4645
- {
4646
- // this will only work if the label is one of the valid ones.
4647
- // modify this so that arrays can be passed as well.
4648
- // if $label is an array then assume that it is key => value pairs
4649
- // else assume that they are both scalar, anything else will probably error
4650
- if (is_array($label)) {
4651
- foreach ($label as $l => $v) {
4652
- $this->o_info($this->infoObject, $l, $v);
4653
- }
4654
- } else {
4655
- $this->o_info($this->infoObject, $label, $value);
4656
- }
4657
- }
4658
-
4659
- /**
4660
- * set the viewer preferences of the document, it is up to the browser to obey these.
4661
- *
4662
- * @param $label
4663
- * @param int $value
4664
- */
4665
- function setPreferences($label, $value = 0)
4666
- {
4667
- // this will only work if the label is one of the valid ones.
4668
- if (is_array($label)) {
4669
- foreach ($label as $l => $v) {
4670
- $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v));
4671
- }
4672
- } else {
4673
- $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value));
4674
- }
4675
- }
4676
-
4677
- /**
4678
- * extract an integer from a position in a byte stream
4679
- *
4680
- * @param $data
4681
- * @param $pos
4682
- * @param $num
4683
- * @return int
4684
- */
4685
- private function getBytes(&$data, $pos, $num)
4686
- {
4687
- // return the integer represented by $num bytes from $pos within $data
4688
- $ret = 0;
4689
- for ($i = 0; $i < $num; $i++) {
4690
- $ret *= 256;
4691
- $ret += ord($data[$pos + $i]);
4692
- }
4693
-
4694
- return $ret;
4695
- }
4696
-
4697
- /**
4698
- * Check if image already added to pdf image directory.
4699
- * If yes, need not to create again (pass empty data)
4700
- *
4701
- * @param $imgname
4702
- * @return bool
4703
- */
4704
- function image_iscached($imgname)
4705
- {
4706
- return isset($this->imagelist[$imgname]);
4707
- }
4708
-
4709
- /**
4710
- * add a PNG image into the document, from a GD object
4711
- * this should work with remote files
4712
- *
4713
- * @param string $file The PNG file
4714
- * @param float $x X position
4715
- * @param float $y Y position
4716
- * @param float $w Width
4717
- * @param float $h Height
4718
- * @param resource $img A GD resource
4719
- * @param bool $is_mask true if the image is a mask
4720
- * @param bool $mask true if the image is masked
4721
- * @throws Exception
4722
- */
4723
- function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null)
4724
- {
4725
- if (!function_exists("imagepng")) {
4726
- throw new \Exception("The PHP GD extension is required, but is not installed.");
4727
- }
4728
-
4729
- //if already cached, need not to read again
4730
- if (isset($this->imagelist[$file])) {
4731
- $data = null;
4732
- } else {
4733
- // Example for transparency handling on new image. Retain for current image
4734
- // $tIndex = imagecolortransparent($img);
4735
- // if ($tIndex > 0) {
4736
- // $tColor = imagecolorsforindex($img, $tIndex);
4737
- // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
4738
- // imagefill($new_img, 0, 0, $new_tIndex);
4739
- // imagecolortransparent($new_img, $new_tIndex);
4740
- // }
4741
- // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
4742
- //imagealphablending($img, true);
4743
-
4744
- //default, but explicitely set to ensure pdf compatibility
4745
- imagesavealpha($img, false/*!$is_mask && !$mask*/);
4746
-
4747
- $error = 0;
4748
- //DEBUG_IMG_TEMP
4749
- //debugpng
4750
- if (defined("DEBUGPNG") && DEBUGPNG) {
4751
- print '[addImagePng ' . $file . ']';
4752
- }
4753
-
4754
- ob_start();
4755
- @imagepng($img);
4756
- $data = ob_get_clean();
4757
-
4758
- if ($data == '') {
4759
- $error = 1;
4760
- $errormsg = 'trouble writing file from GD';
4761
- //DEBUG_IMG_TEMP
4762
- //debugpng
4763
- if (defined("DEBUGPNG") && DEBUGPNG) {
4764
- print 'trouble writing file from GD';
4765
- }
4766
- }
4767
-
4768
- if ($error) {
4769
- $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
4770
-
4771
- return;
4772
- }
4773
- } //End isset($this->imagelist[$file]) (png Duplicate removal)
4774
-
4775
- $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask);
4776
- }
4777
-
4778
- /**
4779
- * @param $file
4780
- * @param $x
4781
- * @param $y
4782
- * @param $w
4783
- * @param $h
4784
- * @param $byte
4785
- */
4786
- protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
4787
- {
4788
- // generate images
4789
- $img = imagecreatefrompng($file);
4790
-
4791
- if ($img === false) {
4792
- return;
4793
- }
4794
-
4795
- // FIXME The pixel transformation doesn't work well with 8bit PNGs
4796
- $eight_bit = ($byte & 4) !== 4;
4797
-
4798
- $wpx = imagesx($img);
4799
- $hpx = imagesy($img);
4800
-
4801
- imagesavealpha($img, false);
4802
-
4803
- // create temp alpha file
4804
- $tempfile_alpha = tempnam($this->tmp, "cpdf_img_");
4805
- @unlink($tempfile_alpha);
4806
- $tempfile_alpha = "$tempfile_alpha.png";
4807
-
4808
- // create temp plain file
4809
- $tempfile_plain = tempnam($this->tmp, "cpdf_img_");
4810
- @unlink($tempfile_plain);
4811
- $tempfile_plain = "$tempfile_plain.png";
4812
-
4813
- $imgalpha = imagecreate($wpx, $hpx);
4814
- imagesavealpha($imgalpha, false);
4815
-
4816
- // generate gray scale palette (0 -> 255)
4817
- for ($c = 0; $c < 256; ++$c) {
4818
- imagecolorallocate($imgalpha, $c, $c, $c);
4819
- }
4820
-
4821
- // Use PECL gmagick + Graphics Magic to process transparent PNG images
4822
- if (extension_loaded("gmagick")) {
4823
- $gmagick = new \Gmagick($file);
4824
- $gmagick->setimageformat('png');
4825
-
4826
- // Get opacity channel (negative of alpha channel)
4827
- $alpha_channel_neg = clone $gmagick;
4828
- $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
4829
-
4830
- // Negate opacity channel
4831
- $alpha_channel = new \Gmagick();
4832
- $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
4833
- $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
4834
- $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
4835
- $alpha_channel->writeimage($tempfile_alpha);
4836
-
4837
- // Cast to 8bit+palette
4838
- $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4839
- imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4840
- imagedestroy($imgalpha_);
4841
- imagepng($imgalpha, $tempfile_alpha);
4842
-
4843
- // Make opaque image
4844
- $color_channels = new \Gmagick();
4845
- $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
4846
- $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
4847
- $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
4848
- $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
4849
- $color_channels->writeimage($tempfile_plain);
4850
-
4851
- $imgplain = imagecreatefrompng($tempfile_plain);
4852
- }
4853
- // Use PECL imagick + ImageMagic to process transparent PNG images
4854
- elseif (extension_loaded("imagick")) {
4855
- // Native cloning was added to pecl-imagick in svn commit 263814
4856
- // the first version containing it was 3.0.1RC1
4857
- static $imagickClonable = null;
4858
- if ($imagickClonable === null) {
4859
- $imagickClonable = version_compare(Imagick::IMAGICK_EXTVER, '3.0.1rc1') > 0;
4860
- }
4861
-
4862
- $imagick = new \Imagick($file);
4863
- $imagick->setFormat('png');
4864
-
4865
- // Get opacity channel (negative of alpha channel)
4866
- if ($imagick->getImageAlphaChannel() !== 0) {
4867
- $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
4868
- $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
4869
- $alpha_channel->negateImage(true);
4870
- $alpha_channel->writeImage($tempfile_alpha);
4871
-
4872
- // Cast to 8bit+palette
4873
- $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4874
- imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4875
- imagedestroy($imgalpha_);
4876
- imagepng($imgalpha, $tempfile_alpha);
4877
- } else {
4878
- $tempfile_alpha = null;
4879
- }
4880
-
4881
- // Make opaque image
4882
- $color_channels = new \Imagick();
4883
- $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
4884
- $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
4885
- $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
4886
- $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
4887
- $color_channels->writeImage($tempfile_plain);
4888
-
4889
- $imgplain = imagecreatefrompng($tempfile_plain);
4890
- } else {
4891
- // allocated colors cache
4892
- $allocated_colors = array();
4893
-
4894
- // extract alpha channel
4895
- for ($xpx = 0; $xpx < $wpx; ++$xpx) {
4896
- for ($ypx = 0; $ypx < $hpx; ++$ypx) {
4897
- $color = imagecolorat($img, $xpx, $ypx);
4898
- $col = imagecolorsforindex($img, $color);
4899
- $alpha = $col['alpha'];
4900
-
4901
- if ($eight_bit) {
4902
- // with gamma correction
4903
- $gammacorr = 2.2;
4904
- $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255;
4905
- } else {
4906
- // without gamma correction
4907
- $pixel = (127 - $alpha) * 2;
4908
-
4909
- $key = $col['red'] . $col['green'] . $col['blue'];
4910
-
4911
- if (!isset($allocated_colors[$key])) {
4912
- $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
4913
- $allocated_colors[$key] = $pixel_img;
4914
- } else {
4915
- $pixel_img = $allocated_colors[$key];
4916
- }
4917
-
4918
- imagesetpixel($img, $xpx, $ypx, $pixel_img);
4919
- }
4920
-
4921
- imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
4922
- }
4923
- }
4924
-
4925
- // extract image without alpha channel
4926
- $imgplain = imagecreatetruecolor($wpx, $hpx);
4927
- imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
4928
- imagedestroy($img);
4929
-
4930
- imagepng($imgalpha, $tempfile_alpha);
4931
- imagepng($imgplain, $tempfile_plain);
4932
- }
4933
-
4934
- // embed mask image
4935
- if ($tempfile_alpha) {
4936
- $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true);
4937
- imagedestroy($imgalpha);
4938
- }
4939
-
4940
- // embed image, masked with previously embedded mask
4941
- $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, ($tempfile_alpha !== null));
4942
- imagedestroy($imgplain);
4943
-
4944
- // remove temp files
4945
- if ($tempfile_alpha) {
4946
- unlink($tempfile_alpha);
4947
- }
4948
- unlink($tempfile_plain);
4949
- }
4950
-
4951
- /**
4952
- * add a PNG image into the document, from a file
4953
- * this should work with remote files
4954
- *
4955
- * @param $file
4956
- * @param $x
4957
- * @param $y
4958
- * @param int $w
4959
- * @param int $h
4960
- * @throws Exception
4961
- */
4962
- function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
4963
- {
4964
- if (!function_exists("imagecreatefrompng")) {
4965
- throw new \Exception("The PHP GD extension is required, but is not installed.");
4966
- }
4967
-
4968
- //if already cached, need not to read again
4969
- if (isset($this->imagelist[$file])) {
4970
- $img = null;
4971
- } else {
4972
- $info = file_get_contents($file, false, null, 24, 5);
4973
- $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
4974
- $bit_depth = $meta["bitDepth"];
4975
- $color_type = $meta["colorType"];
4976
-
4977
- // http://www.w3.org/TR/PNG/#11IHDR
4978
- // 3 => indexed
4979
- // 4 => greyscale with alpha
4980
- // 6 => fullcolor with alpha
4981
- $is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4);
4982
-
4983
- if ($is_alpha) { // exclude grayscale alpha
4984
- $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
4985
- return;
4986
- }
4987
-
4988
- //png files typically contain an alpha channel.
4989
- //pdf file format or class.pdf does not support alpha blending.
4990
- //on alpha blended images, more transparent areas have a color near black.
4991
- //This appears in the result on not storing the alpha channel.
4992
- //Correct would be the box background image or its parent when transparent.
4993
- //But this would make the image dependent on the background.
4994
- //Therefore create an image with white background and copy in
4995
- //A more natural background than black is white.
4996
- //Therefore create an empty image with white background and merge the
4997
- //image in with alpha blending.
4998
- $imgtmp = @imagecreatefrompng($file);
4999
- if (!$imgtmp) {
5000
- return;
5001
- }
5002
- $sx = imagesx($imgtmp);
5003
- $sy = imagesy($imgtmp);
5004
- $img = imagecreatetruecolor($sx, $sy);
5005
- imagealphablending($img, true);
5006
-
5007
- // @todo is it still needed ??
5008
- $ti = imagecolortransparent($imgtmp);
5009
- if ($ti >= 0) {
5010
- $tc = imagecolorsforindex($imgtmp, $ti);
5011
- $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5012
- imagefill($img, 0, 0, $ti);
5013
- imagecolortransparent($img, $ti);
5014
- } else {
5015
- imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5016
- }
5017
-
5018
- imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5019
- imagedestroy($imgtmp);
5020
- }
5021
- $this->addImagePng($file, $x, $y, $w, $h, $img);
5022
-
5023
- if ($img) {
5024
- imagedestroy($img);
5025
- }
5026
- }
5027
-
5028
- /**
5029
- * add a PNG image into the document, from a file
5030
- * this should work with remote files
5031
- *
5032
- * @param $file
5033
- * @param $x
5034
- * @param $y
5035
- * @param int $w
5036
- * @param int $h
5037
- */
5038
- function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
5039
- {
5040
- $doc = new \Svg\Document();
5041
- $doc->loadFile($file);
5042
- $dimensions = $doc->getDimensions();
5043
-
5044
- $this->save();
5045
-
5046
- $this->transform(array($w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y));
5047
-
5048
- $surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
5049
- $doc->render($surface);
5050
-
5051
- $this->restore();
5052
- }
5053
-
5054
- /**
5055
- * add a PNG image into the document, from a memory buffer of the file
5056
- *
5057
- * @param $file
5058
- * @param $x
5059
- * @param $y
5060
- * @param float $w
5061
- * @param float $h
5062
- * @param $data
5063
- * @param bool $is_mask
5064
- * @param null $mask
5065
- */
5066
- function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null)
5067
- {
5068
- if (isset($this->imagelist[$file])) {
5069
- $data = null;
5070
- $info['width'] = $this->imagelist[$file]['w'];
5071
- $info['height'] = $this->imagelist[$file]['h'];
5072
- $label = $this->imagelist[$file]['label'];
5073
- } else {
5074
- if ($data == null) {
5075
- $this->addMessage('addPngFromBuf error - data not present!');
5076
-
5077
- return;
5078
- }
5079
-
5080
- $error = 0;
5081
-
5082
- if (!$error) {
5083
- $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5084
-
5085
- if (mb_substr($data, 0, 8, '8bit') != $header) {
5086
- $error = 1;
5087
-
5088
- if (defined("DEBUGPNG") && DEBUGPNG) {
5089
- print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5090
- }
5091
-
5092
- $errormsg = 'this file does not have a valid header';
5093
- }
5094
- }
5095
-
5096
- if (!$error) {
5097
- // set pointer
5098
- $p = 8;
5099
- $len = mb_strlen($data, '8bit');
5100
-
5101
- // cycle through the file, identifying chunks
5102
- $haveHeader = 0;
5103
- $info = array();
5104
- $idata = '';
5105
- $pdata = '';
5106
-
5107
- while ($p < $len) {
5108
- $chunkLen = $this->getBytes($data, $p, 4);
5109
- $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5110
-
5111
- switch ($chunkType) {
5112
- case 'IHDR':
5113
- // this is where all the file information comes from
5114
- $info['width'] = $this->getBytes($data, $p + 8, 4);
5115
- $info['height'] = $this->getBytes($data, $p + 12, 4);
5116
- $info['bitDepth'] = ord($data[$p + 16]);
5117
- $info['colorType'] = ord($data[$p + 17]);
5118
- $info['compressionMethod'] = ord($data[$p + 18]);
5119
- $info['filterMethod'] = ord($data[$p + 19]);
5120
- $info['interlaceMethod'] = ord($data[$p + 20]);
5121
-
5122
- //print_r($info);
5123
- $haveHeader = 1;
5124
- if ($info['compressionMethod'] != 0) {
5125
- $error = 1;
5126
-
5127
- //debugpng
5128
- if (defined("DEBUGPNG") && DEBUGPNG) {
5129
- print '[addPngFromFile unsupported compression method ' . $file . ']';
5130
- }
5131
-
5132
- $errormsg = 'unsupported compression method';
5133
- }
5134
-
5135
- if ($info['filterMethod'] != 0) {
5136
- $error = 1;
5137
-
5138
- //debugpng
5139
- if (defined("DEBUGPNG") && DEBUGPNG) {
5140
- print '[addPngFromFile unsupported filter method ' . $file . ']';
5141
- }
5142
-
5143
- $errormsg = 'unsupported filter method';
5144
- }
5145
- break;
5146
-
5147
- case 'PLTE':
5148
- $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5149
- break;
5150
-
5151
- case 'IDAT':
5152
- $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5153
- break;
5154
-
5155
- case 'tRNS':
5156
- //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5157
- //print "tRNS found, color type = ".$info['colorType']."\n";
5158
- $transparency = array();
5159
-
5160
- switch ($info['colorType']) {
5161
- // indexed color, rbg
5162
- case 3:
5163
- /* corresponding to entries in the plte chunk
5164
- Alpha for palette index 0: 1 byte
5165
- Alpha for palette index 1: 1 byte
5166
- ...etc...
5167
- */
5168
- // there will be one entry for each palette entry. up until the last non-opaque entry.
5169
- // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5170
- $transparency['type'] = 'indexed';
5171
- $trans = 0;
5172
-
5173
- for ($i = $chunkLen; $i >= 0; $i--) {
5174
- if (ord($data[$p + 8 + $i]) == 0) {
5175
- $trans = $i;
5176
- }
5177
- }
5178
-
5179
- $transparency['data'] = $trans;
5180
- break;
5181
-
5182
- // grayscale
5183
- case 0:
5184
- /* corresponding to entries in the plte chunk
5185
- Gray: 2 bytes, range 0 .. (2^bitdepth)-1
5186
- */
5187
- // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5188
- $transparency['type'] = 'indexed';
5189
- $transparency['data'] = ord($data[$p + 8 + 1]);
5190
- break;
5191
-
5192
- // truecolor
5193
- case 2:
5194
- /* corresponding to entries in the plte chunk
5195
- Red: 2 bytes, range 0 .. (2^bitdepth)-1
5196
- Green: 2 bytes, range 0 .. (2^bitdepth)-1
5197
- Blue: 2 bytes, range 0 .. (2^bitdepth)-1
5198
- */
5199
- $transparency['r'] = $this->getBytes($data, $p + 8, 2);
5200
- // r from truecolor
5201
- $transparency['g'] = $this->getBytes($data, $p + 10, 2);
5202
- // g from truecolor
5203
- $transparency['b'] = $this->getBytes($data, $p + 12, 2);
5204
- // b from truecolor
5205
-
5206
- $transparency['type'] = 'color-key';
5207
- break;
5208
-
5209
- //unsupported transparency type
5210
- default:
5211
- if (defined("DEBUGPNG") && DEBUGPNG) {
5212
- print '[addPngFromFile unsupported transparency type ' . $file . ']';
5213
- }
5214
- break;
5215
- }
5216
-
5217
- // KS End new code
5218
- break;
5219
-
5220
- default:
5221
- break;
5222
- }
5223
-
5224
- $p += $chunkLen + 12;
5225
- }
5226
-
5227
- if (!$haveHeader) {
5228
- $error = 1;
5229
-
5230
- //debugpng
5231
- if (defined("DEBUGPNG") && DEBUGPNG) {
5232
- print '[addPngFromFile information header is missing ' . $file . ']';
5233
- }
5234
-
5235
- $errormsg = 'information header is missing';
5236
- }
5237
-
5238
- if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
5239
- $error = 1;
5240
-
5241
- //debugpng
5242
- if (defined("DEBUGPNG") && DEBUGPNG) {
5243
- print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
5244
- }
5245
-
5246
- $errormsg = 'There appears to be no support for interlaced images in pdf.';
5247
- }
5248
- }
5249
-
5250
- if (!$error && $info['bitDepth'] > 8) {
5251
- $error = 1;
5252
-
5253
- //debugpng
5254
- if (defined("DEBUGPNG") && DEBUGPNG) {
5255
- print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
5256
- }
5257
-
5258
- $errormsg = 'only bit depth of 8 or less is supported';
5259
- }
5260
-
5261
- if (!$error) {
5262
- switch ($info['colorType']) {
5263
- case 3:
5264
- $color = 'DeviceRGB';
5265
- $ncolor = 1;
5266
- break;
5267
-
5268
- case 2:
5269
- $color = 'DeviceRGB';
5270
- $ncolor = 3;
5271
- break;
5272
-
5273
- case 0:
5274
- $color = 'DeviceGray';
5275
- $ncolor = 1;
5276
- break;
5277
-
5278
- default:
5279
- $error = 1;
5280
-
5281
- //debugpng
5282
- if (defined("DEBUGPNG") && DEBUGPNG) {
5283
- print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
5284
- }
5285
-
5286
- $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
5287
- }
5288
- }
5289
-
5290
- if ($error) {
5291
- $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5292
-
5293
- return;
5294
- }
5295
-
5296
- //print_r($info);
5297
- // so this image is ok... add it in.
5298
- $this->numImages++;
5299
- $im = $this->numImages;
5300
- $label = "I$im";
5301
- $this->numObj++;
5302
-
5303
- // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
5304
- $options = array(
5305
- 'label' => $label,
5306
- 'data' => $idata,
5307
- 'bitsPerComponent' => $info['bitDepth'],
5308
- 'pdata' => $pdata,
5309
- 'iw' => $info['width'],
5310
- 'ih' => $info['height'],
5311
- 'type' => 'png',
5312
- 'color' => $color,
5313
- 'ncolor' => $ncolor,
5314
- 'masked' => $mask,
5315
- 'isMask' => $is_mask
5316
- );
5317
-
5318
- if (isset($transparency)) {
5319
- $options['transparency'] = $transparency;
5320
- }
5321
-
5322
- $this->o_image($this->numObj, 'new', $options);
5323
- $this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']);
5324
- }
5325
-
5326
- if ($is_mask) {
5327
- return;
5328
- }
5329
-
5330
- if ($w <= 0 && $h <= 0) {
5331
- $w = $info['width'];
5332
- $h = $info['height'];
5333
- }
5334
-
5335
- if ($w <= 0) {
5336
- $w = $h / $info['height'] * $info['width'];
5337
- }
5338
-
5339
- if ($h <= 0) {
5340
- $h = $w * $info['height'] / $info['width'];
5341
- }
5342
-
5343
- $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
5344
- }
5345
-
5346
- /**
5347
- * add a JPEG image into the document, from a file
5348
- *
5349
- * @param $img
5350
- * @param $x
5351
- * @param $y
5352
- * @param int $w
5353
- * @param int $h
5354
- */
5355
- function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
5356
- {
5357
- // attempt to add a jpeg image straight from a file, using no GD commands
5358
- // note that this function is unable to operate on a remote file.
5359
-
5360
- if (!file_exists($img)) {
5361
- return;
5362
- }
5363
-
5364
- if ($this->image_iscached($img)) {
5365
- $data = null;
5366
- $imageWidth = $this->imagelist[$img]['w'];
5367
- $imageHeight = $this->imagelist[$img]['h'];
5368
- $channels = $this->imagelist[$img]['c'];
5369
- } else {
5370
- $tmp = getimagesize($img);
5371
- $imageWidth = $tmp[0];
5372
- $imageHeight = $tmp[1];
5373
-
5374
- if (isset($tmp['channels'])) {
5375
- $channels = $tmp['channels'];
5376
- } else {
5377
- $channels = 3;
5378
- }
5379
-
5380
- $data = file_get_contents($img);
5381
- }
5382
-
5383
- if ($w <= 0 && $h <= 0) {
5384
- $w = $imageWidth;
5385
- }
5386
-
5387
- if ($w == 0) {
5388
- $w = $h / $imageHeight * $imageWidth;
5389
- }
5390
-
5391
- if ($h == 0) {
5392
- $h = $w * $imageHeight / $imageWidth;
5393
- }
5394
-
5395
- $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img);
5396
- }
5397
-
5398
- /**
5399
- * common code used by the two JPEG adding functions
5400
- * @param $data
5401
- * @param $x
5402
- * @param $y
5403
- * @param int $w
5404
- * @param int $h
5405
- * @param $imageWidth
5406
- * @param $imageHeight
5407
- * @param int $channels
5408
- * @param $imgname
5409
- */
5410
- private function addJpegImage_common(
5411
- &$data,
5412
- $x,
5413
- $y,
5414
- $w = 0,
5415
- $h = 0,
5416
- $imageWidth,
5417
- $imageHeight,
5418
- $channels = 3,
5419
- $imgname
5420
- ) {
5421
- if ($this->image_iscached($imgname)) {
5422
- $label = $this->imagelist[$imgname]['label'];
5423
- //debugpng
5424
- //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
5425
-
5426
- } else {
5427
- if ($data == null) {
5428
- $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
5429
-
5430
- return;
5431
- }
5432
-
5433
- // note that this function is not to be called externally
5434
- // it is just the common code between the GD and the file options
5435
- $this->numImages++;
5436
- $im = $this->numImages;
5437
- $label = "I$im";
5438
- $this->numObj++;
5439
-
5440
- $this->o_image(
5441
- $this->numObj,
5442
- 'new',
5443
- array(
5444
- 'label' => $label,
5445
- 'data' => &$data,
5446
- 'iw' => $imageWidth,
5447
- 'ih' => $imageHeight,
5448
- 'channels' => $channels
5449
- )
5450
- );
5451
-
5452
- $this->imagelist[$imgname] = array(
5453
- 'label' => $label,
5454
- 'w' => $imageWidth,
5455
- 'h' => $imageHeight,
5456
- 'c' => $channels
5457
- );
5458
- }
5459
-
5460
- $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
5461
- }
5462
-
5463
- /**
5464
- * specify where the document should open when it first starts
5465
- *
5466
- * @param $style
5467
- * @param int $a
5468
- * @param int $b
5469
- * @param int $c
5470
- */
5471
- function openHere($style, $a = 0, $b = 0, $c = 0)
5472
- {
5473
- // this function will open the document at a specified page, in a specified style
5474
- // the values for style, and the required parameters are:
5475
- // 'XYZ' left, top, zoom
5476
- // 'Fit'
5477
- // 'FitH' top
5478
- // 'FitV' left
5479
- // 'FitR' left,bottom,right
5480
- // 'FitB'
5481
- // 'FitBH' top
5482
- // 'FitBV' left
5483
- $this->numObj++;
5484
- $this->o_destination(
5485
- $this->numObj,
5486
- 'new',
5487
- array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5488
- );
5489
- $id = $this->catalogId;
5490
- $this->o_catalog($id, 'openHere', $this->numObj);
5491
- }
5492
-
5493
- /**
5494
- * Add JavaScript code to the PDF document
5495
- *
5496
- * @param string $code
5497
- */
5498
- function addJavascript($code)
5499
- {
5500
- $this->javascript .= $code;
5501
- }
5502
-
5503
- /**
5504
- * create a labelled destination within the document
5505
- *
5506
- * @param $label
5507
- * @param $style
5508
- * @param int $a
5509
- * @param int $b
5510
- * @param int $c
5511
- */
5512
- function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
5513
- {
5514
- // associates the given label with the destination, it is done this way so that a destination can be specified after
5515
- // it has been linked to
5516
- // styles are the same as the 'openHere' function
5517
- $this->numObj++;
5518
- $this->o_destination(
5519
- $this->numObj,
5520
- 'new',
5521
- array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5522
- );
5523
- $id = $this->numObj;
5524
-
5525
- // store the label->idf relationship, note that this means that labels can be used only once
5526
- $this->destinations["$label"] = $id;
5527
- }
5528
-
5529
- /**
5530
- * define font families, this is used to initialize the font families for the default fonts
5531
- * and for the user to add new ones for their fonts. The default bahavious can be overridden should
5532
- * that be desired.
5533
- *
5534
- * @param $family
5535
- * @param string $options
5536
- */
5537
- function setFontFamily($family, $options = '')
5538
- {
5539
- if (!is_array($options)) {
5540
- if ($family === 'init') {
5541
- // set the known family groups
5542
- // these font families will be used to enable bold and italic markers to be included
5543
- // within text streams. html forms will be used... <b></b> <i></i>
5544
- $this->fontFamilies['Helvetica.afm'] =
5545
- array(
5546
- 'b' => 'Helvetica-Bold.afm',
5547
- 'i' => 'Helvetica-Oblique.afm',
5548
- 'bi' => 'Helvetica-BoldOblique.afm',
5549
- 'ib' => 'Helvetica-BoldOblique.afm'
5550
- );
5551
-
5552
- $this->fontFamilies['Courier.afm'] =
5553
- array(
5554
- 'b' => 'Courier-Bold.afm',
5555
- 'i' => 'Courier-Oblique.afm',
5556
- 'bi' => 'Courier-BoldOblique.afm',
5557
- 'ib' => 'Courier-BoldOblique.afm'
5558
- );
5559
-
5560
- $this->fontFamilies['Times-Roman.afm'] =
5561
- array(
5562
- 'b' => 'Times-Bold.afm',
5563
- 'i' => 'Times-Italic.afm',
5564
- 'bi' => 'Times-BoldItalic.afm',
5565
- 'ib' => 'Times-BoldItalic.afm'
5566
- );
5567
- }
5568
- } else {
5569
-
5570
- // the user is trying to set a font family
5571
- // note that this can also be used to set the base ones to something else
5572
- if (mb_strlen($family)) {
5573
- $this->fontFamilies[$family] = $options;
5574
- }
5575
- }
5576
- }
5577
-
5578
- /**
5579
- * used to add messages for use in debugging
5580
- *
5581
- * @param $message
5582
- */
5583
- function addMessage($message)
5584
- {
5585
- $this->messages .= $message . "\n";
5586
- }
5587
-
5588
- /**
5589
- * a few functions which should allow the document to be treated transactionally.
5590
- *
5591
- * @param $action
5592
- */
5593
- function transaction($action)
5594
- {
5595
- switch ($action) {
5596
- case 'start':
5597
- // store all the data away into the checkpoint variable
5598
- $data = get_object_vars($this);
5599
- $this->checkpoint = $data;
5600
- unset($data);
5601
- break;
5602
-
5603
- case 'commit':
5604
- if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
5605
- $tmp = $this->checkpoint['checkpoint'];
5606
- $this->checkpoint = $tmp;
5607
- unset($tmp);
5608
- } else {
5609
- $this->checkpoint = '';
5610
- }
5611
- break;
5612
-
5613
- case 'rewind':
5614
- // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
5615
- if (is_array($this->checkpoint)) {
5616
- // can only abort if were inside a checkpoint
5617
- $tmp = $this->checkpoint;
5618
-
5619
- foreach ($tmp as $k => $v) {
5620
- if ($k !== 'checkpoint') {
5621
- $this->$k = $v;
5622
- }
5623
- }
5624
- unset($tmp);
5625
- }
5626
- break;
5627
-
5628
- case 'abort':
5629
- if (is_array($this->checkpoint)) {
5630
- // can only abort if were inside a checkpoint
5631
- $tmp = $this->checkpoint;
5632
- foreach ($tmp as $k => $v) {
5633
- $this->$k = $v;
5634
- }
5635
- unset($tmp);
5636
- }
5637
- break;
5638
- }
5639
- }
5640
- }
1
+ <?php
2
+ /**
3
+ * A PHP class to provide the basic functionality to create a pdf document without
4
+ * any requirement for additional modules.
5
+ *
6
+ * Extended by Orion Richardson to support Unicode / UTF-8 characters using
7
+ * TCPDF and others as a guide.
8
+ *
9
+ * @author Wayne Munro <pdf@ros.co.nz>
10
+ * @author Orion Richardson <orionr@yahoo.com>
11
+ * @author Helmut Tischer <htischer@weihenstephan.org>
12
+ * @author Ryan H. Masten <ryan.masten@gmail.com>
13
+ * @author Brian Sweeney <eclecticgeek@gmail.com>
14
+ * @author Fabien Ménager <fabien.menager@gmail.com>
15
+ * @license Public Domain http://creativecommons.org/licenses/publicdomain/
16
+ * @package Cpdf
17
+ */
18
+ use FontLib\Font;
19
+ use FontLib\BinaryStream;
20
+
21
+ class Cpdf
22
+ {
23
+
24
+ /**
25
+ * @var integer The current number of pdf objects in the document
26
+ */
27
+ public $numObj = 0;
28
+
29
+ /**
30
+ * @var array This array contains all of the pdf objects, ready for final assembly
31
+ */
32
+ public $objects = array();
33
+
34
+ /**
35
+ * @var integer The objectId (number within the objects array) of the document catalog
36
+ */
37
+ public $catalogId;
38
+
39
+ /**
40
+ * @var array Array carrying information about the fonts that the system currently knows about
41
+ * Used to ensure that a font is not loaded twice, among other things
42
+ */
43
+ public $fonts = array();
44
+
45
+ /**
46
+ * @var string The default font metrics file to use if no other font has been loaded.
47
+ * The path to the directory containing the font metrics should be included
48
+ */
49
+ public $defaultFont = './fonts/Helvetica.afm';
50
+
51
+ /**
52
+ * @string A record of the current font
53
+ */
54
+ public $currentFont = '';
55
+
56
+ /**
57
+ * @var string The current base font
58
+ */
59
+ public $currentBaseFont = '';
60
+
61
+ /**
62
+ * @var integer The number of the current font within the font array
63
+ */
64
+ public $currentFontNum = 0;
65
+
66
+ /**
67
+ * @var integer
68
+ */
69
+ public $currentNode;
70
+
71
+ /**
72
+ * @var integer Object number of the current page
73
+ */
74
+ public $currentPage;
75
+
76
+ /**
77
+ * @var integer Object number of the currently active contents block
78
+ */
79
+ public $currentContents;
80
+
81
+ /**
82
+ * @var integer Number of fonts within the system
83
+ */
84
+ public $numFonts = 0;
85
+
86
+ /**
87
+ * @var integer Number of graphic state resources used
88
+ */
89
+ private $numStates = 0;
90
+
91
+ /**
92
+ * @var array Number of graphic state resources used
93
+ */
94
+ private $gstates = array();
95
+
96
+ /**
97
+ * @var array Current color for fill operations, defaults to inactive value,
98
+ * all three components should be between 0 and 1 inclusive when active
99
+ */
100
+ public $currentColor = null;
101
+
102
+ /**
103
+ * @var array Current color for stroke operations (lines etc.)
104
+ */
105
+ public $currentStrokeColor = null;
106
+
107
+ /**
108
+ * @var string Fill rule (nonzero or evenodd)
109
+ */
110
+ public $fillRule = "nonzero";
111
+
112
+ /**
113
+ * @var string Current style that lines are drawn in
114
+ */
115
+ public $currentLineStyle = '';
116
+
117
+ /**
118
+ * @var array Current line transparency (partial graphics state)
119
+ */
120
+ public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
121
+
122
+ /**
123
+ * array Current fill transparency (partial graphics state)
124
+ */
125
+ public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
126
+
127
+ /**
128
+ * @var array An array which is used to save the state of the document, mainly the colors and styles
129
+ * it is used to temporarily change to another state, then change back to what it was before
130
+ */
131
+ public $stateStack = array();
132
+
133
+ /**
134
+ * @var integer Number of elements within the state stack
135
+ */
136
+ public $nStateStack = 0;
137
+
138
+ /**
139
+ * @var integer Number of page objects within the document
140
+ */
141
+ public $numPages = 0;
142
+
143
+ /**
144
+ * @var array Object Id storage stack
145
+ */
146
+ public $stack = array();
147
+
148
+ /**
149
+ * @var integer Number of elements within the object Id storage stack
150
+ */
151
+ public $nStack = 0;
152
+
153
+ /**
154
+ * an array which contains information about the objects which are not firmly attached to pages
155
+ * these have been added with the addObject function
156
+ */
157
+ public $looseObjects = array();
158
+
159
+ /**
160
+ * array contains information about how the loose objects are to be added to the document
161
+ */
162
+ public $addLooseObjects = array();
163
+
164
+ /**
165
+ * @var integer The objectId of the information object for the document
166
+ * this contains authorship, title etc.
167
+ */
168
+ public $infoObject = 0;
169
+
170
+ /**
171
+ * @var integer Number of images being tracked within the document
172
+ */
173
+ public $numImages = 0;
174
+
175
+ /**
176
+ * @var array An array containing options about the document
177
+ * it defaults to turning on the compression of the objects
178
+ */
179
+ public $options = array('compression' => true);
180
+
181
+ /**
182
+ * @var integer The objectId of the first page of the document
183
+ */
184
+ public $firstPageId;
185
+
186
+ /**
187
+ * @var integer The object Id of the procset object
188
+ */
189
+ public $procsetObjectId;
190
+
191
+ /**
192
+ * @var array Store the information about the relationship between font families
193
+ * this used so that the code knows which font is the bold version of another font, etc.
194
+ * the value of this array is initialised in the constructor function.
195
+ */
196
+ public $fontFamilies = array();
197
+
198
+ /**
199
+ * @var string Folder for php serialized formats of font metrics files.
200
+ * If empty string, use same folder as original metrics files.
201
+ * This can be passed in from class creator.
202
+ * If this folder does not exist or is not writable, Cpdf will be **much** slower.
203
+ * Because of potential trouble with php safe mode, folder cannot be created at runtime.
204
+ */
205
+ public $fontcache = '';
206
+
207
+ /**
208
+ * @var integer The version of the font metrics cache file.
209
+ * This value must be manually incremented whenever the internal font data structure is modified.
210
+ */
211
+ public $fontcacheVersion = 6;
212
+
213
+ /**
214
+ * @var string Temporary folder.
215
+ * If empty string, will attempt system tmp folder.
216
+ * This can be passed in from class creator.
217
+ */
218
+ public $tmp = '';
219
+
220
+ /**
221
+ * @var string Track if the current font is bolded or italicised
222
+ */
223
+ public $currentTextState = '';
224
+
225
+ /**
226
+ * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
227
+ */
228
+ public $messages = '';
229
+
230
+ /**
231
+ * @var string The encryption array for the document encryption is stored here
232
+ */
233
+ public $arc4 = '';
234
+
235
+ /**
236
+ * @var integer The object Id of the encryption information
237
+ */
238
+ public $arc4_objnum = 0;
239
+
240
+ /**
241
+ * @var string The file identifier, used to uniquely identify a pdf document
242
+ */
243
+ public $fileIdentifier = '';
244
+
245
+ /**
246
+ * @var boolean A flag to say if a document is to be encrypted or not
247
+ */
248
+ public $encrypted = false;
249
+
250
+ /**
251
+ * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
252
+ */
253
+ public $encryptionKey = '';
254
+
255
+ /**
256
+ * @var array Array which forms a stack to keep track of nested callback functions
257
+ */
258
+ public $callback = array();
259
+
260
+ /**
261
+ * @var integer The number of callback functions in the callback array
262
+ */
263
+ public $nCallback = 0;
264
+
265
+ /**
266
+ * @var array Store label->id pairs for named destinations, these will be used to replace internal links
267
+ * done this way so that destinations can be defined after the location that links to them
268
+ */
269
+ public $destinations = array();
270
+
271
+ /**
272
+ * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
273
+ * publiciables within the class, so that the user can rollback at will (from each 'start' command)
274
+ * note that this includes the objects array, so these can be large.
275
+ */
276
+ public $checkpoint = '';
277
+
278
+ /**
279
+ * @var array Table of Image origin filenames and image labels which were already added with o_image().
280
+ * Allows to merge identical images
281
+ */
282
+ public $imagelist = array();
283
+
284
+ /**
285
+ * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
286
+ */
287
+ public $isUnicode = false;
288
+
289
+ /**
290
+ * @var string the JavaScript code of the document
291
+ */
292
+ public $javascript = '';
293
+
294
+ /**
295
+ * @var boolean whether the compression is possible
296
+ */
297
+ protected $compressionReady = false;
298
+
299
+ /**
300
+ * @var array Current page size
301
+ */
302
+ protected $currentPageSize = array("width" => 0, "height" => 0);
303
+
304
+ /**
305
+ * @var array All the chars that will be required in the font subsets
306
+ */
307
+ protected $stringSubsets = array();
308
+
309
+ /**
310
+ * @var string The target internal encoding
311
+ */
312
+ static protected $targetEncoding = 'Windows-1252';
313
+
314
+ /**
315
+ * @var array The list of the core fonts
316
+ */
317
+ static protected $coreFonts = array(
318
+ 'courier',
319
+ 'courier-bold',
320
+ 'courier-oblique',
321
+ 'courier-boldoblique',
322
+ 'helvetica',
323
+ 'helvetica-bold',
324
+ 'helvetica-oblique',
325
+ 'helvetica-boldoblique',
326
+ 'times-roman',
327
+ 'times-bold',
328
+ 'times-italic',
329
+ 'times-bolditalic',
330
+ 'symbol',
331
+ 'zapfdingbats'
332
+ );
333
+
334
+ /**
335
+ * Class constructor
336
+ * This will start a new document
337
+ *
338
+ * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
339
+ * @param boolean $isUnicode Whether text will be treated as Unicode or not.
340
+ * @param string $fontcache The font cache folder
341
+ * @param string $tmp The temporary folder
342
+ */
343
+ function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '')
344
+ {
345
+ $this->isUnicode = $isUnicode;
346
+ $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
347
+ $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
348
+ $this->newDocument($pageSize);
349
+
350
+ $this->compressionReady = function_exists('gzcompress');
351
+
352
+ if (in_array('Windows-1252', mb_list_encodings())) {
353
+ self::$targetEncoding = 'Windows-1252';
354
+ }
355
+
356
+ // also initialize the font families that are known about already
357
+ $this->setFontFamily('init');
358
+ }
359
+
360
+ /**
361
+ * Document object methods (internal use only)
362
+ *
363
+ * There is about one object method for each type of object in the pdf document
364
+ * Each function has the same call list ($id,$action,$options).
365
+ * $id = the object ID of the object, or what it is to be if it is being created
366
+ * $action = a string specifying the action to be performed, though ALL must support:
367
+ * 'new' - create the object with the id $id
368
+ * 'out' - produce the output for the pdf object
369
+ * $options = optional, a string or array containing the various parameters for the object
370
+ *
371
+ * These, in conjunction with the output function are the ONLY way for output to be produced
372
+ * within the pdf 'file'.
373
+ */
374
+
375
+ /**
376
+ * Destination object, used to specify the location for the user to jump to, presently on opening
377
+ *
378
+ * @param $id
379
+ * @param $action
380
+ * @param string $options
381
+ * @return string|null
382
+ */
383
+ protected function o_destination($id, $action, $options = '')
384
+ {
385
+ switch ($action) {
386
+ case 'new':
387
+ $this->objects[$id] = array('t' => 'destination', 'info' => array());
388
+ $tmp = '';
389
+ switch ($options['type']) {
390
+ case 'XYZ':
391
+ /** @noinspection PhpMissingBreakStatementInspection */
392
+ case 'FitR':
393
+ $tmp = ' ' . $options['p3'] . $tmp;
394
+ case 'FitH':
395
+ case 'FitV':
396
+ case 'FitBH':
397
+ /** @noinspection PhpMissingBreakStatementInspection */
398
+ case 'FitBV':
399
+ $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
400
+ case 'Fit':
401
+ case 'FitB':
402
+ $tmp = $options['type'] . $tmp;
403
+ $this->objects[$id]['info']['string'] = $tmp;
404
+ $this->objects[$id]['info']['page'] = $options['page'];
405
+ }
406
+ break;
407
+
408
+ case 'out':
409
+ $o = &$this->objects[$id];
410
+
411
+ $tmp = $o['info'];
412
+ $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
413
+
414
+ return $res;
415
+ }
416
+
417
+ return null;
418
+ }
419
+
420
+ /**
421
+ * set the viewer preferences
422
+ *
423
+ * @param $id
424
+ * @param $action
425
+ * @param string|array $options
426
+ * @return string|null
427
+ */
428
+ protected function o_viewerPreferences($id, $action, $options = '')
429
+ {
430
+ switch ($action) {
431
+ case 'new':
432
+ $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array());
433
+ break;
434
+
435
+ case 'add':
436
+ $o = &$this->objects[$id];
437
+
438
+ foreach ($options as $k => $v) {
439
+ switch ($k) {
440
+ // Boolean keys
441
+ case 'HideToolbar':
442
+ case 'HideMenubar':
443
+ case 'HideWindowUI':
444
+ case 'FitWindow':
445
+ case 'CenterWindow':
446
+ case 'DisplayDocTitle':
447
+ case 'PickTrayByPDFSize':
448
+ $o['info'][$k] = (bool)$v;
449
+ break;
450
+
451
+ // Integer keys
452
+ case 'NumCopies':
453
+ $o['info'][$k] = (int)$v;
454
+ break;
455
+
456
+ // Name keys
457
+ case 'ViewArea':
458
+ case 'ViewClip':
459
+ case 'PrintClip':
460
+ case 'PrintArea':
461
+ $o['info'][$k] = (string)$v;
462
+ break;
463
+
464
+ // Named with limited valid values
465
+ case 'NonFullScreenPageMode':
466
+ if (!in_array($v, array('UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'))) {
467
+ continue;
468
+ }
469
+ $o['info'][$k] = $v;
470
+ break;
471
+
472
+ case 'Direction':
473
+ if (!in_array($v, array('L2R', 'R2L'))) {
474
+ continue;
475
+ }
476
+ $o['info'][$k] = $v;
477
+ break;
478
+
479
+ case 'PrintScaling':
480
+ if (!in_array($v, array('None', 'AppDefault'))) {
481
+ continue;
482
+ }
483
+ $o['info'][$k] = $v;
484
+ break;
485
+
486
+ case 'Duplex':
487
+ if (!in_array($v, array('None', 'AppDefault'))) {
488
+ continue;
489
+ }
490
+ $o['info'][$k] = $v;
491
+ break;
492
+
493
+ // Integer array
494
+ case 'PrintPageRange':
495
+ // Cast to integer array
496
+ foreach ($v as $vK => $vV) {
497
+ $v[$vK] = (int)$vV;
498
+ }
499
+ $o['info'][$k] = array_values($v);
500
+ break;
501
+ }
502
+ }
503
+ break;
504
+
505
+ case 'out':
506
+ $o = &$this->objects[$id];
507
+ $res = "\n$id 0 obj\n<< ";
508
+
509
+ foreach ($o['info'] as $k => $v) {
510
+ if (is_string($v)) {
511
+ $v = '/' . $v;
512
+ } elseif (is_int($v)) {
513
+ $v = (string) $v;
514
+ } elseif (is_bool($v)) {
515
+ $v = ($v ? 'true' : 'false');
516
+ } elseif (is_array($v)) {
517
+ $v = '[' . implode(' ', $v) . ']';
518
+ }
519
+ $res .= "\n/$k $v";
520
+ }
521
+ $res .= "\n>>\n";
522
+
523
+ return $res;
524
+ }
525
+
526
+ return null;
527
+ }
528
+
529
+ /**
530
+ * define the document catalog, the overall controller for the document
531
+ *
532
+ * @param $id
533
+ * @param $action
534
+ * @param string|array $options
535
+ * @return string|null
536
+ */
537
+ protected function o_catalog($id, $action, $options = '')
538
+ {
539
+ if ($action !== 'new') {
540
+ $o = &$this->objects[$id];
541
+ }
542
+
543
+ switch ($action) {
544
+ case 'new':
545
+ $this->objects[$id] = array('t' => 'catalog', 'info' => array());
546
+ $this->catalogId = $id;
547
+ break;
548
+
549
+ case 'outlines':
550
+ case 'pages':
551
+ case 'openHere':
552
+ case 'javascript':
553
+ $o['info'][$action] = $options;
554
+ break;
555
+
556
+ case 'viewerPreferences':
557
+ if (!isset($o['info']['viewerPreferences'])) {
558
+ $this->numObj++;
559
+ $this->o_viewerPreferences($this->numObj, 'new');
560
+ $o['info']['viewerPreferences'] = $this->numObj;
561
+ }
562
+
563
+ $vp = $o['info']['viewerPreferences'];
564
+ $this->o_viewerPreferences($vp, 'add', $options);
565
+
566
+ break;
567
+
568
+ case 'out':
569
+ $res = "\n$id 0 obj\n<< /Type /Catalog";
570
+
571
+ foreach ($o['info'] as $k => $v) {
572
+ switch ($k) {
573
+ case 'outlines':
574
+ $res .= "\n/Outlines $v 0 R";
575
+ break;
576
+
577
+ case 'pages':
578
+ $res .= "\n/Pages $v 0 R";
579
+ break;
580
+
581
+ case 'viewerPreferences':
582
+ $res .= "\n/ViewerPreferences $v 0 R";
583
+ break;
584
+
585
+ case 'openHere':
586
+ $res .= "\n/OpenAction $v 0 R";
587
+ break;
588
+
589
+ case 'javascript':
590
+ $res .= "\n/Names <</JavaScript $v 0 R>>";
591
+ break;
592
+ }
593
+ }
594
+
595
+ $res .= " >>\nendobj";
596
+
597
+ return $res;
598
+ }
599
+
600
+ return null;
601
+ }
602
+
603
+ /**
604
+ * object which is a parent to the pages in the document
605
+ *
606
+ * @param $id
607
+ * @param $action
608
+ * @param string $options
609
+ * @return string|null
610
+ */
611
+ protected function o_pages($id, $action, $options = '')
612
+ {
613
+ if ($action !== 'new') {
614
+ $o = &$this->objects[$id];
615
+ }
616
+
617
+ switch ($action) {
618
+ case 'new':
619
+ $this->objects[$id] = array('t' => 'pages', 'info' => array());
620
+ $this->o_catalog($this->catalogId, 'pages', $id);
621
+ break;
622
+
623
+ case 'page':
624
+ if (!is_array($options)) {
625
+ // then it will just be the id of the new page
626
+ $o['info']['pages'][] = $options;
627
+ } else {
628
+ // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
629
+ // and pos is either 'before' or 'after', saying where this page will fit.
630
+ if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
631
+ $i = array_search($options['rid'], $o['info']['pages']);
632
+ if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
633
+
634
+ // then there is a match
635
+ // make a space
636
+ switch ($options['pos']) {
637
+ case 'before':
638
+ $k = $i;
639
+ break;
640
+
641
+ case 'after':
642
+ $k = $i + 1;
643
+ break;
644
+
645
+ default:
646
+ $k = -1;
647
+ break;
648
+ }
649
+
650
+ if ($k >= 0) {
651
+ for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
652
+ $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
653
+ }
654
+
655
+ $o['info']['pages'][$k] = $options['id'];
656
+ }
657
+ }
658
+ }
659
+ }
660
+ break;
661
+
662
+ case 'procset':
663
+ $o['info']['procset'] = $options;
664
+ break;
665
+
666
+ case 'mediaBox':
667
+ $o['info']['mediaBox'] = $options;
668
+ // which should be an array of 4 numbers
669
+ $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]);
670
+ break;
671
+
672
+ case 'font':
673
+ $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']);
674
+ break;
675
+
676
+ case 'extGState':
677
+ $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
678
+ break;
679
+
680
+ case 'xObject':
681
+ $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']);
682
+ break;
683
+
684
+ case 'out':
685
+ if (count($o['info']['pages'])) {
686
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
687
+ foreach ($o['info']['pages'] as $v) {
688
+ $res .= "$v 0 R\n";
689
+ }
690
+
691
+ $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
692
+
693
+ if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
694
+ isset($o['info']['procset']) ||
695
+ (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
696
+ ) {
697
+ $res .= "\n/Resources <<";
698
+
699
+ if (isset($o['info']['procset'])) {
700
+ $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
701
+ }
702
+
703
+ if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
704
+ $res .= "\n/Font << ";
705
+ foreach ($o['info']['fonts'] as $finfo) {
706
+ $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
707
+ }
708
+ $res .= "\n>>";
709
+ }
710
+
711
+ if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
712
+ $res .= "\n/XObject << ";
713
+ foreach ($o['info']['xObjects'] as $finfo) {
714
+ $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
715
+ }
716
+ $res .= "\n>>";
717
+ }
718
+
719
+ if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
720
+ $res .= "\n/ExtGState << ";
721
+ foreach ($o['info']['extGStates'] as $gstate) {
722
+ $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
723
+ }
724
+ $res .= "\n>>";
725
+ }
726
+
727
+ $res .= "\n>>";
728
+ if (isset($o['info']['mediaBox'])) {
729
+ $tmp = $o['info']['mediaBox'];
730
+ $res .= "\n/MediaBox [" . sprintf(
731
+ '%.3F %.3F %.3F %.3F',
732
+ $tmp[0],
733
+ $tmp[1],
734
+ $tmp[2],
735
+ $tmp[3]
736
+ ) . ']';
737
+ }
738
+ }
739
+
740
+ $res .= "\n >>\nendobj";
741
+ } else {
742
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
743
+ }
744
+
745
+ return $res;
746
+ }
747
+
748
+ return null;
749
+ }
750
+
751
+ /**
752
+ * define the outlines in the doc, empty for now
753
+ *
754
+ * @param $id
755
+ * @param $action
756
+ * @param string $options
757
+ * @return string|null
758
+ */
759
+ protected function o_outlines($id, $action, $options = '')
760
+ {
761
+ if ($action !== 'new') {
762
+ $o = &$this->objects[$id];
763
+ }
764
+
765
+ switch ($action) {
766
+ case 'new':
767
+ $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array()));
768
+ $this->o_catalog($this->catalogId, 'outlines', $id);
769
+ break;
770
+
771
+ case 'outline':
772
+ $o['info']['outlines'][] = $options;
773
+ break;
774
+
775
+ case 'out':
776
+ if (count($o['info']['outlines'])) {
777
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
778
+ foreach ($o['info']['outlines'] as $v) {
779
+ $res .= "$v 0 R ";
780
+ }
781
+
782
+ $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
783
+ } else {
784
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
785
+ }
786
+
787
+ return $res;
788
+ }
789
+
790
+ return null;
791
+ }
792
+
793
+ /**
794
+ * an object to hold the font description
795
+ *
796
+ * @param $id
797
+ * @param $action
798
+ * @param string|array $options
799
+ * @return string|null
800
+ */
801
+ protected function o_font($id, $action, $options = '')
802
+ {
803
+ if ($action !== 'new') {
804
+ $o = &$this->objects[$id];
805
+ }
806
+
807
+ switch ($action) {
808
+ case 'new':
809
+ $this->objects[$id] = array(
810
+ 't' => 'font',
811
+ 'info' => array(
812
+ 'name' => $options['name'],
813
+ 'fontFileName' => $options['fontFileName'],
814
+ 'SubType' => 'Type1'
815
+ )
816
+ );
817
+ $fontNum = $this->numFonts;
818
+ $this->objects[$id]['info']['fontNum'] = $fontNum;
819
+
820
+ // deal with the encoding and the differences
821
+ if (isset($options['differences'])) {
822
+ // then we'll need an encoding dictionary
823
+ $this->numObj++;
824
+ $this->o_fontEncoding($this->numObj, 'new', $options);
825
+ $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
826
+ } else {
827
+ if (isset($options['encoding'])) {
828
+ // we can specify encoding here
829
+ switch ($options['encoding']) {
830
+ case 'WinAnsiEncoding':
831
+ case 'MacRomanEncoding':
832
+ case 'MacExpertEncoding':
833
+ $this->objects[$id]['info']['encoding'] = $options['encoding'];
834
+ break;
835
+
836
+ case 'none':
837
+ break;
838
+
839
+ default:
840
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
841
+ break;
842
+ }
843
+ } else {
844
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
845
+ }
846
+ }
847
+
848
+ if ($this->fonts[$options['fontFileName']]['isUnicode']) {
849
+ // For Unicode fonts, we need to incorporate font data into
850
+ // sub-sections that are linked from the primary font section.
851
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
852
+ // for more information.
853
+ //
854
+ // All of this code is adapted from the excellent changes made to
855
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
856
+
857
+ $toUnicodeId = ++$this->numObj;
858
+ $this->o_toUnicode($toUnicodeId, 'new');
859
+ $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
860
+
861
+ $cidFontId = ++$this->numObj;
862
+ $this->o_fontDescendentCID($cidFontId, 'new', $options);
863
+ $this->objects[$id]['info']['cidFont'] = $cidFontId;
864
+ }
865
+
866
+ // also tell the pages node about the new font
867
+ $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
868
+ break;
869
+
870
+ case 'add':
871
+ foreach ($options as $k => $v) {
872
+ switch ($k) {
873
+ case 'BaseFont':
874
+ $o['info']['name'] = $v;
875
+ break;
876
+ case 'FirstChar':
877
+ case 'LastChar':
878
+ case 'Widths':
879
+ case 'FontDescriptor':
880
+ case 'SubType':
881
+ $this->addMessage('o_font ' . $k . " : " . $v);
882
+ $o['info'][$k] = $v;
883
+ break;
884
+ }
885
+ }
886
+
887
+ // pass values down to descendent font
888
+ if (isset($o['info']['cidFont'])) {
889
+ $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
890
+ }
891
+ break;
892
+
893
+ case 'out':
894
+ if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
895
+ // For Unicode fonts, we need to incorporate font data into
896
+ // sub-sections that are linked from the primary font section.
897
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
898
+ // for more information.
899
+ //
900
+ // All of this code is adapted from the excellent changes made to
901
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
902
+
903
+ $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
904
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
905
+
906
+ // The horizontal identity mapping for 2-byte CIDs; may be used
907
+ // with CIDFonts using any Registry, Ordering, and Supplement values.
908
+ $res .= "/Encoding /Identity-H\n";
909
+ $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
910
+ $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
911
+ $res .= ">>\n";
912
+ $res .= "endobj";
913
+ } else {
914
+ $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
915
+ $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
916
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
917
+
918
+ if (isset($o['info']['encodingDictionary'])) {
919
+ // then place a reference to the dictionary
920
+ $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
921
+ } else {
922
+ if (isset($o['info']['encoding'])) {
923
+ // use the specified encoding
924
+ $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
925
+ }
926
+ }
927
+
928
+ if (isset($o['info']['FirstChar'])) {
929
+ $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
930
+ }
931
+
932
+ if (isset($o['info']['LastChar'])) {
933
+ $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
934
+ }
935
+
936
+ if (isset($o['info']['Widths'])) {
937
+ $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
938
+ }
939
+
940
+ if (isset($o['info']['FontDescriptor'])) {
941
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
942
+ }
943
+
944
+ $res .= ">>\n";
945
+ $res .= "endobj";
946
+ }
947
+
948
+ return $res;
949
+ }
950
+
951
+ return null;
952
+ }
953
+
954
+ /**
955
+ * A toUnicode section, needed for unicode fonts
956
+ *
957
+ * @param $id
958
+ * @param $action
959
+ * @return null|string
960
+ */
961
+ protected function o_toUnicode($id, $action)
962
+ {
963
+ switch ($action) {
964
+ case 'new':
965
+ $this->objects[$id] = array(
966
+ 't' => 'toUnicode'
967
+ );
968
+ break;
969
+ case 'add':
970
+ break;
971
+ case 'out':
972
+ $ordering = '(UCS)';
973
+ $registry = '(Adobe)';
974
+
975
+ if ($this->encrypted) {
976
+ $this->encryptInit($id);
977
+ $ordering = $this->ARC4($ordering);
978
+ $registry = $this->ARC4($registry);
979
+ }
980
+
981
+ $stream = <<<EOT
982
+ /CIDInit /ProcSet findresource begin
983
+ 12 dict begin
984
+ begincmap
985
+ /CIDSystemInfo
986
+ <</Registry $registry
987
+ /Ordering $ordering
988
+ /Supplement 0
989
+ >> def
990
+ /CMapName /Adobe-Identity-UCS def
991
+ /CMapType 2 def
992
+ 1 begincodespacerange
993
+ <0000> <FFFF>
994
+ endcodespacerange
995
+ 1 beginbfrange
996
+ <0000> <FFFF> <0000>
997
+ endbfrange
998
+ endcmap
999
+ CMapName currentdict /CMap defineresource pop
1000
+ end
1001
+ end
1002
+ EOT;
1003
+
1004
+ $res = "\n$id 0 obj\n";
1005
+ $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1006
+ $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1007
+
1008
+ return $res;
1009
+ }
1010
+
1011
+ return null;
1012
+ }
1013
+
1014
+ /**
1015
+ * a font descriptor, needed for including additional fonts
1016
+ *
1017
+ * @param $id
1018
+ * @param $action
1019
+ * @param string $options
1020
+ * @return null|string
1021
+ */
1022
+ protected function o_fontDescriptor($id, $action, $options = '')
1023
+ {
1024
+ if ($action !== 'new') {
1025
+ $o = &$this->objects[$id];
1026
+ }
1027
+
1028
+ switch ($action) {
1029
+ case 'new':
1030
+ $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options);
1031
+ break;
1032
+
1033
+ case 'out':
1034
+ $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1035
+ foreach ($o['info'] as $label => $value) {
1036
+ switch ($label) {
1037
+ case 'Ascent':
1038
+ case 'CapHeight':
1039
+ case 'Descent':
1040
+ case 'Flags':
1041
+ case 'ItalicAngle':
1042
+ case 'StemV':
1043
+ case 'AvgWidth':
1044
+ case 'Leading':
1045
+ case 'MaxWidth':
1046
+ case 'MissingWidth':
1047
+ case 'StemH':
1048
+ case 'XHeight':
1049
+ case 'CharSet':
1050
+ if (mb_strlen($value, '8bit')) {
1051
+ $res .= "/$label $value\n";
1052
+ }
1053
+
1054
+ break;
1055
+ case 'FontFile':
1056
+ case 'FontFile2':
1057
+ case 'FontFile3':
1058
+ $res .= "/$label $value 0 R\n";
1059
+ break;
1060
+
1061
+ case 'FontBBox':
1062
+ $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1063
+ break;
1064
+
1065
+ case 'FontName':
1066
+ $res .= "/$label /$value\n";
1067
+ break;
1068
+ }
1069
+ }
1070
+
1071
+ $res .= ">>\nendobj";
1072
+
1073
+ return $res;
1074
+ }
1075
+
1076
+ return null;
1077
+ }
1078
+
1079
+ /**
1080
+ * the font encoding
1081
+ *
1082
+ * @param $id
1083
+ * @param $action
1084
+ * @param string $options
1085
+ * @return null|string
1086
+ */
1087
+ protected function o_fontEncoding($id, $action, $options = '')
1088
+ {
1089
+ if ($action !== 'new') {
1090
+ $o = &$this->objects[$id];
1091
+ }
1092
+
1093
+ switch ($action) {
1094
+ case 'new':
1095
+ // the options array should contain 'differences' and maybe 'encoding'
1096
+ $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options);
1097
+ break;
1098
+
1099
+ case 'out':
1100
+ $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1101
+ if (!isset($o['info']['encoding'])) {
1102
+ $o['info']['encoding'] = 'WinAnsiEncoding';
1103
+ }
1104
+
1105
+ if ($o['info']['encoding'] !== 'none') {
1106
+ $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1107
+ }
1108
+
1109
+ $res .= "/Differences \n[";
1110
+
1111
+ $onum = -100;
1112
+
1113
+ foreach ($o['info']['differences'] as $num => $label) {
1114
+ if ($num != $onum + 1) {
1115
+ // we cannot make use of consecutive numbering
1116
+ $res .= "\n$num /$label";
1117
+ } else {
1118
+ $res .= " /$label";
1119
+ }
1120
+
1121
+ $onum = $num;
1122
+ }
1123
+
1124
+ $res .= "\n]\n>>\nendobj";
1125
+
1126
+ return $res;
1127
+ }
1128
+
1129
+ return null;
1130
+ }
1131
+
1132
+ /**
1133
+ * a descendent cid font, needed for unicode fonts
1134
+ *
1135
+ * @param $id
1136
+ * @param $action
1137
+ * @param string|array $options
1138
+ * @return null|string
1139
+ */
1140
+ protected function o_fontDescendentCID($id, $action, $options = '')
1141
+ {
1142
+ if ($action !== 'new') {
1143
+ $o = &$this->objects[$id];
1144
+ }
1145
+
1146
+ switch ($action) {
1147
+ case 'new':
1148
+ $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
1149
+
1150
+ // we need a CID system info section
1151
+ $cidSystemInfoId = ++$this->numObj;
1152
+ $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1153
+ $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1154
+
1155
+ // and a CID to GID map
1156
+ $cidToGidMapId = ++$this->numObj;
1157
+ $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1158
+ $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1159
+ break;
1160
+
1161
+ case 'add':
1162
+ foreach ($options as $k => $v) {
1163
+ switch ($k) {
1164
+ case 'BaseFont':
1165
+ $o['info']['name'] = $v;
1166
+ break;
1167
+
1168
+ case 'FirstChar':
1169
+ case 'LastChar':
1170
+ case 'MissingWidth':
1171
+ case 'FontDescriptor':
1172
+ case 'SubType':
1173
+ $this->addMessage("o_fontDescendentCID $k : $v");
1174
+ $o['info'][$k] = $v;
1175
+ break;
1176
+ }
1177
+ }
1178
+
1179
+ // pass values down to cid to gid map
1180
+ $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1181
+ break;
1182
+
1183
+ case 'out':
1184
+ $res = "\n$id 0 obj\n";
1185
+ $res .= "<</Type /Font\n";
1186
+ $res .= "/Subtype /CIDFontType2\n";
1187
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1188
+ $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1189
+ // if (isset($o['info']['FirstChar'])) {
1190
+ // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1191
+ // }
1192
+
1193
+ // if (isset($o['info']['LastChar'])) {
1194
+ // $res.= "/LastChar ".$o['info']['LastChar']."\n";
1195
+ // }
1196
+ if (isset($o['info']['FontDescriptor'])) {
1197
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1198
+ }
1199
+
1200
+ if (isset($o['info']['MissingWidth'])) {
1201
+ $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1202
+ }
1203
+
1204
+ if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1205
+ $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1206
+ $w = '';
1207
+ foreach ($cid_widths as $cid => $width) {
1208
+ $w .= "$cid [$width] ";
1209
+ }
1210
+ $res .= "/W [$w]\n";
1211
+ }
1212
+
1213
+ $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1214
+ $res .= ">>\n";
1215
+ $res .= "endobj";
1216
+
1217
+ return $res;
1218
+ }
1219
+
1220
+ return null;
1221
+ }
1222
+
1223
+ /**
1224
+ * CID system info section, needed for unicode fonts
1225
+ *
1226
+ * @param $id
1227
+ * @param $action
1228
+ * @return null|string
1229
+ */
1230
+ protected function o_cidSystemInfo($id, $action)
1231
+ {
1232
+ switch ($action) {
1233
+ case 'new':
1234
+ $this->objects[$id] = array(
1235
+ 't' => 'cidSystemInfo'
1236
+ );
1237
+ break;
1238
+ case 'add':
1239
+ break;
1240
+ case 'out':
1241
+ $ordering = '(UCS)';
1242
+ $registry = '(Adobe)';
1243
+
1244
+ if ($this->encrypted) {
1245
+ $this->encryptInit($id);
1246
+ $ordering = $this->ARC4($ordering);
1247
+ $registry = $this->ARC4($registry);
1248
+ }
1249
+
1250
+
1251
+ $res = "\n$id 0 obj\n";
1252
+
1253
+ $res .= '<</Registry ' . $registry . "\n"; // A string identifying an issuer of character collections
1254
+ $res .= '/Ordering ' . $ordering . "\n"; // A string that uniquely names a character collection issued by a specific registry
1255
+ $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1256
+ $res .= ">>";
1257
+
1258
+ $res .= "\nendobj";;
1259
+
1260
+ return $res;
1261
+ }
1262
+
1263
+ return null;
1264
+ }
1265
+
1266
+ /**
1267
+ * a font glyph to character map, needed for unicode fonts
1268
+ *
1269
+ * @param $id
1270
+ * @param $action
1271
+ * @param string $options
1272
+ * @return null|string
1273
+ */
1274
+ protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1275
+ {
1276
+ if ($action !== 'new') {
1277
+ $o = &$this->objects[$id];
1278
+ }
1279
+
1280
+ switch ($action) {
1281
+ case 'new':
1282
+ $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
1283
+ break;
1284
+
1285
+ case 'out':
1286
+ $res = "\n$id 0 obj\n";
1287
+ $fontFileName = $o['info']['fontFileName'];
1288
+ $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1289
+
1290
+ $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1291
+ $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1292
+
1293
+ if (!$compressed && isset($o['raw'])) {
1294
+ $res .= $tmp;
1295
+ } else {
1296
+ $res .= "<<";
1297
+
1298
+ if (!$compressed && $this->compressionReady && $this->options['compression']) {
1299
+ // then implement ZLIB based compression on this content stream
1300
+ $compressed = true;
1301
+ $tmp = gzcompress($tmp, 6);
1302
+ }
1303
+ if ($compressed) {
1304
+ $res .= "\n/Filter /FlateDecode";
1305
+ }
1306
+
1307
+ if ($this->encrypted) {
1308
+ $this->encryptInit($id);
1309
+ $tmp = $this->ARC4($tmp);
1310
+ }
1311
+
1312
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1313
+ }
1314
+
1315
+ $res .= "\nendobj";
1316
+
1317
+ return $res;
1318
+ }
1319
+
1320
+ return null;
1321
+ }
1322
+
1323
+ /**
1324
+ * the document procset, solves some problems with printing to old PS printers
1325
+ *
1326
+ * @param $id
1327
+ * @param $action
1328
+ * @param string $options
1329
+ * @return null|string
1330
+ */
1331
+ protected function o_procset($id, $action, $options = '')
1332
+ {
1333
+ if ($action !== 'new') {
1334
+ $o = &$this->objects[$id];
1335
+ }
1336
+
1337
+ switch ($action) {
1338
+ case 'new':
1339
+ $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1));
1340
+ $this->o_pages($this->currentNode, 'procset', $id);
1341
+ $this->procsetObjectId = $id;
1342
+ break;
1343
+
1344
+ case 'add':
1345
+ // this is to add new items to the procset list, despite the fact that this is considered
1346
+ // obsolete, the items are required for printing to some postscript printers
1347
+ switch ($options) {
1348
+ case 'ImageB':
1349
+ case 'ImageC':
1350
+ case 'ImageI':
1351
+ $o['info'][$options] = 1;
1352
+ break;
1353
+ }
1354
+ break;
1355
+
1356
+ case 'out':
1357
+ $res = "\n$id 0 obj\n[";
1358
+ foreach ($o['info'] as $label => $val) {
1359
+ $res .= "/$label ";
1360
+ }
1361
+ $res .= "]\nendobj";
1362
+
1363
+ return $res;
1364
+ }
1365
+
1366
+ return null;
1367
+ }
1368
+
1369
+ /**
1370
+ * define the document information
1371
+ *
1372
+ * @param $id
1373
+ * @param $action
1374
+ * @param string $options
1375
+ * @return null|string
1376
+ */
1377
+ protected function o_info($id, $action, $options = '')
1378
+ {
1379
+ switch ($action) {
1380
+ case 'new':
1381
+ $this->infoObject = $id;
1382
+ $date = 'D:' . @date('Ymd');
1383
+ $this->objects[$id] = array(
1384
+ 't' => 'info',
1385
+ 'info' => array(
1386
+ 'Producer' => 'CPDF (dompdf)',
1387
+ 'CreationDate' => $date
1388
+ )
1389
+ );
1390
+ break;
1391
+ case 'Title':
1392
+ case 'Author':
1393
+ case 'Subject':
1394
+ case 'Keywords':
1395
+ case 'Creator':
1396
+ case 'Producer':
1397
+ case 'CreationDate':
1398
+ case 'ModDate':
1399
+ case 'Trapped':
1400
+ $this->objects[$id]['info'][$action] = $options;
1401
+ break;
1402
+
1403
+ case 'out':
1404
+ $encrypted = $this->encrypted;
1405
+ if ($encrypted) {
1406
+ $this->encryptInit($id);
1407
+ }
1408
+
1409
+ $res = "\n$id 0 obj\n<<\n";
1410
+ $o = &$this->objects[$id];
1411
+ foreach ($o['info'] as $k => $v) {
1412
+ $res .= "/$k (";
1413
+
1414
+ // dates must be outputted as-is, without Unicode transformations
1415
+ if ($k !== 'CreationDate' && $k !== 'ModDate') {
1416
+ $v = $this->filterText($v, true, false);
1417
+ }
1418
+
1419
+ if ($encrypted) {
1420
+ $v = $this->ARC4($v);
1421
+ }
1422
+
1423
+ $res .= $v;
1424
+ $res .= ")\n";
1425
+ }
1426
+
1427
+ $res .= ">>\nendobj";
1428
+
1429
+ return $res;
1430
+ }
1431
+
1432
+ return null;
1433
+ }
1434
+
1435
+ /**
1436
+ * an action object, used to link to URLS initially
1437
+ *
1438
+ * @param $id
1439
+ * @param $action
1440
+ * @param string $options
1441
+ * @return null|string
1442
+ */
1443
+ protected function o_action($id, $action, $options = '')
1444
+ {
1445
+ if ($action !== 'new') {
1446
+ $o = &$this->objects[$id];
1447
+ }
1448
+
1449
+ switch ($action) {
1450
+ case 'new':
1451
+ if (is_array($options)) {
1452
+ $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']);
1453
+ } else {
1454
+ // then assume a URI action
1455
+ $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI');
1456
+ }
1457
+ break;
1458
+
1459
+ case 'out':
1460
+ if ($this->encrypted) {
1461
+ $this->encryptInit($id);
1462
+ }
1463
+
1464
+ $res = "\n$id 0 obj\n<< /Type /Action";
1465
+ switch ($o['type']) {
1466
+ case 'ilink':
1467
+ if (!isset($this->destinations[(string)$o['info']['label']])) {
1468
+ break;
1469
+ }
1470
+
1471
+ // there will be an 'label' setting, this is the name of the destination
1472
+ $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1473
+ break;
1474
+
1475
+ case 'URI':
1476
+ $res .= "\n/S /URI\n/URI (";
1477
+ if ($this->encrypted) {
1478
+ $res .= $this->filterText($this->ARC4($o['info']), false, false);
1479
+ } else {
1480
+ $res .= $this->filterText($o['info'], false, false);
1481
+ }
1482
+
1483
+ $res .= ")";
1484
+ break;
1485
+ }
1486
+
1487
+ $res .= "\n>>\nendobj";
1488
+
1489
+ return $res;
1490
+ }
1491
+
1492
+ return null;
1493
+ }
1494
+
1495
+ /**
1496
+ * an annotation object, this will add an annotation to the current page.
1497
+ * initially will support just link annotations
1498
+ *
1499
+ * @param $id
1500
+ * @param $action
1501
+ * @param string $options
1502
+ * @return null|string
1503
+ */
1504
+ protected function o_annotation($id, $action, $options = '')
1505
+ {
1506
+ if ($action !== 'new') {
1507
+ $o = &$this->objects[$id];
1508
+ }
1509
+
1510
+ switch ($action) {
1511
+ case 'new':
1512
+ // add the annotation to the current page
1513
+ $pageId = $this->currentPage;
1514
+ $this->o_page($pageId, 'annot', $id);
1515
+
1516
+ // and add the action object which is going to be required
1517
+ switch ($options['type']) {
1518
+ case 'link':
1519
+ $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1520
+ $this->numObj++;
1521
+ $this->o_action($this->numObj, 'new', $options['url']);
1522
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
1523
+ break;
1524
+
1525
+ case 'ilink':
1526
+ // this is to a named internal link
1527
+ $label = $options['label'];
1528
+ $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
1529
+ $this->numObj++;
1530
+ $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label));
1531
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
1532
+ break;
1533
+ }
1534
+ break;
1535
+
1536
+ case 'out':
1537
+ $res = "\n$id 0 obj\n<< /Type /Annot";
1538
+ switch ($o['info']['type']) {
1539
+ case 'link':
1540
+ case 'ilink':
1541
+ $res .= "\n/Subtype /Link";
1542
+ break;
1543
+ }
1544
+ $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1545
+ $res .= "\n/Border [0 0 0]";
1546
+ $res .= "\n/H /I";
1547
+ $res .= "\n/Rect [ ";
1548
+
1549
+ foreach ($o['info']['rect'] as $v) {
1550
+ $res .= sprintf("%.4F ", $v);
1551
+ }
1552
+
1553
+ $res .= "]";
1554
+ $res .= "\n>>\nendobj";
1555
+
1556
+ return $res;
1557
+ }
1558
+
1559
+ return null;
1560
+ }
1561
+
1562
+ /**
1563
+ * a page object, it also creates a contents object to hold its contents
1564
+ *
1565
+ * @param $id
1566
+ * @param $action
1567
+ * @param string $options
1568
+ * @return null|string
1569
+ */
1570
+ protected function o_page($id, $action, $options = '')
1571
+ {
1572
+ if ($action !== 'new') {
1573
+ $o = &$this->objects[$id];
1574
+ }
1575
+
1576
+ switch ($action) {
1577
+ case 'new':
1578
+ $this->numPages++;
1579
+ $this->objects[$id] = array(
1580
+ 't' => 'page',
1581
+ 'info' => array(
1582
+ 'parent' => $this->currentNode,
1583
+ 'pageNum' => $this->numPages,
1584
+ 'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1585
+ )
1586
+ );
1587
+
1588
+ if (is_array($options)) {
1589
+ // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1590
+ $options['id'] = $id;
1591
+ $this->o_pages($this->currentNode, 'page', $options);
1592
+ } else {
1593
+ $this->o_pages($this->currentNode, 'page', $id);
1594
+ }
1595
+
1596
+ $this->currentPage = $id;
1597
+ //make a contents object to go with this page
1598
+ $this->numObj++;
1599
+ $this->o_contents($this->numObj, 'new', $id);
1600
+ $this->currentContents = $this->numObj;
1601
+ $this->objects[$id]['info']['contents'] = array();
1602
+ $this->objects[$id]['info']['contents'][] = $this->numObj;
1603
+
1604
+ $match = ($this->numPages % 2 ? 'odd' : 'even');
1605
+ foreach ($this->addLooseObjects as $oId => $target) {
1606
+ if ($target === 'all' || $match === $target) {
1607
+ $this->objects[$id]['info']['contents'][] = $oId;
1608
+ }
1609
+ }
1610
+ break;
1611
+
1612
+ case 'content':
1613
+ $o['info']['contents'][] = $options;
1614
+ break;
1615
+
1616
+ case 'annot':
1617
+ // add an annotation to this page
1618
+ if (!isset($o['info']['annot'])) {
1619
+ $o['info']['annot'] = array();
1620
+ }
1621
+
1622
+ // $options should contain the id of the annotation dictionary
1623
+ $o['info']['annot'][] = $options;
1624
+ break;
1625
+
1626
+ case 'out':
1627
+ $res = "\n$id 0 obj\n<< /Type /Page";
1628
+ if (isset($o['info']['mediaBox'])) {
1629
+ $tmp = $o['info']['mediaBox'];
1630
+ $res .= "\n/MediaBox [" . sprintf(
1631
+ '%.3F %.3F %.3F %.3F',
1632
+ $tmp[0],
1633
+ $tmp[1],
1634
+ $tmp[2],
1635
+ $tmp[3]
1636
+ ) . ']';
1637
+ }
1638
+ $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1639
+
1640
+ if (isset($o['info']['annot'])) {
1641
+ $res .= "\n/Annots [";
1642
+ foreach ($o['info']['annot'] as $aId) {
1643
+ $res .= " $aId 0 R";
1644
+ }
1645
+ $res .= " ]";
1646
+ }
1647
+
1648
+ $count = count($o['info']['contents']);
1649
+ if ($count == 1) {
1650
+ $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1651
+ } else {
1652
+ if ($count > 1) {
1653
+ $res .= "\n/Contents [\n";
1654
+
1655
+ // reverse the page contents so added objects are below normal content
1656
+ //foreach (array_reverse($o['info']['contents']) as $cId) {
1657
+ // Back to normal now that I've got transparency working --Benj
1658
+ foreach ($o['info']['contents'] as $cId) {
1659
+ $res .= "$cId 0 R\n";
1660
+ }
1661
+ $res .= "]";
1662
+ }
1663
+ }
1664
+
1665
+ $res .= "\n>>\nendobj";
1666
+
1667
+ return $res;
1668
+ }
1669
+
1670
+ return null;
1671
+ }
1672
+
1673
+ /**
1674
+ * the contents objects hold all of the content which appears on pages
1675
+ *
1676
+ * @param $id
1677
+ * @param $action
1678
+ * @param string|array $options
1679
+ * @return null|string
1680
+ */
1681
+ protected function o_contents($id, $action, $options = '')
1682
+ {
1683
+ if ($action !== 'new') {
1684
+ $o = &$this->objects[$id];
1685
+ }
1686
+
1687
+ switch ($action) {
1688
+ case 'new':
1689
+ $this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array());
1690
+ if (mb_strlen($options, '8bit') && intval($options)) {
1691
+ // then this contents is the primary for a page
1692
+ $this->objects[$id]['onPage'] = $options;
1693
+ } else {
1694
+ if ($options === 'raw') {
1695
+ // then this page contains some other type of system object
1696
+ $this->objects[$id]['raw'] = 1;
1697
+ }
1698
+ }
1699
+ break;
1700
+
1701
+ case 'add':
1702
+ // add more options to the declaration
1703
+ foreach ($options as $k => $v) {
1704
+ $o['info'][$k] = $v;
1705
+ }
1706
+
1707
+ case 'out':
1708
+ $tmp = $o['c'];
1709
+ $res = "\n$id 0 obj\n";
1710
+
1711
+ if (isset($this->objects[$id]['raw'])) {
1712
+ $res .= $tmp;
1713
+ } else {
1714
+ $res .= "<<";
1715
+ if ($this->compressionReady && $this->options['compression']) {
1716
+ // then implement ZLIB based compression on this content stream
1717
+ $res .= " /Filter /FlateDecode";
1718
+ $tmp = gzcompress($tmp, 6);
1719
+ }
1720
+
1721
+ if ($this->encrypted) {
1722
+ $this->encryptInit($id);
1723
+ $tmp = $this->ARC4($tmp);
1724
+ }
1725
+
1726
+ foreach ($o['info'] as $k => $v) {
1727
+ $res .= "\n/$k $v";
1728
+ }
1729
+
1730
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
1731
+ }
1732
+
1733
+ $res .= "\nendobj";
1734
+
1735
+ return $res;
1736
+ }
1737
+
1738
+ return null;
1739
+ }
1740
+
1741
+ /**
1742
+ * @param $id
1743
+ * @param $action
1744
+ * @return string|null
1745
+ */
1746
+ protected function o_embedjs($id, $action)
1747
+ {
1748
+ switch ($action) {
1749
+ case 'new':
1750
+ $this->objects[$id] = array(
1751
+ 't' => 'embedjs',
1752
+ 'info' => array(
1753
+ 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
1754
+ )
1755
+ );
1756
+ break;
1757
+
1758
+ case 'out':
1759
+ $o = &$this->objects[$id];
1760
+ $res = "\n$id 0 obj\n<< ";
1761
+ foreach ($o['info'] as $k => $v) {
1762
+ $res .= "\n/$k $v";
1763
+ }
1764
+ $res .= "\n>>\nendobj";
1765
+
1766
+ return $res;
1767
+ }
1768
+
1769
+ return null;
1770
+ }
1771
+
1772
+ /**
1773
+ * @param $id
1774
+ * @param $action
1775
+ * @param string $code
1776
+ * @return null|string
1777
+ */
1778
+ protected function o_javascript($id, $action, $code = '')
1779
+ {
1780
+ switch ($action) {
1781
+ case 'new':
1782
+ $this->objects[$id] = array(
1783
+ 't' => 'javascript',
1784
+ 'info' => array(
1785
+ 'S' => '/JavaScript',
1786
+ 'JS' => '(' . $this->filterText($code, true, false) . ')',
1787
+ )
1788
+ );
1789
+ break;
1790
+
1791
+ case 'out':
1792
+ $o = &$this->objects[$id];
1793
+ $res = "\n$id 0 obj\n<< ";
1794
+
1795
+ foreach ($o['info'] as $k => $v) {
1796
+ $res .= "\n/$k $v";
1797
+ }
1798
+ $res .= "\n>>\nendobj";
1799
+
1800
+ return $res;
1801
+ }
1802
+
1803
+ return null;
1804
+ }
1805
+
1806
+ /**
1807
+ * an image object, will be an XObject in the document, includes description and data
1808
+ *
1809
+ * @param $id
1810
+ * @param $action
1811
+ * @param string $options
1812
+ * @return null|string
1813
+ */
1814
+ protected function o_image($id, $action, $options = '')
1815
+ {
1816
+ switch ($action) {
1817
+ case 'new':
1818
+ // make the new object
1819
+ $this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array());
1820
+
1821
+ $info =& $this->objects[$id]['info'];
1822
+
1823
+ $info['Type'] = '/XObject';
1824
+ $info['Subtype'] = '/Image';
1825
+ $info['Width'] = $options['iw'];
1826
+ $info['Height'] = $options['ih'];
1827
+
1828
+ if (isset($options['masked']) && $options['masked']) {
1829
+ $info['SMask'] = ($this->numObj - 1) . ' 0 R';
1830
+ }
1831
+
1832
+ if (!isset($options['type']) || $options['type'] === 'jpg') {
1833
+ if (!isset($options['channels'])) {
1834
+ $options['channels'] = 3;
1835
+ }
1836
+
1837
+ switch ($options['channels']) {
1838
+ case 1:
1839
+ $info['ColorSpace'] = '/DeviceGray';
1840
+ break;
1841
+ case 4:
1842
+ $info['ColorSpace'] = '/DeviceCMYK';
1843
+ break;
1844
+ default:
1845
+ $info['ColorSpace'] = '/DeviceRGB';
1846
+ break;
1847
+ }
1848
+
1849
+ if ($info['ColorSpace'] === '/DeviceCMYK') {
1850
+ $info['Decode'] = '[1 0 1 0 1 0 1 0]';
1851
+ }
1852
+
1853
+ $info['Filter'] = '/DCTDecode';
1854
+ $info['BitsPerComponent'] = 8;
1855
+ } else {
1856
+ if ($options['type'] === 'png') {
1857
+ $info['Filter'] = '/FlateDecode';
1858
+ $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
1859
+
1860
+ if ($options['isMask']) {
1861
+ $info['ColorSpace'] = '/DeviceGray';
1862
+ } else {
1863
+ if (mb_strlen($options['pdata'], '8bit')) {
1864
+ $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
1865
+ $this->numObj++;
1866
+ $this->o_contents($this->numObj, 'new');
1867
+ $this->objects[$this->numObj]['c'] = $options['pdata'];
1868
+ $tmp .= $this->numObj . ' 0 R';
1869
+ $tmp .= ' ]';
1870
+ $info['ColorSpace'] = $tmp;
1871
+
1872
+ if (isset($options['transparency'])) {
1873
+ $transparency = $options['transparency'];
1874
+ switch ($transparency['type']) {
1875
+ case 'indexed':
1876
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1877
+ $info['Mask'] = $tmp;
1878
+ break;
1879
+
1880
+ case 'color-key':
1881
+ $tmp = ' [ ' .
1882
+ $transparency['r'] . ' ' . $transparency['r'] .
1883
+ $transparency['g'] . ' ' . $transparency['g'] .
1884
+ $transparency['b'] . ' ' . $transparency['b'] .
1885
+ ' ] ';
1886
+ $info['Mask'] = $tmp;
1887
+ break;
1888
+ }
1889
+ }
1890
+ } else {
1891
+ if (isset($options['transparency'])) {
1892
+ $transparency = $options['transparency'];
1893
+
1894
+ switch ($transparency['type']) {
1895
+ case 'indexed':
1896
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
1897
+ $info['Mask'] = $tmp;
1898
+ break;
1899
+
1900
+ case 'color-key':
1901
+ $tmp = ' [ ' .
1902
+ $transparency['r'] . ' ' . $transparency['r'] . ' ' .
1903
+ $transparency['g'] . ' ' . $transparency['g'] . ' ' .
1904
+ $transparency['b'] . ' ' . $transparency['b'] .
1905
+ ' ] ';
1906
+ $info['Mask'] = $tmp;
1907
+ break;
1908
+ }
1909
+ }
1910
+ $info['ColorSpace'] = '/' . $options['color'];
1911
+ }
1912
+ }
1913
+
1914
+ $info['BitsPerComponent'] = $options['bitsPerComponent'];
1915
+ }
1916
+ }
1917
+
1918
+ // assign it a place in the named resource dictionary as an external object, according to
1919
+ // the label passed in with it.
1920
+ $this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id));
1921
+
1922
+ // also make sure that we have the right procset object for it.
1923
+ $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
1924
+ break;
1925
+
1926
+ case 'out':
1927
+ $o = &$this->objects[$id];
1928
+ $tmp = &$o['data'];
1929
+ $res = "\n$id 0 obj\n<<";
1930
+
1931
+ foreach ($o['info'] as $k => $v) {
1932
+ $res .= "\n/$k $v";
1933
+ }
1934
+
1935
+ if ($this->encrypted) {
1936
+ $this->encryptInit($id);
1937
+ $tmp = $this->ARC4($tmp);
1938
+ }
1939
+
1940
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
1941
+
1942
+ return $res;
1943
+ }
1944
+
1945
+ return null;
1946
+ }
1947
+
1948
+ /**
1949
+ * graphics state object
1950
+ *
1951
+ * @param $id
1952
+ * @param $action
1953
+ * @param string $options
1954
+ * @return null|string
1955
+ */
1956
+ protected function o_extGState($id, $action, $options = "")
1957
+ {
1958
+ static $valid_params = array(
1959
+ "LW",
1960
+ "LC",
1961
+ "LC",
1962
+ "LJ",
1963
+ "ML",
1964
+ "D",
1965
+ "RI",
1966
+ "OP",
1967
+ "op",
1968
+ "OPM",
1969
+ "Font",
1970
+ "BG",
1971
+ "BG2",
1972
+ "UCR",
1973
+ "TR",
1974
+ "TR2",
1975
+ "HT",
1976
+ "FL",
1977
+ "SM",
1978
+ "SA",
1979
+ "BM",
1980
+ "SMask",
1981
+ "CA",
1982
+ "ca",
1983
+ "AIS",
1984
+ "TK"
1985
+ );
1986
+
1987
+ switch ($action) {
1988
+ case "new":
1989
+ $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
1990
+
1991
+ // Tell the pages about the new resource
1992
+ $this->numStates++;
1993
+ $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
1994
+ break;
1995
+
1996
+ case "out":
1997
+ $o = &$this->objects[$id];
1998
+ $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
1999
+
2000
+ foreach ($o["info"] as $k => $v) {
2001
+ if (!in_array($k, $valid_params)) {
2002
+ continue;
2003
+ }
2004
+ $res .= "/$k $v\n";
2005
+ }
2006
+
2007
+ $res .= ">>\nendobj";
2008
+
2009
+ return $res;
2010
+ }
2011
+
2012
+ return null;
2013
+ }
2014
+
2015
+ /**
2016
+ * encryption object.
2017
+ *
2018
+ * @param $id
2019
+ * @param $action
2020
+ * @param string $options
2021
+ * @return string|null
2022
+ */
2023
+ protected function o_encryption($id, $action, $options = '')
2024
+ {
2025
+ switch ($action) {
2026
+ case 'new':
2027
+ // make the new object
2028
+ $this->objects[$id] = array('t' => 'encryption', 'info' => $options);
2029
+ $this->arc4_objnum = $id;
2030
+ break;
2031
+
2032
+ case 'keys':
2033
+ // figure out the additional parameters required
2034
+ $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2035
+ . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2036
+ . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2037
+ . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2038
+
2039
+ $info = $this->objects[$id]['info'];
2040
+
2041
+ $len = mb_strlen($info['owner'], '8bit');
2042
+
2043
+ if ($len > 32) {
2044
+ $owner = substr($info['owner'], 0, 32);
2045
+ } else {
2046
+ if ($len < 32) {
2047
+ $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2048
+ } else {
2049
+ $owner = $info['owner'];
2050
+ }
2051
+ }
2052
+
2053
+ $len = mb_strlen($info['user'], '8bit');
2054
+ if ($len > 32) {
2055
+ $user = substr($info['user'], 0, 32);
2056
+ } else {
2057
+ if ($len < 32) {
2058
+ $user = $info['user'] . substr($pad, 0, 32 - $len);
2059
+ } else {
2060
+ $user = $info['user'];
2061
+ }
2062
+ }
2063
+
2064
+ $tmp = $this->md5_16($owner);
2065
+ $okey = substr($tmp, 0, 5);
2066
+ $this->ARC4_init($okey);
2067
+ $ovalue = $this->ARC4($user);
2068
+ $this->objects[$id]['info']['O'] = $ovalue;
2069
+
2070
+ // now make the u value, phew.
2071
+ $tmp = $this->md5_16(
2072
+ $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2073
+ );
2074
+
2075
+ $ukey = substr($tmp, 0, 5);
2076
+ $this->ARC4_init($ukey);
2077
+ $this->encryptionKey = $ukey;
2078
+ $this->encrypted = true;
2079
+ $uvalue = $this->ARC4($pad);
2080
+ $this->objects[$id]['info']['U'] = $uvalue;
2081
+ // initialize the arc4 array
2082
+ break;
2083
+
2084
+ case 'out':
2085
+ $o = &$this->objects[$id];
2086
+
2087
+ $res = "\n$id 0 obj\n<<";
2088
+ $res .= "\n/Filter /Standard";
2089
+ $res .= "\n/V 1";
2090
+ $res .= "\n/R 2";
2091
+ $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2092
+ $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2093
+ // and the p-value needs to be converted to account for the twos-complement approach
2094
+ $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2095
+ $res .= "\n/P " . ($o['info']['p']);
2096
+ $res .= "\n>>\nendobj";
2097
+
2098
+ return $res;
2099
+ }
2100
+
2101
+ return null;
2102
+ }
2103
+
2104
+ /**
2105
+ * ARC4 functions
2106
+ * A series of function to implement ARC4 encoding in PHP
2107
+ */
2108
+
2109
+ /**
2110
+ * calculate the 16 byte version of the 128 bit md5 digest of the string
2111
+ *
2112
+ * @param $string
2113
+ * @return string
2114
+ */
2115
+ function md5_16($string)
2116
+ {
2117
+ $tmp = md5($string);
2118
+ $out = '';
2119
+ for ($i = 0; $i <= 30; $i = $i + 2) {
2120
+ $out .= chr(hexdec(substr($tmp, $i, 2)));
2121
+ }
2122
+
2123
+ return $out;
2124
+ }
2125
+
2126
+ /**
2127
+ * initialize the encryption for processing a particular object
2128
+ *
2129
+ * @param $id
2130
+ */
2131
+ function encryptInit($id)
2132
+ {
2133
+ $tmp = $this->encryptionKey;
2134
+ $hex = dechex($id);
2135
+ if (mb_strlen($hex, '8bit') < 6) {
2136
+ $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2137
+ }
2138
+ $tmp .= chr(hexdec(substr($hex, 4, 2)))
2139
+ . chr(hexdec(substr($hex, 2, 2)))
2140
+ . chr(hexdec(substr($hex, 0, 2)))
2141
+ . chr(0)
2142
+ . chr(0)
2143
+ ;
2144
+ $key = $this->md5_16($tmp);
2145
+ $this->ARC4_init(substr($key, 0, 10));
2146
+ }
2147
+
2148
+ /**
2149
+ * initialize the ARC4 encryption
2150
+ *
2151
+ * @param string $key
2152
+ */
2153
+ function ARC4_init($key = '')
2154
+ {
2155
+ $this->arc4 = '';
2156
+
2157
+ // setup the control array
2158
+ if (mb_strlen($key, '8bit') == 0) {
2159
+ return;
2160
+ }
2161
+
2162
+ $k = '';
2163
+ while (mb_strlen($k, '8bit') < 256) {
2164
+ $k .= $key;
2165
+ }
2166
+
2167
+ $k = substr($k, 0, 256);
2168
+ for ($i = 0; $i < 256; $i++) {
2169
+ $this->arc4 .= chr($i);
2170
+ }
2171
+
2172
+ $j = 0;
2173
+
2174
+ for ($i = 0; $i < 256; $i++) {
2175
+ $t = $this->arc4[$i];
2176
+ $j = ($j + ord($t) + ord($k[$i])) % 256;
2177
+ $this->arc4[$i] = $this->arc4[$j];
2178
+ $this->arc4[$j] = $t;
2179
+ }
2180
+ }
2181
+
2182
+ /**
2183
+ * ARC4 encrypt a text string
2184
+ *
2185
+ * @param $text
2186
+ * @return string
2187
+ */
2188
+ function ARC4($text)
2189
+ {
2190
+ $len = mb_strlen($text, '8bit');
2191
+ $a = 0;
2192
+ $b = 0;
2193
+ $c = $this->arc4;
2194
+ $out = '';
2195
+ for ($i = 0; $i < $len; $i++) {
2196
+ $a = ($a + 1) % 256;
2197
+ $t = $c[$a];
2198
+ $b = ($b + ord($t)) % 256;
2199
+ $c[$a] = $c[$b];
2200
+ $c[$b] = $t;
2201
+ $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
2202
+ $out .= chr(ord($text[$i]) ^ $k);
2203
+ }
2204
+
2205
+ return $out;
2206
+ }
2207
+
2208
+ /**
2209
+ * functions which can be called to adjust or add to the document
2210
+ */
2211
+
2212
+ /**
2213
+ * add a link in the document to an external URL
2214
+ *
2215
+ * @param $url
2216
+ * @param $x0
2217
+ * @param $y0
2218
+ * @param $x1
2219
+ * @param $y1
2220
+ */
2221
+ function addLink($url, $x0, $y0, $x1, $y1)
2222
+ {
2223
+ $this->numObj++;
2224
+ $info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1));
2225
+ $this->o_annotation($this->numObj, 'new', $info);
2226
+ }
2227
+
2228
+ /**
2229
+ * add a link in the document to an internal destination (ie. within the document)
2230
+ *
2231
+ * @param $label
2232
+ * @param $x0
2233
+ * @param $y0
2234
+ * @param $x1
2235
+ * @param $y1
2236
+ */
2237
+ function addInternalLink($label, $x0, $y0, $x1, $y1)
2238
+ {
2239
+ $this->numObj++;
2240
+ $info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1));
2241
+ $this->o_annotation($this->numObj, 'new', $info);
2242
+ }
2243
+
2244
+ /**
2245
+ * set the encryption of the document
2246
+ * can be used to turn it on and/or set the passwords which it will have.
2247
+ * also the functions that the user will have are set here, such as print, modify, add
2248
+ *
2249
+ * @param string $userPass
2250
+ * @param string $ownerPass
2251
+ * @param array $pc
2252
+ */
2253
+ function setEncryption($userPass = '', $ownerPass = '', $pc = array())
2254
+ {
2255
+ $p = bindec("11000000");
2256
+
2257
+ $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32);
2258
+
2259
+ foreach ($pc as $k => $v) {
2260
+ if ($v && isset($options[$k])) {
2261
+ $p += $options[$k];
2262
+ } else {
2263
+ if (isset($options[$v])) {
2264
+ $p += $options[$v];
2265
+ }
2266
+ }
2267
+ }
2268
+
2269
+ // implement encryption on the document
2270
+ if ($this->arc4_objnum == 0) {
2271
+ // then the block does not exist already, add it.
2272
+ $this->numObj++;
2273
+ if (mb_strlen($ownerPass) == 0) {
2274
+ $ownerPass = $userPass;
2275
+ }
2276
+
2277
+ $this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p));
2278
+ }
2279
+ }
2280
+
2281
+ /**
2282
+ * should be used for internal checks, not implemented as yet
2283
+ */
2284
+ function checkAllHere()
2285
+ {
2286
+ }
2287
+
2288
+ /**
2289
+ * return the pdf stream as a string returned from the function
2290
+ *
2291
+ * @param bool $debug
2292
+ * @return string
2293
+ */
2294
+ function output($debug = false)
2295
+ {
2296
+ if ($debug) {
2297
+ // turn compression off
2298
+ $this->options['compression'] = false;
2299
+ }
2300
+
2301
+ if ($this->javascript) {
2302
+ $this->numObj++;
2303
+
2304
+ $js_id = $this->numObj;
2305
+ $this->o_embedjs($js_id, 'new');
2306
+ $this->o_javascript(++$this->numObj, 'new', $this->javascript);
2307
+
2308
+ $id = $this->catalogId;
2309
+
2310
+ $this->o_catalog($id, 'javascript', $js_id);
2311
+ }
2312
+
2313
+ if ($this->fileIdentifier === '') {
2314
+ $tmp = implode('', $this->objects[$this->infoObject]['info']);
2315
+ $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
2316
+ }
2317
+
2318
+ if ($this->arc4_objnum) {
2319
+ $this->o_encryption($this->arc4_objnum, 'keys');
2320
+ $this->ARC4_init($this->encryptionKey);
2321
+ }
2322
+
2323
+ $this->checkAllHere();
2324
+
2325
+ $xref = array();
2326
+ $content = '%PDF-1.3';
2327
+ $pos = mb_strlen($content, '8bit');
2328
+
2329
+ foreach ($this->objects as $k => $v) {
2330
+ $tmp = 'o_' . $v['t'];
2331
+ $cont = $this->$tmp($k, 'out');
2332
+ $content .= $cont;
2333
+ $xref[] = $pos + 1; //+1 to account for \n at the start of each object
2334
+ $pos += mb_strlen($cont, '8bit');
2335
+ }
2336
+
2337
+ $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
2338
+
2339
+ foreach ($xref as $p) {
2340
+ $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
2341
+ }
2342
+
2343
+ $content .= "trailer\n<<\n" .
2344
+ '/Size ' . (count($xref) + 1) . "\n" .
2345
+ '/Root 1 0 R' . "\n" .
2346
+ '/Info ' . $this->infoObject . " 0 R\n"
2347
+ ;
2348
+
2349
+ // if encryption has been applied to this document then add the marker for this dictionary
2350
+ if ($this->arc4_objnum > 0) {
2351
+ $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
2352
+ }
2353
+
2354
+ $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
2355
+
2356
+ // account for \n added at start of xref table
2357
+ $pos++;
2358
+
2359
+ $content .= ">>\nstartxref\n$pos\n%%EOF\n";
2360
+
2361
+ return $content;
2362
+ }
2363
+
2364
+ /**
2365
+ * initialize a new document
2366
+ * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
2367
+ * this function is called automatically by the constructor function
2368
+ *
2369
+ * @param array $pageSize
2370
+ */
2371
+ private function newDocument($pageSize = array(0, 0, 612, 792))
2372
+ {
2373
+ $this->numObj = 0;
2374
+ $this->objects = array();
2375
+
2376
+ $this->numObj++;
2377
+ $this->o_catalog($this->numObj, 'new');
2378
+
2379
+ $this->numObj++;
2380
+ $this->o_outlines($this->numObj, 'new');
2381
+
2382
+ $this->numObj++;
2383
+ $this->o_pages($this->numObj, 'new');
2384
+
2385
+ $this->o_pages($this->numObj, 'mediaBox', $pageSize);
2386
+ $this->currentNode = 3;
2387
+
2388
+ $this->numObj++;
2389
+ $this->o_procset($this->numObj, 'new');
2390
+
2391
+ $this->numObj++;
2392
+ $this->o_info($this->numObj, 'new');
2393
+
2394
+ $this->numObj++;
2395
+ $this->o_page($this->numObj, 'new');
2396
+
2397
+ // need to store the first page id as there is no way to get it to the user during
2398
+ // startup
2399
+ $this->firstPageId = $this->currentContents;
2400
+ }
2401
+
2402
+ /**
2403
+ * open the font file and return a php structure containing it.
2404
+ * first check if this one has been done before and saved in a form more suited to php
2405
+ * note that if a php serialized version does not exist it will try and make one, but will
2406
+ * require write access to the directory to do it... it is MUCH faster to have these serialized
2407
+ * files.
2408
+ *
2409
+ * @param $font
2410
+ */
2411
+ private function openFont($font)
2412
+ {
2413
+ // assume that $font contains the path and file but not the extension
2414
+ $name = basename($font);
2415
+ $dir = dirname($font) . '/';
2416
+
2417
+ $fontcache = $this->fontcache;
2418
+ if ($fontcache == '') {
2419
+ $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
2420
+ }
2421
+
2422
+ //$name filename without folder and extension of font metrics
2423
+ //$dir folder of font metrics
2424
+ //$fontcache folder of runtime created php serialized version of font metrics.
2425
+ // If this is not given, the same folder as the font metrics will be used.
2426
+ // Storing and reusing serialized versions improves speed much
2427
+
2428
+ $this->addMessage("openFont: $font - $name");
2429
+
2430
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
2431
+ $metrics_name = "$name.afm";
2432
+ } else {
2433
+ $metrics_name = "$name.ufm";
2434
+ }
2435
+
2436
+ $cache_name = "$metrics_name.php";
2437
+ $this->addMessage("metrics: $metrics_name, cache: $cache_name");
2438
+
2439
+ if (file_exists($fontcache . '/' . $cache_name)) {
2440
+ $this->addMessage("openFont: php file exists $fontcache/$cache_name");
2441
+ $this->fonts[$font] = require($fontcache . '/' . $cache_name);
2442
+
2443
+ if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
2444
+ // if the font file is old, then clear it out and prepare for re-creation
2445
+ $this->addMessage('openFont: clear out, make way for new version.');
2446
+ $this->fonts[$font] = null;
2447
+ unset($this->fonts[$font]);
2448
+ }
2449
+ } else {
2450
+ $old_cache_name = "php_$metrics_name";
2451
+ if (file_exists($fontcache . '/' . $old_cache_name)) {
2452
+ $this->addMessage(
2453
+ "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
2454
+ );
2455
+ $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
2456
+ file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
2457
+
2458
+ $this->openFont($font);
2459
+ return;
2460
+ }
2461
+ }
2462
+
2463
+ if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
2464
+ // then rebuild the php_<font>.afm file from the <font>.afm file
2465
+ $this->addMessage("openFont: build php file from $dir$metrics_name");
2466
+ $data = array();
2467
+
2468
+ // 20 => 'space'
2469
+ $data['codeToName'] = array();
2470
+
2471
+ // Since we're not going to enable Unicode for the core fonts we need to use a font-based
2472
+ // setting for Unicode support rather than a global setting.
2473
+ $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
2474
+
2475
+ $cidtogid = '';
2476
+ if ($data['isUnicode']) {
2477
+ $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
2478
+ }
2479
+
2480
+ $file = file($dir . $metrics_name);
2481
+
2482
+ foreach ($file as $rowA) {
2483
+ $row = trim($rowA);
2484
+ $pos = strpos($row, ' ');
2485
+
2486
+ if ($pos) {
2487
+ // then there must be some keyword
2488
+ $key = substr($row, 0, $pos);
2489
+ switch ($key) {
2490
+ case 'FontName':
2491
+ case 'FullName':
2492
+ case 'FamilyName':
2493
+ case 'PostScriptName':
2494
+ case 'Weight':
2495
+ case 'ItalicAngle':
2496
+ case 'IsFixedPitch':
2497
+ case 'CharacterSet':
2498
+ case 'UnderlinePosition':
2499
+ case 'UnderlineThickness':
2500
+ case 'Version':
2501
+ case 'EncodingScheme':
2502
+ case 'CapHeight':
2503
+ case 'XHeight':
2504
+ case 'Ascender':
2505
+ case 'Descender':
2506
+ case 'StdHW':
2507
+ case 'StdVW':
2508
+ case 'StartCharMetrics':
2509
+ case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big.
2510
+ $data[$key] = trim(substr($row, $pos));
2511
+ break;
2512
+
2513
+ case 'FontBBox':
2514
+ $data[$key] = explode(' ', trim(substr($row, $pos)));
2515
+ break;
2516
+
2517
+ //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
2518
+ case 'C': // Found in AFM files
2519
+ $bits = explode(';', trim($row));
2520
+ $dtmp = array();
2521
+
2522
+ foreach ($bits as $bit) {
2523
+ $bits2 = explode(' ', trim($bit));
2524
+ if (mb_strlen($bits2[0], '8bit') == 0) {
2525
+ continue;
2526
+ }
2527
+
2528
+ if (count($bits2) > 2) {
2529
+ $dtmp[$bits2[0]] = array();
2530
+ for ($i = 1; $i < count($bits2); $i++) {
2531
+ $dtmp[$bits2[0]][] = $bits2[$i];
2532
+ }
2533
+ } else {
2534
+ if (count($bits2) == 2) {
2535
+ $dtmp[$bits2[0]] = $bits2[1];
2536
+ }
2537
+ }
2538
+ }
2539
+
2540
+ $c = (int)$dtmp['C'];
2541
+ $n = $dtmp['N'];
2542
+ $width = floatval($dtmp['WX']);
2543
+
2544
+ if ($c >= 0) {
2545
+ if ($c != hexdec($n)) {
2546
+ $data['codeToName'][$c] = $n;
2547
+ }
2548
+ $data['C'][$c] = $width;
2549
+ } else {
2550
+ $data['C'][$n] = $width;
2551
+ }
2552
+
2553
+ if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2554
+ $data['MissingWidth'] = $width;
2555
+ }
2556
+
2557
+ break;
2558
+
2559
+ // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
2560
+ case 'U': // Found in UFM files
2561
+ if (!$data['isUnicode']) {
2562
+ break;
2563
+ }
2564
+
2565
+ $bits = explode(';', trim($row));
2566
+ $dtmp = array();
2567
+
2568
+ foreach ($bits as $bit) {
2569
+ $bits2 = explode(' ', trim($bit));
2570
+ if (mb_strlen($bits2[0], '8bit') === 0) {
2571
+ continue;
2572
+ }
2573
+
2574
+ if (count($bits2) > 2) {
2575
+ $dtmp[$bits2[0]] = array();
2576
+ for ($i = 1; $i < count($bits2); $i++) {
2577
+ $dtmp[$bits2[0]][] = $bits2[$i];
2578
+ }
2579
+ } else {
2580
+ if (count($bits2) == 2) {
2581
+ $dtmp[$bits2[0]] = $bits2[1];
2582
+ }
2583
+ }
2584
+ }
2585
+
2586
+ $c = (int)$dtmp['U'];
2587
+ $n = $dtmp['N'];
2588
+ $glyph = $dtmp['G'];
2589
+ $width = floatval($dtmp['WX']);
2590
+
2591
+ if ($c >= 0) {
2592
+ // Set values in CID to GID map
2593
+ if ($c >= 0 && $c < 0xFFFF && $glyph) {
2594
+ $cidtogid[$c * 2] = chr($glyph >> 8);
2595
+ $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
2596
+ }
2597
+
2598
+ if ($c != hexdec($n)) {
2599
+ $data['codeToName'][$c] = $n;
2600
+ }
2601
+ $data['C'][$c] = $width;
2602
+ } else {
2603
+ $data['C'][$n] = $width;
2604
+ }
2605
+
2606
+ if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') {
2607
+ $data['MissingWidth'] = $width;
2608
+ }
2609
+
2610
+ break;
2611
+
2612
+ case 'KPX':
2613
+ break; // don't include them as they are not used yet
2614
+ //KPX Adieresis yacute -40
2615
+ /*$bits = explode(' ', trim($row));
2616
+ $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
2617
+ break;*/
2618
+ }
2619
+ }
2620
+ }
2621
+
2622
+ if ($this->compressionReady && $this->options['compression']) {
2623
+ // then implement ZLIB based compression on CIDtoGID string
2624
+ $data['CIDtoGID_Compressed'] = true;
2625
+ $cidtogid = gzcompress($cidtogid, 6);
2626
+ }
2627
+ $data['CIDtoGID'] = base64_encode($cidtogid);
2628
+ $data['_version_'] = $this->fontcacheVersion;
2629
+ $this->fonts[$font] = $data;
2630
+
2631
+ //Because of potential trouble with php safe mode, expect that the folder already exists.
2632
+ //If not existing, this will hit performance because of missing cached results.
2633
+ if (is_dir($fontcache) && is_writable($fontcache)) {
2634
+ file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
2635
+ }
2636
+ $data = null;
2637
+ }
2638
+
2639
+ if (!isset($this->fonts[$font])) {
2640
+ $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
2641
+ }
2642
+
2643
+ //pre_r($this->messages);
2644
+ }
2645
+
2646
+ /**
2647
+ * if the font is not loaded then load it and make the required object
2648
+ * else just make it the current font
2649
+ * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
2650
+ * note that encoding='none' will need to be used for symbolic fonts
2651
+ * and 'differences' => an array of mappings between numbers 0->255 and character names.
2652
+ *
2653
+ * @param $fontName
2654
+ * @param string $encoding
2655
+ * @param bool $set
2656
+ * @return int
2657
+ */
2658
+ function selectFont($fontName, $encoding = '', $set = true)
2659
+ {
2660
+ $ext = substr($fontName, -4);
2661
+ if ($ext === '.afm' || $ext === '.ufm') {
2662
+ $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
2663
+ }
2664
+
2665
+ if (!isset($this->fonts[$fontName])) {
2666
+ $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
2667
+
2668
+ // load the file
2669
+ $this->openFont($fontName);
2670
+
2671
+ if (isset($this->fonts[$fontName])) {
2672
+ $this->numObj++;
2673
+ $this->numFonts++;
2674
+
2675
+ $font = &$this->fonts[$fontName];
2676
+
2677
+ $name = basename($fontName);
2678
+ $dir = dirname($fontName) . '/';
2679
+ $options = array('name' => $name, 'fontFileName' => $fontName);
2680
+
2681
+ if (is_array($encoding)) {
2682
+ // then encoding and differences might be set
2683
+ if (isset($encoding['encoding'])) {
2684
+ $options['encoding'] = $encoding['encoding'];
2685
+ }
2686
+
2687
+ if (isset($encoding['differences'])) {
2688
+ $options['differences'] = $encoding['differences'];
2689
+ }
2690
+ } else {
2691
+ if (mb_strlen($encoding, '8bit')) {
2692
+ // then perhaps only the encoding has been set
2693
+ $options['encoding'] = $encoding;
2694
+ }
2695
+ }
2696
+
2697
+ $fontObj = $this->numObj;
2698
+ $this->o_font($this->numObj, 'new', $options);
2699
+ $font['fontNum'] = $this->numFonts;
2700
+
2701
+ // if this is a '.afm' font, and there is a '.pfa' file to go with it (as there
2702
+ // should be for all non-basic fonts), then load it into an object and put the
2703
+ // references into the font object
2704
+ $basefile = $fontName;
2705
+
2706
+ $fbtype = '';
2707
+ if (file_exists("$basefile.ttf")) {
2708
+ $fbtype = 'ttf';
2709
+ } elseif (file_exists("$basefile.TTF")) {
2710
+ $fbtype = 'TTF';
2711
+ } elseif (file_exists("$basefile.pfb")) {
2712
+ $fbtype = 'pfb';
2713
+ } elseif (file_exists("$basefile.PFB")) {
2714
+ $fbtype = 'PFB';
2715
+ }
2716
+
2717
+ $fbfile = "$basefile.$fbtype";
2718
+
2719
+ // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
2720
+ // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
2721
+ $this->addMessage('selectFont: checking for - ' . $fbfile);
2722
+
2723
+ // OAR - I don't understand this old check
2724
+ // if (substr($fontName, -4) === '.afm' && strlen($fbtype)) {
2725
+ if ($fbtype) {
2726
+ $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
2727
+ // $fontObj = $this->numObj;
2728
+ $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
2729
+
2730
+ // find the array of font widths, and put that into an object.
2731
+ $firstChar = -1;
2732
+ $lastChar = 0;
2733
+ $widths = array();
2734
+ $cid_widths = array();
2735
+
2736
+ foreach ($font['C'] as $num => $d) {
2737
+ if (intval($num) > 0 || $num == '0') {
2738
+ if (!$font['isUnicode']) {
2739
+ // With Unicode, widths array isn't used
2740
+ if ($lastChar > 0 && $num > $lastChar + 1) {
2741
+ for ($i = $lastChar + 1; $i < $num; $i++) {
2742
+ $widths[] = 0;
2743
+ }
2744
+ }
2745
+ }
2746
+
2747
+ $widths[] = $d;
2748
+
2749
+ if ($font['isUnicode']) {
2750
+ $cid_widths[$num] = $d;
2751
+ }
2752
+
2753
+ if ($firstChar == -1) {
2754
+ $firstChar = $num;
2755
+ }
2756
+
2757
+ $lastChar = $num;
2758
+ }
2759
+ }
2760
+
2761
+ // also need to adjust the widths for the differences array
2762
+ if (isset($options['differences'])) {
2763
+ foreach ($options['differences'] as $charNum => $charName) {
2764
+ if ($charNum > $lastChar) {
2765
+ if (!$font['isUnicode']) {
2766
+ // With Unicode, widths array isn't used
2767
+ for ($i = $lastChar + 1; $i <= $charNum; $i++) {
2768
+ $widths[] = 0;
2769
+ }
2770
+ }
2771
+
2772
+ $lastChar = $charNum;
2773
+ }
2774
+
2775
+ if (isset($font['C'][$charName])) {
2776
+ $widths[$charNum - $firstChar] = $font['C'][$charName];
2777
+ if ($font['isUnicode']) {
2778
+ $cid_widths[$charName] = $font['C'][$charName];
2779
+ }
2780
+ }
2781
+ }
2782
+ }
2783
+
2784
+ if ($font['isUnicode']) {
2785
+ $font['CIDWidths'] = $cid_widths;
2786
+ }
2787
+
2788
+ $this->addMessage('selectFont: FirstChar = ' . $firstChar);
2789
+ $this->addMessage('selectFont: LastChar = ' . $lastChar);
2790
+
2791
+ $widthid = -1;
2792
+
2793
+ if (!$font['isUnicode']) {
2794
+ // With Unicode, widths array isn't used
2795
+
2796
+ $this->numObj++;
2797
+ $this->o_contents($this->numObj, 'new', 'raw');
2798
+ $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
2799
+ $widthid = $this->numObj;
2800
+ }
2801
+
2802
+ $missing_width = 500;
2803
+ $stemV = 70;
2804
+
2805
+ if (isset($font['MissingWidth'])) {
2806
+ $missing_width = $font['MissingWidth'];
2807
+ }
2808
+ if (isset($font['StdVW'])) {
2809
+ $stemV = $font['StdVW'];
2810
+ } else {
2811
+ if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
2812
+ $stemV = 120;
2813
+ }
2814
+ }
2815
+
2816
+ // load the pfb file, and put that into an object too.
2817
+ // note that pdf supports only binary format type 1 font files, though there is a
2818
+ // simple utility to convert them from pfa to pfb.
2819
+ // FIXME: should we move font subset creation to CPDF::output? See notes in issue #750.
2820
+ if (!$this->isUnicode || strtolower($fbtype) !== 'ttf' || empty($this->stringSubsets)) {
2821
+ $data = file_get_contents($fbfile);
2822
+ } else {
2823
+ $this->stringSubsets[$fontName][] = 32; // Force space if not in yet
2824
+
2825
+ $subset = $this->stringSubsets[$fontName];
2826
+ sort($subset);
2827
+
2828
+ // Load font
2829
+ $font_obj = Font::load($fbfile);
2830
+ $font_obj->parse();
2831
+
2832
+ // Define subset
2833
+ $font_obj->setSubset($subset);
2834
+ $font_obj->reduce();
2835
+
2836
+ // Write new font
2837
+ $tmp_name = $this->tmp . "/" . basename($fbfile) . ".tmp." . uniqid();
2838
+ $font_obj->open($tmp_name, BinaryStream::modeWrite);
2839
+ $font_obj->encode(array("OS/2"));
2840
+ $font_obj->close();
2841
+
2842
+ // Parse the new font to get cid2gid and widths
2843
+ $font_obj = Font::load($tmp_name);
2844
+
2845
+ // Find Unicode char map table
2846
+ $subtable = null;
2847
+ foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
2848
+ if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
2849
+ $subtable = $_subtable;
2850
+ break;
2851
+ }
2852
+ }
2853
+
2854
+ if ($subtable) {
2855
+ $glyphIndexArray = $subtable["glyphIndexArray"];
2856
+ $hmtx = $font_obj->getData("hmtx");
2857
+
2858
+ unset($glyphIndexArray[0xFFFF]);
2859
+
2860
+ $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
2861
+ $font['CIDWidths'] = array();
2862
+ foreach ($glyphIndexArray as $cid => $gid) {
2863
+ if ($cid >= 0 && $cid < 0xFFFF && $gid) {
2864
+ $cidtogid[$cid * 2] = chr($gid >> 8);
2865
+ $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
2866
+ }
2867
+
2868
+ $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
2869
+ $font['CIDWidths'][$cid] = $width;
2870
+ }
2871
+
2872
+ $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
2873
+ $font['CIDtoGID_Compressed'] = true;
2874
+
2875
+ $data = file_get_contents($tmp_name);
2876
+ } else {
2877
+ $data = file_get_contents($fbfile);
2878
+ }
2879
+
2880
+ $font_obj->close();
2881
+ unlink($tmp_name);
2882
+ }
2883
+
2884
+ // create the font descriptor
2885
+ $this->numObj++;
2886
+ $fontDescriptorId = $this->numObj;
2887
+
2888
+ $this->numObj++;
2889
+ $pfbid = $this->numObj;
2890
+
2891
+ // determine flags (more than a little flakey, hopefully will not matter much)
2892
+ $flags = 0;
2893
+
2894
+ if ($font['ItalicAngle'] != 0) {
2895
+ $flags += pow(2, 6);
2896
+ }
2897
+
2898
+ if ($font['IsFixedPitch'] === 'true') {
2899
+ $flags += 1;
2900
+ }
2901
+
2902
+ $flags += pow(2, 5); // assume non-sybolic
2903
+ $list = array(
2904
+ 'Ascent' => 'Ascender',
2905
+ 'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
2906
+ 'MissingWidth' => 'MissingWidth',
2907
+ 'Descent' => 'Descender',
2908
+ 'FontBBox' => 'FontBBox',
2909
+ 'ItalicAngle' => 'ItalicAngle'
2910
+ );
2911
+ $fdopt = array(
2912
+ 'Flags' => $flags,
2913
+ 'FontName' => $adobeFontName,
2914
+ 'StemV' => $stemV
2915
+ );
2916
+
2917
+ foreach ($list as $k => $v) {
2918
+ if (isset($font[$v])) {
2919
+ $fdopt[$k] = $font[$v];
2920
+ }
2921
+ }
2922
+
2923
+ if (strtolower($fbtype) === 'pfb') {
2924
+ $fdopt['FontFile'] = $pfbid;
2925
+ } elseif (strtolower($fbtype) === 'ttf') {
2926
+ $fdopt['FontFile2'] = $pfbid;
2927
+ }
2928
+
2929
+ $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
2930
+
2931
+ // embed the font program
2932
+ $this->o_contents($this->numObj, 'new');
2933
+ $this->objects[$pfbid]['c'] .= $data;
2934
+
2935
+ // determine the cruicial lengths within this file
2936
+ if (strtolower($fbtype) === 'pfb') {
2937
+ $l1 = strpos($data, 'eexec') + 6;
2938
+ $l2 = strpos($data, '00000000') - $l1;
2939
+ $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
2940
+ $this->o_contents(
2941
+ $this->numObj,
2942
+ 'add',
2943
+ array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3)
2944
+ );
2945
+ } elseif (strtolower($fbtype) == 'ttf') {
2946
+ $l1 = mb_strlen($data, '8bit');
2947
+ $this->o_contents($this->numObj, 'add', array('Length1' => $l1));
2948
+ }
2949
+
2950
+ // tell the font object about all this new stuff
2951
+ $tmp = array(
2952
+ 'BaseFont' => $adobeFontName,
2953
+ 'MissingWidth' => $missing_width,
2954
+ 'Widths' => $widthid,
2955
+ 'FirstChar' => $firstChar,
2956
+ 'LastChar' => $lastChar,
2957
+ 'FontDescriptor' => $fontDescriptorId
2958
+ );
2959
+
2960
+ if (strtolower($fbtype) === 'ttf') {
2961
+ $tmp['SubType'] = 'TrueType';
2962
+ }
2963
+
2964
+ $this->addMessage("adding extra info to font.($fontObj)");
2965
+
2966
+ foreach ($tmp as $fk => $fv) {
2967
+ $this->addMessage("$fk : $fv");
2968
+ }
2969
+
2970
+ $this->o_font($fontObj, 'add', $tmp);
2971
+ } else {
2972
+ $this->addMessage(
2973
+ 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
2974
+ );
2975
+ }
2976
+
2977
+ // also set the differences here, note that this means that these will take effect only the
2978
+ //first time that a font is selected, else they are ignored
2979
+ if (isset($options['differences'])) {
2980
+ $font['differences'] = $options['differences'];
2981
+ }
2982
+ }
2983
+ }
2984
+
2985
+ if ($set && isset($this->fonts[$fontName])) {
2986
+ // so if for some reason the font was not set in the last one then it will not be selected
2987
+ $this->currentBaseFont = $fontName;
2988
+
2989
+ // the next lines mean that if a new font is selected, then the current text state will be
2990
+ // applied to it as well.
2991
+ $this->currentFont = $this->currentBaseFont;
2992
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
2993
+
2994
+ //$this->setCurrentFont();
2995
+ }
2996
+
2997
+ return $this->currentFontNum;
2998
+ //return $this->numObj;
2999
+ }
3000
+
3001
+ /**
3002
+ * sets up the current font, based on the font families, and the current text state
3003
+ * note that this system is quite flexible, a bold-italic font can be completely different to a
3004
+ * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3005
+ * This function is to be called whenever the currentTextState is changed, it will update
3006
+ * the currentFont setting to whatever the appropriate family one is.
3007
+ * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3008
+ * This function will change the currentFont to whatever it should be, but will not change the
3009
+ * currentBaseFont.
3010
+ */
3011
+ private function setCurrentFont()
3012
+ {
3013
+ // if (strlen($this->currentBaseFont) == 0){
3014
+ // // then assume an initial font
3015
+ // $this->selectFont($this->defaultFont);
3016
+ // }
3017
+ // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3018
+ // if (strlen($this->currentTextState)
3019
+ // && isset($this->fontFamilies[$cf])
3020
+ // && isset($this->fontFamilies[$cf][$this->currentTextState])){
3021
+ // // then we are in some state or another
3022
+ // // and this font has a family, and the current setting exists within it
3023
+ // // select the font, then return it
3024
+ // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3025
+ // $this->selectFont($nf,'',0);
3026
+ // $this->currentFont = $nf;
3027
+ // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3028
+ // } else {
3029
+ // // the this font must not have the right family member for the current state
3030
+ // // simply assume the base font
3031
+ $this->currentFont = $this->currentBaseFont;
3032
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3033
+ // }
3034
+ }
3035
+
3036
+ /**
3037
+ * function for the user to find out what the ID is of the first page that was created during
3038
+ * startup - useful if they wish to add something to it later.
3039
+ *
3040
+ * @return int
3041
+ */
3042
+ function getFirstPageId()
3043
+ {
3044
+ return $this->firstPageId;
3045
+ }
3046
+
3047
+ /**
3048
+ * add content to the currently active object
3049
+ *
3050
+ * @param $content
3051
+ */
3052
+ private function addContent($content)
3053
+ {
3054
+ $this->objects[$this->currentContents]['c'] .= $content;
3055
+ }
3056
+
3057
+ /**
3058
+ * sets the color for fill operations
3059
+ *
3060
+ * @param $color
3061
+ * @param bool $force
3062
+ */
3063
+ function setColor($color, $force = false)
3064
+ {
3065
+ $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3066
+
3067
+ if (!$force && $this->currentColor == $new_color) {
3068
+ return;
3069
+ }
3070
+
3071
+ if (isset($new_color[3])) {
3072
+ $this->currentColor = $new_color;
3073
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3074
+ } else {
3075
+ if (isset($new_color[2])) {
3076
+ $this->currentColor = $new_color;
3077
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3078
+ }
3079
+ }
3080
+ }
3081
+
3082
+ /**
3083
+ * sets the color for fill operations
3084
+ *
3085
+ * @param $fillRule
3086
+ */
3087
+ function setFillRule($fillRule)
3088
+ {
3089
+ if (!in_array($fillRule, array("nonzero", "evenodd"))) {
3090
+ return;
3091
+ }
3092
+
3093
+ $this->fillRule = $fillRule;
3094
+ }
3095
+
3096
+ /**
3097
+ * sets the color for stroke operations
3098
+ *
3099
+ * @param $color
3100
+ * @param bool $force
3101
+ */
3102
+ function setStrokeColor($color, $force = false)
3103
+ {
3104
+ $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null);
3105
+
3106
+ if (!$force && $this->currentStrokeColor == $new_color) {
3107
+ return;
3108
+ }
3109
+
3110
+ if (isset($new_color[3])) {
3111
+ $this->currentStrokeColor = $new_color;
3112
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3113
+ } else {
3114
+ if (isset($new_color[2])) {
3115
+ $this->currentStrokeColor = $new_color;
3116
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3117
+ }
3118
+ }
3119
+ }
3120
+
3121
+ /**
3122
+ * Set the graphics state for compositions
3123
+ *
3124
+ * @param $parameters
3125
+ */
3126
+ function setGraphicsState($parameters)
3127
+ {
3128
+ // Create a new graphics state object if necessary
3129
+ if (($gstate = array_search($parameters, $this->gstates)) === false) {
3130
+ $this->numObj++;
3131
+ $this->o_extGState($this->numObj, 'new', $parameters);
3132
+ $gstate = $this->numStates;
3133
+ $this->gstates[$gstate] = $parameters;
3134
+ }
3135
+ $this->addContent("\n/GS$gstate gs");
3136
+ }
3137
+
3138
+ /**
3139
+ * Set current blend mode & opacity for lines.
3140
+ *
3141
+ * Valid blend modes are:
3142
+ *
3143
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3144
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3145
+ * Exclusion
3146
+ *
3147
+ * @param string $mode the blend mode to use
3148
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3149
+ */
3150
+ function setLineTransparency($mode, $opacity)
3151
+ {
3152
+ static $blend_modes = array(
3153
+ "Normal",
3154
+ "Multiply",
3155
+ "Screen",
3156
+ "Overlay",
3157
+ "Darken",
3158
+ "Lighten",
3159
+ "ColorDogde",
3160
+ "ColorBurn",
3161
+ "HardLight",
3162
+ "SoftLight",
3163
+ "Difference",
3164
+ "Exclusion"
3165
+ );
3166
+
3167
+ if (!in_array($mode, $blend_modes)) {
3168
+ $mode = "Normal";
3169
+ }
3170
+
3171
+ // Only create a new graphics state if required
3172
+ if ($mode === $this->currentLineTransparency["mode"] &&
3173
+ $opacity == $this->currentLineTransparency["opacity"]
3174
+ ) {
3175
+ return;
3176
+ }
3177
+
3178
+ $this->currentLineTransparency["mode"] = $mode;
3179
+ $this->currentLineTransparency["opacity"] = $opacity;
3180
+
3181
+ $options = array(
3182
+ "BM" => "/$mode",
3183
+ "CA" => (float)$opacity
3184
+ );
3185
+
3186
+ $this->setGraphicsState($options);
3187
+ }
3188
+
3189
+ /**
3190
+ * Set current blend mode & opacity for filled objects.
3191
+ *
3192
+ * Valid blend modes are:
3193
+ *
3194
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3195
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3196
+ * Exclusion
3197
+ *
3198
+ * @param string $mode the blend mode to use
3199
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
3200
+ */
3201
+ function setFillTransparency($mode, $opacity)
3202
+ {
3203
+ static $blend_modes = array(
3204
+ "Normal",
3205
+ "Multiply",
3206
+ "Screen",
3207
+ "Overlay",
3208
+ "Darken",
3209
+ "Lighten",
3210
+ "ColorDogde",
3211
+ "ColorBurn",
3212
+ "HardLight",
3213
+ "SoftLight",
3214
+ "Difference",
3215
+ "Exclusion"
3216
+ );
3217
+
3218
+ if (!in_array($mode, $blend_modes)) {
3219
+ $mode = "Normal";
3220
+ }
3221
+
3222
+ if ($mode === $this->currentFillTransparency["mode"] &&
3223
+ $opacity == $this->currentFillTransparency["opacity"]
3224
+ ) {
3225
+ return;
3226
+ }
3227
+
3228
+ $this->currentFillTransparency["mode"] = $mode;
3229
+ $this->currentFillTransparency["opacity"] = $opacity;
3230
+
3231
+ $options = array(
3232
+ "BM" => "/$mode",
3233
+ "ca" => (float)$opacity,
3234
+ );
3235
+
3236
+ $this->setGraphicsState($options);
3237
+ }
3238
+
3239
+ /**
3240
+ * draw a line from one set of coordinates to another
3241
+ *
3242
+ * @param $x1
3243
+ * @param $y1
3244
+ * @param $x2
3245
+ * @param $y2
3246
+ * @param bool $stroke
3247
+ */
3248
+ function line($x1, $y1, $x2, $y2, $stroke = true)
3249
+ {
3250
+ $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3251
+
3252
+ if ($stroke) {
3253
+ $this->addContent(' S');
3254
+ }
3255
+ }
3256
+
3257
+ /**
3258
+ * draw a bezier curve based on 4 control points
3259
+ *
3260
+ * @param $x0
3261
+ * @param $y0
3262
+ * @param $x1
3263
+ * @param $y1
3264
+ * @param $x2
3265
+ * @param $y2
3266
+ * @param $x3
3267
+ * @param $y3
3268
+ */
3269
+ function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3270
+ {
3271
+ // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3272
+ // as the control points for the curve.
3273
+ $this->addContent(
3274
+ sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3275
+ );
3276
+ }
3277
+
3278
+ /**
3279
+ * draw a part of an ellipse
3280
+ *
3281
+ * @param $x0
3282
+ * @param $y0
3283
+ * @param $astart
3284
+ * @param $afinish
3285
+ * @param $r1
3286
+ * @param int $r2
3287
+ * @param int $angle
3288
+ * @param int $nSeg
3289
+ */
3290
+ function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3291
+ {
3292
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3293
+ }
3294
+
3295
+ /**
3296
+ * draw a filled ellipse
3297
+ *
3298
+ * @param $x0
3299
+ * @param $y0
3300
+ * @param $r1
3301
+ * @param int $r2
3302
+ * @param int $angle
3303
+ * @param int $nSeg
3304
+ * @param int $astart
3305
+ * @param int $afinish
3306
+ */
3307
+ function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3308
+ {
3309
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3310
+ }
3311
+
3312
+ /**
3313
+ * @param $x
3314
+ * @param $y
3315
+ */
3316
+ function lineTo($x, $y)
3317
+ {
3318
+ $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3319
+ }
3320
+
3321
+ /**
3322
+ * @param $x
3323
+ * @param $y
3324
+ */
3325
+ function moveTo($x, $y)
3326
+ {
3327
+ $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3328
+ }
3329
+
3330
+ /**
3331
+ * draw a bezier curve based on 4 control points
3332
+ *
3333
+ * @param $x1
3334
+ * @param $y1
3335
+ * @param $x2
3336
+ * @param $y2
3337
+ * @param $x3
3338
+ * @param $y3
3339
+ */
3340
+ function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3341
+ {
3342
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3343
+ }
3344
+
3345
+ /**
3346
+ * draw a bezier curve based on 4 control points
3347
+ */ function quadTo($cpx, $cpy, $x, $y)
3348
+ {
3349
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3350
+ }
3351
+
3352
+ function closePath()
3353
+ {
3354
+ $this->addContent(' h');
3355
+ }
3356
+
3357
+ function endPath()
3358
+ {
3359
+ $this->addContent(' n');
3360
+ }
3361
+
3362
+ /**
3363
+ * draw an ellipse
3364
+ * note that the part and filled ellipse are just special cases of this function
3365
+ *
3366
+ * draws an ellipse in the current line style
3367
+ * centered at $x0,$y0, radii $r1,$r2
3368
+ * if $r2 is not set, then a circle is drawn
3369
+ * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3370
+ * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3371
+ * pretty crappy shape at 2, as we are approximating with bezier curves.
3372
+ *
3373
+ * @param $x0
3374
+ * @param $y0
3375
+ * @param $r1
3376
+ * @param int $r2
3377
+ * @param int $angle
3378
+ * @param int $nSeg
3379
+ * @param int $astart
3380
+ * @param int $afinish
3381
+ * @param bool $close
3382
+ * @param bool $fill
3383
+ * @param bool $stroke
3384
+ * @param bool $incomplete
3385
+ */
3386
+ function ellipse(
3387
+ $x0,
3388
+ $y0,
3389
+ $r1,
3390
+ $r2 = 0,
3391
+ $angle = 0,
3392
+ $nSeg = 8,
3393
+ $astart = 0,
3394
+ $afinish = 360,
3395
+ $close = true,
3396
+ $fill = false,
3397
+ $stroke = true,
3398
+ $incomplete = false
3399
+ ) {
3400
+ if ($r1 == 0) {
3401
+ return;
3402
+ }
3403
+
3404
+ if ($r2 == 0) {
3405
+ $r2 = $r1;
3406
+ }
3407
+
3408
+ if ($nSeg < 2) {
3409
+ $nSeg = 2;
3410
+ }
3411
+
3412
+ $astart = deg2rad((float)$astart);
3413
+ $afinish = deg2rad((float)$afinish);
3414
+ $totalAngle = $afinish - $astart;
3415
+
3416
+ $dt = $totalAngle / $nSeg;
3417
+ $dtm = $dt / 3;
3418
+
3419
+ if ($angle != 0) {
3420
+ $a = -1 * deg2rad((float)$angle);
3421
+
3422
+ $this->addContent(
3423
+ sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
3424
+ );
3425
+
3426
+ $x0 = 0;
3427
+ $y0 = 0;
3428
+ }
3429
+
3430
+ $t1 = $astart;
3431
+ $a0 = $x0 + $r1 * cos($t1);
3432
+ $b0 = $y0 + $r2 * sin($t1);
3433
+ $c0 = -$r1 * sin($t1);
3434
+ $d0 = $r2 * cos($t1);
3435
+
3436
+ if (!$incomplete) {
3437
+ $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
3438
+ }
3439
+
3440
+ for ($i = 1; $i <= $nSeg; $i++) {
3441
+ // draw this bit of the total curve
3442
+ $t1 = $i * $dt + $astart;
3443
+ $a1 = $x0 + $r1 * cos($t1);
3444
+ $b1 = $y0 + $r2 * sin($t1);
3445
+ $c1 = -$r1 * sin($t1);
3446
+ $d1 = $r2 * cos($t1);
3447
+
3448
+ $this->addContent(
3449
+ sprintf(
3450
+ "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
3451
+ ($a0 + $c0 * $dtm),
3452
+ ($b0 + $d0 * $dtm),
3453
+ ($a1 - $c1 * $dtm),
3454
+ ($b1 - $d1 * $dtm),
3455
+ $a1,
3456
+ $b1
3457
+ )
3458
+ );
3459
+
3460
+ $a0 = $a1;
3461
+ $b0 = $b1;
3462
+ $c0 = $c1;
3463
+ $d0 = $d1;
3464
+ }
3465
+
3466
+ if (!$incomplete) {
3467
+ if ($fill) {
3468
+ $this->addContent(' f');
3469
+ }
3470
+
3471
+ if ($stroke) {
3472
+ if ($close) {
3473
+ $this->addContent(' s'); // small 's' signifies closing the path as well
3474
+ } else {
3475
+ $this->addContent(' S');
3476
+ }
3477
+ }
3478
+ }
3479
+
3480
+ if ($angle != 0) {
3481
+ $this->addContent(' Q');
3482
+ }
3483
+ }
3484
+
3485
+ /**
3486
+ * this sets the line drawing style.
3487
+ * width, is the thickness of the line in user units
3488
+ * cap is the type of cap to put on the line, values can be 'butt','round','square'
3489
+ * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
3490
+ * end of the line.
3491
+ * join can be 'miter', 'round', 'bevel'
3492
+ * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
3493
+ * on and off dashes.
3494
+ * (2) represents 2 on, 2 off, 2 on , 2 off ...
3495
+ * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
3496
+ * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
3497
+ *
3498
+ * @param int $width
3499
+ * @param string $cap
3500
+ * @param string $join
3501
+ * @param string $dash
3502
+ * @param int $phase
3503
+ */
3504
+ function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
3505
+ {
3506
+ // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
3507
+ $string = '';
3508
+
3509
+ if ($width > 0) {
3510
+ $string .= "$width w";
3511
+ }
3512
+
3513
+ $ca = array('butt' => 0, 'round' => 1, 'square' => 2);
3514
+
3515
+ if (isset($ca[$cap])) {
3516
+ $string .= " $ca[$cap] J";
3517
+ }
3518
+
3519
+ $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
3520
+
3521
+ if (isset($ja[$join])) {
3522
+ $string .= " $ja[$join] j";
3523
+ }
3524
+
3525
+ if (is_array($dash)) {
3526
+ $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
3527
+ }
3528
+
3529
+ $this->currentLineStyle = $string;
3530
+ $this->addContent("\n$string");
3531
+ }
3532
+
3533
+ /**
3534
+ * draw a polygon, the syntax for this is similar to the GD polygon command
3535
+ *
3536
+ * @param $p
3537
+ * @param $np
3538
+ * @param bool $f
3539
+ */
3540
+ function polygon($p, $np, $f = false)
3541
+ {
3542
+ $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
3543
+
3544
+ for ($i = 2; $i < $np * 2; $i = $i + 2) {
3545
+ $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
3546
+ }
3547
+
3548
+ if ($f) {
3549
+ $this->addContent(' f');
3550
+ } else {
3551
+ $this->addContent(' S');
3552
+ }
3553
+ }
3554
+
3555
+ /**
3556
+ * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3557
+ * the coordinates of the upper-right corner
3558
+ *
3559
+ * @param $x1
3560
+ * @param $y1
3561
+ * @param $width
3562
+ * @param $height
3563
+ */
3564
+ function filledRectangle($x1, $y1, $width, $height)
3565
+ {
3566
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
3567
+ }
3568
+
3569
+ /**
3570
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3571
+ * the coordinates of the upper-right corner
3572
+ *
3573
+ * @param $x1
3574
+ * @param $y1
3575
+ * @param $width
3576
+ * @param $height
3577
+ */
3578
+ function rectangle($x1, $y1, $width, $height)
3579
+ {
3580
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
3581
+ }
3582
+
3583
+ /**
3584
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
3585
+ * the coordinates of the upper-right corner
3586
+ *
3587
+ * @param $x1
3588
+ * @param $y1
3589
+ * @param $width
3590
+ * @param $height
3591
+ */
3592
+ function rect($x1, $y1, $width, $height)
3593
+ {
3594
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
3595
+ }
3596
+
3597
+ function stroke()
3598
+ {
3599
+ $this->addContent("\nS");
3600
+ }
3601
+
3602
+ function fill()
3603
+ {
3604
+ $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
3605
+ }
3606
+
3607
+ function fillStroke()
3608
+ {
3609
+ $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
3610
+ }
3611
+
3612
+ /**
3613
+ * save the current graphic state
3614
+ */
3615
+ function save()
3616
+ {
3617
+ // we must reset the color cache or it will keep bad colors after clipping
3618
+ $this->currentColor = null;
3619
+ $this->currentStrokeColor = null;
3620
+ $this->addContent("\nq");
3621
+ }
3622
+
3623
+ /**
3624
+ * restore the last graphic state
3625
+ */
3626
+ function restore()
3627
+ {
3628
+ // we must reset the color cache or it will keep bad colors after clipping
3629
+ $this->currentColor = null;
3630
+ $this->currentStrokeColor = null;
3631
+ $this->addContent("\nQ");
3632
+ }
3633
+
3634
+ /**
3635
+ * draw a clipping rectangle, all the elements added after this will be clipped
3636
+ *
3637
+ * @param $x1
3638
+ * @param $y1
3639
+ * @param $width
3640
+ * @param $height
3641
+ */
3642
+ function clippingRectangle($x1, $y1, $width, $height)
3643
+ {
3644
+ $this->save();
3645
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
3646
+ }
3647
+
3648
+ /**
3649
+ * draw a clipping rounded rectangle, all the elements added after this will be clipped
3650
+ *
3651
+ * @param $x1
3652
+ * @param $y1
3653
+ * @param $w
3654
+ * @param $h
3655
+ * @param $rTL
3656
+ * @param $rTR
3657
+ * @param $rBR
3658
+ * @param $rBL
3659
+ */
3660
+ function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
3661
+ {
3662
+ $this->save();
3663
+
3664
+ // start: top edge, left end
3665
+ $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
3666
+
3667
+ // line: bottom edge, left end
3668
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
3669
+
3670
+ // curve: bottom-left corner
3671
+ $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
3672
+
3673
+ // line: right edge, bottom end
3674
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
3675
+
3676
+ // curve: bottom-right corner
3677
+ $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
3678
+
3679
+ // line: right edge, top end
3680
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
3681
+
3682
+ // curve: bottom-right corner
3683
+ $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
3684
+
3685
+ // line: bottom edge, right end
3686
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
3687
+
3688
+ // curve: top-right corner
3689
+ $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
3690
+
3691
+ // line: top edge, left end
3692
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
3693
+
3694
+ // Close & clip
3695
+ $this->addContent(" W n");
3696
+ }
3697
+
3698
+ /**
3699
+ * ends the last clipping shape
3700
+ */
3701
+ function clippingEnd()
3702
+ {
3703
+ $this->restore();
3704
+ }
3705
+
3706
+ /**
3707
+ * scale
3708
+ *
3709
+ * @param float $s_x scaling factor for width as percent
3710
+ * @param float $s_y scaling factor for height as percent
3711
+ * @param float $x Origin abscissa
3712
+ * @param float $y Origin ordinate
3713
+ */
3714
+ function scale($s_x, $s_y, $x, $y)
3715
+ {
3716
+ $y = $this->currentPageSize["height"] - $y;
3717
+
3718
+ $tm = array(
3719
+ $s_x,
3720
+ 0,
3721
+ 0,
3722
+ $s_y,
3723
+ $x * (1 - $s_x),
3724
+ $y * (1 - $s_y)
3725
+ );
3726
+
3727
+ $this->transform($tm);
3728
+ }
3729
+
3730
+ /**
3731
+ * translate
3732
+ *
3733
+ * @param float $t_x movement to the right
3734
+ * @param float $t_y movement to the bottom
3735
+ */
3736
+ function translate($t_x, $t_y)
3737
+ {
3738
+ $tm = array(
3739
+ 1,
3740
+ 0,
3741
+ 0,
3742
+ 1,
3743
+ $t_x,
3744
+ -$t_y
3745
+ );
3746
+
3747
+ $this->transform($tm);
3748
+ }
3749
+
3750
+ /**
3751
+ * rotate
3752
+ *
3753
+ * @param float $angle angle in degrees for counter-clockwise rotation
3754
+ * @param float $x Origin abscissa
3755
+ * @param float $y Origin ordinate
3756
+ */
3757
+ function rotate($angle, $x, $y)
3758
+ {
3759
+ $y = $this->currentPageSize["height"] - $y;
3760
+
3761
+ $a = deg2rad($angle);
3762
+ $cos_a = cos($a);
3763
+ $sin_a = sin($a);
3764
+
3765
+ $tm = array(
3766
+ $cos_a,
3767
+ -$sin_a,
3768
+ $sin_a,
3769
+ $cos_a,
3770
+ $x - $sin_a * $y - $cos_a * $x,
3771
+ $y - $cos_a * $y + $sin_a * $x,
3772
+ );
3773
+
3774
+ $this->transform($tm);
3775
+ }
3776
+
3777
+ /**
3778
+ * skew
3779
+ *
3780
+ * @param float $angle_x
3781
+ * @param float $angle_y
3782
+ * @param float $x Origin abscissa
3783
+ * @param float $y Origin ordinate
3784
+ */
3785
+ function skew($angle_x, $angle_y, $x, $y)
3786
+ {
3787
+ $y = $this->currentPageSize["height"] - $y;
3788
+
3789
+ $tan_x = tan(deg2rad($angle_x));
3790
+ $tan_y = tan(deg2rad($angle_y));
3791
+
3792
+ $tm = array(
3793
+ 1,
3794
+ -$tan_y,
3795
+ -$tan_x,
3796
+ 1,
3797
+ $tan_x * $y,
3798
+ $tan_y * $x,
3799
+ );
3800
+
3801
+ $this->transform($tm);
3802
+ }
3803
+
3804
+ /**
3805
+ * apply graphic transformations
3806
+ *
3807
+ * @param array $tm transformation matrix
3808
+ */
3809
+ function transform($tm)
3810
+ {
3811
+ $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
3812
+ }
3813
+
3814
+ /**
3815
+ * add a new page to the document
3816
+ * this also makes the new page the current active object
3817
+ *
3818
+ * @param int $insert
3819
+ * @param int $id
3820
+ * @param string $pos
3821
+ * @return int
3822
+ */
3823
+ function newPage($insert = 0, $id = 0, $pos = 'after')
3824
+ {
3825
+ // if there is a state saved, then go up the stack closing them
3826
+ // then on the new page, re-open them with the right setings
3827
+
3828
+ if ($this->nStateStack) {
3829
+ for ($i = $this->nStateStack; $i >= 1; $i--) {
3830
+ $this->restoreState($i);
3831
+ }
3832
+ }
3833
+
3834
+ $this->numObj++;
3835
+
3836
+ if ($insert) {
3837
+ // the id from the ezPdf class is the id of the contents of the page, not the page object itself
3838
+ // query that object to find the parent
3839
+ $rid = $this->objects[$id]['onPage'];
3840
+ $opt = array('rid' => $rid, 'pos' => $pos);
3841
+ $this->o_page($this->numObj, 'new', $opt);
3842
+ } else {
3843
+ $this->o_page($this->numObj, 'new');
3844
+ }
3845
+
3846
+ // if there is a stack saved, then put that onto the page
3847
+ if ($this->nStateStack) {
3848
+ for ($i = 1; $i <= $this->nStateStack; $i++) {
3849
+ $this->saveState($i);
3850
+ }
3851
+ }
3852
+
3853
+ // and if there has been a stroke or fill color set, then transfer them
3854
+ if (isset($this->currentColor)) {
3855
+ $this->setColor($this->currentColor, true);
3856
+ }
3857
+
3858
+ if (isset($this->currentStrokeColor)) {
3859
+ $this->setStrokeColor($this->currentStrokeColor, true);
3860
+ }
3861
+
3862
+ // if there is a line style set, then put this in too
3863
+ if (mb_strlen($this->currentLineStyle, '8bit')) {
3864
+ $this->addContent("\n$this->currentLineStyle");
3865
+ }
3866
+
3867
+ // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
3868
+ return $this->currentContents;
3869
+ }
3870
+
3871
+ /**
3872
+ * Streams the PDF to the client.
3873
+ *
3874
+ * @param string $filename The filename to present to the client.
3875
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
3876
+ */
3877
+ function stream($filename = "document.pdf", $options = array())
3878
+ {
3879
+ if (headers_sent()) {
3880
+ die("Unable to stream pdf: headers already sent");
3881
+ }
3882
+
3883
+ if (!isset($options["compress"])) $options["compress"] = true;
3884
+ if (!isset($options["Attachment"])) $options["Attachment"] = true;
3885
+
3886
+ $debug = !$options['compress'];
3887
+ $tmp = ltrim($this->output($debug));
3888
+
3889
+ header("Cache-Control: private");
3890
+ header("Content-Type: application/pdf");
3891
+ header("Content-Length: " . mb_strlen($tmp, "8bit"));
3892
+
3893
+ $filename = str_replace(array("\n", "'"), "", basename($filename, ".pdf")) . ".pdf";
3894
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
3895
+
3896
+ $encoding = mb_detect_encoding($filename);
3897
+ $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
3898
+ $fallbackfilename = str_replace("\"", "", $fallbackfilename);
3899
+ $encodedfilename = rawurlencode($filename);
3900
+
3901
+ $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
3902
+ if ($fallbackfilename !== $filename) {
3903
+ $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
3904
+ }
3905
+ header($contentDisposition);
3906
+
3907
+ echo $tmp;
3908
+ flush();
3909
+ }
3910
+
3911
+ /**
3912
+ * return the height in units of the current font in the given size
3913
+ *
3914
+ * @param $size
3915
+ * @return float|int
3916
+ */
3917
+ function getFontHeight($size)
3918
+ {
3919
+ if (!$this->numFonts) {
3920
+ $this->selectFont($this->defaultFont);
3921
+ }
3922
+
3923
+ $font = $this->fonts[$this->currentFont];
3924
+
3925
+ // for the current font, and the given size, what is the height of the font in user units
3926
+ if (isset($font['Ascender']) && isset($font['Descender'])) {
3927
+ $h = $font['Ascender'] - $font['Descender'];
3928
+ } else {
3929
+ $h = $font['FontBBox'][3] - $font['FontBBox'][1];
3930
+ }
3931
+
3932
+ // have to adjust by a font offset for Windows fonts. unfortunately it looks like
3933
+ // the bounding box calculations are wrong and I don't know why.
3934
+ if (isset($font['FontHeightOffset'])) {
3935
+ // For CourierNew from Windows this needs to be -646 to match the
3936
+ // Adobe native Courier font.
3937
+ //
3938
+ // For FreeMono from GNU this needs to be -337 to match the
3939
+ // Courier font.
3940
+ //
3941
+ // Both have been added manually to the .afm and .ufm files.
3942
+ $h += (int)$font['FontHeightOffset'];
3943
+ }
3944
+
3945
+ return $size * $h / 1000;
3946
+ }
3947
+
3948
+ /**
3949
+ * @param $size
3950
+ * @return float|int
3951
+ */
3952
+ function getFontXHeight($size)
3953
+ {
3954
+ if (!$this->numFonts) {
3955
+ $this->selectFont($this->defaultFont);
3956
+ }
3957
+
3958
+ $font = $this->fonts[$this->currentFont];
3959
+
3960
+ // for the current font, and the given size, what is the height of the font in user units
3961
+ if (isset($font['XHeight'])) {
3962
+ $xh = $font['Ascender'] - $font['Descender'];
3963
+ } else {
3964
+ $xh = $this->getFontHeight($size) / 2;
3965
+ }
3966
+
3967
+ return $size * $xh / 1000;
3968
+ }
3969
+
3970
+ /**
3971
+ * return the font descender, this will normally return a negative number
3972
+ * if you add this number to the baseline, you get the level of the bottom of the font
3973
+ * it is in the pdf user units
3974
+ *
3975
+ * @param $size
3976
+ * @return float|int
3977
+ */
3978
+ function getFontDescender($size)
3979
+ {
3980
+ // note that this will most likely return a negative value
3981
+ if (!$this->numFonts) {
3982
+ $this->selectFont($this->defaultFont);
3983
+ }
3984
+
3985
+ //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
3986
+ $h = $this->fonts[$this->currentFont]['Descender'];
3987
+
3988
+ return $size * $h / 1000;
3989
+ }
3990
+
3991
+ /**
3992
+ * filter the text, this is applied to all text just before being inserted into the pdf document
3993
+ * it escapes the various things that need to be escaped, and so on
3994
+ *
3995
+ * @access private
3996
+ *
3997
+ * @param $text
3998
+ * @param bool $bom
3999
+ * @param bool $convert_encoding
4000
+ * @return string
4001
+ */
4002
+ function filterText($text, $bom = true, $convert_encoding = true)
4003
+ {
4004
+ if (!$this->numFonts) {
4005
+ $this->selectFont($this->defaultFont);
4006
+ }
4007
+
4008
+ if ($convert_encoding) {
4009
+ $cf = $this->currentFont;
4010
+ if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4011
+ $text = $this->utf8toUtf16BE($text, $bom);
4012
+ } else {
4013
+ //$text = html_entity_decode($text, ENT_QUOTES);
4014
+ $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4015
+ }
4016
+ } else if ($bom) {
4017
+ $text = $this->utf8toUtf16BE($text, $bom);
4018
+ }
4019
+
4020
+ // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4021
+ return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
4022
+ }
4023
+
4024
+ /**
4025
+ * return array containing codepoints (UTF-8 character values) for the
4026
+ * string passed in.
4027
+ *
4028
+ * based on the excellent TCPDF code by Nicola Asuni and the
4029
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4030
+ *
4031
+ * @access private
4032
+ * @author Orion Richardson
4033
+ * @since January 5, 2008
4034
+ *
4035
+ * @param string $text UTF-8 string to process
4036
+ *
4037
+ * @return array UTF-8 codepoints array for the string
4038
+ */
4039
+ function utf8toCodePointsArray(&$text)
4040
+ {
4041
+ $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4042
+ $unicode = array(); // array containing unicode values
4043
+ $bytes = array(); // array containing single character byte sequences
4044
+ $numbytes = 1; // number of octets needed to represent the UTF-8 character
4045
+
4046
+ for ($i = 0; $i < $length; $i++) {
4047
+ $c = ord($text[$i]); // get one string character at time
4048
+ if (count($bytes) === 0) { // get starting octect
4049
+ if ($c <= 0x7F) {
4050
+ $unicode[] = $c; // use the character "as is" because is ASCII
4051
+ $numbytes = 1;
4052
+ } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4053
+ $bytes[] = ($c - 0xC0) << 0x06;
4054
+ $numbytes = 2;
4055
+ } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4056
+ $bytes[] = ($c - 0xE0) << 0x0C;
4057
+ $numbytes = 3;
4058
+ } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4059
+ $bytes[] = ($c - 0xF0) << 0x12;
4060
+ $numbytes = 4;
4061
+ } else {
4062
+ // use replacement character for other invalid sequences
4063
+ $unicode[] = 0xFFFD;
4064
+ $bytes = array();
4065
+ $numbytes = 1;
4066
+ }
4067
+ } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4068
+ $bytes[] = $c - 0x80;
4069
+ if (count($bytes) === $numbytes) {
4070
+ // compose UTF-8 bytes to a single unicode value
4071
+ $c = $bytes[0];
4072
+ for ($j = 1; $j < $numbytes; $j++) {
4073
+ $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4074
+ }
4075
+ if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) {
4076
+ // The definition of UTF-8 prohibits encoding character numbers between
4077
+ // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4078
+ // encoding form (as surrogate pairs) and do not directly represent
4079
+ // characters.
4080
+ $unicode[] = 0xFFFD; // use replacement character
4081
+ } else {
4082
+ $unicode[] = $c; // add char to array
4083
+ }
4084
+ // reset data for next char
4085
+ $bytes = array();
4086
+ $numbytes = 1;
4087
+ }
4088
+ } else {
4089
+ // use replacement character for other invalid sequences
4090
+ $unicode[] = 0xFFFD;
4091
+ $bytes = array();
4092
+ $numbytes = 1;
4093
+ }
4094
+ }
4095
+
4096
+ return $unicode;
4097
+ }
4098
+
4099
+ /**
4100
+ * convert UTF-8 to UTF-16 with an additional byte order marker
4101
+ * at the front if required.
4102
+ *
4103
+ * based on the excellent TCPDF code by Nicola Asuni and the
4104
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4105
+ *
4106
+ * @access private
4107
+ * @author Orion Richardson
4108
+ * @since January 5, 2008
4109
+ *
4110
+ * @param string $text UTF-8 string to process
4111
+ * @param boolean $bom whether to add the byte order marker
4112
+ *
4113
+ * @return string UTF-16 result string
4114
+ */
4115
+ function utf8toUtf16BE(&$text, $bom = true)
4116
+ {
4117
+ $out = $bom ? "\xFE\xFF" : '';
4118
+
4119
+ $unicode = $this->utf8toCodePointsArray($text);
4120
+ foreach ($unicode as $c) {
4121
+ if ($c === 0xFFFD) {
4122
+ $out .= "\xFF\xFD"; // replacement character
4123
+ } elseif ($c < 0x10000) {
4124
+ $out .= chr($c >> 0x08) . chr($c & 0xFF);
4125
+ } else {
4126
+ $c -= 0x10000;
4127
+ $w1 = 0xD800 | ($c >> 0x10);
4128
+ $w2 = 0xDC00 | ($c & 0x3FF);
4129
+ $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4130
+ }
4131
+ }
4132
+
4133
+ return $out;
4134
+ }
4135
+
4136
+ /**
4137
+ * given a start position and information about how text is to be laid out, calculate where
4138
+ * on the page the text will end
4139
+ *
4140
+ * @param $x
4141
+ * @param $y
4142
+ * @param $angle
4143
+ * @param $size
4144
+ * @param $wa
4145
+ * @param $text
4146
+ * @return array
4147
+ */
4148
+ private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4149
+ {
4150
+ // given this information return an array containing x and y for the end position as elements 0 and 1
4151
+ $w = $this->getTextWidth($size, $text);
4152
+
4153
+ // need to adjust for the number of spaces in this text
4154
+ $words = explode(' ', $text);
4155
+ $nspaces = count($words) - 1;
4156
+ $w += $wa * $nspaces;
4157
+ $a = deg2rad((float)$angle);
4158
+
4159
+ return array(cos($a) * $w + $x, -sin($a) * $w + $y);
4160
+ }
4161
+
4162
+ /**
4163
+ * Callback method used by smallCaps
4164
+ *
4165
+ * @param array $matches
4166
+ *
4167
+ * @return string
4168
+ */
4169
+ function toUpper($matches)
4170
+ {
4171
+ return mb_strtoupper($matches[0]);
4172
+ }
4173
+
4174
+ function concatMatches($matches)
4175
+ {
4176
+ $str = "";
4177
+ foreach ($matches as $match) {
4178
+ $str .= $match[0];
4179
+ }
4180
+
4181
+ return $str;
4182
+ }
4183
+
4184
+ /**
4185
+ * register text for font subsetting
4186
+ *
4187
+ * @param $font
4188
+ * @param $text
4189
+ */
4190
+ function registerText($font, $text)
4191
+ {
4192
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4193
+ return;
4194
+ }
4195
+
4196
+ if (!isset($this->stringSubsets[$font])) {
4197
+ $this->stringSubsets[$font] = array();
4198
+ }
4199
+
4200
+ $this->stringSubsets[$font] = array_unique(
4201
+ array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4202
+ );
4203
+ }
4204
+
4205
+ /**
4206
+ * add text to the document, at a specified location, size and angle on the page
4207
+ *
4208
+ * @param $x
4209
+ * @param $y
4210
+ * @param $size
4211
+ * @param $text
4212
+ * @param int $angle
4213
+ * @param int $wordSpaceAdjust
4214
+ * @param int $charSpaceAdjust
4215
+ * @param bool $smallCaps
4216
+ */
4217
+ function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4218
+ {
4219
+ if (!$this->numFonts) {
4220
+ $this->selectFont($this->defaultFont);
4221
+ }
4222
+
4223
+ $text = str_replace(array("\r", "\n"), "", $text);
4224
+
4225
+ if ($smallCaps) {
4226
+ preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4227
+ $lower = $this->concatMatches($matches);
4228
+ d($lower);
4229
+
4230
+ preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4231
+ $other = $this->concatMatches($matches);
4232
+ d($other);
4233
+
4234
+ //$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4235
+ }
4236
+
4237
+ // if there are any open callbacks, then they should be called, to show the start of the line
4238
+ if ($this->nCallback > 0) {
4239
+ for ($i = $this->nCallback; $i > 0; $i--) {
4240
+ // call each function
4241
+ $info = array(
4242
+ 'x' => $x,
4243
+ 'y' => $y,
4244
+ 'angle' => $angle,
4245
+ 'status' => 'sol',
4246
+ 'p' => $this->callback[$i]['p'],
4247
+ 'nCallback' => $this->callback[$i]['nCallback'],
4248
+ 'height' => $this->callback[$i]['height'],
4249
+ 'descender' => $this->callback[$i]['descender']
4250
+ );
4251
+
4252
+ $func = $this->callback[$i]['f'];
4253
+ $this->$func($info);
4254
+ }
4255
+ }
4256
+
4257
+ if ($angle == 0) {
4258
+ $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
4259
+ } else {
4260
+ $a = deg2rad((float)$angle);
4261
+ $this->addContent(
4262
+ sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
4263
+ );
4264
+ }
4265
+
4266
+ if ($wordSpaceAdjust != 0) {
4267
+ $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
4268
+ }
4269
+
4270
+ if ($charSpaceAdjust != 0) {
4271
+ $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
4272
+ }
4273
+
4274
+ $len = mb_strlen($text);
4275
+ $start = 0;
4276
+
4277
+ if ($start < $len) {
4278
+ $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start);
4279
+ $place_text = $this->filterText($part, false);
4280
+ // modify unicode text so that extra word spacing is manually implemented (bug #)
4281
+ $cf = $this->currentFont;
4282
+ if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) {
4283
+ $space_scale = 1000 / $size;
4284
+ $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
4285
+ }
4286
+ $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
4287
+ $this->addContent(" [($place_text)] TJ");
4288
+ }
4289
+
4290
+ if ($wordSpaceAdjust != 0) {
4291
+ $this->addContent(sprintf(" %.3F Tw", 0));
4292
+ }
4293
+
4294
+ if ($charSpaceAdjust != 0) {
4295
+ $this->addContent(sprintf(" %.3F Tc", 0));
4296
+ }
4297
+
4298
+ $this->addContent(' ET');
4299
+
4300
+ // if there are any open callbacks, then they should be called, to show the end of the line
4301
+ if ($this->nCallback > 0) {
4302
+ for ($i = $this->nCallback; $i > 0; $i--) {
4303
+ // call each function
4304
+ $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
4305
+ $info = array(
4306
+ 'x' => $tmp[0],
4307
+ 'y' => $tmp[1],
4308
+ 'angle' => $angle,
4309
+ 'status' => 'eol',
4310
+ 'p' => $this->callback[$i]['p'],
4311
+ 'nCallback' => $this->callback[$i]['nCallback'],
4312
+ 'height' => $this->callback[$i]['height'],
4313
+ 'descender' => $this->callback[$i]['descender']
4314
+ );
4315
+ $func = $this->callback[$i]['f'];
4316
+ $this->$func($info);
4317
+ }
4318
+ }
4319
+ }
4320
+
4321
+ /**
4322
+ * calculate how wide a given text string will be on a page, at a given size.
4323
+ * this can be called externally, but is also used by the other class functions
4324
+ *
4325
+ * @param $size
4326
+ * @param $text
4327
+ * @param int $word_spacing
4328
+ * @param int $char_spacing
4329
+ * @return float|int
4330
+ */
4331
+ function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
4332
+ {
4333
+ static $ord_cache = array();
4334
+
4335
+ // this function should not change any of the settings, though it will need to
4336
+ // track any directives which change during calculation, so copy them at the start
4337
+ // and put them back at the end.
4338
+ $store_currentTextState = $this->currentTextState;
4339
+
4340
+ if (!$this->numFonts) {
4341
+ $this->selectFont($this->defaultFont);
4342
+ }
4343
+
4344
+ $text = str_replace(array("\r", "\n"), "", $text);
4345
+
4346
+ // converts a number or a float to a string so it can get the width
4347
+ $text = "$text";
4348
+
4349
+ // hmm, this is where it all starts to get tricky - use the font information to
4350
+ // calculate the width of each character, add them up and convert to user units
4351
+ $w = 0;
4352
+ $cf = $this->currentFont;
4353
+ $current_font = $this->fonts[$cf];
4354
+ $space_scale = 1000 / ($size > 0 ? $size : 1);
4355
+ $n_spaces = 0;
4356
+
4357
+ if ($current_font['isUnicode']) {
4358
+ // for Unicode, use the code points array to calculate width rather
4359
+ // than just the string itself
4360
+ $unicode = $this->utf8toCodePointsArray($text);
4361
+
4362
+ foreach ($unicode as $char) {
4363
+ // check if we have to replace character
4364
+ if (isset($current_font['differences'][$char])) {
4365
+ $char = $current_font['differences'][$char];
4366
+ }
4367
+
4368
+ if (isset($current_font['C'][$char])) {
4369
+ $char_width = $current_font['C'][$char];
4370
+
4371
+ // add the character width
4372
+ $w += $char_width;
4373
+
4374
+ // add additional padding for space
4375
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4376
+ $w += $word_spacing * $space_scale;
4377
+ $n_spaces++;
4378
+ }
4379
+ }
4380
+ }
4381
+
4382
+ // add additional char spacing
4383
+ if ($char_spacing != 0) {
4384
+ $w += $char_spacing * $space_scale * (count($unicode) + $n_spaces);
4385
+ }
4386
+
4387
+ } else {
4388
+ // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
4389
+ if ($this->isUnicode) {
4390
+ $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
4391
+ }
4392
+
4393
+ $len = mb_strlen($text, 'Windows-1252');
4394
+
4395
+ for ($i = 0; $i < $len; $i++) {
4396
+ $c = $text[$i];
4397
+ $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
4398
+
4399
+ // check if we have to replace character
4400
+ if (isset($current_font['differences'][$char])) {
4401
+ $char = $current_font['differences'][$char];
4402
+ }
4403
+
4404
+ if (isset($current_font['C'][$char])) {
4405
+ $char_width = $current_font['C'][$char];
4406
+
4407
+ // add the character width
4408
+ $w += $char_width;
4409
+
4410
+ // add additional padding for space
4411
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
4412
+ $w += $word_spacing * $space_scale;
4413
+ $n_spaces++;
4414
+ }
4415
+ }
4416
+ }
4417
+
4418
+ // add additional char spacing
4419
+ if ($char_spacing != 0) {
4420
+ $w += $char_spacing * $space_scale * ($len + $n_spaces);
4421
+ }
4422
+ }
4423
+
4424
+ $this->currentTextState = $store_currentTextState;
4425
+ $this->setCurrentFont();
4426
+
4427
+ return $w * $size / 1000;
4428
+ }
4429
+
4430
+ /**
4431
+ * this will be called at a new page to return the state to what it was on the
4432
+ * end of the previous page, before the stack was closed down
4433
+ * This is to get around not being able to have open 'q' across pages
4434
+ *
4435
+ * @param int $pageEnd
4436
+ */
4437
+ function saveState($pageEnd = 0)
4438
+ {
4439
+ if ($pageEnd) {
4440
+ // this will be called at a new page to return the state to what it was on the
4441
+ // end of the previous page, before the stack was closed down
4442
+ // This is to get around not being able to have open 'q' across pages
4443
+ $opt = $this->stateStack[$pageEnd];
4444
+ // ok to use this as stack starts numbering at 1
4445
+ $this->setColor($opt['col'], true);
4446
+ $this->setStrokeColor($opt['str'], true);
4447
+ $this->addContent("\n" . $opt['lin']);
4448
+ // $this->currentLineStyle = $opt['lin'];
4449
+ } else {
4450
+ $this->nStateStack++;
4451
+ $this->stateStack[$this->nStateStack] = array(
4452
+ 'col' => $this->currentColor,
4453
+ 'str' => $this->currentStrokeColor,
4454
+ 'lin' => $this->currentLineStyle
4455
+ );
4456
+ }
4457
+
4458
+ $this->save();
4459
+ }
4460
+
4461
+ /**
4462
+ * restore a previously saved state
4463
+ *
4464
+ * @param int $pageEnd
4465
+ */
4466
+ function restoreState($pageEnd = 0)
4467
+ {
4468
+ if (!$pageEnd) {
4469
+ $n = $this->nStateStack;
4470
+ $this->currentColor = $this->stateStack[$n]['col'];
4471
+ $this->currentStrokeColor = $this->stateStack[$n]['str'];
4472
+ $this->addContent("\n" . $this->stateStack[$n]['lin']);
4473
+ $this->currentLineStyle = $this->stateStack[$n]['lin'];
4474
+ $this->stateStack[$n] = null;
4475
+ unset($this->stateStack[$n]);
4476
+ $this->nStateStack--;
4477
+ }
4478
+
4479
+ $this->restore();
4480
+ }
4481
+
4482
+ /**
4483
+ * make a loose object, the output will go into this object, until it is closed, then will revert to
4484
+ * the current one.
4485
+ * this object will not appear until it is included within a page.
4486
+ * the function will return the object number
4487
+ *
4488
+ * @return int
4489
+ */
4490
+ function openObject()
4491
+ {
4492
+ $this->nStack++;
4493
+ $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4494
+ // add a new object of the content type, to hold the data flow
4495
+ $this->numObj++;
4496
+ $this->o_contents($this->numObj, 'new');
4497
+ $this->currentContents = $this->numObj;
4498
+ $this->looseObjects[$this->numObj] = 1;
4499
+
4500
+ return $this->numObj;
4501
+ }
4502
+
4503
+ /**
4504
+ * open an existing object for editing
4505
+ *
4506
+ * @param $id
4507
+ */
4508
+ function reopenObject($id)
4509
+ {
4510
+ $this->nStack++;
4511
+ $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
4512
+ $this->currentContents = $id;
4513
+
4514
+ // also if this object is the primary contents for a page, then set the current page to its parent
4515
+ if (isset($this->objects[$id]['onPage'])) {
4516
+ $this->currentPage = $this->objects[$id]['onPage'];
4517
+ }
4518
+ }
4519
+
4520
+ /**
4521
+ * close an object
4522
+ */
4523
+ function closeObject()
4524
+ {
4525
+ // close the object, as long as there was one open in the first place, which will be indicated by
4526
+ // an objectId on the stack.
4527
+ if ($this->nStack > 0) {
4528
+ $this->currentContents = $this->stack[$this->nStack]['c'];
4529
+ $this->currentPage = $this->stack[$this->nStack]['p'];
4530
+ $this->nStack--;
4531
+ // easier to probably not worry about removing the old entries, they will be overwritten
4532
+ // if there are new ones.
4533
+ }
4534
+ }
4535
+
4536
+ /**
4537
+ * stop an object from appearing on pages from this point on
4538
+ *
4539
+ * @param $id
4540
+ */
4541
+ function stopObject($id)
4542
+ {
4543
+ // if an object has been appearing on pages up to now, then stop it, this page will
4544
+ // be the last one that could contain it.
4545
+ if (isset($this->addLooseObjects[$id])) {
4546
+ $this->addLooseObjects[$id] = '';
4547
+ }
4548
+ }
4549
+
4550
+ /**
4551
+ * after an object has been created, it wil only show if it has been added, using this function.
4552
+ *
4553
+ * @param $id
4554
+ * @param string $options
4555
+ */
4556
+ function addObject($id, $options = 'add')
4557
+ {
4558
+ // add the specified object to the page
4559
+ if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
4560
+ // then it is a valid object, and it is not being added to itself
4561
+ switch ($options) {
4562
+ case 'all':
4563
+ // then this object is to be added to this page (done in the next block) and
4564
+ // all future new pages.
4565
+ $this->addLooseObjects[$id] = 'all';
4566
+
4567
+ case 'add':
4568
+ if (isset($this->objects[$this->currentContents]['onPage'])) {
4569
+ // then the destination contents is the primary for the page
4570
+ // (though this object is actually added to that page)
4571
+ $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
4572
+ }
4573
+ break;
4574
+
4575
+ case 'even':
4576
+ $this->addLooseObjects[$id] = 'even';
4577
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4578
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
4579
+ $this->addObject($id);
4580
+ // hacky huh :)
4581
+ }
4582
+ break;
4583
+
4584
+ case 'odd':
4585
+ $this->addLooseObjects[$id] = 'odd';
4586
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
4587
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
4588
+ $this->addObject($id);
4589
+ // hacky huh :)
4590
+ }
4591
+ break;
4592
+
4593
+ case 'next':
4594
+ $this->addLooseObjects[$id] = 'all';
4595
+ break;
4596
+
4597
+ case 'nexteven':
4598
+ $this->addLooseObjects[$id] = 'even';
4599
+ break;
4600
+
4601
+ case 'nextodd':
4602
+ $this->addLooseObjects[$id] = 'odd';
4603
+ break;
4604
+ }
4605
+ }
4606
+ }
4607
+
4608
+ /**
4609
+ * return a storable representation of a specific object
4610
+ *
4611
+ * @param $id
4612
+ * @return string|null
4613
+ */
4614
+ function serializeObject($id)
4615
+ {
4616
+ if (array_key_exists($id, $this->objects)) {
4617
+ return serialize($this->objects[$id]);
4618
+ }
4619
+
4620
+ return null;
4621
+ }
4622
+
4623
+ /**
4624
+ * restore an object from its stored representation. returns its new object id.
4625
+ *
4626
+ * @param $obj
4627
+ * @return int
4628
+ */
4629
+ function restoreSerializedObject($obj)
4630
+ {
4631
+ $obj_id = $this->openObject();
4632
+ $this->objects[$obj_id] = unserialize($obj);
4633
+ $this->closeObject();
4634
+
4635
+ return $obj_id;
4636
+ }
4637
+
4638
+ /**
4639
+ * add content to the documents info object
4640
+ *
4641
+ * @param $label
4642
+ * @param int $value
4643
+ */
4644
+ function addInfo($label, $value = 0)
4645
+ {
4646
+ // this will only work if the label is one of the valid ones.
4647
+ // modify this so that arrays can be passed as well.
4648
+ // if $label is an array then assume that it is key => value pairs
4649
+ // else assume that they are both scalar, anything else will probably error
4650
+ if (is_array($label)) {
4651
+ foreach ($label as $l => $v) {
4652
+ $this->o_info($this->infoObject, $l, $v);
4653
+ }
4654
+ } else {
4655
+ $this->o_info($this->infoObject, $label, $value);
4656
+ }
4657
+ }
4658
+
4659
+ /**
4660
+ * set the viewer preferences of the document, it is up to the browser to obey these.
4661
+ *
4662
+ * @param $label
4663
+ * @param int $value
4664
+ */
4665
+ function setPreferences($label, $value = 0)
4666
+ {
4667
+ // this will only work if the label is one of the valid ones.
4668
+ if (is_array($label)) {
4669
+ foreach ($label as $l => $v) {
4670
+ $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v));
4671
+ }
4672
+ } else {
4673
+ $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value));
4674
+ }
4675
+ }
4676
+
4677
+ /**
4678
+ * extract an integer from a position in a byte stream
4679
+ *
4680
+ * @param $data
4681
+ * @param $pos
4682
+ * @param $num
4683
+ * @return int
4684
+ */
4685
+ private function getBytes(&$data, $pos, $num)
4686
+ {
4687
+ // return the integer represented by $num bytes from $pos within $data
4688
+ $ret = 0;
4689
+ for ($i = 0; $i < $num; $i++) {
4690
+ $ret *= 256;
4691
+ $ret += ord($data[$pos + $i]);
4692
+ }
4693
+
4694
+ return $ret;
4695
+ }
4696
+
4697
+ /**
4698
+ * Check if image already added to pdf image directory.
4699
+ * If yes, need not to create again (pass empty data)
4700
+ *
4701
+ * @param $imgname
4702
+ * @return bool
4703
+ */
4704
+ function image_iscached($imgname)
4705
+ {
4706
+ return isset($this->imagelist[$imgname]);
4707
+ }
4708
+
4709
+ /**
4710
+ * add a PNG image into the document, from a GD object
4711
+ * this should work with remote files
4712
+ *
4713
+ * @param string $file The PNG file
4714
+ * @param float $x X position
4715
+ * @param float $y Y position
4716
+ * @param float $w Width
4717
+ * @param float $h Height
4718
+ * @param resource $img A GD resource
4719
+ * @param bool $is_mask true if the image is a mask
4720
+ * @param bool $mask true if the image is masked
4721
+ * @throws Exception
4722
+ */
4723
+ function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null)
4724
+ {
4725
+ if (!function_exists("imagepng")) {
4726
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
4727
+ }
4728
+
4729
+ //if already cached, need not to read again
4730
+ if (isset($this->imagelist[$file])) {
4731
+ $data = null;
4732
+ } else {
4733
+ // Example for transparency handling on new image. Retain for current image
4734
+ // $tIndex = imagecolortransparent($img);
4735
+ // if ($tIndex > 0) {
4736
+ // $tColor = imagecolorsforindex($img, $tIndex);
4737
+ // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
4738
+ // imagefill($new_img, 0, 0, $new_tIndex);
4739
+ // imagecolortransparent($new_img, $new_tIndex);
4740
+ // }
4741
+ // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
4742
+ //imagealphablending($img, true);
4743
+
4744
+ //default, but explicitely set to ensure pdf compatibility
4745
+ imagesavealpha($img, false/*!$is_mask && !$mask*/);
4746
+
4747
+ $error = 0;
4748
+ //DEBUG_IMG_TEMP
4749
+ //debugpng
4750
+ if (defined("DEBUGPNG") && DEBUGPNG) {
4751
+ print '[addImagePng ' . $file . ']';
4752
+ }
4753
+
4754
+ ob_start();
4755
+ @imagepng($img);
4756
+ $data = ob_get_clean();
4757
+
4758
+ if ($data == '') {
4759
+ $error = 1;
4760
+ $errormsg = 'trouble writing file from GD';
4761
+ //DEBUG_IMG_TEMP
4762
+ //debugpng
4763
+ if (defined("DEBUGPNG") && DEBUGPNG) {
4764
+ print 'trouble writing file from GD';
4765
+ }
4766
+ }
4767
+
4768
+ if ($error) {
4769
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
4770
+
4771
+ return;
4772
+ }
4773
+ } //End isset($this->imagelist[$file]) (png Duplicate removal)
4774
+
4775
+ $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask);
4776
+ }
4777
+
4778
+ /**
4779
+ * @param $file
4780
+ * @param $x
4781
+ * @param $y
4782
+ * @param $w
4783
+ * @param $h
4784
+ * @param $byte
4785
+ */
4786
+ protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
4787
+ {
4788
+ // generate images
4789
+ $img = imagecreatefrompng($file);
4790
+
4791
+ if ($img === false) {
4792
+ return;
4793
+ }
4794
+
4795
+ // FIXME The pixel transformation doesn't work well with 8bit PNGs
4796
+ $eight_bit = ($byte & 4) !== 4;
4797
+
4798
+ $wpx = imagesx($img);
4799
+ $hpx = imagesy($img);
4800
+
4801
+ imagesavealpha($img, false);
4802
+
4803
+ // create temp alpha file
4804
+ $tempfile_alpha = tempnam($this->tmp, "cpdf_img_");
4805
+ @unlink($tempfile_alpha);
4806
+ $tempfile_alpha = "$tempfile_alpha.png";
4807
+
4808
+ // create temp plain file
4809
+ $tempfile_plain = tempnam($this->tmp, "cpdf_img_");
4810
+ @unlink($tempfile_plain);
4811
+ $tempfile_plain = "$tempfile_plain.png";
4812
+
4813
+ $imgalpha = imagecreate($wpx, $hpx);
4814
+ imagesavealpha($imgalpha, false);
4815
+
4816
+ // generate gray scale palette (0 -> 255)
4817
+ for ($c = 0; $c < 256; ++$c) {
4818
+ imagecolorallocate($imgalpha, $c, $c, $c);
4819
+ }
4820
+
4821
+ // Use PECL gmagick + Graphics Magic to process transparent PNG images
4822
+ if (extension_loaded("gmagick")) {
4823
+ $gmagick = new \Gmagick($file);
4824
+ $gmagick->setimageformat('png');
4825
+
4826
+ // Get opacity channel (negative of alpha channel)
4827
+ $alpha_channel_neg = clone $gmagick;
4828
+ $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
4829
+
4830
+ // Negate opacity channel
4831
+ $alpha_channel = new \Gmagick();
4832
+ $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
4833
+ $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
4834
+ $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
4835
+ $alpha_channel->writeimage($tempfile_alpha);
4836
+
4837
+ // Cast to 8bit+palette
4838
+ $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4839
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4840
+ imagedestroy($imgalpha_);
4841
+ imagepng($imgalpha, $tempfile_alpha);
4842
+
4843
+ // Make opaque image
4844
+ $color_channels = new \Gmagick();
4845
+ $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
4846
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
4847
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
4848
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
4849
+ $color_channels->writeimage($tempfile_plain);
4850
+
4851
+ $imgplain = imagecreatefrompng($tempfile_plain);
4852
+ }
4853
+ // Use PECL imagick + ImageMagic to process transparent PNG images
4854
+ elseif (extension_loaded("imagick")) {
4855
+ // Native cloning was added to pecl-imagick in svn commit 263814
4856
+ // the first version containing it was 3.0.1RC1
4857
+ static $imagickClonable = null;
4858
+ if ($imagickClonable === null) {
4859
+ $imagickClonable = version_compare(Imagick::IMAGICK_EXTVER, '3.0.1rc1') > 0;
4860
+ }
4861
+
4862
+ $imagick = new \Imagick($file);
4863
+ $imagick->setFormat('png');
4864
+
4865
+ // Get opacity channel (negative of alpha channel)
4866
+ if ($imagick->getImageAlphaChannel() !== 0) {
4867
+ $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
4868
+ $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
4869
+ $alpha_channel->negateImage(true);
4870
+ $alpha_channel->writeImage($tempfile_alpha);
4871
+
4872
+ // Cast to 8bit+palette
4873
+ $imgalpha_ = imagecreatefrompng($tempfile_alpha);
4874
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
4875
+ imagedestroy($imgalpha_);
4876
+ imagepng($imgalpha, $tempfile_alpha);
4877
+ } else {
4878
+ $tempfile_alpha = null;
4879
+ }
4880
+
4881
+ // Make opaque image
4882
+ $color_channels = new \Imagick();
4883
+ $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
4884
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
4885
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
4886
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
4887
+ $color_channels->writeImage($tempfile_plain);
4888
+
4889
+ $imgplain = imagecreatefrompng($tempfile_plain);
4890
+ } else {
4891
+ // allocated colors cache
4892
+ $allocated_colors = array();
4893
+
4894
+ // extract alpha channel
4895
+ for ($xpx = 0; $xpx < $wpx; ++$xpx) {
4896
+ for ($ypx = 0; $ypx < $hpx; ++$ypx) {
4897
+ $color = imagecolorat($img, $xpx, $ypx);
4898
+ $col = imagecolorsforindex($img, $color);
4899
+ $alpha = $col['alpha'];
4900
+
4901
+ if ($eight_bit) {
4902
+ // with gamma correction
4903
+ $gammacorr = 2.2;
4904
+ $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255;
4905
+ } else {
4906
+ // without gamma correction
4907
+ $pixel = (127 - $alpha) * 2;
4908
+
4909
+ $key = $col['red'] . $col['green'] . $col['blue'];
4910
+
4911
+ if (!isset($allocated_colors[$key])) {
4912
+ $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
4913
+ $allocated_colors[$key] = $pixel_img;
4914
+ } else {
4915
+ $pixel_img = $allocated_colors[$key];
4916
+ }
4917
+
4918
+ imagesetpixel($img, $xpx, $ypx, $pixel_img);
4919
+ }
4920
+
4921
+ imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
4922
+ }
4923
+ }
4924
+
4925
+ // extract image without alpha channel
4926
+ $imgplain = imagecreatetruecolor($wpx, $hpx);
4927
+ imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
4928
+ imagedestroy($img);
4929
+
4930
+ imagepng($imgalpha, $tempfile_alpha);
4931
+ imagepng($imgplain, $tempfile_plain);
4932
+ }
4933
+
4934
+ // embed mask image
4935
+ if ($tempfile_alpha) {
4936
+ $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true);
4937
+ imagedestroy($imgalpha);
4938
+ }
4939
+
4940
+ // embed image, masked with previously embedded mask
4941
+ $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, ($tempfile_alpha !== null));
4942
+ imagedestroy($imgplain);
4943
+
4944
+ // remove temp files
4945
+ if ($tempfile_alpha) {
4946
+ unlink($tempfile_alpha);
4947
+ }
4948
+ unlink($tempfile_plain);
4949
+ }
4950
+
4951
+ /**
4952
+ * add a PNG image into the document, from a file
4953
+ * this should work with remote files
4954
+ *
4955
+ * @param $file
4956
+ * @param $x
4957
+ * @param $y
4958
+ * @param int $w
4959
+ * @param int $h
4960
+ * @throws Exception
4961
+ */
4962
+ function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
4963
+ {
4964
+ if (!function_exists("imagecreatefrompng")) {
4965
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
4966
+ }
4967
+
4968
+ //if already cached, need not to read again
4969
+ if (isset($this->imagelist[$file])) {
4970
+ $img = null;
4971
+ } else {
4972
+ $info = file_get_contents($file, false, null, 24, 5);
4973
+ $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
4974
+ $bit_depth = $meta["bitDepth"];
4975
+ $color_type = $meta["colorType"];
4976
+
4977
+ // http://www.w3.org/TR/PNG/#11IHDR
4978
+ // 3 => indexed
4979
+ // 4 => greyscale with alpha
4980
+ // 6 => fullcolor with alpha
4981
+ $is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4);
4982
+
4983
+ if ($is_alpha) { // exclude grayscale alpha
4984
+ $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
4985
+ return;
4986
+ }
4987
+
4988
+ //png files typically contain an alpha channel.
4989
+ //pdf file format or class.pdf does not support alpha blending.
4990
+ //on alpha blended images, more transparent areas have a color near black.
4991
+ //This appears in the result on not storing the alpha channel.
4992
+ //Correct would be the box background image or its parent when transparent.
4993
+ //But this would make the image dependent on the background.
4994
+ //Therefore create an image with white background and copy in
4995
+ //A more natural background than black is white.
4996
+ //Therefore create an empty image with white background and merge the
4997
+ //image in with alpha blending.
4998
+ $imgtmp = @imagecreatefrompng($file);
4999
+ if (!$imgtmp) {
5000
+ return;
5001
+ }
5002
+ $sx = imagesx($imgtmp);
5003
+ $sy = imagesy($imgtmp);
5004
+ $img = imagecreatetruecolor($sx, $sy);
5005
+ imagealphablending($img, true);
5006
+
5007
+ // @todo is it still needed ??
5008
+ $ti = imagecolortransparent($imgtmp);
5009
+ if ($ti >= 0) {
5010
+ $tc = imagecolorsforindex($imgtmp, $ti);
5011
+ $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5012
+ imagefill($img, 0, 0, $ti);
5013
+ imagecolortransparent($img, $ti);
5014
+ } else {
5015
+ imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5016
+ }
5017
+
5018
+ imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5019
+ imagedestroy($imgtmp);
5020
+ }
5021
+ $this->addImagePng($file, $x, $y, $w, $h, $img);
5022
+
5023
+ if ($img) {
5024
+ imagedestroy($img);
5025
+ }
5026
+ }
5027
+
5028
+ /**
5029
+ * add a PNG image into the document, from a file
5030
+ * this should work with remote files
5031
+ *
5032
+ * @param $file
5033
+ * @param $x
5034
+ * @param $y
5035
+ * @param int $w
5036
+ * @param int $h
5037
+ */
5038
+ function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
5039
+ {
5040
+ $doc = new \Svg\Document();
5041
+ $doc->loadFile($file);
5042
+ $dimensions = $doc->getDimensions();
5043
+
5044
+ $this->save();
5045
+
5046
+ $this->transform(array($w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y));
5047
+
5048
+ $surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
5049
+ $doc->render($surface);
5050
+
5051
+ $this->restore();
5052
+ }
5053
+
5054
+ /**
5055
+ * add a PNG image into the document, from a memory buffer of the file
5056
+ *
5057
+ * @param $file
5058
+ * @param $x
5059
+ * @param $y
5060
+ * @param float $w
5061
+ * @param float $h
5062
+ * @param $data
5063
+ * @param bool $is_mask
5064
+ * @param null $mask
5065
+ */
5066
+ function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null)
5067
+ {
5068
+ if (isset($this->imagelist[$file])) {
5069
+ $data = null;
5070
+ $info['width'] = $this->imagelist[$file]['w'];
5071
+ $info['height'] = $this->imagelist[$file]['h'];
5072
+ $label = $this->imagelist[$file]['label'];
5073
+ } else {
5074
+ if ($data == null) {
5075
+ $this->addMessage('addPngFromBuf error - data not present!');
5076
+
5077
+ return;
5078
+ }
5079
+
5080
+ $error = 0;
5081
+
5082
+ if (!$error) {
5083
+ $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5084
+
5085
+ if (mb_substr($data, 0, 8, '8bit') != $header) {
5086
+ $error = 1;
5087
+
5088
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5089
+ print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5090
+ }
5091
+
5092
+ $errormsg = 'this file does not have a valid header';
5093
+ }
5094
+ }
5095
+
5096
+ if (!$error) {
5097
+ // set pointer
5098
+ $p = 8;
5099
+ $len = mb_strlen($data, '8bit');
5100
+
5101
+ // cycle through the file, identifying chunks
5102
+ $haveHeader = 0;
5103
+ $info = array();
5104
+ $idata = '';
5105
+ $pdata = '';
5106
+
5107
+ while ($p < $len) {
5108
+ $chunkLen = $this->getBytes($data, $p, 4);
5109
+ $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5110
+
5111
+ switch ($chunkType) {
5112
+ case 'IHDR':
5113
+ // this is where all the file information comes from
5114
+ $info['width'] = $this->getBytes($data, $p + 8, 4);
5115
+ $info['height'] = $this->getBytes($data, $p + 12, 4);
5116
+ $info['bitDepth'] = ord($data[$p + 16]);
5117
+ $info['colorType'] = ord($data[$p + 17]);
5118
+ $info['compressionMethod'] = ord($data[$p + 18]);
5119
+ $info['filterMethod'] = ord($data[$p + 19]);
5120
+ $info['interlaceMethod'] = ord($data[$p + 20]);
5121
+
5122
+ //print_r($info);
5123
+ $haveHeader = 1;
5124
+ if ($info['compressionMethod'] != 0) {
5125
+ $error = 1;
5126
+
5127
+ //debugpng
5128
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5129
+ print '[addPngFromFile unsupported compression method ' . $file . ']';
5130
+ }
5131
+
5132
+ $errormsg = 'unsupported compression method';
5133
+ }
5134
+
5135
+ if ($info['filterMethod'] != 0) {
5136
+ $error = 1;
5137
+
5138
+ //debugpng
5139
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5140
+ print '[addPngFromFile unsupported filter method ' . $file . ']';
5141
+ }
5142
+
5143
+ $errormsg = 'unsupported filter method';
5144
+ }
5145
+ break;
5146
+
5147
+ case 'PLTE':
5148
+ $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5149
+ break;
5150
+
5151
+ case 'IDAT':
5152
+ $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5153
+ break;
5154
+
5155
+ case 'tRNS':
5156
+ //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5157
+ //print "tRNS found, color type = ".$info['colorType']."\n";
5158
+ $transparency = array();
5159
+
5160
+ switch ($info['colorType']) {
5161
+ // indexed color, rbg
5162
+ case 3:
5163
+ /* corresponding to entries in the plte chunk
5164
+ Alpha for palette index 0: 1 byte
5165
+ Alpha for palette index 1: 1 byte
5166
+ ...etc...
5167
+ */
5168
+ // there will be one entry for each palette entry. up until the last non-opaque entry.
5169
+ // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5170
+ $transparency['type'] = 'indexed';
5171
+ $trans = 0;
5172
+
5173
+ for ($i = $chunkLen; $i >= 0; $i--) {
5174
+ if (ord($data[$p + 8 + $i]) == 0) {
5175
+ $trans = $i;
5176
+ }
5177
+ }
5178
+
5179
+ $transparency['data'] = $trans;
5180
+ break;
5181
+
5182
+ // grayscale
5183
+ case 0:
5184
+ /* corresponding to entries in the plte chunk
5185
+ Gray: 2 bytes, range 0 .. (2^bitdepth)-1
5186
+ */
5187
+ // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5188
+ $transparency['type'] = 'indexed';
5189
+ $transparency['data'] = ord($data[$p + 8 + 1]);
5190
+ break;
5191
+
5192
+ // truecolor
5193
+ case 2:
5194
+ /* corresponding to entries in the plte chunk
5195
+ Red: 2 bytes, range 0 .. (2^bitdepth)-1
5196
+ Green: 2 bytes, range 0 .. (2^bitdepth)-1
5197
+ Blue: 2 bytes, range 0 .. (2^bitdepth)-1
5198
+ */
5199
+ $transparency['r'] = $this->getBytes($data, $p + 8, 2);
5200
+ // r from truecolor
5201
+ $transparency['g'] = $this->getBytes($data, $p + 10, 2);
5202
+ // g from truecolor
5203
+ $transparency['b'] = $this->getBytes($data, $p + 12, 2);
5204
+ // b from truecolor
5205
+
5206
+ $transparency['type'] = 'color-key';
5207
+ break;
5208
+
5209
+ //unsupported transparency type
5210
+ default:
5211
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5212
+ print '[addPngFromFile unsupported transparency type ' . $file . ']';
5213
+ }
5214
+ break;
5215
+ }
5216
+
5217
+ // KS End new code
5218
+ break;
5219
+
5220
+ default:
5221
+ break;
5222
+ }
5223
+
5224
+ $p += $chunkLen + 12;
5225
+ }
5226
+
5227
+ if (!$haveHeader) {
5228
+ $error = 1;
5229
+
5230
+ //debugpng
5231
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5232
+ print '[addPngFromFile information header is missing ' . $file . ']';
5233
+ }
5234
+
5235
+ $errormsg = 'information header is missing';
5236
+ }
5237
+
5238
+ if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
5239
+ $error = 1;
5240
+
5241
+ //debugpng
5242
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5243
+ print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
5244
+ }
5245
+
5246
+ $errormsg = 'There appears to be no support for interlaced images in pdf.';
5247
+ }
5248
+ }
5249
+
5250
+ if (!$error && $info['bitDepth'] > 8) {
5251
+ $error = 1;
5252
+
5253
+ //debugpng
5254
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5255
+ print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
5256
+ }
5257
+
5258
+ $errormsg = 'only bit depth of 8 or less is supported';
5259
+ }
5260
+
5261
+ if (!$error) {
5262
+ switch ($info['colorType']) {
5263
+ case 3:
5264
+ $color = 'DeviceRGB';
5265
+ $ncolor = 1;
5266
+ break;
5267
+
5268
+ case 2:
5269
+ $color = 'DeviceRGB';
5270
+ $ncolor = 3;
5271
+ break;
5272
+
5273
+ case 0:
5274
+ $color = 'DeviceGray';
5275
+ $ncolor = 1;
5276
+ break;
5277
+
5278
+ default:
5279
+ $error = 1;
5280
+
5281
+ //debugpng
5282
+ if (defined("DEBUGPNG") && DEBUGPNG) {
5283
+ print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
5284
+ }
5285
+
5286
+ $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
5287
+ }
5288
+ }
5289
+
5290
+ if ($error) {
5291
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5292
+
5293
+ return;
5294
+ }
5295
+
5296
+ //print_r($info);
5297
+ // so this image is ok... add it in.
5298
+ $this->numImages++;
5299
+ $im = $this->numImages;
5300
+ $label = "I$im";
5301
+ $this->numObj++;
5302
+
5303
+ // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
5304
+ $options = array(
5305
+ 'label' => $label,
5306
+ 'data' => $idata,
5307
+ 'bitsPerComponent' => $info['bitDepth'],
5308
+ 'pdata' => $pdata,
5309
+ 'iw' => $info['width'],
5310
+ 'ih' => $info['height'],
5311
+ 'type' => 'png',
5312
+ 'color' => $color,
5313
+ 'ncolor' => $ncolor,
5314
+ 'masked' => $mask,
5315
+ 'isMask' => $is_mask
5316
+ );
5317
+
5318
+ if (isset($transparency)) {
5319
+ $options['transparency'] = $transparency;
5320
+ }
5321
+
5322
+ $this->o_image($this->numObj, 'new', $options);
5323
+ $this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']);
5324
+ }
5325
+
5326
+ if ($is_mask) {
5327
+ return;
5328
+ }
5329
+
5330
+ if ($w <= 0 && $h <= 0) {
5331
+ $w = $info['width'];
5332
+ $h = $info['height'];
5333
+ }
5334
+
5335
+ if ($w <= 0) {
5336
+ $w = $h / $info['height'] * $info['width'];
5337
+ }
5338
+
5339
+ if ($h <= 0) {
5340
+ $h = $w * $info['height'] / $info['width'];
5341
+ }
5342
+
5343
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
5344
+ }
5345
+
5346
+ /**
5347
+ * add a JPEG image into the document, from a file
5348
+ *
5349
+ * @param $img
5350
+ * @param $x
5351
+ * @param $y
5352
+ * @param int $w
5353
+ * @param int $h
5354
+ */
5355
+ function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
5356
+ {
5357
+ // attempt to add a jpeg image straight from a file, using no GD commands
5358
+ // note that this function is unable to operate on a remote file.
5359
+
5360
+ if (!file_exists($img)) {
5361
+ return;
5362
+ }
5363
+
5364
+ if ($this->image_iscached($img)) {
5365
+ $data = null;
5366
+ $imageWidth = $this->imagelist[$img]['w'];
5367
+ $imageHeight = $this->imagelist[$img]['h'];
5368
+ $channels = $this->imagelist[$img]['c'];
5369
+ } else {
5370
+ $tmp = getimagesize($img);
5371
+ $imageWidth = $tmp[0];
5372
+ $imageHeight = $tmp[1];
5373
+
5374
+ if (isset($tmp['channels'])) {
5375
+ $channels = $tmp['channels'];
5376
+ } else {
5377
+ $channels = 3;
5378
+ }
5379
+
5380
+ $data = file_get_contents($img);
5381
+ }
5382
+
5383
+ if ($w <= 0 && $h <= 0) {
5384
+ $w = $imageWidth;
5385
+ }
5386
+
5387
+ if ($w == 0) {
5388
+ $w = $h / $imageHeight * $imageWidth;
5389
+ }
5390
+
5391
+ if ($h == 0) {
5392
+ $h = $w * $imageHeight / $imageWidth;
5393
+ }
5394
+
5395
+ $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img);
5396
+ }
5397
+
5398
+ /**
5399
+ * common code used by the two JPEG adding functions
5400
+ * @param $data
5401
+ * @param $x
5402
+ * @param $y
5403
+ * @param int $w
5404
+ * @param int $h
5405
+ * @param $imageWidth
5406
+ * @param $imageHeight
5407
+ * @param int $channels
5408
+ * @param $imgname
5409
+ */
5410
+ private function addJpegImage_common(
5411
+ &$data,
5412
+ $x,
5413
+ $y,
5414
+ $w = 0,
5415
+ $h = 0,
5416
+ $imageWidth,
5417
+ $imageHeight,
5418
+ $channels = 3,
5419
+ $imgname
5420
+ ) {
5421
+ if ($this->image_iscached($imgname)) {
5422
+ $label = $this->imagelist[$imgname]['label'];
5423
+ //debugpng
5424
+ //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
5425
+
5426
+ } else {
5427
+ if ($data == null) {
5428
+ $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
5429
+
5430
+ return;
5431
+ }
5432
+
5433
+ // note that this function is not to be called externally
5434
+ // it is just the common code between the GD and the file options
5435
+ $this->numImages++;
5436
+ $im = $this->numImages;
5437
+ $label = "I$im";
5438
+ $this->numObj++;
5439
+
5440
+ $this->o_image(
5441
+ $this->numObj,
5442
+ 'new',
5443
+ array(
5444
+ 'label' => $label,
5445
+ 'data' => &$data,
5446
+ 'iw' => $imageWidth,
5447
+ 'ih' => $imageHeight,
5448
+ 'channels' => $channels
5449
+ )
5450
+ );
5451
+
5452
+ $this->imagelist[$imgname] = array(
5453
+ 'label' => $label,
5454
+ 'w' => $imageWidth,
5455
+ 'h' => $imageHeight,
5456
+ 'c' => $channels
5457
+ );
5458
+ }
5459
+
5460
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
5461
+ }
5462
+
5463
+ /**
5464
+ * specify where the document should open when it first starts
5465
+ *
5466
+ * @param $style
5467
+ * @param int $a
5468
+ * @param int $b
5469
+ * @param int $c
5470
+ */
5471
+ function openHere($style, $a = 0, $b = 0, $c = 0)
5472
+ {
5473
+ // this function will open the document at a specified page, in a specified style
5474
+ // the values for style, and the required parameters are:
5475
+ // 'XYZ' left, top, zoom
5476
+ // 'Fit'
5477
+ // 'FitH' top
5478
+ // 'FitV' left
5479
+ // 'FitR' left,bottom,right
5480
+ // 'FitB'
5481
+ // 'FitBH' top
5482
+ // 'FitBV' left
5483
+ $this->numObj++;
5484
+ $this->o_destination(
5485
+ $this->numObj,
5486
+ 'new',
5487
+ array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5488
+ );
5489
+ $id = $this->catalogId;
5490
+ $this->o_catalog($id, 'openHere', $this->numObj);
5491
+ }
5492
+
5493
+ /**
5494
+ * Add JavaScript code to the PDF document
5495
+ *
5496
+ * @param string $code
5497
+ */
5498
+ function addJavascript($code)
5499
+ {
5500
+ $this->javascript .= $code;
5501
+ }
5502
+
5503
+ /**
5504
+ * create a labelled destination within the document
5505
+ *
5506
+ * @param $label
5507
+ * @param $style
5508
+ * @param int $a
5509
+ * @param int $b
5510
+ * @param int $c
5511
+ */
5512
+ function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
5513
+ {
5514
+ // associates the given label with the destination, it is done this way so that a destination can be specified after
5515
+ // it has been linked to
5516
+ // styles are the same as the 'openHere' function
5517
+ $this->numObj++;
5518
+ $this->o_destination(
5519
+ $this->numObj,
5520
+ 'new',
5521
+ array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)
5522
+ );
5523
+ $id = $this->numObj;
5524
+
5525
+ // store the label->idf relationship, note that this means that labels can be used only once
5526
+ $this->destinations["$label"] = $id;
5527
+ }
5528
+
5529
+ /**
5530
+ * define font families, this is used to initialize the font families for the default fonts
5531
+ * and for the user to add new ones for their fonts. The default bahavious can be overridden should
5532
+ * that be desired.
5533
+ *
5534
+ * @param $family
5535
+ * @param string $options
5536
+ */
5537
+ function setFontFamily($family, $options = '')
5538
+ {
5539
+ if (!is_array($options)) {
5540
+ if ($family === 'init') {
5541
+ // set the known family groups
5542
+ // these font families will be used to enable bold and italic markers to be included
5543
+ // within text streams. html forms will be used... <b></b> <i></i>
5544
+ $this->fontFamilies['Helvetica.afm'] =
5545
+ array(
5546
+ 'b' => 'Helvetica-Bold.afm',
5547
+ 'i' => 'Helvetica-Oblique.afm',
5548
+ 'bi' => 'Helvetica-BoldOblique.afm',
5549
+ 'ib' => 'Helvetica-BoldOblique.afm'
5550
+ );
5551
+
5552
+ $this->fontFamilies['Courier.afm'] =
5553
+ array(
5554
+ 'b' => 'Courier-Bold.afm',
5555
+ 'i' => 'Courier-Oblique.afm',
5556
+ 'bi' => 'Courier-BoldOblique.afm',
5557
+ 'ib' => 'Courier-BoldOblique.afm'
5558
+ );
5559
+
5560
+ $this->fontFamilies['Times-Roman.afm'] =
5561
+ array(
5562
+ 'b' => 'Times-Bold.afm',
5563
+ 'i' => 'Times-Italic.afm',
5564
+ 'bi' => 'Times-BoldItalic.afm',
5565
+ 'ib' => 'Times-BoldItalic.afm'
5566
+ );
5567
+ }
5568
+ } else {
5569
+
5570
+ // the user is trying to set a font family
5571
+ // note that this can also be used to set the base ones to something else
5572
+ if (mb_strlen($family)) {
5573
+ $this->fontFamilies[$family] = $options;
5574
+ }
5575
+ }
5576
+ }
5577
+
5578
+ /**
5579
+ * used to add messages for use in debugging
5580
+ *
5581
+ * @param $message
5582
+ */
5583
+ function addMessage($message)
5584
+ {
5585
+ $this->messages .= $message . "\n";
5586
+ }
5587
+
5588
+ /**
5589
+ * a few functions which should allow the document to be treated transactionally.
5590
+ *
5591
+ * @param $action
5592
+ */
5593
+ function transaction($action)
5594
+ {
5595
+ switch ($action) {
5596
+ case 'start':
5597
+ // store all the data away into the checkpoint variable
5598
+ $data = get_object_vars($this);
5599
+ $this->checkpoint = $data;
5600
+ unset($data);
5601
+ break;
5602
+
5603
+ case 'commit':
5604
+ if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
5605
+ $tmp = $this->checkpoint['checkpoint'];
5606
+ $this->checkpoint = $tmp;
5607
+ unset($tmp);
5608
+ } else {
5609
+ $this->checkpoint = '';
5610
+ }
5611
+ break;
5612
+
5613
+ case 'rewind':
5614
+ // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
5615
+ if (is_array($this->checkpoint)) {
5616
+ // can only abort if were inside a checkpoint
5617
+ $tmp = $this->checkpoint;
5618
+
5619
+ foreach ($tmp as $k => $v) {
5620
+ if ($k !== 'checkpoint') {
5621
+ $this->$k = $v;
5622
+ }
5623
+ }
5624
+ unset($tmp);
5625
+ }
5626
+ break;
5627
+
5628
+ case 'abort':
5629
+ if (is_array($this->checkpoint)) {
5630
+ // can only abort if were inside a checkpoint
5631
+ $tmp = $this->checkpoint;
5632
+ foreach ($tmp as $k => $v) {
5633
+ $this->$k = $v;
5634
+ }
5635
+ unset($tmp);
5636
+ }
5637
+ break;
5638
+ }
5639
+ }
5640
+ }
vendor/dompdf/dompdf/lib/html5lib/Data.php CHANGED
@@ -1,123 +1,123 @@
1
- <?php
2
-
3
- // warning: this file is encoded in UTF-8!
4
-
5
- class HTML5_Data
6
- {
7
-
8
- // at some point this should be moved to a .ser file. Another
9
- // possible optimization is to give UTF-8 bytes, not Unicode
10
- // codepoints
11
- // XXX: Not quite sure why it's named this; this is
12
- // actually the numeric entity dereference table.
13
- protected static $realCodepointTable = array(
14
- 0x00 => 0xFFFD, // REPLACEMENT CHARACTER
15
- 0x0D => 0x000A, // LINE FEED (LF)
16
- 0x80 => 0x20AC, // EURO SIGN ('€')
17
- 0x81 => 0x0081, // <control>
18
- 0x82 => 0x201A, // SINGLE LOW-9 QUOTATION MARK ('‚')
19
- 0x83 => 0x0192, // LATIN SMALL LETTER F WITH HOOK ('ƒ')
20
- 0x84 => 0x201E, // DOUBLE LOW-9 QUOTATION MARK ('„')
21
- 0x85 => 0x2026, // HORIZONTAL ELLIPSIS ('…')
22
- 0x86 => 0x2020, // DAGGER ('†')
23
- 0x87 => 0x2021, // DOUBLE DAGGER ('‡')
24
- 0x88 => 0x02C6, // MODIFIER LETTER CIRCUMFLEX ACCENT ('ˆ')
25
- 0x89 => 0x2030, // PER MILLE SIGN ('‰')
26
- 0x8A => 0x0160, // LATIN CAPITAL LETTER S WITH CARON ('Š')
27
- 0x8B => 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK ('‹')
28
- 0x8C => 0x0152, // LATIN CAPITAL LIGATURE OE ('Œ')
29
- 0x8D => 0x008D, // <control>
30
- 0x8E => 0x017D, // LATIN CAPITAL LETTER Z WITH CARON ('Ž')
31
- 0x8F => 0x008F, // <control>
32
- 0x90 => 0x0090, // <control>
33
- 0x91 => 0x2018, // LEFT SINGLE QUOTATION MARK ('‘')
34
- 0x92 => 0x2019, // RIGHT SINGLE QUOTATION MARK ('’')
35
- 0x93 => 0x201C, // LEFT DOUBLE QUOTATION MARK ('“')
36
- 0x94 => 0x201D, // RIGHT DOUBLE QUOTATION MARK ('”')
37
- 0x95 => 0x2022, // BULLET ('•')
38
- 0x96 => 0x2013, // EN DASH ('–')
39
- 0x97 => 0x2014, // EM DASH ('—')
40
- 0x98 => 0x02DC, // SMALL TILDE ('˜')
41
- 0x99 => 0x2122, // TRADE MARK SIGN ('™')
42
- 0x9A => 0x0161, // LATIN SMALL LETTER S WITH CARON ('š')
43
- 0x9B => 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK ('›')
44
- 0x9C => 0x0153, // LATIN SMALL LIGATURE OE ('œ')
45
- 0x9D => 0x009D, // <control>
46
- 0x9E => 0x017E, // LATIN SMALL LETTER Z WITH CARON ('ž')
47
- 0x9F => 0x0178, // LATIN CAPITAL LETTER Y WITH DIAERESIS ('Ÿ')
48
- );
49
-
50
- protected static $namedCharacterReferences;
51
-
52
- protected static $namedCharacterReferenceMaxLength;
53
-
54
- /**
55
- * Returns the "real" Unicode codepoint of a malformed character
56
- * reference.
57
- */
58
- public static function getRealCodepoint($ref) {
59
- if (!isset(self::$realCodepointTable[$ref])) {
60
- return false;
61
- } else {
62
- return self::$realCodepointTable[$ref];
63
- }
64
- }
65
-
66
- public static function getNamedCharacterReferences() {
67
- if (!self::$namedCharacterReferences) {
68
- self::$namedCharacterReferences = unserialize(
69
- file_get_contents(dirname(__FILE__) . '/named-character-references.ser'));
70
- }
71
- return self::$namedCharacterReferences;
72
- }
73
-
74
- /**
75
- * Converts a Unicode codepoint to sequence of UTF-8 bytes.
76
- * @note Shamelessly stolen from HTML Purifier, which is also
77
- * shamelessly stolen from Feyd (which is in public domain).
78
- */
79
- public static function utf8chr($code) {
80
- /* We don't care: we live dangerously
81
- * if($code > 0x10FFFF or $code < 0x0 or
82
- ($code >= 0xD800 and $code <= 0xDFFF) ) {
83
- // bits are set outside the "valid" range as defined
84
- // by UNICODE 4.1.0
85
- return "\xEF\xBF\xBD";
86
- }*/
87
-
88
- $y = $z = $w = 0;
89
- if ($code < 0x80) {
90
- // regular ASCII character
91
- $x = $code;
92
- } else {
93
- // set up bits for UTF-8
94
- $x = ($code & 0x3F) | 0x80;
95
- if ($code < 0x800) {
96
- $y = (($code & 0x7FF) >> 6) | 0xC0;
97
- } else {
98
- $y = (($code & 0xFC0) >> 6) | 0x80;
99
- if ($code < 0x10000) {
100
- $z = (($code >> 12) & 0x0F) | 0xE0;
101
- } else {
102
- $z = (($code >> 12) & 0x3F) | 0x80;
103
- $w = (($code >> 18) & 0x07) | 0xF0;
104
- }
105
- }
106
- }
107
- // set up the actual character
108
- $ret = '';
109
- if ($w) {
110
- $ret .= chr($w);
111
- }
112
- if ($z) {
113
- $ret .= chr($z);
114
- }
115
- if ($y) {
116
- $ret .= chr($y);
117
- }
118
- $ret .= chr($x);
119
-
120
- return $ret;
121
- }
122
-
123
- }
1
+ <?php
2
+
3
+ // warning: this file is encoded in UTF-8!
4
+
5
+ class HTML5_Data
6
+ {
7
+
8
+ // at some point this should be moved to a .ser file. Another
9
+ // possible optimization is to give UTF-8 bytes, not Unicode
10
+ // codepoints
11
+ // XXX: Not quite sure why it's named this; this is
12
+ // actually the numeric entity dereference table.
13
+ protected static $realCodepointTable = array(
14
+ 0x00 => 0xFFFD, // REPLACEMENT CHARACTER
15
+ 0x0D => 0x000A, // LINE FEED (LF)
16
+ 0x80 => 0x20AC, // EURO SIGN ('€')
17
+ 0x81 => 0x0081, // <control>
18
+ 0x82 => 0x201A, // SINGLE LOW-9 QUOTATION MARK ('‚')
19
+ 0x83 => 0x0192, // LATIN SMALL LETTER F WITH HOOK ('ƒ')
20
+ 0x84 => 0x201E, // DOUBLE LOW-9 QUOTATION MARK ('„')
21
+ 0x85 => 0x2026, // HORIZONTAL ELLIPSIS ('…')
22
+ 0x86 => 0x2020, // DAGGER ('†')
23
+ 0x87 => 0x2021, // DOUBLE DAGGER ('‡')
24
+ 0x88 => 0x02C6, // MODIFIER LETTER CIRCUMFLEX ACCENT ('ˆ')
25
+ 0x89 => 0x2030, // PER MILLE SIGN ('‰')
26
+ 0x8A => 0x0160, // LATIN CAPITAL LETTER S WITH CARON ('Š')
27
+ 0x8B => 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK ('‹')
28
+ 0x8C => 0x0152, // LATIN CAPITAL LIGATURE OE ('Œ')
29
+ 0x8D => 0x008D, // <control>
30
+ 0x8E => 0x017D, // LATIN CAPITAL LETTER Z WITH CARON ('Ž')
31
+ 0x8F => 0x008F, // <control>
32
+ 0x90 => 0x0090, // <control>
33
+ 0x91 => 0x2018, // LEFT SINGLE QUOTATION MARK ('‘')
34
+ 0x92 => 0x2019, // RIGHT SINGLE QUOTATION MARK ('’')
35
+ 0x93 => 0x201C, // LEFT DOUBLE QUOTATION MARK ('“')
36
+ 0x94 => 0x201D, // RIGHT DOUBLE QUOTATION MARK ('”')
37
+ 0x95 => 0x2022, // BULLET ('•')
38
+ 0x96 => 0x2013, // EN DASH ('–')
39
+ 0x97 => 0x2014, // EM DASH ('—')
40
+ 0x98 => 0x02DC, // SMALL TILDE ('˜')
41
+ 0x99 => 0x2122, // TRADE MARK SIGN ('™')
42
+ 0x9A => 0x0161, // LATIN SMALL LETTER S WITH CARON ('š')
43
+ 0x9B => 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK ('›')
44
+ 0x9C => 0x0153, // LATIN SMALL LIGATURE OE ('œ')
45
+ 0x9D => 0x009D, // <control>
46
+ 0x9E => 0x017E, // LATIN SMALL LETTER Z WITH CARON ('ž')
47
+ 0x9F => 0x0178, // LATIN CAPITAL LETTER Y WITH DIAERESIS ('Ÿ')
48
+ );
49
+
50
+ protected static $namedCharacterReferences;
51
+
52
+ protected static $namedCharacterReferenceMaxLength;
53
+
54
+ /**
55
+ * Returns the "real" Unicode codepoint of a malformed character
56
+ * reference.
57
+ */
58
+ public static function getRealCodepoint($ref) {
59
+ if (!isset(self::$realCodepointTable[$ref])) {
60
+ return false;
61
+ } else {
62
+ return self::$realCodepointTable[$ref];
63
+ }
64
+ }
65
+
66
+ public static function getNamedCharacterReferences() {
67
+ if (!self::$namedCharacterReferences) {
68
+ self::$namedCharacterReferences = unserialize(
69
+ file_get_contents(dirname(__FILE__) . '/named-character-references.ser'));
70
+ }
71
+ return self::$namedCharacterReferences;
72
+ }
73
+
74
+ /**
75
+ * Converts a Unicode codepoint to sequence of UTF-8 bytes.
76
+ * @note Shamelessly stolen from HTML Purifier, which is also
77
+ * shamelessly stolen from Feyd (which is in public domain).
78
+ */
79
+ public static function utf8chr($code) {
80
+ /* We don't care: we live dangerously
81
+ * if($code > 0x10FFFF or $code < 0x0 or
82
+ ($code >= 0xD800 and $code <= 0xDFFF) ) {
83
+ // bits are set outside the "valid" range as defined
84
+ // by UNICODE 4.1.0
85
+ return "\xEF\xBF\xBD";
86
+ }*/
87
+
88
+ $y = $z = $w = 0;
89
+ if ($code < 0x80) {
90
+ // regular ASCII character
91
+ $x = $code;
92
+ } else {
93
+ // set up bits for UTF-8
94
+ $x = ($code & 0x3F) | 0x80;
95
+ if ($code < 0x800) {
96
+ $y = (($code & 0x7FF) >> 6) | 0xC0;
97
+ } else {
98
+ $y = (($code & 0xFC0) >> 6) | 0x80;
99
+ if ($code < 0x10000) {
100
+ $z = (($code >> 12) & 0x0F) | 0xE0;
101
+ } else {
102
+ $z = (($code >> 12) & 0x3F) | 0x80;
103
+ $w = (($code >> 18) & 0x07) | 0xF0;
104
+ }
105
+ }
106
+ }
107
+ // set up the actual character
108
+ $ret = '';
109
+ if ($w) {
110
+ $ret .= chr($w);
111
+ }
112
+ if ($z) {
113
+ $ret .= chr($z);
114
+ }
115
+ if ($y) {
116
+ $ret .= chr($y);
117
+ }
118
+ $ret .= chr($x);
119
+
120
+ return $ret;
121
+ }
122
+
123
+ }
vendor/dompdf/dompdf/lib/html5lib/Tokenizer.php CHANGED
@@ -1,2470 +1,2470 @@
1
- <?php
2
-
3
- /*
4
-
5
- Copyright 2007 Jeroen van der Meer <http://jero.net/>
6
- Copyright 2008 Edward Z. Yang <http://htmlpurifier.org/>
7
- Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a
10
- copy of this software and associated documentation files (the
11
- "Software"), to deal in the Software without restriction, including
12
- without limitation the rights to use, copy, modify, merge, publish,
13
- distribute, sublicense, and/or sell copies of the Software, and to
14
- permit persons to whom the Software is furnished to do so, subject to
15
- the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included
18
- in all copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
- SOFTWARE OR THE USE OR