uest );
if ( is_wp_error( $user ) ) {
return $user;
}
if ( ! current_user_can( 'delete_app_password', $user->ID, $request['uuid'] ) ) {
return new WP_Error(
'rest_cannot_delete_application_password',
__( 'Sorry, you are not allowed to delete this application password.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Deletes an application password for a user.
*
* @since 5.6.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$user = $this->get_user( $request );
if ( is_wp_error( $user ) ) {
return $user;
}
$password = $this->get_application_password( $request );
if ( is_wp_error( $password ) ) {
return $password;
}
$request->set_param( 'context', 'edit' );
$previous = $this->prepare_item_for_response( $password, $request );
$deleted = WP_Application_Passwords::delete_application_password( $user->ID, $password['uuid'] );
if ( is_wp_error( $deleted ) ) {
return $deleted;
}
return new WP_REST_Response(
array(
'deleted' => true,
'previous' => $previous->get_data(),
)
);
}
/**
* Checks if a given request has access to get the currently used application password for a user.
*
* @since 5.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
*/
public function get_current_item_permissions_check( $request ) {
$user = $this->get_user( $request );
if ( is_wp_error( $user ) ) {
return $user;
}
if ( get_current_user_id() !== $user->ID ) {
return new WP_Error(
'rest_cannot_introspect_app_password_for_non_authenticated_user',
__( 'The authenticated application password can only be introspected for the current user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Retrieves the application password being currently used for authentication of a user.
*
* @since 5.7.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_current_item( $request ) {
$user = $this->get_user( $request );
if ( is_wp_error( $user ) ) {
return $user;
}
$uuid = rest_get_authenticated_app_password();
if ( ! $uuid ) {
return new WP_Error(
'rest_no_authenticated_app_password',
__( 'Cannot introspect application password.' ),
array( 'status' => 404 )
);
}
$password = WP_Application_Passwords::get_user_application_password( $user->ID, $uuid );
if ( ! $password ) {
return new WP_Error(
'rest_application_password_not_found',
__( 'Application password not found.' ),
array( 'status' => 500 )
);
}
return $this->prepare_item_for_response( $password, $request );
}
/**
* Performs a permissions check for the request.
*
* @since 5.6.0
* @deprecated 5.7.0 Use `edit_user` directly or one of the specific meta capabilities introduced in 5.7.0.
*
* @param WP_REST_Request $request
* @return true|WP_Error
*/
protected function do_permissions_check( $request ) {
_deprecated_function( __METHOD__, '5.7.0' );
$user = $this->get_user( $request );
if ( is_wp_error( $user ) ) {
return $user;
}
if ( ! current_user_can( 'edit_user', $user->ID ) ) {
return new WP_Error(
'rest_cannot_manage_application_passwords',
__( 'Sorry, you are not allowed to manage application passwords for this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Prepares an application password for a create or update operation.
*
* @since 5.6.0
*
* @param WP_REST_Request $request Request object.
* @return object|WP_Error The prepared item, or WP_Error object on failure.
*/
protected function prepare_item_for_database( $request ) {
$prepared = (object) array(
'name' => $request['name'],
);
if ( $request['app_id'] && ! $request['uuid'] ) {
$prepared->app_id = $request['app_id'];
}
/**
* Filters an application password before it is inserted via the REST API.
*
* @since 5.6.0
*
* @param stdClass $prepared An object representing a single application password prepared for inserting or updating the database.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'rest_pre_insert_application_password', $prepared, $request );
}
/**
* Prepares the application password for the REST response.
*
* @since 5.6.0
*
* @param array $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function prepare_item_for_response( $item, $request ) {
$user = $this->get_user( $request );
if ( is_wp_error( $user ) ) {
return $user;
}
$fields = $this->get_fields_for_response( $request );
$prepared = array(
'uuid' => $item['uuid'],
'app_id' => empty( $item['app_id'] ) ? '' : $item['app_id'],
'name' => $item['name'],
'created' => gmdate( 'Y-m-d\TH:i:s', $item['created'] ),
'last_used' => $item['last_used'] ? gmdate( 'Y-m-d\TH:i:s', $item['last_used'] ) : null,
'last_ip' => $item['last_ip'] ? $item['last_ip'] : null,
);
if ( isset( $item['new_password'] ) ) {
$prepared['password'] = $item['new_password'];
}
$prepared = $this->add_additional_fields_to_object( $prepared, $request );
$prepared = $this->filter_response_by_context( $prepared, $request['context'] );
$response = new WP_REST_Response( $prepared );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$response->add_links( $this->prepare_links( $user, $item ) );
}
/**
* Filters the REST API response for an application password.
*
* @since 5.6.0
*
* @param WP_REST_Response $response The response object.
* @param array $item The application password array.
* @param WP_REST_Request $request The request object.
*/
return apply_filters( 'rest_prepare_application_password', $response, $item, $request );
}
/**
* Prepares links for the request.
*
* @since 5.6.0
*
* @param WP_User $user The requested user.
* @param array $item The application password.
* @return array The list of links.
*/
protected function prepare_links( WP_User $user, $item ) {
return array(
'self' => array(
'href' => rest_url(
sprintf(
'%s/users/%d/application-passwords/%s',
$this->namespace,
$user->ID,
$item['uuid']
)
),
),
);
}
/**
* Gets the requested user.
*
* @since 5.6.0
*
* @param WP_REST_Request $request The request object.
* @return WP_User|WP_Error The WordPress user associated with the request, or a WP_Error if none found.
*/
protected function get_user( $request ) {
if ( ! wp_is_application_passwords_available() ) {
return new WP_Error(
'application_passwords_disabled',
__( 'Application passwords are not available.' ),
array( 'status' => 501 )
);
}
$error = new WP_Error(
'rest_user_invalid_id',
__( 'Invalid user ID.' ),
array( 'status' => 404 )
);
$id = $request['user_id'];
if ( 'me' === $id ) {
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_not_logged_in',
__( 'You are not currently logged in.' ),
array( 'status' => 401 )
);
}
$user = wp_get_current_user();
} else {
$id = (int) $id;
if ( $id <= 0 ) {
return $error;
}
$user = get_userdata( $id );
}
if ( empty( $user ) || ! $user->exists() ) {
return $error;
}
if ( is_multisite() && ! user_can( $user->ID, 'manage_sites' ) && ! is_user_member_of_blog( $user->ID ) ) {
return $error;
}
if ( ! wp_is_application_passwords_available_for_user( $user ) ) {
return new WP_Error(
'application_passwords_disabled_for_user',
__( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' ),
array( 'status' => 501 )
);
}
return $user;
}
/**
* Gets the requested application password for a user.
*
* @since 5.6.0
*
* @param WP_REST_Request $request The request object.
* @return array|WP_Error The application password details if found, a WP_Error otherwise.
*/
protected function get_application_password( $request ) {
$user = $this->get_user( $request );
if ( is_wp_error( $user ) ) {
return $user;
}
$password = WP_Application_Passwords::get_user_application_password( $user->ID, $request['uuid'] );
if ( ! $password ) {
return new WP_Error(
'rest_application_password_not_found',
__( 'Application password not found.' ),
array( 'status' => 404 )
);
}
return $password;
}
/**
* Retrieves the query params for the collections.
*
* @since 5.6.0
*
* @return array Query parameters for the collection.
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
/**
* Retrieves the application password's schema, conforming to JSON Schema.
*
* @since 5.6.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$this->schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'application-password',
'type' => 'object',
'properties' => array(
'uuid' => array(
'description' => __( 'The unique identifier for the application password.' ),
'type' => 'string',
'format' => 'uuid',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'app_id' => array(
'description' => __( 'A UUID provided by the application to uniquely identify it. It is recommended to use an UUID v5 with the URL or DNS namespace.' ),
'type' => 'string',
'format' => 'uuid',
'context' => array( 'view', 'edit', 'embed' ),
),
'name' => array(
'description' => __( 'The name of the application password.' ),
'type' => 'string',
'required' => true,
'context' => array( 'view', 'edit', 'embed' ),
'minLength' => 1,
'pattern' => '.*\S.*',
),
'password' => array(
'description' => __( 'The generated password. Only available after adding an application.' ),
'type' => 'string',
'context' => array( 'edit' ),
'readonly' => true,
),
'created' => array(
'description' => __( 'The GMT date the application password was created.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'last_used' => array(
'description' => __( 'The GMT date the application password was last used.' ),
'type' => array( 'string', 'null' ),
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'last_ip' => array(
'description' => __( 'The IP address the application password was last used by.' ),
'type' => array( 'string', 'null' ),
'format' => 'ip',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $this->schema );
}
}
states'] ) ) {
$tab['preview_states'] = 1;
}
}
}
return $settings_tabs;
}
public function ajax_preview() {
check_ajax_referer( 'wpo_wcpdf_preview', 'security' );
try {
// check permissions
if ( ! $this->user_can_manage_settings() ) {
throw new \Exception( esc_html__( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ), 403 );
}
// get document type
if ( ! empty( $_POST['document_type'] ) ) {
$document_type = sanitize_text_field( $_POST['document_type'] );
} else {
$document_type = 'invoice';
}
// get order ID
if ( ! empty( $_POST['order_id'] ) ) {
$order_id = sanitize_text_field( $_POST['order_id'] );
if ( $document_type == 'credit-note' ) {
// get last refund ID of the order if available
$refund = wc_get_orders(
array(
'type' => 'shop_order_refund',
'parent' => $order_id,
'limit' => 1,
)
);
$order_id = ! empty( $refund ) ? $refund[0]->get_id() : $order_id;
}
} else {
// default to last order
$default_order_id = wc_get_orders( apply_filters( 'wpo_wcpdf_preview_default_order_id_query_args', array(
'limit' => 1,
'return' => 'ids',
'type' => 'shop_order',
), $document_type ) );
$order_id = apply_filters( 'wpo_wcpdf_preview_default_order_id', ! empty( $default_order_id ) ? reset( $default_order_id ) : false );
}
// get PDF data for preview
if ( $order_id ) {
$order = apply_filters( 'wpo_wcpdf_preview_order_object', wc_get_order( $order_id ), $order_id, $document_type );
if ( empty( $order ) ) {
wp_send_json_error( array( 'error' => esc_html__( 'Order not found!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
if ( ! in_array( $order->get_type(), array( 'shop_order', 'shop_order_refund' ) ) ) {
wp_send_json_error( array( 'error' => esc_html__( 'Object found is not an order!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
// process settings data
if ( ! empty( $_POST['data'] ) ) {
// parse form data
parse_str( $_POST['data'], $form_data );
$form_data = stripslashes_deep( $form_data );
foreach ( $form_data as $option_key => $form_settings ) {
if ( apply_filters( 'wpo_wcpdf_preview_filter_option', strpos( $option_key, 'wpo_wcpdf' ) === 0, $option_key ) === false ) {
continue; // not our business
}
// validate option values
$form_settings = WPO_WCPDF()->settings->callbacks->validate( $form_settings );
// filter the options
add_filter( "option_{$option_key}", function( $value, $option ) use ( $form_settings ) {
return maybe_unserialize( $form_settings );
}, 99, 2 );
}
// reload settings
$this->general_settings = get_option( 'wpo_wcpdf_settings_general' );
$this->debug_settings = get_option( 'wpo_wcpdf_settings_debug' );
do_action( 'wpo_wcpdf_preview_after_reload_settings' );
}
$document = wcpdf_get_document( $document_type, $order );
if ( $document ) {
if ( ! $document->exists() ) {
$document->set_date( current_time( 'timestamp', true ) );
$number_store_method = WPO_WCPDF()->settings->get_sequential_number_store_method();
$number_store_name = apply_filters( 'wpo_wcpdf_document_sequential_number_store', "{$document->slug}_number", $document );
$number_store = new Sequential_Number_Store( $number_store_name, $number_store_method );
$document->set_number( $number_store->get_next() );
}
// apply document number formatting
if ( $document_number = $document->get_number( $document->get_type() ) ) {
if ( ! empty( $document->settings['number_format'] ) ) {
foreach ( $document->settings['number_format'] as $key => $value ) {
$document_number->$key = $document->settings['number_format'][$key];
}
}
$document_number->apply_formatting( $document, $order );
}
// preview
$pdf_data = $document->preview_pdf();
wp_send_json_success( array( 'pdf_data' => base64_encode( $pdf_data ) ) );
} else {
wp_send_json_error(
array(
'error' => sprintf(
/* translators: order ID */
esc_html__( 'Document not available for order #%s, try selecting a different order.', 'woocommerce-pdf-invoices-packing-slips' ),
$order_id
)
)
);
}
} else {
wp_send_json_error( array( 'error' => esc_html__( 'No WooCommerce orders found! Please consider adding your first order to see this preview.', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
} catch ( \Throwable $th ) {
wp_send_json_error(
array(
'error' => sprintf(
/* translators: error message */
esc_html__( 'Error trying to generate document: %s', 'woocommerce-pdf-invoices-packing-slips' ),
$th->getMessage()
)
)
);
}
wp_die();
}
public function preview_order_search() {
check_ajax_referer( 'wpo_wcpdf_preview', 'security' );
try {
// check permissions
if ( ! $this->user_can_manage_settings() ) {
throw new \Exception( esc_html__( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ), 403 );
}
if ( ! empty( $_POST['search'] ) && ! empty( $_POST['document_type'] ) ) {
$search = sanitize_text_field( $_POST['search'] );
$document_type = sanitize_text_field( $_POST['document_type'] );
$results = array();
// we have an order ID
if ( is_numeric( $search ) && wc_get_order( $search ) ) {
$results = [ $search ];
// no order ID, let's try with customer
} else {
$default_args = apply_filters( 'wpo_wcpdf_preview_order_search_args', array(
'type' => 'shop_order',
'limit' => 10,
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
), $document_type );
// search by email
if ( is_email( $search ) ) {
$args = array( 'customer' => $search );
$args = $args + $default_args;
$results = wc_get_orders( $args );
// search by names
} else {
$names = array( 'billing_first_name', 'billing_last_name', 'billing_company' );
foreach ( $names as $name ) {
$args = array( $name => $search );
$args = $args + $default_args;
$results = wc_get_orders( $args );
if ( count( $results ) > 0 ) {
break;
}
}
}
}
// filter results
$results = apply_filters( 'wpo_wcpdf_preview_order_search_results', $results, $search, $document_type );
// if we got here we have results!
if ( ! empty( $results ) ) {
$data = array();
foreach ( $results as $value ) {
$order = wc_get_order( $value );
if ( empty( $order ) ) {
continue;
}
$order_id = is_callable( array( $order, 'get_id' ) ) ? $order->get_id() : 0;
$data[$order_id]['order_number'] = is_callable( array( $order, 'get_order_number' ) ) ? $order->get_order_number() : '';
$data[$order_id]['billing_first_name'] = is_callable( array( $order, 'get_billing_first_name' ) ) ? $order->get_billing_first_name() : '';
$data[$order_id]['billing_last_name'] = is_callable( array( $order, 'get_billing_last_name' ) ) ? $order->get_billing_last_name() : '';
$data[$order_id]['billing_company'] = is_callable( array( $order, 'get_billing_company' ) ) ? $order->get_billing_company() : '';
$data[$order_id]['date_created'] = is_callable( array( $order, 'get_date_created' ) ) ? '' . esc_attr__( 'Date', 'woocommerce-pdf-invoices-packing-slips' ) . ': ' . $order->get_date_created()->format( 'Y/m/d' ) : '';
$data[$order_id]['total'] = is_callable( array( $order, 'get_total' ) ) ? '' . esc_attr__( 'Total', 'woocommerce-pdf-invoices-packing-slips' ) . ': ' . wc_price( $order->get_total() ) : '';
}
$data = apply_filters( 'wpo_wcpdf_preview_order_search_data', $data, $results );
wp_send_json_success( $data );
} else {
wp_send_json_error( array( 'error' => esc_html__( 'No order(s) found!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
} else {
wp_send_json_error( array( 'error' => esc_html__( 'An error occurred when trying to process your request!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
} catch ( \Throwable $th ) {
wp_send_json_error(
array(
'error' => sprintf(
/* translators: error message */
esc_html__( 'Error trying to get orders: %s', 'woocommerce-pdf-invoices-packing-slips' ),
$th->getMessage()
)
)
);
}
wp_die();
}
public function add_settings_fields( $settings_fields, $page, $option_group, $option_name ) {
foreach ( $settings_fields as $settings_field ) {
if ( ! isset( $settings_field['callback'] ) ) {
continue;
} elseif ( is_callable( array( $this->callbacks, $settings_field['callback'] ) ) ) {
$callback = array( $this->callbacks, $settings_field['callback'] );
} elseif ( is_callable( $settings_field['callback'] ) ) {
$callback = $settings_field['callback'];
} else {
continue;
}
if ( $settings_field['type'] == 'section' ) {
add_settings_section(
$settings_field['id'],
$settings_field['title'],
$callback,
$page
);
} else {
add_settings_field(
$settings_field['id'],
$settings_field['title'],
$callback,
$page,
$settings_field['section'],
$settings_field['args']
);
// register option separately for singular options
if ( is_string( $settings_field['callback'] ) && $settings_field['callback'] == 'singular_text_element') {
register_setting( $option_group, $settings_field['args']['option_name'], array( $this->callbacks, 'validate' ) );
}
}
}
// $page, $option_group & $option_name are all the same...
register_setting( $option_group, $option_name, array( $this->callbacks, 'validate' ) );
add_filter( 'option_page_capability_'.$page, array( $this, 'user_settings_capability' ) );
}
public function get_common_document_settings() {
$common_settings = array(
'paper_size' => isset( $this->general_settings['paper_size'] ) ? $this->general_settings['paper_size'] : '',
'font_subsetting' => isset( $this->general_settings['font_subsetting'] ) || ( defined("DOMPDF_ENABLE_FONTSUBSETTING") && DOMPDF_ENABLE_FONTSUBSETTING === true ) ? true : false,
'header_logo' => isset( $this->general_settings['header_logo'] ) ? $this->general_settings['header_logo'] : '',
'header_logo_height' => isset( $this->general_settings['header_logo_height'] ) ? $this->general_settings['header_logo_height'] : '',
'shop_name' => isset( $this->general_settings['shop_name'] ) ? $this->general_settings['shop_name'] : '',
'shop_address' => isset( $this->general_settings['shop_address'] ) ? $this->general_settings['shop_address'] : '',
'footer' => isset( $this->general_settings['footer'] ) ? $this->general_settings['footer'] : '',
'extra_1' => isset( $this->general_settings['extra_1'] ) ? $this->general_settings['extra_1'] : '',
'extra_2' => isset( $this->general_settings['extra_2'] ) ? $this->general_settings['extra_2'] : '',
'extra_3' => isset( $this->general_settings['extra_3'] ) ? $this->general_settings['extra_3'] : '',
);
return $common_settings;
}
public function get_document_settings( $document_type ) {
$documents = WPO_WCPDF()->documents->get_documents( 'all' );
foreach ( $documents as $document ) {
if ( $document->get_type() == $document_type ) {
return $document->settings;
}
}
return false;
}
public function get_output_format( $document_type = null ) {
if ( isset( $this->debug_settings['html_output'] ) ) {
$output_format = 'html';
} else {
$output_format = 'pdf';
}
return apply_filters( 'wpo_wcpdf_output_format', $output_format, $document_type );
}
public function get_output_mode() {
if ( isset( WPO_WCPDF()->settings->general_settings['download_display'] ) ) {
switch ( WPO_WCPDF()->settings->general_settings['download_display'] ) {
case 'display':
$output_mode = 'inline';
break;
case 'download':
default:
$output_mode = 'download';
break;
}
} else {
$output_mode = 'download';
}
return $output_mode;
}
public function get_template_path() {
// return default path if no template selected
if ( empty( $this->general_settings['template_path'] ) ) {
return $this->normalize_path( WPO_WCPDF()->plugin_path() . '/templates/Simple' );
}
$installed_templates = $this->get_installed_templates();
$selected_template = $this->general_settings['template_path'];
if ( in_array( $selected_template, $installed_templates ) ) {
return array_search( $selected_template, $installed_templates );
} else {
// unknown template or full template path (filter override)
$template_path = $this->normalize_path( $selected_template );
// add base path, checking if it's not already there
// alternative setups like Bedrock have WP_CONTENT_DIR & ABSPATH separated
if ( defined( 'WP_CONTENT_DIR' ) && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
$base_path = $this->normalize_path( ABSPATH );
} else {
$base_path = $this->normalize_path( WP_CONTENT_DIR );
}
if ( strpos( $template_path, $base_path ) === false ) {
$template_path = $this->normalize_path( $base_path . $template_path );
}
}
return $template_path;
}
public function get_installed_templates( $force_reload = false ) {
// because this method can be called (too) early we load from a cached list in those cases
// this cache is updated each time the template settings are saved/updated
if ( ! did_action( 'wpo_wcpdf_init_documents' ) && ( $cached_template_list = $this->get_template_list_cache() ) ) {
return $cached_template_list;
}
// to save resources on the disk operations we only do this once
if ( $force_reload === false && ! empty ( $this->installed_templates ) ) {
return $this->installed_templates;
}
$installed_templates = array();
// get base paths
$template_base_path = ( function_exists( 'WC' ) && is_callable( array( WC(), 'template_path' ) ) ) ? WC()->template_path() : apply_filters( 'woocommerce_template_path', 'woocommerce/' );
$template_base_path = untrailingslashit( $template_base_path );
$template_paths = array (
// note the order: theme before child-theme, so that child theme is always preferred (overwritten)
'default' => WPO_WCPDF()->plugin_path() . '/templates/',
'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
);
$template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
foreach ( $template_paths as $template_source => $template_path ) {
$dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR );
foreach ( $dirs as $dir ) {
$clean_dir = $this->normalize_path( $dir );
$template_name = basename( $clean_dir );
// let child theme override parent theme
$group = ( $template_source == 'child-theme' ) ? 'theme' : $template_source;
$installed_templates[ $clean_dir ] = "{$group}/{$template_name}" ;
}
}
if ( empty( $installed_templates ) ) {
// fallback to Simple template for servers with glob() disabled
$simple_template_path = $this->normalize_path( $template_paths['default'] . 'Simple' );
$installed_templates[$simple_template_path] = 'default/Simple';
}
$installed_templates = apply_filters( 'wpo_wcpdf_installed_templates', $installed_templates );
$this->installed_templates = $installed_templates;
if ( ! empty( $this->template_list_cache ) && array_diff_assoc( $this->template_list_cache, $this->installed_templates ) ) {
$this->set_template_list_cache( $this->installed_templates );
}
return $installed_templates;
}
public function get_template_list_cache() {
$template_list = get_option( 'wpo_wcpdf_installed_template_paths', array() );
if ( ! empty( $template_list ) ) {
$checked_list = array();
$outdated = false;
// cache could be outdated, so we check whether the folders exist
foreach ( $template_list as $path => $template_id ) {
if ( @is_dir( $path ) ) {
$checked_list[$path] = $template_id; // folder exists
continue;
}
$outdated = true;
// folder does not exist, try replacing base if we can locate wp-content
$wp_content_folder = 'wp-content';
if ( strpos( $path, $wp_content_folder ) !== false && defined( WP_CONTENT_DIR ) ) {
// try wp-content
$relative_path = substr( $path, strrpos( $path, $wp_content_folder ) + strlen( $wp_content_folder ) );
$new_path = WP_CONTENT_DIR . $relative_path;
if ( @is_dir( $new_path ) ) {
$checked_list[$new_path] = $template_id;
}
}
}
if ( $outdated ) {
$this->set_template_list_cache( $checked_list );
}
$this->installed_templates_cache = $checked_list;
return $checked_list;
} else {
return array();
}
}
public function set_template_list_cache( $template_list ) {
$this->template_list_cache = $template_list;
update_option( 'wpo_wcpdf_installed_template_paths', $template_list );
}
public function delete_template_list_cache() {
delete_option( 'wpo_wcpdf_installed_template_paths' );
}
public function general_settings_updated( $old_settings, $settings, $option ) {
if ( is_array( $settings ) && ! empty ( $settings['template_path'] ) ) {
$this->delete_template_list_cache();
$this->set_template_list_cache( $this->get_installed_templates() );
}
}
public function debug_settings_updated( $old_settings, $settings, $option ) {
if ( is_array( $settings ) && is_array( $old_settings ) && empty( $old_settings['pretty_document_links'] ) && ! empty ( $settings['pretty_document_links'] ) ) {
set_transient( 'wpo_wcpdf_flush_rewrite_rules', 'yes', HOUR_IN_SECONDS );
}
}
public function maybe_delete_flush_rewrite_rules_transient() {
if ( get_transient( 'wpo_wcpdf_flush_rewrite_rules' ) ) {
flush_rewrite_rules();
delete_transient( 'wpo_wcpdf_flush_rewrite_rules' );
}
}
public function get_relative_template_path( $absolute_path ) {
if ( defined('WP_CONTENT_DIR') && strpos( WP_CONTENT_DIR, ABSPATH ) !== false ) {
$base_path = $this->normalize_path( ABSPATH );
} else {
$base_path = $this->normalize_path( WP_CONTENT_DIR );
}
return str_replace( $base_path, '', $this->normalize_path( $absolute_path ) );
}
public function normalize_path( $path ) {
return function_exists( 'wp_normalize_path' ) ? wp_normalize_path( $path ) : str_replace('\\','/', $path );
}
public function maybe_migrate_template_paths( $settings_section = null ) {
// bail if no template is selected yet (fresh install)
if ( empty( $this->general_settings['template_path'] ) ) {
return;
}
$installed_templates = $this->get_installed_templates( true );
$selected_template = $this->normalize_path( $this->general_settings['template_path'] );
$template_match = '';
if ( ! in_array( $selected_template, $installed_templates ) && substr_count( $selected_template, '/' ) > 1 ) {
// search for path match
foreach ( $installed_templates as $path => $template_id ) {
$path = $this->normalize_path( $path );
// check if the last part of the path matches
if ( substr( $path, -strlen( $selected_template ) ) === $selected_template ) {
$template_match = $template_id;
break;
}
}
// fallback to template name if no path match
if ( empty( $template_match ) ) {
$template_ids = array_flip( array_unique( array_combine( $installed_templates, array_map( 'basename', $installed_templates ) ) ) );
$template_name = basename( $selected_template );
if ( ! empty ( $template_ids[$template_name] ) ) {
$template_match = $template_ids[$template_name];
}
}
// migrate setting if we have a match
if ( ! empty( $template_match ) ) {
$this->general_settings['template_path'] = $template_match;
update_option( 'wpo_wcpdf_settings_general', $this->general_settings );
/* translators: 1. path, 2. template ID */
wcpdf_log_error( sprintf( 'Template setting migrated from %1$s to %2$s', $path, $template_id ), 'info' );
}
}
}
public function set_number_store() {
check_ajax_referer( "wpo_wcpdf_next_{$_POST['store']}", 'security' );
// check permissions
if ( ! $this->user_can_manage_settings() ) {
die();
}
$number = ! empty( $_POST['number'] ) ? (int) $_POST['number'] : 0;
if ( $number > 0 ) {
$number_store_method = $this->get_sequential_number_store_method();
$number_store = new Sequential_Number_Store( $_POST['store'], $number_store_method );
$number_store->set_next( $number );
echo wp_kses_post( "next number ({$_POST['store']}) set to {$number}" );
}
die();
}
public function get_sequential_number_store_method() {
global $wpdb;
$method = isset( $this->debug_settings['calculate_document_numbers'] ) ? 'calculate' : 'auto_increment';
// safety first - always use calculate when auto_increment_increment is not 1
$row = $wpdb->get_row("SHOW VARIABLES LIKE 'auto_increment_increment'");
if ( ! empty( $row ) && ! empty( $row->Value ) && $row->Value != 1 ) {
$method = 'calculate';
}
return $method;
}
public function schedule_yearly_reset_numbers() {
if ( ! $this->maybe_schedule_yearly_reset_numbers() ) {
return;
}
// checks AS functions existence
if ( ! function_exists( 'as_schedule_single_action' ) || ! function_exists( 'as_get_scheduled_actions' ) ) {
return;
}
$next_year = strval( intval( current_time( 'Y' ) ) + 1 );
$datetime = new \WC_DateTime( "{$next_year}-01-01 00:00:01", new \DateTimeZone( wc_timezone_string() ) );
$lock = new Semaphore( $this->lock_name, $this->lock_time, array( wc_get_logger() ), $this->lock_context );
$hook = 'wpo_wcpdf_schedule_yearly_reset_numbers';
// checks if there are pending actions
$scheduled_actions = count( as_get_scheduled_actions( array(
'hook' => $hook,
'status' => \ActionScheduler_Store::STATUS_PENDING,
) ) );
// if no concurrent actions sets the action
if ( $scheduled_actions < 1 ) {
if ( $lock->lock( $this->lock_retries ) ) {
try {
$action_id = as_schedule_single_action( $datetime->getTimestamp(), $hook );
if ( ! empty( $action_id ) ) {
wcpdf_log_error(
"Yearly document numbers reset scheduled with the action id: {$action_id}",
'info'
);
} else {
wcpdf_log_error(
'The yearly document numbers reset action schedule failed!',
'critical'
);
}
} catch ( \Exception $e ) {
$lock->log( $e, 'critical' );
} catch ( \Error $e ) {
$lock->log( $e, 'critical' );
}
$lock->release();
} else {
$lock->log( "Couldn't get the lock!", 'critical' );
}
} else {
wcpdf_log_error(
"Number of concurrent yearly document numbers reset actions found: {$scheduled_actions}",
'error'
);
if ( function_exists( 'as_unschedule_all_actions' ) ) {
as_unschedule_all_actions( $hook );
}
// reschedule
$this->schedule_yearly_reset_numbers();
}
}
public function yearly_reset_numbers() {
$lock = new Semaphore( $this->lock_name, $this->lock_time, array( wc_get_logger() ), $this->lock_context );
if ( $lock->lock( $this->lock_retries ) ) {
try {
// reset numbers
$documents = WPO_WCPDF()->documents->get_documents( 'all' );
$number_stores = array();
foreach ( $documents as $document ) {
if ( is_callable( array( $document, 'get_sequential_number_store' ) ) ) {
$number_stores[$document->get_type()] = $document->get_sequential_number_store();
}
}
// log reset number events
if ( ! empty( $number_stores ) ) {
foreach( $number_stores as $document_type => $number_store ) {
if ( $number_store->get_next() === 1 ) {
wcpdf_log_error(
"Yearly number reset succeed for '{$document_type}' with database table name: {$number_store->table_name}",
'info'
);
} else {
wcpdf_log_error(
"An error occurred while trying to reset yearly number for '{$document_type}' with database table name: {$number_store->table_name}",
'error'
);
}
}
}
} catch ( \Exception $e ) {
$lock->log( $e, 'critical' );
} catch ( \Error $e ) {
$lock->log( $e, 'critical' );
}
$lock->release();
} else {
$lock->log( "Couldn't get the lock!", 'critical' );
}
// reschedule the action for the next year
$this->schedule_yearly_reset_numbers();
}
public function maybe_schedule_yearly_reset_numbers() {
$schedule = false;
foreach ( WPO_WCPDF()->documents->get_documents( 'all' ) as $document ) {
if ( isset( $document->settings['reset_number_yearly'] ) ) {
$schedule = true;
break;
}
}
// unschedule existing actions
if ( ! $schedule && function_exists( 'as_unschedule_all_actions' ) ) {
as_unschedule_all_actions( 'wpo_wcpdf_schedule_yearly_reset_numbers' );
}
return $schedule;
}
public function yearly_reset_action_is_scheduled() {
$is_scheduled = false;
$scheduled_actions = as_get_scheduled_actions( array(
'hook' => 'wpo_wcpdf_schedule_yearly_reset_numbers',
'status' => \ActionScheduler_Store::STATUS_PENDING,
) );
if ( ! empty( $scheduled_actions ) ) {
$total_actions = count( $scheduled_actions );
if ( $total_actions === 1 ) {
$is_scheduled = true;
} else {
$message = sprintf(
/* translators: total scheduled actions */
__( 'Only 1 scheduled action should exist for the yearly reset of the numbering system, but %s were found', 'woocommerce-pdf-invoices-packing-slips' ),
$total_actions
);
wcpdf_log_error( $message );
}
}
return $is_scheduled;
}
public function get_media_upload_setting_html() {
check_ajax_referer( 'wpo_wcpdf_get_media_upload_setting_html', 'security' );
// check permissions
if ( ! $this->user_can_manage_settings() ) {
wp_send_json_error();
}
// get previous (default) args and preset current
$args = $_POST['args'];
$args['current'] = absint( $_POST['attachment_id'] );
// get settings HTML
ob_start();
$this->callbacks->media_upload( $args );
$html = ob_get_clean();
return wp_send_json_success( $html );
}
public function move_setting_after_id( $settings, $insert_settings, $after_setting_id ) {
$pos = 1; // this is already +1 to insert after the actual pos
foreach ( $settings as $setting ) {
if ( isset( $setting['id'] ) && $setting['id'] == $after_setting_id ) {
$section = $setting['section'];
break;
} else {
$pos++;
}
}
// replace section
if ( isset( $section ) ) {
foreach ( $insert_settings as $key => $insert_setting ) {
$insert_settings[$key]['section'] = $section;
}
} else {
$empty_section = array(
array(
'type' => 'section',
'id' => 'custom',
'title' => '',
'callback' => 'section',
),
);
$insert_settings = array_merge( $empty_section, $insert_settings );
}
// insert our api settings
$new_settings = array_merge( array_slice( $settings, 0, $pos, true ), $insert_settings, array_slice( $settings, $pos, NULL, true ) );
return $new_settings;
}
}
endif; // class_exists