| 1 |
<?php |
| 2 |
/** |
| 3 |
* Ebook Store - the ebook value object used for download links and for the |
| 4 |
* per-order watermarked/encrypted PDF. |
| 5 |
* |
| 6 |
* @package EbookStore |
| 7 |
*/ |
| 8 |
|
| 9 |
if ( ! defined( 'ABSPATH' ) ) { |
| 10 |
exit; |
| 11 |
} |
| 12 |
|
| 13 |
class EbookStoreEbook { |
| 14 |
/** |
| 15 |
* Whether l() writes to the PHP error log. |
| 16 |
* |
| 17 |
* @var bool |
| 18 |
*/ |
| 19 |
public $log = true; |
| 20 |
|
| 21 |
/** |
| 22 |
* Post ID of the ebook. |
| 23 |
* |
| 24 |
* @var int|string |
| 25 |
*/ |
| 26 |
public $ebook_id = 0; |
| 27 |
|
| 28 |
/** |
| 29 |
* Raw get_post_meta() dump for the ebook, keyed by meta key. |
| 30 |
* |
| 31 |
* @var array |
| 32 |
*/ |
| 33 |
public $meta = array(); |
| 34 |
|
| 35 |
/** |
| 36 |
* The primary attachment record ('file', 'url', 'type'). |
| 37 |
* |
| 38 |
* @var array |
| 39 |
*/ |
| 40 |
public $file = array(); |
| 41 |
|
| 42 |
/** |
| 43 |
* The 'ebook' meta record (price and other per-book settings). |
| 44 |
* |
| 45 |
* @var array |
| 46 |
*/ |
| 47 |
public $ebook = array(); |
| 48 |
|
| 49 |
/** |
| 50 |
* Price taken from the 'ebook' meta record. |
| 51 |
* |
| 52 |
* @var string |
| 53 |
*/ |
| 54 |
public $price = ''; |
| 55 |
|
| 56 |
/** |
| 57 |
* Size of the primary attachment in bytes. |
| 58 |
* |
| 59 |
* @var int |
| 60 |
*/ |
| 61 |
public $filesize = 0; |
| 62 |
|
| 63 |
/** |
| 64 |
* Human readable size of the primary attachment. |
| 65 |
* |
| 66 |
* Note: this property deliberately shares its name with the human_filesize() |
| 67 |
* method. PHP keeps the two in separate symbol tables so both work, and |
| 68 |
* renaming either one would break callers outside this class. |
| 69 |
* |
| 70 |
* @var string |
| 71 |
*/ |
| 72 |
public $human_filesize = ''; |
| 73 |
|
| 74 |
/** |
| 75 |
* URL of the primary attachment. |
| 76 |
* |
| 77 |
* @var string |
| 78 |
*/ |
| 79 |
public $url = ''; |
| 80 |
|
| 81 |
/** |
| 82 |
* MIME type of the primary attachment. |
| 83 |
* |
| 84 |
* @var string |
| 85 |
*/ |
| 86 |
public $type = ''; |
| 87 |
|
| 88 |
/** |
| 89 |
* Ebook post title. |
| 90 |
* |
| 91 |
* @var string |
| 92 |
*/ |
| 93 |
public $title = ''; |
| 94 |
|
| 95 |
/** |
| 96 |
* Formats this ebook is available in, e.g. array('pdf','epub'). |
| 97 |
* |
| 98 |
* @var array |
| 99 |
*/ |
| 100 |
public $formats = array(); |
| 101 |
|
| 102 |
/** |
| 103 |
* Absolute paths and sizes per format, keyed 'pdf' and 'pdf_size'. |
| 104 |
* |
| 105 |
* @var array |
| 106 |
*/ |
| 107 |
public $files = array(); |
| 108 |
|
| 109 |
/** |
| 110 |
* Per-format download URL overrides. Assigned from outside the class by |
| 111 |
* ebook_store.php and functions.php when an order already has a stored link. |
| 112 |
* |
| 113 |
* @var array |
| 114 |
*/ |
| 115 |
public $setLink = array(); |
| 116 |
|
| 117 |
/** |
| 118 |
* WooCommerce order key, assigned from outside the class. |
| 119 |
* |
| 120 |
* @var string |
| 121 |
*/ |
| 122 |
public $wc_order = ''; |
| 123 |
|
| 124 |
/** |
| 125 |
* Order post ID, assigned from outside the class. |
| 126 |
* |
| 127 |
* @var int|string |
| 128 |
*/ |
| 129 |
public $order_id = 0; |
| 130 |
|
| 131 |
/** |
| 132 |
* Plugin-order download key, assigned from outside the class. Declared so |
| 133 |
* PHP 8.2+ does not report a dynamic-property deprecation. |
| 134 |
* |
| 135 |
* @var string |
| 136 |
*/ |
| 137 |
public $ebook_key = ''; |
| 138 |
|
| 139 |
/** |
| 140 |
* Saved-checkout-form nonce, assigned from outside the class. |
| 141 |
* |
| 142 |
* @var string |
| 143 |
*/ |
| 144 |
public $md5_nonce = ''; |
| 145 |
|
| 146 |
function __construct($ebook_id) { |
| 147 |
$this->ebook_id = $ebook_id; |
| 148 |
$this->meta = get_post_meta($ebook_id); |
| 149 |
$this->meta = is_array($this->meta) ? $this->meta : array(); |
| 150 |
$this->file = get_post_meta($ebook_id, 'ebook_wp_custom_attachment', true); |
| 151 |
$this->file = is_array($this->file) ? $this->file : array(); |
| 152 |
$this->ebook = get_post_meta($ebook_id, 'ebook', true); |
| 153 |
$this->ebook = is_array($this->ebook) ? $this->ebook : array(); |
| 154 |
$this->price = isset($this->ebook['ebook_price']) ? $this->ebook['ebook_price'] : ''; |
| 155 |
$this->filesize = isset($this->file['file']) ? (int) @filesize($this->file['file']) : 0; |
| 156 |
$this->human_filesize = $this->human_filesize($this->filesize); |
| 157 |
$this->url = isset($this->file['url']) ? $this->file['url'] : ''; |
| 158 |
$this->type = isset($this->file['type']) ? $this->file['type'] : ''; |
| 159 |
$this->title = get_the_title($ebook_id); |
| 160 |
//$this->l($this->title); |
| 161 |
if (is_array(@$this->meta['ebook_wp_custom_attachment'])) { |
| 162 |
if (count($this->meta['ebook_wp_custom_attachment']) > 0) { |
| 163 |
$this->formats[] = 'pdf'; |
| 164 |
$file = unserialize($this->meta['ebook_wp_custom_attachment'][0]); |
| 165 |
$file = $file['file']; |
| 166 |
$this->files['pdf'] = $file; |
| 167 |
$this->files['pdf_size'] = (int) @filesize($file); |
| 168 |
} |
| 169 |
|
| 170 |
} |
| 171 |
$extra_formats = array('mobi','epub','txt','zip','mp3','mp4'); |
| 172 |
foreach ($extra_formats as $format) { |
| 173 |
if (@is_array($this->meta['ebook_wp_custom_attachment_' . $format])) { |
| 174 |
if (@count(@$this->meta['ebook_wp_custom_attachment_' . $format]) > 0) { |
| 175 |
$this->formats[] = $format; |
| 176 |
$file = unserialize($this->meta['ebook_wp_custom_attachment_' . $format][0]); |
| 177 |
$file = $file['file']; |
| 178 |
$this->files[$format] = $file; |
| 179 |
$this->files[$format . '_size'] = (int) @filesize($file); |
| 180 |
} |
| 181 |
} |
| 182 |
} |
| 183 |
// $this->l($this->files,true); |
| 184 |
} |
| 185 |
function l($data, $v = false) { |
| 186 |
if ($this->log) { |
| 187 |
error_log(print_r(debug_backtrace(),true)); |
| 188 |
if (is_array($data)) { |
| 189 |
error_log(print_r($data,true)); |
| 190 |
} else { |
| 191 |
error_log($data); |
| 192 |
} |
| 193 |
} |
| 194 |
if ($v) { |
| 195 |
echo "<pre>" . var_export($data, true) . "</pre>"; |
| 196 |
} |
| 197 |
} |
| 198 |
function human_filesize($bytes, $decimals = 2) { |
| 199 |
$sz = 'BKMGTP'; |
| 200 |
$bytes = is_numeric($bytes) ? $bytes + 0 : 0; |
| 201 |
// Clamp rather than suppress: an empty or huge value used to index past |
| 202 |
// the end of $sz, which the old @ hid instead of fixing. |
| 203 |
$factor = (int) floor((strlen((string) $bytes) - 1) / 3); |
| 204 |
$factor = max(0, min($factor, strlen($sz) - 1)); |
| 205 |
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $sz[$factor]; |
| 206 |
} |
| 207 |
function format_links() { |
| 208 |
if (is_array($this->formats) == false) { |
| 209 |
return ''; |
| 210 |
} |
| 211 |
return implode(', ', array_map(array($this, 'link'), $this->formats)); |
| 212 |
} |
| 213 |
function link($format) { |
| 214 |
include_once( plugin_dir_path( EBOOK_STORE_PLUGIN_FILE ) . 'locale.php' ); |
| 215 |
$locale = ebook_store_get_locale_strings(); |
| 216 |
$formats_locale = ebook_store_get_format_labels(); |
| 217 |
|
| 218 |
$order_id = isset($this->order_id) ? absint($this->order_id) : 0; |
| 219 |
$wc_order = isset($this->wc_order) ? (string) $this->wc_order : ''; |
| 220 |
$ebook_key = isset($this->ebook_key) ? (string) $this->ebook_key : ''; |
| 221 |
|
| 222 |
if ($wc_order !== '' && $order_id > 0) { |
| 223 |
$link = add_query_arg(array('action' => 'wc_order_download','ebook_id' => $this->ebook_id, 'format' => $format, 'wc_order' => $wc_order, 'order_id' => $order_id),get_permalink($this->ebook_id)); |
| 224 |
} elseif ($ebook_key !== '') { |
| 225 |
// A plugin (non-WooCommerce) order has no WooCommerce order key, so a |
| 226 |
// wc_order_download link can never pass the order-key check. Send those |
| 227 |
// buyers through the ebook_key download path instead, like the PDF link. |
| 228 |
$key_args = array('action' => 'download', 'format' => $format, 'ebook_key' => $ebook_key); |
| 229 |
if (isset($this->md5_nonce) && $this->md5_nonce != '') { |
| 230 |
$key_args['md5_nonce'] = (string) $this->md5_nonce; |
| 231 |
} |
| 232 |
if ($order_id > 0) { |
| 233 |
$key_args['order_id'] = $order_id; |
| 234 |
} |
| 235 |
$link = add_query_arg($key_args,get_permalink($this->ebook_id)); |
| 236 |
} else { |
| 237 |
$link = add_query_arg(array('action' => 'wc_order_download','ebook_id' => $this->ebook_id, 'format' => $format, 'wc_order' => $wc_order, 'order_id' => $order_id),get_permalink($this->ebook_id)); |
| 238 |
} |
| 239 |
if (isset($this->setLink[$format]) && $this->setLink[$format] != '') { |
| 240 |
$link = $this->setLink[$format]; |
| 241 |
} |
| 242 |
$label = isset($formats_locale[$format]) ? $formats_locale[$format] : $format; |
| 243 |
$size = isset($this->files[$format . '_size']) ? $this->files[$format . '_size'] : 0; |
| 244 |
return '<a href="' . esc_url($link) . '" target="_blank">' . esc_html($label) . '</a> <small><em>(' . esc_html($this->human_filesize($size)) . ')</em></small>'; |
| 245 |
} |
| 246 |
function encrypted($order_id = null, $format = 'pdf') { |
| 247 |
//error_reporting(E_ALL); |
| 248 |
//ini_set('display_errors','1'); |
| 249 |
global $ebook_email_delivery, $ebook_qr_text, $ebook_png_path, $ebook_pngname, $attachment, $pdfHeaderText; |
| 250 |
if (class_exists('FPDF') == false) { |
| 251 |
require_once('fpdi/qrcode.class.php'); |
| 252 |
} |
| 253 |
|
| 254 |
require_once('fpdi/qrcode.class.php'); |
| 255 |
|
| 256 |
if ($format == 'pdf' && get_option('encrypt_pdf')) { |
| 257 |
$meta = get_post_meta($order_id); |
| 258 |
$meta = is_array($meta) ? $meta : array(); |
| 259 |
|
| 260 |
$meta_value = function ($key) use ($meta) { |
| 261 |
return isset($meta[$key][0]) ? $meta[$key][0] : ''; |
| 262 |
}; |
| 263 |
|
| 264 |
$r['payer_email'] = $meta_value('_billing_email'); |
| 265 |
$r['payment_date'] = $meta_value('_paid_date'); |
| 266 |
$r['txn_id'] = $meta_value('_transaction_id'); |
| 267 |
$r['first_name'] = $meta_value('_billing_first_name'); |
| 268 |
$r['last_name'] = $meta_value('_billing_last_name'); |
| 269 |
$r['residence_country'] = $meta_value('_billing_country'); |
| 270 |
|
| 271 |
if ($r['payer_email'] == '') { |
| 272 |
$r['payer_email'] = $meta_value('payer_email'); |
| 273 |
$r['payment_date'] = $meta_value('payment_date'); |
| 274 |
$r['txn_id'] = $meta_value('txn_id'); |
| 275 |
$r['first_name'] = $meta_value('first_name'); |
| 276 |
$r['last_name'] = $meta_value('last_name'); |
| 277 |
$r['residence_country'] = $meta_value('residence_country'); |
| 278 |
} |
| 279 |
|
| 280 |
// Under WooCommerce HPOS none of the billing details live in wp_postmeta, |
| 281 |
// so both lookups above come back empty; ask the order object instead. |
| 282 |
if ($r['payer_email'] == '' && (int) $order_id > 0 && function_exists('wc_get_order')) { |
| 283 |
$wc_order = wc_get_order((int) $order_id); |
| 284 |
if ($wc_order && method_exists($wc_order, 'get_billing_email')) { |
| 285 |
$r['payer_email'] = (string) $wc_order->get_billing_email(); |
| 286 |
$r['first_name'] = (string) $wc_order->get_billing_first_name(); |
| 287 |
$r['last_name'] = (string) $wc_order->get_billing_last_name(); |
| 288 |
$r['residence_country'] = (string) $wc_order->get_billing_country(); |
| 289 |
$r['txn_id'] = (string) $wc_order->get_transaction_id(); |
| 290 |
$paid_date = $wc_order->get_date_paid(); |
| 291 |
$r['payment_date'] = $paid_date ? $paid_date->date('Y-m-d H:i:s') : ''; |
| 292 |
} |
| 293 |
} |
| 294 |
|
| 295 |
// The QR points at the order-verification page on this site (a link |
| 296 |
// only — no buyer PII in the code itself), matching the other |
| 297 |
// encryption path and the promise made in the settings screen. |
| 298 |
$qr_key = $meta_value('ebook_key'); |
| 299 |
$ebook_qr_text = function_exists('ebook_store_get_order_verification_url') |
| 300 |
? ebook_store_get_order_verification_url($qr_key) |
| 301 |
: home_url('/'); |
| 302 |
// PDF text is not HTML — esc_attr() would leak entities into the file. |
| 303 |
$pdfHeaderText = function_exists( 'ebook_store_sanitize_pdf_text' ) |
| 304 |
? ebook_store_sanitize_pdf_text( (string) get_option('buyer_info_text') ) |
| 305 |
: (string) get_option('buyer_info_text'); |
| 306 |
|
| 307 |
foreach ($r as $k => $v) { |
| 308 |
if ( is_scalar( $v ) ) { |
| 309 |
$clean = function_exists( 'ebook_store_sanitize_pdf_text' ) ? ebook_store_sanitize_pdf_text( (string) $v ) : (string) $v; |
| 310 |
$pdfHeaderText = str_replace("%%$k%%", $clean, $pdfHeaderText); |
| 311 |
} |
| 312 |
} |
| 313 |
|
| 314 |
|
| 315 |
$file = $this->files['pdf']; |
| 316 |
|
| 317 |
$ebook_pdfname = basename($file); |
| 318 |
|
| 319 |
$cache_dir = ebook_store_get_cache_dir($order_id); |
| 320 |
if ($cache_dir === '') { |
| 321 |
// Without a writable protected directory we must not fall back to |
| 322 |
// the plugin folder, so serve the unwatermarked source instead. |
| 323 |
if (function_exists('ebook_store_log')) { |
| 324 |
ebook_store_log('pdf', 'Cache directory unavailable, serving the source file.', array('order_id' => $order_id)); |
| 325 |
} |
| 326 |
return $this->files[$format]; |
| 327 |
} |
| 328 |
|
| 329 |
$ebook_pdf_path = $cache_dir . '/' . $ebook_pdfname; |
| 330 |
|
| 331 |
// Versions before 6.00 wrote this file inside the plugin folder, where |
| 332 |
// it was served over HTTP. Move rather than regenerate so that live |
| 333 |
// orders keep resolving to the copy the buyer was already given. |
| 334 |
$legacy_dir = plugin_dir_path( __FILE__ ) . '/cache/' . md5($order_id . substr(NONCE_KEY, 0, 8)); |
| 335 |
$legacy_pdf_path = $legacy_dir . '/' . $ebook_pdfname; |
| 336 |
if (!file_exists($ebook_pdf_path) && file_exists($legacy_pdf_path)) { |
| 337 |
if (@rename($legacy_pdf_path, $ebook_pdf_path)) { |
| 338 |
@rmdir($legacy_dir); |
| 339 |
} else { |
| 340 |
// The move failed; drop the web-readable copy anyway and let the |
| 341 |
// generator below rebuild it in the protected location. |
| 342 |
@unlink($legacy_pdf_path); |
| 343 |
} |
| 344 |
} |
| 345 |
|
| 346 |
// The QR image is only read back while the PDF is being built, so a |
| 347 |
// cache hit can skip generating it entirely. |
| 348 |
if (file_exists($ebook_pdf_path)) { |
| 349 |
return $ebook_pdf_path; |
| 350 |
} |
| 351 |
|
| 352 |
$password = $r['payer_email']; |
| 353 |
if (function_exists('ebook_store_get_password_for_order')) { |
| 354 |
// The shared resolver honours the "No password" and random-password |
| 355 |
// settings and stores the secret once per order, so the PDF opens |
| 356 |
// with the password the buyer was actually shown. |
| 357 |
$password_context = array( |
| 358 |
'payer_email' => $r['payer_email'], |
| 359 |
'billing_email' => $meta_value('_billing_email'), |
| 360 |
'fallback_email' => $r['payer_email'], |
| 361 |
); |
| 362 |
if (get_option('ebook_store_random_password') == 1) { |
| 363 |
// Only used when no password is stored yet; wp_generate_password() is a CSPRNG. |
| 364 |
$password_context['password'] = wp_generate_password( 10, false, false ); |
| 365 |
} |
| 366 |
$password = (string) ebook_store_get_password_for_order((int) $order_id, $password_context); |
| 367 |
} else { |
| 368 |
if (get_option('ebook_store_random_password') == 1) { |
| 369 |
// The open password must not be derivable from the cache path, so |
| 370 |
// it comes from a CSPRNG and is stored once per order instead of |
| 371 |
// being recomputed from the same hash that names the directory. |
| 372 |
$stored_password = ($order_id > 0) ? get_post_meta($order_id, 'password', true) : ''; |
| 373 |
$stored_password = is_string($stored_password) ? $stored_password : ''; |
| 374 |
if ($stored_password === '') { |
| 375 |
$stored_password = wp_generate_password( 10, false, false ); |
| 376 |
if ($order_id > 0) { |
| 377 |
update_post_meta($order_id, 'password', $stored_password); |
| 378 |
} |
| 379 |
} |
| 380 |
$password = $stored_password; |
| 381 |
} |
| 382 |
if (get_option('ebook_store_blank_password') == 1) { |
| 383 |
$password = ''; |
| 384 |
} |
| 385 |
} |
| 386 |
$r['password'] = $password; |
| 387 |
|
| 388 |
$owner_password = (string) get_option('ebook_store_owner_password'); |
| 389 |
|
| 390 |
if (function_exists('ebook_store_raise_pdf_memory')) { |
| 391 |
ebook_store_raise_pdf_memory(); |
| 392 |
} |
| 393 |
$pdf = new QRPDF(); |
| 394 |
$pdf->watermarkText = $pdfHeaderText; |
| 395 |
$pdf->qrText = $ebook_qr_text; |
| 396 |
$pdf->stampCount = 0; |
| 397 |
try { |
| 398 |
$pagecount = $pdf->setSourceFile($this->files[$format]); |
| 399 |
for ($loop = 1; $loop <= $pagecount; $loop++) { |
| 400 |
$tplidx = $pdf->importPage($loop); |
| 401 |
$size = $pdf->getTemplateSize($tplidx); |
| 402 |
$pdf->AddPage($size['orientation'], array($size['width'], $size['height'])); |
| 403 |
$pdf->useTemplate($tplidx); |
| 404 |
$pdf->stampCurrentPage(); |
| 405 |
} |
| 406 |
|
| 407 |
$pdf->applyProtection($password, $owner_password); |
| 408 |
$pdf->Output($ebook_pdf_path, 'F'); |
| 409 |
} catch (\Throwable $e) { |
| 410 |
// A half-written file must not be served as a cache hit next time. |
| 411 |
if (file_exists($ebook_pdf_path)) { |
| 412 |
@unlink($ebook_pdf_path); |
| 413 |
} |
| 414 |
if (function_exists('ebook_store_log')) { |
| 415 |
ebook_store_log('pdf', 'PDF protection failed: ' . $e->getMessage(), array('order_id' => $order_id, 'ebook_id' => $this->ebook_id, 'file' => $this->files[$format])); |
| 416 |
} |
| 417 |
wp_die( esc_html( sprintf( |
| 418 |
/* translators: %s: PDF import error. */ |
| 419 |
__( 'This PDF could not be processed for protection. Please re-save it as a standard PDF or turn encryption off. Details: %s', 'ebook-store' ), |
| 420 |
$e->getMessage() |
| 421 |
) ) ); |
| 422 |
} |
| 423 |
return $ebook_pdf_path; |
| 424 |
|
| 425 |
// update_post_meta(@$order_id,'encrypted_pdf',wp_slash($ebook_pdf_path)); |
| 426 |
//wp_die(print_r($meta,true)); |
| 427 |
} elseif ( $format === 'epub' && get_option( 'ebook_store_epub_watermark' ) && isset( $this->files['epub'] ) ) { |
| 428 |
// EPUB "protection" is social-DRM watermarking (it cannot be encrypted |
| 429 |
// and stay reader-compatible). Mirror the PDF per-order cache pattern. |
| 430 |
$file = $this->files['epub']; |
| 431 |
$cache_dir = ebook_store_get_cache_dir( $order_id ); |
| 432 |
if ( $cache_dir === '' ) { |
| 433 |
if ( function_exists( 'ebook_store_log' ) ) { |
| 434 |
ebook_store_log( 'epub', 'cache dir unavailable, serving source', array( 'order_id' => $order_id ) ); |
| 435 |
} |
| 436 |
return $this->files[$format]; |
| 437 |
} |
| 438 |
$dest = $cache_dir . '/' . basename( $file ); |
| 439 |
if ( file_exists( $dest ) ) { |
| 440 |
return $dest; // idempotent cache hit |
| 441 |
} |
| 442 |
if ( ! function_exists( 'ebook_store_watermark_epub' ) ) { |
| 443 |
return $this->files[$format]; |
| 444 |
} |
| 445 |
$watermark = ebook_store_resolve_buyer_watermark( (int) $order_id, (string) get_option( 'ebook_store_epub_watermark_text' ) ); |
| 446 |
$qr_text = ''; |
| 447 |
if ( get_option( 'ebook_store_epub_qr' ) && function_exists( 'ebook_store_get_order_verification_url' ) ) { |
| 448 |
$qr_key = (string) get_post_meta( $order_id, 'ebook_key', true ); |
| 449 |
$qr_text = ebook_store_get_order_verification_url( $qr_key ); |
| 450 |
} |
| 451 |
$out = ebook_store_watermark_epub( |
| 452 |
$file, |
| 453 |
$dest, |
| 454 |
$watermark, |
| 455 |
array( |
| 456 |
'scope' => get_option( 'ebook_store_epub_watermark_scope', 'first' ), |
| 457 |
'position' => get_option( 'ebook_store_epub_watermark_position', 'bottom' ), |
| 458 |
'style' => get_option( 'ebook_store_epub_watermark_style', 'subtle' ), |
| 459 |
'stamp_metadata' => get_option( 'ebook_store_epub_stamp_metadata' ), |
| 460 |
'qr' => get_option( 'ebook_store_epub_qr' ), |
| 461 |
'qr_scope' => get_option( 'ebook_store_epub_qr_scope', 'first' ), |
| 462 |
'qr_position' => get_option( 'ebook_store_epub_qr_position', 'bottom' ), |
| 463 |
'qr_text' => $qr_text, |
| 464 |
'order_id' => (int) $order_id, |
| 465 |
) |
| 466 |
); |
| 467 |
if ( $out === $dest && file_exists( $dest ) ) { |
| 468 |
if ( $order_id > 0 ) { |
| 469 |
update_post_meta( $order_id, 'encrypted_epub', wp_slash( $dest ) ); |
| 470 |
} |
| 471 |
return $dest; |
| 472 |
} |
| 473 |
return $this->files[$format]; // fail-open: engine returned the source |
| 474 |
} else { |
| 475 |
return $this->files[$format]; |
| 476 |
} |
| 477 |
} |
| 478 |
function password_desc() { |
| 479 |
include_once( plugin_dir_path( EBOOK_STORE_PLUGIN_FILE ) . 'locale.php' ); |
| 480 |
$locale = ebook_store_get_locale_strings(); |
| 481 |
$formats_locale = ebook_store_get_format_labels(); |
| 482 |
if (get_option('encrypt_pdf') != 1) { |
| 483 |
return ''; |
| 484 |
} |
| 485 |
$context = $this->password_order_context(); |
| 486 |
$password = ''; |
| 487 |
if (function_exists('ebook_store_get_password_for_order')) { |
| 488 |
$password = ebook_store_get_password_for_order($context['order_id'], array( |
| 489 |
'payer_email' => $context['payer_email'], |
| 490 |
'billing_email' => $context['billing_email'], |
| 491 |
'fallback_email' => $context['fallback_email'], |
| 492 |
)); |
| 493 |
} |
| 494 |
if ($password === '' && intval(get_option('ebook_store_blank_password')) === 1) { |
| 495 |
return ''; |
| 496 |
} |
| 497 |
if ($password === '' && $context['fallback_email'] !== '' && intval(get_option('ebook_store_random_password')) !== 1) { |
| 498 |
$password = $context['fallback_email']; |
| 499 |
} |
| 500 |
if ($password === '' && intval(get_option('ebook_store_random_password')) !== 1) { |
| 501 |
return $locale['password_desc'] . ': ' . __('your email address', 'ebook-store'); |
| 502 |
} |
| 503 |
if ($password === '') { |
| 504 |
return ''; |
| 505 |
} |
| 506 |
return $locale['password_desc'] . ': ' . $password; |
| 507 |
} |
| 508 |
function password() { |
| 509 |
if (get_option('encrypt_pdf') != 1) { |
| 510 |
return ''; |
| 511 |
} |
| 512 |
$context = $this->password_order_context(); |
| 513 |
$password = ''; |
| 514 |
if (function_exists('ebook_store_get_password_for_order')) { |
| 515 |
$password = ebook_store_get_password_for_order($context['order_id'], array( |
| 516 |
'payer_email' => $context['payer_email'], |
| 517 |
'billing_email' => $context['billing_email'], |
| 518 |
'fallback_email' => $context['fallback_email'], |
| 519 |
)); |
| 520 |
} |
| 521 |
if ($password === '' && intval(get_option('ebook_store_blank_password')) !== 1) { |
| 522 |
$password = $context['fallback_email']; |
| 523 |
} |
| 524 |
if ($password !== '' || intval(get_option('ebook_store_blank_password')) === 1) { |
| 525 |
return $password; |
| 526 |
} |
| 527 |
$meta = get_post_meta($this->order_id); |
| 528 |
$meta = is_array($meta) ? $meta : array(); |
| 529 |
if (get_option('ebook_store_random_password') == 1) { |
| 530 |
// Legacy derivation, kept only for orders whose PDF was encrypted with |
| 531 |
// it before 6.00. New orders never reach here: the branches above |
| 532 |
// return the secret stored under the 'password' meta key. |
| 533 |
return substr(md5($this->order_id . substr(NONCE_KEY, 0, 8)), 0, 8); |
| 534 |
} else if (get_option('ebook_store_blank_password') == 1) { |
| 535 |
return ''; |
| 536 |
} |
| 537 |
return isset($meta['_billing_email'][0]) ? $meta['_billing_email'][0] : ''; |
| 538 |
} |
| 539 |
protected function password_order_context() { |
| 540 |
$context = array( |
| 541 |
'order_id' => intval($this->order_id), |
| 542 |
'payer_email' => '', |
| 543 |
'billing_email' => '', |
| 544 |
'fallback_email' => '', |
| 545 |
); |
| 546 |
if ($context['order_id'] === 0 && !empty($this->wc_order) && function_exists('wc_get_order_id_by_order_key')) { |
| 547 |
$context['order_id'] = wc_get_order_id_by_order_key($this->wc_order); |
| 548 |
} |
| 549 |
if ($context['order_id'] > 0) { |
| 550 |
$context['payer_email'] = get_post_meta($context['order_id'],'payer_email',true); |
| 551 |
$context['billing_email'] = get_post_meta($context['order_id'],'_billing_email',true); |
| 552 |
} |
| 553 |
if ($context['billing_email'] === '' && $context['order_id'] > 0 && function_exists('wc_get_order')) { |
| 554 |
$order_obj = wc_get_order($context['order_id']); |
| 555 |
if ($order_obj && method_exists($order_obj, 'get_billing_email')) { |
| 556 |
$context['billing_email'] = $order_obj->get_billing_email(); |
| 557 |
} |
| 558 |
} |
| 559 |
if ($context['fallback_email'] === '') { |
| 560 |
$context['fallback_email'] = $context['billing_email'] ?: $context['payer_email']; |
| 561 |
} |
| 562 |
return $context; |
| 563 |
} |
| 564 |
} |
| 565 |
/** |
| 566 |
* Absolute path to the protected per-order cache directory, without a trailing |
| 567 |
* slash. |
| 568 |
* |
| 569 |
* Lives under wp-content/uploads, never inside the plugin folder: the generated |
| 570 |
* PDF is watermarked with the buyer's email, transaction ID and IP address and |
| 571 |
* must never be reachable over HTTP. |
| 572 |
* |
| 573 |
* @param int|string $order_id Order post ID. |
| 574 |
* @return string Absolute path, or '' when the directory cannot be created. |
| 575 |
*/ |
| 576 |
function ebook_store_get_cache_dir( $order_id ) { |
| 577 |
$uploads = wp_upload_dir(); |
| 578 |
if ( ! empty( $uploads['error'] ) ) { |
| 579 |
return ''; |
| 580 |
} |
| 581 |
|
| 582 |
$base = untrailingslashit( $uploads['basedir'] ) . '/ebook-store-cache'; |
| 583 |
|
| 584 |
// The per-order hash is unchanged from earlier versions so that files moved |
| 585 |
// out of the plugin folder are still found at the same relative location. |
| 586 |
$dir = $base . '/' . md5( $order_id . substr( NONCE_KEY, 0, 8 ) ); |
| 587 |
|
| 588 |
if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) { |
| 589 |
return ''; |
| 590 |
} |
| 591 |
|
| 592 |
// Belt and braces: block direct HTTP access on both Apache and any server |
| 593 |
// that respects a directory index. |
| 594 |
if ( ! file_exists( $base . '/.htaccess' ) ) { |
| 595 |
@file_put_contents( $base . '/.htaccess', "Require all denied\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n" ); |
| 596 |
} |
| 597 |
if ( ! file_exists( $base . '/index.php' ) ) { |
| 598 |
@file_put_contents( $base . '/index.php', "<?php\n// Silence is golden.\n" ); |
| 599 |
} |
| 600 |
|
| 601 |
return $dir; |
| 602 |
} |
| 603 |
function ebook_store_human_filesize($bytes, $decimals = 2) { |
| 604 |
$sz = 'BKMGTP'; |
| 605 |
$bytes = is_numeric($bytes) ? $bytes + 0 : 0; |
| 606 |
// Clamp rather than suppress, see EbookStoreEbook::human_filesize(). |
| 607 |
$factor = (int) floor((strlen((string) $bytes) - 1) / 3); |
| 608 |
$factor = max(0, min($factor, strlen($sz) - 1)); |
| 609 |
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $sz[$factor]; |
| 610 |
} |
| 611 |
|