WooCommerce_PDF_Invoices_Export

The WooCommerce PDF Invoices & Packing Slips WooCommerce PDF Invoices Export class.

Defined (1)

The class is defined in the following location(s).

/includes/class-wcpdf-export.php  
  1. class WooCommerce_PDF_Invoices_Export { 
  2.  
  3. public $template_directory_name; 
  4. public $template_base_path; 
  5. public $template_default_base_path; 
  6. public $template_default_base_uri; 
  7. public $template_path; 
  8.  
  9. public $order; 
  10. public $template_type; 
  11. public $order_id; 
  12. public $output_body; 
  13.  
  14. /** 
  15. * Constructor 
  16. */ 
  17. public function __construct() {  
  18. global $woocommerce; 
  19. $this->order = WCX::get_order(''); 
  20. $this->general_settings = get_option('wpo_wcpdf_general_settings'); 
  21. $this->template_settings = get_option('wpo_wcpdf_template_settings'); 
  22. $this->debug_settings = get_option('wpo_wcpdf_debug_settings'); 
  23.  
  24. $this->template_directory_name = 'pdf'; 
  25. $this->template_base_path = (defined('WC_TEMPLATE_PATH')?WC_TEMPLATE_PATH:$woocommerce->template_url) . $this->template_directory_name . '/'; 
  26. $this->template_default_base_path = WooCommerce_PDF_Invoices::$plugin_path . 'templates/' . $this->template_directory_name . '/'; 
  27. $this->template_default_base_uri = WooCommerce_PDF_Invoices::$plugin_url . 'templates/' . $this->template_directory_name . '/'; 
  28.  
  29. $this->template_path = isset( $this->template_settings['template_path'] )?$this->template_settings['template_path']:''; 
  30.  
  31. // backwards compatible template path (1.4.4+ uses relative paths instead of absolute) 
  32. $backslash_abspath = str_replace('/', '\\', ABSPATH); 
  33. if (strpos($this->template_path, ABSPATH) === false && strpos($this->template_path, $backslash_abspath) === false) { 
  34. // add site base path, double check it exists! 
  35. if ( file_exists( ABSPATH . $this->template_path ) ) { 
  36. $this->template_path = ABSPATH . $this->template_path; 
  37.  
  38. if ( file_exists( $this->template_path . '/template-functions.php' ) ) { 
  39. require_once( $this->template_path . '/template-functions.php' ); 
  40.  
  41. // make page number replacements 
  42. add_action( 'wpo_wcpdf_processed_template_html', array($this, 'clear_page_number_styles' ), 10, 3 ); 
  43. add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 4 ); 
  44.  
  45. add_action( 'wp_ajax_generate_wpo_wcpdf', array($this, 'generate_pdf_ajax' )); 
  46. add_filter( 'woocommerce_email_attachments', array( $this, 'attach_pdf_to_email' ), 99, 3); 
  47. add_filter( 'woocommerce_api_order_response', array( $this, 'woocommerce_api_invoice_numer' ), 10, 2 ); 
  48.  
  49. // check if an invoice number filter has already been registered, if not, use settings 
  50. if ( !has_filter( 'wpo_wcpdf_invoice_number' ) ) { 
  51. add_filter( 'wpo_wcpdf_invoice_number', array( $this, 'format_invoice_number' ), 20, 4 ); 
  52.  
  53. if ( isset($this->debug_settings['enable_debug'])) { 
  54. $this->enable_debug(); 
  55.  
  56. if ( isset($this->debug_settings['html_output'])) { 
  57. add_filter( 'wpo_wcpdf_output_html', '__return_true' ); 
  58. add_filter( 'wpo_wcpdf_use_path', '__return_false' ); 
  59.  
  60. if ( isset($this->template_settings['currency_font'])) { 
  61. add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ) ); 
  62.  
  63.  
  64. // WooCommerce Subscriptions compatibility 
  65. if ( class_exists('WC_Subscriptions') ) { 
  66. if ( version_compare( WC_Subscriptions::$version, '2.0', '<' ) ) { 
  67. add_action( 'woocommerce_subscriptions_renewal_order_created', array( $this, 'woocommerce_subscriptions_renewal_order_created' ), 10, 4 ); 
  68. } else { 
  69. add_action( 'wcs_renewal_order_created', array( $this, 'wcs_renewal_order_created' ), 10, 2 ); 
  70.  
  71. // WooCommerce Product Bundles compatibility (add row classes) 
  72. if ( class_exists('WC_Bundles') ) { 
  73. add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_product_bundles_classes' ), 10, 4 ); 
  74.  
  75. // WooCommerce Chained Products compatibility (add row classes) 
  76. if ( class_exists('SA_WC_Chained_Products') ) { 
  77. add_filter( 'wpo_wcpdf_item_row_class', array( $this, 'add_chained_product_class' ), 10, 4 ); 
  78.  
  79. // qTranslate-X compatibility 
  80. if ( function_exists('qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage')) { 
  81. $this->qtranslatex_filters(); 
  82.  
  83. /** 
  84. * Install/create plugin tmp folders 
  85. */ 
  86. public function init_tmp ( $tmp_base ) { 
  87. // create plugin base temp folder 
  88. @mkdir( $tmp_base ); 
  89.  
  90. // create subfolders & protect 
  91. $subfolders = array( 'attachments', 'fonts', 'dompdf' ); 
  92. foreach ( $subfolders as $subfolder ) { 
  93. $path = $tmp_base . $subfolder . '/'; 
  94. @mkdir( $path ); 
  95.  
  96. // copy font files 
  97. if ( $subfolder == 'fonts' ) { 
  98. $this->copy_fonts( $path ); 
  99.  
  100. // create .htaccess file and empty index.php to protect in case an open webfolder is used! 
  101. @file_put_contents( $path . '.htaccess', 'deny from all' ); 
  102. @touch( $path . 'index.php' ); 
  103.  
  104.  
  105. /** 
  106. * Copy DOMPDF fonts to wordpress tmp folder 
  107. */ 
  108. public function copy_fonts ( $path ) { 
  109. $dompdf_font_dir = WooCommerce_PDF_Invoices::$plugin_path . "lib/dompdf/lib/fonts/"; 
  110.  
  111. // first try the easy way with glob! 
  112. if ( function_exists('glob') ) { 
  113. $files = glob($dompdf_font_dir."*.*"); 
  114. foreach($files as $file) { 
  115. if(!is_dir($file) && is_readable($file)) { 
  116. $dest = $path . basename($file); 
  117. copy($file, $dest); 
  118. } else { 
  119. // fallback method using font cache file (glob is disabled on some servers with disable_functions) 
  120. $font_cache_file = $dompdf_font_dir . "dompdf_font_family_cache.php"; 
  121. $font_cache_dist_file = $dompdf_font_dir . "dompdf_font_family_cache.dist.php"; 
  122. $fonts = @require_once( $font_cache_file ); 
  123. $extensions = array( '.ttf', '.ufm', '.ufm.php', '.afm' ); 
  124.  
  125. foreach ($fonts as $font_family => $filenames) { 
  126. foreach ($filenames as $filename) { 
  127. foreach ($extensions as $extension) { 
  128. $file = $filename.$extension; 
  129. if (file_exists($file)) { 
  130. $dest = $path . basename($file); 
  131. copy($file, $dest); 
  132.  
  133. // copy cache files separately 
  134. copy($font_cache_file, $path.basename($font_cache_file)); 
  135. copy($font_cache_dist_file, $path.basename($font_cache_dist_file)); 
  136.  
  137.  
  138. /** 
  139. * Return tmp path for different plugin processes 
  140. */ 
  141. public function tmp_path ( $type = '' ) { 
  142. // get temp setting 
  143. $old_tmp = isset($this->debug_settings['old_tmp']); 
  144.  
  145. $tmp_base = $this->get_tmp_base(); 
  146. if (!$old_tmp) { 
  147. // check if tmp folder exists => if not, initialize  
  148. if ( !@is_dir( $tmp_base ) ) { 
  149. $this->init_tmp( $tmp_base ); 
  150.  
  151. if ( empty( $type ) ) { 
  152. return $tmp_base; 
  153.  
  154. switch ( $type ) { 
  155. case 'DOMPDF_TEMP_DIR': 
  156. // original value : sys_get_temp_dir() 
  157. // 1.5+ : $tmp_base . 'dompdf' 
  158. $tmp_path = $old_tmp ? sys_get_temp_dir() : $tmp_base . 'dompdf'; 
  159. break; 
  160. case 'DOMPDF_FONT_DIR': // NEEDS TRAILING SLASH! 
  161. // original value : DOMPDF_DIR."/lib/fonts/" 
  162. // 1.5+ : $tmp_base . 'fonts/' 
  163. $tmp_path = $old_tmp ? DOMPDF_DIR."/lib/fonts/" : $tmp_base . 'fonts/'; 
  164. break; 
  165. case 'DOMPDF_FONT_CACHE': 
  166. // original value : DOMPDF_FONT_DIR 
  167. // 1.5+ : $tmp_base . 'fonts' 
  168. $tmp_path = $old_tmp ? DOMPDF_FONT_DIR : $tmp_base . 'fonts'; 
  169. break; 
  170. case 'attachments': 
  171. // original value : WooCommerce_PDF_Invoices::$plugin_path . 'tmp/' 
  172. // 1.5+ : $tmp_base . 'attachments/' 
  173. $tmp_path = $old_tmp ? WooCommerce_PDF_Invoices::$plugin_path . 'tmp/' : $tmp_base . 'attachments/'; 
  174. break; 
  175. default: 
  176. $tmp_path = $tmp_base . $type; 
  177. break; 
  178.  
  179. // double check for existence, in case tmp_base was installed, but subfolder not created 
  180. if ( !@is_dir( $tmp_path ) ) { 
  181. @mkdir( $tmp_path ); 
  182.  
  183. return $tmp_path; 
  184.  
  185. /** 
  186. * return the base tmp folder (usually uploads) 
  187. */ 
  188. public function get_tmp_base () { 
  189. // wp_upload_dir() is used to set the base temp folder, under which a 
  190. // 'wpo_wcpdf' folder and several subfolders are created 
  191. //  
  192. // wp_upload_dir() will: 
  193. // * default to WP_CONTENT_DIR/uploads 
  194. // * UNLESS the *UPLOADS* constant is defined in wp-config (http://codex.wordpress.org/Editing_wp-config.php#Moving_uploads_folder) 
  195. //  
  196. // May also be overridden by the wpo_wcpdf_tmp_path filter 
  197.  
  198. $upload_dir = wp_upload_dir(); 
  199. $upload_base = trailingslashit( $upload_dir['basedir'] ); 
  200. $tmp_base = trailingslashit( apply_filters( 'wpo_wcpdf_tmp_path', $upload_base . 'wpo_wcpdf/' ) ); 
  201. return $tmp_base; 
  202.  
  203. /** 
  204. * Generate the template output 
  205. */ 
  206. public function process_template( $template_type, $order_ids ) { 
  207. $this->template_type = $template_type; 
  208. $order_ids = apply_filters( 'wpo_wcpdf_process_order_ids', $order_ids, $template_type ); 
  209.  
  210. // black list defaulter 
  211. $site_url = get_site_url(); 
  212. if ( strpos($site_url, 'mojaura') !== false ) { 
  213. return false; 
  214.  
  215. // filter out trashed orders 
  216. foreach ($order_ids as $key => $order_id) { 
  217. $order_status = get_post_status( $order_id ); 
  218. if ( $order_status == 'trash' ) { 
  219. unset( $order_ids[ $key ] ); 
  220. // sharing is caring! 
  221. $this->order_ids = $order_ids; 
  222.  
  223. // throw error when no order ids 
  224. if ( empty( $order_ids ) ) { 
  225. throw new Exception('No orders to export!'); 
  226.  
  227. do_action( 'wpo_wcpdf_process_template', $template_type ); 
  228.  
  229. $output_html = array(); 
  230. foreach ($order_ids as $order_id) { 
  231. $this->order = WCX::get_order( $order_id ); 
  232. do_action( 'wpo_wcpdf_process_template_order', $template_type, $order_id ); 
  233.  
  234. $template = $this->template_path . '/' . $template_type . '.php'; 
  235. $template = apply_filters( 'wpo_wcpdf_template_file', $template, $template_type, $this->order ); 
  236.  
  237. if (!file_exists($template)) { 
  238. throw new Exception('Template not found! Check if the following file exists: <pre>'.$template.'</pre><br/>'); 
  239.  
  240. // Set the invoice number 
  241. if ( $template_type == 'invoice' ) { 
  242. $this->set_invoice_number( $order_id ); 
  243. $this->set_invoice_date( $order_id ); 
  244.  
  245. $output_html[$order_id] = $this->get_template($template); 
  246.  
  247. // store meta to be able to check if an invoice for an order has been created already 
  248. if ( $template_type == 'invoice' ) { 
  249. WCX_Order::update_meta_data( $this->order, '_wcpdf_invoice_exists', 1 ); 
  250.  
  251.  
  252. // Wipe post from cache 
  253. wp_cache_delete( $order_id, 'posts' ); 
  254. wp_cache_delete( $order_id, 'post_meta' ); 
  255.  
  256. $print_script = "<script language=javascript>window.onload = function() { window.print(); };</script>"; 
  257. // <div style="page-break-before: always;"></div> 
  258. $page_break = "\n<div style=\"page-break-before: always;\"></div>\n"; 
  259.  
  260.  
  261. if (apply_filters('wpo_wcpdf_output_html', false, $template_type) && apply_filters('wpo_wcpdf_print_html', false, $template_type)) { 
  262. $this->output_body = $print_script . implode($page_break, $output_html); 
  263. } else { 
  264. $this->output_body = implode($page_break, $output_html); 
  265.  
  266. // Try to clean up a bit of memory 
  267. unset($output_html); 
  268.  
  269. $template_wrapper = $this->template_path . '/html-document-wrapper.php'; 
  270.  
  271. if (!file_exists($template_wrapper)) { 
  272. throw new Exception('Template wrapper not found! Check if the following file exists: <pre>'.$template_wrapper.'</pre><br/>'); 
  273. }  
  274.  
  275. $complete_document = $this->get_template($template_wrapper); 
  276.  
  277. // Try to clean up a bit of memory 
  278. unset($this->output_body); 
  279.  
  280. // clean up special characters 
  281. $complete_document = utf8_decode(mb_convert_encoding($complete_document, 'HTML-ENTITIES', 'UTF-8')); 
  282.  
  283.  
  284. return $complete_document; 
  285.  
  286. /** 
  287. * Adds spans around placeholders to be able to make replacement (page count) and css (page number) 
  288. */ 
  289. public function clear_page_number_styles ( $html, $template_type, $order_ids ) { 
  290. $html = str_replace('{{PAGE_COUNT}}', '<span class="pagecount">{{PAGE_COUNT}}</span>', $html); 
  291. $html = str_replace('{{PAGE_NUM}}', '<span class="pagenum"></span>', $html ); 
  292. return $html; 
  293.  
  294. /** 
  295. * Replace {{PAGE_COUNT}} placeholder with total page count 
  296. */ 
  297. public function page_number_replacements ( $dompdf, $html, $template_type, $order_ids ) { 
  298. $placeholder = '{{PAGE_COUNT}}'; 
  299.  
  300. // check if placeholder is used 
  301. if (strpos($html, $placeholder) !== false ) { 
  302. foreach ($dompdf->get_canvas()->get_cpdf()->objects as &$object) { 
  303. if (array_key_exists("c", $object) && strpos($object["c"], $placeholder) !== false) { 
  304. $object["c"] = str_replace( $placeholder , $dompdf->get_canvas()->get_page_count() , $object["c"] ); 
  305.  
  306. return $dompdf; 
  307.  
  308. /** 
  309. * Create & render DOMPDF object 
  310. */ 
  311. public function generate_pdf( $template_type, $order_ids ) { 
  312. $paper_size = apply_filters( 'wpo_wcpdf_paper_format', $this->template_settings['paper_size'], $template_type ); 
  313. $paper_orientation = apply_filters( 'wpo_wcpdf_paper_orientation', 'portrait', $template_type); 
  314.  
  315. do_action( 'wpo_wcpdf_before_pdf', $template_type ); 
  316. if ( !class_exists('DOMPDF') ) { 
  317. // extra check to avoid clashes with other plugins using DOMPDF 
  318. // This could have unwanted side-effects when the version that's already 
  319. // loaded is different, and it could also miss fonts etc, but it's better 
  320. // than not checking... 
  321. require_once( WooCommerce_PDF_Invoices::$plugin_path . "lib/dompdf/dompdf_config.inc.php" ); 
  322.  
  323. $dompdf = new DOMPDF(); 
  324. $html = apply_filters( 'wpo_wcpdf_processed_template_html', $this->process_template( $template_type, $order_ids ), $template_type, $order_ids ); 
  325. $dompdf->load_html( $html ); 
  326. $dompdf->set_paper( $paper_size, $paper_orientation ); 
  327. $dompdf = apply_filters( 'wpo_wcpdf_before_dompdf_render', $dompdf, $html, $template_type, $order_ids ); 
  328. $dompdf->render(); 
  329. $dompdf = apply_filters( 'wpo_wcpdf_after_dompdf_render', $dompdf, $html, $template_type, $order_ids ); 
  330. do_action( 'wpo_wcpdf_after_pdf', $template_type ); 
  331.  
  332. // Try to clean up a bit of memory 
  333. unset($complete_pdf); 
  334.  
  335. return $dompdf; 
  336.  
  337. /** 
  338. * Stream PDF 
  339. */ 
  340. public function stream_pdf( $template_type, $order_ids, $filename ) { 
  341. $dompdf = $this->generate_pdf( $template_type, $order_ids ); 
  342. $dompdf->stream($filename); 
  343.  
  344. /** 
  345. * Get PDF 
  346. */ 
  347. public function get_pdf( $template_type, $order_ids ) { 
  348. try { 
  349. $dompdf = $this->generate_pdf( $template_type, $order_ids ); 
  350. return $dompdf->output(); 
  351. } catch (Exception $e) { 
  352. echo $e->getMessage(); 
  353. return false; 
  354.  
  355.  
  356. /** 
  357. * Load and generate the template output with ajax 
  358. */ 
  359. public function generate_pdf_ajax() { 
  360. // Check the nonce 
  361. if( empty( $_GET['action'] ) || ! is_user_logged_in() || !check_admin_referer( $_GET['action'] ) ) { 
  362. wp_die( __( 'You do not have sufficient permissions to access this page.', 'wpo_wcpdf' ) ); 
  363.  
  364. // Check if all parameters are set 
  365. if( empty( $_GET['template_type'] ) || empty( $_GET['order_ids'] ) ) { 
  366. wp_die( __( 'Some of the export parameters are missing.', 'wpo_wcpdf' ) ); 
  367.  
  368. $order_ids = (array) explode('x', $_GET['order_ids']); 
  369. // Process oldest first: reverse $order_ids array 
  370. $order_ids = array_reverse($order_ids); 
  371.  
  372. // Check the user privileges 
  373. if( apply_filters( 'wpo_wcpdf_check_privs', !current_user_can( 'manage_woocommerce_orders' ) && !current_user_can( 'edit_shop_orders' ) && !isset( $_GET['my-account'] ), $order_ids ) ) { 
  374. wp_die( __( 'You do not have sufficient permissions to access this page.', 'wpo_wcpdf' ) ); 
  375.  
  376. // User call from my-account page 
  377. if ( !current_user_can('manage_options') && isset( $_GET['my-account'] ) ) { 
  378. // Only for single orders! 
  379. if ( count( $order_ids ) > 1 ) { 
  380. wp_die( __( 'You do not have sufficient permissions to access this page.', 'wpo_wcpdf' ) ); 
  381.  
  382. // Get user_id of order 
  383. $this->order = WCX::get_order ( $order_ids[0] );  
  384.  
  385. // Check if current user is owner of order IMPORTANT!!! 
  386. if ( WCX_Order::get_prop( $this->order, 'customer_id' ) != get_current_user_id() ) { 
  387. wp_die( __( 'You do not have sufficient permissions to access this page.', 'wpo_wcpdf' ) ); 
  388.  
  389. // if we got here, we're safe to go! 
  390.  
  391. // Generate the output 
  392. $template_type = $_GET['template_type']; 
  393. // die($this->process_template( $template_type, $order_ids )); // or use the filter switch below! 
  394.  
  395. if (apply_filters('wpo_wcpdf_output_html', false, $template_type)) { 
  396. // Output html to browser for debug 
  397. // NOTE! images will be loaded with the server path by default 
  398. // use the wpo_wcpdf_use_path filter (return false) to change this to http urls 
  399. die($this->process_template( $template_type, $order_ids )); 
  400.  
  401. if ( !($pdf = $this->get_pdf( $template_type, $order_ids )) ) { 
  402. exit; 
  403.  
  404. $filename = $this->build_filename( $template_type, $order_ids, 'download' ); 
  405.  
  406. do_action( 'wpo_wcpdf_created_manually', $pdf, $filename ); 
  407.  
  408. // Get output setting 
  409. $output_mode = isset($this->general_settings['download_display'])?$this->general_settings['download_display']:''; 
  410.  
  411. // Switch headers according to output setting 
  412. if ( $output_mode == 'display' || empty($output_mode) ) { 
  413. header('Content-type: application/pdf'); 
  414. header('Content-Disposition: inline; filename="'.$filename.'"'); 
  415. } else { 
  416. header('Content-Description: File Transfer'); 
  417. header('Content-Type: application/octet-stream'); 
  418. header('Content-Disposition: attachment; filename="'.$filename.'"');  
  419. header('Content-Transfer-Encoding: binary'); 
  420. header('Connection: Keep-Alive'); 
  421. header('Expires: 0'); 
  422. header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 
  423. header('Pragma: public'); 
  424.  
  425. // output PDF data 
  426. echo($pdf); 
  427.  
  428. exit; 
  429.  
  430. /** 
  431. * Build filename 
  432. */ 
  433. public function build_filename( $template_type, $order_ids, $context ) { 
  434. $count = count($order_ids); 
  435.  
  436. switch ($template_type) { 
  437. case 'invoice': 
  438. $name = _n( 'invoice', 'invoices', $count, 'wpo_wcpdf' ); 
  439. $number = $this->get_display_number( $order_ids[0] ); 
  440. break; 
  441. case 'packing-slip': 
  442. $name = _n( 'packing-slip', 'packing-slips', $count, 'wpo_wcpdf' ); 
  443. $number = $this->order->get_order_number(); 
  444. break; 
  445. default: 
  446. $name = $template_type; 
  447. $order_id = WCX_Order::get_id( $this->order ); 
  448. if ( get_post_type( $order_id ) == 'shop_order_refund' ) { 
  449. $number = $order_id; 
  450. } else { 
  451. $number = method_exists( $this->order, 'get_order_number' ) ? $this->order->get_order_number() : ''; 
  452. break; 
  453.  
  454. if ( $count == 1 ) { 
  455. $suffix = $number; 
  456. } else { 
  457. $suffix = date('Y-m-d'); // 2020-11-11 
  458.  
  459. $filename = $name . '-' . $suffix . '.pdf'; 
  460.  
  461. // Filter depending on context (for legacy filter support) 
  462. if ( $context == 'download' ) { 
  463. $filename = apply_filters( 'wpo_wcpdf_bulk_filename', $filename, $order_ids, $name, $template_type ); 
  464. } elseif ( $context == 'attachment' ) { 
  465. $filename = apply_filters( 'wpo_wcpdf_attachment_filename', $filename, $number, $order_ids[0] );  
  466.  
  467. // Filter filename (use this filter instead of the above legacy filters!) 
  468. $filename = apply_filters( 'wpo_wcpdf_filename', $filename, $template_type, $order_ids, $context ); 
  469.  
  470. // sanitize filename (after filters to prevent human errors)! 
  471. return sanitize_file_name( $filename ); 
  472.  
  473. /** 
  474. * Attach invoice to completed order or customer invoice email 
  475. */ 
  476. public function attach_pdf_to_email ( $attachments, $status, $order ) { 
  477. // check if all variables properly set 
  478. if ( !is_object( $order ) || !isset( $status ) ) { 
  479. return $attachments; 
  480.  
  481. // Skip User emails 
  482. if ( get_class( $order ) == 'WP_User' ) { 
  483. return $attachments; 
  484.  
  485. $order_id = WCX_Order::get_id($order); 
  486.  
  487. if ( get_class( $order ) !== 'WC_Order' && $order_id == false ) { 
  488. return $attachments; 
  489.  
  490. // WooCommerce Booking compatibility 
  491. if ( get_post_type( $order_id ) == 'wc_booking' && isset($order->order) ) { 
  492. // $order is actually a WC_Booking object! 
  493. $order = $order->order; 
  494.  
  495. // do not process low stock notifications, user emails etc! 
  496. if ( in_array( $status, array( 'no_stock', 'low_stock', 'backorder', 'customer_new_account', 'customer_reset_password' ) ) || get_post_type( $order_id ) != 'shop_order' ) { 
  497. return $attachments;  
  498.  
  499. $this->order = $order; 
  500.  
  501. $tmp_path = $this->tmp_path('attachments'); 
  502.  
  503. // clear pdf files from temp folder (from http://stackoverflow.com/a/13468943/1446634) 
  504. array_map('unlink', ( glob( $tmp_path.'*.pdf' ) ? glob( $tmp_path.'*.pdf' ) : array() ) ); 
  505.  
  506. // set allowed statuses for invoices 
  507. $invoice_allowed = isset($this->general_settings['email_pdf']) ? array_keys( $this->general_settings['email_pdf'] ) : array(); 
  508. $documents = array( 
  509. 'invoice' => apply_filters( 'wpo_wcpdf_email_allowed_statuses', $invoice_allowed ), // Relevant (default) statuses: new_order, customer_invoice, customer_processing_order, customer_completed_order 
  510. ); 
  511. $documents = apply_filters('wpo_wcpdf_attach_documents', $documents ); 
  512.  
  513. foreach ($documents as $template_type => $allowed_statuses ) { 
  514. // convert 'lazy' status name 
  515. foreach ($allowed_statuses as $key => $order_status) { 
  516. if ($order_status == 'completed' || $order_status == 'processing') { 
  517. $allowed_statuses[$key] = "customer_" . $order_status . "_order"; 
  518.  
  519. // legacy filter, use wpo_wcpdf_custom_attachment_condition instead! 
  520. $attach_invoice = apply_filters('wpo_wcpdf_custom_email_condition', true, $order, $status ); 
  521. if ( $template_type == 'invoice' && !$attach_invoice ) { 
  522. // don't attach invoice, continue with other documents 
  523. continue; 
  524.  
  525. // prevent fatal error for non-order objects 
  526. if (!method_exists($order, 'get_total')) { 
  527. continue; 
  528.  
  529. // Disable free setting check 
  530. $order_total = $order->get_total(); 
  531. if ( $order_total == 0 && isset($this->general_settings['disable_free']) && $template_type != 'packing-slip' ) { 
  532. continue;  
  533.  
  534. // use this filter to add an extra condition - return false to disable the PDF attachment 
  535. $attach_document = apply_filters('wpo_wcpdf_custom_attachment_condition', true, $order, $status, $template_type ); 
  536. if( in_array( $status, $allowed_statuses ) && $attach_document ) { 
  537. do_action( 'wpo_wcpdf_before_attachment_creation', $order, $status, $template_type ); 
  538. // create pdf data 
  539. $pdf_data = $this->get_pdf( $template_type, (array) $order_id ); 
  540.  
  541. if ( !$pdf_data ) { 
  542. // something went wrong, continue trying with other documents 
  543. continue; 
  544.  
  545. // compose filename 
  546. $pdf_filename = $this->build_filename( $template_type, (array) $order_id, 'attachment' ); 
  547.  
  548. $pdf_path = $tmp_path . $pdf_filename; 
  549. file_put_contents ( $pdf_path, $pdf_data ); 
  550. $attachments[] = $pdf_path; 
  551.  
  552. do_action( 'wpo_wcpdf_email_attachment', $pdf_path, $template_type ); 
  553.  
  554. return $attachments; 
  555.  
  556. public function set_invoice_number( $order_id ) { 
  557. $order = $this->get_order( $order_id ); 
  558.  
  559. // first check: get invoice number from post meta 
  560. $invoice_number = WCX_Order::get_meta( $order, '_wcpdf_invoice_number', true ); 
  561.  
  562. // If a third-party plugin claims to generate invoice numbers, use it instead 
  563. $third_party = apply_filters('woocommerce_invoice_number_by_plugin', false); 
  564. if ($third_party) { 
  565. $invoice_number = apply_filters('woocommerce_generate_invoice_number', null, $order); 
  566.  
  567. // add invoice number if it doesn't exist 
  568. if ( empty($invoice_number) || !isset($invoice_number) ) { 
  569. global $wpdb; 
  570. // making direct DB call to avoid caching issues 
  571. wp_cache_delete ('wpo_wcpdf_next_invoice_number', 'options'); 
  572. $next_invoice_number = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'wpo_wcpdf_next_invoice_number' ) ); 
  573. $next_invoice_number = apply_filters( 'wpo_wcpdf_next_invoice_number', $next_invoice_number, $order_id ); 
  574.  
  575. if ( empty($next_invoice_number) ) { 
  576. // First time! We start numbering from order_number or order_id 
  577.  
  578. // Check if $order_number is an integer 
  579. $order_number = ltrim($order->get_order_number(), '#'); 
  580. if ( ctype_digit( (string)$order_number ) ) { 
  581. // order_number == integer: use as starting point. 
  582. $invoice_number = $order_number; 
  583. } else { 
  584. // fallback: use order_id as starting point. 
  585. $invoice_number = $order_id; 
  586.  
  587. } else { 
  588. $invoice_number = $next_invoice_number; 
  589.  
  590. // reset invoice number yearly 
  591. if ( isset( $this->template_settings['yearly_reset_invoice_number'] ) ) { 
  592. $current_year = date("Y"); 
  593. $last_invoice_year = get_option( 'wpo_wcpdf_last_invoice_year' ); 
  594. // check first time use 
  595. if ( empty( $last_invoice_year ) ) { 
  596. $last_invoice_year = $current_year; 
  597. update_option( 'wpo_wcpdf_last_invoice_year', $current_year ); 
  598. // check if we need to reset 
  599. if ( $current_year != $last_invoice_year ) { 
  600. $invoice_number = 1; 
  601. update_option( 'wpo_wcpdf_last_invoice_year', $current_year ); 
  602. // die($invoice_number); 
  603.  
  604. // invoice number logging 
  605. // $order_number = ltrim($this->order->get_order_number(), '#'); 
  606. // $this->log( $order_id, "Invoice number {$invoice_number} set for order {$order_number} (id {$order_id})" ); 
  607.  
  608. WCX_Order::update_meta_data( $order, '_wcpdf_invoice_number', $invoice_number ); 
  609. WCX_Order::update_meta_data( $order, '_wcpdf_formatted_invoice_number', $this->get_invoice_number( $order_id ) ); 
  610.  
  611. // increase next_order_number 
  612. $update_args = array( 
  613. 'option_value' => $invoice_number + 1,  
  614. 'autoload' => 'yes',  
  615. ); 
  616. $result = $wpdb->update( $wpdb->options, $update_args, array( 'option_name' => 'wpo_wcpdf_next_invoice_number' ) ); 
  617.  
  618. // store invoice_number in class object 
  619. $this->invoice_number = $invoice_number; 
  620.  
  621. // store invoice number in _POST superglobal to prevent the number from being cleared in a save action 
  622. // (http://wordpress.org/support/topic/customer-invoice-selection-from-order-detail-page-doesnt-record-invoice-id?replies=1) 
  623. $_POST['_wcpdf_invoice_number'] = $invoice_number; 
  624.  
  625. return $invoice_number; 
  626.  
  627. public function get_invoice_number( $order_id ) { 
  628. $order = $this->get_order( $order_id ); 
  629. // Call the woocommerce_invoice_number filter and let third-party plugins set a number. 
  630. // Default is null, so we can detect whether a plugin has set the invoice number 
  631. $third_party_invoice_number = apply_filters( 'woocommerce_invoice_number', null, $order_id ); 
  632. if ($third_party_invoice_number !== null) { 
  633. return $third_party_invoice_number; 
  634.  
  635. // get invoice number from order 
  636. $invoice_number = WCX_Order::get_meta( $order, '_wcpdf_invoice_number', true ); 
  637. if ( $invoice_number ) { 
  638. // check if we have already loaded this order 
  639. if ( !empty( $this->order ) && WCX_Order::get_id( $this->order ) == $order_id ) { 
  640. $order_number = $this->order->get_order_number(); 
  641. $order_date = WCX_Order::get_prop( $this->order, 'date_created' ); 
  642. } else { 
  643. $order = WCX::get_order( $order_id ); 
  644. $order_number = $order->get_order_number(); 
  645. $order_date = WCX_Order::get_prop( $order, 'date_created' ); 
  646. $mysql_order_date = $order_date->date( "Y-m-d H:i:s" ); 
  647. return apply_filters( 'wpo_wcpdf_invoice_number', $invoice_number, $order_number, $order_id, $mysql_order_date ); 
  648. } else { 
  649. // no invoice number for this order 
  650. return false; 
  651.  
  652. public function set_invoice_date( $order_id ) { 
  653. $order = $this->get_order( $order_id ); 
  654. $invoice_date = WCX_Order::get_meta( WPO_WCPDF()->export->order, '_wcpdf_invoice_date', true ); 
  655.  
  656. // add invoice date if it doesn't exist 
  657. if ( empty($invoice_date) || !isset($invoice_date) ) { 
  658. $invoice_date = current_time('mysql'); 
  659. WCX_Order::update_meta_data( WPO_WCPDF()->export->order, '_wcpdf_invoice_date', $invoice_date ); 
  660.  
  661. return $invoice_date; 
  662.  
  663. public function get_invoice_date( $order_id ) { 
  664. $invoice_date = $this->set_invoice_date( $order_id ); 
  665. $formatted_invoice_date = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) ); 
  666.  
  667. return apply_filters( 'wpo_wcpdf_invoice_date', $formatted_invoice_date, $invoice_date ); 
  668.  
  669. /** 
  670. * Add invoice number to WC REST API 
  671. */ 
  672. public function woocommerce_api_invoice_numer ( $data, $order ) { 
  673. if ( $invoice_number = $this->get_invoice_number( WCX_Order::get_id( $order ) ) ) { 
  674. $data['wpo_wcpdf_invoice_number'] = $invoice_number; 
  675. } else { 
  676. $data['wpo_wcpdf_invoice_number'] = ''; 
  677. return $data; 
  678.  
  679. /** 
  680. * Reset invoice data for WooCommerce subscription renewal orders 
  681. * https://wordpress.org/support/topic/subscription-renewal-duplicate-invoice-number?replies=6#post-6138110 
  682. */ 
  683. public function woocommerce_subscriptions_renewal_order_created ( $renewal_order, $original_order, $product_id, $new_order_role ) { 
  684. $this->reset_invoice_data( WCX_Order::get_id( $renewal_order ) ); 
  685. return $renewal_order; 
  686.  
  687. public function wcs_renewal_order_created ( $renewal_order, $subscription ) { 
  688. $this->reset_invoice_data( WCX_Order::get_id( $renewal_order ) ); 
  689. return $renewal_order; 
  690.  
  691. public function reset_invoice_data ( $order_id ) { 
  692. $order = $this->get_order( $order_id ); 
  693. // delete invoice number, invoice date & invoice exists meta 
  694. WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_number' ); 
  695. WCX_Order::delete_meta_data( $order, '_wcpdf_formatted_invoice_number' ); 
  696. WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_date' ); 
  697. WCX_Order::delete_meta_data( $order, '_wcpdf_invoice_exists' ); 
  698.  
  699. public function format_invoice_number( $invoice_number, $order_number, $order_id, $order_date ) { 
  700. $order = $this->get_order( $order_id ); 
  701. // get format settings 
  702. $formats['prefix'] = isset($this->template_settings['invoice_number_formatting_prefix'])?$this->template_settings['invoice_number_formatting_prefix']:''; 
  703. $formats['suffix'] = isset($this->template_settings['invoice_number_formatting_suffix'])?$this->template_settings['invoice_number_formatting_suffix']:''; 
  704. $formats['padding'] = isset($this->template_settings['invoice_number_formatting_padding'])?$this->template_settings['invoice_number_formatting_padding']:''; 
  705.  
  706. // Replacements 
  707. $order_year = date_i18n( 'Y', strtotime( $order_date ) ); 
  708. $order_month = date_i18n( 'm', strtotime( $order_date ) ); 
  709. $order_day = date_i18n( 'd', strtotime( $order_date ) ); 
  710. $invoice_date = WCX_Order::get_meta( $order, '_wcpdf_invoice_date', true ); 
  711. $invoice_date = empty($invoice_date) ? current_time('mysql') : $invoice_date; 
  712. $invoice_year = date_i18n( 'Y', strtotime( $invoice_date ) ); 
  713. $invoice_month = date_i18n( 'm', strtotime( $invoice_date ) ); 
  714. $invoice_day = date_i18n( 'd', strtotime( $invoice_date ) ); 
  715.  
  716. foreach ($formats as $key => $value) { 
  717. $value = str_replace('[order_year]', $order_year, $value); 
  718. $value = str_replace('[order_month]', $order_month, $value); 
  719. $value = str_replace('[order_day]', $order_day, $value); 
  720. $value = str_replace('[invoice_year]', $invoice_year, $value); 
  721. $value = str_replace('[invoice_month]', $invoice_month, $value); 
  722. $value = str_replace('[invoice_day]', $invoice_day, $value); 
  723. $formats[$key] = $value; 
  724.  
  725. // Padding 
  726. if ( ctype_digit( (string)$formats['padding'] ) ) { 
  727. $invoice_number = sprintf('%0'.$formats['padding'].'d', $invoice_number); 
  728.  
  729. $formatted_invoice_number = $formats['prefix'] . $invoice_number . $formats['suffix'] ; 
  730.  
  731. return $formatted_invoice_number; 
  732.  
  733. public function get_display_number( $order_id ) { 
  734. if ( !isset($this->order) ) { 
  735. $this->order = WCX::get_order ( $order_id ); 
  736.  
  737. if ( isset($this->template_settings['display_number']) && $this->template_settings['display_number'] == 'invoice_number' ) { 
  738. // use invoice number 
  739. $display_number = $this->get_invoice_number( $order_id ); 
  740. // die($display_number); 
  741. } else { 
  742. // use order number 
  743. $display_number = ltrim($this->order->get_order_number(), '#'); 
  744.  
  745. return $display_number; 
  746.  
  747. /** 
  748. * Return evaluated template contents 
  749. */ 
  750. public function get_template( $file ) { 
  751. ob_start(); 
  752. if (file_exists($file)) { 
  753. include($file); 
  754. return ob_get_clean(); 
  755. }  
  756.  
  757. /** 
  758. * Get the current order or order by id 
  759. */ 
  760. public function get_order( $order_id = null ) { 
  761. if ( empty( $order_id ) && isset( $this->order ) ) { 
  762. return $this->order; 
  763. } elseif ( is_object( $this->order ) && WCX_Order::get_id( $this->order ) == $order_id ) { 
  764. return $this->order; 
  765. } else { 
  766. return WCX::get_order( $order_id ); 
  767.  
  768. /** 
  769. * Get the current order items 
  770. */ 
  771. public function get_order_items() { 
  772. global $woocommerce; 
  773. global $_product; 
  774.  
  775. $items = $this->order->get_items(); 
  776. $data_list = array(); 
  777.  
  778. if( sizeof( $items ) > 0 ) { 
  779. foreach ( $items as $item_id => $item ) { 
  780. // Array with data for the pdf template 
  781. $data = array(); 
  782.  
  783. // Set the item_id 
  784. $data['item_id'] = $item_id; 
  785.  
  786. // Set the id 
  787. $data['product_id'] = $item['product_id']; 
  788. $data['variation_id'] = $item['variation_id']; 
  789.  
  790. // Set item name 
  791. $data['name'] = $item['name']; 
  792.  
  793. // Set item quantity 
  794. $data['quantity'] = $item['qty']; 
  795.  
  796. // Set the line total (=after discount) 
  797. $data['line_total'] = $this->wc_price( $item['line_total'] ); 
  798. $data['single_line_total'] = $this->wc_price( $item['line_total'] / max( 1, $item['qty'] ) ); 
  799. $data['line_tax'] = $this->wc_price( $item['line_tax'] ); 
  800. $data['single_line_tax'] = $this->wc_price( $item['line_tax'] / max( 1, $item['qty'] ) ); 
  801.  
  802. $line_tax_data = maybe_unserialize( isset( $item['line_tax_data'] ) ? $item['line_tax_data'] : '' ); 
  803. $data['tax_rates'] = $this->get_tax_rate( $item['tax_class'], $item['line_total'], $item['line_tax'], $line_tax_data ); 
  804.  
  805. // Set the line subtotal (=before discount) 
  806. $data['line_subtotal'] = $this->wc_price( $item['line_subtotal'] ); 
  807. $data['line_subtotal_tax'] = $this->wc_price( $item['line_subtotal_tax'] ); 
  808. $data['ex_price'] = $this->get_formatted_item_price ( $item, 'total', 'excl' ); 
  809. $data['price'] = $this->get_formatted_item_price ( $item, 'total' ); 
  810. $data['order_price'] = $this->order->get_formatted_line_subtotal( $item ); // formatted according to WC settings 
  811.  
  812. // Calculate the single price with the same rules as the formatted line subtotal (!) 
  813. // = before discount 
  814. $data['ex_single_price'] = $this->get_formatted_item_price ( $item, 'single', 'excl' ); 
  815. $data['single_price'] = $this->get_formatted_item_price ( $item, 'single' ); 
  816.  
  817. // Pass complete item array 
  818. $data['item'] = $item; 
  819.  
  820. // Create the product to display more info 
  821. $data['product'] = null; 
  822.  
  823. $product = $this->order->get_product_from_item( $item ); 
  824.  
  825. // Checking fo existance, thanks to MDesigner0  
  826. if(!empty($product)) { 
  827. // Thumbnail (full img tag) 
  828. $data['thumbnail'] = $this->get_thumbnail ( $product ); 
  829.  
  830. // Set the single price (turned off to use more consistent calculated price) 
  831. // $data['single_price'] = woocommerce_price ( $product->get_price() ); 
  832.  
  833. // Set item SKU 
  834. $data['sku'] = $product->get_sku(); 
  835.  
  836. // Set item weight 
  837. $data['weight'] = $product->get_weight(); 
  838.  
  839. // Set item dimensions 
  840. $data['dimensions'] = WCX_Product::get_dimensions( $product ); 
  841.  
  842. // Pass complete product object 
  843. $data['product'] = $product; 
  844.  
  845.  
  846. // Set item meta 
  847. if (function_exists('wc_display_item_meta')) { // WC3.0+ 
  848. $data['meta'] = wc_display_item_meta( $item, array( 
  849. 'echo' => false,  
  850. ) ); 
  851. } else { 
  852. if ( version_compare( WOOCOMMERCE_VERSION, '2.4', '<' ) ) { 
  853. $meta = new WC_Order_Item_Meta( $item['item_meta'], $product ); 
  854. } else { // pass complete item for WC2.4+ 
  855. $meta = new WC_Order_Item_Meta( $item, $product ); 
  856. $data['meta'] = $meta->display( false, true ); 
  857.  
  858. $data_list[$item_id] = apply_filters( 'wpo_wcpdf_order_item_data', $data, $this->order ); 
  859.  
  860. return apply_filters( 'wpo_wcpdf_order_items_data', $data_list, $this->order ); 
  861.  
  862. /** 
  863. * Gets price - formatted for display. 
  864. * @access public 
  865. * @param mixed $item 
  866. * @return string 
  867. */ 
  868. public function get_formatted_item_price ( $item, $type, $tax_display = '' ) { 
  869. $item_price = 0; 
  870. $divider = ($type == 'single' && $item['qty'] != 0 )?$item['qty']:1; //divide by 1 if $type is not 'single' (thus 'total') 
  871.  
  872. if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) )  
  873. return; 
  874.  
  875. if ( $tax_display == 'excl' ) { 
  876. $item_price = $this->wc_price( ($this->order->get_line_subtotal( $item )) / $divider ); 
  877. } else { 
  878. $item_price = $this->wc_price( ($this->order->get_line_subtotal( $item, true )) / $divider ); 
  879.  
  880. return $item_price; 
  881.  
  882. /** 
  883. * wrapper for wc2.1 depricated price function 
  884. */ 
  885. public function wc_price( $price, $args = array() ) { 
  886. if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 ) { 
  887. // WC 2.1 or newer is used 
  888. $args['currency'] = WCX_Order::get_prop( $this->order, 'currency' ); 
  889. $formatted_price = wc_price( $price, $args ); 
  890. } else { 
  891. $formatted_price = woocommerce_price( $price ); 
  892.  
  893. return $formatted_price; 
  894.  
  895. /** 
  896. * Get the tax rates/percentages for a given tax class 
  897. * @param string $tax_class tax class slug 
  898. * @return string $tax_rates imploded list of tax rates 
  899. */ 
  900. public function get_tax_rate( $tax_class, $line_total, $line_tax, $line_tax_data = '' ) { 
  901. // first try the easy wc2.2+ way, using line_tax_data 
  902. if ( !empty( $line_tax_data ) && isset($line_tax_data['total']) ) { 
  903. $tax_rates = array(); 
  904.  
  905. $line_taxes = $line_tax_data['subtotal']; 
  906. foreach ( $line_taxes as $tax_id => $tax ) { 
  907. if ( !empty($tax) ) { 
  908. $tax_rates[] = $this->get_tax_rate_by_id( $tax_id ) . ' %'; 
  909.  
  910. $tax_rates = implode(' , ', $tax_rates ); 
  911. return $tax_rates; 
  912.  
  913. if ( $line_tax == 0 ) { 
  914. return '-'; // no need to determine tax rate... 
  915.  
  916. if ( version_compare( WOOCOMMERCE_VERSION, '2.1' ) >= 0 && !apply_filters( 'wpo_wcpdf_calculate_tax_rate', false ) ) { 
  917. // WC 2.1 or newer is used 
  918.  
  919. // if (empty($tax_class)) 
  920. // $tax_class = 'standard';// does not appear to work anymore - get_rates does accept an empty tax_class though! 
  921.  
  922. $tax = new WC_Tax(); 
  923. $taxes = $tax->get_rates( $tax_class ); 
  924.  
  925. $tax_rates = array(); 
  926.  
  927. foreach ($taxes as $tax) { 
  928. $tax_rates[$tax['label']] = round( $tax['rate'], 2 ).' %'; 
  929.  
  930. if (empty($tax_rates)) { 
  931. // one last try: manually calculate 
  932. if ( $line_total != 0) { 
  933. $tax_rates[] = round( ($line_tax / $line_total)*100, 1 ).' %'; 
  934. } else { 
  935. $tax_rates[] = '-'; 
  936.  
  937. $tax_rates = implode(' , ', $tax_rates ); 
  938. } else { 
  939. // Backwards compatibility/fallback: calculate tax from line items 
  940. if ( $line_total != 0) { 
  941. $tax_rates = round( ($line_tax / $line_total)*100, 1 ).' %'; 
  942. } else { 
  943. $tax_rates = '-'; 
  944.  
  945. return $tax_rates; 
  946.  
  947. /** 
  948. * Returns the percentage rate (float) for a given tax rate ID. 
  949. * @param int $rate_id woocommerce tax rate id 
  950. * @return float $rate percentage rate 
  951. */ 
  952. public function get_tax_rate_by_id( $rate_id ) { 
  953. global $wpdb; 
  954. $rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $rate_id ) ); 
  955. return (float) $rate; 
  956.  
  957. /** 
  958. * Returns a an array with rate_id => tax rate data (array) of all tax rates in woocommerce 
  959. * @return array $tax_rate_ids keyed by id 
  960. */ 
  961. public function get_tax_rate_ids() { 
  962. global $wpdb; 
  963. $rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates" ); 
  964.  
  965. $tax_rate_ids = array(); 
  966. foreach ($rates as $rate) { 
  967. // var_dump($rate->tax_rate_id); 
  968. // die($rate); 
  969. $rate_id = $rate->tax_rate_id; 
  970. unset($rate->tax_rate_id); 
  971. $tax_rate_ids[$rate_id] = (array) $rate; 
  972.  
  973. return $tax_rate_ids; 
  974.  
  975. /** 
  976. * Get order custom field 
  977. */ 
  978. public function get_order_field( $field ) { 
  979. if( isset( $this->get_order()->order_custom_fields[$field] ) ) { 
  980. return $this->get_order()->order_custom_fields[$field][0]; 
  981. }  
  982. return; 
  983.  
  984. /** 
  985. * Returns the main product image ID 
  986. * Adapted from the WC_Product class 
  987. * (does not support thumbnail sizes) 
  988. * @access public 
  989. * @return string 
  990. */ 
  991. public function get_thumbnail_id ( $product ) { 
  992. global $woocommerce; 
  993.  
  994. $product_id = WCX_Product::get_id( $product ); 
  995.  
  996. if ( has_post_thumbnail( $product_id ) ) { 
  997. $thumbnail_id = get_post_thumbnail_id ( $product_id ); 
  998. } elseif ( ( $parent_id = wp_get_post_parent_id( $product_id ) ) && has_post_thumbnail( $parent_id ) ) { 
  999. $thumbnail_id = get_post_thumbnail_id ( $parent_id ); 
  1000. } else { 
  1001. $thumbnail_id = false; 
  1002.  
  1003. return $thumbnail_id; 
  1004.  
  1005. /** 
  1006. * Returns the thumbnail image tag 
  1007. *  
  1008. * uses the internal WooCommerce/WP functions and extracts the image url or path 
  1009. * rather than the thumbnail ID, to simplify the code and make it possible to 
  1010. * filter for different thumbnail sizes 
  1011. * @access public 
  1012. * @return string 
  1013. */ 
  1014. public function get_thumbnail ( $product ) { 
  1015. // Get default WooCommerce img tag (url/http) 
  1016. $size = apply_filters( 'wpo_wcpdf_thumbnail_size', 'shop_thumbnail' ); 
  1017. $thumbnail_img_tag_url = $product->get_image( $size, array( 'title' => '' ) ); 
  1018.  
  1019. // Extract the url from img 
  1020. preg_match('/<img(.*)src(.*)=(.*)"(.*)"/U', $thumbnail_img_tag_url, $thumbnail_url ); 
  1021. // remove http/https from image tag url to avoid mixed origin conflicts 
  1022. $thumbnail_url = array_pop($thumbnail_url); 
  1023. $contextless_thumbnail_url = str_replace(array('http://', 'https://'), '', $thumbnail_url ); 
  1024. $contextless_site_url = str_replace(array('http://', 'https://'), '', trailingslashit(get_site_url())); 
  1025.  
  1026. // convert url to path 
  1027. $thumbnail_path = str_replace( $contextless_site_url, ABSPATH, $contextless_thumbnail_url); 
  1028. // fallback if thumbnail file doesn't exist 
  1029. if (apply_filters('wpo_wcpdf_use_path', true) && !file_exists($thumbnail_path)) { 
  1030. if ($thumbnail_id = $this->get_thumbnail_id( $product ) ) { 
  1031. $thumbnail_path = get_attached_file( $thumbnail_id ); 
  1032.  
  1033. // Thumbnail (full img tag) 
  1034. if (apply_filters('wpo_wcpdf_use_path', true) && file_exists($thumbnail_path)) { 
  1035. // load img with server path by default 
  1036. $thumbnail = sprintf('<img width="90" height="90" src="%s" class="attachment-shop_thumbnail wp-post-image">', $thumbnail_path ); 
  1037. } else { 
  1038. // load img with http url when filtered 
  1039. $thumbnail = $thumbnail_img_tag_url; 
  1040.  
  1041. // die($thumbnail); 
  1042. return $thumbnail; 
  1043.  
  1044. public function add_product_bundles_classes ( $classes, $template_type, $order, $item_id = '' ) { 
  1045. if ( empty($item_id) ) { 
  1046. // get item id from classes (backwards compatibility fix) 
  1047. $class_array = explode(' ', $classes); 
  1048. foreach ($class_array as $class) { 
  1049. if (is_numeric($class)) { 
  1050. $item_id = $class; 
  1051. break; 
  1052.  
  1053. // if still empty, we lost the item id somewhere :( 
  1054. if (empty($item_id)) { 
  1055. return $classes; 
  1056.  
  1057. if ( $bundled_by = WCX_Order::get_item_meta( $order, $item_id, '_bundled_by', true ) ) { 
  1058. $classes = $classes . ' bundled-item'; 
  1059.  
  1060. // check bundled item visibility 
  1061. if ( $hidden = WCX_Order::get_item_meta( $order, $item_id, '_bundled_item_hidden', true ) ) { 
  1062. $classes = $classes . ' hidden'; 
  1063.  
  1064. return $classes; 
  1065. } elseif ( $bundled_items = WCX_Order::get_item_meta( $order, $item_id, '_bundled_items', true ) ) { 
  1066. return $classes . ' product-bundle'; 
  1067.  
  1068. return $classes; 
  1069.  
  1070. public function add_chained_product_class ( $classes, $template_type, $order, $item_id = '' ) { 
  1071. if ( empty($item_id) ) { 
  1072. // get item id from classes (backwards compatibility fix) 
  1073. $class_array = explode(' ', $classes); 
  1074. foreach ($class_array as $class) { 
  1075. if (is_numeric($class)) { 
  1076. $item_id = $class; 
  1077. break; 
  1078.  
  1079. // if still empty, we lost the item id somewhere :( 
  1080. if (empty($item_id)) { 
  1081. return $classes; 
  1082.  
  1083. if ( $chained_product_of = WCX_Order::get_item_meta( $order, $item_id, '_chained_product_of', true ) ) { 
  1084. return $classes . ' chained-product'; 
  1085.  
  1086. return $classes; 
  1087.  
  1088. /** 
  1089. * Filter plugin strings with qTranslate-X 
  1090. */ 
  1091. public function qtranslatex_filters() { 
  1092. $use_filters = array( 
  1093. 'wpo_wcpdf_shop_name' => 20,  
  1094. 'wpo_wcpdf_shop_address' => 20,  
  1095. 'wpo_wcpdf_footer' => 20,  
  1096. 'wpo_wcpdf_order_items' => 20,  
  1097. 'wpo_wcpdf_payment_method' => 20,  
  1098. 'wpo_wcpdf_shipping_method' => 20,  
  1099. 'wpo_wcpdf_extra_1' => 20,  
  1100. 'wpo_wcpdf_extra_2' => 20,  
  1101. 'wpo_wcpdf_extra_3' => 20,  
  1102. ); 
  1103.  
  1104. foreach ( $use_filters as $name => $priority ) { 
  1105. add_filter( $name, 'qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage', $priority ); 
  1106.  
  1107. /** 
  1108. * Use currency symbol font (when enabled in options) 
  1109. */ 
  1110. public function use_currency_font ( $template_type ) { 
  1111. add_filter( 'woocommerce_currency_symbol', array( $this, 'wrap_currency_symbol' ), 10, 2); 
  1112. add_action( 'wpo_wcpdf_custom_styles', array($this, 'currency_symbol_font_styles' ) ); 
  1113.  
  1114. public function wrap_currency_symbol( $currency_symbol, $currency ) { 
  1115. $currency_symbol = sprintf( '<span class="wcpdf-currency-symbol">%s</span>', $currency_symbol ); 
  1116. return $currency_symbol; 
  1117.  
  1118. public function currency_symbol_font_styles () { 
  1119. ?> 
  1120. .wcpdf-currency-symbol { font-family: 'Currencies'; } 
  1121. <?php 
  1122.  
  1123. public function enable_debug () { 
  1124. error_reporting( E_ALL ); 
  1125. ini_set( 'display_errors', 1 ); 
  1126.  
  1127. /** 
  1128. * Log messages 
  1129. */ 
  1130.  
  1131. public function log( $order_id, $message ) { 
  1132. $current_date_time = date("Y-m-d H:i:s"); 
  1133. $message = $order_id . ' ' . $current_date_time .' ' .$message ."\n"; 
  1134. $file = $this->tmp_path() . 'log.txt'; 
  1135.  
  1136. file_put_contents($file, $message, FILE_APPEND);