settings->debug_settings['enable_debug']) ) {
$this->enable_debug();
}
// include template specific custom functions
$template_path = WPO_WCPDF()->settings->get_template_path();
if ( file_exists( $template_path . '/template-functions.php' ) ) {
require_once( $template_path . '/template-functions.php' );
}
// test mode
add_filter( 'wpo_wcpdf_document_use_historical_settings', array( $this, 'test_mode_settings' ), 15, 2 );
// page numbers & currency filters
add_filter( 'wpo_wcpdf_get_html', array($this, 'format_page_number_placeholders' ), 10, 2 );
add_action( 'wpo_wcpdf_after_dompdf_render', array($this, 'page_number_replacements' ), 9, 2 );
if ( isset( WPO_WCPDF()->settings->general_settings['currency_font'] ) ) {
add_action( 'wpo_wcpdf_before_pdf', array($this, 'use_currency_font' ), 10, 2 );
}
// scheduled attachments cleanup (following settings on Status tab)
add_action( 'wp_scheduled_delete', array( $this, 'attachments_cleanup') );
// remove private data
add_action( 'woocommerce_privacy_remove_order_personal_data_meta', array( $this, 'remove_order_personal_data_meta' ), 10, 1 );
add_action( 'woocommerce_privacy_remove_order_personal_data', array( $this, 'remove_order_personal_data' ), 10, 1 );
// export private data
add_action( 'woocommerce_privacy_export_order_personal_data_meta', array( $this, 'export_order_personal_data_meta' ), 10, 1 );
// apply header logo height
add_action( 'wpo_wcpdf_custom_styles', array( $this, 'set_header_logo_height' ), 9, 2 );
}
/**
* Attach PDF to WooCommerce email
*/
public function attach_pdf_to_email ( $attachments, $email_id, $order, $email = null ) {
// check if all variables properly set
if ( !is_object( $order ) || !isset( $email_id ) ) {
return $attachments;
}
// Skip User emails
if ( get_class( $order ) == 'WP_User' ) {
return $attachments;
}
$order_id = WCX_Order::get_id( $order );
if ( ! ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) && $order_id == false ) {
return $attachments;
}
// WooCommerce Booking compatibility
if ( get_post_type( $order_id ) == 'wc_booking' && isset($order->order) ) {
// $order is actually a WC_Booking object!
$order = $order->order;
$order_id = WCX_Order::get_id( $order );
}
// do not process low stock notifications, user emails etc!
if ( in_array( $email_id, array( 'no_stock', 'low_stock', 'backorder', 'customer_new_account', 'customer_reset_password' ) ) ) {
return $attachments;
}
// final check on order object
if ( ! ( $order instanceof \WC_Order || is_subclass_of( $order, '\WC_Abstract_Order') ) ) {
return $attachments;
}
$tmp_path = $this->get_tmp_path('attachments');
// clear pdf files from temp folder (from http://stackoverflow.com/a/13468943/1446634)
// array_map('unlink', ( glob( $tmp_path.'*.pdf' ) ? glob( $tmp_path.'*.pdf' ) : array() ) );
// disable deprecation notices during email sending
add_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
// reload translations because WC may have switched to site locale (by setting the plugin_locale filter to site locale in wc_switch_to_site_locale())
WPO_WCPDF()->translations();
do_action( 'wpo_wcpdf_reload_attachment_translations' );
$attach_to_document_types = $this->get_documents_for_email( $email_id, $order );
foreach ( $attach_to_document_types as $document_type ) {
$email_order = apply_filters( 'wpo_wcpdf_email_attachment_order', $order, $email, $document_type );
$email_order_id = WCX_Order::get_id( $email_order );
do_action( 'wpo_wcpdf_before_attachment_creation', $email_order, $email_id, $document_type );
try {
// prepare document
// we use ID to force to reloading the order to make sure that all meta data is up to date.
// this is especially important when multiple emails with the PDF document are sent in the same session
$document = wcpdf_get_document( $document_type, (array) $email_order_id, true );
if ( !$document ) { // something went wrong, continue trying with other documents
continue;
}
$filename = $document->get_filename();
$pdf_path = $tmp_path . $filename;
$lock_file = apply_filters( 'wpo_wcpdf_lock_attachment_file', true );
// if this file already exists in the temp path, we'll reuse it if it's not older than 60 seconds
$max_reuse_age = apply_filters( 'wpo_wcpdf_reuse_attachment_age', 60 );
if ( file_exists($pdf_path) && $max_reuse_age > 0 ) {
// get last modification date
if ($filemtime = filemtime($pdf_path)) {
$time_difference = time() - $filemtime;
if ( $time_difference < $max_reuse_age ) {
// check if file is still being written to
if ( $lock_file && $this->wait_for_file_lock( $pdf_path ) === false ) {
$attachments[] = $pdf_path;
continue;
} else {
// make sure this gets logged, but don't abort process
wcpdf_log_error( "Attachment file locked (reusing: {$pdf_path})", 'critical' );
}
}
}
}
// get pdf data & store
$pdf_data = $document->get_pdf();
if ( $lock_file ) {
file_put_contents ( $pdf_path, $pdf_data, LOCK_EX );
} else {
file_put_contents ( $pdf_path, $pdf_data );
}
// wait for file lock
if ( $lock_file && $this->wait_for_file_lock( $pdf_path ) === true ) {
wcpdf_log_error( "Attachment file locked ({$pdf_path})", 'critical' );
}
$attachments[] = $pdf_path;
do_action( 'wpo_wcpdf_email_attachment', $pdf_path, $document_type, $document );
} catch ( \Exception $e ) {
wcpdf_log_error( $e->getMessage(), 'critical', $e );
continue;
} catch ( \Dompdf\Exception $e ) {
wcpdf_log_error( 'DOMPDF exception: '.$e->getMessage(), 'critical', $e );
continue;
} catch ( \Error $e ) {
wcpdf_log_error( $e->getMessage(), 'critical', $e );
continue;
}
}
remove_filter( 'wcpdf_disable_deprecation_notices', '__return_true' );
return $attachments;
}
public function file_is_locked( $fp ) {
if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
if ($wouldblock) {
return true; // file is locked
} else {
return true; // can't lock for whatever reason (could be locked in Windows + PHP5.3)
}
} else {
flock($fp,LOCK_UN); // release lock
return false; // not locked
}
}
public function wait_for_file_lock( $path ) {
$fp = fopen($path, 'r+');
if ( $locked = $this->file_is_locked( $fp ) ) {
// optional delay (ms) to double check if the write process is finished
$delay = intval( apply_filters( 'wpo_wcpdf_attachment_locked_file_delay', 250 ) );
if ( $delay > 0 ) {
usleep( $delay * 1000 );
$locked = $this->file_is_locked( $fp );
}
}
fclose($fp);
return $locked;
}
public function get_documents_for_email( $email_id, $order ) {
$documents = WPO_WCPDF()->documents->get_documents();
$attach_documents = array();
foreach ($documents as $document) {
$attach_documents[ $document->get_type() ] = $document->get_attach_to_email_ids();
}
$attach_documents = apply_filters('wpo_wcpdf_attach_documents', $attach_documents );
$document_types = array();
foreach ($attach_documents as $document_type => $attach_to_email_ids ) {
// legacy settings: convert abbreviated email_ids
foreach ($attach_to_email_ids as $key => $attach_to_email_id) {
if ($attach_to_email_id == 'completed' || $attach_to_email_id == 'processing') {
$attach_to_email_ids[$key] = "customer_" . $attach_to_email_id . "_order";
}
}
$extra_condition = apply_filters('wpo_wcpdf_custom_attachment_condition', true, $order, $email_id, $document_type );
if ( in_array( $email_id, $attach_to_email_ids ) && $extra_condition === true ) {
$document_types[] = $document_type;
}
}
return apply_filters( 'wpo_wcpdf_document_types_for_email', $document_types, $email_id, $order );
}
/**
* Load and generate the template output with ajax
*/
public function generate_pdf_ajax() {
$guest_access = isset( WPO_WCPDF()->settings->debug_settings['guest_access'] );
if ( !$guest_access && current_filter() == 'wp_ajax_nopriv_generate_wpo_wcpdf') {
wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
}
// Check the nonce - guest access doesn't use nonces but checks the unique order key (hash)
if( empty( $_GET['action'] ) || ( !$guest_access && !check_admin_referer( $_GET['action'] ) ) ) {
wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
}
// Check if all parameters are set
if ( empty( $_GET['document_type'] ) && !empty( $_GET['template_type'] ) ) {
$_GET['document_type'] = $_GET['template_type'];
}
if ( empty( $_GET['order_ids'] ) ) {
wp_die( __( "You haven't selected any orders", 'woocommerce-pdf-invoices-packing-slips' ) );
}
if( empty( $_GET['document_type'] ) ) {
wp_die( __( 'Some of the export parameters are missing.', 'woocommerce-pdf-invoices-packing-slips' ) );
}
// debug enabled by URL
if ( isset( $_GET['debug'] ) && !( $guest_access || isset( $_GET['my-account'] ) ) ) {
$this->enable_debug();
}
// Generate the output
$document_type = sanitize_text_field( $_GET['document_type'] );
$order_ids = (array) array_map( 'absint', explode( 'x', $_GET['order_ids'] ) );
// Process oldest first: reverse $order_ids array
$order_ids = array_reverse( $order_ids );
// set default is allowed
$allowed = true;
if ( $guest_access && isset( $_GET['order_key'] ) ) {
// Guest access with order key
if ( count( $order_ids ) > 1 ) {
$allowed = false;
} else {
$order = wc_get_order( $order_ids[0] );
if ( !$order || ! hash_equals( $order->get_order_key(), $_GET['order_key'] ) ) {
$allowed = false;
}
}
} else {
// check if user is logged in
if ( ! is_user_logged_in() ) {
$allowed = false;
}
// Check the user privileges
if( !( current_user_can( 'manage_woocommerce_orders' ) || current_user_can( 'edit_shop_orders' ) ) && !isset( $_GET['my-account'] ) ) {
$allowed = false;
}
// User call from my-account page
if ( !current_user_can('manage_options') && isset( $_GET['my-account'] ) ) {
// Only for single orders!
if ( count( $order_ids ) > 1 ) {
$allowed = false;
}
// Check if current user is owner of order IMPORTANT!!!
if ( ! current_user_can( 'view_order', $order_ids[0] ) ) {
$allowed = false;
}
}
}
$allowed = apply_filters( 'wpo_wcpdf_check_privs', $allowed, $order_ids );
if ( ! $allowed ) {
wp_die( __( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ) );
}
// if we got here, we're safe to go!
try {
$document = wcpdf_get_document( $document_type, $order_ids, true );
if ( $document ) {
$output_format = WPO_WCPDF()->settings->get_output_format( $document_type );
// allow URL override
if ( isset( $_GET['output'] ) && in_array( $_GET['output'], array( 'html', 'pdf' ) ) ) {
$output_format = $_GET['output'];
}
switch ( $output_format ) {
case 'html':
add_filter( 'wpo_wcpdf_use_path', '__return_false' );
$document->output_html();
break;
case 'pdf':
default:
if ( has_action( 'wpo_wcpdf_created_manually' ) ) {
do_action( 'wpo_wcpdf_created_manually', $document->get_pdf(), $document->get_filename() );
}
$output_mode = WPO_WCPDF()->settings->get_output_mode( $document_type );
$document->output_pdf( $output_mode );
break;
}
} else {
wp_die( sprintf( __( "Document of type '%s' for the selected order(s) could not be generated", 'woocommerce-pdf-invoices-packing-slips' ), $document_type ) );
}
} catch ( \Dompdf\Exception $e ) {
$message = 'DOMPDF Exception: '.$e->getMessage();
wcpdf_log_error( $message, 'critical', $e );
wcpdf_output_error( $message, 'critical', $e );
} catch ( \Exception $e ) {
$message = 'Exception: '.$e->getMessage();
wcpdf_log_error( $message, 'critical', $e );
wcpdf_output_error( $message, 'critical', $e );
} catch ( \Error $e ) {
$message = 'Fatal error: '.$e->getMessage();
wcpdf_log_error( $message, 'critical', $e );
wcpdf_output_error( $message, 'critical', $e );
}
exit;
}
/**
* Return tmp path for different plugin processes
*/
public function get_tmp_path ( $type = '' ) {
$tmp_base = $this->get_tmp_base();
// don't continue if we don't have an upload dir
if ($tmp_base === false) {
return false;
}
// check if tmp folder exists => if not, initialize
if ( !@is_dir( $tmp_base ) ) {
$this->init_tmp( $tmp_base );
}
if ( empty( $type ) ) {
return $tmp_base;
}
switch ( $type ) {
case 'dompdf':
$tmp_path = $tmp_base . 'dompdf';
break;
case 'font_cache':
case 'fonts':
$tmp_path = $tmp_base . 'fonts';
break;
case 'attachments':
$tmp_path = $tmp_base . 'attachments/';
break;
default:
$tmp_path = $tmp_base . $type;
break;
}
// double check for existence, in case tmp_base was installed, but subfolder not created
if ( !@is_dir( $tmp_path ) ) {
@mkdir( $tmp_path );
}
return $tmp_path;
}
/**
* return the base tmp folder (usually uploads)
*/
public function get_tmp_base () {
// wp_upload_dir() is used to set the base temp folder, under which a
// 'wpo_wcpdf' folder and several subfolders are created
//
// wp_upload_dir() will:
// * default to WP_CONTENT_DIR/uploads
// * UNLESS the ‘UPLOADS’ constant is defined in wp-config (http://codex.wordpress.org/Editing_wp-config.php#Moving_uploads_folder)
//
// May also be overridden by the wpo_wcpdf_tmp_path filter
$upload_dir = wp_upload_dir();
if (!empty($upload_dir['error'])) {
$tmp_base = false;
} else {
$upload_base = trailingslashit( $upload_dir['basedir'] );
$tmp_base = $upload_base . 'wpo_wcpdf/';
}
$tmp_base = apply_filters( 'wpo_wcpdf_tmp_path', $tmp_base );
if ($tmp_base !== false) {
$tmp_base = trailingslashit( $tmp_base );
}
return $tmp_base;
}
/**
* Install/create plugin tmp folders
*/
public function init_tmp ( $tmp_base ) {
// create plugin base temp folder
mkdir( $tmp_base );
if (!is_dir($tmp_base)) {
wcpdf_log_error( "Unable to create temp folder {$tmp_base}", 'critical' );
}
// create subfolders & protect
$subfolders = array( 'attachments', 'fonts', 'dompdf' );
foreach ( $subfolders as $subfolder ) {
$path = $tmp_base . $subfolder . '/';
if ( !is_dir( $path ) ) {
mkdir( $path );
}
// copy font files
if ( $subfolder == 'fonts' ) {
$this->copy_fonts( $path, false );
}
// create .htaccess file and empty index.php to protect in case an open webfolder is used!
file_put_contents( $path . '.htaccess', 'deny from all' );
touch( $path . 'index.php' );
}
}
/**
* Copy DOMPDF fonts to wordpress tmp folder
*/
public function copy_fonts ( $path, $merge_with_local = true ) {
$path = trailingslashit( $path );
$dompdf_font_dir = WPO_WCPDF()->plugin_path() . "/vendor/dompdf/dompdf/lib/fonts/";
// get local font dir from filtered options
$dompdf_options = apply_filters( 'wpo_wcpdf_dompdf_options', array(
'defaultFont' => 'dejavu sans',
'tempDir' => $this->get_tmp_path('dompdf'),
'logOutputFile' => $this->get_tmp_path('dompdf') . "/log.htm",
'fontDir' => $this->get_tmp_path('fonts'),
'fontCache' => $this->get_tmp_path('fonts'),
'isRemoteEnabled' => true,
'isFontSubsettingEnabled' => true,
'isHtml5ParserEnabled' => true,
) );
$fontDir = $dompdf_options['fontDir'];
// merge font family cache with local/custom if present
$font_cache_files = array(
'cache' => 'dompdf_font_family_cache.php',
'cache_dist' => 'dompdf_font_family_cache.dist.php',
);
foreach ( $font_cache_files as $font_cache_name => $font_cache_filename ) {
$plugin_fonts = @require $dompdf_font_dir . $font_cache_filename;
if ( $merge_with_local && is_readable( $path . $font_cache_filename ) ) {
$local_fonts = @require $path . $font_cache_filename;
if (is_array($local_fonts) && is_array($plugin_fonts)) {
// merge local & plugin fonts, plugin fonts overwrite (update) local fonts
// while custom local fonts are retained
$local_fonts = array_merge($local_fonts, $plugin_fonts);
// create readable array with $fontDir in place of the actual folder for portability
$fonts_export = var_export($local_fonts,true);
$fonts_export = str_replace('\'' . $fontDir , '$fontDir . \'', $fonts_export);
$cacheData = sprintf("", $fonts_export, PHP_EOL );
// write file with merged cache data
file_put_contents($path . $font_cache_filename, $cacheData);
} else { // empty local file
copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
}
} else {
// we couldn't read the local font cache file so we're simply copying over plugin cache file
copy( $dompdf_font_dir . $font_cache_filename, $path . $font_cache_filename );
}
}
// first try the easy way with glob!
if ( function_exists('glob') ) {
$files = glob($dompdf_font_dir."*.*");
foreach($files as $file){
$filename = basename($file);
if( !is_dir($file) && is_readable($file) && !in_array($filename, $font_cache_files)) {
$dest = $path . $filename;
copy($file, $dest);
}
}
} else {
// fallback method using font cache file (glob is disabled on some servers with disable_functions)
$extensions = array( '.ttf', '.ufm', '.ufm.php', '.afm', '.afm.php' );
$fontDir = untrailingslashit($dompdf_font_dir);
$plugin_fonts = @require $dompdf_font_dir . $font_cache_files['cache'];
foreach ($plugin_fonts as $font_family => $filenames) {
foreach ($filenames as $filename) {
foreach ($extensions as $extension) {
$file = $filename.$extension;
if (file_exists($file)) {
$dest = $path . basename($file);
copy($file, $dest);
}
}
}
}
}
}
public function disable_free_attachment( $attach, $order, $email_id, $document_type ) {
// prevent fatal error for non-order objects
if ( !method_exists( $order, 'get_total' ) ) {
return false;
}
$document_settings = WPO_WCPDF()->settings->get_document_settings( $document_type );
// check order total & setting
$order_total = $order->get_total();
if ( $order_total == 0 && isset( $document_settings['disable_free'] ) ) {
return false;
}
return $attach;
}
public function test_mode_settings( $use_historical_settings, $document ) {
if ( isset( WPO_WCPDF()->settings->general_settings['test_mode'] ) ) {
$use_historical_settings = false;
}
return $use_historical_settings;
}
/**
* Adds spans around placeholders to be able to make replacement (page count) and css (page number)
*/
public function format_page_number_placeholders ( $html, $document ) {
$html = str_replace('{{PAGE_COUNT}}', '^C^', $html);
$html = str_replace('{{PAGE_NUM}}', '', $html );
return $html;
}
/**
* Replace {{PAGE_COUNT}} placeholder with total page count
*/
public function page_number_replacements ( $dompdf, $html ) {
$placeholder = '^C^';
// create placeholder version with ASCII 0 spaces (dompdf 0.8)
$placeholder_0 = '';
$placeholder_chars = str_split($placeholder);
foreach ($placeholder_chars as $placeholder_char) {
$placeholder_0 .= chr(0).$placeholder_char;
}
// check if placeholder is used
if (strpos($html, $placeholder) !== false ) {
foreach ($dompdf->get_canvas()->get_cpdf()->objects as &$object) {
if (array_key_exists("c", $object) && strpos($object["c"], $placeholder) !== false ) {
$object["c"] = str_replace( array($placeholder,$placeholder_0) , $dompdf->get_canvas()->get_page_count() , $object["c"] );
} elseif (array_key_exists("c", $object) && strpos($object["c"], $placeholder_0) !== false ) {
$object["c"] = str_replace( array($placeholder,$placeholder_0) , chr(0).$dompdf->get_canvas()->get_page_count() , $object["c"] );
}
}
}
return $dompdf;
}
/**
* Use currency symbol font (when enabled in options)
*/
public function use_currency_font ( $document_type, $document ) {
add_filter( 'woocommerce_currency_symbol', array( $this, 'wrap_currency_symbol' ), 10001, 2);
add_action( 'wpo_wcpdf_custom_styles', array($this, 'currency_symbol_font_styles' ) );
}
public function wrap_currency_symbol( $currency_symbol, $currency ) {
$currency_symbol = sprintf( '%s', $currency_symbol );
return $currency_symbol;
}
public function currency_symbol_font_styles () {
?>
.wcpdf-currency-symbol { font-family: 'Currencies'; }
get_header_logo_height() ) {
?>
td.header img {
max-height: ;
}
settings->debug_settings['enable_cleanup'] ) ) {
return;
}
$cleanup_age_days = isset(WPO_WCPDF()->settings->debug_settings['cleanup_days']) ? floatval(WPO_WCPDF()->settings->debug_settings['cleanup_days']) : 7.0;
$delete_timestamp = time() - ( intval ( DAY_IN_SECONDS * $cleanup_age_days ) );
$tmp_path = $this->get_tmp_path('attachments');
if ( $files = glob( $tmp_path.'*.pdf' ) ) { // get all pdf files
foreach( $files as $file ) {
if( is_file( $file ) ) {
$file_timestamp = filemtime( $file );
if ( !empty( $file_timestamp ) && $file_timestamp < $delete_timestamp ) {
@unlink($file);
}
}
}
}
}
/**
* Remove all invoice data when requested
*/
public function remove_order_personal_data_meta( $meta_to_remove ) {
$wcpdf_private_meta = array(
'_wcpdf_invoice_number' => 'numeric_id',
'_wcpdf_invoice_number_data' => 'array',
'_wcpdf_invoice_date' => 'timestamp',
'_wcpdf_invoice_date_formatted' => 'date',
);
return $meta_to_remove + $wcpdf_private_meta;
}
/**
* Remove references to order in number store tables when removing WC data
*/
public function remove_order_personal_data( $order ) {
global $wpdb;
// remove order ID from number stores
$number_stores = apply_filters( "wpo_wcpdf_privacy_number_stores", array( 'invoice_number' ) );
foreach ( $number_stores as $store_name ) {
$order_id = $order->get_id();
$table_name = apply_filters( "wpo_wcpdf_number_store_table_name", "{$wpdb->prefix}wcpdf_{$store_name}", $store_name, 'auto_increment' ); // i.e. wp_wcpdf_invoice_number
$wpdb->query( $wpdb->prepare( "UPDATE $table_name SET order_id = 0 WHERE order_id = %s", $order_id ) );
}
}
/**
* Export all invoice data when requested
*/
public function export_order_personal_data_meta( $meta_to_export ) {
$private_address_meta = array(
// _wcpdf_invoice_number_data & _wcpdf_invoice_date are duplicates of the below and therefor not included
'_wcpdf_invoice_number' => __( 'Invoice Number', 'woocommerce-pdf-invoices-packing-slips' ),
'_wcpdf_invoice_date_formatted' => __( 'Invoice Date', 'woocommerce-pdf-invoices-packing-slips' ),
);
return $meta_to_export + $private_address_meta;
}
/**
* Set the default PHPMailer validator to 'php' ( which uses filter_var($address, FILTER_VALIDATE_EMAIL) )
* This avoids issues with the presence of attachments affecting email address validation in some distros of PHP 7.3
* See: https://wordpress.org/support/topic/invalid-address-setfrom/#post-11583815
* Fixed in WP5.5 due to upgrade to newer PHPMailer
*/
public function set_phpmailer_validator( $mailArray ) {
if ( version_compare( PHP_VERSION, '7.3', '>=' ) && version_compare( get_bloginfo( 'version' ), '5.5-dev', '<' ) ) {
global $phpmailer;
if ( ! ( $phpmailer instanceof \PHPMailer ) ) {
require_once ABSPATH . WPINC . '/class-phpmailer.php';
require_once ABSPATH . WPINC . '/class-smtp.php';
$phpmailer = new \PHPMailer( true );
}
$phpmailer::$validator = 'php';
}
return $mailArray;
}
/**
* Enable PHP error output
*/
public function enable_debug () {
error_reporting( E_ALL );
ini_set( 'display_errors', 1 );
}
}
endif; // class_exists
return new Main();