' . esc_html__( 'Settings', 'eazign' ) . '' );
return $links;
}
/**
* Styles and scripts of the Eazign pages.
*
* @param string $hook Page hook.
*/
public static function assets( $hook ) {
if ( false === strpos( $hook, 'eazign' ) ) {
return;
}
wp_enqueue_style( 'eazign-admin', EAZIGN_URL . 'assets/css/admin.css', array(), EAZIGN_VERSION );
wp_enqueue_script( 'eazign-admin', EAZIGN_URL . 'assets/js/admin.js', array(), EAZIGN_VERSION, true );
wp_localize_script(
'eazign-admin',
'eazignAdmin',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'eazign_sync' ),
'i18n' => array(
'working' => __( 'Creating the product… this takes a moment.', 'eazign' ),
'created' => __( 'Designer product created.', 'eazign' ),
'updated' => __( 'Designer product updated.', 'eazign' ),
'view' => __( 'View product', 'eazign' ),
'failed' => __( 'Failed:', 'eazign' ),
/* translators: 1: current file, 2: number of files */
'upload' => __( 'Uploading %1$d of %2$d…', 'eazign' ),
'noFiles' => __( 'Please choose a file.', 'eazign' ),
/* translators: %d: number of variations */
'one' => __( '1 product with %1$d variations', 'eazign' ),
/* translators: 1: number of products, 2: number of variations */
'summary' => __( '%1$d products with %2$d variations', 'eazign' ),
/* translators: 1: done, 2: total */
'running' => __( 'Creating products… %1$d of %2$d', 'eazign' ),
'done' => __( 'Done.', 'eazign' ),
),
)
);
}
// ---- notices -------------------------------------------------------------
/**
* Stores a notice for the next page load.
*
* @param string $type success|error|warning.
* @param string $message Text.
*/
public static function notice( $type, $message ) {
set_transient(
'eazign_notice_' . get_current_user_id(),
array(
'type' => $type,
'message' => $message,
),
60
);
}
/**
* Prints the stored notice.
*/
public static function show_notice() {
$notice = get_transient( 'eazign_notice_' . get_current_user_id() );
if ( ! $notice ) {
return;
}
delete_transient( 'eazign_notice_' . get_current_user_id() );
printf( '
', esc_attr( $notice['type'] ), esc_html( $notice['message'] ) );
}
/**
* Error box for failed API calls.
*
* @param WP_Error $error Error.
*/
private static function error_box( $error ) {
printf( '', esc_html( $error->get_error_message() ) );
}
/**
* Stops admin requests of users without the WooCommerce capability.
*/
private static function guard() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to do this.', 'eazign' ) );
}
}
// ---- connection ------------------------------------------------------------
/**
* Connection page (start page of the plugin).
*/
public static function page_connection() {
$connected = Eazign_Client::is_connected();
if ( $connected ) {
// Keeps name, language and currency of the shop up to date.
$status = Eazign_Client::post(
'status',
array(
'name' => get_bloginfo( 'name' ),
'locale' => get_locale(),
'currency' => get_woocommerce_currency(),
)
);
}
?>
Eazign
terms of service and the privacy policy of Eazign.', 'eazign' ) ),
esc_url( $de ? 'https://eazign.at/wordpress/agb/' : 'https://eazign.at/en/wordpress/terms/' ),
esc_url( $de ? 'https://eazign.at/wordpress/datenschutz/' : 'https://eazign.at/en/wordpress/privacy/' )
);
?>
' . esc_html( Eazign_Client::site()['id'] ) . '' );
?>
blank: product data, colours, sizes, print sides and a base photo.', 'eazign' ), esc_url( admin_url( 'admin.php?page=eazign-blanks' ) ) ) ); ?>
Print files.', 'eazign' ), esc_url( admin_url( 'admin.php?page=eazign-files' ) ) ) ); ?>
' . esc_html( $connected_url ) . '', '' . esc_html( home_url() ) . '' );
?>
home_url() ) );
self::notice( is_wp_error( $result ) ? 'error' : 'success', is_wp_error( $result ) ? $result->get_error_message() : __( 'Shop address updated.', 'eazign' ) );
wp_safe_redirect( admin_url( 'admin.php?page=eazign' ) );
exit;
}
// ---- blanks ----------------------------------------------------------------
/**
* Blank list or, with ?blank=, the blank editor.
*/
public static function page_blanks() {
echo '';
if ( ! Eazign_Client::is_connected() ) {
echo '
' . esc_html__( 'Blanks', 'eazign' ) . ' ';
echo '
' . wp_kses_post( sprintf( /* translators: %s: URL */ __( 'Please connect your shop first.', 'eazign' ), esc_url( admin_url( 'admin.php?page=eazign' ) ) ) ) . '
';
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only navigation.
$id = isset( $_GET['blank'] ) ? sanitize_key( wp_unslash( $_GET['blank'] ) ) : '';
if ( $id ) {
self::blank_editor( $id );
} else {
self::blank_list();
}
echo '';
}
/**
* List of blanks.
*/
private static function blank_list() {
$data = Eazign_Client::get( 'blanks' );
?>
get_id() ) ) . '">' . esc_html( $product->get_name() ) . '';
} else {
echo '–';
}
?>
' . esc_html__( 'Edit blank', 'eazign' ) . '';
echo '← ' . esc_html__( 'All blanks', 'eazign' ) . '
';
if ( is_wp_error( $data ) ) {
self::error_box( $data );
return;
}
$blank = $data['blank'];
$techniques = self::techniques();
$custom = $blank['customSize'];
$product = ! empty( $blank['productId'] ) ? wc_get_product( $blank['productId'] ) : null;
?>
get_id() ) ) . '">' . esc_html( $product->get_name() ) . '' );
?>
·
get_status() ); ?>>
label.
*/
private static function techniques() {
$cached = get_transient( 'eazign_techniques' );
if ( is_array( $cached ) ) {
return $cached;
}
$status = Eazign_Client::post( 'status', array() );
$list = array();
if ( ! is_wp_error( $status ) ) {
foreach ( $status['techniques'] as $t ) {
$list[ $t['id'] ] = $t['label'];
}
set_transient( 'eazign_techniques', $list, DAY_IN_SECONDS );
}
return $list ? $list : array( 'dtf' => 'DTF' );
}
/**
* Add blank.
*/
public static function create_blank() {
self::guard();
check_admin_referer( 'eazign_create_blank' );
$result = Eazign_Client::post( 'blanks', array( 'name' => '' ) );
if ( is_wp_error( $result ) ) {
self::notice( 'error', $result->get_error_message() );
wp_safe_redirect( admin_url( 'admin.php?page=eazign-blanks' ) );
} else {
wp_safe_redirect( admin_url( 'admin.php?page=eazign-blanks&blank=' . rawurlencode( $result['blank']['id'] ) ) );
}
exit;
}
/**
* Save blank (form above), then upload new base photos.
*/
public static function save_blank() {
self::guard();
$id = isset( $_POST['blank'] ) ? sanitize_key( wp_unslash( $_POST['blank'] ) ) : '';
check_admin_referer( 'eazign_save_blank_' . $id );
$back = admin_url( 'admin.php?page=eazign-blanks&blank=' . rawurlencode( $id ) );
$current = Eazign_Client::get( 'blanks/' . rawurlencode( $id ) );
if ( is_wp_error( $current ) ) {
self::notice( 'error', $current->get_error_message() );
wp_safe_redirect( $back );
exit;
}
$text = function ( $key ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- checked above.
return isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
};
$colors = array();
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked above, sanitized per field.
foreach ( (array) ( isset( $_POST['colors'] ) ? wp_unslash( $_POST['colors'] ) : array() ) as $color ) {
$name = sanitize_text_field( $color['name'] ?? '' );
if ( '' !== $name ) {
$colors[] = array(
'name' => $name,
'hex' => sanitize_hex_color( $color['hex'] ?? '' ) ?? '#ffffff',
);
}
}
$views = array();
$indices = array();
$sheets = array();
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked above, sanitized per field.
foreach ( (array) ( isset( $_POST['views'] ) ? wp_unslash( $_POST['views'] ) : array() ) as $i => $view ) {
if ( '__i__' === (string) $i ) {
continue;
}
$remove = ! empty( $view['remove'] );
$views[] = array(
'id' => sanitize_key( $view['id'] ?? '' ),
'remove' => $remove,
'name' => sanitize_text_field( $view['name'] ?? '' ),
'printWidthMm' => (float) ( $view['printWidthMm'] ?? 0 ),
'printHeightMm' => (float) ( $view['printHeightMm'] ?? 0 ),
'areaX' => (float) ( $view['areaX'] ?? 0 ) / 100,
'areaY' => (float) ( $view['areaY'] ?? 0 ) / 100,
'areaWidth' => (float) ( $view['areaWidth'] ?? 0 ) / 100,
'areaHeight' => (float) ( $view['areaHeight'] ?? 0 ) / 100,
'surcharge' => (float) ( $view['surcharge'] ?? 0 ),
);
if ( ! $remove ) {
$indices[] = $i;
if ( ! empty( $view['sheet'] ) ) {
$sheets[ $i ] = true;
}
}
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked above, numbers only.
$custom_in = isset( $_POST['custom'] ) ? (array) wp_unslash( $_POST['custom'] ) : array();
$custom = array( 'enabled' => ! empty( $custom_in['enabled'] ) );
foreach ( array( 'minWidthMm', 'maxWidthMm', 'minHeightMm', 'maxHeightMm', 'pricePer100Cm2' ) as $key ) {
if ( isset( $custom_in[ $key ] ) && '' !== $custom_in[ $key ] ) {
$custom[ $key ] = (float) $custom_in[ $key ];
}
}
$saved = Eazign_Client::post(
'blanks/' . rawurlencode( $id ),
array(
'name' => $text( 'name' ),
'productType' => $text( 'productType' ),
'technique' => $text( 'technique' ),
'basePrice' => (float) str_replace( ',', '.', $text( 'basePrice' ) ),
'sizes' => array_values( array_filter( array_map( 'trim', explode( ',', $text( 'sizes' ) ) ), 'strlen' ) ),
'skuPattern' => $text( 'skuPattern' ),
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- checked above.
'descriptionHtml' => isset( $_POST['descriptionHtml'] ) ? wp_kses_post( wp_unslash( $_POST['descriptionHtml'] ) ) : '',
'priceRules' => $current['blank']['priceRules'],
'customSize' => $custom,
'colors' => $colors,
'views' => $views,
)
);
if ( is_wp_error( $saved ) ) {
self::notice( 'error', $saved->get_error_message() );
wp_safe_redirect( $back );
exit;
}
// Base photos: the saved views come back in the order of the form.
$notices = array();
foreach ( $indices as $position => $i ) {
$view_id = $saved['blank']['views'][ $position ]['id'] ?? '';
$field = 'view_photo_' . $i;
if ( ! $view_id ) {
continue;
}
if ( ! empty( $sheets[ $i ] ) ) {
$result = Eazign_Client::post(
'blanks/' . rawurlencode( $id ) . '/photo',
array(
'viewId' => $view_id,
'sheet' => true,
)
);
} elseif ( ! empty( $_FILES[ $field ]['tmp_name'] ) && is_uploaded_file( $_FILES[ $field ]['tmp_name'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- file path from PHP.
$bytes = file_get_contents( $_FILES[ $field ]['tmp_name'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.Security.ValidatedSanitizedInput
$result = Eazign_Client::post(
'blanks/' . rawurlencode( $id ) . '/photo',
array(
'viewId' => $view_id,
'data' => base64_encode( $bytes ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- binary upload in JSON.
),
array( 'timeout' => 120 )
);
} else {
continue;
}
if ( is_wp_error( $result ) ) {
$notices[] = $result->get_error_message();
} elseif ( ! empty( $result['notice'] ) ) {
$notices[] = $result['notice'];
}
}
self::notice( $notices ? 'warning' : 'success', $notices ? implode( ' · ', $notices ) : __( 'Blank saved.', 'eazign' ) );
wp_safe_redirect( $back );
exit;
}
/**
* Delete blank.
*/
public static function delete_blank() {
self::guard();
$id = isset( $_GET['blank'] ) ? sanitize_key( wp_unslash( $_GET['blank'] ) ) : '';
check_admin_referer( 'eazign_delete_blank_' . $id );
$result = Eazign_Client::post( 'blanks/' . rawurlencode( $id ) . '/delete' );
self::notice( is_wp_error( $result ) ? 'error' : 'success', is_wp_error( $result ) ? $result->get_error_message() : __( 'Blank deleted.', 'eazign' ) );
wp_safe_redirect( admin_url( 'admin.php?page=eazign-blanks' ) );
exit;
}
/**
* Starts the designer product sync (background job at the service).
*/
public static function ajax_sync() {
check_ajax_referer( 'eazign_sync' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to do this.', 'eazign' ) ), 403 );
}
$id = isset( $_POST['blank'] ) ? sanitize_key( wp_unslash( $_POST['blank'] ) ) : '';
$status = isset( $_POST['status'] ) && 'draft' === $_POST['status'] ? 'draft' : 'publish';
$blank = Eazign_Client::get( 'blanks/' . rawurlencode( $id ) );
$result = Eazign_Client::post( 'blanks/' . rawurlencode( $id ) . '/sync', array( 'status' => $status ) );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
// Mockups of the current product: replaced ones are deleted when the job is done.
$product = ! is_wp_error( $blank ) && ! empty( $blank['blank']['productId'] ) ? wc_get_product( (int) $blank['blank']['productId'] ) : null;
if ( $product ) {
set_transient( 'eazign_sync_images_' . sanitize_key( $result['jobId'] ), self::image_ids( $product ), HOUR_IN_SECONDS );
}
wp_send_json_success( $result );
}
/**
* Image attachments of a product and its variations.
*
* @param WC_Product $product Product.
* @return int[]
*/
private static function image_ids( $product ) {
$ids = array_merge( array( (int) $product->get_image_id() ), array_map( 'intval', $product->get_gallery_image_ids() ) );
foreach ( $product->get_children() as $child_id ) {
$child = wc_get_product( $child_id );
if ( $child ) {
$ids[] = (int) $child->get_image_id();
}
}
return array_values( array_unique( array_filter( $ids ) ) );
}
/**
* Progress of the sync.
*/
public static function ajax_sync_status() {
check_ajax_referer( 'eazign_sync' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to do this.', 'eazign' ) ), 403 );
}
$job = isset( $_POST['job'] ) ? sanitize_key( wp_unslash( $_POST['job'] ) ) : '';
$result = Eazign_Client::get( 'sync/' . rawurlencode( $job ) );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
if ( 'done' === $result['status'] && ! empty( $result['productId'] ) ) {
delete_transient( 'eazign_config_' . (int) $result['productId'] );
$product = wc_get_product( (int) $result['productId'] );
if ( $product ) {
$result['permalink'] = $product->get_permalink();
$result['editUrl'] = get_edit_post_link( $product->get_id(), 'raw' );
// Remove the mockups WooCommerce downloaded for the previous version.
$old = get_transient( 'eazign_sync_images_' . $job );
delete_transient( 'eazign_sync_images_' . $job );
if ( is_array( $old ) ) {
foreach ( array_diff( $old, self::image_ids( $product ) ) as $attachment_id ) {
if ( (int) wp_get_post_parent_id( $attachment_id ) === $product->get_id() ) {
wp_delete_attachment( $attachment_id, true );
}
}
}
}
}
wp_send_json_success( $result );
}
// ---- print files -------------------------------------------------------------
/**
* Recent print jobs.
*/
public static function page_files() {
echo '' . esc_html__( 'Print files', 'eazign' ) . ' ';
if ( ! Eazign_Client::is_connected() ) {
echo '
' . wp_kses_post( sprintf( /* translators: %s: URL */ __( 'Please connect your shop first.', 'eazign' ), esc_url( admin_url( 'admin.php?page=eazign' ) ) ) ) . '
';
return;
}
$data = Eazign_Client::get( 'jobs' );
if ( is_wp_error( $data ) ) {
self::error_box( $data );
echo '';
return;
}
echo '';
echo '' . esc_html__( 'Preview', 'eazign' ) . ' ' . esc_html__( 'Order', 'eazign' ) . ' ' . esc_html__( 'Variant', 'eazign' ) . ' ' . esc_html__( 'Status', 'eazign' ) . ' ' . esc_html__( 'Files', 'eazign' ) . ' ';
echo ' ';
if ( empty( $data['jobs'] ) ) {
echo '' . esc_html__( 'No print files yet. They are created when a designed order is paid.', 'eazign' ) . ' ';
}
foreach ( $data['jobs'] as $job ) {
Eazign_Orders::job_row( $job, true );
}
echo '
';
}
}